mod completion_item; mod reference_completion; use ra_editor::find_node_at_offset; use ra_text_edit::AtomTextEdit; use ra_syntax::{ algo::visit::{visitor_ctx, VisitorCtx}, ast, AstNode, SyntaxNodeRef, }; use ra_db::SyntaxDatabase; use rustc_hash::{FxHashMap}; use hir::source_binder; use crate::{ db, Cancelable, FilePosition }; pub use crate::completion::completion_item::{CompletionItem, InsertText}; pub(crate) fn completions( db: &db::RootDatabase, position: FilePosition, ) -> Cancelable>> { let original_file = db.source_file(position.file_id); // Insert a fake ident to get a valid parse tree let file = { let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); original_file.reparse(&edit) }; let module = ctry!(source_binder::module_from_position(db, position)?); let mut res = Vec::new(); let mut has_completions = false; // First, let's try to complete a reference to some declaration. if let Some(name_ref) = find_node_at_offset::(file.syntax(), position.offset) { has_completions = true; reference_completion::completions(&mut res, db, &module, &file, name_ref)?; // special case, `trait T { fn foo(i_am_a_name_ref) {} }` if is_node::(name_ref.syntax()) { param_completions(&mut res, name_ref.syntax()); } } // Otherwise, if this is a declaration, use heuristics to suggest a name. if let Some(name) = find_node_at_offset::(file.syntax(), position.offset) { if is_node::(name.syntax()) { has_completions = true; param_completions(&mut res, name.syntax()); } } let res = if has_completions { Some(res) } else { None }; Ok(res) } /// 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. fn param_completions(acc: &mut Vec, ctx: SyntaxNodeRef) { let mut params = FxHashMap::default(); for node in ctx.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).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; }) } } fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { match node.ancestors().filter_map(N::cast).next() { None => false, Some(n) => n.syntax().range() == node.range(), } } #[cfg(test)] mod tests { use test_utils::assert_eq_dbg; use crate::mock_analysis::single_file_with_position; use super::*; fn is_snippet(completion_item: &CompletionItem) -> bool { match completion_item.insert_text() { InsertText::Snippet { .. } => true, _ => false, } } fn check_scope_completion(code: &str, expected_completions: &str) { let (analysis, position) = single_file_with_position(code); let completions = completions(&analysis.imp.db, position) .unwrap() .unwrap() .into_iter() .filter(|c| !is_snippet(c)) .collect::>(); assert_eq_dbg(expected_completions, &completions); } fn check_snippet_completion(code: &str, expected_completions: &str) { let (analysis, position) = single_file_with_position(code); let completions = completions(&analysis.imp.db, position) .unwrap() .unwrap() .into_iter() .filter(is_snippet) .collect::>(); assert_eq_dbg(expected_completions, &completions); } #[test] fn test_completion_let_scope() { check_scope_completion( r" fn quux(x: i32) { let y = 92; 1 + <|>; let z = (); } ", r#"[CompletionItem { label: "y", lookup: None, snippet: None }, CompletionItem { label: "x", lookup: None, snippet: None }, CompletionItem { label: "quux", lookup: None, snippet: None }]"#, ); } #[test] fn test_completion_if_let_scope() { check_scope_completion( r" fn quux() { if let Some(x) = foo() { let y = 92; }; if let Some(a) = bar() { let b = 62; 1 + <|> } } ", r#"[CompletionItem { label: "b", lookup: None, snippet: None }, CompletionItem { label: "a", lookup: None, snippet: None }, CompletionItem { label: "quux", lookup: None, snippet: None }]"#, ); } #[test] fn test_completion_for_scope() { check_scope_completion( r" fn quux() { for x in &[1, 2, 3] { <|> } } ", r#"[CompletionItem { label: "x", lookup: None, snippet: None }, CompletionItem { label: "quux", lookup: None, snippet: None }]"#, ); } #[test] fn test_completion_mod_scope() { check_scope_completion( r" struct Foo; enum Baz {} fn quux() { <|> } ", r#"[CompletionItem { label: "quux", lookup: None, snippet: None }, CompletionItem { label: "Foo", lookup: None, snippet: None }, CompletionItem { label: "Baz", lookup: None, snippet: None }]"#, ); } #[test] fn test_completion_mod_scope_no_self_use() { check_scope_completion( r" use foo<|>; ", r#"[]"#, ); } #[test] fn test_completion_self_path() { check_scope_completion( r" use self::m::<|>; mod m { struct Bar; } ", r#"[CompletionItem { label: "Bar", lookup: None, snippet: None }]"#, ); } #[test] fn test_completion_mod_scope_nested() { check_scope_completion( r" struct Foo; mod m { struct Bar; fn quux() { <|> } } ", r#"[CompletionItem { label: "quux", lookup: None, snippet: None }, CompletionItem { label: "Bar", lookup: None, snippet: None }]"#, ); } #[test] fn test_complete_type() { check_scope_completion( r" struct Foo; fn x() -> <|> ", r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, CompletionItem { label: "x", lookup: None, snippet: None }]"#, ) } #[test] fn test_complete_shadowing() { check_scope_completion( r" fn foo() -> { let bar = 92; { let bar = 62; <|> } } ", r#"[CompletionItem { label: "bar", lookup: None, snippet: None }, CompletionItem { label: "foo", lookup: None, snippet: None }]"#, ) } #[test] fn test_complete_self() { check_scope_completion( r" impl S { fn foo(&self) { <|> } } ", r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#, ) } #[test] fn test_completion_kewords() { check_snippet_completion(r" fn quux() { <|> } ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, CompletionItem { label: "return", lookup: None, snippet: Some("return") }, CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); } #[test] fn test_completion_else() { check_snippet_completion(r" fn quux() { if true { () } <|> } ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, CompletionItem { label: "else", lookup: None, snippet: Some("else {$0}") }, CompletionItem { label: "else if", lookup: None, snippet: Some("else if $0 {}") }, CompletionItem { label: "return", lookup: None, snippet: Some("return") }, CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); } #[test] fn test_completion_return_value() { check_snippet_completion(r" fn quux() -> i32 { <|> 92 } ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, CompletionItem { label: "return", lookup: None, snippet: Some("return $0;") }, CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); check_snippet_completion(r" fn quux() { <|> 92 } ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, CompletionItem { label: "return", lookup: None, snippet: Some("return;") }, CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); } #[test] fn test_completion_return_no_stmt() { check_snippet_completion(r" fn quux() -> i32 { match () { () => <|> } } ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); } #[test] fn test_continue_break_completion() { check_snippet_completion(r" fn quux() -> i32 { loop { <|> } } ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, CompletionItem { label: "continue", lookup: None, snippet: Some("continue") }, CompletionItem { label: "break", lookup: None, snippet: Some("break") }, CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); check_snippet_completion(r" fn quux() -> i32 { loop { || { <|> } } } ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); } #[test] fn test_param_completion_last_param() { check_scope_completion(r" fn foo(file_id: FileId) {} fn bar(file_id: FileId) {} fn baz(file<|>) {} ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); } #[test] fn test_param_completion_nth_param() { check_scope_completion(r" fn foo(file_id: FileId) {} fn bar(file_id: FileId) {} fn baz(file<|>, x: i32) {} ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); } #[test] fn test_param_completion_trait_param() { check_scope_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#"[CompletionItem { label: "self", lookup: None, snippet: None }, CompletionItem { label: "SourceRoot", lookup: None, snippet: None }, CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); } #[test] fn test_item_snippets() { // 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##"[CompletionItem { label: "Test function", lookup: Some("tfn"), snippet: Some("#[test]\nfn ${1:feature}() {\n $0\n}") }, CompletionItem { label: "pub(crate)", lookup: None, snippet: Some("pub(crate) $0") }]"##, ); } }