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 ++++++++++++-------------- crates/text_edit/src/lib.rs | 4 ++ 4 files changed, 74 insertions(+), 88 deletions(-) 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; diff --git a/crates/text_edit/src/lib.rs b/crates/text_edit/src/lib.rs index eb3c8caa2..9eef7a890 100644 --- a/crates/text_edit/src/lib.rs +++ b/crates/text_edit/src/lib.rs @@ -48,6 +48,10 @@ impl TextEdit { TextEditBuilder::default() } + pub fn into_builder(self) -> TextEditBuilder { + TextEditBuilder { indels: self.indels } + } + pub fn insert(offset: TextSize, text: String) -> TextEdit { let mut builder = TextEdit::builder(); builder.insert(offset, text); -- cgit v1.2.3