Share import_assets and related entities

This commit is contained in:
Kirill Bulatov 2021-01-16 19:33:36 +02:00
parent 3782c78d75
commit 6742f38e49
20 changed files with 411 additions and 332 deletions

View File

@ -4,7 +4,7 @@
//! module, and we use to statically check that we only produce snippet
//! assists if we are allowed to.
use ide_db::helpers::{insert_use::MergeBehavior, SnippetCap};
use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
use crate::AssistKind;
@ -14,9 +14,3 @@ pub struct AssistConfig {
pub allowed: Option<Vec<AssistKind>>,
pub insert_use: InsertUseConfig,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct InsertUseConfig {
pub merge: Option<MergeBehavior>,
pub prefix_kind: hir::PrefixKind,
}

View File

@ -1,13 +1,11 @@
use ide_db::helpers::{
import_assets::{ImportAssets, ImportCandidate},
insert_use::{insert_use, ImportScope},
mod_path_to_ast,
};
use syntax::ast;
use crate::{
utils::import_assets::{ImportAssets, ImportCandidate},
AssistContext, AssistId, AssistKind, Assists, GroupLabel,
};
use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
// Feature: Auto Import
//
@ -121,8 +119,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
let name = match import_candidate {
ImportCandidate::UnqualifiedName(candidate)
| ImportCandidate::QualifierStart(candidate) => format!("Import {}", &candidate.name),
ImportCandidate::Path(candidate) => format!("Import {}", &candidate.name),
ImportCandidate::TraitAssocItem(candidate) => {
format!("Import a trait for item {}", &candidate.name)
}

View File

@ -1,7 +1,10 @@
use std::iter;
use hir::AsName;
use ide_db::helpers::mod_path_to_ast;
use ide_db::helpers::{
import_assets::{ImportAssets, ImportCandidate},
mod_path_to_ast,
};
use ide_db::RootDatabase;
use syntax::{
ast,
@ -12,7 +15,6 @@
use crate::{
assist_context::{AssistContext, Assists},
utils::import_assets::{ImportAssets, ImportCandidate},
AssistId, AssistKind, GroupLabel,
};
@ -53,17 +55,18 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
let qualify_candidate = match candidate {
ImportCandidate::QualifierStart(_) => {
mark::hit!(qualify_path_qualifier_start);
let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
}
ImportCandidate::UnqualifiedName(_) => {
mark::hit!(qualify_path_unqualified_name);
let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
let generics = path.segment()?.generic_arg_list();
QualifyCandidate::UnqualifiedName(generics)
ImportCandidate::Path(candidate) => {
if candidate.qualifier.is_some() {
mark::hit!(qualify_path_qualifier_start);
let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
} else {
mark::hit!(qualify_path_unqualified_name);
let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
let generics = path.segment()?.generic_arg_list();
QualifyCandidate::UnqualifiedName(generics)
}
}
ImportCandidate::TraitAssocItem(_) => {
mark::hit!(qualify_path_trait_assoc_item);
@ -186,7 +189,7 @@ fn item_as_trait(item: hir::ItemInNs) -> Option<hir::Trait> {
fn group_label(candidate: &ImportCandidate) -> GroupLabel {
let name = match candidate {
ImportCandidate::UnqualifiedName(it) | ImportCandidate::QualifierStart(it) => &it.name,
ImportCandidate::Path(it) => &it.name,
ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name,
};
GroupLabel(format!("Qualify {}", name))
@ -194,8 +197,13 @@ fn group_label(candidate: &ImportCandidate) -> GroupLabel {
fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String {
match candidate {
ImportCandidate::UnqualifiedName(_) => format!("Qualify as `{}`", &import),
ImportCandidate::QualifierStart(_) => format!("Qualify with `{}`", &import),
ImportCandidate::Path(candidate) => {
if candidate.qualifier.is_some() {
format!("Qualify with `{}`", &import)
} else {
format!("Qualify as `{}`", &import)
}
}
ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import),
ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import),
}

View File

@ -24,7 +24,7 @@ macro_rules! eprintln {
pub(crate) use crate::assist_context::{AssistContext, Assists};
pub use assist_config::{AssistConfig, InsertUseConfig};
pub use assist_config::AssistConfig;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssistKind {

View File

@ -3,16 +3,17 @@
use hir::Semantics;
use ide_db::{
base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt},
helpers::{insert_use::MergeBehavior, SnippetCap},
helpers::{
insert_use::{InsertUseConfig, MergeBehavior},
SnippetCap,
},
source_change::FileSystemEdit,
RootDatabase,
};
use syntax::TextRange;
use test_utils::{assert_eq_text, extract_offset, extract_range};
use crate::{
handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists, InsertUseConfig,
};
use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
use stdx::{format_to, trim_indent};
pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {

View File

@ -1,5 +1,4 @@
//! Assorted functions shared by several assists.
pub(crate) mod import_assets;
use std::ops;

View File

@ -13,6 +13,7 @@
pub(crate) mod macro_in_item_position;
pub(crate) mod trait_impl;
pub(crate) mod mod_;
pub(crate) mod flyimport;
use hir::{ModPath, ScopeDef, Type};

View File

@ -0,0 +1,291 @@
//! Feature: completion with imports-on-the-fly
//!
//! When completing names in the current scope, proposes additional imports from other modules or crates,
//! if they can be qualified in the scope and their name contains all symbols from the completion input
//! (case-insensitive, in any order or places).
//!
//! ```
//! fn main() {
//! pda$0
//! }
//! # pub mod std { pub mod marker { pub struct PhantomData { } } }
//! ```
//! ->
//! ```
//! use std::marker::PhantomData;
//!
//! fn main() {
//! PhantomData
//! }
//! # pub mod std { pub mod marker { pub struct PhantomData { } } }
//! ```
//!
//! .Fuzzy search details
//!
//! To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
//! (i.e. in `HashMap` in the `std::collections::HashMap` path).
//! For the same reasons, avoids searching for any imports for inputs with their length less that 2 symbols.
//!
//! .Import configuration
//!
//! It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
//! Mimics the corresponding behavior of the `Auto Import` feature.
//!
//! .LSP and performance implications
//!
//! The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
//! (case sensitive) resolve client capability in its client capabilities.
//! This way the server is able to defer the costly computations, doing them for a selected completion item only.
//! For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
//! which might be slow ergo the feature is automatically disabled.
//!
//! .Feature toggle
//!
//! The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag.
//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
//! capability enabled.
use either::Either;
use hir::{ModPath, ScopeDef};
use ide_db::{helpers::insert_use::ImportScope, imports_locator};
use syntax::AstNode;
use test_utils::mark;
use crate::{
context::CompletionContext,
render::{render_resolution_with_import, RenderContext},
ImportEdit,
};
use super::Completions;
pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
if !ctx.config.enable_autoimport_completions {
return None;
}
if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() {
return None;
}
let potential_import_name = ctx.token.to_string();
if potential_import_name.len() < 2 {
return None;
}
let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string());
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 user_input_lowercased = potential_import_name.to_lowercase();
let mut all_mod_paths = imports_locator::find_similar_imports(
&ctx.sema,
ctx.krate?,
Some(40),
potential_import_name,
true,
true,
)
.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(|(mod_path, _)| mod_path.len() > 1)
.collect::<Vec<_>>();
all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
});
acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
render_resolution_with_import(
RenderContext::new(ctx),
ImportEdit { import_path, import_scope: import_scope.clone() },
&definition,
)
}));
Some(())
}
fn compute_fuzzy_completion_order_key(
proposed_mod_path: &ModPath,
user_input_lowercased: &str,
) -> usize {
mark::hit!(certain_fuzzy_order_test);
let proposed_import_name = match proposed_mod_path.segments.last() {
Some(name) => name.to_string().to_lowercase(),
None => return usize::MAX,
};
match proposed_import_name.match_indices(user_input_lowercased).next() {
Some((first_matching_index, _)) => first_matching_index,
None => usize::MAX,
}
}
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
use test_utils::mark;
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 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$0
}
"#,
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$0
}
"#,
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$0
}
"#,
r#"
use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
fn main() {
ThirdStruct
}
"#,
);
}
#[test]
fn fuzzy_completions_come_in_specific_order() {
mark::check!(certain_fuzzy_order_test);
check(
r#"
//- /lib.rs crate:dep
pub struct FirstStruct;
pub mod some_module {
// already imported, omitted
pub struct SecondStruct;
// does not contain all letters from the query, omitted
pub struct UnrelatedOne;
// contains all letters from the query, but not in sequence, displayed last
pub struct ThiiiiiirdStruct;
// contains all letters from the query, but not in the beginning, displayed second
pub struct AfterThirdStruct;
// contains all letters from the query in the begginning, displayed first
pub struct ThirdStruct;
}
//- /main.rs crate:main deps:dep
use dep::{FirstStruct, some_module::SecondStruct};
fn main() {
hir$0
}
"#,
expect![[r#"
st dep::some_module::ThirdStruct
st dep::some_module::AfterThirdStruct
st dep::some_module::ThiiiiiirdStruct
"#]],
);
}
#[test]
fn does_not_propose_names_in_scope() {
check(
r#"
//- /lib.rs crate:dep
pub mod test_mod {
pub trait TestTrait {
const SPECIAL_CONST: u8;
type HumbleType;
fn weird_function();
fn random_method(&self);
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
const SPECIAL_CONST: u8 = 42;
type HumbleType = ();
fn weird_function() {}
fn random_method(&self) {}
}
}
//- /main.rs crate:main deps:dep
use dep::test_mod::TestStruct;
fn main() {
TestSt$0
}
"#,
expect![[r#""#]],
);
}
}

View File

@ -2,17 +2,11 @@
use std::iter;
use either::Either;
use hir::{Adt, ModPath, ModuleDef, ScopeDef, Type};
use ide_db::helpers::insert_use::ImportScope;
use ide_db::imports_locator;
use hir::{Adt, ModuleDef, ScopeDef, Type};
use syntax::AstNode;
use test_utils::mark;
use crate::{
render::{render_resolution_with_import, RenderContext},
CompletionContext, Completions, ImportEdit,
};
use crate::{CompletionContext, Completions};
pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
@ -45,10 +39,6 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
}
acc.add_resolution(ctx, name.to_string(), &res)
});
if ctx.config.enable_autoimport_completions {
fuzzy_completion(acc, ctx);
}
}
fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) {
@ -77,124 +67,13 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T
}
}
// Feature: Fuzzy Completion and Autoimports
//
// When completing names in the current scope, proposes additional imports from other modules or crates,
// if they can be qualified in the scope and their name contains all symbols from the completion input
// (case-insensitive, in any order or places).
//
// ```
// fn main() {
// pda$0
// }
// # pub mod std { pub mod marker { pub struct PhantomData { } } }
// ```
// ->
// ```
// use std::marker::PhantomData;
//
// fn main() {
// PhantomData
// }
// # pub mod std { pub mod marker { pub struct PhantomData { } } }
// ```
//
// .Fuzzy search details
//
// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
// (i.e. in `HashMap` in the `std::collections::HashMap` path).
// For the same reasons, avoids searching for any imports for inputs with their length less that 2 symbols.
//
// .Merge Behavior
//
// It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
// Mimics the corresponding behavior of the `Auto Import` feature.
//
// .LSP and performance implications
//
// The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
// (case sensitive) resolve client capability in its client capabilities.
// This way the server is able to defer the costly computations, doing them for a selected completion item only.
// For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
// which might be slow ergo the feature is automatically disabled.
//
// .Feature toggle
//
// The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag.
// Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
// capability enabled.
fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
let potential_import_name = ctx.token.to_string();
let _p = profile::span("fuzzy_completion").detail(|| potential_import_name.clone());
if potential_import_name.len() < 2 {
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 user_input_lowercased = potential_import_name.to_lowercase();
let mut all_mod_paths = imports_locator::find_similar_imports(
&ctx.sema,
ctx.krate?,
Some(40),
potential_import_name,
true,
true,
)
.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(|(mod_path, _)| mod_path.len() > 1)
.collect::<Vec<_>>();
all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
});
acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
render_resolution_with_import(
RenderContext::new(ctx),
ImportEdit { import_path, import_scope: import_scope.clone() },
&definition,
)
}));
Some(())
}
fn compute_fuzzy_completion_order_key(
proposed_mod_path: &ModPath,
user_input_lowercased: &str,
) -> usize {
mark::hit!(certain_fuzzy_order_test);
let proposed_import_name = match proposed_mod_path.segments.last() {
Some(name) => name.to_string().to_lowercase(),
None => return usize::MAX,
};
match proposed_import_name.match_indices(user_input_lowercased).next() {
Some((first_matching_index, _)) => first_matching_index,
None => usize::MAX,
}
}
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
use test_utils::mark;
use crate::{
test_utils::{
check_edit, check_edit_with_config, completion_list_with_config, TEST_CONFIG,
},
test_utils::{check_edit, completion_list_with_config, TEST_CONFIG},
CompletionConfig, CompletionKind,
};
@ -855,128 +734,4 @@ impl My$0
"#]],
)
}
#[test]
fn function_fuzzy_completion() {
check_edit_with_config(
TEST_CONFIG,
"stdin",
r#"
//- /lib.rs crate:dep
pub mod io {
pub fn stdin() {}
};
//- /main.rs crate:main deps:dep
fn main() {
stdi$0
}
"#,
r#"
use dep::io::stdin;
fn main() {
stdin()$0
}
"#,
);
}
#[test]
fn macro_fuzzy_completion() {
check_edit_with_config(
TEST_CONFIG,
"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$0
}
"#,
r#"
use dep::macro_with_curlies;
fn main() {
macro_with_curlies! {$0}
}
"#,
);
}
#[test]
fn struct_fuzzy_completion() {
check_edit_with_config(
TEST_CONFIG,
"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$0
}
"#,
r#"
use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
fn main() {
ThirdStruct
}
"#,
);
}
#[test]
fn fuzzy_completions_come_in_specific_order() {
mark::check!(certain_fuzzy_order_test);
check_with_config(
TEST_CONFIG,
r#"
//- /lib.rs crate:dep
pub struct FirstStruct;
pub mod some_module {
// already imported, omitted
pub struct SecondStruct;
// does not contain all letters from the query, omitted
pub struct UnrelatedOne;
// contains all letters from the query, but not in sequence, displayed last
pub struct ThiiiiiirdStruct;
// contains all letters from the query, but not in the beginning, displayed second
pub struct AfterThirdStruct;
// contains all letters from the query in the begginning, displayed first
pub struct ThirdStruct;
}
//- /main.rs crate:main deps:dep
use dep::{FirstStruct, some_module::SecondStruct};
fn main() {
hir$0
}
"#,
expect![[r#"
fn main() fn main()
st SecondStruct
st FirstStruct
md dep
st dep::some_module::ThirdStruct
st dep::some_module::AfterThirdStruct
st dep::some_module::ThiiiiiirdStruct
"#]],
);
}
}

View File

@ -4,7 +4,7 @@
//! module, and we use to statically check that we only produce snippet
//! completions if we are allowed to.
use ide_db::helpers::{insert_use::MergeBehavior, SnippetCap};
use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CompletionConfig {
@ -13,5 +13,5 @@ pub struct CompletionConfig {
pub add_call_parenthesis: bool,
pub add_call_argument_snippets: bool,
pub snippet_cap: Option<SnippetCap>,
pub merge: Option<MergeBehavior>,
pub insert_use: InsertUseConfig,
}

View File

@ -127,6 +127,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::flyimport::import_on_the_fly(&mut acc, &ctx);
Some(acc)
}
@ -153,7 +154,9 @@ pub fn resolve_completion_edits(
})
.find(|mod_path| mod_path.to_string() == full_import_path)?;
ImportEdit { import_path, import_scope }.to_text_edit(config.merge).map(|edit| vec![edit])
ImportEdit { import_path, import_scope }
.to_text_edit(config.insert_use.merge)
.map(|edit| vec![edit])
}
#[cfg(test)]

View File

@ -1,9 +1,12 @@
//! Runs completion for testing purposes.
use hir::Semantics;
use hir::{PrefixKind, Semantics};
use ide_db::{
base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
helpers::{insert_use::MergeBehavior, SnippetCap},
helpers::{
insert_use::{InsertUseConfig, MergeBehavior},
SnippetCap,
},
RootDatabase,
};
use itertools::Itertools;
@ -19,7 +22,10 @@
add_call_parenthesis: true,
add_call_argument_snippets: true,
snippet_cap: SnippetCap::new(true),
merge: Some(MergeBehavior::Full),
insert_use: InsertUseConfig {
merge: Some(MergeBehavior::Full),
prefix_kind: PrefixKind::Plain,
},
};
/// Creates analysis from a multi-file fixture, returns positions marked with $0.
@ -110,7 +116,7 @@ pub(crate) fn check_edit_with_config(
let mut combined_edit = completion.text_edit().to_owned();
if let Some(import_text_edit) =
completion.import_to_add().and_then(|edit| edit.to_text_edit(config.merge))
completion.import_to_add().and_then(|edit| edit.to_text_edit(config.insert_use.merge))
{
combined_edit.union(import_text_edit).expect(
"Failed to apply completion resolve changes: change ranges overlap, but should not",

View File

@ -80,7 +80,7 @@ macro_rules! eprintln {
HlRange,
},
};
pub use assists::{Assist, AssistConfig, AssistId, AssistKind, InsertUseConfig};
pub use assists::{Assist, AssistConfig, AssistId, AssistKind};
pub use completion::{
CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, ImportEdit,
InsertTextFormat,

View File

@ -1,5 +1,6 @@
//! A module with ide helpers for high-level ide features.
pub mod insert_use;
pub mod import_assets;
use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait};
use syntax::ast::{self, make};

View File

@ -1,19 +1,17 @@
//! Look up accessible paths for items.
use either::Either;
use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics};
use ide_db::{imports_locator, RootDatabase};
use rustc_hash::FxHashSet;
use syntax::{ast, AstNode, SyntaxNode};
use crate::assist_config::InsertUseConfig;
use crate::{imports_locator, RootDatabase};
use super::insert_use::InsertUseConfig;
#[derive(Debug)]
pub(crate) enum ImportCandidate {
/// Simple name like 'HashMap'
UnqualifiedName(PathImportCandidate),
/// First part of the qualified name.
/// For 'std::collections::HashMap', that will be 'std'.
QualifierStart(PathImportCandidate),
pub enum ImportCandidate {
// A path, qualified (`std::collections::HashMap`) or not (`HashMap`).
Path(PathImportCandidate),
/// A trait associated function (with no self parameter) or associated constant.
/// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type
/// and `name` is the `test_function`
@ -25,25 +23,26 @@ pub(crate) enum ImportCandidate {
}
#[derive(Debug)]
pub(crate) struct TraitImportCandidate {
pub(crate) ty: hir::Type,
pub(crate) name: ast::NameRef,
pub struct TraitImportCandidate {
pub ty: hir::Type,
pub name: ast::NameRef,
}
#[derive(Debug)]
pub(crate) struct PathImportCandidate {
pub(crate) name: ast::NameRef,
pub struct PathImportCandidate {
pub qualifier: Option<ast::Path>,
pub name: ast::NameRef,
}
#[derive(Debug)]
pub(crate) struct ImportAssets {
pub struct ImportAssets {
import_candidate: ImportCandidate,
module_with_name_to_import: hir::Module,
syntax_under_caret: SyntaxNode,
}
impl ImportAssets {
pub(crate) fn for_method_call(
pub fn for_method_call(
method_call: ast::MethodCallExpr,
sema: &Semantics<RootDatabase>,
) -> Option<Self> {
@ -56,7 +55,7 @@ pub(crate) fn for_method_call(
})
}
pub(crate) fn for_regular_path(
pub fn for_regular_path(
path_under_caret: ast::Path,
sema: &Semantics<RootDatabase>,
) -> Option<Self> {
@ -73,24 +72,23 @@ pub(crate) fn for_regular_path(
})
}
pub(crate) fn syntax_under_caret(&self) -> &SyntaxNode {
pub fn syntax_under_caret(&self) -> &SyntaxNode {
&self.syntax_under_caret
}
pub(crate) fn import_candidate(&self) -> &ImportCandidate {
pub fn import_candidate(&self) -> &ImportCandidate {
&self.import_candidate
}
fn get_search_query(&self) -> &str {
match &self.import_candidate {
ImportCandidate::UnqualifiedName(candidate)
| ImportCandidate::QualifierStart(candidate) => candidate.name.text(),
ImportCandidate::Path(candidate) => candidate.name.text(),
ImportCandidate::TraitAssocItem(candidate)
| ImportCandidate::TraitMethod(candidate) => candidate.name.text(),
}
}
pub(crate) fn search_for_imports(
pub fn search_for_imports(
&self,
sema: &Semantics<RootDatabase>,
config: &InsertUseConfig,
@ -101,7 +99,7 @@ pub(crate) fn search_for_imports(
/// This may return non-absolute paths if a part of the returned path is already imported into scope.
#[allow(dead_code)]
pub(crate) fn search_for_relative_paths(
pub fn search_for_relative_paths(
&self,
sema: &Semantics<RootDatabase>,
) -> Vec<(hir::ModPath, hir::ItemInNs)> {
@ -253,10 +251,14 @@ fn for_regular_path(
_ => return None,
}
} else {
ImportCandidate::QualifierStart(PathImportCandidate { name: qualifier_start })
ImportCandidate::Path(PathImportCandidate {
qualifier: Some(qualifier),
name: qualifier_start,
})
}
} else {
ImportCandidate::UnqualifiedName(PathImportCandidate {
ImportCandidate::Path(PathImportCandidate {
qualifier: None,
name: segment.syntax().descendants().find_map(ast::NameRef::cast)?,
})
};

View File

@ -15,6 +15,12 @@
};
use test_utils::mark;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct InsertUseConfig {
pub merge: Option<MergeBehavior>,
pub prefix_kind: hir::PrefixKind,
}
#[derive(Debug, Clone)]
pub enum ImportScope {
File(ast::SourceFile),

View File

@ -12,6 +12,8 @@
use either::Either;
use rustc_hash::FxHashSet;
const QUERY_SEARCH_LIMIT: usize = 40;
pub fn find_exact_imports<'a>(
sema: &Semantics<'a, RootDatabase>,
krate: Crate,
@ -24,11 +26,11 @@ pub fn find_exact_imports<'a>(
{
let mut local_query = symbol_index::Query::new(name_to_import.clone());
local_query.exact();
local_query.limit(40);
local_query.limit(QUERY_SEARCH_LIMIT);
local_query
},
import_map::Query::new(name_to_import)
.limit(40)
.limit(QUERY_SEARCH_LIMIT)
.name_only()
.search_mode(import_map::SearchMode::Equals)
.case_sensitive(),

View File

@ -3,6 +3,7 @@
use std::{env, path::PathBuf, str::FromStr, sync::Arc, time::Instant};
use anyhow::{bail, format_err, Result};
use hir::PrefixKind;
use ide::{
Analysis, AnalysisHost, Change, CompletionConfig, DiagnosticsConfig, FilePosition, LineCol,
};
@ -11,7 +12,7 @@
salsa::{Database, Durability},
FileId,
},
helpers::SnippetCap,
helpers::{insert_use::InsertUseConfig, SnippetCap},
};
use vfs::AbsPathBuf;
@ -96,7 +97,7 @@ pub fn run(self, verbosity: Verbosity) -> Result<()> {
add_call_parenthesis: true,
add_call_argument_snippets: true,
snippet_cap: SnippetCap::new(true),
merge: None,
insert_use: InsertUseConfig { merge: None, prefix_kind: PrefixKind::Plain },
};
let res = do_work(&mut host, file_id, |analysis| {
analysis.completions(&options, file_position)

View File

@ -11,11 +11,11 @@
use flycheck::FlycheckConfig;
use hir::PrefixKind;
use ide::{
AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig,
InsertUseConfig,
use ide::{AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig};
use ide_db::helpers::{
insert_use::{InsertUseConfig, MergeBehavior},
SnippetCap,
};
use ide_db::helpers::{insert_use::MergeBehavior, SnippetCap};
use itertools::Itertools;
use lsp_types::{ClientCapabilities, MarkupKind};
use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest};
@ -542,11 +542,18 @@ pub fn inlay_hints(&self) -> InlayHintsConfig {
max_length: self.data.inlayHints_maxLength,
}
}
fn merge_behavior(&self) -> Option<MergeBehavior> {
match self.data.assist_importMergeBehavior {
MergeBehaviorDef::None => None,
MergeBehaviorDef::Full => Some(MergeBehavior::Full),
MergeBehaviorDef::Last => Some(MergeBehavior::Last),
fn insert_use_config(&self) -> InsertUseConfig {
InsertUseConfig {
merge: match self.data.assist_importMergeBehavior {
MergeBehaviorDef::None => None,
MergeBehaviorDef::Full => Some(MergeBehavior::Full),
MergeBehaviorDef::Last => Some(MergeBehavior::Last),
},
prefix_kind: match self.data.assist_importPrefix {
ImportPrefixDef::Plain => PrefixKind::Plain,
ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
ImportPrefixDef::BySelf => PrefixKind::BySelf,
},
}
}
pub fn completion(&self) -> CompletionConfig {
@ -556,7 +563,7 @@ pub fn completion(&self) -> CompletionConfig {
&& completion_item_edit_resolve(&self.caps),
add_call_parenthesis: self.data.completion_addCallParenthesis,
add_call_argument_snippets: self.data.completion_addCallArgumentSnippets,
merge: self.merge_behavior(),
insert_use: self.insert_use_config(),
snippet_cap: SnippetCap::new(try_or!(
self.caps
.text_document
@ -575,7 +582,11 @@ pub fn assist(&self) -> AssistConfig {
snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
allowed: None,
insert_use: InsertUseConfig {
merge: self.merge_behavior(),
merge: match self.data.assist_importMergeBehavior {
MergeBehaviorDef::None => None,
MergeBehaviorDef::Full => Some(MergeBehavior::Full),
MergeBehaviorDef::Last => Some(MergeBehavior::Last),
},
prefix_kind: match self.data.assist_importPrefix {
ImportPrefixDef::Plain => PrefixKind::Plain,
ImportPrefixDef::ByCrate => PrefixKind::ByCrate,

View File

@ -861,8 +861,9 @@ pub(crate) fn rename_error(err: RenameError) -> crate::LspError {
#[cfg(test)]
mod tests {
use hir::PrefixKind;
use ide::Analysis;
use ide_db::helpers::SnippetCap;
use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
use super::*;
@ -887,7 +888,7 @@ fn main() {
add_call_parenthesis: true,
add_call_argument_snippets: true,
snippet_cap: SnippetCap::new(true),
merge: None,
insert_use: InsertUseConfig { merge: None, prefix_kind: PrefixKind::Plain },
},
ide_db::base_db::FilePosition { file_id, offset },
)