From f62e8616c879255e70052ae35ce7f98bffedac11 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 18 Sep 2020 23:40:11 +0300 Subject: Add imports in auto completion --- crates/completion/Cargo.toml | 2 + crates/completion/src/completions.rs | 1 + .../completion/src/completions/complete_magic.rs | 114 +++++++++++++++++++++ crates/completion/src/item.rs | 1 + crates/completion/src/lib.rs | 1 + 5 files changed, 119 insertions(+) create mode 100644 crates/completion/src/completions/complete_magic.rs (limited to 'crates/completion') diff --git a/crates/completion/Cargo.toml b/crates/completion/Cargo.toml index 3015ec9e0..799b4a3d5 100644 --- a/crates/completion/Cargo.toml +++ b/crates/completion/Cargo.toml @@ -13,6 +13,7 @@ doctest = false itertools = "0.9.0" log = "0.4.8" rustc-hash = "1.1.0" +either = "1.6.1" assists = { path = "../assists", version = "0.0.0" } stdx = { path = "../stdx", version = "0.0.0" } @@ -21,6 +22,7 @@ text_edit = { path = "../text_edit", version = "0.0.0" } base_db = { path = "../base_db", version = "0.0.0" } ide_db = { path = "../ide_db", version = "0.0.0" } profile = { path = "../profile", version = "0.0.0" } +assists = { path = "../assists", version = "0.0.0" } test_utils = { path = "../test_utils", version = "0.0.0" } # completions crate should depend only on the top-level `hir` package. if you need diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 75dbb1a23..99db5f998 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -13,6 +13,7 @@ pub(crate) mod postfix; pub(crate) mod macro_in_item_position; pub(crate) mod trait_impl; pub(crate) mod mod_; +pub(crate) mod complete_magic; use hir::{ModPath, ScopeDef, Type}; diff --git a/crates/completion/src/completions/complete_magic.rs b/crates/completion/src/completions/complete_magic.rs new file mode 100644 index 000000000..857a0b620 --- /dev/null +++ b/crates/completion/src/completions/complete_magic.rs @@ -0,0 +1,114 @@ +//! TODO kb move this into the complete_unqualified_path when starts to work properly + +use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour}; +use hir::Query; +use itertools::Itertools; +use syntax::AstNode; +use text_edit::TextEdit; + +use crate::{context::CompletionContext, item::CompletionKind, CompletionItem, CompletionItemKind}; + +use super::Completions; + +pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { + if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { + return None; + } + let current_module = ctx.scope.module()?; + let anchor = ctx.name_ref_syntax.as_ref()?; + let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; + // TODO kb now this is the whole file, which is not disjoint with any other change in the same file, fix it + // otherwise it's impossible to correctly add the use statement and also change the completed text into something more meaningful + let import_syntax = import_scope.as_syntax_node(); + + // TODO kb consider heuristics, such as "don't show `hash_map` import if `HashMap` is the import for completion" + // TODO kb module functions are not completed, consider `std::io::stdin` one + let potential_import_name = ctx.token.to_string(); + + let possible_imports = ctx + .krate? + // TODO kb use imports_locator instead? + .query_external_importables(ctx.db, Query::new(&potential_import_name).limit(40)) + .unique() + .filter_map(|import_candidate| match import_candidate { + either::Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), + either::Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), + }) + .filter_map(|mod_path| { + let correct_qualifier = mod_path.segments.last()?.to_string(); + let rewriter = + insert_use(&import_scope, mod_path_to_ast(&mod_path), Some(MergeBehaviour::Full)); + let rewritten_node = rewriter.rewrite(import_syntax); + let insert_use_edit = + TextEdit::replace(import_syntax.text_range(), rewritten_node.to_string()); + let mut completion_edit = + TextEdit::replace(anchor.syntax().text_range(), correct_qualifier); + completion_edit.union(insert_use_edit).expect("TODO kb"); + + let completion_item: CompletionItem = CompletionItem::new( + CompletionKind::Magic, + ctx.source_range(), + mod_path.to_string(), + ) + .kind(CompletionItemKind::Struct) + .text_edit(completion_edit) + .into(); + Some(completion_item) + }); + acc.add_all(possible_imports); + + Some(()) +} + +#[cfg(test)] +mod tests { + use expect_test::{expect, Expect}; + + use crate::{ + item::CompletionKind, + test_utils::{check_edit, completion_list}, + }; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture, CompletionKind::Magic); + expect.assert_eq(&actual) + } + + #[test] + fn case_insensitive_magic_completion_works() { + check( + r#" +//- /lib.rs crate:dep +pub struct TestStruct; + +//- /main.rs crate:main deps:dep +fn main() { + teru<|> +} +"#, + expect![[r#" + st dep::TestStruct + "#]], + ); + + check_edit( + "dep::TestStruct", + r#" +//- /lib.rs crate:dep +pub struct TestStruct; + +//- /main.rs crate:main deps:dep +fn main() { + teru<|> +} +"#, + r#" +use dep::TestStruct; + +fn main() { + TestStruct +} +"#, + ); + } +} diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs index 6d1d085f4..f23913935 100644 --- a/crates/completion/src/item.rs +++ b/crates/completion/src/item.rs @@ -31,6 +31,7 @@ pub struct CompletionItem { /// /// Typically, replaces `source_range` with new identifier. text_edit: TextEdit, + insert_text_format: InsertTextFormat, /// What item (struct, function, etc) are we completing. diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs index cb6e0554e..e920fa6b5 100644 --- a/crates/completion/src/lib.rs +++ b/crates/completion/src/lib.rs @@ -118,6 +118,7 @@ pub fn completions( completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); completions::trait_impl::complete_trait_impl(&mut acc, &ctx); completions::mod_::complete_mod(&mut acc, &ctx); + completions::complete_magic::complete_magic(&mut acc, &ctx); Some(acc) } -- cgit v1.2.3 From 6866a05e6ff4052bd45744d54f5c032aa737c36a Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 8 Nov 2020 00:23:05 +0200 Subject: Use rewriter api to add both changes --- .../completion/src/completions/complete_magic.rs | 62 ++++++++++++++++++---- 1 file changed, 51 insertions(+), 11 deletions(-) (limited to 'crates/completion') diff --git a/crates/completion/src/completions/complete_magic.rs b/crates/completion/src/completions/complete_magic.rs index 857a0b620..9242b860c 100644 --- a/crates/completion/src/completions/complete_magic.rs +++ b/crates/completion/src/completions/complete_magic.rs @@ -3,7 +3,7 @@ use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour}; use hir::Query; use itertools::Itertools; -use syntax::AstNode; +use syntax::{algo, AstNode}; use text_edit::TextEdit; use crate::{context::CompletionContext, item::CompletionKind, CompletionItem, CompletionItemKind}; @@ -17,9 +17,6 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> let current_module = ctx.scope.module()?; let anchor = ctx.name_ref_syntax.as_ref()?; let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; - // TODO kb now this is the whole file, which is not disjoint with any other change in the same file, fix it - // otherwise it's impossible to correctly add the use statement and also change the completed text into something more meaningful - let import_syntax = import_scope.as_syntax_node(); // TODO kb consider heuristics, such as "don't show `hash_map` import if `HashMap` is the import for completion" // TODO kb module functions are not completed, consider `std::io::stdin` one @@ -35,15 +32,16 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> either::Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), }) .filter_map(|mod_path| { + let mut builder = TextEdit::builder(); + let correct_qualifier = mod_path.segments.last()?.to_string(); + builder.replace(anchor.syntax().text_range(), correct_qualifier); + + // TODO kb: assists already have the merge behaviour setting, need to unite both let rewriter = insert_use(&import_scope, mod_path_to_ast(&mod_path), Some(MergeBehaviour::Full)); - let rewritten_node = rewriter.rewrite(import_syntax); - let insert_use_edit = - TextEdit::replace(import_syntax.text_range(), rewritten_node.to_string()); - let mut completion_edit = - TextEdit::replace(anchor.syntax().text_range(), correct_qualifier); - completion_edit.union(insert_use_edit).expect("TODO kb"); + let old_ast = rewriter.rewrite_root()?; + algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut builder); let completion_item: CompletionItem = CompletionItem::new( CompletionKind::Magic, @@ -51,7 +49,7 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> mod_path.to_string(), ) .kind(CompletionItemKind::Struct) - .text_edit(completion_edit) + .text_edit(builder.finish()) .into(); Some(completion_item) }); @@ -74,6 +72,48 @@ mod tests { expect.assert_eq(&actual) } + #[test] + fn function_magic_completion() { + check( + r#" +//- /lib.rs crate:dep +pub mod io { + pub fn stdin() {} +}; + +//- /main.rs crate:main deps:dep +fn main() { + stdi<|> +} +"#, + expect![[r#" + st dep::io::stdin + "#]], + ); + + check_edit( + "dep::io::stdin", + r#" +//- /lib.rs crate:dep +pub mod io { + pub fn stdin() {} +}; + +//- /main.rs crate:main deps:dep +fn main() { + stdi<|> +} +"#, + r#" +use dep::io::stdin; + +fn main() { + stdin +} +"#, + ); + } + #[test] fn case_insensitive_magic_completion_works() { check( -- cgit v1.2.3 From 6ab97244b88b180c1cafd5b47533bd4366a09177 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 10 Nov 2020 11:08:59 +0200 Subject: Tidy up the tests --- .../completion/src/completions/complete_magic.rs | 64 +++++----------------- 1 file changed, 15 insertions(+), 49 deletions(-) (limited to 'crates/completion') diff --git a/crates/completion/src/completions/complete_magic.rs b/crates/completion/src/completions/complete_magic.rs index 9242b860c..15af2190d 100644 --- a/crates/completion/src/completions/complete_magic.rs +++ b/crates/completion/src/completions/complete_magic.rs @@ -10,6 +10,7 @@ use crate::{context::CompletionContext, item::CompletionKind, CompletionItem, Co use super::Completions; +// TODO kb when typing, completes partial results, need to rerun manually to see the proper ones pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { return None; @@ -19,7 +20,7 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; // TODO kb consider heuristics, such as "don't show `hash_map` import if `HashMap` is the import for completion" - // TODO kb module functions are not completed, consider `std::io::stdin` one + // also apply completion ordering let potential_import_name = ctx.token.to_string(); let possible_imports = ctx @@ -38,6 +39,7 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> builder.replace(anchor.syntax().text_range(), correct_qualifier); // TODO kb: assists already have the merge behaviour setting, need to unite both + // also consider a settings toggle for this particular feature? let rewriter = insert_use(&import_scope, mod_path_to_ast(&mod_path), Some(MergeBehaviour::Full)); let old_ast = rewriter.rewrite_root()?; @@ -60,37 +62,10 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> #[cfg(test)] mod tests { - use expect_test::{expect, Expect}; - - use crate::{ - item::CompletionKind, - test_utils::{check_edit, completion_list}, - }; - - fn check(ra_fixture: &str, expect: Expect) { - let actual = completion_list(ra_fixture, CompletionKind::Magic); - expect.assert_eq(&actual) - } + use crate::test_utils::check_edit; #[test] fn function_magic_completion() { - check( - r#" -//- /lib.rs crate:dep -pub mod io { - pub fn stdin() {} -}; - -//- /main.rs crate:main deps:dep -fn main() { - stdi<|> -} -"#, - expect![[r#" - st dep::io::stdin - "#]], - ); - check_edit( "dep::io::stdin", r#" @@ -116,37 +91,28 @@ fn main() { #[test] fn case_insensitive_magic_completion_works() { - check( - r#" -//- /lib.rs crate:dep -pub struct TestStruct; - -//- /main.rs crate:main deps:dep -fn main() { - teru<|> -} -"#, - expect![[r#" - st dep::TestStruct - "#]], - ); - check_edit( - "dep::TestStruct", + "dep::some_module::ThirdStruct", r#" //- /lib.rs crate:dep -pub struct TestStruct; +pub struct FirstStruct; +pub mod some_module { + pub struct SecondStruct; + pub struct ThirdStruct; +} //- /main.rs crate:main deps:dep +use dep::{FirstStruct, some_module::SecondStruct}; + fn main() { - teru<|> + this<|> } "#, r#" -use dep::TestStruct; +use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}}; fn main() { - TestStruct + ThirdStruct } "#, ); -- cgit v1.2.3 From 1e458efe628215dfc07943f8dd39f66ac059d3de Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 10 Nov 2020 12:00:42 +0200 Subject: Add braces to functions and macros --- .../completion/src/completions/complete_magic.rs | 66 +++++++++++++++++--- crates/completion/src/render/macro_.rs | 72 ++++++++++++---------- 2 files changed, 99 insertions(+), 39 deletions(-) (limited to 'crates/completion') diff --git a/crates/completion/src/completions/complete_magic.rs b/crates/completion/src/completions/complete_magic.rs index 15af2190d..4cf21e19d 100644 --- a/crates/completion/src/completions/complete_magic.rs +++ b/crates/completion/src/completions/complete_magic.rs @@ -1,7 +1,8 @@ //! TODO kb move this into the complete_unqualified_path when starts to work properly use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour}; -use hir::Query; +use either::Either; +use hir::{db::HirDatabase, MacroDef, ModuleDef, Query}; use itertools::Itertools; use syntax::{algo, AstNode}; use text_edit::TextEdit; @@ -28,14 +29,23 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> // TODO kb use imports_locator instead? .query_external_importables(ctx.db, Query::new(&potential_import_name).limit(40)) .unique() - .filter_map(|import_candidate| match import_candidate { - either::Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), - either::Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), + .filter_map(|import_candidate| { + let use_path = match import_candidate { + Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), + Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), + }?; + // TODO kb need to omit braces when there are some already. + // maybe remove braces completely? + Some((use_path, additional_completion(ctx.db, import_candidate))) }) - .filter_map(|mod_path| { + .filter_map(|(mod_path, additional_completion)| { let mut builder = TextEdit::builder(); - let correct_qualifier = mod_path.segments.last()?.to_string(); + let correct_qualifier = format!( + "{}{}", + mod_path.segments.last()?, + additional_completion.unwrap_or_default() + ); builder.replace(anchor.syntax().text_range(), correct_qualifier); // TODO kb: assists already have the merge behaviour setting, need to unite both @@ -60,6 +70,21 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Some(()) } +fn additional_completion( + db: &dyn HirDatabase, + import_candidate: Either, +) -> Option { + match import_candidate { + Either::Left(ModuleDef::Function(_)) => Some("()".to_string()), + Either::Right(macro_def) => { + let (left_brace, right_brace) = + crate::render::macro_::guess_macro_braces(db, macro_def); + Some(format!("!{}{}", left_brace, right_brace)) + } + _ => None, + } +} + #[cfg(test)] mod tests { use crate::test_utils::check_edit; @@ -83,7 +108,34 @@ fn main() { use dep::io::stdin; fn main() { - stdin + stdin() +} +"#, + ); + } + + #[test] + fn macro_magic_completion() { + check_edit( + "dep::macro_with_curlies", + r#" +//- /lib.rs crate:dep +/// Please call me as macro_with_curlies! {} +#[macro_export] +macro_rules! macro_with_curlies { + () => {} +} + +//- /main.rs crate:main deps:dep +fn main() { + curli<|> +} +"#, + r#" +use dep::macro_with_curlies; + +fn main() { + macro_with_curlies! {} } "#, ); diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs index 96be59cc3..b41c00b98 100644 --- a/crates/completion/src/render/macro_.rs +++ b/crates/completion/src/render/macro_.rs @@ -1,6 +1,6 @@ //! Renderer for macro invocations. -use hir::{Documentation, HasSource}; +use hir::{db::HirDatabase, Documentation, HasAttrs, HasSource}; use syntax::display::macro_label; use test_utils::mark; @@ -27,12 +27,48 @@ struct MacroRender<'a> { ket: &'static str, } +pub fn guess_macro_braces( + db: &dyn HirDatabase, + macro_: hir::MacroDef, +) -> (&'static str, &'static str) { + let macro_name = match macro_.name(db) { + Some(name) => name.to_string(), + None => return ("(", ")"), + }; + let macro_docs = macro_.docs(db); + let macro_docs = macro_docs.as_ref().map(Documentation::as_str).unwrap_or(""); + + let mut votes = [0, 0, 0]; + for (idx, s) in macro_docs.match_indices(¯o_name) { + let (before, after) = (¯o_docs[..idx], ¯o_docs[idx + s.len()..]); + // Ensure to match the full word + if after.starts_with('!') + && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) + { + // It may have spaces before the braces like `foo! {}` + match after[1..].chars().find(|&c| !c.is_whitespace()) { + Some('{') => votes[0] += 1, + Some('[') => votes[1] += 1, + Some('(') => votes[2] += 1, + _ => {} + } + } + } + + // Insert a space before `{}`. + // We prefer the last one when some votes equal. + let (_vote, (bra, ket)) = votes + .iter() + .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) + .max_by_key(|&(&vote, _)| vote) + .unwrap(); + (*bra, *ket) +} + impl<'a> MacroRender<'a> { fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> { let docs = ctx.docs(macro_); - let docs_str = docs.as_ref().map_or("", |s| s.as_str()); - let (bra, ket) = guess_macro_braces(&name, docs_str); - + let (bra, ket) = guess_macro_braces(ctx.db(), macro_); MacroRender { ctx, name, macro_, docs, bra, ket } } @@ -97,34 +133,6 @@ impl<'a> MacroRender<'a> { } } -fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) { - let mut votes = [0, 0, 0]; - for (idx, s) in docs.match_indices(¯o_name) { - let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); - // Ensure to match the full word - if after.starts_with('!') - && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) - { - // It may have spaces before the braces like `foo! {}` - match after[1..].chars().find(|&c| !c.is_whitespace()) { - Some('{') => votes[0] += 1, - Some('[') => votes[1] += 1, - Some('(') => votes[2] += 1, - _ => {} - } - } - } - - // Insert a space before `{}`. - // We prefer the last one when some votes equal. - let (_vote, (bra, ket)) = votes - .iter() - .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) - .max_by_key(|&(&vote, _)| vote) - .unwrap(); - (*bra, *ket) -} - #[cfg(test)] mod tests { use test_utils::mark; -- cgit v1.2.3 From 0e050fc3eba251f3debf964c7779c522e5639cd8 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 10 Nov 2020 23:40:07 +0200 Subject: Allow to configure the merge behavior --- crates/completion/src/completions/complete_magic.rs | 7 ++----- crates/completion/src/config.rs | 4 ++++ 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'crates/completion') diff --git a/crates/completion/src/completions/complete_magic.rs b/crates/completion/src/completions/complete_magic.rs index 4cf21e19d..58509fc5b 100644 --- a/crates/completion/src/completions/complete_magic.rs +++ b/crates/completion/src/completions/complete_magic.rs @@ -1,6 +1,6 @@ //! TODO kb move this into the complete_unqualified_path when starts to work properly -use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour}; +use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; use either::Either; use hir::{db::HirDatabase, MacroDef, ModuleDef, Query}; use itertools::Itertools; @@ -48,10 +48,7 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> ); builder.replace(anchor.syntax().text_range(), correct_qualifier); - // TODO kb: assists already have the merge behaviour setting, need to unite both - // also consider a settings toggle for this particular feature? - let rewriter = - insert_use(&import_scope, mod_path_to_ast(&mod_path), Some(MergeBehaviour::Full)); + let rewriter = insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); let old_ast = rewriter.rewrite_root()?; algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut builder); diff --git a/crates/completion/src/config.rs b/crates/completion/src/config.rs index 71b49ace8..82874ff25 100644 --- a/crates/completion/src/config.rs +++ b/crates/completion/src/config.rs @@ -4,12 +4,15 @@ //! module, and we use to statically check that we only produce snippet //! completions if we are allowed to. +use assists::utils::MergeBehaviour; + #[derive(Clone, Debug, PartialEq, Eq)] pub struct CompletionConfig { pub enable_postfix_completions: bool, pub add_call_parenthesis: bool, pub add_call_argument_snippets: bool, pub snippet_cap: Option, + pub merge: Option, } impl CompletionConfig { @@ -30,6 +33,7 @@ impl Default for CompletionConfig { add_call_parenthesis: true, add_call_argument_snippets: true, snippet_cap: Some(SnippetCap { _private: () }), + merge: Some(MergeBehaviour::Full), } } } -- cgit v1.2.3 From d1556550f83b7b8e9dd42c80ab6e08a632dfd256 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 13 Nov 2020 18:32:30 +0200 Subject: Rename the module --- crates/completion/src/completions.rs | 2 +- .../completion/src/completions/complete_magic.rs | 169 --------------------- crates/completion/src/completions/magic.rs | 165 ++++++++++++++++++++ 3 files changed, 166 insertions(+), 170 deletions(-) delete mode 100644 crates/completion/src/completions/complete_magic.rs create mode 100644 crates/completion/src/completions/magic.rs (limited to 'crates/completion') diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 99db5f998..4abb10156 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -13,7 +13,7 @@ pub(crate) mod postfix; pub(crate) mod macro_in_item_position; pub(crate) mod trait_impl; pub(crate) mod mod_; -pub(crate) mod complete_magic; +pub(crate) mod magic; use hir::{ModPath, ScopeDef, Type}; diff --git a/crates/completion/src/completions/complete_magic.rs b/crates/completion/src/completions/complete_magic.rs deleted file mode 100644 index 58509fc5b..000000000 --- a/crates/completion/src/completions/complete_magic.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! TODO kb move this into the complete_unqualified_path when starts to work properly - -use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; -use either::Either; -use hir::{db::HirDatabase, MacroDef, ModuleDef, Query}; -use itertools::Itertools; -use syntax::{algo, AstNode}; -use text_edit::TextEdit; - -use crate::{context::CompletionContext, item::CompletionKind, CompletionItem, CompletionItemKind}; - -use super::Completions; - -// TODO kb when typing, completes partial results, need to rerun manually to see the proper ones -pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { - if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { - return None; - } - let current_module = ctx.scope.module()?; - let anchor = ctx.name_ref_syntax.as_ref()?; - let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; - - // TODO kb consider heuristics, such as "don't show `hash_map` import if `HashMap` is the import for completion" - // also apply completion ordering - let potential_import_name = ctx.token.to_string(); - - let possible_imports = ctx - .krate? - // TODO kb use imports_locator instead? - .query_external_importables(ctx.db, Query::new(&potential_import_name).limit(40)) - .unique() - .filter_map(|import_candidate| { - let use_path = match import_candidate { - Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), - Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), - }?; - // TODO kb need to omit braces when there are some already. - // maybe remove braces completely? - Some((use_path, additional_completion(ctx.db, import_candidate))) - }) - .filter_map(|(mod_path, additional_completion)| { - let mut builder = TextEdit::builder(); - - let correct_qualifier = format!( - "{}{}", - mod_path.segments.last()?, - additional_completion.unwrap_or_default() - ); - builder.replace(anchor.syntax().text_range(), correct_qualifier); - - let rewriter = insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); - let old_ast = rewriter.rewrite_root()?; - algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut builder); - - let completion_item: CompletionItem = CompletionItem::new( - CompletionKind::Magic, - ctx.source_range(), - mod_path.to_string(), - ) - .kind(CompletionItemKind::Struct) - .text_edit(builder.finish()) - .into(); - Some(completion_item) - }); - acc.add_all(possible_imports); - - Some(()) -} - -fn additional_completion( - db: &dyn HirDatabase, - import_candidate: Either, -) -> Option { - match import_candidate { - Either::Left(ModuleDef::Function(_)) => Some("()".to_string()), - Either::Right(macro_def) => { - let (left_brace, right_brace) = - crate::render::macro_::guess_macro_braces(db, macro_def); - Some(format!("!{}{}", left_brace, right_brace)) - } - _ => None, - } -} - -#[cfg(test)] -mod tests { - use crate::test_utils::check_edit; - - #[test] - fn function_magic_completion() { - check_edit( - "dep::io::stdin", - r#" -//- /lib.rs crate:dep -pub mod io { - pub fn stdin() {} -}; - -//- /main.rs crate:main deps:dep -fn main() { - stdi<|> -} -"#, - r#" -use dep::io::stdin; - -fn main() { - stdin() -} -"#, - ); - } - - #[test] - fn macro_magic_completion() { - check_edit( - "dep::macro_with_curlies", - r#" -//- /lib.rs crate:dep -/// Please call me as macro_with_curlies! {} -#[macro_export] -macro_rules! macro_with_curlies { - () => {} -} - -//- /main.rs crate:main deps:dep -fn main() { - curli<|> -} -"#, - r#" -use dep::macro_with_curlies; - -fn main() { - macro_with_curlies! {} -} -"#, - ); - } - - #[test] - fn case_insensitive_magic_completion_works() { - check_edit( - "dep::some_module::ThirdStruct", - r#" -//- /lib.rs crate:dep -pub struct FirstStruct; -pub mod some_module { - pub struct SecondStruct; - pub struct ThirdStruct; -} - -//- /main.rs crate:main deps:dep -use dep::{FirstStruct, some_module::SecondStruct}; - -fn main() { - this<|> -} -"#, - r#" -use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}}; - -fn main() { - ThirdStruct -} -"#, - ); - } -} diff --git a/crates/completion/src/completions/magic.rs b/crates/completion/src/completions/magic.rs new file mode 100644 index 000000000..34fc35847 --- /dev/null +++ b/crates/completion/src/completions/magic.rs @@ -0,0 +1,165 @@ +//! TODO kb move this into the complete_unqualified_path when starts to work properly + +use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; +use either::Either; +use hir::{db::HirDatabase, MacroDef, ModuleDef, Query}; +use itertools::Itertools; +use syntax::{algo, AstNode}; +use text_edit::TextEdit; + +use crate::{context::CompletionContext, item::CompletionKind, CompletionItem, CompletionItemKind}; + +use super::Completions; + +// TODO kb add a setting toggle for this feature? +pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { + if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { + return None; + } + let current_module = ctx.scope.module()?; + let anchor = ctx.name_ref_syntax.as_ref()?; + let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; + + let potential_import_name = ctx.token.to_string(); + + let possible_imports = ctx + .krate? + // TODO kb use imports_locator instead? + .query_external_importables(ctx.db, Query::new(&potential_import_name).limit(40)) + .unique() + .filter_map(|import_candidate| { + let use_path = match import_candidate { + Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), + Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), + }?; + Some((use_path, additional_completion(ctx.db, import_candidate))) + }) + .filter_map(|(mod_path, additional_completion)| { + let mut builder = TextEdit::builder(); + + let correct_qualifier = format!( + "{}{}", + mod_path.segments.last()?, + additional_completion.unwrap_or_default() + ); + builder.replace(anchor.syntax().text_range(), correct_qualifier); + + let rewriter = insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); + let old_ast = rewriter.rewrite_root()?; + algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut builder); + + let completion_item: CompletionItem = CompletionItem::new( + CompletionKind::Magic, + ctx.source_range(), + mod_path.to_string(), + ) + .kind(CompletionItemKind::Struct) + .text_edit(builder.finish()) + .into(); + Some(completion_item) + }); + acc.add_all(possible_imports); + + Some(()) +} + +fn additional_completion( + db: &dyn HirDatabase, + import_candidate: Either, +) -> Option { + match import_candidate { + Either::Left(ModuleDef::Function(_)) => Some("()".to_string()), + Either::Right(macro_def) => { + let (left_brace, right_brace) = + crate::render::macro_::guess_macro_braces(db, macro_def); + Some(format!("!{}{}", left_brace, right_brace)) + } + _ => None, + } +} + +#[cfg(test)] +mod tests { + use crate::test_utils::check_edit; + + #[test] + fn function_magic_completion() { + check_edit( + "dep::io::stdin", + r#" +//- /lib.rs crate:dep +pub mod io { + pub fn stdin() {} +}; + +//- /main.rs crate:main deps:dep +fn main() { + stdi<|> +} +"#, + r#" +use dep::io::stdin; + +fn main() { + stdin() +} +"#, + ); + } + + #[test] + fn macro_magic_completion() { + check_edit( + "dep::macro_with_curlies", + r#" +//- /lib.rs crate:dep +/// Please call me as macro_with_curlies! {} +#[macro_export] +macro_rules! macro_with_curlies { + () => {} +} + +//- /main.rs crate:main deps:dep +fn main() { + curli<|> +} +"#, + r#" +use dep::macro_with_curlies; + +fn main() { + macro_with_curlies! {} +} +"#, + ); + } + + #[test] + fn case_insensitive_magic_completion_works() { + check_edit( + "dep::some_module::ThirdStruct", + r#" +//- /lib.rs crate:dep +pub struct FirstStruct; +pub mod some_module { + pub struct SecondStruct; + pub struct ThirdStruct; +} + +//- /main.rs crate:main deps:dep +use dep::{FirstStruct, some_module::SecondStruct}; + +fn main() { + this<|> +} +"#, + r#" +use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}}; + +fn main() { + ThirdStruct +} +"#, + ); + } +} -- cgit v1.2.3 From 4c8edd003aa447bd2da10fd81b24f582deacdc11 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 13 Nov 2020 19:16:56 +0200 Subject: Use imports_locator --- crates/completion/src/completions/magic.rs | 74 +++++++++++++++--------------- crates/completion/src/lib.rs | 2 +- 2 files changed, 37 insertions(+), 39 deletions(-) (limited to 'crates/completion') diff --git a/crates/completion/src/completions/magic.rs b/crates/completion/src/completions/magic.rs index 34fc35847..272c9a354 100644 --- a/crates/completion/src/completions/magic.rs +++ b/crates/completion/src/completions/magic.rs @@ -2,8 +2,8 @@ use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; use either::Either; -use hir::{db::HirDatabase, MacroDef, ModuleDef, Query}; -use itertools::Itertools; +use hir::{db::HirDatabase, MacroDef, ModuleDef}; +use ide_db::imports_locator; use syntax::{algo, AstNode}; use text_edit::TextEdit; @@ -22,42 +22,40 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> let potential_import_name = ctx.token.to_string(); - let possible_imports = ctx - .krate? - // TODO kb use imports_locator instead? - .query_external_importables(ctx.db, Query::new(&potential_import_name).limit(40)) - .unique() - .filter_map(|import_candidate| { - let use_path = match import_candidate { - Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), - Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), - }?; - Some((use_path, additional_completion(ctx.db, import_candidate))) - }) - .filter_map(|(mod_path, additional_completion)| { - let mut builder = TextEdit::builder(); - - let correct_qualifier = format!( - "{}{}", - mod_path.segments.last()?, - additional_completion.unwrap_or_default() - ); - builder.replace(anchor.syntax().text_range(), correct_qualifier); - - let rewriter = insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); - let old_ast = rewriter.rewrite_root()?; - algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut builder); - - let completion_item: CompletionItem = CompletionItem::new( - CompletionKind::Magic, - ctx.source_range(), - mod_path.to_string(), - ) - .kind(CompletionItemKind::Struct) - .text_edit(builder.finish()) - .into(); - Some(completion_item) - }); + let possible_imports = + imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name) + .filter_map(|import_candidate| { + let use_path = match import_candidate { + Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), + Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), + }?; + Some((use_path, additional_completion(ctx.db, import_candidate))) + }) + .filter_map(|(mod_path, additional_completion)| { + let mut builder = TextEdit::builder(); + + let correct_qualifier = format!( + "{}{}", + mod_path.segments.last()?, + additional_completion.unwrap_or_default() + ); + builder.replace(anchor.syntax().text_range(), correct_qualifier); + + let rewriter = + insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); + let old_ast = rewriter.rewrite_root()?; + algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut builder); + + let completion_item: CompletionItem = CompletionItem::new( + CompletionKind::Magic, + ctx.source_range(), + mod_path.to_string(), + ) + .kind(CompletionItemKind::Struct) + .text_edit(builder.finish()) + .into(); + Some(completion_item) + }); acc.add_all(possible_imports); Some(()) diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs index e920fa6b5..8323af8b2 100644 --- a/crates/completion/src/lib.rs +++ b/crates/completion/src/lib.rs @@ -118,7 +118,7 @@ pub fn completions( completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); completions::trait_impl::complete_trait_impl(&mut acc, &ctx); completions::mod_::complete_mod(&mut acc, &ctx); - completions::complete_magic::complete_magic(&mut acc, &ctx); + completions::magic::complete_magic(&mut acc, &ctx); Some(acc) } -- cgit v1.2.3 From 1598740292c29613ef2b384a82de3a2735bc5566 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 13 Nov 2020 21:25:45 +0200 Subject: Reuse existing element rendering --- crates/completion/src/completions/magic.rs | 82 +++++++++++++----------------- crates/completion/src/item.rs | 4 ++ crates/completion/src/render/macro_.rs | 72 ++++++++++++-------------- 3 files changed, 70 insertions(+), 88 deletions(-) (limited to 'crates/completion') diff --git a/crates/completion/src/completions/magic.rs b/crates/completion/src/completions/magic.rs index 272c9a354..ef0fc27ba 100644 --- a/crates/completion/src/completions/magic.rs +++ b/crates/completion/src/completions/magic.rs @@ -2,12 +2,14 @@ use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; use either::Either; -use hir::{db::HirDatabase, MacroDef, ModuleDef}; +use hir::ScopeDef; use ide_db::imports_locator; use syntax::{algo, AstNode}; -use text_edit::TextEdit; -use crate::{context::CompletionContext, item::CompletionKind, CompletionItem, CompletionItemKind}; +use crate::{ + context::CompletionContext, + render::{render_resolution, RenderContext}, +}; use super::Completions; @@ -25,57 +27,41 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> let possible_imports = imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name) .filter_map(|import_candidate| { - let use_path = match import_candidate { - Either::Left(module_def) => current_module.find_use_path(ctx.db, module_def), - Either::Right(macro_def) => current_module.find_use_path(ctx.db, macro_def), - }?; - Some((use_path, additional_completion(ctx.db, import_candidate))) + Some(match import_candidate { + Either::Left(module_def) => ( + current_module.find_use_path(ctx.db, module_def)?, + ScopeDef::ModuleDef(module_def), + ), + Either::Right(macro_def) => ( + current_module.find_use_path(ctx.db, macro_def)?, + ScopeDef::MacroDef(macro_def), + ), + }) }) - .filter_map(|(mod_path, additional_completion)| { - let mut builder = TextEdit::builder(); + .filter_map(|(mod_path, definition)| { + let mut resolution_with_missing_import = render_resolution( + RenderContext::new(ctx), + mod_path.segments.last()?.to_string(), + &definition, + )?; - let correct_qualifier = format!( - "{}{}", - mod_path.segments.last()?, - additional_completion.unwrap_or_default() - ); - builder.replace(anchor.syntax().text_range(), correct_qualifier); + let mut text_edits = + resolution_with_missing_import.text_edit().to_owned().into_builder(); let rewriter = insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); let old_ast = rewriter.rewrite_root()?; - algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut builder); - - let completion_item: CompletionItem = CompletionItem::new( - CompletionKind::Magic, - ctx.source_range(), - mod_path.to_string(), - ) - .kind(CompletionItemKind::Struct) - .text_edit(builder.finish()) - .into(); - Some(completion_item) + algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); + + resolution_with_missing_import.update_text_edit(text_edits.finish()); + + Some(resolution_with_missing_import) }); - acc.add_all(possible_imports); + acc.add_all(possible_imports); Some(()) } -fn additional_completion( - db: &dyn HirDatabase, - import_candidate: Either, -) -> Option { - match import_candidate { - Either::Left(ModuleDef::Function(_)) => Some("()".to_string()), - Either::Right(macro_def) => { - let (left_brace, right_brace) = - crate::render::macro_::guess_macro_braces(db, macro_def); - Some(format!("!{}{}", left_brace, right_brace)) - } - _ => None, - } -} - #[cfg(test)] mod tests { use crate::test_utils::check_edit; @@ -83,7 +69,7 @@ mod tests { #[test] fn function_magic_completion() { check_edit( - "dep::io::stdin", + "stdin", r#" //- /lib.rs crate:dep pub mod io { @@ -99,7 +85,7 @@ fn main() { use dep::io::stdin; fn main() { - stdin() + stdin()$0 } "#, ); @@ -108,7 +94,7 @@ fn main() { #[test] fn macro_magic_completion() { check_edit( - "dep::macro_with_curlies", + "macro_with_curlies!", r#" //- /lib.rs crate:dep /// Please call me as macro_with_curlies! {} @@ -126,7 +112,7 @@ fn main() { use dep::macro_with_curlies; fn main() { - macro_with_curlies! {} + macro_with_curlies! {$0} } "#, ); @@ -135,7 +121,7 @@ fn main() { #[test] fn case_insensitive_magic_completion_works() { check_edit( - "dep::some_module::ThirdStruct", + "ThirdStruct", r#" //- /lib.rs crate:dep pub struct FirstStruct; diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs index f23913935..53a12a763 100644 --- a/crates/completion/src/item.rs +++ b/crates/completion/src/item.rs @@ -218,6 +218,10 @@ impl CompletionItem { &self.text_edit } + pub fn update_text_edit(&mut self, new_text_edit: TextEdit) { + self.text_edit = new_text_edit; + } + /// Short one-line additional information, like a type pub fn detail(&self) -> Option<&str> { self.detail.as_deref() diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs index b41c00b98..96be59cc3 100644 --- a/crates/completion/src/render/macro_.rs +++ b/crates/completion/src/render/macro_.rs @@ -1,6 +1,6 @@ //! Renderer for macro invocations. -use hir::{db::HirDatabase, Documentation, HasAttrs, HasSource}; +use hir::{Documentation, HasSource}; use syntax::display::macro_label; use test_utils::mark; @@ -27,48 +27,12 @@ struct MacroRender<'a> { ket: &'static str, } -pub fn guess_macro_braces( - db: &dyn HirDatabase, - macro_: hir::MacroDef, -) -> (&'static str, &'static str) { - let macro_name = match macro_.name(db) { - Some(name) => name.to_string(), - None => return ("(", ")"), - }; - let macro_docs = macro_.docs(db); - let macro_docs = macro_docs.as_ref().map(Documentation::as_str).unwrap_or(""); - - let mut votes = [0, 0, 0]; - for (idx, s) in macro_docs.match_indices(¯o_name) { - let (before, after) = (¯o_docs[..idx], ¯o_docs[idx + s.len()..]); - // Ensure to match the full word - if after.starts_with('!') - && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) - { - // It may have spaces before the braces like `foo! {}` - match after[1..].chars().find(|&c| !c.is_whitespace()) { - Some('{') => votes[0] += 1, - Some('[') => votes[1] += 1, - Some('(') => votes[2] += 1, - _ => {} - } - } - } - - // Insert a space before `{}`. - // We prefer the last one when some votes equal. - let (_vote, (bra, ket)) = votes - .iter() - .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) - .max_by_key(|&(&vote, _)| vote) - .unwrap(); - (*bra, *ket) -} - impl<'a> MacroRender<'a> { fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> { let docs = ctx.docs(macro_); - let (bra, ket) = guess_macro_braces(ctx.db(), macro_); + let docs_str = docs.as_ref().map_or("", |s| s.as_str()); + let (bra, ket) = guess_macro_braces(&name, docs_str); + MacroRender { ctx, name, macro_, docs, bra, ket } } @@ -133,6 +97,34 @@ impl<'a> MacroRender<'a> { } } +fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) { + let mut votes = [0, 0, 0]; + for (idx, s) in docs.match_indices(¯o_name) { + let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); + // Ensure to match the full word + if after.starts_with('!') + && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) + { + // It may have spaces before the braces like `foo! {}` + match after[1..].chars().find(|&c| !c.is_whitespace()) { + Some('{') => votes[0] += 1, + Some('[') => votes[1] += 1, + Some('(') => votes[2] += 1, + _ => {} + } + } + } + + // Insert a space before `{}`. + // We prefer the last one when some votes equal. + let (_vote, (bra, ket)) = votes + .iter() + .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) + .max_by_key(|&(&vote, _)| vote) + .unwrap(); + (*bra, *ket) +} + #[cfg(test)] mod tests { use test_utils::mark; -- cgit v1.2.3 From 46514448b740b82e4f5f9cf742c6f8c6caec1d38 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 13 Nov 2020 22:31:41 +0200 Subject: Tweak the search limits a bit --- crates/completion/src/completions/magic.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'crates/completion') diff --git a/crates/completion/src/completions/magic.rs b/crates/completion/src/completions/magic.rs index ef0fc27ba..2ac6f94a9 100644 --- a/crates/completion/src/completions/magic.rs +++ b/crates/completion/src/completions/magic.rs @@ -13,11 +13,13 @@ use crate::{ use super::Completions; +// TODO kb reuse auto_import assist approach to add trait completion // TODO kb add a setting toggle for this feature? pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { return None; } + let _p = profile::span("complete_magic"); let current_module = ctx.scope.module()?; let anchor = ctx.name_ref_syntax.as_ref()?; let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; @@ -25,7 +27,7 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> let potential_import_name = ctx.token.to_string(); let possible_imports = - imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name) + imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name, 400) .filter_map(|import_candidate| { Some(match import_candidate { Either::Left(module_def) => ( -- cgit v1.2.3 From 3b0fc4d7f2b922a1b7d8d32fc0b065e4023d749b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 14 Nov 2020 01:07:03 +0200 Subject: Omit modules during autocompletion --- crates/completion/src/completions/magic.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'crates/completion') diff --git a/crates/completion/src/completions/magic.rs b/crates/completion/src/completions/magic.rs index 2ac6f94a9..0c4db0199 100644 --- a/crates/completion/src/completions/magic.rs +++ b/crates/completion/src/completions/magic.rs @@ -2,7 +2,7 @@ use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; use either::Either; -use hir::ScopeDef; +use hir::{ModuleDef, ScopeDef}; use ide_db::imports_locator; use syntax::{algo, AstNode}; @@ -13,7 +13,6 @@ use crate::{ use super::Completions; -// TODO kb reuse auto_import assist approach to add trait completion // TODO kb add a setting toggle for this feature? pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { @@ -28,17 +27,18 @@ pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> let possible_imports = imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name, 400) - .filter_map(|import_candidate| { - Some(match import_candidate { - Either::Left(module_def) => ( - current_module.find_use_path(ctx.db, module_def)?, - ScopeDef::ModuleDef(module_def), - ), - Either::Right(macro_def) => ( - current_module.find_use_path(ctx.db, macro_def)?, - ScopeDef::MacroDef(macro_def), - ), - }) + .filter_map(|import_candidate| match import_candidate { + // when completing outside the use declaration, modules are pretty useless + // and tend to bloat the completion suggestions a lot + Either::Left(ModuleDef::Module(_)) => None, + Either::Left(module_def) => Some(( + current_module.find_use_path(ctx.db, module_def)?, + ScopeDef::ModuleDef(module_def), + )), + Either::Right(macro_def) => Some(( + current_module.find_use_path(ctx.db, macro_def)?, + ScopeDef::MacroDef(macro_def), + )), }) .filter_map(|(mod_path, definition)| { let mut resolution_with_missing_import = render_resolution( -- cgit v1.2.3 From ee99620754cdcfbab28a2c067dfa31087376d6c3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 14 Nov 2020 01:26:31 +0200 Subject: Move autoimport completion into the unqialified_path module --- crates/completion/Cargo.toml | 1 - crates/completion/src/completions.rs | 1 - crates/completion/src/completions/magic.rs | 151 --------------------- .../completion/src/completions/unqualified_path.rs | 141 ++++++++++++++++++- crates/completion/src/lib.rs | 1 - 5 files changed, 139 insertions(+), 156 deletions(-) delete mode 100644 crates/completion/src/completions/magic.rs (limited to 'crates/completion') diff --git a/crates/completion/Cargo.toml b/crates/completion/Cargo.toml index 799b4a3d5..e7df9d955 100644 --- a/crates/completion/Cargo.toml +++ b/crates/completion/Cargo.toml @@ -22,7 +22,6 @@ text_edit = { path = "../text_edit", version = "0.0.0" } base_db = { path = "../base_db", version = "0.0.0" } ide_db = { path = "../ide_db", version = "0.0.0" } profile = { path = "../profile", version = "0.0.0" } -assists = { path = "../assists", version = "0.0.0" } test_utils = { path = "../test_utils", version = "0.0.0" } # completions crate should depend only on the top-level `hir` package. if you need diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 4abb10156..75dbb1a23 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -13,7 +13,6 @@ pub(crate) mod postfix; pub(crate) mod macro_in_item_position; pub(crate) mod trait_impl; pub(crate) mod mod_; -pub(crate) mod magic; use hir::{ModPath, ScopeDef, Type}; diff --git a/crates/completion/src/completions/magic.rs b/crates/completion/src/completions/magic.rs deleted file mode 100644 index 0c4db0199..000000000 --- a/crates/completion/src/completions/magic.rs +++ /dev/null @@ -1,151 +0,0 @@ -//! TODO kb move this into the complete_unqualified_path when starts to work properly - -use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; -use either::Either; -use hir::{ModuleDef, ScopeDef}; -use ide_db::imports_locator; -use syntax::{algo, AstNode}; - -use crate::{ - context::CompletionContext, - render::{render_resolution, RenderContext}, -}; - -use super::Completions; - -// TODO kb add a setting toggle for this feature? -pub(crate) fn complete_magic(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { - if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { - return None; - } - let _p = profile::span("complete_magic"); - let current_module = ctx.scope.module()?; - let anchor = ctx.name_ref_syntax.as_ref()?; - let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; - - let potential_import_name = ctx.token.to_string(); - - let possible_imports = - imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name, 400) - .filter_map(|import_candidate| match import_candidate { - // when completing outside the use declaration, modules are pretty useless - // and tend to bloat the completion suggestions a lot - Either::Left(ModuleDef::Module(_)) => None, - Either::Left(module_def) => Some(( - current_module.find_use_path(ctx.db, module_def)?, - ScopeDef::ModuleDef(module_def), - )), - Either::Right(macro_def) => Some(( - current_module.find_use_path(ctx.db, macro_def)?, - ScopeDef::MacroDef(macro_def), - )), - }) - .filter_map(|(mod_path, definition)| { - let mut resolution_with_missing_import = render_resolution( - RenderContext::new(ctx), - mod_path.segments.last()?.to_string(), - &definition, - )?; - - let mut text_edits = - resolution_with_missing_import.text_edit().to_owned().into_builder(); - - let rewriter = - insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); - let old_ast = rewriter.rewrite_root()?; - algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); - - resolution_with_missing_import.update_text_edit(text_edits.finish()); - - Some(resolution_with_missing_import) - }); - - acc.add_all(possible_imports); - Some(()) -} - -#[cfg(test)] -mod tests { - use crate::test_utils::check_edit; - - #[test] - fn function_magic_completion() { - check_edit( - "stdin", - r#" -//- /lib.rs crate:dep -pub mod io { - pub fn stdin() {} -}; - -//- /main.rs crate:main deps:dep -fn main() { - stdi<|> -} -"#, - r#" -use dep::io::stdin; - -fn main() { - stdin()$0 -} -"#, - ); - } - - #[test] - fn macro_magic_completion() { - check_edit( - "macro_with_curlies!", - r#" -//- /lib.rs crate:dep -/// Please call me as macro_with_curlies! {} -#[macro_export] -macro_rules! macro_with_curlies { - () => {} -} - -//- /main.rs crate:main deps:dep -fn main() { - curli<|> -} -"#, - r#" -use dep::macro_with_curlies; - -fn main() { - macro_with_curlies! {$0} -} -"#, - ); - } - - #[test] - fn case_insensitive_magic_completion_works() { - check_edit( - "ThirdStruct", - r#" -//- /lib.rs crate:dep -pub struct FirstStruct; -pub mod some_module { - pub struct SecondStruct; - pub struct ThirdStruct; -} - -//- /main.rs crate:main deps:dep -use dep::{FirstStruct, some_module::SecondStruct}; - -fn main() { - this<|> -} -"#, - r#" -use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}}; - -fn main() { - ThirdStruct -} -"#, - ); - } -} diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index 7df58e1da..ecda37862 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs @@ -1,10 +1,16 @@ //! Completion of names from the current scope, e.g. locals and imported items. +use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; +use either::Either; use hir::{Adt, ModuleDef, ScopeDef, Type}; -use syntax::AstNode; +use ide_db::imports_locator; +use syntax::{algo, AstNode}; use test_utils::mark; -use crate::{CompletionContext, Completions}; +use crate::{ + render::{render_resolution, RenderContext}, + CompletionContext, Completions, +}; pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { @@ -37,6 +43,56 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC } acc.add_resolution(ctx, name.to_string(), &res) }); + + fuzzy_completion(acc, ctx).unwrap_or_default() +} + +// TODO kb add a setting toggle for this feature? +fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { + let _p = profile::span("fuzzy_completion®"); + let current_module = ctx.scope.module()?; + let anchor = ctx.name_ref_syntax.as_ref()?; + let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; + + let potential_import_name = ctx.token.to_string(); + + let possible_imports = + imports_locator::find_similar_imports(&ctx.sema, ctx.krate?, &potential_import_name, 400) + .filter_map(|import_candidate| match import_candidate { + // when completing outside the use declaration, modules are pretty useless + // and tend to bloat the completion suggestions a lot + Either::Left(ModuleDef::Module(_)) => None, + Either::Left(module_def) => Some(( + current_module.find_use_path(ctx.db, module_def)?, + ScopeDef::ModuleDef(module_def), + )), + Either::Right(macro_def) => Some(( + current_module.find_use_path(ctx.db, macro_def)?, + ScopeDef::MacroDef(macro_def), + )), + }) + .filter_map(|(mod_path, definition)| { + let mut resolution_with_missing_import = render_resolution( + RenderContext::new(ctx), + mod_path.segments.last()?.to_string(), + &definition, + )?; + + let mut text_edits = + resolution_with_missing_import.text_edit().to_owned().into_builder(); + + let rewriter = + insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); + let old_ast = rewriter.rewrite_root()?; + algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); + + resolution_with_missing_import.update_text_edit(text_edits.finish()); + + Some(resolution_with_missing_import) + }); + + acc.add_all(possible_imports); + Some(()) } fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { @@ -676,4 +732,85 @@ impl My<|> "#]], ) } + + #[test] + fn function_magic_completion() { + check_edit( + "stdin", + r#" +//- /lib.rs crate:dep +pub mod io { + pub fn stdin() {} +}; + +//- /main.rs crate:main deps:dep +fn main() { + stdi<|> +} +"#, + r#" +use dep::io::stdin; + +fn main() { + stdin()$0 +} +"#, + ); + } + + #[test] + fn macro_magic_completion() { + check_edit( + "macro_with_curlies!", + r#" +//- /lib.rs crate:dep +/// Please call me as macro_with_curlies! {} +#[macro_export] +macro_rules! macro_with_curlies { + () => {} +} + +//- /main.rs crate:main deps:dep +fn main() { + curli<|> +} +"#, + r#" +use dep::macro_with_curlies; + +fn main() { + macro_with_curlies! {$0} +} +"#, + ); + } + + #[test] + fn case_insensitive_magic_completion_works() { + check_edit( + "ThirdStruct", + r#" +//- /lib.rs crate:dep +pub struct FirstStruct; +pub mod some_module { + pub struct SecondStruct; + pub struct ThirdStruct; +} + +//- /main.rs crate:main deps:dep +use dep::{FirstStruct, some_module::SecondStruct}; + +fn main() { + this<|> +} +"#, + r#" +use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}}; + +fn main() { + ThirdStruct +} +"#, + ); + } } diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs index 8323af8b2..cb6e0554e 100644 --- a/crates/completion/src/lib.rs +++ b/crates/completion/src/lib.rs @@ -118,7 +118,6 @@ pub fn completions( completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); completions::trait_impl::complete_trait_impl(&mut acc, &ctx); completions::mod_::complete_mod(&mut acc, &ctx); - completions::magic::complete_magic(&mut acc, &ctx); Some(acc) } -- cgit v1.2.3 From 38ef1fd4ad7fd26439201a1a4147a7d90a13601f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 14 Nov 2020 11:59:23 +0200 Subject: Better filter mod paths --- .../completion/src/completions/unqualified_path.rs | 52 +++++++++++----------- 1 file changed, 27 insertions(+), 25 deletions(-) (limited to 'crates/completion') diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index ecda37862..7ce92a07b 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs @@ -47,6 +47,30 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC fuzzy_completion(acc, ctx).unwrap_or_default() } +fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { + if let Some(Adt::Enum(enum_data)) = ty.as_adt() { + let variants = enum_data.variants(ctx.db); + + let module = if let Some(module) = ctx.scope.module() { + // Compute path from the completion site if available. + module + } else { + // Otherwise fall back to the enum's definition site. + enum_data.module(ctx.db) + }; + + for variant in variants { + if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) { + // Variants with trivial paths are already added by the existing completion logic, + // so we should avoid adding these twice + if path.segments.len() > 1 { + acc.add_qualified_enum_variant(ctx, variant, path); + } + } + } + } +} + // TODO kb add a setting toggle for this feature? fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { let _p = profile::span("fuzzy_completion®"); @@ -71,6 +95,7 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<() ScopeDef::MacroDef(macro_def), )), }) + .filter(|(mod_path, _)| mod_path.len() > 1) .filter_map(|(mod_path, definition)| { let mut resolution_with_missing_import = render_resolution( RenderContext::new(ctx), @@ -89,36 +114,13 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<() resolution_with_missing_import.update_text_edit(text_edits.finish()); Some(resolution_with_missing_import) - }); + }) + .take(20); acc.add_all(possible_imports); Some(()) } -fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { - if let Some(Adt::Enum(enum_data)) = ty.as_adt() { - let variants = enum_data.variants(ctx.db); - - let module = if let Some(module) = ctx.scope.module() { - // Compute path from the completion site if available. - module - } else { - // Otherwise fall back to the enum's definition site. - enum_data.module(ctx.db) - }; - - for variant in variants { - if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) { - // Variants with trivial paths are already added by the existing completion logic, - // so we should avoid adding these twice - if path.segments.len() > 1 { - acc.add_qualified_enum_variant(ctx, variant, path); - } - } - } - } -} - #[cfg(test)] mod tests { use expect_test::{expect, Expect}; -- cgit v1.2.3 From bbe1fbd1786b416908d3c6bc34c8cf805b39b761 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 14 Nov 2020 14:50:57 +0200 Subject: Qualify autoimport completion suggestions --- .../completion/src/completions/unqualified_path.rs | 35 ++++++++++++++-------- crates/completion/src/item.rs | 24 ++++++++++++--- 2 files changed, 43 insertions(+), 16 deletions(-) (limited to 'crates/completion') diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index 7ce92a07b..fca8d3a72 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs @@ -71,7 +71,6 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T } } -// TODO kb add a setting toggle for this feature? fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { let _p = profile::span("fuzzy_completion®"); let current_module = ctx.scope.module()?; @@ -97,23 +96,35 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<() }) .filter(|(mod_path, _)| mod_path.len() > 1) .filter_map(|(mod_path, definition)| { - let mut resolution_with_missing_import = render_resolution( - RenderContext::new(ctx), - mod_path.segments.last()?.to_string(), - &definition, - )?; + let use_to_insert = mod_path_to_ast(&mod_path); + let mut mod_path_without_last_segment = mod_path; + let name_after_import = mod_path_without_last_segment.segments.pop()?.to_string(); + + let resolution_with_missing_import = + render_resolution(RenderContext::new(ctx), name_after_import, &definition)?; + let lookup_string = resolution_with_missing_import.lookup().to_owned(); let mut text_edits = resolution_with_missing_import.text_edit().to_owned().into_builder(); - - let rewriter = - insert_use(&import_scope, mod_path_to_ast(&mod_path), ctx.config.merge); + let rewriter = insert_use(&import_scope, use_to_insert, ctx.config.merge); let old_ast = rewriter.rewrite_root()?; algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); - resolution_with_missing_import.update_text_edit(text_edits.finish()); - - Some(resolution_with_missing_import) + let qualifier_string = mod_path_without_last_segment.to_string(); + let qualified_label = if qualifier_string.is_empty() { + resolution_with_missing_import.label().to_owned() + } else { + format!("{}::{}", qualifier_string, resolution_with_missing_import.label()) + }; + + Some( + resolution_with_missing_import + .into_builder() + .text_edit(text_edits.finish()) + .label(qualified_label) + .lookup_by(lookup_string) + .build(), + ) }) .take(20); diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs index 53a12a763..24b9d036a 100644 --- a/crates/completion/src/item.rs +++ b/crates/completion/src/item.rs @@ -202,6 +202,26 @@ impl CompletionItem { ref_match: None, } } + + pub(crate) fn into_builder(self) -> Builder { + Builder { + source_range: self.source_range, + completion_kind: self.completion_kind, + label: self.label, + insert_text: None, + insert_text_format: self.insert_text_format, + detail: self.detail, + documentation: self.documentation, + lookup: self.lookup, + kind: self.kind, + text_edit: Some(self.text_edit), + deprecated: Some(self.deprecated), + trigger_call_info: Some(self.trigger_call_info), + score: self.score, + ref_match: self.ref_match, + } + } + /// What user sees in pop-up in the UI. pub fn label(&self) -> &str { &self.label @@ -218,10 +238,6 @@ impl CompletionItem { &self.text_edit } - pub fn update_text_edit(&mut self, new_text_edit: TextEdit) { - self.text_edit = new_text_edit; - } - /// Short one-line additional information, like a type pub fn detail(&self) -> Option<&str> { self.detail.as_deref() -- cgit v1.2.3 From 1de7848b5710f21cccf90dabc99a0cf6fcdabad3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 14 Nov 2020 14:59:03 +0200 Subject: Fix the other test --- .../completion/src/completions/unqualified_path.rs | 6 +++--- crates/completion/src/render.rs | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) (limited to 'crates/completion') diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index fca8d3a72..362d31f25 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs @@ -747,7 +747,7 @@ impl My<|> } #[test] - fn function_magic_completion() { + fn function_fuzzy_completion() { check_edit( "stdin", r#" @@ -772,7 +772,7 @@ fn main() { } #[test] - fn macro_magic_completion() { + fn macro_fuzzy_completion() { check_edit( "macro_with_curlies!", r#" @@ -799,7 +799,7 @@ fn main() { } #[test] - fn case_insensitive_magic_completion_works() { + fn struct_fuzzy_completion() { check_edit( "ThirdStruct", r#" diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index 1fa02c375..5a4353846 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs @@ -425,6 +425,28 @@ fn main() { let _: m::Spam = S<|> } insert: "m", kind: Module, }, + CompletionItem { + label: "m::Spam", + source_range: 75..76, + text_edit: TextEdit { + indels: [ + Indel { + insert: "use m::Spam;", + delete: 0..0, + }, + Indel { + insert: "\n\n", + delete: 0..0, + }, + Indel { + insert: "Spam", + delete: 75..76, + }, + ], + }, + kind: Enum, + lookup: "Spam", + }, CompletionItem { label: "m::Spam::Foo", source_range: 75..76, -- cgit v1.2.3 From 410996893489f6c64b472e6128f099f1de229806 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 16 Nov 2020 21:24:54 +0200 Subject: Remove query aliases --- crates/completion/src/completions/unqualified_path.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates/completion') diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index 362d31f25..4f8ec1e67 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs @@ -72,7 +72,7 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T } fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { - let _p = profile::span("fuzzy_completion®"); + let _p = profile::span("fuzzy_completion"); let current_module = ctx.scope.module()?; let anchor = ctx.name_ref_syntax.as_ref()?; let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; -- cgit v1.2.3 From d4128beb3d8c647674ae43407d0ed6edd71ff420 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 16 Nov 2020 23:16:41 +0200 Subject: Avoid turning completion objects into builders --- crates/completion/src/completions.rs | 8 +-- .../completion/src/completions/unqualified_path.rs | 42 +++--------- crates/completion/src/item.rs | 76 +++++++++++++--------- crates/completion/src/render.rs | 35 ++++++++-- crates/completion/src/render/enum_variant.rs | 10 ++- crates/completion/src/render/function.rs | 12 +++- crates/completion/src/render/macro_.rs | 12 +++- 7 files changed, 116 insertions(+), 79 deletions(-) (limited to 'crates/completion') diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 75dbb1a23..9b7d6c580 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -90,7 +90,7 @@ impl Completions { Some(it) => it, None => return, }; - if let Some(item) = render_macro(RenderContext::new(ctx), name, macro_) { + if let Some(item) = render_macro(RenderContext::new(ctx), None, name, macro_) { self.add(item); } } @@ -101,7 +101,7 @@ impl Completions { func: hir::Function, local_name: Option, ) { - let item = render_fn(RenderContext::new(ctx), local_name, func); + let item = render_fn(RenderContext::new(ctx), None, local_name, func); self.add(item) } @@ -123,7 +123,7 @@ impl Completions { variant: hir::EnumVariant, path: ModPath, ) { - let item = render_enum_variant(RenderContext::new(ctx), None, variant, Some(path)); + let item = render_enum_variant(RenderContext::new(ctx), None, None, variant, Some(path)); self.add(item); } @@ -133,7 +133,7 @@ impl Completions { variant: hir::EnumVariant, local_name: Option, ) { - let item = render_enum_variant(RenderContext::new(ctx), local_name, variant, None); + let item = render_enum_variant(RenderContext::new(ctx), None, local_name, variant, None); self.add(item); } } diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index 4f8ec1e67..86c143b63 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs @@ -1,14 +1,14 @@ //! Completion of names from the current scope, e.g. locals and imported items. -use assists::utils::{insert_use, mod_path_to_ast, ImportScope}; +use assists::utils::ImportScope; use either::Either; use hir::{Adt, ModuleDef, ScopeDef, Type}; use ide_db::imports_locator; -use syntax::{algo, AstNode}; +use syntax::AstNode; use test_utils::mark; use crate::{ - render::{render_resolution, RenderContext}, + render::{render_resolution_with_import, RenderContext}, CompletionContext, Completions, }; @@ -95,35 +95,13 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<() )), }) .filter(|(mod_path, _)| mod_path.len() > 1) - .filter_map(|(mod_path, definition)| { - let use_to_insert = mod_path_to_ast(&mod_path); - let mut mod_path_without_last_segment = mod_path; - let name_after_import = mod_path_without_last_segment.segments.pop()?.to_string(); - - let resolution_with_missing_import = - render_resolution(RenderContext::new(ctx), name_after_import, &definition)?; - let lookup_string = resolution_with_missing_import.lookup().to_owned(); - - let mut text_edits = - resolution_with_missing_import.text_edit().to_owned().into_builder(); - let rewriter = insert_use(&import_scope, use_to_insert, ctx.config.merge); - let old_ast = rewriter.rewrite_root()?; - algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); - - let qualifier_string = mod_path_without_last_segment.to_string(); - let qualified_label = if qualifier_string.is_empty() { - resolution_with_missing_import.label().to_owned() - } else { - format!("{}::{}", qualifier_string, resolution_with_missing_import.label()) - }; - - Some( - resolution_with_missing_import - .into_builder() - .text_edit(text_edits.finish()) - .label(qualified_label) - .lookup_by(lookup_string) - .build(), + .filter_map(|(import_path, definition)| { + render_resolution_with_import( + RenderContext::new(ctx), + import_path.clone(), + import_scope.clone(), + ctx.config.merge, + &definition, ) }) .take(20); diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs index 24b9d036a..b13c3f376 100644 --- a/crates/completion/src/item.rs +++ b/crates/completion/src/item.rs @@ -2,8 +2,9 @@ use std::fmt; -use hir::{Documentation, Mutability}; -use syntax::TextRange; +use assists::utils::{insert_use, mod_path_to_ast, ImportScope, MergeBehaviour}; +use hir::{Documentation, ModPath, Mutability}; +use syntax::{algo, TextRange}; use text_edit::TextEdit; use crate::config::SnippetCap; @@ -200,25 +201,7 @@ impl CompletionItem { trigger_call_info: None, score: None, ref_match: None, - } - } - - pub(crate) fn into_builder(self) -> Builder { - Builder { - source_range: self.source_range, - completion_kind: self.completion_kind, - label: self.label, - insert_text: None, - insert_text_format: self.insert_text_format, - detail: self.detail, - documentation: self.documentation, - lookup: self.lookup, - kind: self.kind, - text_edit: Some(self.text_edit), - deprecated: Some(self.deprecated), - trigger_call_info: Some(self.trigger_call_info), - score: self.score, - ref_match: self.ref_match, + import_data: None, } } @@ -278,6 +261,7 @@ impl CompletionItem { pub(crate) struct Builder { source_range: TextRange, completion_kind: CompletionKind, + import_data: Option<(ModPath, ImportScope, Option)>, label: String, insert_text: Option, insert_text_format: InsertTextFormat, @@ -294,23 +278,50 @@ pub(crate) struct Builder { impl Builder { pub(crate) fn build(self) -> CompletionItem { - let label = self.label; - let text_edit = match self.text_edit { + let mut label = self.label; + let mut lookup = self.lookup; + let mut insert_text = self.insert_text; + let mut text_edits = TextEdit::builder(); + + if let Some((import_path, import_scope, merge_behaviour)) = self.import_data { + let import = mod_path_to_ast(&import_path); + let mut import_path_without_last_segment = import_path; + let _ = import_path_without_last_segment.segments.pop(); + + if !import_path_without_last_segment.segments.is_empty() { + if lookup.is_none() { + lookup = Some(label.clone()); + } + if insert_text.is_none() { + insert_text = Some(label.clone()); + } + label = format!("{}::{}", import_path_without_last_segment, label); + } + + let rewriter = insert_use(&import_scope, import, merge_behaviour); + if let Some(old_ast) = rewriter.rewrite_root() { + algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); + } + } + + let original_edit = match self.text_edit { Some(it) => it, - None => TextEdit::replace( - self.source_range, - self.insert_text.unwrap_or_else(|| label.clone()), - ), + None => { + TextEdit::replace(self.source_range, insert_text.unwrap_or_else(|| label.clone())) + } }; + let mut resulting_edit = text_edits.finish(); + resulting_edit.union(original_edit).expect("Failed to unite text edits"); + CompletionItem { source_range: self.source_range, label, insert_text_format: self.insert_text_format, - text_edit, + text_edit: resulting_edit, detail: self.detail, documentation: self.documentation, - lookup: self.lookup, + lookup, kind: self.kind, completion_kind: self.completion_kind, deprecated: self.deprecated.unwrap_or(false), @@ -379,6 +390,13 @@ impl Builder { self.trigger_call_info = Some(true); self } + pub(crate) fn import_data( + mut self, + import_data: Option<(ModPath, ImportScope, Option)>, + ) -> Builder { + self.import_data = import_data; + self + } pub(crate) fn set_ref_match( mut self, ref_match: Option<(Mutability, CompletionScore)>, diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index 5a4353846..e892d4de8 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs @@ -9,7 +9,8 @@ pub(crate) mod type_alias; mod builder_ext; -use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type}; +use assists::utils::{ImportScope, MergeBehaviour}; +use hir::{Documentation, HasAttrs, HirDisplay, ModPath, Mutability, ScopeDef, Type}; use ide_db::RootDatabase; use syntax::TextRange; use test_utils::mark; @@ -42,7 +43,22 @@ pub(crate) fn render_resolution<'a>( local_name: String, resolution: &ScopeDef, ) -> Option { - Render::new(ctx).render_resolution(local_name, resolution) + Render::new(ctx).render_resolution(local_name, None, resolution) +} + +pub(crate) fn render_resolution_with_import<'a>( + ctx: RenderContext<'a>, + import: ModPath, + import_scope: ImportScope, + merge_behaviour: Option, + resolution: &ScopeDef, +) -> Option { + let local_name = import.segments.last()?.to_string(); + Render::new(ctx).render_resolution( + local_name, + Some((import, import_scope, merge_behaviour)), + resolution, + ) } /// Interface for data and methods required for items rendering. @@ -131,6 +147,7 @@ impl<'a> Render<'a> { fn render_resolution( self, local_name: String, + import_data: Option<(ModPath, ImportScope, Option)>, resolution: &ScopeDef, ) -> Option { use hir::ModuleDef::*; @@ -142,15 +159,15 @@ impl<'a> Render<'a> { let kind = match resolution { ScopeDef::ModuleDef(Function(func)) => { - let item = render_fn(self.ctx, Some(local_name), *func); + let item = render_fn(self.ctx, import_data, Some(local_name), *func); return Some(item); } ScopeDef::ModuleDef(EnumVariant(var)) => { - let item = render_enum_variant(self.ctx, Some(local_name), *var, None); + let item = render_enum_variant(self.ctx, import_data, Some(local_name), *var, None); return Some(item); } ScopeDef::MacroDef(mac) => { - let item = render_macro(self.ctx, local_name, *mac); + let item = render_macro(self.ctx, import_data, local_name, *mac); return item; } @@ -175,6 +192,7 @@ impl<'a> Render<'a> { local_name, ) .kind(CompletionItemKind::UnresolvedReference) + .import_data(import_data) .build(); return Some(item); } @@ -227,7 +245,12 @@ impl<'a> Render<'a> { } } - let item = item.kind(kind).set_documentation(docs).set_ref_match(ref_match).build(); + let item = item + .kind(kind) + .import_data(import_data) + .set_documentation(docs) + .set_ref_match(ref_match) + .build(); Some(item) } diff --git a/crates/completion/src/render/enum_variant.rs b/crates/completion/src/render/enum_variant.rs index fd412ed0e..6070e9b1d 100644 --- a/crates/completion/src/render/enum_variant.rs +++ b/crates/completion/src/render/enum_variant.rs @@ -1,5 +1,6 @@ //! Renderer for `enum` variants. +use assists::utils::{ImportScope, MergeBehaviour}; use hir::{HasAttrs, HirDisplay, ModPath, StructKind}; use itertools::Itertools; use test_utils::mark; @@ -11,11 +12,12 @@ use crate::{ pub(crate) fn render_enum_variant<'a>( ctx: RenderContext<'a>, + import_data: Option<(ModPath, ImportScope, Option)>, local_name: Option, variant: hir::EnumVariant, path: Option, ) -> CompletionItem { - EnumVariantRender::new(ctx, local_name, variant, path).render() + EnumVariantRender::new(ctx, local_name, variant, path).render(import_data) } #[derive(Debug)] @@ -60,7 +62,10 @@ impl<'a> EnumVariantRender<'a> { } } - fn render(self) -> CompletionItem { + fn render( + self, + import_data: Option<(ModPath, ImportScope, Option)>, + ) -> CompletionItem { let mut builder = CompletionItem::new( CompletionKind::Reference, self.ctx.source_range(), @@ -69,6 +74,7 @@ impl<'a> EnumVariantRender<'a> { .kind(CompletionItemKind::EnumVariant) .set_documentation(self.variant.docs(self.ctx.db())) .set_deprecated(self.ctx.is_deprecated(self.variant)) + .import_data(import_data) .detail(self.detail()); if self.variant_kind == StructKind::Tuple { diff --git a/crates/completion/src/render/function.rs b/crates/completion/src/render/function.rs index 4fa6eafd7..9dd5cd18c 100644 --- a/crates/completion/src/render/function.rs +++ b/crates/completion/src/render/function.rs @@ -1,6 +1,7 @@ //! Renderer for function calls. -use hir::{HasSource, Type}; +use assists::utils::{ImportScope, MergeBehaviour}; +use hir::{HasSource, ModPath, Type}; use syntax::{ast::Fn, display::function_declaration}; use crate::{ @@ -10,10 +11,11 @@ use crate::{ pub(crate) fn render_fn<'a>( ctx: RenderContext<'a>, + import_data: Option<(ModPath, ImportScope, Option)>, local_name: Option, fn_: hir::Function, ) -> CompletionItem { - FunctionRender::new(ctx, local_name, fn_).render() + FunctionRender::new(ctx, local_name, fn_).render(import_data) } #[derive(Debug)] @@ -36,7 +38,10 @@ impl<'a> FunctionRender<'a> { FunctionRender { ctx, name, fn_, ast_node } } - fn render(self) -> CompletionItem { + fn render( + self, + import_data: Option<(ModPath, ImportScope, Option)>, + ) -> CompletionItem { let params = self.params(); CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone()) .kind(self.kind()) @@ -44,6 +49,7 @@ impl<'a> FunctionRender<'a> { .set_deprecated(self.ctx.is_deprecated(self.fn_)) .detail(self.detail()) .add_call_parens(self.ctx.completion, self.name, params) + .import_data(import_data) .build() } diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs index 96be59cc3..fead59e41 100644 --- a/crates/completion/src/render/macro_.rs +++ b/crates/completion/src/render/macro_.rs @@ -1,6 +1,7 @@ //! Renderer for macro invocations. -use hir::{Documentation, HasSource}; +use assists::utils::{ImportScope, MergeBehaviour}; +use hir::{Documentation, HasSource, ModPath}; use syntax::display::macro_label; use test_utils::mark; @@ -11,10 +12,11 @@ use crate::{ pub(crate) fn render_macro<'a>( ctx: RenderContext<'a>, + import_data: Option<(ModPath, ImportScope, Option)>, name: String, macro_: hir::MacroDef, ) -> Option { - MacroRender::new(ctx, name, macro_).render() + MacroRender::new(ctx, name, macro_).render(import_data) } #[derive(Debug)] @@ -36,7 +38,10 @@ impl<'a> MacroRender<'a> { MacroRender { ctx, name, macro_, docs, bra, ket } } - fn render(&self) -> Option { + fn render( + &self, + import_data: Option<(ModPath, ImportScope, Option)>, + ) -> Option { // FIXME: Currently proc-macro do not have ast-node, // such that it does not have source if self.macro_.is_proc_macro() { @@ -48,6 +53,7 @@ impl<'a> MacroRender<'a> { .kind(CompletionItemKind::Macro) .set_documentation(self.docs.clone()) .set_deprecated(self.ctx.is_deprecated(self.macro_)) + .import_data(import_data) .detail(self.detail()); let needs_bang = self.needs_bang(); -- cgit v1.2.3