mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-12 21:28:51 +00:00
Merge #6553
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:
commit
156f7d6963
22 changed files with 369 additions and 96 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -255,6 +255,7 @@ version = "0.0.0"
|
|||
dependencies = [
|
||||
"assists",
|
||||
"base_db",
|
||||
"either",
|
||||
"expect-test",
|
||||
"hir",
|
||||
"ide_db",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)>,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()),
|
||||
})
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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>(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue