Auto merge of #15713 - Veykril:flyimport-completions-short, r=Veykril

Do flyimport completions by prefix search for short paths

Fixes https://github.com/rust-lang/rust-analyzer/issues/15711
This commit is contained in:
bors 2023-10-09 07:56:02 +00:00
commit dca63d1b21
6 changed files with 174 additions and 47 deletions

View File

@ -283,6 +283,8 @@ enum SearchMode {
/// Import map entry should contain all letters from the query string, /// Import map entry should contain all letters from the query string,
/// in the same order, but not necessary adjacent. /// in the same order, but not necessary adjacent.
Fuzzy, Fuzzy,
/// Import map entry should match the query string by prefix.
Prefix,
} }
/// Three possible ways to search for the name in associated and/or other items. /// Three possible ways to search for the name in associated and/or other items.
@ -324,6 +326,14 @@ impl Query {
Self { search_mode: SearchMode::Fuzzy, ..self } Self { search_mode: SearchMode::Fuzzy, ..self }
} }
pub fn prefix(self) -> Self {
Self { search_mode: SearchMode::Prefix, ..self }
}
pub fn exact(self) -> Self {
Self { search_mode: SearchMode::Exact, ..self }
}
/// Specifies whether we want to include associated items in the result. /// Specifies whether we want to include associated items in the result.
pub fn assoc_search_mode(self, assoc_mode: AssocSearchMode) -> Self { pub fn assoc_search_mode(self, assoc_mode: AssocSearchMode) -> Self {
Self { assoc_mode, ..self } Self { assoc_mode, ..self }
@ -361,7 +371,8 @@ impl Query {
let query_string = if case_insensitive { &self.lowercased } else { &self.query }; let query_string = if case_insensitive { &self.lowercased } else { &self.query };
match self.search_mode { match self.search_mode {
SearchMode::Exact => &input == query_string, SearchMode::Exact => input == *query_string,
SearchMode::Prefix => input.starts_with(query_string),
SearchMode::Fuzzy => { SearchMode::Fuzzy => {
let mut input_chars = input.chars(); let mut input_chars = input.chars();
for query_char in query_string.chars() { for query_char in query_string.chars() {

View File

@ -13,10 +13,9 @@ use crate::{
TypeLocation, TypeLocation,
}, },
render::{render_resolution_with_import, render_resolution_with_import_pat, RenderContext}, render::{render_resolution_with_import, render_resolution_with_import_pat, RenderContext},
Completions,
}; };
use super::Completions;
// Feature: Completion With Autoimport // Feature: Completion With Autoimport
// //
// When completing names in the current scope, proposes additional imports from other modules or crates, // When completing names in the current scope, proposes additional imports from other modules or crates,
@ -377,9 +376,12 @@ fn import_assets_for_path(
&ctx.sema, &ctx.sema,
ctx.token.parent()?, ctx.token.parent()?,
)?; )?;
if fuzzy_name_length < 3 { if fuzzy_name_length == 0 {
cov_mark::hit!(flyimport_exact_on_short_path); // nothing matches the empty string exactly, but we still compute assoc items in this case
assets_for_path.path_fuzzy_name_to_exact(false); assets_for_path.path_fuzzy_name_to_exact();
} else if fuzzy_name_length < 3 {
cov_mark::hit!(flyimport_prefix_on_short_path);
assets_for_path.path_fuzzy_name_to_prefix();
} }
Some(assets_for_path) Some(assets_for_path)
} }

View File

@ -116,19 +116,47 @@ fn main() {
} }
#[test] #[test]
fn short_paths_are_ignored() { fn short_paths_are_prefix_matched() {
cov_mark::check!(flyimport_exact_on_short_path); cov_mark::check!(flyimport_prefix_on_short_path);
check( check(
r#" r#"
//- /lib.rs crate:dep //- /lib.rs crate:dep
pub struct Bar; pub struct Barc;
pub struct Rcar; pub struct Rcar;
pub struct Rc; pub struct Rc;
pub const RC: () = ();
pub mod some_module { pub mod some_module {
pub struct Bar; pub struct Bar;
pub struct Rcar; pub struct Rcar;
pub struct Rc; pub struct Rc;
pub const RC: () = ();
}
//- /main.rs crate:main deps:dep
fn main() {
Rc$0
}
"#,
expect![[r#"
st Rc (use dep::Rc)
st Rcar (use dep::Rcar)
st Rc (use dep::some_module::Rc)
st Rcar (use dep::some_module::Rcar)
"#]],
);
check(
r#"
//- /lib.rs crate:dep
pub struct Barc;
pub struct Rcar;
pub struct Rc;
pub const RC: () = ();
pub mod some_module {
pub struct Bar;
pub struct Rcar;
pub struct Rc;
pub const RC: () = ();
} }
//- /main.rs crate:main deps:dep //- /main.rs crate:main deps:dep
@ -137,8 +165,36 @@ fn main() {
} }
"#, "#,
expect![[r#" expect![[r#"
ct RC (use dep::RC)
st Rc (use dep::Rc) st Rc (use dep::Rc)
st Rcar (use dep::Rcar)
ct RC (use dep::some_module::RC)
st Rc (use dep::some_module::Rc) st Rc (use dep::some_module::Rc)
st Rcar (use dep::some_module::Rcar)
"#]],
);
check(
r#"
//- /lib.rs crate:dep
pub struct Barc;
pub struct Rcar;
pub struct Rc;
pub const RC: () = ();
pub mod some_module {
pub struct Bar;
pub struct Rcar;
pub struct Rc;
pub const RC: () = ();
}
//- /main.rs crate:main deps:dep
fn main() {
RC$0
}
"#,
expect![[r#"
ct RC (use dep::RC)
ct RC (use dep::some_module::RC)
"#]], "#]],
); );
} }
@ -841,8 +897,8 @@ fn main() {
TES$0 TES$0
}"#, }"#,
expect![[r#" expect![[r#"
ct TEST_CONST (use foo::TEST_CONST) ct TEST_CONST (use foo::TEST_CONST)
"#]], "#]],
); );
check( check(
@ -858,9 +914,9 @@ fn main() {
tes$0 tes$0
}"#, }"#,
expect![[r#" expect![[r#"
ct TEST_CONST (use foo::TEST_CONST) ct TEST_CONST (use foo::TEST_CONST)
fn test_function() (use foo::test_function) fn() -> i32 fn test_function() (use foo::test_function) fn() -> i32
"#]], "#]],
); );
check( check(
@ -873,9 +929,9 @@ mod foo {
} }
fn main() { fn main() {
Te$0 Tes$0
}"#, }"#,
expect![[]], expect![""],
); );
} }

View File

@ -68,22 +68,29 @@ pub struct FirstSegmentUnresolved {
pub enum NameToImport { pub enum NameToImport {
/// Requires items with names that exactly match the given string, bool indicates case-sensitivity. /// Requires items with names that exactly match the given string, bool indicates case-sensitivity.
Exact(String, bool), Exact(String, bool),
/// Requires items with names that case-insensitively contain all letters from the string, /// Requires items with names that match the given string by prefix, bool indicates case-sensitivity.
Prefix(String, bool),
/// Requires items with names contain all letters from the string,
/// in the same order, but not necessary adjacent. /// in the same order, but not necessary adjacent.
Fuzzy(String), Fuzzy(String, bool),
} }
impl NameToImport { impl NameToImport {
pub fn exact_case_sensitive(s: String) -> NameToImport { pub fn exact_case_sensitive(s: String) -> NameToImport {
NameToImport::Exact(s, true) NameToImport::Exact(s, true)
} }
}
impl NameToImport { pub fn fuzzy(s: String) -> NameToImport {
// unless all chars are lowercase, we do a case sensitive search
let case_sensitive = s.chars().any(|c| c.is_uppercase());
NameToImport::Fuzzy(s, case_sensitive)
}
pub fn text(&self) -> &str { pub fn text(&self) -> &str {
match self { match self {
NameToImport::Exact(text, _) => text.as_str(), NameToImport::Prefix(text, _)
NameToImport::Fuzzy(text) => text.as_str(), | NameToImport::Exact(text, _)
| NameToImport::Fuzzy(text, _) => text.as_str(),
} }
} }
} }
@ -165,7 +172,7 @@ impl ImportAssets {
Some(Self { Some(Self {
import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate { import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
receiver_ty, receiver_ty,
assoc_item_name: NameToImport::Fuzzy(fuzzy_method_name), assoc_item_name: NameToImport::fuzzy(fuzzy_method_name),
}), }),
module_with_candidate: module_with_method_call, module_with_candidate: module_with_method_call,
candidate_node, candidate_node,
@ -228,12 +235,30 @@ impl ImportAssets {
self.search_for(sema, None, prefer_no_std) self.search_for(sema, None, prefer_no_std)
} }
pub fn path_fuzzy_name_to_exact(&mut self, case_sensitive: bool) { /// Requires imports to by prefix instead of fuzzily.
pub fn path_fuzzy_name_to_prefix(&mut self) {
if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) = if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
&mut self.import_candidate &mut self.import_candidate
{ {
let name = match to_import { let (name, case_sensitive) = match to_import {
NameToImport::Fuzzy(name) => std::mem::take(name), NameToImport::Fuzzy(name, case_sensitive) => {
(std::mem::take(name), *case_sensitive)
}
_ => return,
};
*to_import = NameToImport::Prefix(name, case_sensitive);
}
}
/// Requires imports to match exactly instead of fuzzily.
pub fn path_fuzzy_name_to_exact(&mut self) {
if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
&mut self.import_candidate
{
let (name, case_sensitive) = match to_import {
NameToImport::Fuzzy(name, case_sensitive) => {
(std::mem::take(name), *case_sensitive)
}
_ => return, _ => return,
}; };
*to_import = NameToImport::Exact(name, case_sensitive); *to_import = NameToImport::Exact(name, case_sensitive);
@ -623,7 +648,7 @@ impl ImportCandidate {
fuzzy_name: String, fuzzy_name: String,
sema: &Semantics<'_, RootDatabase>, sema: &Semantics<'_, RootDatabase>,
) -> Option<Self> { ) -> Option<Self> {
path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name)) path_import_candidate(sema, qualifier, NameToImport::fuzzy(fuzzy_name))
} }
} }

View File

@ -31,26 +31,34 @@ pub fn items_with_name<'a>(
) )
}); });
let prefix = matches!(name, NameToImport::Prefix(..));
let (mut local_query, mut external_query) = match name { let (mut local_query, mut external_query) = match name {
NameToImport::Exact(exact_name, case_sensitive) => { NameToImport::Prefix(exact_name, case_sensitive)
| NameToImport::Exact(exact_name, case_sensitive) => {
let mut local_query = symbol_index::Query::new(exact_name.clone()); let mut local_query = symbol_index::Query::new(exact_name.clone());
local_query.exact(); let mut external_query = import_map::Query::new(exact_name);
if prefix {
let external_query = import_map::Query::new(exact_name); local_query.prefix();
external_query = external_query.prefix();
( } else {
local_query, local_query.exact();
if case_sensitive { external_query.case_sensitive() } else { external_query }, external_query = external_query.exact();
) }
if case_sensitive {
local_query.case_sensitive();
external_query = external_query.case_sensitive();
}
(local_query, external_query)
} }
NameToImport::Fuzzy(fuzzy_search_string) => { NameToImport::Fuzzy(fuzzy_search_string, case_sensitive) => {
let mut local_query = symbol_index::Query::new(fuzzy_search_string.clone()); let mut local_query = symbol_index::Query::new(fuzzy_search_string.clone());
local_query.fuzzy();
let mut external_query = import_map::Query::new(fuzzy_search_string.clone()) let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
.fuzzy() .fuzzy()
.assoc_search_mode(assoc_item_search); .assoc_search_mode(assoc_item_search);
if fuzzy_search_string.to_lowercase() != fuzzy_search_string { if case_sensitive {
local_query.case_sensitive(); local_query.case_sensitive();
external_query = external_query.case_sensitive(); external_query = external_query.case_sensitive();
} }

View File

@ -43,13 +43,20 @@ use triomphe::Arc;
use crate::RootDatabase; use crate::RootDatabase;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum SearchMode {
Fuzzy,
Exact,
Prefix,
}
#[derive(Debug)] #[derive(Debug)]
pub struct Query { pub struct Query {
query: String, query: String,
lowercased: String, lowercased: String,
only_types: bool, only_types: bool,
libs: bool, libs: bool,
exact: bool, mode: SearchMode,
case_sensitive: bool, case_sensitive: bool,
limit: usize, limit: usize,
} }
@ -62,7 +69,7 @@ impl Query {
lowercased, lowercased,
only_types: false, only_types: false,
libs: false, libs: false,
exact: false, mode: SearchMode::Fuzzy,
case_sensitive: false, case_sensitive: false,
limit: usize::max_value(), limit: usize::max_value(),
} }
@ -76,8 +83,16 @@ impl Query {
self.libs = true; self.libs = true;
} }
pub fn fuzzy(&mut self) {
self.mode = SearchMode::Fuzzy;
}
pub fn exact(&mut self) { pub fn exact(&mut self) {
self.exact = true; self.mode = SearchMode::Exact;
}
pub fn prefix(&mut self) {
self.mode = SearchMode::Prefix;
} }
pub fn case_sensitive(&mut self) { pub fn case_sensitive(&mut self) {
@ -329,13 +344,23 @@ impl Query {
{ {
continue; continue;
} }
if self.exact { let skip = match self.mode {
if symbol.name != self.query { SearchMode::Fuzzy => {
continue; self.case_sensitive
&& self.query.chars().any(|c| !symbol.name.contains(c))
} }
} else if self.case_sensitive SearchMode::Exact => symbol.name != self.query,
&& self.query.chars().any(|c| !symbol.name.contains(c)) SearchMode::Prefix if self.case_sensitive => {
{ !symbol.name.starts_with(&self.query)
}
SearchMode::Prefix => symbol
.name
.chars()
.zip(self.lowercased.chars())
.all(|(n, q)| n.to_lowercase().next() == Some(q)),
};
if skip {
continue; continue;
} }