Share import_assets and related entities
This commit is contained in:
parent
3782c78d75
commit
6742f38e49
@ -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,
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -1,5 +1,4 @@
|
||||
//! Assorted functions shared by several assists.
|
||||
pub(crate) mod import_assets;
|
||||
|
||||
use std::ops;
|
||||
|
||||
|
@ -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};
|
||||
|
||||
|
291
crates/completion/src/completions/flyimport.rs
Normal file
291
crates/completion/src/completions/flyimport.rs
Normal 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#""#]],
|
||||
);
|
||||
}
|
||||
}
|
@ -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
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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};
|
||||
|
@ -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)?,
|
||||
})
|
||||
};
|
@ -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),
|
||||
|
@ -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(),
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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 },
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user