From c2bf174e9c3f994d83e7e72b6e15c9b26c5b31a2 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 21 Dec 2018 20:25:29 +0300 Subject: Start splitting completion into components --- .../src/completion/complete_fn_param.rs | 107 ++++++++++ .../src/completion/complete_keywords.rs | 206 +++++++++++++++++++ .../src/completion/reference_completion.rs | 225 +-------------------- 3 files changed, 316 insertions(+), 222 deletions(-) create mode 100644 crates/ra_analysis/src/completion/complete_fn_param.rs create mode 100644 crates/ra_analysis/src/completion/complete_keywords.rs (limited to 'crates/ra_analysis/src/completion') diff --git a/crates/ra_analysis/src/completion/complete_fn_param.rs b/crates/ra_analysis/src/completion/complete_fn_param.rs new file mode 100644 index 000000000..d05a5e3cf --- /dev/null +++ b/crates/ra_analysis/src/completion/complete_fn_param.rs @@ -0,0 +1,107 @@ +use ra_syntax::{ + algo::{ + visit::{visitor_ctx, VisitorCtx} + }, + ast, + AstNode, +}; +use rustc_hash::{FxHashMap}; + +use crate::{ + completion::{SyntaxContext, Completions, CompletionKind, CompletionItem}, +}; + +/// Complete repeated parametes, both name and type. For example, if all +/// functions in a file have a `spam: &mut Spam` parameter, a completion with +/// `spam: &mut Spam` insert text/label and `spam` lookup string will be +/// suggested. +pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &SyntaxContext) { + if !ctx.is_param { + return; + } + + let mut params = FxHashMap::default(); + for node in ctx.leaf.ancestors() { + let _ = visitor_ctx(&mut params) + .visit::(process) + .visit::(process) + .accept(node); + } + params + .into_iter() + .filter_map(|(label, (count, param))| { + let lookup = param.pat()?.syntax().text().to_string(); + if count < 2 { + None + } else { + Some((label, lookup)) + } + }) + .for_each(|(label, lookup)| { + CompletionItem::new(label) + .lookup_by(lookup) + .kind(CompletionKind::Magic) + .add_to(acc) + }); + + fn process<'a, N: ast::FnDefOwner<'a>>( + node: N, + params: &mut FxHashMap)>, + ) { + node.functions() + .filter_map(|it| it.param_list()) + .flat_map(|it| it.params()) + .for_each(|param| { + let text = param.syntax().text().to_string(); + params.entry(text).or_insert((0, param)).0 += 1; + }) + } +} + +#[cfg(test)] +mod tests { + use crate::completion::*; + + fn check_magic_completion(code: &str, expected_completions: &str) { + check_completion(code, expected_completions, CompletionKind::Magic); + } + + #[test] + fn test_param_completion_last_param() { + check_magic_completion( + r" + fn foo(file_id: FileId) {} + fn bar(file_id: FileId) {} + fn baz(file<|>) {} + ", + r#"file_id "file_id: FileId""#, + ); + } + + #[test] + fn test_param_completion_nth_param() { + check_magic_completion( + r" + fn foo(file_id: FileId) {} + fn bar(file_id: FileId) {} + fn baz(file<|>, x: i32) {} + ", + r#"file_id "file_id: FileId""#, + ); + } + + #[test] + fn test_param_completion_trait_param() { + check_magic_completion( + r" + pub(crate) trait SourceRoot { + pub fn contains(&self, file_id: FileId) -> bool; + pub fn module_map(&self) -> &ModuleMap; + pub fn lines(&self, file_id: FileId) -> &LineIndex; + pub fn syntax(&self, file<|>) + } + ", + r#"file_id "file_id: FileId""#, + ); + } +} diff --git a/crates/ra_analysis/src/completion/complete_keywords.rs b/crates/ra_analysis/src/completion/complete_keywords.rs new file mode 100644 index 000000000..d0a6ec19e --- /dev/null +++ b/crates/ra_analysis/src/completion/complete_keywords.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/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs index c2ac95453..15ff4c5dd 100644 --- a/crates/ra_analysis/src/completion/reference_completion.rs +++ b/crates/ra_analysis/src/completion/reference_completion.rs @@ -1,9 +1,7 @@ use rustc_hash::{FxHashSet}; -use ra_editor::find_node_at_offset; use ra_syntax::{ - algo::visit::{visitor, Visitor}, SourceFileNode, AstNode, - ast::{self, LoopBodyOwner}, + ast, SyntaxKind::*, }; use hir::{ @@ -21,7 +19,7 @@ pub(super) fn completions( acc: &mut Completions, db: &RootDatabase, module: &hir::Module, - file: &SourceFileNode, + _file: &SourceFileNode, name_ref: ast::NameRef, ) -> Cancelable<()> { let kind = match classify_name_ref(name_ref) { @@ -34,7 +32,7 @@ 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_keywords(&file, fn_def, name_ref, acc); complete_expr_snippets(acc); } @@ -182,91 +180,6 @@ fn ${1:feature}() { .add_to(acc); } -fn complete_expr_keywords( - file: &SourceFileNode, - fn_def: ast::FnDef, - name_ref: ast::NameRef, - acc: &mut Completions, -) { - 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 let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) { - if let Some(if_expr) = find_node_at_offset::(file.syntax(), off) { - if if_expr.syntax().range().end() < name_ref.syntax().range().start() { - acc.add(keyword("else", "else {$0}")); - acc.add(keyword("else if", "else if $0 {}")); - } - } - } - if is_in_loop_body(name_ref) { - acc.add(keyword("continue", "continue")); - acc.add(keyword("break", "break")); - } - acc.add_all(complete_return(fn_def, name_ref)); -} - -fn is_in_loop_body(name_ref: ast::NameRef) -> bool { - for node in name_ref.syntax().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 name_ref - .syntax() - .range() - .is_subrange(&body.syntax().range()) - { - return true; - } - } - } - false -} - -fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option { - // let is_last_in_block = name_ref.syntax().ancestors().filter_map(ast::Expr::cast) - // .next() - // .and_then(|it| it.syntax().parent()) - // .and_then(ast::Block::cast) - // .is_some(); - - // if is_last_in_block { - // return None; - // } - - let is_stmt = match name_ref - .syntax() - .ancestors() - .filter_map(ast::ExprStmt::cast) - .next() - { - None => false, - Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(), - }; - 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() -} - fn complete_expr_snippets(acc: &mut Completions) { CompletionItem::new("pd") .snippet("eprintln!(\"$0 = {:?}\", $0);") @@ -286,10 +199,6 @@ mod tests { check_completion(code, expected_completions, CompletionKind::Reference); } - fn check_keyword_completion(code: &str, expected_completions: &str) { - check_completion(code, expected_completions, CompletionKind::Keyword); - } - fn check_snippet_completion(code: &str, expected_completions: &str) { check_completion(code, expected_completions, CompletionKind::Snippet); } @@ -470,134 +379,6 @@ mod tests { ); } - #[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" - "#, - ); - } - #[test] fn completes_snippets_in_expressions() { check_snippet_completion( -- cgit v1.2.3