From 74406ca8ea45df8b44cb38ecba4a5b561038c4a0 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 21 Dec 2018 14:02:14 +0300 Subject: introduce completion_item module --- crates/ra_analysis/src/completion.rs | 23 ++++------- .../ra_analysis/src/completion/completion_item.rs | 44 ++++++++++++++++++++++ .../src/completion/reference_completion.rs | 8 ++-- 3 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 crates/ra_analysis/src/completion/completion_item.rs (limited to 'crates') diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index f480af611..ed1b6dd0c 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs @@ -1,3 +1,4 @@ +mod completion_item; mod reference_completion; use ra_editor::find_node_at_offset; @@ -17,15 +18,7 @@ use crate::{ Cancelable, FilePosition }; -#[derive(Debug)] -pub struct CompletionItem { - /// What user sees in pop-up - pub label: String, - /// What string is used for filtering, defaults to label - pub lookup: Option, - /// What is inserted, defaults to label - pub snippet: Option, -} +pub use crate::completion::completion_item::CompletionItem; pub(crate) fn completions( db: &db::RootDatabase, @@ -63,6 +56,10 @@ pub(crate) fn completions( 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(ctx: SyntaxNodeRef, acc: &mut Vec) { let mut params = FxHashMap::default(); for node in ctx.ancestors() { @@ -81,13 +78,7 @@ fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec) { Some((label, lookup)) } }) - .for_each(|(label, lookup)| { - acc.push(CompletionItem { - label, - lookup: Some(lookup), - snippet: None, - }) - }); + .for_each(|(label, lookup)| CompletionItem::new(label).lookup_by(lookup).add_to(acc)); fn process<'a, N: ast::FnDefOwner<'a>>( node: N, diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs new file mode 100644 index 000000000..309b0108d --- /dev/null +++ b/crates/ra_analysis/src/completion/completion_item.rs @@ -0,0 +1,44 @@ +#[derive(Debug)] +pub struct CompletionItem { + /// What user sees in pop-up in the UI. + pub label: String, + /// What string is used for filtering, defaults to label. + pub lookup: Option, + /// What is inserted, defaults to label. + pub snippet: Option, +} + +impl CompletionItem { + pub(crate) fn new(label: impl Into) -> Builder { + let label = label.into(); + Builder { + label, + lookup: None, + snippet: None, + } + } +} + +pub(crate) struct Builder { + label: String, + lookup: Option, + snippet: Option, +} + +impl Builder { + pub fn add_to(self, acc: &mut Vec) { + acc.push(self.build()) + } + + pub fn build(self) -> CompletionItem { + CompletionItem { + label: self.label, + lookup: self.lookup, + snippet: self.snippet, + } + } + pub fn lookup_by(mut self, lookup: impl Into) -> Builder { + self.lookup = Some(lookup.into()); + self + } +} diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs index f483ed045..457ca13cc 100644 --- a/crates/ra_analysis/src/completion/reference_completion.rs +++ b/crates/ra_analysis/src/completion/reference_completion.rs @@ -6,11 +6,9 @@ use ra_syntax::{ ast::{self, LoopBodyOwner}, SyntaxKind::*, }; -use hir::{ - self, - FnScopes, - Def, - Path, +use hir::{ + self, + FnScopes, Def, Path }; use crate::{ -- cgit v1.2.3 From b0ff6176ed0401251ae9f84d115a888fa4baee89 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 21 Dec 2018 14:02:51 +0300 Subject: flip params --- crates/ra_analysis/src/completion.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'crates') diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index ed1b6dd0c..6d84558d1 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs @@ -41,7 +41,7 @@ pub(crate) fn completions( 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(name_ref.syntax(), &mut res); + param_completions(&mut res, name_ref.syntax()); } } @@ -49,7 +49,7 @@ pub(crate) fn completions( if let Some(name) = find_node_at_offset::(file.syntax(), position.offset) { if is_node::(name.syntax()) { has_completions = true; - param_completions(name.syntax(), &mut res); + param_completions(&mut res, name.syntax()); } } let res = if has_completions { Some(res) } else { None }; @@ -60,7 +60,7 @@ pub(crate) fn completions( /// 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(ctx: SyntaxNodeRef, acc: &mut Vec) { +fn param_completions(acc: &mut Vec, ctx: SyntaxNodeRef) { let mut params = FxHashMap::default(); for node in ctx.ancestors() { let _ = visitor_ctx(&mut params) -- cgit v1.2.3 From b5c5995bf13da31bb97113a7eea5c138555c2b1b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 21 Dec 2018 14:38:41 +0300 Subject: use builder interface for completion item --- crates/ra_analysis/src/completion.rs | 2 +- .../ra_analysis/src/completion/completion_item.rs | 5 ++ .../src/completion/reference_completion.rs | 84 ++++++++-------------- 3 files changed, 36 insertions(+), 55 deletions(-) (limited to 'crates') diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index 6d84558d1..fd7b78c2a 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs @@ -428,7 +428,7 @@ mod tests { <|> } ", - r##"[CompletionItem { label: "Test function", lookup: Some("tfn"), snippet: Some("#[test]\nfn ${1:feature}() {\n$0\n}") }, + 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") }]"##, ); } diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs index 309b0108d..7edb86436 100644 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ b/crates/ra_analysis/src/completion/completion_item.rs @@ -19,6 +19,7 @@ impl CompletionItem { } } +#[must_use] pub(crate) struct Builder { label: String, lookup: Option, @@ -41,4 +42,8 @@ impl Builder { self.lookup = Some(lookup.into()); self } + pub fn snippet(mut self, snippet: impl Into) -> Builder { + self.snippet = Some(snippet.into()); + self + } } diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs index 457ca13cc..23052295c 100644 --- a/crates/ra_analysis/src/completion/reference_completion.rs +++ b/crates/ra_analysis/src/completion/reference_completion.rs @@ -125,23 +125,13 @@ fn classify_name_ref(name_ref: ast::NameRef) -> Option { fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec) { let mut shadowed = FxHashSet::default(); - acc.extend( - scopes - .scope_chain(name_ref.syntax()) - .flat_map(|scope| scopes.entries(scope).iter()) - .filter(|entry| shadowed.insert(entry.name())) - .map(|entry| CompletionItem { - label: entry.name().to_string(), - lookup: None, - snippet: None, - }), - ); + scopes + .scope_chain(name_ref.syntax()) + .flat_map(|scope| scopes.entries(scope).iter()) + .filter(|entry| shadowed.insert(entry.name())) + .for_each(|entry| CompletionItem::new(entry.name().to_string()).add_to(acc)); if scopes.self_param.is_some() { - acc.push(CompletionItem { - label: "self".to_string(), - lookup: None, - snippet: None, - }) + CompletionItem::new("self").add_to(acc); } } @@ -164,32 +154,26 @@ fn complete_path( _ => return Ok(()), }; let module_scope = target_module.scope(db)?; - let completions = module_scope.entries().map(|(name, _res)| CompletionItem { - label: name.to_string(), - lookup: None, - snippet: None, - }); - acc.extend(completions); + module_scope + .entries() + .for_each(|(name, _res)| CompletionItem::new(name.to_string()).add_to(acc)); Ok(()) } fn complete_mod_item_snippets(acc: &mut Vec) { - acc.push(CompletionItem { - label: "Test function".to_string(), - lookup: Some("tfn".to_string()), - snippet: Some( - "#[test]\n\ - fn ${1:feature}() {\n\ - $0\n\ - }" - .to_string(), - ), - }); - acc.push(CompletionItem { - label: "pub(crate)".to_string(), - lookup: None, - snippet: Some("pub(crate) $0".to_string()), - }) + CompletionItem::new("Test function") + .lookup_by("tfn") + .snippet( + "\ +#[test] +fn ${1:feature}() { + $0 +}", + ) + .add_to(acc); + CompletionItem::new("pub(crate)") + .snippet("pub(crate) $0") + .add_to(acc); } fn complete_expr_keywords( @@ -270,23 +254,15 @@ fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option CompletionItem { - CompletionItem { - label: kw.to_string(), - lookup: None, - snippet: Some(snip.to_string()), - } +fn keyword(kw: &str, snippet: &str) -> CompletionItem { + CompletionItem::new(kw).snippet(snippet).build() } fn complete_expr_snippets(acc: &mut Vec) { - acc.push(CompletionItem { - label: "pd".to_string(), - lookup: None, - snippet: Some("eprintln!(\"$0 = {:?}\", $0);".to_string()), - }); - acc.push(CompletionItem { - label: "ppd".to_string(), - lookup: None, - snippet: Some("eprintln!(\"$0 = {:#?}\", $0);".to_string()), - }); + CompletionItem::new("pd") + .snippet("eprintln!(\"$0 = {:?}\", $0);") + .add_to(acc); + CompletionItem::new("ppd") + .snippet("eprintln!(\"$0 = {:#?}\", $0);") + .add_to(acc); } -- cgit v1.2.3 From 4092b8d0b58598d0b4b820fff37b1d8c741c47b9 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 21 Dec 2018 15:19:46 +0300 Subject: make compleion item details private --- crates/ra_analysis/src/completion.rs | 13 +++++++-- .../ra_analysis/src/completion/completion_item.rs | 34 ++++++++++++++++++---- .../src/completion/reference_completion.rs | 30 ++++++++----------- crates/ra_analysis/src/lib.rs | 2 +- crates/ra_lsp_server/src/main_loop/handlers.rs | 22 +++++++++----- 5 files changed, 65 insertions(+), 36 deletions(-) (limited to 'crates') diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index fd7b78c2a..222b6854c 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs @@ -18,7 +18,7 @@ use crate::{ Cancelable, FilePosition }; -pub use crate::completion::completion_item::CompletionItem; +pub use crate::completion::completion_item::{CompletionItem, InsertText}; pub(crate) fn completions( db: &db::RootDatabase, @@ -109,13 +109,20 @@ mod tests { 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| c.snippet.is_none()) + .filter(|c| !is_snippet(c)) .collect::>(); assert_eq_dbg(expected_completions, &completions); } @@ -126,7 +133,7 @@ mod tests { .unwrap() .unwrap() .into_iter() - .filter(|c| c.snippet.is_some()) + .filter(is_snippet) .collect::>(); assert_eq_dbg(expected_completions, &completions); } diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs index 7edb86436..4280976e7 100644 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ b/crates/ra_analysis/src/completion/completion_item.rs @@ -1,11 +1,13 @@ #[derive(Debug)] pub struct CompletionItem { - /// What user sees in pop-up in the UI. - pub label: String, - /// What string is used for filtering, defaults to label. - pub lookup: Option, - /// What is inserted, defaults to label. - pub snippet: Option, + label: String, + lookup: Option, + snippet: Option, +} + +pub enum InsertText { + PlainText { text: String }, + Snippet { text: String }, } impl CompletionItem { @@ -17,6 +19,26 @@ impl CompletionItem { snippet: None, } } + /// What user sees in pop-up in the UI. + pub fn label(&self) -> &str { + &self.label + } + /// What string is used for filtering. + pub fn lookup(&self) -> &str { + self.lookup + .as_ref() + .map(|it| it.as_str()) + .unwrap_or(self.label()) + } + /// What is inserted. + pub fn insert_text(&self) -> InsertText { + match &self.snippet { + None => InsertText::PlainText { + text: self.label.clone(), + }, + Some(it) => InsertText::Snippet { text: it.clone() }, + } + } } #[must_use] diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs index 23052295c..f9f01a642 100644 --- a/crates/ra_analysis/src/completion/reference_completion.rs +++ b/crates/ra_analysis/src/completion/reference_completion.rs @@ -39,25 +39,19 @@ pub(super) fn completions( } let module_scope = module.scope(db)?; - acc.extend( - module_scope - .entries() - .filter(|(_name, res)| { - // Don't expose this item - match res.import { - None => true, - Some(import) => { - let range = import.range(db, module.source().file_id()); - !range.is_subrange(&name_ref.syntax().range()) - } + module_scope + .entries() + .filter(|(_name, res)| { + // Don't expose this item + match res.import { + None => true, + Some(import) => { + let range = import.range(db, module.source().file_id()); + !range.is_subrange(&name_ref.syntax().range()) } - }) - .map(|(name, _res)| CompletionItem { - label: name.to_string(), - lookup: None, - snippet: None, - }), - ); + } + }) + .for_each(|(name, _res)| CompletionItem::new(name.to_string()).add_to(acc)); } NameRefKind::Path(path) => complete_path(acc, db, module, path)?, NameRefKind::BareIdentInMod => { diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs index c7e7dc1c0..1c8aa308b 100644 --- a/crates/ra_analysis/src/lib.rs +++ b/crates/ra_analysis/src/lib.rs @@ -30,7 +30,7 @@ use crate::{ }; pub use crate::{ - completion::CompletionItem, + completion::{CompletionItem, InsertText}, }; pub use ra_editor::{ FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, RunnableKind, StructureNode, diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 1751d7fa8..2dfeb061a 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs @@ -8,7 +8,7 @@ use languageserver_types::{ PrepareRenameResponse, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit, ParameterInformation, ParameterLabel, SignatureInformation, Hover, HoverContents, }; -use ra_analysis::{FileId, FoldKind, Query, RunnableKind, FilePosition}; +use ra_analysis::{FileId, FoldKind, Query, RunnableKind, FilePosition, InsertText}; use ra_syntax::{TextUnit, text_utils::intersect}; use ra_text_edit::text_utils::contains_offset_nonstrict; use rustc_hash::FxHashMap; @@ -423,15 +423,21 @@ pub fn handle_completion( .into_iter() .map(|item| { let mut res = CompletionItem { - label: item.label, - filter_text: item.lookup, + label: item.label().to_string(), + filter_text: Some(item.lookup().to_string()), ..Default::default() }; - if let Some(snip) = item.snippet { - res.insert_text = Some(snip); - res.insert_text_format = Some(InsertTextFormat::Snippet); - res.kind = Some(CompletionItemKind::Keyword); - }; + match item.insert_text() { + InsertText::PlainText { text } => { + res.insert_text = Some(text); + res.insert_text_format = Some(InsertTextFormat::PlainText); + } + InsertText::Snippet { text } => { + res.insert_text = Some(text); + res.insert_text_format = Some(InsertTextFormat::Snippet); + res.kind = Some(CompletionItemKind::Keyword); + } + } res }) .collect(); -- cgit v1.2.3 From 0ce82516c1023d9aed84556949ee2ac6294a3079 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 21 Dec 2018 15:32:29 +0300 Subject: introduce Completions --- .../ra_analysis/src/completion/completion_item.rs | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'crates') diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs index 4280976e7..322a7c6be 100644 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ b/crates/ra_analysis/src/completion/completion_item.rs @@ -69,3 +69,26 @@ impl Builder { self } } + +impl Into for Builder { + fn into(self) -> CompletionItem { + self.build() + } +} + +#[derive(Debug)] +pub(crate) struct Completions { + buf: Vec, +} + +impl Completions { + pub(crate) fn add(&mut self, item: impl Into) { + self.buf.push(item.into()) + } +} + +impl Into> for Completions { + fn into(self) -> Vec { + self.buf + } +} -- cgit v1.2.3 From 052e20162a026356c716116ac10ea795ca5dc28d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 21 Dec 2018 15:34:11 +0300 Subject: docs --- crates/ra_analysis/src/completion/completion_item.rs | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'crates') diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs index 322a7c6be..445d6bf41 100644 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ b/crates/ra_analysis/src/completion/completion_item.rs @@ -1,3 +1,6 @@ +/// `CompletionItem` describes a single completion variant in the editor pop-up. +/// It is basically a POD with various properties. To construct a +/// `CompletionItem`, use `new` method and the `Builder` struct. #[derive(Debug)] pub struct CompletionItem { label: String, @@ -41,6 +44,7 @@ impl CompletionItem { } } +/// A helper to make `CompletionItem`s. #[must_use] pub(crate) struct Builder { label: String, @@ -76,6 +80,7 @@ impl Into for Builder { } } +/// Represents an in-progress set of completions being built. #[derive(Debug)] pub(crate) struct Completions { buf: Vec, -- cgit v1.2.3 From ba0072401c3b8b6c9391428672bd91055150c8ee Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 21 Dec 2018 15:46:01 +0300 Subject: use Completions to collect completions --- crates/ra_analysis/src/completion.rs | 19 ++++++++----- .../ra_analysis/src/completion/completion_item.rs | 13 +++++++-- .../src/completion/reference_completion.rs | 32 +++++++++++----------- 3 files changed, 38 insertions(+), 26 deletions(-) (limited to 'crates') diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index 222b6854c..074033ad8 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs @@ -15,7 +15,8 @@ use hir::source_binder; use crate::{ db, - Cancelable, FilePosition + Cancelable, FilePosition, + completion::completion_item::Completions, }; pub use crate::completion::completion_item::{CompletionItem, InsertText}; @@ -33,15 +34,15 @@ pub(crate) fn completions( let module = ctry!(source_binder::module_from_position(db, position)?); - let mut res = Vec::new(); + let mut acc = Completions::default(); 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)?; + reference_completion::completions(&mut acc, 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()); + param_completions(&mut acc, name_ref.syntax()); } } @@ -49,10 +50,14 @@ pub(crate) fn completions( 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()); + param_completions(&mut acc, name.syntax()); } } - let res = if has_completions { Some(res) } else { None }; + let res = if has_completions { + Some(acc.into()) + } else { + None + }; Ok(res) } @@ -60,7 +65,7 @@ pub(crate) fn completions( /// 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) { +fn param_completions(acc: &mut Completions, ctx: SyntaxNodeRef) { let mut params = FxHashMap::default(); for node in ctx.ancestors() { let _ = visitor_ctx(&mut params) diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs index 445d6bf41..8aa9da005 100644 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ b/crates/ra_analysis/src/completion/completion_item.rs @@ -53,8 +53,8 @@ pub(crate) struct Builder { } impl Builder { - pub fn add_to(self, acc: &mut Vec) { - acc.push(self.build()) + pub fn add_to(self, acc: &mut Completions) { + acc.add(self.build()) } pub fn build(self) -> CompletionItem { @@ -81,7 +81,7 @@ impl Into for Builder { } /// Represents an in-progress set of completions being built. -#[derive(Debug)] +#[derive(Debug, Default)] pub(crate) struct Completions { buf: Vec, } @@ -90,6 +90,13 @@ impl Completions { pub(crate) fn add(&mut self, item: impl Into) { self.buf.push(item.into()) } + pub(crate) fn add_all(&mut self, items: I) + where + I: IntoIterator, + I::Item: Into, + { + items.into_iter().for_each(|item| self.add(item.into())) + } } impl Into> for Completions { diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs index f9f01a642..c578e9e8b 100644 --- a/crates/ra_analysis/src/completion/reference_completion.rs +++ b/crates/ra_analysis/src/completion/reference_completion.rs @@ -13,12 +13,12 @@ use hir::{ use crate::{ db::RootDatabase, - completion::CompletionItem, + completion::{CompletionItem, Completions}, Cancelable }; pub(super) fn completions( - acc: &mut Vec, + acc: &mut Completions, db: &RootDatabase, module: &hir::Module, file: &SourceFileNode, @@ -117,7 +117,7 @@ fn classify_name_ref(name_ref: ast::NameRef) -> Option { None } -fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec) { +fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Completions) { let mut shadowed = FxHashSet::default(); scopes .scope_chain(name_ref.syntax()) @@ -130,7 +130,7 @@ fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec, + acc: &mut Completions, db: &RootDatabase, module: &hir::Module, mut path: Path, @@ -154,7 +154,7 @@ fn complete_path( Ok(()) } -fn complete_mod_item_snippets(acc: &mut Vec) { +fn complete_mod_item_snippets(acc: &mut Completions) { CompletionItem::new("Test function") .lookup_by("tfn") .snippet( @@ -174,26 +174,26 @@ fn complete_expr_keywords( file: &SourceFileNode, fn_def: ast::FnDef, name_ref: ast::NameRef, - acc: &mut Vec, + acc: &mut Completions, ) { - acc.push(keyword("if", "if $0 {}")); - acc.push(keyword("match", "match $0 {}")); - acc.push(keyword("while", "while $0 {}")); - acc.push(keyword("loop", "loop {$0}")); + 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.push(keyword("else", "else {$0}")); - acc.push(keyword("else if", "else if $0 {}")); + acc.add(keyword("else", "else {$0}")); + acc.add(keyword("else if", "else if $0 {}")); } } } if is_in_loop_body(name_ref) { - acc.push(keyword("continue", "continue")); - acc.push(keyword("break", "break")); + acc.add(keyword("continue", "continue")); + acc.add(keyword("break", "break")); } - acc.extend(complete_return(fn_def, name_ref)); + acc.add_all(complete_return(fn_def, name_ref)); } fn is_in_loop_body(name_ref: ast::NameRef) -> bool { @@ -252,7 +252,7 @@ fn keyword(kw: &str, snippet: &str) -> CompletionItem { CompletionItem::new(kw).snippet(snippet).build() } -fn complete_expr_snippets(acc: &mut Vec) { +fn complete_expr_snippets(acc: &mut Completions) { CompletionItem::new("pd") .snippet("eprintln!(\"$0 = {:?}\", $0);") .add_to(acc); -- cgit v1.2.3 From d4ef07b2355df891d4f9b7641f0246ebe5bd6a6b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 21 Dec 2018 15:50:07 +0300 Subject: use completions in API --- crates/ra_analysis/src/completion.rs | 12 +++++------- crates/ra_analysis/src/imp.rs | 3 ++- 2 files changed, 7 insertions(+), 8 deletions(-) (limited to 'crates') diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index 074033ad8..09894d955 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs @@ -24,7 +24,7 @@ pub use crate::completion::completion_item::{CompletionItem, InsertText}; pub(crate) fn completions( db: &db::RootDatabase, position: FilePosition, -) -> Cancelable>> { +) -> Cancelable> { let original_file = db.source_file(position.file_id); // Insert a fake ident to get a valid parse tree let file = { @@ -53,12 +53,10 @@ pub(crate) fn completions( param_completions(&mut acc, name.syntax()); } } - let res = if has_completions { - Some(acc.into()) - } else { - None - }; - Ok(res) + if !has_completions { + return Ok(None); + } + Ok(Some(acc)) } /// Complete repeated parametes, both name and type. For example, if all diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs index 5701e1ae2..340f7c78c 100644 --- a/crates/ra_analysis/src/imp.rs +++ b/crates/ra_analysis/src/imp.rs @@ -219,7 +219,8 @@ impl AnalysisImpl { self.db.crate_graph().crate_root(crate_id) } pub fn completions(&self, position: FilePosition) -> Cancelable>> { - completions(&self.db, position) + let completions = completions(&self.db, position)?; + Ok(completions.map(|it| it.into())) } pub fn approximately_resolve_symbol( &self, -- cgit v1.2.3 From 45232dfa689bafadf98b92ef30fd32ea9a5e9e7a Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 21 Dec 2018 18:13:21 +0300 Subject: organize completion tests better --- crates/ra_analysis/src/completion.rs | 343 ++----------------- .../ra_analysis/src/completion/completion_item.rs | 80 ++++- .../src/completion/reference_completion.rs | 376 ++++++++++++++++++++- crates/ra_analysis/tests/tests.rs | 60 ---- crates/test_utils/src/lib.rs | 22 +- 5 files changed, 488 insertions(+), 393 deletions(-) (limited to 'crates') diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index 09894d955..a11e98ac0 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs @@ -16,7 +16,7 @@ use hir::source_binder; use crate::{ db, Cancelable, FilePosition, - completion::completion_item::Completions, + completion::completion_item::{Completions, CompletionKind}, }; pub use crate::completion::completion_item::{CompletionItem, InsertText}; @@ -81,7 +81,12 @@ fn param_completions(acc: &mut Completions, ctx: SyntaxNodeRef) { Some((label, lookup)) } }) - .for_each(|(label, lookup)| CompletionItem::new(label).lookup_by(lookup).add_to(acc)); + .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, @@ -105,341 +110,61 @@ fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { } #[cfg(test)] -mod tests { - use test_utils::assert_eq_dbg; - - use crate::mock_analysis::single_file_with_position; +fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind) { + use crate::mock_analysis::{single_file_with_position, analysis_and_position}; + let (analysis, position) = if code.contains("//-") { + analysis_and_position(code) + } else { + single_file_with_position(code) + }; + let completions = completions(&analysis.imp.db, position).unwrap().unwrap(); + completions.assert_match(expected_completions, kind); +} +#[cfg(test)] +mod tests { 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);") }]"#); + fn check_magic_completion(code: &str, expected_completions: &str) { + check_completion(code, expected_completions, CompletionKind::Magic); } #[test] fn test_param_completion_last_param() { - check_scope_completion(r" + check_magic_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 }]"#); + ", + r#"file_id "file_id: FileId""#, + ); } #[test] fn test_param_completion_nth_param() { - check_scope_completion(r" + check_magic_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 }]"#); + ", + r#"file_id "file_id: FileId""#, + ); } #[test] fn test_param_completion_trait_param() { - check_scope_completion(r" + 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#"[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") }]"##, + r#"file_id "file_id: FileId""#, ); } } diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs index 8aa9da005..d5d751759 100644 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ b/crates/ra_analysis/src/completion/completion_item.rs @@ -6,6 +6,8 @@ pub struct CompletionItem { label: String, lookup: Option, snippet: Option, + /// Used only internally in test, to check only specific kind of completion. + kind: CompletionKind, } pub enum InsertText { @@ -13,6 +15,18 @@ pub enum InsertText { Snippet { text: String }, } +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum CompletionKind { + /// Parser-based keyword completion. + Keyword, + /// Your usual "complete all valid identifiers". + Reference, + /// "Secret sauce" completions. + Magic, + Snippet, + Unspecified, +} + impl CompletionItem { pub(crate) fn new(label: impl Into) -> Builder { let label = label.into(); @@ -20,6 +34,7 @@ impl CompletionItem { label, lookup: None, snippet: None, + kind: CompletionKind::Unspecified, } } /// What user sees in pop-up in the UI. @@ -50,28 +65,34 @@ pub(crate) struct Builder { label: String, lookup: Option, snippet: Option, + kind: CompletionKind, } impl Builder { - pub fn add_to(self, acc: &mut Completions) { + pub(crate) fn add_to(self, acc: &mut Completions) { acc.add(self.build()) } - pub fn build(self) -> CompletionItem { + pub(crate) fn build(self) -> CompletionItem { CompletionItem { label: self.label, lookup: self.lookup, snippet: self.snippet, + kind: self.kind, } } - pub fn lookup_by(mut self, lookup: impl Into) -> Builder { + pub(crate) fn lookup_by(mut self, lookup: impl Into) -> Builder { self.lookup = Some(lookup.into()); self } - pub fn snippet(mut self, snippet: impl Into) -> Builder { + pub(crate) fn snippet(mut self, snippet: impl Into) -> Builder { self.snippet = Some(snippet.into()); self } + pub(crate) fn kind(mut self, kind: CompletionKind) -> Builder { + self.kind = kind; + self + } } impl Into for Builder { @@ -97,6 +118,57 @@ impl Completions { { items.into_iter().for_each(|item| self.add(item.into())) } + + #[cfg(test)] + pub(crate) fn assert_match(&self, expected: &str, kind: CompletionKind) { + let expected = normalize(expected); + let actual = self.debug_render(kind); + test_utils::assert_eq_text!(expected.as_str(), actual.as_str(),); + + /// Normalize the textual representation of `Completions`: + /// replace `;` with newlines, normalize whitespace + fn normalize(expected: &str) -> String { + use ra_syntax::{tokenize, TextUnit, TextRange, SyntaxKind::SEMI}; + let mut res = String::new(); + for line in expected.trim().lines() { + let line = line.trim(); + let mut start_offset: TextUnit = 0.into(); + // Yep, we use rust tokenize in completion tests :-) + for token in tokenize(line) { + let range = TextRange::offset_len(start_offset, token.len); + start_offset += token.len; + if token.kind == SEMI { + res.push('\n'); + } else { + res.push_str(&line[range]); + } + } + + res.push('\n'); + } + res + } + } + + #[cfg(test)] + fn debug_render(&self, kind: CompletionKind) -> String { + let mut res = String::new(); + for c in self.buf.iter() { + if c.kind == kind { + if let Some(lookup) = &c.lookup { + res.push_str(lookup); + res.push_str(&format!(" {:?}", c.label)); + } else { + res.push_str(&c.label); + } + if let Some(snippet) = &c.snippet { + res.push_str(&format!(" {:?}", snippet)); + } + res.push('\n'); + } + } + res + } } impl Into> for Completions { diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs index c578e9e8b..c2a650b6d 100644 --- a/crates/ra_analysis/src/completion/reference_completion.rs +++ b/crates/ra_analysis/src/completion/reference_completion.rs @@ -13,7 +13,7 @@ use hir::{ use crate::{ db::RootDatabase, - completion::{CompletionItem, Completions}, + completion::{CompletionItem, Completions, CompletionKind::*}, Cancelable }; @@ -51,7 +51,11 @@ pub(super) fn completions( } } }) - .for_each(|(name, _res)| CompletionItem::new(name.to_string()).add_to(acc)); + .for_each(|(name, _res)| { + CompletionItem::new(name.to_string()) + .kind(Reference) + .add_to(acc) + }); } NameRefKind::Path(path) => complete_path(acc, db, module, path)?, NameRefKind::BareIdentInMod => { @@ -123,9 +127,13 @@ fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Completions) .scope_chain(name_ref.syntax()) .flat_map(|scope| scopes.entries(scope).iter()) .filter(|entry| shadowed.insert(entry.name())) - .for_each(|entry| CompletionItem::new(entry.name().to_string()).add_to(acc)); + .for_each(|entry| { + CompletionItem::new(entry.name().to_string()) + .kind(Reference) + .add_to(acc) + }); if scopes.self_param.is_some() { - CompletionItem::new("self").add_to(acc); + CompletionItem::new("self").kind(Reference).add_to(acc); } } @@ -148,9 +156,11 @@ fn complete_path( _ => return Ok(()), }; let module_scope = target_module.scope(db)?; - module_scope - .entries() - .for_each(|(name, _res)| CompletionItem::new(name.to_string()).add_to(acc)); + module_scope.entries().for_each(|(name, _res)| { + CompletionItem::new(name.to_string()) + .kind(Reference) + .add_to(acc) + }); Ok(()) } @@ -164,9 +174,11 @@ fn ${1:feature}() { $0 }", ) + .kind(Snippet) .add_to(acc); CompletionItem::new("pub(crate)") .snippet("pub(crate) $0") + .kind(Snippet) .add_to(acc); } @@ -249,14 +261,362 @@ fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option CompletionItem { - CompletionItem::new(kw).snippet(snippet).build() + CompletionItem::new(kw) + .kind(Keyword) + .snippet(snippet) + .build() } 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}; + + fn check_reference_completion(code: &str, expected_completions: &str) { + 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); + } + + #[test] + fn test_completion_let_scope() { + check_reference_completion( + r" + fn quux(x: i32) { + let y = 92; + 1 + <|>; + let z = (); + } + ", + "y;x;quux", + ); + } + + #[test] + fn test_completion_if_let_scope() { + check_reference_completion( + r" + fn quux() { + if let Some(x) = foo() { + let y = 92; + }; + if let Some(a) = bar() { + let b = 62; + 1 + <|> + } + } + ", + "b;a;quux", + ); + } + + #[test] + fn test_completion_for_scope() { + check_reference_completion( + r" + fn quux() { + for x in &[1, 2, 3] { + <|> + } + } + ", + "x;quux", + ); + } + + #[test] + fn test_completion_mod_scope() { + check_reference_completion( + r" + struct Foo; + enum Baz {} + fn quux() { + <|> + } + ", + "quux;Foo;Baz", + ); + } + + #[test] + fn test_completion_mod_scope_no_self_use() { + check_reference_completion( + r" + use foo<|>; + ", + "", + ); + } + + #[test] + fn test_completion_self_path() { + check_reference_completion( + r" + use self::m::<|>; + + mod m { + struct Bar; + } + ", + "Bar", + ); + } + + #[test] + fn test_completion_mod_scope_nested() { + check_reference_completion( + r" + struct Foo; + mod m { + struct Bar; + fn quux() { <|> } + } + ", + "quux;Bar", + ); + } + + #[test] + fn test_complete_type() { + check_reference_completion( + r" + struct Foo; + fn x() -> <|> + ", + "Foo;x", + ) + } + + #[test] + fn test_complete_shadowing() { + check_reference_completion( + r" + fn foo() -> { + let bar = 92; + { + let bar = 62; + <|> + } + } + ", + "bar;foo", + ) + } + + #[test] + fn test_complete_self() { + check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self") + } + + #[test] + fn test_complete_crate_path() { + check_reference_completion( + " + //- /lib.rs + mod foo; + struct Spam; + //- /foo.rs + use crate::Sp<|> + ", + "Spam;foo", + ); + } + + #[test] + fn test_complete_crate_path_with_braces() { + check_reference_completion( + " + //- /lib.rs + mod foo; + struct Spam; + //- /foo.rs + use crate::{Sp<|>}; + ", + "Spam;foo", + ); + } + + #[test] + fn test_complete_crate_path_in_nested_tree() { + check_reference_completion( + " + //- /lib.rs + mod foo; + pub mod bar { + pub mod baz { + pub struct Spam; + } + } + //- /foo.rs + use crate::{bar::{baz::Sp<|>}}; + ", + "Spam", + ); + } + + #[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 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##" + tfn "Test function" "#[test]\nfn ${1:feature}() {\n $0\n}" + pub(crate) "pub(crate) $0" + "##, + ); + } + +} diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs index 67738da48..938ca797a 100644 --- a/crates/ra_analysis/tests/tests.rs +++ b/crates/ra_analysis/tests/tests.rs @@ -452,63 +452,3 @@ fn test_find_all_refs_for_fn_param() { let refs = get_all_refs(code); assert_eq!(refs.len(), 2); } - -#[test] -fn test_complete_crate_path() { - let (analysis, position) = analysis_and_position( - " - //- /lib.rs - mod foo; - struct Spam; - //- /foo.rs - use crate::Sp<|> - ", - ); - let completions = analysis.completions(position).unwrap().unwrap(); - assert_eq_dbg( - r#"[CompletionItem { label: "Spam", lookup: None, snippet: None }, - CompletionItem { label: "foo", lookup: None, snippet: None }]"#, - &completions, - ); -} - -#[test] -fn test_complete_crate_path_with_braces() { - let (analysis, position) = analysis_and_position( - " - //- /lib.rs - mod foo; - struct Spam; - //- /foo.rs - use crate::{Sp<|>}; - ", - ); - let completions = analysis.completions(position).unwrap().unwrap(); - assert_eq_dbg( - r#"[CompletionItem { label: "Spam", lookup: None, snippet: None }, - CompletionItem { label: "foo", lookup: None, snippet: None }]"#, - &completions, - ); -} - -#[test] -fn test_complete_crate_path_in_nested_tree() { - let (analysis, position) = analysis_and_position( - " - //- /lib.rs - mod foo; - pub mod bar { - pub mod baz { - pub struct Spam; - } - } - //- /foo.rs - use crate::{bar::{baz::Sp<|>}}; - ", - ); - let completions = analysis.completions(position).unwrap().unwrap(); - assert_eq_dbg( - r#"[CompletionItem { label: "Spam", lookup: None, snippet: None }]"#, - &completions, - ); -} diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index 1ae800d7c..beb936c61 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -10,22 +10,20 @@ pub const CURSOR_MARKER: &str = "<|>"; #[macro_export] macro_rules! assert_eq_text { - ($expected:expr, $actual:expr) => {{ - let expected = $expected; - let actual = $actual; - if expected != actual { - let changeset = $crate::__Changeset::new(actual, expected, "\n"); - println!("Expected:\n{}\n\nActual:\n{}\nDiff:{}\n", expected, actual, changeset); - panic!("text differs"); - } - }}; + ($expected:expr, $actual:expr) => { + assert_eq_text!($expected, $actual,) + }; ($expected:expr, $actual:expr, $($tt:tt)*) => {{ let expected = $expected; let actual = $actual; if expected != actual { - let changeset = $crate::__Changeset::new(actual, expected, "\n"); - println!("Expected:\n{}\n\nActual:\n{}\n\nDiff:\n{}\n", expected, actual, changeset); - println!($($tt)*); + if expected.trim() == actual.trim() { + eprintln!("Expected:\n{:?}\n\nActual:\n{:?}\n\nWhitespace difference\n", expected, actual); + } else { + let changeset = $crate::__Changeset::new(actual, expected, "\n"); + eprintln!("Expected:\n{}\n\nActual:\n{}\n\nDiff:\n{}\n", expected, actual, changeset); + } + eprintln!($($tt)*); panic!("text differs"); } }}; -- cgit v1.2.3