aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/handlers/extract_variable.rs2
-rw-r--r--crates/assists/src/handlers/replace_derive_with_manual_impl.rs31
-rw-r--r--crates/assists/src/utils/import_assets.rs37
-rw-r--r--crates/completion/src/completions/postfix.rs2
-rw-r--r--crates/completion/src/completions/postfix/format_like.rs13
-rw-r--r--crates/completion/src/completions/unqualified_path.rs15
-rw-r--r--crates/completion/src/lib.rs2
-rw-r--r--crates/hir_def/src/import_map.rs222
-rw-r--r--crates/hir_def/src/nameres.rs2
-rw-r--r--crates/ide/src/lib.rs2
-rw-r--r--crates/ide_db/src/imports_locator.rs23
-rw-r--r--crates/rust-analyzer/src/handlers.rs2
-rw-r--r--editors/code/src/commands.ts10
13 files changed, 259 insertions, 104 deletions
diff --git a/crates/assists/src/handlers/extract_variable.rs b/crates/assists/src/handlers/extract_variable.rs
index d2ae137cd..9957012fe 100644
--- a/crates/assists/src/handlers/extract_variable.rs
+++ b/crates/assists/src/handlers/extract_variable.rs
@@ -91,7 +91,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
91 // extra newlines in the indent block 91 // extra newlines in the indent block
92 let text = indent.text(); 92 let text = indent.text();
93 if text.starts_with('\n') { 93 if text.starts_with('\n') {
94 buf.push_str("\n"); 94 buf.push('\n');
95 buf.push_str(text.trim_start_matches('\n')); 95 buf.push_str(text.trim_start_matches('\n'));
96 } else { 96 } else {
97 buf.push_str(text); 97 buf.push_str(text);
diff --git a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
index 4d6a1956b..cb7a5c104 100644
--- a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -62,21 +62,22 @@ pub(crate) fn replace_derive_with_manual_impl(
62 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; 62 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?;
63 let current_crate = current_module.krate(); 63 let current_crate = current_module.krate();
64 64
65 let found_traits = 65 let found_traits = imports_locator::find_exact_imports(
66 imports_locator::find_exact_imports(&ctx.sema, current_crate, trait_token.text()) 66 &ctx.sema,
67 .filter_map( 67 current_crate,
68 |candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate { 68 trait_token.text().to_string(),
69 either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_), 69 )
70 _ => None, 70 .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
71 }, 71 either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
72 ) 72 _ => None,
73 .flat_map(|trait_| { 73 })
74 current_module 74 .flat_map(|trait_| {
75 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_)) 75 current_module
76 .as_ref() 76 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
77 .map(mod_path_to_ast) 77 .as_ref()
78 .zip(Some(trait_)) 78 .map(mod_path_to_ast)
79 }); 79 .zip(Some(trait_))
80 });
80 81
81 let mut no_traits_found = true; 82 let mut no_traits_found = true;
82 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { 83 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {
diff --git a/crates/assists/src/utils/import_assets.rs b/crates/assists/src/utils/import_assets.rs
index ff5c0e78e..4ce82c1ba 100644
--- a/crates/assists/src/utils/import_assets.rs
+++ b/crates/assists/src/utils/import_assets.rs
@@ -179,25 +179,24 @@ impl ImportAssets {
179 } 179 }
180 }; 180 };
181 181
182 let mut res = 182 let mut res = imports_locator::find_exact_imports(
183 imports_locator::find_exact_imports(sema, current_crate, &self.get_search_query()) 183 sema,
184 .filter_map(filter) 184 current_crate,
185 .filter_map(|candidate| { 185 self.get_search_query().to_string(),
186 let item: hir::ItemInNs = candidate.either(Into::into, Into::into); 186 )
187 if let Some(prefix_kind) = prefixed { 187 .filter_map(filter)
188 self.module_with_name_to_import.find_use_path_prefixed( 188 .filter_map(|candidate| {
189 db, 189 let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
190 item, 190 if let Some(prefix_kind) = prefixed {
191 prefix_kind, 191 self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind)
192 ) 192 } else {
193 } else { 193 self.module_with_name_to_import.find_use_path(db, item)
194 self.module_with_name_to_import.find_use_path(db, item) 194 }
195 } 195 .map(|path| (path, item))
196 .map(|path| (path, item)) 196 })
197 }) 197 .filter(|(use_path, _)| use_path.len() > 1)
198 .filter(|(use_path, _)| use_path.len() > 1) 198 .take(20)
199 .take(20) 199 .collect::<Vec<_>>();
200 .collect::<Vec<_>>();
201 res.sort_by_key(|(path, _)| path.clone()); 200 res.sort_by_key(|(path, _)| path.clone());
202 res 201 res
203 } 202 }
diff --git a/crates/completion/src/completions/postfix.rs b/crates/completion/src/completions/postfix.rs
index d6db82a93..3883d6d21 100644
--- a/crates/completion/src/completions/postfix.rs
+++ b/crates/completion/src/completions/postfix.rs
@@ -502,7 +502,7 @@ fn main() {
502 #[test] 502 #[test]
503 fn postfix_completion_for_format_like_strings() { 503 fn postfix_completion_for_format_like_strings() {
504 check_edit( 504 check_edit(
505 "fmt", 505 "format",
506 r#"fn main() { "{some_var:?}".<|> }"#, 506 r#"fn main() { "{some_var:?}".<|> }"#,
507 r#"fn main() { format!("{:?}", some_var) }"#, 507 r#"fn main() { format!("{:?}", some_var) }"#,
508 ); 508 );
diff --git a/crates/completion/src/completions/postfix/format_like.rs b/crates/completion/src/completions/postfix/format_like.rs
index 88ba86acb..def4b13fb 100644
--- a/crates/completion/src/completions/postfix/format_like.rs
+++ b/crates/completion/src/completions/postfix/format_like.rs
@@ -22,7 +22,7 @@ use syntax::ast::{self, AstToken};
22 22
23/// Mapping ("postfix completion item" => "macro to use") 23/// Mapping ("postfix completion item" => "macro to use")
24static KINDS: &[(&str, &str)] = &[ 24static KINDS: &[(&str, &str)] = &[
25 ("fmt", "format!"), 25 ("format", "format!"),
26 ("panic", "panic!"), 26 ("panic", "panic!"),
27 ("println", "println!"), 27 ("println", "println!"),
28 ("eprintln", "eprintln!"), 28 ("eprintln", "eprintln!"),
@@ -108,7 +108,8 @@ impl FormatStrParser {
108 // "{MyStruct { val_a: 0, val_b: 1 }}". 108 // "{MyStruct { val_a: 0, val_b: 1 }}".
109 let mut inexpr_open_count = 0; 109 let mut inexpr_open_count = 0;
110 110
111 for chr in self.input.chars() { 111 let mut chars = self.input.chars().peekable();
112 while let Some(chr) = chars.next() {
112 match (self.state, chr) { 113 match (self.state, chr) {
113 (State::NotExpr, '{') => { 114 (State::NotExpr, '{') => {
114 self.output.push(chr); 115 self.output.push(chr);
@@ -157,6 +158,11 @@ impl FormatStrParser {
157 inexpr_open_count -= 1; 158 inexpr_open_count -= 1;
158 } 159 }
159 } 160 }
161 (State::Expr, ':') if chars.peek().copied() == Some(':') => {
162 // path seperator
163 current_expr.push_str("::");
164 chars.next();
165 }
160 (State::Expr, ':') => { 166 (State::Expr, ':') => {
161 if inexpr_open_count == 0 { 167 if inexpr_open_count == 0 {
162 // We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}" 168 // We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
@@ -249,6 +255,9 @@ mod tests {
249 expect![["{:?}; SomeStruct { val_a: 0, val_b: 1 }"]], 255 expect![["{:?}; SomeStruct { val_a: 0, val_b: 1 }"]],
250 ), 256 ),
251 ("{ 2 + 2 }", expect![["{}; 2 + 2"]]), 257 ("{ 2 + 2 }", expect![["{}; 2 + 2"]]),
258 ("{strsim::jaro_winkle(a)}", expect![["{}; strsim::jaro_winkle(a)"]]),
259 ("{foo::bar::baz()}", expect![["{}; foo::bar::baz()"]]),
260 ("{foo::bar():?}", expect![["{:?}; foo::bar()"]]),
252 ]; 261 ];
253 262
254 for (input, output) in test_vector { 263 for (input, output) in test_vector {
diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs
index d09849752..81a6d00e2 100644
--- a/crates/completion/src/completions/unqualified_path.rs
+++ b/crates/completion/src/completions/unqualified_path.rs
@@ -101,8 +101,9 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T
101// 101//
102// .Fuzzy search details 102// .Fuzzy search details
103// 103//
104// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the identifiers only 104// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
105// (i.e. in `HashMap` in the `std::collections::HashMap` path), also not in the module indentifiers. 105// (i.e. in `HashMap` in the `std::collections::HashMap` path).
106// For the same reasons, avoids searching for any imports for inputs with their length less that 2 symbols.
106// 107//
107// .Merge Behavior 108// .Merge Behavior
108// 109//
@@ -126,15 +127,20 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()
126 let _p = profile::span("fuzzy_completion"); 127 let _p = profile::span("fuzzy_completion");
127 let potential_import_name = ctx.token.to_string(); 128 let potential_import_name = ctx.token.to_string();
128 129
130 if potential_import_name.len() < 2 {
131 return None;
132 }
133
129 let current_module = ctx.scope.module()?; 134 let current_module = ctx.scope.module()?;
130 let anchor = ctx.name_ref_syntax.as_ref()?; 135 let anchor = ctx.name_ref_syntax.as_ref()?;
131 let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; 136 let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
132 137
138 let user_input_lowercased = potential_import_name.to_lowercase();
133 let mut all_mod_paths = imports_locator::find_similar_imports( 139 let mut all_mod_paths = imports_locator::find_similar_imports(
134 &ctx.sema, 140 &ctx.sema,
135 ctx.krate?, 141 ctx.krate?,
136 Some(100), 142 Some(40),
137 &potential_import_name, 143 potential_import_name,
138 true, 144 true,
139 ) 145 )
140 .filter_map(|import_candidate| { 146 .filter_map(|import_candidate| {
@@ -150,7 +156,6 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()
150 .filter(|(mod_path, _)| mod_path.len() > 1) 156 .filter(|(mod_path, _)| mod_path.len() > 1)
151 .collect::<Vec<_>>(); 157 .collect::<Vec<_>>();
152 158
153 let user_input_lowercased = potential_import_name.to_lowercase();
154 all_mod_paths.sort_by_cached_key(|(mod_path, _)| { 159 all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
155 compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased) 160 compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
156 }); 161 });
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs
index 8e27bb153..c57d05bbe 100644
--- a/crates/completion/src/lib.rs
+++ b/crates/completion/src/lib.rs
@@ -137,7 +137,7 @@ pub fn resolve_completion_edits(
137 config: &CompletionConfig, 137 config: &CompletionConfig,
138 position: FilePosition, 138 position: FilePosition,
139 full_import_path: &str, 139 full_import_path: &str,
140 imported_name: &str, 140 imported_name: String,
141) -> Option<Vec<TextEdit>> { 141) -> Option<Vec<TextEdit>> {
142 let ctx = CompletionContext::new(db, position, config)?; 142 let ctx = CompletionContext::new(db, position, config)?;
143 let anchor = ctx.name_ref_syntax.as_ref()?; 143 let anchor = ctx.name_ref_syntax.as_ref()?;
diff --git a/crates/hir_def/src/import_map.rs b/crates/hir_def/src/import_map.rs
index c0f108848..c4dc894df 100644
--- a/crates/hir_def/src/import_map.rs
+++ b/crates/hir_def/src/import_map.rs
@@ -238,32 +238,53 @@ pub enum ImportKind {
238 BuiltinType, 238 BuiltinType,
239} 239}
240 240
241/// A way to match import map contents against the search query.
242#[derive(Debug)]
243pub enum SearchMode {
244 /// Import map entry should strictly match the query string.
245 Equals,
246 /// Import map entry should contain the query string.
247 Contains,
248 /// Import map entry should contain all letters from the query string,
249 /// in the same order, but not necessary adjacent.
250 Fuzzy,
251}
252
241#[derive(Debug)] 253#[derive(Debug)]
242pub struct Query { 254pub struct Query {
243 query: String, 255 query: String,
244 lowercased: String, 256 lowercased: String,
245 anchor_end: bool, 257 name_only: bool,
258 search_mode: SearchMode,
246 case_sensitive: bool, 259 case_sensitive: bool,
247 limit: usize, 260 limit: usize,
248 exclude_import_kinds: FxHashSet<ImportKind>, 261 exclude_import_kinds: FxHashSet<ImportKind>,
249} 262}
250 263
251impl Query { 264impl Query {
252 pub fn new(query: &str) -> Self { 265 pub fn new(query: String) -> Self {
266 let lowercased = query.to_lowercase();
253 Self { 267 Self {
254 lowercased: query.to_lowercase(), 268 query,
255 query: query.to_string(), 269 lowercased,
256 anchor_end: false, 270 name_only: false,
271 search_mode: SearchMode::Contains,
257 case_sensitive: false, 272 case_sensitive: false,
258 limit: usize::max_value(), 273 limit: usize::max_value(),
259 exclude_import_kinds: FxHashSet::default(), 274 exclude_import_kinds: FxHashSet::default(),
260 } 275 }
261 } 276 }
262 277
263 /// Only returns items whose paths end with the (case-insensitive) query string as their last 278 /// Matches entries' names only, ignoring the rest of
264 /// segment. 279 /// the qualifier.
265 pub fn anchor_end(self) -> Self { 280 /// Example: for `std::marker::PhantomData`, the name is `PhantomData`.
266 Self { anchor_end: true, ..self } 281 pub fn name_only(self) -> Self {
282 Self { name_only: true, ..self }
283 }
284
285 /// Specifies the way to search for the entries using the query.
286 pub fn search_mode(self, search_mode: SearchMode) -> Self {
287 Self { search_mode, ..self }
267 } 288 }
268 289
269 /// Limits the returned number of items to `limit`. 290 /// Limits the returned number of items to `limit`.
@@ -283,6 +304,40 @@ impl Query {
283 } 304 }
284} 305}
285 306
307fn contains_query(query: &Query, input_path: &ImportPath, enforce_lowercase: bool) -> bool {
308 let mut input = if query.name_only {
309 input_path.segments.last().unwrap().to_string()
310 } else {
311 input_path.to_string()
312 };
313 if enforce_lowercase || !query.case_sensitive {
314 input.make_ascii_lowercase();
315 }
316
317 let query_string =
318 if !enforce_lowercase && query.case_sensitive { &query.query } else { &query.lowercased };
319
320 match query.search_mode {
321 SearchMode::Equals => &input == query_string,
322 SearchMode::Contains => input.contains(query_string),
323 SearchMode::Fuzzy => {
324 let mut unchecked_query_chars = query_string.chars();
325 let mut mismatching_query_char = unchecked_query_chars.next();
326
327 for input_char in input.chars() {
328 match mismatching_query_char {
329 None => return true,
330 Some(matching_query_char) if matching_query_char == input_char => {
331 mismatching_query_char = unchecked_query_chars.next();
332 }
333 _ => (),
334 }
335 }
336 mismatching_query_char.is_none()
337 }
338 }
339}
340
286/// Searches dependencies of `krate` for an importable path matching `query`. 341/// Searches dependencies of `krate` for an importable path matching `query`.
287/// 342///
288/// This returns a list of items that could be imported from dependencies of `krate`. 343/// This returns a list of items that could be imported from dependencies of `krate`.
@@ -312,39 +367,29 @@ pub fn search_dependencies<'a>(
312 let importables = &import_map.importables[indexed_value.value as usize..]; 367 let importables = &import_map.importables[indexed_value.value as usize..];
313 368
314 // Path shared by the importable items in this group. 369 // Path shared by the importable items in this group.
315 let path = &import_map.map[&importables[0]].path; 370 let common_importables_path = &import_map.map[&importables[0]].path;
316 371 if !contains_query(&query, common_importables_path, true) {
317 if query.anchor_end { 372 continue;
318 // Last segment must match query.
319 let last = path.segments.last().unwrap().to_string();
320 if last.to_lowercase() != query.lowercased {
321 continue;
322 }
323 } 373 }
324 374
375 let common_importables_path_fst = fst_path(common_importables_path);
325 // Add the items from this `ModPath` group. Those are all subsequent items in 376 // Add the items from this `ModPath` group. Those are all subsequent items in
326 // `importables` whose paths match `path`. 377 // `importables` whose paths match `path`.
327 let iter = importables 378 let iter = importables
328 .iter() 379 .iter()
329 .copied() 380 .copied()
330 .take_while(|item| { 381 .take_while(|item| {
331 let item_path = &import_map.map[item].path; 382 common_importables_path_fst == fst_path(&import_map.map[item].path)
332 fst_path(item_path) == fst_path(path)
333 }) 383 })
334 .filter(|&item| match item_import_kind(item) { 384 .filter(|&item| match item_import_kind(item) {
335 Some(import_kind) => !query.exclude_import_kinds.contains(&import_kind), 385 Some(import_kind) => !query.exclude_import_kinds.contains(&import_kind),
336 None => true, 386 None => true,
387 })
388 .filter(|item| {
389 !query.case_sensitive // we've already checked the common importables path case-insensitively
390 || contains_query(&query, &import_map.map[item].path, false)
337 }); 391 });
338 392 res.extend(iter);
339 if query.case_sensitive {
340 // FIXME: This does not do a subsequence match.
341 res.extend(iter.filter(|item| {
342 let item_path = &import_map.map[item].path;
343 item_path.to_string().contains(&query.query)
344 }));
345 } else {
346 res.extend(iter);
347 }
348 393
349 if res.len() >= query.limit { 394 if res.len() >= query.limit {
350 res.truncate(query.limit); 395 res.truncate(query.limit);
@@ -388,7 +433,7 @@ mod tests {
388 use base_db::{fixture::WithFixture, SourceDatabase, Upcast}; 433 use base_db::{fixture::WithFixture, SourceDatabase, Upcast};
389 use expect_test::{expect, Expect}; 434 use expect_test::{expect, Expect};
390 435
391 use crate::{test_db::TestDB, AssocContainerId, Lookup}; 436 use crate::{data::FunctionData, test_db::TestDB, AssocContainerId, Lookup};
392 437
393 use super::*; 438 use super::*;
394 439
@@ -407,14 +452,31 @@ mod tests {
407 .into_iter() 452 .into_iter()
408 .filter_map(|item| { 453 .filter_map(|item| {
409 let mark = match item { 454 let mark = match item {
455 ItemInNs::Types(ModuleDefId::FunctionId(_))
456 | ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f",
410 ItemInNs::Types(_) => "t", 457 ItemInNs::Types(_) => "t",
411 ItemInNs::Values(_) => "v", 458 ItemInNs::Values(_) => "v",
412 ItemInNs::Macros(_) => "m", 459 ItemInNs::Macros(_) => "m",
413 }; 460 };
414 let item = assoc_to_trait(&db, item);
415 item.krate(db.upcast()).map(|krate| { 461 item.krate(db.upcast()).map(|krate| {
416 let map = db.import_map(krate); 462 let map = db.import_map(krate);
417 let path = map.path_of(item).unwrap(); 463
464 let path = match assoc_to_trait(&db, item) {
465 Some(trait_) => {
466 let mut full_path = map.path_of(trait_).unwrap().to_string();
467 if let ItemInNs::Types(ModuleDefId::FunctionId(function_id))
468 | ItemInNs::Values(ModuleDefId::FunctionId(function_id)) = item
469 {
470 full_path += &format!(
471 "::{}",
472 FunctionData::fn_data_query(&db, function_id).name
473 );
474 }
475 full_path
476 }
477 None => map.path_of(item).unwrap().to_string(),
478 };
479
418 format!( 480 format!(
419 "{}::{} ({})\n", 481 "{}::{} ({})\n",
420 crate_graph[krate].display_name.as_ref().unwrap(), 482 crate_graph[krate].display_name.as_ref().unwrap(),
@@ -427,15 +489,15 @@ mod tests {
427 expect.assert_eq(&actual) 489 expect.assert_eq(&actual)
428 } 490 }
429 491
430 fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> ItemInNs { 492 fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> Option<ItemInNs> {
431 let assoc: AssocItemId = match item { 493 let assoc: AssocItemId = match item {
432 ItemInNs::Types(it) | ItemInNs::Values(it) => match it { 494 ItemInNs::Types(it) | ItemInNs::Values(it) => match it {
433 ModuleDefId::TypeAliasId(it) => it.into(), 495 ModuleDefId::TypeAliasId(it) => it.into(),
434 ModuleDefId::FunctionId(it) => it.into(), 496 ModuleDefId::FunctionId(it) => it.into(),
435 ModuleDefId::ConstId(it) => it.into(), 497 ModuleDefId::ConstId(it) => it.into(),
436 _ => return item, 498 _ => return None,
437 }, 499 },
438 _ => return item, 500 _ => return None,
439 }; 501 };
440 502
441 let container = match assoc { 503 let container = match assoc {
@@ -445,8 +507,8 @@ mod tests {
445 }; 507 };
446 508
447 match container { 509 match container {
448 AssocContainerId::TraitId(it) => ItemInNs::Types(it.into()), 510 AssocContainerId::TraitId(it) => Some(ItemInNs::Types(it.into())),
449 _ => item, 511 _ => None,
450 } 512 }
451 } 513 }
452 514
@@ -685,7 +747,7 @@ mod tests {
685 } 747 }
686 748
687 #[test] 749 #[test]
688 fn search() { 750 fn search_mode() {
689 let ra_fixture = r#" 751 let ra_fixture = r#"
690 //- /main.rs crate:main deps:dep 752 //- /main.rs crate:main deps:dep
691 //- /dep.rs crate:dep deps:tdep 753 //- /dep.rs crate:dep deps:tdep
@@ -713,28 +775,96 @@ mod tests {
713 check_search( 775 check_search(
714 ra_fixture, 776 ra_fixture,
715 "main", 777 "main",
716 Query::new("fmt"), 778 Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy),
717 expect![[r#" 779 expect![[r#"
718 dep::fmt (t) 780 dep::fmt (t)
719 dep::Fmt (t) 781 dep::Fmt (t)
720 dep::Fmt (v) 782 dep::Fmt (v)
721 dep::Fmt (m) 783 dep::Fmt (m)
722 dep::fmt::Display (t) 784 dep::fmt::Display (t)
723 dep::format (v) 785 dep::format (f)
786 dep::fmt::Display::fmt (f)
787 "#]],
788 );
789
790 check_search(
791 ra_fixture,
792 "main",
793 Query::new("fmt".to_string()).search_mode(SearchMode::Equals),
794 expect![[r#"
795 dep::fmt (t)
796 dep::Fmt (t)
797 dep::Fmt (v)
798 dep::Fmt (m)
799 dep::fmt::Display::fmt (f)
800 "#]],
801 );
802
803 check_search(
804 ra_fixture,
805 "main",
806 Query::new("fmt".to_string()).search_mode(SearchMode::Contains),
807 expect![[r#"
808 dep::fmt (t)
809 dep::Fmt (t)
810 dep::Fmt (v)
811 dep::Fmt (m)
724 dep::fmt::Display (t) 812 dep::fmt::Display (t)
813 dep::fmt::Display::fmt (f)
725 "#]], 814 "#]],
726 ); 815 );
816 }
817
818 #[test]
819 fn name_only() {
820 let ra_fixture = r#"
821 //- /main.rs crate:main deps:dep
822 //- /dep.rs crate:dep deps:tdep
823 use tdep::fmt as fmt_dep;
824 pub mod fmt {
825 pub trait Display {
826 fn fmt();
827 }
828 }
829 #[macro_export]
830 macro_rules! Fmt {
831 () => {};
832 }
833 pub struct Fmt;
834
835 pub fn format() {}
836 pub fn no() {}
837
838 //- /tdep.rs crate:tdep
839 pub mod fmt {
840 pub struct NotImportableFromMain;
841 }
842 "#;
727 843
728 check_search( 844 check_search(
729 ra_fixture, 845 ra_fixture,
730 "main", 846 "main",
731 Query::new("fmt").anchor_end(), 847 Query::new("fmt".to_string()),
732 expect![[r#" 848 expect![[r#"
733 dep::fmt (t) 849 dep::fmt (t)
734 dep::Fmt (t) 850 dep::Fmt (t)
735 dep::Fmt (v) 851 dep::Fmt (v)
736 dep::Fmt (m) 852 dep::Fmt (m)
737 dep::fmt::Display (t) 853 dep::fmt::Display (t)
854 dep::fmt::Display::fmt (f)
855 "#]],
856 );
857
858 check_search(
859 ra_fixture,
860 "main",
861 Query::new("fmt".to_string()).name_only(),
862 expect![[r#"
863 dep::fmt (t)
864 dep::Fmt (t)
865 dep::Fmt (v)
866 dep::Fmt (m)
867 dep::fmt::Display::fmt (f)
738 "#]], 868 "#]],
739 ); 869 );
740 } 870 }
@@ -752,7 +882,7 @@ mod tests {
752 check_search( 882 check_search(
753 ra_fixture, 883 ra_fixture,
754 "main", 884 "main",
755 Query::new("FMT"), 885 Query::new("FMT".to_string()),
756 expect![[r#" 886 expect![[r#"
757 dep::fmt (t) 887 dep::fmt (t)
758 dep::fmt (v) 888 dep::fmt (v)
@@ -764,7 +894,7 @@ mod tests {
764 check_search( 894 check_search(
765 ra_fixture, 895 ra_fixture,
766 "main", 896 "main",
767 Query::new("FMT").case_sensitive(), 897 Query::new("FMT".to_string()).case_sensitive(),
768 expect![[r#" 898 expect![[r#"
769 dep::FMT (t) 899 dep::FMT (t)
770 dep::FMT (v) 900 dep::FMT (v)
@@ -793,7 +923,7 @@ mod tests {
793 pub fn no() {} 923 pub fn no() {}
794 "#, 924 "#,
795 "main", 925 "main",
796 Query::new("").limit(2), 926 Query::new("".to_string()).limit(2),
797 expect![[r#" 927 expect![[r#"
798 dep::fmt (t) 928 dep::fmt (t)
799 dep::Fmt (t) 929 dep::Fmt (t)
@@ -814,7 +944,7 @@ mod tests {
814 check_search( 944 check_search(
815 ra_fixture, 945 ra_fixture,
816 "main", 946 "main",
817 Query::new("FMT"), 947 Query::new("FMT".to_string()),
818 expect![[r#" 948 expect![[r#"
819 dep::fmt (t) 949 dep::fmt (t)
820 dep::fmt (v) 950 dep::fmt (v)
@@ -826,7 +956,7 @@ mod tests {
826 check_search( 956 check_search(
827 ra_fixture, 957 ra_fixture,
828 "main", 958 "main",
829 Query::new("FMT").exclude_import_kind(ImportKind::Adt), 959 Query::new("FMT".to_string()).exclude_import_kind(ImportKind::Adt),
830 expect![[r#""#]], 960 expect![[r#""#]],
831 ); 961 );
832 } 962 }
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index ffd0381d4..9bf358775 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -249,7 +249,7 @@ impl CrateDefMap {
249 buf.push_str(" _"); 249 buf.push_str(" _");
250 } 250 }
251 251
252 buf.push_str("\n"); 252 buf.push('\n');
253 } 253 }
254 254
255 for (name, child) in map.modules[module].children.iter() { 255 for (name, child) in map.modules[module].children.iter() {
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 41eb139d1..b3331f03f 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -475,7 +475,7 @@ impl Analysis {
475 config: &CompletionConfig, 475 config: &CompletionConfig,
476 position: FilePosition, 476 position: FilePosition,
477 full_import_path: &str, 477 full_import_path: &str,
478 imported_name: &str, 478 imported_name: String,
479 ) -> Cancelable<Vec<TextEdit>> { 479 ) -> Cancelable<Vec<TextEdit>> {
480 Ok(self 480 Ok(self
481 .with_db(|db| { 481 .with_db(|db| {
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs
index b2980a5d6..0f4c2ca47 100644
--- a/crates/ide_db/src/imports_locator.rs
+++ b/crates/ide_db/src/imports_locator.rs
@@ -15,19 +15,23 @@ use rustc_hash::FxHashSet;
15pub fn find_exact_imports<'a>( 15pub fn find_exact_imports<'a>(
16 sema: &Semantics<'a, RootDatabase>, 16 sema: &Semantics<'a, RootDatabase>,
17 krate: Crate, 17 krate: Crate,
18 name_to_import: &str, 18 name_to_import: String,
19) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> { 19) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
20 let _p = profile::span("find_exact_imports"); 20 let _p = profile::span("find_exact_imports");
21 find_imports( 21 find_imports(
22 sema, 22 sema,
23 krate, 23 krate,
24 { 24 {
25 let mut local_query = symbol_index::Query::new(name_to_import.to_string()); 25 let mut local_query = symbol_index::Query::new(name_to_import.clone());
26 local_query.exact(); 26 local_query.exact();
27 local_query.limit(40); 27 local_query.limit(40);
28 local_query 28 local_query
29 }, 29 },
30 import_map::Query::new(name_to_import).anchor_end().case_sensitive().limit(40), 30 import_map::Query::new(name_to_import)
31 .limit(40)
32 .name_only()
33 .search_mode(import_map::SearchMode::Equals)
34 .case_sensitive(),
31 ) 35 )
32} 36}
33 37
@@ -35,17 +39,18 @@ pub fn find_similar_imports<'a>(
35 sema: &Semantics<'a, RootDatabase>, 39 sema: &Semantics<'a, RootDatabase>,
36 krate: Crate, 40 krate: Crate,
37 limit: Option<usize>, 41 limit: Option<usize>,
38 name_to_import: &str, 42 fuzzy_search_string: String,
39 ignore_modules: bool, 43 name_only: bool,
40) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> { 44) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
41 let _p = profile::span("find_similar_imports"); 45 let _p = profile::span("find_similar_imports");
42 46
43 let mut external_query = import_map::Query::new(name_to_import); 47 let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
44 if ignore_modules { 48 .search_mode(import_map::SearchMode::Fuzzy);
45 external_query = external_query.exclude_import_kind(import_map::ImportKind::Module); 49 if name_only {
50 external_query = external_query.name_only();
46 } 51 }
47 52
48 let mut local_query = symbol_index::Query::new(name_to_import.to_string()); 53 let mut local_query = symbol_index::Query::new(fuzzy_search_string);
49 54
50 if let Some(limit) = limit { 55 if let Some(limit) = limit {
51 local_query.limit(limit); 56 local_query.limit(limit);
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 374fb5302..23f323f55 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -681,7 +681,7 @@ pub(crate) fn handle_completion_resolve(
681 &snap.config.completion, 681 &snap.config.completion,
682 FilePosition { file_id, offset }, 682 FilePosition { file_id, offset },
683 &resolve_data.full_import_path, 683 &resolve_data.full_import_path,
684 &resolve_data.imported_name, 684 resolve_data.imported_name,
685 )? 685 )?
686 .into_iter() 686 .into_iter()
687 .flat_map(|edit| { 687 .flat_map(|edit| {
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 9d4823a34..b12e134ca 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -469,8 +469,14 @@ export function resolveCodeAction(ctx: Ctx): Cmd {
469 if (!item.edit) { 469 if (!item.edit) {
470 return; 470 return;
471 } 471 }
472 const edit = client.protocol2CodeConverter.asWorkspaceEdit(item.edit); 472 const itemEdit = item.edit;
473 await vscode.workspace.applyEdit(edit); 473 const edit = client.protocol2CodeConverter.asWorkspaceEdit(itemEdit);
474 // filter out all text edits and recreate the WorkspaceEdit without them so we can apply
475 // snippet edits on our own
476 const itemEditWithoutTextEdits = { ...item, documentChanges: itemEdit.documentChanges?.filter(change => "kind" in change) };
477 const editWithoutTextEdits = client.protocol2CodeConverter.asWorkspaceEdit(itemEditWithoutTextEdits);
478 await applySnippetWorkspaceEdit(edit);
479 await vscode.workspace.applyEdit(editWithoutTextEdits);
474 }; 480 };
475} 481}
476 482