Do flyimport completions by prefix search for short paths

This commit is contained in:
Lukas Wirth 2023-10-05 13:21:12 +02:00
parent 695c612489
commit 4af730eb26
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,
/// in the same order, but not necessary adjacent.
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.
@ -324,6 +326,14 @@ impl Query {
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.
pub fn assoc_search_mode(self, assoc_mode: AssocSearchMode) -> Self {
Self { assoc_mode, ..self }
@ -361,7 +371,8 @@ impl Query {
let query_string = if case_insensitive { &self.lowercased } else { &self.query };
match self.search_mode {
SearchMode::Exact => &input == query_string,
SearchMode::Exact => input == *query_string,
SearchMode::Prefix => input.starts_with(query_string),
SearchMode::Fuzzy => {
let mut input_chars = input.chars();
for query_char in query_string.chars() {

View File

@ -13,10 +13,9 @@ use crate::{
TypeLocation,
},
render::{render_resolution_with_import, render_resolution_with_import_pat, RenderContext},
Completions,
};
use super::Completions;
// Feature: Completion With Autoimport
//
// 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.token.parent()?,
)?;
if fuzzy_name_length < 3 {
cov_mark::hit!(flyimport_exact_on_short_path);
assets_for_path.path_fuzzy_name_to_exact(false);
if fuzzy_name_length == 0 {
// nothing matches the empty string exactly, but we still compute assoc items in this case
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)
}

View File

@ -116,19 +116,47 @@ fn main() {
}
#[test]
fn short_paths_are_ignored() {
cov_mark::check!(flyimport_exact_on_short_path);
fn short_paths_are_prefix_matched() {
cov_mark::check!(flyimport_prefix_on_short_path);
check(
r#"
//- /lib.rs crate:dep
pub struct Bar;
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#"
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
@ -137,8 +165,36 @@ fn main() {
}
"#,
expect![[r#"
ct 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 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
}"#,
expect![[r#"
ct TEST_CONST (use foo::TEST_CONST)
"#]],
ct TEST_CONST (use foo::TEST_CONST)
"#]],
);
check(
@ -858,9 +914,9 @@ fn main() {
tes$0
}"#,
expect![[r#"
ct TEST_CONST (use foo::TEST_CONST)
fn test_function() (use foo::test_function) fn() -> i32
"#]],
ct TEST_CONST (use foo::TEST_CONST)
fn test_function() (use foo::test_function) fn() -> i32
"#]],
);
check(
@ -873,9 +929,9 @@ mod foo {
}
fn main() {
Te$0
Tes$0
}"#,
expect![[]],
expect![""],
);
}

View File

@ -68,22 +68,29 @@ pub struct FirstSegmentUnresolved {
pub enum NameToImport {
/// Requires items with names that exactly match the given string, bool indicates case-sensitivity.
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.
Fuzzy(String),
Fuzzy(String, bool),
}
impl NameToImport {
pub fn exact_case_sensitive(s: String) -> NameToImport {
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 {
match self {
NameToImport::Exact(text, _) => text.as_str(),
NameToImport::Fuzzy(text) => text.as_str(),
NameToImport::Prefix(text, _)
| NameToImport::Exact(text, _)
| NameToImport::Fuzzy(text, _) => text.as_str(),
}
}
}
@ -165,7 +172,7 @@ impl ImportAssets {
Some(Self {
import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
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,
candidate_node,
@ -228,12 +235,30 @@ impl ImportAssets {
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, .. }) =
&mut self.import_candidate
{
let name = match to_import {
NameToImport::Fuzzy(name) => std::mem::take(name),
let (name, case_sensitive) = match to_import {
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,
};
*to_import = NameToImport::Exact(name, case_sensitive);
@ -623,7 +648,7 @@ impl ImportCandidate {
fuzzy_name: String,
sema: &Semantics<'_, RootDatabase>,
) -> 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 {
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());
local_query.exact();
let external_query = import_map::Query::new(exact_name);
(
local_query,
if case_sensitive { external_query.case_sensitive() } else { external_query },
)
let mut external_query = import_map::Query::new(exact_name);
if prefix {
local_query.prefix();
external_query = external_query.prefix();
} else {
local_query.exact();
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());
local_query.fuzzy();
let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
.fuzzy()
.assoc_search_mode(assoc_item_search);
if fuzzy_search_string.to_lowercase() != fuzzy_search_string {
if case_sensitive {
local_query.case_sensitive();
external_query = external_query.case_sensitive();
}

View File

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