6553: Auto imports in completion r=matklad a=SomeoneToIgnore

![completion](https://user-images.githubusercontent.com/2690773/99155339-ae4fb380-26bf-11eb-805a-655b1706ce70.gif)

Closes https://github.com/rust-analyzer/rust-analyzer/issues/1062 but does not handle the completion order, since it's a separate task for https://github.com/rust-analyzer/rust-analyzer/issues/4922 , https://github.com/rust-analyzer/rust-analyzer/issues/4922 and maybe something else.

2 quirks in the current implementation:

* traits are not auto imported during method completion

If I understand the current situation right, we cannot search for traits by a **part** of a method name, we need a full name with correct case to get a trait for it.

* VSCode (?) autocompletion is not as rigid as in Intellij Rust as you can notice on the animation.

Intellij is able to refresh the completions on every new symbol added, yet VS Code does not query the completions on every symbol for me.
With a few debug prints placed in RA, I've observed the following behaviour: after the first set of completion suggestions is received, next symbol input does not trigger a server request, if the completions contain this symbol.
When more symbols added, the existing completion suggestions are filtered out until none are left and only then, on the next symbol it queries for completions.
It seems like the only alternative to get an updated set of results is to manually retrigger it with Esc and Ctrl + Space.

Despite the eerie latter bullet, the completion seems to work pretty fine and fast nontheless, but if you have any ideas on how to make it more smooth, I'll gladly try it out.

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
This commit is contained in:
bors[bot] 2020-11-17 17:50:08 +00:00 committed by GitHub
commit 156f7d6963
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 369 additions and 96 deletions

1
Cargo.lock generated
View file

@ -255,6 +255,7 @@ version = "0.0.0"
dependencies = [
"assists",
"base_db",
"either",
"expect-test",
"hir",
"ide_db",

View file

@ -98,7 +98,8 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
let group = import_group_message(import_assets.import_candidate());
let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?;
let scope =
ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), &ctx.sema)?;
for (import, _) in proposed_imports {
acc.add_group(
&group,

View file

@ -143,8 +143,7 @@ fn insert_import(
if let Some(mut mod_path) = mod_path {
mod_path.segments.pop();
mod_path.segments.push(variant_hir_name.clone());
let scope = ImportScope::find_insert_use_container(scope_node, ctx)?;
let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
*rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
}
Some(())

View file

@ -62,19 +62,21 @@ pub(crate) fn replace_derive_with_manual_impl(
let current_module = ctx.sema.scope(annotated_name.syntax()).module()?;
let current_crate = current_module.krate();
let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text())
.into_iter()
.filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
_ => None,
})
.flat_map(|trait_| {
current_module
.find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
.as_ref()
.map(mod_path_to_ast)
.zip(Some(trait_))
});
let found_traits =
imports_locator::find_exact_imports(&ctx.sema, current_crate, trait_token.text())
.filter_map(
|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
_ => None,
},
)
.flat_map(|trait_| {
current_module
.find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
.as_ref()
.map(mod_path_to_ast)
.zip(Some(trait_))
});
let mut no_traits_found = true;
for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {

View file

@ -34,7 +34,7 @@ pub(crate) fn replace_qualified_name_with_use(
}
let target = path.syntax().text_range();
let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?;
let syntax = scope.as_syntax_node();
acc.add(
AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),

View file

@ -22,8 +22,7 @@ use crate::{
ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
};
pub use insert_use::MergeBehaviour;
pub(crate) use insert_use::{insert_use, ImportScope};
pub use insert_use::{insert_use, ImportScope, MergeBehaviour};
pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path {
let mut segments = Vec::new();

View file

@ -179,21 +179,25 @@ impl ImportAssets {
}
};
let mut res = imports_locator::find_imports(sema, current_crate, &self.get_search_query())
.into_iter()
.filter_map(filter)
.filter_map(|candidate| {
let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
if let Some(prefix_kind) = prefixed {
self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind)
} else {
self.module_with_name_to_import.find_use_path(db, item)
}
.map(|path| (path, item))
})
.filter(|(use_path, _)| !use_path.segments.is_empty())
.take(20)
.collect::<Vec<_>>();
let mut res =
imports_locator::find_exact_imports(sema, current_crate, &self.get_search_query())
.filter_map(filter)
.filter_map(|candidate| {
let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
if let Some(prefix_kind) = prefixed {
self.module_with_name_to_import.find_use_path_prefixed(
db,
item,
prefix_kind,
)
} else {
self.module_with_name_to_import.find_use_path(db, item)
}
.map(|path| (path, item))
})
.filter(|(use_path, _)| use_path.len() > 1)
.take(20)
.collect::<Vec<_>>();
res.sort_by_key(|(path, _)| path.clone());
res
}

View file

@ -1,6 +1,8 @@
//! Handle syntactic aspects of inserting a new `use`.
use std::{cmp::Ordering, iter::successors};
use hir::Semantics;
use ide_db::RootDatabase;
use itertools::{EitherOrBoth, Itertools};
use syntax::{
algo::SyntaxRewriter,
@ -13,8 +15,8 @@ use syntax::{
};
use test_utils::mark;
#[derive(Debug)]
pub(crate) enum ImportScope {
#[derive(Debug, Clone)]
pub enum ImportScope {
File(ast::SourceFile),
Module(ast::ItemList),
}
@ -31,14 +33,14 @@ impl ImportScope {
}
/// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
pub(crate) fn find_insert_use_container(
pub fn find_insert_use_container(
position: &SyntaxNode,
ctx: &crate::assist_context::AssistContext,
sema: &Semantics<'_, RootDatabase>,
) -> Option<Self> {
ctx.sema.ancestors_with_macros(position.clone()).find_map(Self::from)
sema.ancestors_with_macros(position.clone()).find_map(Self::from)
}
pub(crate) fn as_syntax_node(&self) -> &SyntaxNode {
pub fn as_syntax_node(&self) -> &SyntaxNode {
match self {
ImportScope::File(file) => file.syntax(),
ImportScope::Module(item_list) => item_list.syntax(),
@ -88,7 +90,7 @@ fn is_inner_comment(token: SyntaxToken) -> bool {
}
/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
pub(crate) fn insert_use<'a>(
pub fn insert_use<'a>(
scope: &ImportScope,
path: ast::Path,
merge: Option<MergeBehaviour>,

View file

@ -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" }

View file

@ -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<String>,
) {
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<String>,
) {
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);
}
}

View file

@ -1,10 +1,16 @@
//! Completion of names from the current scope, e.g. locals and imported items.
use assists::utils::ImportScope;
use either::Either;
use hir::{Adt, ModuleDef, ScopeDef, Type};
use ide_db::imports_locator;
use syntax::AstNode;
use test_utils::mark;
use crate::{CompletionContext, Completions};
use crate::{
render::{render_resolution_with_import, 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,8 @@ 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()
}
fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) {
@ -63,6 +71,45 @@ 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 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(|(mod_path, _)| mod_path.len() > 1)
.filter_map(|(import_path, definition)| {
render_resolution_with_import(
RenderContext::new(ctx),
import_path.clone(),
import_scope.clone(),
ctx.config.merge,
&definition,
)
})
.take(20);
acc.add_all(possible_imports);
Some(())
}
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
@ -676,4 +723,85 @@ impl My<|>
"#]],
)
}
#[test]
fn function_fuzzy_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_fuzzy_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 struct_fuzzy_completion() {
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
}
"#,
);
}
}

View file

@ -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<SnippetCap>,
pub merge: Option<MergeBehaviour>,
}
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),
}
}
}

View file

@ -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;
@ -31,6 +32,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.
@ -199,8 +201,10 @@ impl CompletionItem {
trigger_call_info: None,
score: None,
ref_match: None,
import_data: None,
}
}
/// What user sees in pop-up in the UI.
pub fn label(&self) -> &str {
&self.label
@ -257,6 +261,7 @@ impl CompletionItem {
pub(crate) struct Builder {
source_range: TextRange,
completion_kind: CompletionKind,
import_data: Option<(ModPath, ImportScope, Option<MergeBehaviour>)>,
label: String,
insert_text: Option<String>,
insert_text_format: InsertTextFormat,
@ -273,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),
@ -358,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<MergeBehaviour>)>,
) -> Builder {
self.import_data = import_data;
self
}
pub(crate) fn set_ref_match(
mut self,
ref_match: Option<(Mutability, CompletionScore)>,

View file

@ -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<CompletionItem> {
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<MergeBehaviour>,
resolution: &ScopeDef,
) -> Option<CompletionItem> {
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<MergeBehaviour>)>,
resolution: &ScopeDef,
) -> Option<CompletionItem> {
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)
}
@ -425,6 +448,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,

View file

@ -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<MergeBehaviour>)>,
local_name: Option<String>,
variant: hir::EnumVariant,
path: Option<ModPath>,
) -> 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<MergeBehaviour>)>,
) -> 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 {

View file

@ -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<MergeBehaviour>)>,
local_name: Option<String>,
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<MergeBehaviour>)>,
) -> 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()
}

View file

@ -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<MergeBehaviour>)>,
name: String,
macro_: hir::MacroDef,
) -> Option<CompletionItem> {
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<CompletionItem> {
fn render(
&self,
import_data: Option<(ModPath, ImportScope, Option<MergeBehaviour>)>,
) -> Option<CompletionItem> {
// 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();

View file

@ -110,15 +110,9 @@ impl Crate {
pub fn query_external_importables(
self,
db: &dyn DefDatabase,
query: &str,
query: import_map::Query,
) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
import_map::search_dependencies(
db,
self.into(),
import_map::Query::new(query).anchor_end().case_sensitive().limit(40),
)
.into_iter()
.map(|item| match item {
import_map::search_dependencies(db, self.into(), query).into_iter().map(|item| match item {
ItemInNs::Types(mod_id) | ItemInNs::Values(mod_id) => Either::Left(mod_id.into()),
ItemInNs::Macros(mac_id) => Either::Right(mac_id.into()),
})

View file

@ -49,6 +49,7 @@ pub use hir_def::{
builtin_type::BuiltinType,
docs::Documentation,
find_path::PrefixKind,
import_map,
item_scope::ItemInNs,
nameres::ModuleSource,
path::{ModPath, PathKind},

View file

@ -1,36 +1,70 @@
//! This module contains an import search funcionality that is provided to the assists module.
//! Later, this should be moved away to a separate crate that is accessible from the assists module.
use hir::{Crate, MacroDef, ModuleDef, Semantics};
use hir::{import_map, Crate, MacroDef, ModuleDef, Semantics};
use syntax::{ast, AstNode, SyntaxKind::NAME};
use crate::{
defs::{Definition, NameClass},
symbol_index::{self, FileSymbol, Query},
symbol_index::{self, FileSymbol},
RootDatabase,
};
use either::Either;
use rustc_hash::FxHashSet;
pub fn find_imports<'a>(
pub fn find_exact_imports<'a>(
sema: &Semantics<'a, RootDatabase>,
krate: Crate,
name_to_import: &str,
) -> Vec<Either<ModuleDef, MacroDef>> {
let _p = profile::span("search_for_imports");
) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
let _p = profile::span("find_exact_imports");
find_imports(
sema,
krate,
{
let mut local_query = symbol_index::Query::new(name_to_import.to_string());
local_query.exact();
local_query.limit(40);
local_query
},
import_map::Query::new(name_to_import).anchor_end().case_sensitive().limit(40),
)
}
pub fn find_similar_imports<'a>(
sema: &Semantics<'a, RootDatabase>,
krate: Crate,
name_to_import: &str,
limit: usize,
) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
let _p = profile::span("find_similar_imports");
find_imports(
sema,
krate,
{
let mut local_query = symbol_index::Query::new(name_to_import.to_string());
local_query.limit(limit);
local_query
},
import_map::Query::new(name_to_import).limit(limit),
)
}
fn find_imports<'a>(
sema: &Semantics<'a, RootDatabase>,
krate: Crate,
local_query: symbol_index::Query,
external_query: import_map::Query,
) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
let _p = profile::span("find_similar_imports");
let db = sema.db;
// Query dependencies first.
let mut candidates: FxHashSet<_> =
krate.query_external_importables(db, name_to_import).collect();
krate.query_external_importables(db, external_query).collect();
// Query the local crate using the symbol index.
let local_results = {
let mut query = Query::new(name_to_import.to_string());
query.exact();
query.limit(40);
symbol_index::crate_symbols(db, krate.into(), query)
};
let local_results = symbol_index::crate_symbols(db, krate.into(), local_query);
candidates.extend(
local_results
@ -43,7 +77,7 @@ pub fn find_imports<'a>(
}),
);
candidates.into_iter().collect()
candidates.into_iter()
}
fn get_name_definition<'a>(

View file

@ -294,10 +294,6 @@ impl Config {
max_length: data.inlayHints_maxLength,
};
self.completion.enable_postfix_completions = data.completion_postfix_enable;
self.completion.add_call_parenthesis = data.completion_addCallParenthesis;
self.completion.add_call_argument_snippets = data.completion_addCallArgumentSnippets;
self.assist.insert_use.merge = match data.assist_importMergeBehaviour {
MergeBehaviourDef::None => None,
MergeBehaviourDef::Full => Some(MergeBehaviour::Full),
@ -309,6 +305,11 @@ impl Config {
ImportPrefixDef::BySelf => PrefixKind::BySelf,
};
self.completion.enable_postfix_completions = data.completion_postfix_enable;
self.completion.add_call_parenthesis = data.completion_addCallParenthesis;
self.completion.add_call_argument_snippets = data.completion_addCallArgumentSnippets;
self.completion.merge = self.assist.insert_use.merge;
self.call_info_full = data.callInfo_full;
self.lens = LensConfig {

View file

@ -573,7 +573,7 @@ pub(crate) fn handle_completion(
.flat_map(|item| to_proto::completion_item(&line_index, line_endings, item))
.collect();
let completion_list = lsp_types::CompletionList { is_incomplete: false, items };
let completion_list = lsp_types::CompletionList { is_incomplete: true, items };
Ok(Some(completion_list.into()))
}