mirror of
https://github.com/rust-lang/rust.git
synced 2025-01-21 04:03:11 +00:00
Do flyimport completions by prefix search for short paths
This commit is contained in:
parent
695c612489
commit
4af730eb26
@ -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() {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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![""],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user