From cbe67339df2bbcb17e12ad74e8b8cd53baffb9f7 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 21 Dec 2018 20:55:00 +0300 Subject: more completion components --- crates/ra_analysis/src/completion.rs | 39 ++-- .../ra_analysis/src/completion/complete_keyword.rs | 206 +++++++++++++++++++++ .../src/completion/complete_keywords.rs | 206 --------------------- .../ra_analysis/src/completion/complete_snippet.rs | 78 ++++++++ .../src/completion/reference_completion.rs | 82 +------- 5 files changed, 310 insertions(+), 301 deletions(-) create mode 100644 crates/ra_analysis/src/completion/complete_keyword.rs delete mode 100644 crates/ra_analysis/src/completion/complete_keywords.rs create mode 100644 crates/ra_analysis/src/completion/complete_snippet.rs diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index 39066d51f..883b3e851 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs @@ -2,7 +2,8 @@ mod completion_item; mod reference_completion; mod complete_fn_param; -mod complete_keywords; +mod complete_keyword; +mod complete_snippet; use ra_editor::find_node_at_offset; use ra_text_edit::AtomTextEdit; @@ -49,7 +50,9 @@ pub(crate) fn completions( let ctx = ctry!(SyntaxContext::new(&original_file, position.offset)); complete_fn_param::complete_fn_param(&mut acc, &ctx); - complete_keywords::complete_expr_keyword(&mut acc, &ctx); + complete_keyword::complete_expr_keyword(&mut acc, &ctx); + complete_snippet::complete_expr_snippet(&mut acc, &ctx); + complete_snippet::complete_item_snippet(&mut acc, &ctx); Ok(Some(acc)) } @@ -61,10 +64,12 @@ pub(super) struct SyntaxContext<'a> { leaf: SyntaxNodeRef<'a>, enclosing_fn: Option>, is_param: bool, - /// a single-indent path, like `foo`. + /// A single-indent path, like `foo`. is_trivial_path: bool, after_if: bool, is_stmt: bool, + /// Something is typed at the "top" level, in module or impl/trait. + is_new_item: bool, } impl SyntaxContext<'_> { @@ -77,6 +82,7 @@ impl SyntaxContext<'_> { is_trivial_path: false, after_if: false, is_stmt: false, + is_new_item: false, }; ctx.fill(original_file, offset); Some(ctx) @@ -112,17 +118,22 @@ impl SyntaxContext<'_> { } } fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) { - // let name_range = name_ref.syntax().range(); - // let top_node = name_ref - // .syntax() - // .ancestors() - // .take_while(|it| it.range() == name_range) - // .last() - // .unwrap(); - // match top_node.parent().map(|it| it.kind()) { - // Some(SOURCE_FILE) | Some(ITEM_LIST) => return Some(NameRefKind::BareIdentInMod), - // _ => (), - // } + let name_range = name_ref.syntax().range(); + let top_node = name_ref + .syntax() + .ancestors() + .take_while(|it| it.range() == name_range) + .last() + .unwrap(); + + match top_node.parent().map(|it| it.kind()) { + Some(SOURCE_FILE) | Some(ITEM_LIST) => { + self.is_new_item = true; + return; + } + _ => (), + } + let parent = match name_ref.syntax().parent() { Some(it) => it, None => return, diff --git a/crates/ra_analysis/src/completion/complete_keyword.rs b/crates/ra_analysis/src/completion/complete_keyword.rs new file mode 100644 index 000000000..d0a6ec19e --- /dev/null +++ b/crates/ra_analysis/src/completion/complete_keyword.rs @@ -0,0 +1,206 @@ +use ra_syntax::{ + algo::visit::{visitor, Visitor}, + AstNode, + ast::{self, LoopBodyOwner}, + SyntaxKind::*, SyntaxNodeRef, +}; + +use crate::{ + completion::{SyntaxContext, CompletionItem, Completions, CompletionKind::*}, +}; + +pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &SyntaxContext) { + if !ctx.is_trivial_path { + return; + } + let fn_def = match ctx.enclosing_fn { + Some(it) => it, + None => return, + }; + acc.add(keyword("if", "if $0 {}")); + acc.add(keyword("match", "match $0 {}")); + acc.add(keyword("while", "while $0 {}")); + acc.add(keyword("loop", "loop {$0}")); + + if ctx.after_if { + acc.add(keyword("else", "else {$0}")); + acc.add(keyword("else if", "else if $0 {}")); + } + if is_in_loop_body(ctx.leaf) { + acc.add(keyword("continue", "continue")); + acc.add(keyword("break", "break")); + } + acc.add_all(complete_return(fn_def, ctx.is_stmt)); +} + +fn is_in_loop_body(leaf: SyntaxNodeRef) -> bool { + for node in leaf.ancestors() { + if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { + break; + } + let loop_body = visitor() + .visit::(LoopBodyOwner::loop_body) + .visit::(LoopBodyOwner::loop_body) + .visit::(LoopBodyOwner::loop_body) + .accept(node); + if let Some(Some(body)) = loop_body { + if leaf.range().is_subrange(&body.syntax().range()) { + return true; + } + } + } + false +} + +fn complete_return(fn_def: ast::FnDef, is_stmt: bool) -> Option { + let snip = match (is_stmt, fn_def.ret_type().is_some()) { + (true, true) => "return $0;", + (true, false) => "return;", + (false, true) => "return $0", + (false, false) => "return", + }; + Some(keyword("return", snip)) +} + +fn keyword(kw: &str, snippet: &str) -> CompletionItem { + CompletionItem::new(kw) + .kind(Keyword) + .snippet(snippet) + .build() +} + +#[cfg(test)] +mod tests { + use crate::completion::{CompletionKind, check_completion}; + fn check_keyword_completion(code: &str, expected_completions: &str) { + check_completion(code, expected_completions, CompletionKind::Keyword); + } + + #[test] + fn test_completion_kewords() { + check_keyword_completion( + r" + fn quux() { + <|> + } + ", + r#" + if "if $0 {}" + match "match $0 {}" + while "while $0 {}" + loop "loop {$0}" + return "return" + "#, + ); + } + + #[test] + fn test_completion_else() { + check_keyword_completion( + r" + fn quux() { + if true { + () + } <|> + } + ", + r#" + if "if $0 {}" + match "match $0 {}" + while "while $0 {}" + loop "loop {$0}" + else "else {$0}" + else if "else if $0 {}" + return "return" + "#, + ); + } + + #[test] + fn test_completion_return_value() { + check_keyword_completion( + r" + fn quux() -> i32 { + <|> + 92 + } + ", + r#" + if "if $0 {}" + match "match $0 {}" + while "while $0 {}" + loop "loop {$0}" + return "return $0;" + "#, + ); + check_keyword_completion( + r" + fn quux() { + <|> + 92 + } + ", + r#" + if "if $0 {}" + match "match $0 {}" + while "while $0 {}" + loop "loop {$0}" + return "return;" + "#, + ); + } + + #[test] + fn test_completion_return_no_stmt() { + check_keyword_completion( + r" + fn quux() -> i32 { + match () { + () => <|> + } + } + ", + r#" + if "if $0 {}" + match "match $0 {}" + while "while $0 {}" + loop "loop {$0}" + return "return $0" + "#, + ); + } + + #[test] + fn test_continue_break_completion() { + check_keyword_completion( + r" + fn quux() -> i32 { + loop { <|> } + } + ", + r#" + if "if $0 {}" + match "match $0 {}" + while "while $0 {}" + loop "loop {$0}" + continue "continue" + break "break" + return "return $0" + "#, + ); + check_keyword_completion( + r" + fn quux() -> i32 { + loop { || { <|> } } + } + ", + r#" + if "if $0 {}" + match "match $0 {}" + while "while $0 {}" + loop "loop {$0}" + return "return $0" + "#, + ); + } +} diff --git a/crates/ra_analysis/src/completion/complete_keywords.rs b/crates/ra_analysis/src/completion/complete_keywords.rs deleted file mode 100644 index d0a6ec19e..000000000 --- a/crates/ra_analysis/src/completion/complete_keywords.rs +++ /dev/null @@ -1,206 +0,0 @@ -use ra_syntax::{ - algo::visit::{visitor, Visitor}, - AstNode, - ast::{self, LoopBodyOwner}, - SyntaxKind::*, SyntaxNodeRef, -}; - -use crate::{ - completion::{SyntaxContext, CompletionItem, Completions, CompletionKind::*}, -}; - -pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &SyntaxContext) { - if !ctx.is_trivial_path { - return; - } - let fn_def = match ctx.enclosing_fn { - Some(it) => it, - None => return, - }; - acc.add(keyword("if", "if $0 {}")); - acc.add(keyword("match", "match $0 {}")); - acc.add(keyword("while", "while $0 {}")); - acc.add(keyword("loop", "loop {$0}")); - - if ctx.after_if { - acc.add(keyword("else", "else {$0}")); - acc.add(keyword("else if", "else if $0 {}")); - } - if is_in_loop_body(ctx.leaf) { - acc.add(keyword("continue", "continue")); - acc.add(keyword("break", "break")); - } - acc.add_all(complete_return(fn_def, ctx.is_stmt)); -} - -fn is_in_loop_body(leaf: SyntaxNodeRef) -> bool { - for node in leaf.ancestors() { - if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { - break; - } - let loop_body = visitor() - .visit::(LoopBodyOwner::loop_body) - .visit::(LoopBodyOwner::loop_body) - .visit::(LoopBodyOwner::loop_body) - .accept(node); - if let Some(Some(body)) = loop_body { - if leaf.range().is_subrange(&body.syntax().range()) { - return true; - } - } - } - false -} - -fn complete_return(fn_def: ast::FnDef, is_stmt: bool) -> Option { - let snip = match (is_stmt, fn_def.ret_type().is_some()) { - (true, true) => "return $0;", - (true, false) => "return;", - (false, true) => "return $0", - (false, false) => "return", - }; - Some(keyword("return", snip)) -} - -fn keyword(kw: &str, snippet: &str) -> CompletionItem { - CompletionItem::new(kw) - .kind(Keyword) - .snippet(snippet) - .build() -} - -#[cfg(test)] -mod tests { - use crate::completion::{CompletionKind, check_completion}; - fn check_keyword_completion(code: &str, expected_completions: &str) { - check_completion(code, expected_completions, CompletionKind::Keyword); - } - - #[test] - fn test_completion_kewords() { - check_keyword_completion( - r" - fn quux() { - <|> - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - return "return" - "#, - ); - } - - #[test] - fn test_completion_else() { - check_keyword_completion( - r" - fn quux() { - if true { - () - } <|> - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - else "else {$0}" - else if "else if $0 {}" - return "return" - "#, - ); - } - - #[test] - fn test_completion_return_value() { - check_keyword_completion( - r" - fn quux() -> i32 { - <|> - 92 - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - return "return $0;" - "#, - ); - check_keyword_completion( - r" - fn quux() { - <|> - 92 - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - return "return;" - "#, - ); - } - - #[test] - fn test_completion_return_no_stmt() { - check_keyword_completion( - r" - fn quux() -> i32 { - match () { - () => <|> - } - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - return "return $0" - "#, - ); - } - - #[test] - fn test_continue_break_completion() { - check_keyword_completion( - r" - fn quux() -> i32 { - loop { <|> } - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - continue "continue" - break "break" - return "return $0" - "#, - ); - check_keyword_completion( - r" - fn quux() -> i32 { - loop { || { <|> } } - } - ", - r#" - if "if $0 {}" - match "match $0 {}" - while "while $0 {}" - loop "loop {$0}" - return "return $0" - "#, - ); - } -} diff --git a/crates/ra_analysis/src/completion/complete_snippet.rs b/crates/ra_analysis/src/completion/complete_snippet.rs new file mode 100644 index 000000000..5d6cc5dc9 --- /dev/null +++ b/crates/ra_analysis/src/completion/complete_snippet.rs @@ -0,0 +1,78 @@ +use crate::{ + completion::{CompletionItem, Completions, CompletionKind::*, SyntaxContext}, +}; + +pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &SyntaxContext) { + if !(ctx.is_trivial_path && ctx.enclosing_fn.is_some()) { + return; + } + CompletionItem::new("pd") + .snippet("eprintln!(\"$0 = {:?}\", $0);") + .kind(Snippet) + .add_to(acc); + CompletionItem::new("ppd") + .snippet("eprintln!(\"$0 = {:#?}\", $0);") + .kind(Snippet) + .add_to(acc); +} + +pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &SyntaxContext) { + if !ctx.is_new_item { + return; + } + CompletionItem::new("Test function") + .lookup_by("tfn") + .snippet( + "\ +#[test] +fn ${1:feature}() { + $0 +}", + ) + .kind(Snippet) + .add_to(acc); + CompletionItem::new("pub(crate)") + .snippet("pub(crate) $0") + .kind(Snippet) + .add_to(acc); +} + +#[cfg(test)] +mod tests { + use crate::completion::{CompletionKind, check_completion}; + fn check_snippet_completion(code: &str, expected_completions: &str) { + check_completion(code, expected_completions, CompletionKind::Snippet); + } + + #[test] + fn completes_snippets_in_expressions() { + check_snippet_completion( + r"fn foo(x: i32) { <|> }", + r##" + pd "eprintln!(\"$0 = {:?}\", $0);" + ppd "eprintln!(\"$0 = {:#?}\", $0);" + "##, + ); + } + + #[test] + fn completes_snippets_in_items() { + // check_snippet_completion(r" + // <|> + // ", + // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##, + // ); + check_snippet_completion( + r" + #[cfg(test)] + mod tests { + <|> + } + ", + r##" + tfn "Test function" "#[test]\nfn ${1:feature}() {\n $0\n}" + pub(crate) "pub(crate) $0" + "##, + ); + } +} diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs index 15ff4c5dd..46d381927 100644 --- a/crates/ra_analysis/src/completion/reference_completion.rs +++ b/crates/ra_analysis/src/completion/reference_completion.rs @@ -32,8 +32,6 @@ pub(super) fn completions( if let Some(fn_def) = enclosing_fn { let scopes = FnScopes::new(fn_def); complete_fn(name_ref, &scopes, acc); - // complete_expr_keywords(&file, fn_def, name_ref, acc); - complete_expr_snippets(acc); } let module_scope = module.scope(db)?; @@ -56,19 +54,7 @@ pub(super) fn completions( }); } NameRefKind::Path(path) => complete_path(acc, db, module, path)?, - NameRefKind::BareIdentInMod => { - let name_range = name_ref.syntax().range(); - let top_node = name_ref - .syntax() - .ancestors() - .take_while(|it| it.range() == name_range) - .last() - .unwrap(); - match top_node.parent().map(|it| it.kind()) { - Some(SOURCE_FILE) | Some(ITEM_LIST) => complete_mod_item_snippets(acc), - _ => (), - } - } + NameRefKind::BareIdentInMod => (), } Ok(()) } @@ -162,35 +148,6 @@ fn complete_path( Ok(()) } -fn complete_mod_item_snippets(acc: &mut Completions) { - CompletionItem::new("Test function") - .lookup_by("tfn") - .snippet( - "\ -#[test] -fn ${1:feature}() { - $0 -}", - ) - .kind(Snippet) - .add_to(acc); - CompletionItem::new("pub(crate)") - .snippet("pub(crate) $0") - .kind(Snippet) - .add_to(acc); -} - -fn complete_expr_snippets(acc: &mut Completions) { - CompletionItem::new("pd") - .snippet("eprintln!(\"$0 = {:?}\", $0);") - .kind(Snippet) - .add_to(acc); - CompletionItem::new("ppd") - .snippet("eprintln!(\"$0 = {:#?}\", $0);") - .kind(Snippet) - .add_to(acc); -} - #[cfg(test)] mod tests { use crate::completion::{CompletionKind, check_completion}; @@ -199,10 +156,6 @@ mod tests { check_completion(code, expected_completions, CompletionKind::Reference); } - fn check_snippet_completion(code: &str, expected_completions: &str) { - check_completion(code, expected_completions, CompletionKind::Snippet); - } - #[test] fn test_completion_let_scope() { check_reference_completion( @@ -378,37 +331,4 @@ mod tests { "Spam", ); } - - #[test] - fn completes_snippets_in_expressions() { - check_snippet_completion( - r"fn foo(x: i32) { <|> }", - r##" - pd "eprintln!(\"$0 = {:?}\", $0);" - ppd "eprintln!(\"$0 = {:#?}\", $0);" - "##, - ); - } - - #[test] - fn completes_snippets_in_items() { - // check_snippet_completion(r" - // <|> - // ", - // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##, - // ); - check_snippet_completion( - r" - #[cfg(test)] - mod tests { - <|> - } - ", - r##" - tfn "Test function" "#[test]\nfn ${1:feature}() {\n $0\n}" - pub(crate) "pub(crate) $0" - "##, - ); - } - } -- cgit v1.2.3