Skip to content

Commit dca63d1

Browse files
committed
Auto merge of #15713 - Veykril:flyimport-completions-short, r=Veykril
Do flyimport completions by prefix search for short paths Fixes rust-lang/rust-analyzer#15711
2 parents d646ae8 + 4af730e commit dca63d1

File tree

6 files changed

+174
-47
lines changed

6 files changed

+174
-47
lines changed

crates/hir-def/src/import_map.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,8 @@ enum SearchMode {
283283
/// Import map entry should contain all letters from the query string,
284284
/// in the same order, but not necessary adjacent.
285285
Fuzzy,
286+
/// Import map entry should match the query string by prefix.
287+
Prefix,
286288
}
287289

288290
/// Three possible ways to search for the name in associated and/or other items.
@@ -324,6 +326,14 @@ impl Query {
324326
Self { search_mode: SearchMode::Fuzzy, ..self }
325327
}
326328

329+
pub fn prefix(self) -> Self {
330+
Self { search_mode: SearchMode::Prefix, ..self }
331+
}
332+
333+
pub fn exact(self) -> Self {
334+
Self { search_mode: SearchMode::Exact, ..self }
335+
}
336+
327337
/// Specifies whether we want to include associated items in the result.
328338
pub fn assoc_search_mode(self, assoc_mode: AssocSearchMode) -> Self {
329339
Self { assoc_mode, ..self }
@@ -361,7 +371,8 @@ impl Query {
361371
let query_string = if case_insensitive { &self.lowercased } else { &self.query };
362372

363373
match self.search_mode {
364-
SearchMode::Exact => &input == query_string,
374+
SearchMode::Exact => input == *query_string,
375+
SearchMode::Prefix => input.starts_with(query_string),
365376
SearchMode::Fuzzy => {
366377
let mut input_chars = input.chars();
367378
for query_char in query_string.chars() {

crates/ide-completion/src/completions/flyimport.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ use crate::{
1313
TypeLocation,
1414
},
1515
render::{render_resolution_with_import, render_resolution_with_import_pat, RenderContext},
16+
Completions,
1617
};
1718

18-
use super::Completions;
19-
2019
// Feature: Completion With Autoimport
2120
//
2221
// When completing names in the current scope, proposes additional imports from other modules or crates,
@@ -377,9 +376,12 @@ fn import_assets_for_path(
377376
&ctx.sema,
378377
ctx.token.parent()?,
379378
)?;
380-
if fuzzy_name_length < 3 {
381-
cov_mark::hit!(flyimport_exact_on_short_path);
382-
assets_for_path.path_fuzzy_name_to_exact(false);
379+
if fuzzy_name_length == 0 {
380+
// nothing matches the empty string exactly, but we still compute assoc items in this case
381+
assets_for_path.path_fuzzy_name_to_exact();
382+
} else if fuzzy_name_length < 3 {
383+
cov_mark::hit!(flyimport_prefix_on_short_path);
384+
assets_for_path.path_fuzzy_name_to_prefix();
383385
}
384386
Some(assets_for_path)
385387
}

crates/ide-completion/src/tests/flyimport.rs

+66-10
Original file line numberDiff line numberDiff line change
@@ -116,19 +116,47 @@ fn main() {
116116
}
117117

118118
#[test]
119-
fn short_paths_are_ignored() {
120-
cov_mark::check!(flyimport_exact_on_short_path);
119+
fn short_paths_are_prefix_matched() {
120+
cov_mark::check!(flyimport_prefix_on_short_path);
121121

122122
check(
123123
r#"
124124
//- /lib.rs crate:dep
125-
pub struct Bar;
125+
pub struct Barc;
126126
pub struct Rcar;
127127
pub struct Rc;
128+
pub const RC: () = ();
128129
pub mod some_module {
129130
pub struct Bar;
130131
pub struct Rcar;
131132
pub struct Rc;
133+
pub const RC: () = ();
134+
}
135+
136+
//- /main.rs crate:main deps:dep
137+
fn main() {
138+
Rc$0
139+
}
140+
"#,
141+
expect![[r#"
142+
st Rc (use dep::Rc)
143+
st Rcar (use dep::Rcar)
144+
st Rc (use dep::some_module::Rc)
145+
st Rcar (use dep::some_module::Rcar)
146+
"#]],
147+
);
148+
check(
149+
r#"
150+
//- /lib.rs crate:dep
151+
pub struct Barc;
152+
pub struct Rcar;
153+
pub struct Rc;
154+
pub const RC: () = ();
155+
pub mod some_module {
156+
pub struct Bar;
157+
pub struct Rcar;
158+
pub struct Rc;
159+
pub const RC: () = ();
132160
}
133161
134162
//- /main.rs crate:main deps:dep
@@ -137,8 +165,36 @@ fn main() {
137165
}
138166
"#,
139167
expect![[r#"
168+
ct RC (use dep::RC)
140169
st Rc (use dep::Rc)
170+
st Rcar (use dep::Rcar)
171+
ct RC (use dep::some_module::RC)
141172
st Rc (use dep::some_module::Rc)
173+
st Rcar (use dep::some_module::Rcar)
174+
"#]],
175+
);
176+
check(
177+
r#"
178+
//- /lib.rs crate:dep
179+
pub struct Barc;
180+
pub struct Rcar;
181+
pub struct Rc;
182+
pub const RC: () = ();
183+
pub mod some_module {
184+
pub struct Bar;
185+
pub struct Rcar;
186+
pub struct Rc;
187+
pub const RC: () = ();
188+
}
189+
190+
//- /main.rs crate:main deps:dep
191+
fn main() {
192+
RC$0
193+
}
194+
"#,
195+
expect![[r#"
196+
ct RC (use dep::RC)
197+
ct RC (use dep::some_module::RC)
142198
"#]],
143199
);
144200
}
@@ -841,8 +897,8 @@ fn main() {
841897
TES$0
842898
}"#,
843899
expect![[r#"
844-
ct TEST_CONST (use foo::TEST_CONST)
845-
"#]],
900+
ct TEST_CONST (use foo::TEST_CONST)
901+
"#]],
846902
);
847903

848904
check(
@@ -858,9 +914,9 @@ fn main() {
858914
tes$0
859915
}"#,
860916
expect![[r#"
861-
ct TEST_CONST (use foo::TEST_CONST)
862-
fn test_function() (use foo::test_function) fn() -> i32
863-
"#]],
917+
ct TEST_CONST (use foo::TEST_CONST)
918+
fn test_function() (use foo::test_function) fn() -> i32
919+
"#]],
864920
);
865921

866922
check(
@@ -873,9 +929,9 @@ mod foo {
873929
}
874930
875931
fn main() {
876-
Te$0
932+
Tes$0
877933
}"#,
878-
expect![[]],
934+
expect![""],
879935
);
880936
}
881937

crates/ide-db/src/imports/import_assets.rs

+36-11
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,29 @@ pub struct FirstSegmentUnresolved {
6868
pub enum NameToImport {
6969
/// Requires items with names that exactly match the given string, bool indicates case-sensitivity.
7070
Exact(String, bool),
71-
/// Requires items with names that case-insensitively contain all letters from the string,
71+
/// Requires items with names that match the given string by prefix, bool indicates case-sensitivity.
72+
Prefix(String, bool),
73+
/// Requires items with names contain all letters from the string,
7274
/// in the same order, but not necessary adjacent.
73-
Fuzzy(String),
75+
Fuzzy(String, bool),
7476
}
7577

7678
impl NameToImport {
7779
pub fn exact_case_sensitive(s: String) -> NameToImport {
7880
NameToImport::Exact(s, true)
7981
}
80-
}
8182

82-
impl NameToImport {
83+
pub fn fuzzy(s: String) -> NameToImport {
84+
// unless all chars are lowercase, we do a case sensitive search
85+
let case_sensitive = s.chars().any(|c| c.is_uppercase());
86+
NameToImport::Fuzzy(s, case_sensitive)
87+
}
88+
8389
pub fn text(&self) -> &str {
8490
match self {
85-
NameToImport::Exact(text, _) => text.as_str(),
86-
NameToImport::Fuzzy(text) => text.as_str(),
91+
NameToImport::Prefix(text, _)
92+
| NameToImport::Exact(text, _)
93+
| NameToImport::Fuzzy(text, _) => text.as_str(),
8794
}
8895
}
8996
}
@@ -165,7 +172,7 @@ impl ImportAssets {
165172
Some(Self {
166173
import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
167174
receiver_ty,
168-
assoc_item_name: NameToImport::Fuzzy(fuzzy_method_name),
175+
assoc_item_name: NameToImport::fuzzy(fuzzy_method_name),
169176
}),
170177
module_with_candidate: module_with_method_call,
171178
candidate_node,
@@ -228,12 +235,30 @@ impl ImportAssets {
228235
self.search_for(sema, None, prefer_no_std)
229236
}
230237

231-
pub fn path_fuzzy_name_to_exact(&mut self, case_sensitive: bool) {
238+
/// Requires imports to by prefix instead of fuzzily.
239+
pub fn path_fuzzy_name_to_prefix(&mut self) {
240+
if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
241+
&mut self.import_candidate
242+
{
243+
let (name, case_sensitive) = match to_import {
244+
NameToImport::Fuzzy(name, case_sensitive) => {
245+
(std::mem::take(name), *case_sensitive)
246+
}
247+
_ => return,
248+
};
249+
*to_import = NameToImport::Prefix(name, case_sensitive);
250+
}
251+
}
252+
253+
/// Requires imports to match exactly instead of fuzzily.
254+
pub fn path_fuzzy_name_to_exact(&mut self) {
232255
if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
233256
&mut self.import_candidate
234257
{
235-
let name = match to_import {
236-
NameToImport::Fuzzy(name) => std::mem::take(name),
258+
let (name, case_sensitive) = match to_import {
259+
NameToImport::Fuzzy(name, case_sensitive) => {
260+
(std::mem::take(name), *case_sensitive)
261+
}
237262
_ => return,
238263
};
239264
*to_import = NameToImport::Exact(name, case_sensitive);
@@ -623,7 +648,7 @@ impl ImportCandidate {
623648
fuzzy_name: String,
624649
sema: &Semantics<'_, RootDatabase>,
625650
) -> Option<Self> {
626-
path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name))
651+
path_import_candidate(sema, qualifier, NameToImport::fuzzy(fuzzy_name))
627652
}
628653
}
629654

crates/ide-db/src/items_locator.rs

+19-11
Original file line numberDiff line numberDiff line change
@@ -31,26 +31,34 @@ pub fn items_with_name<'a>(
3131
)
3232
});
3333

34+
let prefix = matches!(name, NameToImport::Prefix(..));
3435
let (mut local_query, mut external_query) = match name {
35-
NameToImport::Exact(exact_name, case_sensitive) => {
36+
NameToImport::Prefix(exact_name, case_sensitive)
37+
| NameToImport::Exact(exact_name, case_sensitive) => {
3638
let mut local_query = symbol_index::Query::new(exact_name.clone());
37-
local_query.exact();
38-
39-
let external_query = import_map::Query::new(exact_name);
40-
41-
(
42-
local_query,
43-
if case_sensitive { external_query.case_sensitive() } else { external_query },
44-
)
39+
let mut external_query = import_map::Query::new(exact_name);
40+
if prefix {
41+
local_query.prefix();
42+
external_query = external_query.prefix();
43+
} else {
44+
local_query.exact();
45+
external_query = external_query.exact();
46+
}
47+
if case_sensitive {
48+
local_query.case_sensitive();
49+
external_query = external_query.case_sensitive();
50+
}
51+
(local_query, external_query)
4552
}
46-
NameToImport::Fuzzy(fuzzy_search_string) => {
53+
NameToImport::Fuzzy(fuzzy_search_string, case_sensitive) => {
4754
let mut local_query = symbol_index::Query::new(fuzzy_search_string.clone());
55+
local_query.fuzzy();
4856

4957
let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
5058
.fuzzy()
5159
.assoc_search_mode(assoc_item_search);
5260

53-
if fuzzy_search_string.to_lowercase() != fuzzy_search_string {
61+
if case_sensitive {
5462
local_query.case_sensitive();
5563
external_query = external_query.case_sensitive();
5664
}

crates/ide-db/src/symbol_index.rs

+34-9
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,20 @@ use triomphe::Arc;
4343

4444
use crate::RootDatabase;
4545

46+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
47+
enum SearchMode {
48+
Fuzzy,
49+
Exact,
50+
Prefix,
51+
}
52+
4653
#[derive(Debug)]
4754
pub struct Query {
4855
query: String,
4956
lowercased: String,
5057
only_types: bool,
5158
libs: bool,
52-
exact: bool,
59+
mode: SearchMode,
5360
case_sensitive: bool,
5461
limit: usize,
5562
}
@@ -62,7 +69,7 @@ impl Query {
6269
lowercased,
6370
only_types: false,
6471
libs: false,
65-
exact: false,
72+
mode: SearchMode::Fuzzy,
6673
case_sensitive: false,
6774
limit: usize::max_value(),
6875
}
@@ -76,8 +83,16 @@ impl Query {
7683
self.libs = true;
7784
}
7885

86+
pub fn fuzzy(&mut self) {
87+
self.mode = SearchMode::Fuzzy;
88+
}
89+
7990
pub fn exact(&mut self) {
80-
self.exact = true;
91+
self.mode = SearchMode::Exact;
92+
}
93+
94+
pub fn prefix(&mut self) {
95+
self.mode = SearchMode::Prefix;
8196
}
8297

8398
pub fn case_sensitive(&mut self) {
@@ -329,13 +344,23 @@ impl Query {
329344
{
330345
continue;
331346
}
332-
if self.exact {
333-
if symbol.name != self.query {
334-
continue;
347+
let skip = match self.mode {
348+
SearchMode::Fuzzy => {
349+
self.case_sensitive
350+
&& self.query.chars().any(|c| !symbol.name.contains(c))
335351
}
336-
} else if self.case_sensitive
337-
&& self.query.chars().any(|c| !symbol.name.contains(c))
338-
{
352+
SearchMode::Exact => symbol.name != self.query,
353+
SearchMode::Prefix if self.case_sensitive => {
354+
!symbol.name.starts_with(&self.query)
355+
}
356+
SearchMode::Prefix => symbol
357+
.name
358+
.chars()
359+
.zip(self.lowercased.chars())
360+
.all(|(n, q)| n.to_lowercase().next() == Some(q)),
361+
};
362+
363+
if skip {
339364
continue;
340365
}
341366

0 commit comments

Comments
 (0)