Skip to content

Commit

Permalink
Merge #6553
Browse files Browse the repository at this point in the history
6553: Auto imports in completion r=matklad a=SomeoneToIgnore

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

Closes #1062 but does not handle the completion order, since it's a separate task for #4922 , #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>
  • Loading branch information
bors[bot] and SomeoneToIgnore committed Nov 17, 2020
2 parents f4b4f17 + 16f0b2f commit 156f7d6
Show file tree
Hide file tree
Showing 22 changed files with 369 additions and 96 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion crates/assists/src/handlers/auto_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down
28 changes: 15 additions & 13 deletions crates/assists/src/handlers/replace_derive_with_manual_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
3 changes: 1 addition & 2 deletions crates/assists/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
34 changes: 19 additions & 15 deletions crates/assists/src/utils/import_assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
16 changes: 9 additions & 7 deletions crates/assists/src/utils/insert_use.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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),
}
Expand All @@ -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(),
Expand Down Expand Up @@ -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>,
Expand Down
1 change: 1 addition & 0 deletions crates/completion/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
8 changes: 4 additions & 4 deletions crates/completion/src/completions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand All @@ -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)
}

Expand All @@ -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);
}

Expand All @@ -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);
}
}
130 changes: 129 additions & 1 deletion crates/completion/src/completions/unqualified_path.rs
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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};
Expand Down Expand Up @@ -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
}
"#,
);
}
}
4 changes: 4 additions & 0 deletions crates/completion/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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),
}
}
}
Loading

0 comments on commit 156f7d6

Please sign in to comment.