aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/assist_config.rs8
-rw-r--r--crates/assists/src/assist_context.rs41
-rw-r--r--crates/assists/src/handlers/auto_import.rs9
-rw-r--r--crates/assists/src/handlers/inline_local_variable.rs45
-rw-r--r--crates/assists/src/handlers/qualify_path.rs40
-rw-r--r--crates/assists/src/handlers/unmerge_use.rs231
-rw-r--r--crates/assists/src/lib.rs4
-rw-r--r--crates/assists/src/tests.rs49
-rw-r--r--crates/assists/src/tests/generated.rs14
-rw-r--r--crates/assists/src/utils.rs1
-rw-r--r--crates/completion/src/completions.rs1
-rw-r--r--crates/completion/src/completions/flyimport.rs291
-rw-r--r--crates/completion/src/completions/keyword.rs106
-rw-r--r--crates/completion/src/completions/unqualified_path.rs251
-rw-r--r--crates/completion/src/config.rs4
-rw-r--r--crates/completion/src/context.rs26
-rw-r--r--crates/completion/src/lib.rs5
-rw-r--r--crates/completion/src/render.rs15
-rw-r--r--crates/completion/src/test_utils.rs14
-rw-r--r--crates/hir/src/semantics.rs1
-rw-r--r--crates/hir/src/semantics/source_to_def.rs9
-rw-r--r--crates/hir_def/Cargo.toml2
-rw-r--r--crates/hir_def/src/body/lower.rs4
-rw-r--r--crates/hir_def/src/expr.rs5
-rw-r--r--crates/hir_def/src/resolver.rs2
-rw-r--r--crates/hir_expand/Cargo.toml2
-rw-r--r--crates/hir_expand/src/ast_id_map.rs30
-rw-r--r--crates/hir_expand/src/db.rs2
-rw-r--r--crates/hir_expand/src/lib.rs8
-rw-r--r--crates/hir_ty/Cargo.toml2
-rw-r--r--crates/hir_ty/src/infer/expr.rs7
-rw-r--r--crates/ide/src/diagnostics.rs21
-rw-r--r--crates/ide/src/diagnostics/field_shorthand.rs7
-rw-r--r--crates/ide/src/diagnostics/fixes.rs26
-rw-r--r--crates/ide/src/display/navigation_target.rs23
-rw-r--r--crates/ide/src/extend_selection.rs6
-rw-r--r--crates/ide/src/goto_definition.rs35
-rw-r--r--crates/ide/src/hover.rs47
-rw-r--r--crates/ide/src/join_lines.rs4
-rw-r--r--crates/ide/src/lib.rs6
-rw-r--r--crates/ide/src/references.rs119
-rw-r--r--crates/ide/src/references/rename.rs241
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs39
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html6
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html2
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html6
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html18
-rw-r--r--crates/ide/src/syntax_tree.rs4
-rw-r--r--crates/ide/src/typing.rs8
-rw-r--r--crates/ide_db/src/defs.rs45
-rw-r--r--crates/ide_db/src/helpers.rs1
-rw-r--r--crates/ide_db/src/helpers/import_assets.rs (renamed from crates/assists/src/utils/import_assets.rs)50
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs18
-rw-r--r--crates/ide_db/src/imports_locator.rs6
-rw-r--r--crates/ide_db/src/search.rs2
-rw-r--r--crates/ide_db/src/source_change.rs56
-rw-r--r--crates/parser/src/grammar/expressions/atom.rs16
-rw-r--r--crates/parser/src/grammar/paths.rs6
-rw-r--r--crates/parser/src/syntax_kind/generated.rs8
-rw-r--r--crates/profile/Cargo.toml2
-rw-r--r--crates/project_model/Cargo.toml2
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs5
-rw-r--r--crates/rust-analyzer/src/cli/ssr.rs8
-rw-r--r--crates/rust-analyzer/src/config.rs33
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt47
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt47
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt47
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt120
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs53
-rw-r--r--crates/rust-analyzer/src/handlers.rs14
-rw-r--r--crates/rust-analyzer/src/to_proto.rs24
-rw-r--r--crates/ssr/src/lib.rs24
-rw-r--r--crates/ssr/src/matching.rs4
-rw-r--r--crates/ssr/src/tests.rs3
-rw-r--r--crates/stdx/src/macros.rs2
-rw-r--r--crates/syntax/Cargo.toml2
-rw-r--r--crates/syntax/src/algo.rs8
-rw-r--r--crates/syntax/src/ast/generated/nodes.rs40
-rw-r--r--crates/syntax/src/ast/make.rs8
-rw-r--r--crates/syntax/src/ast/node_ext.rs22
-rw-r--r--crates/syntax/src/parsing/reparsing.rs5
-rw-r--r--crates/syntax/src/validation.rs2
-rw-r--r--crates/syntax/test_data/parser/err/0018_incomplete_fn.rast6
-rw-r--r--crates/syntax/test_data/parser/err/0035_use_recover.rast3
-rw-r--r--crates/syntax/test_data/parser/err/0040_illegal_crate_kw_location.rast15
-rw-r--r--crates/syntax/test_data/parser/err/0041_illegal_super_keyword_location.rast15
-rw-r--r--crates/syntax/test_data/parser/err/0042_illegal_self_keyword_location.rast6
-rw-r--r--crates/syntax/test_data/parser/inline/err/0015_empty_segment.rast3
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0002_use_tree_list.rast3
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0052_path_type.rast6
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0062_mod_contents.rast3
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0067_crate_path.rast3
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0069_use_tree_list_after_path.rast6
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0117_macro_call_type.rast3
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0125_crate_keyword_path.rast3
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0153_pub_parens_typepath.rast6
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0159_yield_expr.rast28
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0159_yield_expr.rs4
-rw-r--r--crates/syntax/test_data/parser/ok/0013_use_path_self_super.rast9
-rw-r--r--crates/syntax/test_data/parser/ok/0020_type_param_bounds.rast3
-rw-r--r--crates/syntax/test_data/parser/ok/0034_crate_path_in_call.rast3
101 files changed, 1465 insertions, 1240 deletions
diff --git a/crates/assists/src/assist_config.rs b/crates/assists/src/assist_config.rs
index 4fe8ea761..9cabf037c 100644
--- a/crates/assists/src/assist_config.rs
+++ b/crates/assists/src/assist_config.rs
@@ -4,7 +4,7 @@
4//! module, and we use to statically check that we only produce snippet 4//! module, and we use to statically check that we only produce snippet
5//! assists if we are allowed to. 5//! assists if we are allowed to.
6 6
7use ide_db::helpers::{insert_use::MergeBehavior, SnippetCap}; 7use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
8 8
9use crate::AssistKind; 9use crate::AssistKind;
10 10
@@ -14,9 +14,3 @@ pub struct AssistConfig {
14 pub allowed: Option<Vec<AssistKind>>, 14 pub allowed: Option<Vec<AssistKind>>,
15 pub insert_use: InsertUseConfig, 15 pub insert_use: InsertUseConfig,
16} 16}
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct InsertUseConfig {
20 pub merge: Option<MergeBehavior>,
21 pub prefix_kind: hir::PrefixKind,
22}
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs
index 91cc63427..8d93edba2 100644
--- a/crates/assists/src/assist_context.rs
+++ b/crates/assists/src/assist_context.rs
@@ -2,7 +2,6 @@
2 2
3use std::mem; 3use std::mem;
4 4
5use algo::find_covering_element;
6use hir::Semantics; 5use hir::Semantics;
7use ide_db::{ 6use ide_db::{
8 base_db::{AnchoredPathBuf, FileId, FileRange}, 7 base_db::{AnchoredPathBuf, FileId, FileRange},
@@ -10,7 +9,7 @@ use ide_db::{
10}; 9};
11use ide_db::{ 10use ide_db::{
12 label::Label, 11 label::Label,
13 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 12 source_change::{FileSystemEdit, SourceChange},
14 RootDatabase, 13 RootDatabase,
15}; 14};
16use syntax::{ 15use syntax::{
@@ -94,11 +93,11 @@ impl<'a> AssistContext<'a> {
94 self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset()) 93 self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset())
95 } 94 }
96 pub(crate) fn covering_element(&self) -> SyntaxElement { 95 pub(crate) fn covering_element(&self) -> SyntaxElement {
97 find_covering_element(self.source_file.syntax(), self.frange.range) 96 self.source_file.syntax().covering_element(self.frange.range)
98 } 97 }
99 // FIXME: remove 98 // FIXME: remove
100 pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement { 99 pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement {
101 find_covering_element(self.source_file.syntax(), range) 100 self.source_file.syntax().covering_element(range)
102 } 101 }
103} 102}
104 103
@@ -180,20 +179,12 @@ impl Assists {
180pub(crate) struct AssistBuilder { 179pub(crate) struct AssistBuilder {
181 edit: TextEditBuilder, 180 edit: TextEditBuilder,
182 file_id: FileId, 181 file_id: FileId,
183 is_snippet: bool, 182 source_change: SourceChange,
184 source_file_edits: Vec<SourceFileEdit>,
185 file_system_edits: Vec<FileSystemEdit>,
186} 183}
187 184
188impl AssistBuilder { 185impl AssistBuilder {
189 pub(crate) fn new(file_id: FileId) -> AssistBuilder { 186 pub(crate) fn new(file_id: FileId) -> AssistBuilder {
190 AssistBuilder { 187 AssistBuilder { edit: TextEdit::builder(), file_id, source_change: SourceChange::default() }
191 edit: TextEdit::builder(),
192 file_id,
193 is_snippet: false,
194 source_file_edits: Vec::default(),
195 file_system_edits: Vec::default(),
196 }
197 } 188 }
198 189
199 pub(crate) fn edit_file(&mut self, file_id: FileId) { 190 pub(crate) fn edit_file(&mut self, file_id: FileId) {
@@ -204,15 +195,7 @@ impl AssistBuilder {
204 fn commit(&mut self) { 195 fn commit(&mut self) {
205 let edit = mem::take(&mut self.edit).finish(); 196 let edit = mem::take(&mut self.edit).finish();
206 if !edit.is_empty() { 197 if !edit.is_empty() {
207 match self.source_file_edits.binary_search_by_key(&self.file_id, |edit| edit.file_id) { 198 self.source_change.insert_source_edit(self.file_id, edit);
208 Ok(idx) => self.source_file_edits[idx]
209 .edit
210 .union(edit)
211 .expect("overlapping edits for same file"),
212 Err(idx) => self
213 .source_file_edits
214 .insert(idx, SourceFileEdit { file_id: self.file_id, edit }),
215 }
216 } 199 }
217 } 200 }
218 201
@@ -231,7 +214,7 @@ impl AssistBuilder {
231 offset: TextSize, 214 offset: TextSize,
232 snippet: impl Into<String>, 215 snippet: impl Into<String>,
233 ) { 216 ) {
234 self.is_snippet = true; 217 self.source_change.is_snippet = true;
235 self.insert(offset, snippet); 218 self.insert(offset, snippet);
236 } 219 }
237 /// Replaces specified `range` of text with a given string. 220 /// Replaces specified `range` of text with a given string.
@@ -245,7 +228,7 @@ impl AssistBuilder {
245 range: TextRange, 228 range: TextRange,
246 snippet: impl Into<String>, 229 snippet: impl Into<String>,
247 ) { 230 ) {
248 self.is_snippet = true; 231 self.source_change.is_snippet = true;
249 self.replace(range, snippet); 232 self.replace(range, snippet);
250 } 233 }
251 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { 234 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
@@ -260,15 +243,11 @@ impl AssistBuilder {
260 pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) { 243 pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
261 let file_system_edit = 244 let file_system_edit =
262 FileSystemEdit::CreateFile { dst: dst.clone(), initial_contents: content.into() }; 245 FileSystemEdit::CreateFile { dst: dst.clone(), initial_contents: content.into() };
263 self.file_system_edits.push(file_system_edit); 246 self.source_change.push_file_system_edit(file_system_edit);
264 } 247 }
265 248
266 fn finish(mut self) -> SourceChange { 249 fn finish(mut self) -> SourceChange {
267 self.commit(); 250 self.commit();
268 SourceChange { 251 mem::take(&mut self.source_change)
269 source_file_edits: mem::take(&mut self.source_file_edits),
270 file_system_edits: mem::take(&mut self.file_system_edits),
271 is_snippet: self.is_snippet,
272 }
273 } 252 }
274} 253}
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index 55620f0f3..4e2a4fcd9 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -1,13 +1,11 @@
1use ide_db::helpers::{ 1use ide_db::helpers::{
2 import_assets::{ImportAssets, ImportCandidate},
2 insert_use::{insert_use, ImportScope}, 3 insert_use::{insert_use, ImportScope},
3 mod_path_to_ast, 4 mod_path_to_ast,
4}; 5};
5use syntax::ast; 6use syntax::ast;
6 7
7use crate::{ 8use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
8 utils::import_assets::{ImportAssets, ImportCandidate},
9 AssistContext, AssistId, AssistKind, Assists, GroupLabel,
10};
11 9
12// Feature: Auto Import 10// Feature: Auto Import
13// 11//
@@ -121,8 +119,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
121 119
122fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel { 120fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
123 let name = match import_candidate { 121 let name = match import_candidate {
124 ImportCandidate::UnqualifiedName(candidate) 122 ImportCandidate::Path(candidate) => format!("Import {}", &candidate.name),
125 | ImportCandidate::QualifierStart(candidate) => format!("Import {}", &candidate.name),
126 ImportCandidate::TraitAssocItem(candidate) => { 123 ImportCandidate::TraitAssocItem(candidate) => {
127 format!("Import a trait for item {}", &candidate.name) 124 format!("Import a trait for item {}", &candidate.name)
128 } 125 }
diff --git a/crates/assists/src/handlers/inline_local_variable.rs b/crates/assists/src/handlers/inline_local_variable.rs
index dc798daaa..0e63a60e8 100644
--- a/crates/assists/src/handlers/inline_local_variable.rs
+++ b/crates/assists/src/handlers/inline_local_variable.rs
@@ -79,29 +79,30 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
79 None => return Ok(false), 79 None => return Ok(false),
80 }; 80 };
81 81
82 Ok(!matches!((&initializer_expr, usage_parent), 82 Ok(!matches!(
83 (&initializer_expr, usage_parent),
83 (ast::Expr::CallExpr(_), _) 84 (ast::Expr::CallExpr(_), _)
84 | (ast::Expr::IndexExpr(_), _) 85 | (ast::Expr::IndexExpr(_), _)
85 | (ast::Expr::MethodCallExpr(_), _) 86 | (ast::Expr::MethodCallExpr(_), _)
86 | (ast::Expr::FieldExpr(_), _) 87 | (ast::Expr::FieldExpr(_), _)
87 | (ast::Expr::TryExpr(_), _) 88 | (ast::Expr::TryExpr(_), _)
88 | (ast::Expr::RefExpr(_), _) 89 | (ast::Expr::RefExpr(_), _)
89 | (ast::Expr::Literal(_), _) 90 | (ast::Expr::Literal(_), _)
90 | (ast::Expr::TupleExpr(_), _) 91 | (ast::Expr::TupleExpr(_), _)
91 | (ast::Expr::ArrayExpr(_), _) 92 | (ast::Expr::ArrayExpr(_), _)
92 | (ast::Expr::ParenExpr(_), _) 93 | (ast::Expr::ParenExpr(_), _)
93 | (ast::Expr::PathExpr(_), _) 94 | (ast::Expr::PathExpr(_), _)
94 | (ast::Expr::BlockExpr(_), _) 95 | (ast::Expr::BlockExpr(_), _)
95 | (ast::Expr::EffectExpr(_), _) 96 | (ast::Expr::EffectExpr(_), _)
96 | (_, ast::Expr::CallExpr(_)) 97 | (_, ast::Expr::CallExpr(_))
97 | (_, ast::Expr::TupleExpr(_)) 98 | (_, ast::Expr::TupleExpr(_))
98 | (_, ast::Expr::ArrayExpr(_)) 99 | (_, ast::Expr::ArrayExpr(_))
99 | (_, ast::Expr::ParenExpr(_)) 100 | (_, ast::Expr::ParenExpr(_))
100 | (_, ast::Expr::ForExpr(_)) 101 | (_, ast::Expr::ForExpr(_))
101 | (_, ast::Expr::WhileExpr(_)) 102 | (_, ast::Expr::WhileExpr(_))
102 | (_, ast::Expr::BreakExpr(_)) 103 | (_, ast::Expr::BreakExpr(_))
103 | (_, ast::Expr::ReturnExpr(_)) 104 | (_, ast::Expr::ReturnExpr(_))
104 | (_, ast::Expr::MatchExpr(_)) 105 | (_, ast::Expr::MatchExpr(_))
105 )) 106 ))
106 }) 107 })
107 .collect::<Result<Vec<_>, _>>()?; 108 .collect::<Result<Vec<_>, _>>()?;
diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs
index f7fbf37f4..a7d9fd4dc 100644
--- a/crates/assists/src/handlers/qualify_path.rs
+++ b/crates/assists/src/handlers/qualify_path.rs
@@ -1,7 +1,10 @@
1use std::iter; 1use std::iter;
2 2
3use hir::AsName; 3use hir::AsName;
4use ide_db::helpers::mod_path_to_ast; 4use ide_db::helpers::{
5 import_assets::{ImportAssets, ImportCandidate},
6 mod_path_to_ast,
7};
5use ide_db::RootDatabase; 8use ide_db::RootDatabase;
6use syntax::{ 9use syntax::{
7 ast, 10 ast,
@@ -12,7 +15,6 @@ use test_utils::mark;
12 15
13use crate::{ 16use crate::{
14 assist_context::{AssistContext, Assists}, 17 assist_context::{AssistContext, Assists},
15 utils::import_assets::{ImportAssets, ImportCandidate},
16 AssistId, AssistKind, GroupLabel, 18 AssistId, AssistKind, GroupLabel,
17}; 19};
18 20
@@ -53,17 +55,18 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
53 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; 55 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
54 56
55 let qualify_candidate = match candidate { 57 let qualify_candidate = match candidate {
56 ImportCandidate::QualifierStart(_) => { 58 ImportCandidate::Path(candidate) => {
57 mark::hit!(qualify_path_qualifier_start); 59 if candidate.qualifier.is_some() {
58 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; 60 mark::hit!(qualify_path_qualifier_start);
59 let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?); 61 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
60 QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list()) 62 let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
61 } 63 QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
62 ImportCandidate::UnqualifiedName(_) => { 64 } else {
63 mark::hit!(qualify_path_unqualified_name); 65 mark::hit!(qualify_path_unqualified_name);
64 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; 66 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
65 let generics = path.segment()?.generic_arg_list(); 67 let generics = path.segment()?.generic_arg_list();
66 QualifyCandidate::UnqualifiedName(generics) 68 QualifyCandidate::UnqualifiedName(generics)
69 }
67 } 70 }
68 ImportCandidate::TraitAssocItem(_) => { 71 ImportCandidate::TraitAssocItem(_) => {
69 mark::hit!(qualify_path_trait_assoc_item); 72 mark::hit!(qualify_path_trait_assoc_item);
@@ -186,7 +189,7 @@ fn item_as_trait(item: hir::ItemInNs) -> Option<hir::Trait> {
186 189
187fn group_label(candidate: &ImportCandidate) -> GroupLabel { 190fn group_label(candidate: &ImportCandidate) -> GroupLabel {
188 let name = match candidate { 191 let name = match candidate {
189 ImportCandidate::UnqualifiedName(it) | ImportCandidate::QualifierStart(it) => &it.name, 192 ImportCandidate::Path(it) => &it.name,
190 ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name, 193 ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name,
191 }; 194 };
192 GroupLabel(format!("Qualify {}", name)) 195 GroupLabel(format!("Qualify {}", name))
@@ -194,8 +197,13 @@ fn group_label(candidate: &ImportCandidate) -> GroupLabel {
194 197
195fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String { 198fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String {
196 match candidate { 199 match candidate {
197 ImportCandidate::UnqualifiedName(_) => format!("Qualify as `{}`", &import), 200 ImportCandidate::Path(candidate) => {
198 ImportCandidate::QualifierStart(_) => format!("Qualify with `{}`", &import), 201 if candidate.qualifier.is_some() {
202 format!("Qualify with `{}`", &import)
203 } else {
204 format!("Qualify as `{}`", &import)
205 }
206 }
199 ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import), 207 ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import),
200 ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import), 208 ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import),
201 } 209 }
diff --git a/crates/assists/src/handlers/unmerge_use.rs b/crates/assists/src/handlers/unmerge_use.rs
new file mode 100644
index 000000000..3dbef8e51
--- /dev/null
+++ b/crates/assists/src/handlers/unmerge_use.rs
@@ -0,0 +1,231 @@
1use syntax::{
2 algo::SyntaxRewriter,
3 ast::{self, edit::AstNodeEdit, VisibilityOwner},
4 AstNode, SyntaxKind,
5};
6use test_utils::mark;
7
8use crate::{
9 assist_context::{AssistContext, Assists},
10 AssistId, AssistKind,
11};
12
13// Assist: unmerge_use
14//
15// Extracts single use item from use list.
16//
17// ```
18// use std::fmt::{Debug, Display$0};
19// ```
20// ->
21// ```
22// use std::fmt::{Debug};
23// use std::fmt::Display;
24// ```
25pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let tree: ast::UseTree = ctx.find_node_at_offset()?;
27
28 let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?;
29 if tree_list.use_trees().count() < 2 {
30 mark::hit!(skip_single_use_item);
31 return None;
32 }
33
34 let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
35 let path = resolve_full_path(&tree)?;
36
37 let target = tree.syntax().text_range();
38 acc.add(
39 AssistId("unmerge_use", AssistKind::RefactorRewrite),
40 "Unmerge use",
41 target,
42 |builder| {
43 let new_use = ast::make::use_(
44 use_.visibility(),
45 ast::make::use_tree(
46 path,
47 tree.use_tree_list(),
48 tree.rename(),
49 tree.star_token().is_some(),
50 ),
51 );
52
53 let mut rewriter = SyntaxRewriter::default();
54 rewriter += tree.remove();
55 rewriter.insert_after(use_.syntax(), &ast::make::tokens::single_newline());
56 if let ident_level @ 1..=usize::MAX = use_.indent_level().0 as usize {
57 rewriter.insert_after(
58 use_.syntax(),
59 &ast::make::tokens::whitespace(&" ".repeat(4 * ident_level)),
60 );
61 }
62 rewriter.insert_after(use_.syntax(), new_use.syntax());
63
64 builder.rewrite(rewriter);
65 },
66 )
67}
68
69fn resolve_full_path(tree: &ast::UseTree) -> Option<ast::Path> {
70 let mut paths = tree
71 .syntax()
72 .ancestors()
73 .take_while(|n| n.kind() != SyntaxKind::USE_KW)
74 .filter_map(ast::UseTree::cast)
75 .filter_map(|t| t.path());
76
77 let mut final_path = paths.next()?;
78 for path in paths {
79 final_path = ast::make::path_concat(path, final_path)
80 }
81 Some(final_path)
82}
83
84#[cfg(test)]
85mod tests {
86 use crate::tests::{check_assist, check_assist_not_applicable};
87
88 use super::*;
89
90 #[test]
91 fn skip_single_use_item() {
92 mark::check!(skip_single_use_item);
93 check_assist_not_applicable(
94 unmerge_use,
95 r"
96use std::fmt::Debug$0;
97",
98 );
99 check_assist_not_applicable(
100 unmerge_use,
101 r"
102use std::fmt::{Debug$0};
103",
104 );
105 check_assist_not_applicable(
106 unmerge_use,
107 r"
108use std::fmt::Debug as Dbg$0;
109",
110 );
111 }
112
113 #[test]
114 fn skip_single_glob_import() {
115 check_assist_not_applicable(
116 unmerge_use,
117 r"
118use std::fmt::*$0;
119",
120 );
121 }
122
123 #[test]
124 fn unmerge_use_item() {
125 check_assist(
126 unmerge_use,
127 r"
128use std::fmt::{Debug, Display$0};
129",
130 r"
131use std::fmt::{Debug};
132use std::fmt::Display;
133",
134 );
135
136 check_assist(
137 unmerge_use,
138 r"
139use std::fmt::{Debug, format$0, Display};
140",
141 r"
142use std::fmt::{Debug, Display};
143use std::fmt::format;
144",
145 );
146 }
147
148 #[test]
149 fn unmerge_glob_import() {
150 check_assist(
151 unmerge_use,
152 r"
153use std::fmt::{*$0, Display};
154",
155 r"
156use std::fmt::{Display};
157use std::fmt::*;
158",
159 );
160 }
161
162 #[test]
163 fn unmerge_renamed_use_item() {
164 check_assist(
165 unmerge_use,
166 r"
167use std::fmt::{Debug, Display as Disp$0};
168",
169 r"
170use std::fmt::{Debug};
171use std::fmt::Display as Disp;
172",
173 );
174 }
175
176 #[test]
177 fn unmerge_indented_use_item() {
178 check_assist(
179 unmerge_use,
180 r"
181mod format {
182 use std::fmt::{Debug, Display$0 as Disp, format};
183}
184",
185 r"
186mod format {
187 use std::fmt::{Debug, format};
188 use std::fmt::Display as Disp;
189}
190",
191 );
192 }
193
194 #[test]
195 fn unmerge_nested_use_item() {
196 check_assist(
197 unmerge_use,
198 r"
199use foo::bar::{baz::{qux$0, foobar}, barbaz};
200",
201 r"
202use foo::bar::{baz::{foobar}, barbaz};
203use foo::bar::baz::qux;
204",
205 );
206 check_assist(
207 unmerge_use,
208 r"
209use foo::bar::{baz$0::{qux, foobar}, barbaz};
210",
211 r"
212use foo::bar::{barbaz};
213use foo::bar::baz::{qux, foobar};
214",
215 );
216 }
217
218 #[test]
219 fn unmerge_use_item_with_visibility() {
220 check_assist(
221 unmerge_use,
222 r"
223pub use std::fmt::{Debug, Display$0};
224",
225 r"
226pub use std::fmt::{Debug};
227pub use std::fmt::Display;
228",
229 );
230 }
231}
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index 1080294ab..14178a651 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -24,7 +24,7 @@ use syntax::TextRange;
24 24
25pub(crate) use crate::assist_context::{AssistContext, Assists}; 25pub(crate) use crate::assist_context::{AssistContext, Assists};
26 26
27pub use assist_config::{AssistConfig, InsertUseConfig}; 27pub use assist_config::AssistConfig;
28 28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)] 29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum AssistKind { 30pub enum AssistKind {
@@ -156,6 +156,7 @@ mod handlers {
156 mod replace_unwrap_with_match; 156 mod replace_unwrap_with_match;
157 mod split_import; 157 mod split_import;
158 mod toggle_ignore; 158 mod toggle_ignore;
159 mod unmerge_use;
159 mod unwrap_block; 160 mod unwrap_block;
160 mod wrap_return_type_in_result; 161 mod wrap_return_type_in_result;
161 162
@@ -213,6 +214,7 @@ mod handlers {
213 replace_unwrap_with_match::replace_unwrap_with_match, 214 replace_unwrap_with_match::replace_unwrap_with_match,
214 split_import::split_import, 215 split_import::split_import,
215 toggle_ignore::toggle_ignore, 216 toggle_ignore::toggle_ignore,
217 unmerge_use::unmerge_use,
216 unwrap_block::unwrap_block, 218 unwrap_block::unwrap_block,
217 wrap_return_type_in_result::wrap_return_type_in_result, 219 wrap_return_type_in_result::wrap_return_type_in_result,
218 // These are manually sorted for better priorities 220 // These are manually sorted for better priorities
diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs
index fef29a0b8..32bd8698b 100644
--- a/crates/assists/src/tests.rs
+++ b/crates/assists/src/tests.rs
@@ -3,16 +3,17 @@ mod generated;
3use hir::Semantics; 3use hir::Semantics;
4use ide_db::{ 4use ide_db::{
5 base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}, 5 base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt},
6 helpers::{insert_use::MergeBehavior, SnippetCap}, 6 helpers::{
7 insert_use::{InsertUseConfig, MergeBehavior},
8 SnippetCap,
9 },
7 source_change::FileSystemEdit, 10 source_change::FileSystemEdit,
8 RootDatabase, 11 RootDatabase,
9}; 12};
10use syntax::TextRange; 13use syntax::TextRange;
11use test_utils::{assert_eq_text, extract_offset, extract_range}; 14use test_utils::{assert_eq_text, extract_offset, extract_range};
12 15
13use crate::{ 16use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
14 handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists, InsertUseConfig,
15};
16use stdx::{format_to, trim_indent}; 17use stdx::{format_to, trim_indent};
17 18
18pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { 19pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
@@ -80,10 +81,8 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
80 let actual = { 81 let actual = {
81 let source_change = assist.source_change.unwrap(); 82 let source_change = assist.source_change.unwrap();
82 let mut actual = before; 83 let mut actual = before;
83 for source_file_edit in source_change.source_file_edits { 84 if let Some(source_file_edit) = source_change.get_source_edit(file_id) {
84 if source_file_edit.file_id == file_id { 85 source_file_edit.apply(&mut actual);
85 source_file_edit.edit.apply(&mut actual)
86 }
87 } 86 }
88 actual 87 actual
89 }; 88 };
@@ -116,37 +115,33 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
116 115
117 match (assist, expected) { 116 match (assist, expected) {
118 (Some(assist), ExpectedResult::After(after)) => { 117 (Some(assist), ExpectedResult::After(after)) => {
119 let mut source_change = assist.source_change.unwrap(); 118 let source_change = assist.source_change.unwrap();
120 assert!(!source_change.source_file_edits.is_empty()); 119 assert!(!source_change.source_file_edits.is_empty());
121 let skip_header = source_change.source_file_edits.len() == 1 120 let skip_header = source_change.source_file_edits.len() == 1
122 && source_change.file_system_edits.len() == 0; 121 && source_change.file_system_edits.len() == 0;
123 source_change.source_file_edits.sort_by_key(|it| it.file_id);
124 122
125 let mut buf = String::new(); 123 let mut buf = String::new();
126 for source_file_edit in source_change.source_file_edits { 124 for (file_id, edit) in source_change.source_file_edits {
127 let mut text = db.file_text(source_file_edit.file_id).as_ref().to_owned(); 125 let mut text = db.file_text(file_id).as_ref().to_owned();
128 source_file_edit.edit.apply(&mut text); 126 edit.apply(&mut text);
129 if !skip_header { 127 if !skip_header {
130 let sr = db.file_source_root(source_file_edit.file_id); 128 let sr = db.file_source_root(file_id);
131 let sr = db.source_root(sr); 129 let sr = db.source_root(sr);
132 let path = sr.path_for_file(&source_file_edit.file_id).unwrap(); 130 let path = sr.path_for_file(&file_id).unwrap();
133 format_to!(buf, "//- {}\n", path) 131 format_to!(buf, "//- {}\n", path)
134 } 132 }
135 buf.push_str(&text); 133 buf.push_str(&text);
136 } 134 }
137 135
138 for file_system_edit in source_change.file_system_edits.clone() { 136 for file_system_edit in source_change.file_system_edits {
139 match file_system_edit { 137 if let FileSystemEdit::CreateFile { dst, initial_contents } = file_system_edit {
140 FileSystemEdit::CreateFile { dst, initial_contents } => { 138 let sr = db.file_source_root(dst.anchor);
141 let sr = db.file_source_root(dst.anchor); 139 let sr = db.source_root(sr);
142 let sr = db.source_root(sr); 140 let mut base = sr.path_for_file(&dst.anchor).unwrap().clone();
143 let mut base = sr.path_for_file(&dst.anchor).unwrap().clone(); 141 base.pop();
144 base.pop(); 142 let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]);
145 let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]); 143 format_to!(buf, "//- {}\n", created_file_path);
146 format_to!(buf, "//- {}\n", created_file_path); 144 buf.push_str(&initial_contents);
147 buf.push_str(&initial_contents);
148 }
149 _ => (),
150 } 145 }
151 } 146 }
152 147
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 217f577eb..d48d063b4 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -1138,6 +1138,20 @@ fn arithmetics {
1138} 1138}
1139 1139
1140#[test] 1140#[test]
1141fn doctest_unmerge_use() {
1142 check_doc_test(
1143 "unmerge_use",
1144 r#####"
1145use std::fmt::{Debug, Display$0};
1146"#####,
1147 r#####"
1148use std::fmt::{Debug};
1149use std::fmt::Display;
1150"#####,
1151 )
1152}
1153
1154#[test]
1141fn doctest_unwrap_block() { 1155fn doctest_unwrap_block() {
1142 check_doc_test( 1156 check_doc_test(
1143 "unwrap_block", 1157 "unwrap_block",
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index 9ea96eb73..fc9f83bab 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -1,5 +1,4 @@
1//! Assorted functions shared by several assists. 1//! Assorted functions shared by several assists.
2pub(crate) mod import_assets;
3 2
4use std::ops; 3use std::ops;
5 4
diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs
index 00c9e76f0..c3ce6e51d 100644
--- a/crates/completion/src/completions.rs
+++ b/crates/completion/src/completions.rs
@@ -13,6 +13,7 @@ pub(crate) mod postfix;
13pub(crate) mod macro_in_item_position; 13pub(crate) mod macro_in_item_position;
14pub(crate) mod trait_impl; 14pub(crate) mod trait_impl;
15pub(crate) mod mod_; 15pub(crate) mod mod_;
16pub(crate) mod flyimport;
16 17
17use hir::{ModPath, ScopeDef, Type}; 18use hir::{ModPath, ScopeDef, Type};
18 19
diff --git a/crates/completion/src/completions/flyimport.rs b/crates/completion/src/completions/flyimport.rs
new file mode 100644
index 000000000..222809638
--- /dev/null
+++ b/crates/completion/src/completions/flyimport.rs
@@ -0,0 +1,291 @@
1//! Feature: completion with imports-on-the-fly
2//!
3//! When completing names in the current scope, proposes additional imports from other modules or crates,
4//! if they can be qualified in the scope and their name contains all symbols from the completion input
5//! (case-insensitive, in any order or places).
6//!
7//! ```
8//! fn main() {
9//! pda$0
10//! }
11//! # pub mod std { pub mod marker { pub struct PhantomData { } } }
12//! ```
13//! ->
14//! ```
15//! use std::marker::PhantomData;
16//!
17//! fn main() {
18//! PhantomData
19//! }
20//! # pub mod std { pub mod marker { pub struct PhantomData { } } }
21//! ```
22//!
23//! .Fuzzy search details
24//!
25//! To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
26//! (i.e. in `HashMap` in the `std::collections::HashMap` path).
27//! For the same reasons, avoids searching for any imports for inputs with their length less that 2 symbols.
28//!
29//! .Import configuration
30//!
31//! It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
32//! Mimics the corresponding behavior of the `Auto Import` feature.
33//!
34//! .LSP and performance implications
35//!
36//! The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
37//! (case sensitive) resolve client capability in its client capabilities.
38//! This way the server is able to defer the costly computations, doing them for a selected completion item only.
39//! For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
40//! which might be slow ergo the feature is automatically disabled.
41//!
42//! .Feature toggle
43//!
44//! The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag.
45//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
46//! capability enabled.
47
48use either::Either;
49use hir::{ModPath, ScopeDef};
50use ide_db::{helpers::insert_use::ImportScope, imports_locator};
51use syntax::AstNode;
52use test_utils::mark;
53
54use crate::{
55 context::CompletionContext,
56 render::{render_resolution_with_import, RenderContext},
57 ImportEdit,
58};
59
60use super::Completions;
61
62pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
63 if !ctx.config.enable_autoimport_completions {
64 return None;
65 }
66 if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() {
67 return None;
68 }
69 let potential_import_name = ctx.token.to_string();
70 if potential_import_name.len() < 2 {
71 return None;
72 }
73 let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string());
74
75 let current_module = ctx.scope.module()?;
76 let anchor = ctx.name_ref_syntax.as_ref()?;
77 let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
78
79 let user_input_lowercased = potential_import_name.to_lowercase();
80 let mut all_mod_paths = imports_locator::find_similar_imports(
81 &ctx.sema,
82 ctx.krate?,
83 Some(40),
84 potential_import_name,
85 true,
86 true,
87 )
88 .filter_map(|import_candidate| {
89 Some(match import_candidate {
90 Either::Left(module_def) => {
91 (current_module.find_use_path(ctx.db, module_def)?, ScopeDef::ModuleDef(module_def))
92 }
93 Either::Right(macro_def) => {
94 (current_module.find_use_path(ctx.db, macro_def)?, ScopeDef::MacroDef(macro_def))
95 }
96 })
97 })
98 .filter(|(mod_path, _)| mod_path.len() > 1)
99 .collect::<Vec<_>>();
100
101 all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
102 compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
103 });
104
105 acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
106 render_resolution_with_import(
107 RenderContext::new(ctx),
108 ImportEdit { import_path, import_scope: import_scope.clone() },
109 &definition,
110 )
111 }));
112 Some(())
113}
114
115fn compute_fuzzy_completion_order_key(
116 proposed_mod_path: &ModPath,
117 user_input_lowercased: &str,
118) -> usize {
119 mark::hit!(certain_fuzzy_order_test);
120 let proposed_import_name = match proposed_mod_path.segments.last() {
121 Some(name) => name.to_string().to_lowercase(),
122 None => return usize::MAX,
123 };
124 match proposed_import_name.match_indices(user_input_lowercased).next() {
125 Some((first_matching_index, _)) => first_matching_index,
126 None => usize::MAX,
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use expect_test::{expect, Expect};
133 use test_utils::mark;
134
135 use crate::{
136 item::CompletionKind,
137 test_utils::{check_edit, completion_list},
138 };
139
140 fn check(ra_fixture: &str, expect: Expect) {
141 let actual = completion_list(ra_fixture, CompletionKind::Magic);
142 expect.assert_eq(&actual);
143 }
144
145 #[test]
146 fn function_fuzzy_completion() {
147 check_edit(
148 "stdin",
149 r#"
150//- /lib.rs crate:dep
151pub mod io {
152 pub fn stdin() {}
153};
154
155//- /main.rs crate:main deps:dep
156fn main() {
157 stdi$0
158}
159"#,
160 r#"
161use dep::io::stdin;
162
163fn main() {
164 stdin()$0
165}
166"#,
167 );
168 }
169
170 #[test]
171 fn macro_fuzzy_completion() {
172 check_edit(
173 "macro_with_curlies!",
174 r#"
175//- /lib.rs crate:dep
176/// Please call me as macro_with_curlies! {}
177#[macro_export]
178macro_rules! macro_with_curlies {
179 () => {}
180}
181
182//- /main.rs crate:main deps:dep
183fn main() {
184 curli$0
185}
186"#,
187 r#"
188use dep::macro_with_curlies;
189
190fn main() {
191 macro_with_curlies! {$0}
192}
193"#,
194 );
195 }
196
197 #[test]
198 fn struct_fuzzy_completion() {
199 check_edit(
200 "ThirdStruct",
201 r#"
202//- /lib.rs crate:dep
203pub struct FirstStruct;
204pub mod some_module {
205 pub struct SecondStruct;
206 pub struct ThirdStruct;
207}
208
209//- /main.rs crate:main deps:dep
210use dep::{FirstStruct, some_module::SecondStruct};
211
212fn main() {
213 this$0
214}
215"#,
216 r#"
217use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
218
219fn main() {
220 ThirdStruct
221}
222"#,
223 );
224 }
225
226 #[test]
227 fn fuzzy_completions_come_in_specific_order() {
228 mark::check!(certain_fuzzy_order_test);
229 check(
230 r#"
231//- /lib.rs crate:dep
232pub struct FirstStruct;
233pub mod some_module {
234 // already imported, omitted
235 pub struct SecondStruct;
236 // does not contain all letters from the query, omitted
237 pub struct UnrelatedOne;
238 // contains all letters from the query, but not in sequence, displayed last
239 pub struct ThiiiiiirdStruct;
240 // contains all letters from the query, but not in the beginning, displayed second
241 pub struct AfterThirdStruct;
242 // contains all letters from the query in the begginning, displayed first
243 pub struct ThirdStruct;
244}
245
246//- /main.rs crate:main deps:dep
247use dep::{FirstStruct, some_module::SecondStruct};
248
249fn main() {
250 hir$0
251}
252"#,
253 expect![[r#"
254 st dep::some_module::ThirdStruct
255 st dep::some_module::AfterThirdStruct
256 st dep::some_module::ThiiiiiirdStruct
257 "#]],
258 );
259 }
260
261 #[test]
262 fn does_not_propose_names_in_scope() {
263 check(
264 r#"
265//- /lib.rs crate:dep
266pub mod test_mod {
267 pub trait TestTrait {
268 const SPECIAL_CONST: u8;
269 type HumbleType;
270 fn weird_function();
271 fn random_method(&self);
272 }
273 pub struct TestStruct {}
274 impl TestTrait for TestStruct {
275 const SPECIAL_CONST: u8 = 42;
276 type HumbleType = ();
277 fn weird_function() {}
278 fn random_method(&self) {}
279 }
280}
281
282//- /main.rs crate:main deps:dep
283use dep::test_mod::TestStruct;
284fn main() {
285 TestSt$0
286}
287"#,
288 expect![[r#""#]],
289 );
290 }
291}
diff --git a/crates/completion/src/completions/keyword.rs b/crates/completion/src/completions/keyword.rs
index 425a688ff..c1af348dc 100644
--- a/crates/completion/src/completions/keyword.rs
+++ b/crates/completion/src/completions/keyword.rs
@@ -1,6 +1,6 @@
1//! Completes keywords. 1//! Completes keywords.
2 2
3use syntax::{ast, SyntaxKind}; 3use syntax::SyntaxKind;
4use test_utils::mark; 4use test_utils::mark;
5 5
6use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions}; 6use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions};
@@ -86,8 +86,8 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
86 add_keyword(ctx, acc, "match", "match $0 {}"); 86 add_keyword(ctx, acc, "match", "match $0 {}");
87 add_keyword(ctx, acc, "while", "while $0 {}"); 87 add_keyword(ctx, acc, "while", "while $0 {}");
88 add_keyword(ctx, acc, "loop", "loop {$0}"); 88 add_keyword(ctx, acc, "loop", "loop {$0}");
89 add_keyword(ctx, acc, "if", "if "); 89 add_keyword(ctx, acc, "if", "if $0 {}");
90 add_keyword(ctx, acc, "if let", "if let "); 90 add_keyword(ctx, acc, "if let", "if let $1 = $0 {}");
91 } 91 }
92 92
93 if ctx.if_is_prev || ctx.block_expr_parent { 93 if ctx.if_is_prev || ctx.block_expr_parent {
@@ -143,47 +143,49 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
143 Some(it) => it, 143 Some(it) => it,
144 None => return, 144 None => return,
145 }; 145 };
146 acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt));
147}
148
149fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem {
150 let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw)
151 .kind(CompletionItemKind::Keyword);
152 146
153 match ctx.config.snippet_cap { 147 add_keyword(
154 Some(cap) => res.insert_snippet(cap, snippet), 148 ctx,
155 _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }), 149 acc,
156 } 150 "return",
157 .build() 151 match (ctx.can_be_stmt, fn_def.ret_type().is_some()) {
152 (true, true) => "return $0;",
153 (true, false) => "return;",
154 (false, true) => "return $0",
155 (false, false) => "return",
156 },
157 )
158} 158}
159 159
160fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) { 160fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) {
161 acc.add(keyword(ctx, kw, snippet)); 161 let builder = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw)
162} 162 .kind(CompletionItemKind::Keyword);
163 163 let builder = match ctx.config.snippet_cap {
164fn complete_return( 164 Some(cap) => {
165 ctx: &CompletionContext, 165 let tmp;
166 fn_def: &ast::Fn, 166 let snippet = if snippet.ends_with('}') && ctx.incomplete_let {
167 can_be_stmt: bool, 167 mark::hit!(let_semi);
168) -> Option<CompletionItem> { 168 tmp = format!("{};", snippet);
169 let snip = match (can_be_stmt, fn_def.ret_type().is_some()) { 169 &tmp
170 (true, true) => "return $0;", 170 } else {
171 (true, false) => "return;", 171 snippet
172 (false, true) => "return $0", 172 };
173 (false, false) => "return", 173 builder.insert_snippet(cap, snippet)
174 }
175 None => builder.insert_text(if snippet.contains('$') { kw } else { snippet }),
174 }; 176 };
175 Some(keyword(ctx, "return", snip)) 177 acc.add(builder.build());
176} 178}
177 179
178#[cfg(test)] 180#[cfg(test)]
179mod tests { 181mod tests {
180 use expect_test::{expect, Expect}; 182 use expect_test::{expect, Expect};
183 use test_utils::mark;
181 184
182 use crate::{ 185 use crate::{
183 test_utils::{check_edit, completion_list}, 186 test_utils::{check_edit, completion_list},
184 CompletionKind, 187 CompletionKind,
185 }; 188 };
186 use test_utils::mark;
187 189
188 fn check(ra_fixture: &str, expect: Expect) { 190 fn check(ra_fixture: &str, expect: Expect) {
189 let actual = completion_list(ra_fixture, CompletionKind::Keyword); 191 let actual = completion_list(ra_fixture, CompletionKind::Keyword);
@@ -609,4 +611,50 @@ fn foo() {
609 "#]], 611 "#]],
610 ); 612 );
611 } 613 }
614
615 #[test]
616 fn let_semi() {
617 mark::check!(let_semi);
618 check_edit(
619 "match",
620 r#"
621fn main() { let x = $0 }
622"#,
623 r#"
624fn main() { let x = match $0 {}; }
625"#,
626 );
627
628 check_edit(
629 "if",
630 r#"
631fn main() {
632 let x = $0
633 let y = 92;
634}
635"#,
636 r#"
637fn main() {
638 let x = if $0 {};
639 let y = 92;
640}
641"#,
642 );
643
644 check_edit(
645 "loop",
646 r#"
647fn main() {
648 let x = $0
649 bar();
650}
651"#,
652 r#"
653fn main() {
654 let x = loop {$0};
655 bar();
656}
657"#,
658 );
659 }
612} 660}
diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs
index 53e1391f3..ac5596ca4 100644
--- a/crates/completion/src/completions/unqualified_path.rs
+++ b/crates/completion/src/completions/unqualified_path.rs
@@ -2,17 +2,11 @@
2 2
3use std::iter; 3use std::iter;
4 4
5use either::Either; 5use hir::{Adt, ModuleDef, ScopeDef, Type};
6use hir::{Adt, ModPath, ModuleDef, ScopeDef, Type};
7use ide_db::helpers::insert_use::ImportScope;
8use ide_db::imports_locator;
9use syntax::AstNode; 6use syntax::AstNode;
10use test_utils::mark; 7use test_utils::mark;
11 8
12use crate::{ 9use crate::{CompletionContext, Completions};
13 render::{render_resolution_with_import, RenderContext},
14 CompletionContext, Completions, ImportEdit,
15};
16 10
17pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { 11pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
18 if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { 12 if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
@@ -45,10 +39,6 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
45 } 39 }
46 acc.add_resolution(ctx, name.to_string(), &res) 40 acc.add_resolution(ctx, name.to_string(), &res)
47 }); 41 });
48
49 if ctx.config.enable_autoimport_completions {
50 fuzzy_completion(acc, ctx);
51 }
52} 42}
53 43
54fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { 44fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) {
@@ -77,124 +67,13 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T
77 } 67 }
78} 68}
79 69
80// Feature: Fuzzy Completion and Autoimports
81//
82// When completing names in the current scope, proposes additional imports from other modules or crates,
83// if they can be qualified in the scope and their name contains all symbols from the completion input
84// (case-insensitive, in any order or places).
85//
86// ```
87// fn main() {
88// pda$0
89// }
90// # pub mod std { pub mod marker { pub struct PhantomData { } } }
91// ```
92// ->
93// ```
94// use std::marker::PhantomData;
95//
96// fn main() {
97// PhantomData
98// }
99// # pub mod std { pub mod marker { pub struct PhantomData { } } }
100// ```
101//
102// .Fuzzy search details
103//
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).
106// For the same reasons, avoids searching for any imports for inputs with their length less that 2 symbols.
107//
108// .Merge Behavior
109//
110// It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
111// Mimics the corresponding behavior of the `Auto Import` feature.
112//
113// .LSP and performance implications
114//
115// The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
116// (case sensitive) resolve client capability in its client capabilities.
117// This way the server is able to defer the costly computations, doing them for a selected completion item only.
118// For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
119// which might be slow ergo the feature is automatically disabled.
120//
121// .Feature toggle
122//
123// The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag.
124// Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
125// capability enabled.
126fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
127 let potential_import_name = ctx.token.to_string();
128 let _p = profile::span("fuzzy_completion").detail(|| potential_import_name.clone());
129
130 if potential_import_name.len() < 2 {
131 return None;
132 }
133
134 let current_module = ctx.scope.module()?;
135 let anchor = ctx.name_ref_syntax.as_ref()?;
136 let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
137
138 let user_input_lowercased = potential_import_name.to_lowercase();
139 let mut all_mod_paths = imports_locator::find_similar_imports(
140 &ctx.sema,
141 ctx.krate?,
142 Some(40),
143 potential_import_name,
144 true,
145 true,
146 )
147 .filter_map(|import_candidate| {
148 Some(match import_candidate {
149 Either::Left(module_def) => {
150 (current_module.find_use_path(ctx.db, module_def)?, ScopeDef::ModuleDef(module_def))
151 }
152 Either::Right(macro_def) => {
153 (current_module.find_use_path(ctx.db, macro_def)?, ScopeDef::MacroDef(macro_def))
154 }
155 })
156 })
157 .filter(|(mod_path, _)| mod_path.len() > 1)
158 .collect::<Vec<_>>();
159
160 all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
161 compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
162 });
163
164 acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
165 render_resolution_with_import(
166 RenderContext::new(ctx),
167 ImportEdit { import_path, import_scope: import_scope.clone() },
168 &definition,
169 )
170 }));
171 Some(())
172}
173
174fn compute_fuzzy_completion_order_key(
175 proposed_mod_path: &ModPath,
176 user_input_lowercased: &str,
177) -> usize {
178 mark::hit!(certain_fuzzy_order_test);
179 let proposed_import_name = match proposed_mod_path.segments.last() {
180 Some(name) => name.to_string().to_lowercase(),
181 None => return usize::MAX,
182 };
183 match proposed_import_name.match_indices(user_input_lowercased).next() {
184 Some((first_matching_index, _)) => first_matching_index,
185 None => usize::MAX,
186 }
187}
188
189#[cfg(test)] 70#[cfg(test)]
190mod tests { 71mod tests {
191 use expect_test::{expect, Expect}; 72 use expect_test::{expect, Expect};
192 use test_utils::mark; 73 use test_utils::mark;
193 74
194 use crate::{ 75 use crate::{
195 test_utils::{ 76 test_utils::{check_edit, completion_list_with_config, TEST_CONFIG},
196 check_edit, check_edit_with_config, completion_list_with_config, TEST_CONFIG,
197 },
198 CompletionConfig, CompletionKind, 77 CompletionConfig, CompletionKind,
199 }; 78 };
200 79
@@ -855,128 +734,4 @@ impl My$0
855 "#]], 734 "#]],
856 ) 735 )
857 } 736 }
858
859 #[test]
860 fn function_fuzzy_completion() {
861 check_edit_with_config(
862 TEST_CONFIG,
863 "stdin",
864 r#"
865//- /lib.rs crate:dep
866pub mod io {
867 pub fn stdin() {}
868};
869
870//- /main.rs crate:main deps:dep
871fn main() {
872 stdi$0
873}
874"#,
875 r#"
876use dep::io::stdin;
877
878fn main() {
879 stdin()$0
880}
881"#,
882 );
883 }
884
885 #[test]
886 fn macro_fuzzy_completion() {
887 check_edit_with_config(
888 TEST_CONFIG,
889 "macro_with_curlies!",
890 r#"
891//- /lib.rs crate:dep
892/// Please call me as macro_with_curlies! {}
893#[macro_export]
894macro_rules! macro_with_curlies {
895 () => {}
896}
897
898//- /main.rs crate:main deps:dep
899fn main() {
900 curli$0
901}
902"#,
903 r#"
904use dep::macro_with_curlies;
905
906fn main() {
907 macro_with_curlies! {$0}
908}
909"#,
910 );
911 }
912
913 #[test]
914 fn struct_fuzzy_completion() {
915 check_edit_with_config(
916 TEST_CONFIG,
917 "ThirdStruct",
918 r#"
919//- /lib.rs crate:dep
920pub struct FirstStruct;
921pub mod some_module {
922 pub struct SecondStruct;
923 pub struct ThirdStruct;
924}
925
926//- /main.rs crate:main deps:dep
927use dep::{FirstStruct, some_module::SecondStruct};
928
929fn main() {
930 this$0
931}
932"#,
933 r#"
934use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
935
936fn main() {
937 ThirdStruct
938}
939"#,
940 );
941 }
942
943 #[test]
944 fn fuzzy_completions_come_in_specific_order() {
945 mark::check!(certain_fuzzy_order_test);
946 check_with_config(
947 TEST_CONFIG,
948 r#"
949//- /lib.rs crate:dep
950pub struct FirstStruct;
951pub mod some_module {
952 // already imported, omitted
953 pub struct SecondStruct;
954 // does not contain all letters from the query, omitted
955 pub struct UnrelatedOne;
956 // contains all letters from the query, but not in sequence, displayed last
957 pub struct ThiiiiiirdStruct;
958 // contains all letters from the query, but not in the beginning, displayed second
959 pub struct AfterThirdStruct;
960 // contains all letters from the query in the begginning, displayed first
961 pub struct ThirdStruct;
962}
963
964//- /main.rs crate:main deps:dep
965use dep::{FirstStruct, some_module::SecondStruct};
966
967fn main() {
968 hir$0
969}
970"#,
971 expect![[r#"
972 fn main() fn main()
973 st SecondStruct
974 st FirstStruct
975 md dep
976 st dep::some_module::ThirdStruct
977 st dep::some_module::AfterThirdStruct
978 st dep::some_module::ThiiiiiirdStruct
979 "#]],
980 );
981 }
982} 737}
diff --git a/crates/completion/src/config.rs b/crates/completion/src/config.rs
index b4439b7d1..58fc700f3 100644
--- a/crates/completion/src/config.rs
+++ b/crates/completion/src/config.rs
@@ -4,7 +4,7 @@
4//! module, and we use to statically check that we only produce snippet 4//! module, and we use to statically check that we only produce snippet
5//! completions if we are allowed to. 5//! completions if we are allowed to.
6 6
7use ide_db::helpers::{insert_use::MergeBehavior, SnippetCap}; 7use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
8 8
9#[derive(Clone, Debug, PartialEq, Eq)] 9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct CompletionConfig { 10pub struct CompletionConfig {
@@ -13,5 +13,5 @@ pub struct CompletionConfig {
13 pub add_call_parenthesis: bool, 13 pub add_call_parenthesis: bool,
14 pub add_call_argument_snippets: bool, 14 pub add_call_argument_snippets: bool,
15 pub snippet_cap: Option<SnippetCap>, 15 pub snippet_cap: Option<SnippetCap>,
16 pub merge: Option<MergeBehavior>, 16 pub insert_use: InsertUseConfig,
17} 17}
diff --git a/crates/completion/src/context.rs b/crates/completion/src/context.rs
index ebf28e887..b1e8eba85 100644
--- a/crates/completion/src/context.rs
+++ b/crates/completion/src/context.rs
@@ -4,10 +4,8 @@ use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type};
4use ide_db::base_db::{FilePosition, SourceDatabase}; 4use ide_db::base_db::{FilePosition, SourceDatabase};
5use ide_db::{call_info::ActiveParameter, RootDatabase}; 5use ide_db::{call_info::ActiveParameter, RootDatabase};
6use syntax::{ 6use syntax::{
7 algo::{find_covering_element, find_node_at_offset}, 7 algo::find_node_at_offset, ast, match_ast, AstNode, NodeOrToken, SyntaxKind::*, SyntaxNode,
8 ast, match_ast, AstNode, NodeOrToken, 8 SyntaxToken, TextRange, TextSize,
9 SyntaxKind::*,
10 SyntaxNode, SyntaxToken, TextRange, TextSize,
11}; 9};
12use test_utils::mark; 10use test_utils::mark;
13use text_edit::Indel; 11use text_edit::Indel;
@@ -92,6 +90,7 @@ pub(crate) struct CompletionContext<'a> {
92 pub(super) has_item_list_or_source_file_parent: bool, 90 pub(super) has_item_list_or_source_file_parent: bool,
93 pub(super) for_is_prev2: bool, 91 pub(super) for_is_prev2: bool,
94 pub(super) fn_is_prev: bool, 92 pub(super) fn_is_prev: bool,
93 pub(super) incomplete_let: bool,
95 pub(super) locals: Vec<(String, Local)>, 94 pub(super) locals: Vec<(String, Local)>,
96} 95}
97 96
@@ -132,9 +131,9 @@ impl<'a> CompletionContext<'a> {
132 scope, 131 scope,
133 db, 132 db,
134 config, 133 config,
134 position,
135 original_token, 135 original_token,
136 token, 136 token,
137 position,
138 krate, 137 krate,
139 expected_type: None, 138 expected_type: None,
140 name_ref_syntax: None, 139 name_ref_syntax: None,
@@ -155,30 +154,31 @@ impl<'a> CompletionContext<'a> {
155 is_expr: false, 154 is_expr: false,
156 is_new_item: false, 155 is_new_item: false,
157 dot_receiver: None, 156 dot_receiver: None,
157 dot_receiver_is_ambiguous_float_literal: false,
158 is_call: false, 158 is_call: false,
159 is_pattern_call: false, 159 is_pattern_call: false,
160 is_macro_call: false, 160 is_macro_call: false,
161 is_path_type: false, 161 is_path_type: false,
162 has_type_args: false, 162 has_type_args: false,
163 dot_receiver_is_ambiguous_float_literal: false,
164 attribute_under_caret: None, 163 attribute_under_caret: None,
165 mod_declaration_under_caret: None, 164 mod_declaration_under_caret: None,
166 unsafe_is_prev: false, 165 unsafe_is_prev: false,
167 in_loop_body: false, 166 if_is_prev: false,
168 ref_pat_parent: false,
169 bind_pat_parent: false,
170 block_expr_parent: false, 167 block_expr_parent: false,
168 bind_pat_parent: false,
169 ref_pat_parent: false,
170 in_loop_body: false,
171 has_trait_parent: false, 171 has_trait_parent: false,
172 has_impl_parent: false, 172 has_impl_parent: false,
173 inside_impl_trait_block: false, 173 inside_impl_trait_block: false,
174 has_field_list_parent: false, 174 has_field_list_parent: false,
175 trait_as_prev_sibling: false, 175 trait_as_prev_sibling: false,
176 impl_as_prev_sibling: false, 176 impl_as_prev_sibling: false,
177 if_is_prev: false,
178 is_match_arm: false, 177 is_match_arm: false,
179 has_item_list_or_source_file_parent: false, 178 has_item_list_or_source_file_parent: false,
180 for_is_prev2: false, 179 for_is_prev2: false,
181 fn_is_prev: false, 180 fn_is_prev: false,
181 incomplete_let: false,
182 locals, 182 locals,
183 }; 183 };
184 184
@@ -270,6 +270,10 @@ impl<'a> CompletionContext<'a> {
270 .filter(|module| module.item_list().is_none()); 270 .filter(|module| module.item_list().is_none());
271 self.for_is_prev2 = for_is_prev2(syntax_element.clone()); 271 self.for_is_prev2 = for_is_prev2(syntax_element.clone());
272 self.fn_is_prev = fn_is_prev(syntax_element.clone()); 272 self.fn_is_prev = fn_is_prev(syntax_element.clone());
273 self.incomplete_let =
274 syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| {
275 it.syntax().text_range().end() == syntax_element.text_range().end()
276 });
273 } 277 }
274 278
275 fn fill( 279 fn fill(
@@ -507,7 +511,7 @@ impl<'a> CompletionContext<'a> {
507} 511}
508 512
509fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> { 513fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
510 find_covering_element(syntax, range).ancestors().find_map(N::cast) 514 syntax.covering_element(range).ancestors().find_map(N::cast)
511} 515}
512 516
513fn is_node<N: AstNode>(node: &SyntaxNode) -> bool { 517fn is_node<N: AstNode>(node: &SyntaxNode) -> bool {
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs
index 6cba88a6b..ee1b822e7 100644
--- a/crates/completion/src/lib.rs
+++ b/crates/completion/src/lib.rs
@@ -127,6 +127,7 @@ pub fn completions(
127 completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); 127 completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
128 completions::trait_impl::complete_trait_impl(&mut acc, &ctx); 128 completions::trait_impl::complete_trait_impl(&mut acc, &ctx);
129 completions::mod_::complete_mod(&mut acc, &ctx); 129 completions::mod_::complete_mod(&mut acc, &ctx);
130 completions::flyimport::import_on_the_fly(&mut acc, &ctx);
130 131
131 Some(acc) 132 Some(acc)
132} 133}
@@ -153,7 +154,9 @@ pub fn resolve_completion_edits(
153 }) 154 })
154 .find(|mod_path| mod_path.to_string() == full_import_path)?; 155 .find(|mod_path| mod_path.to_string() == full_import_path)?;
155 156
156 ImportEdit { import_path, import_scope }.to_text_edit(config.merge).map(|edit| vec![edit]) 157 ImportEdit { import_path, import_scope }
158 .to_text_edit(config.insert_use.merge)
159 .map(|edit| vec![edit])
157} 160}
158 161
159#[cfg(test)] 162#[cfg(test)]
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs
index e93c59f71..820dd01d1 100644
--- a/crates/completion/src/render.rs
+++ b/crates/completion/src/render.rs
@@ -51,11 +51,16 @@ pub(crate) fn render_resolution_with_import<'a>(
51 import_edit: ImportEdit, 51 import_edit: ImportEdit,
52 resolution: &ScopeDef, 52 resolution: &ScopeDef,
53) -> Option<CompletionItem> { 53) -> Option<CompletionItem> {
54 Render::new(ctx).render_resolution( 54 Render::new(ctx)
55 import_edit.import_path.segments.last()?.to_string(), 55 .render_resolution(
56 Some(import_edit), 56 import_edit.import_path.segments.last()?.to_string(),
57 resolution, 57 Some(import_edit),
58 ) 58 resolution,
59 )
60 .map(|mut item| {
61 item.completion_kind = CompletionKind::Magic;
62 item
63 })
59} 64}
60 65
61/// Interface for data and methods required for items rendering. 66/// Interface for data and methods required for items rendering.
diff --git a/crates/completion/src/test_utils.rs b/crates/completion/src/test_utils.rs
index 3f4b9d4ac..6ea6da989 100644
--- a/crates/completion/src/test_utils.rs
+++ b/crates/completion/src/test_utils.rs
@@ -1,9 +1,12 @@
1//! Runs completion for testing purposes. 1//! Runs completion for testing purposes.
2 2
3use hir::Semantics; 3use hir::{PrefixKind, Semantics};
4use ide_db::{ 4use ide_db::{
5 base_db::{fixture::ChangeFixture, FileLoader, FilePosition}, 5 base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
6 helpers::{insert_use::MergeBehavior, SnippetCap}, 6 helpers::{
7 insert_use::{InsertUseConfig, MergeBehavior},
8 SnippetCap,
9 },
7 RootDatabase, 10 RootDatabase,
8}; 11};
9use itertools::Itertools; 12use itertools::Itertools;
@@ -19,7 +22,10 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
19 add_call_parenthesis: true, 22 add_call_parenthesis: true,
20 add_call_argument_snippets: true, 23 add_call_argument_snippets: true,
21 snippet_cap: SnippetCap::new(true), 24 snippet_cap: SnippetCap::new(true),
22 merge: Some(MergeBehavior::Full), 25 insert_use: InsertUseConfig {
26 merge: Some(MergeBehavior::Full),
27 prefix_kind: PrefixKind::Plain,
28 },
23}; 29};
24 30
25/// Creates analysis from a multi-file fixture, returns positions marked with $0. 31/// Creates analysis from a multi-file fixture, returns positions marked with $0.
@@ -110,7 +116,7 @@ pub(crate) fn check_edit_with_config(
110 116
111 let mut combined_edit = completion.text_edit().to_owned(); 117 let mut combined_edit = completion.text_edit().to_owned();
112 if let Some(import_text_edit) = 118 if let Some(import_text_edit) =
113 completion.import_to_add().and_then(|edit| edit.to_text_edit(config.merge)) 119 completion.import_to_add().and_then(|edit| edit.to_text_edit(config.insert_use.merge))
114 { 120 {
115 combined_edit.union(import_text_edit).expect( 121 combined_edit.union(import_text_edit).expect(
116 "Failed to apply completion resolve changes: change ranges overlap, but should not", 122 "Failed to apply completion resolve changes: change ranges overlap, but should not",
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index cd689c869..ab213e04c 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -750,6 +750,7 @@ to_def_impls![
750 (crate::ConstParam, ast::ConstParam, const_param_to_def), 750 (crate::ConstParam, ast::ConstParam, const_param_to_def),
751 (crate::MacroDef, ast::MacroRules, macro_rules_to_def), 751 (crate::MacroDef, ast::MacroRules, macro_rules_to_def),
752 (crate::Local, ast::IdentPat, bind_pat_to_def), 752 (crate::Local, ast::IdentPat, bind_pat_to_def),
753 (crate::Local, ast::SelfParam, self_param_to_def),
753 (crate::Label, ast::Label, label_to_def), 754 (crate::Label, ast::Label, label_to_def),
754]; 755];
755 756
diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs
index 4b9ebff72..9bf60c72a 100644
--- a/crates/hir/src/semantics/source_to_def.rs
+++ b/crates/hir/src/semantics/source_to_def.rs
@@ -114,6 +114,15 @@ impl SourceToDefCtx<'_, '_> {
114 let pat_id = source_map.node_pat(src.as_ref())?; 114 let pat_id = source_map.node_pat(src.as_ref())?;
115 Some((container, pat_id)) 115 Some((container, pat_id))
116 } 116 }
117 pub(super) fn self_param_to_def(
118 &mut self,
119 src: InFile<ast::SelfParam>,
120 ) -> Option<(DefWithBodyId, PatId)> {
121 let container = self.find_pat_or_label_container(src.as_ref().map(|it| it.syntax()))?;
122 let (_body, source_map) = self.db.body_with_source_map(container);
123 let pat_id = source_map.node_self_param(src.as_ref())?;
124 Some((container, pat_id))
125 }
117 pub(super) fn label_to_def( 126 pub(super) fn label_to_def(
118 &mut self, 127 &mut self,
119 src: InFile<ast::Label>, 128 src: InFile<ast::Label>,
diff --git a/crates/hir_def/Cargo.toml b/crates/hir_def/Cargo.toml
index 5d21283f7..023d288d1 100644
--- a/crates/hir_def/Cargo.toml
+++ b/crates/hir_def/Cargo.toml
@@ -20,7 +20,7 @@ fst = { version = "0.4", default-features = false }
20itertools = "0.10.0" 20itertools = "0.10.0"
21indexmap = "1.4.0" 21indexmap = "1.4.0"
22smallvec = "1.4.0" 22smallvec = "1.4.0"
23la-arena = "0.1.0" 23la-arena = { version = "0.1.0", path = "../../lib/arena" }
24 24
25stdx = { path = "../stdx", version = "0.0.0" } 25stdx = { path = "../stdx", version = "0.0.0" }
26base_db = { path = "../base_db", version = "0.0.0" } 26base_db = { path = "../base_db", version = "0.0.0" }
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 27575c537..4ce5e5b72 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -386,6 +386,10 @@ impl ExprCollector<'_> {
386 let expr = e.expr().map(|e| self.collect_expr(e)); 386 let expr = e.expr().map(|e| self.collect_expr(e));
387 self.alloc_expr(Expr::Return { expr }, syntax_ptr) 387 self.alloc_expr(Expr::Return { expr }, syntax_ptr)
388 } 388 }
389 ast::Expr::YieldExpr(e) => {
390 let expr = e.expr().map(|e| self.collect_expr(e));
391 self.alloc_expr(Expr::Yield { expr }, syntax_ptr)
392 }
389 ast::Expr::RecordExpr(e) => { 393 ast::Expr::RecordExpr(e) => {
390 let path = e.path().and_then(|path| self.expander.parse_path(path)); 394 let path = e.path().and_then(|path| self.expander.parse_path(path));
391 let mut field_ptrs = Vec::new(); 395 let mut field_ptrs = Vec::new();
diff --git a/crates/hir_def/src/expr.rs b/crates/hir_def/src/expr.rs
index af01d32dc..a293df9f1 100644
--- a/crates/hir_def/src/expr.rs
+++ b/crates/hir_def/src/expr.rs
@@ -99,6 +99,9 @@ pub enum Expr {
99 Return { 99 Return {
100 expr: Option<ExprId>, 100 expr: Option<ExprId>,
101 }, 101 },
102 Yield {
103 expr: Option<ExprId>,
104 },
102 RecordLit { 105 RecordLit {
103 path: Option<Path>, 106 path: Option<Path>,
104 fields: Vec<RecordLitField>, 107 fields: Vec<RecordLitField>,
@@ -294,7 +297,7 @@ impl Expr {
294 } 297 }
295 } 298 }
296 Expr::Continue { .. } => {} 299 Expr::Continue { .. } => {}
297 Expr::Break { expr, .. } | Expr::Return { expr } => { 300 Expr::Break { expr, .. } | Expr::Return { expr } | Expr::Yield { expr } => {
298 if let Some(expr) = expr { 301 if let Some(expr) = expr {
299 f(*expr); 302 f(*expr);
300 } 303 }
diff --git a/crates/hir_def/src/resolver.rs b/crates/hir_def/src/resolver.rs
index 61059c349..85ddc2c47 100644
--- a/crates/hir_def/src/resolver.rs
+++ b/crates/hir_def/src/resolver.rs
@@ -258,7 +258,7 @@ impl Resolver {
258 ) -> Option<ResolveValueResult> { 258 ) -> Option<ResolveValueResult> {
259 let n_segments = path.segments.len(); 259 let n_segments = path.segments.len();
260 let tmp = name![self]; 260 let tmp = name![self];
261 let first_name = if path.is_self() { &tmp } else { &path.segments.first()? }; 261 let first_name = if path.is_self() { &tmp } else { path.segments.first()? };
262 let skip_to_mod = path.kind != PathKind::Plain && !path.is_self(); 262 let skip_to_mod = path.kind != PathKind::Plain && !path.is_self();
263 for scope in self.scopes.iter().rev() { 263 for scope in self.scopes.iter().rev() {
264 match scope { 264 match scope {
diff --git a/crates/hir_expand/Cargo.toml b/crates/hir_expand/Cargo.toml
index b535a3d4f..1b1c7da65 100644
--- a/crates/hir_expand/Cargo.toml
+++ b/crates/hir_expand/Cargo.toml
@@ -13,7 +13,7 @@ doctest = false
13log = "0.4.8" 13log = "0.4.8"
14either = "1.5.3" 14either = "1.5.3"
15rustc-hash = "1.0.0" 15rustc-hash = "1.0.0"
16la-arena = "0.1.0" 16la-arena = { version = "0.1.0", path = "../../lib/arena" }
17 17
18base_db = { path = "../base_db", version = "0.0.0" } 18base_db = { path = "../base_db", version = "0.0.0" }
19syntax = { path = "../syntax", version = "0.0.0" } 19syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/hir_expand/src/ast_id_map.rs b/crates/hir_expand/src/ast_id_map.rs
index f4f6e11fd..2401b0cc5 100644
--- a/crates/hir_expand/src/ast_id_map.rs
+++ b/crates/hir_expand/src/ast_id_map.rs
@@ -72,10 +72,12 @@ impl AstIdMap {
72 // get lower ids then children. That is, adding a new child does not 72 // get lower ids then children. That is, adding a new child does not
73 // change parent's id. This means that, say, adding a new function to a 73 // change parent's id. This means that, say, adding a new function to a
74 // trait does not change ids of top-level items, which helps caching. 74 // trait does not change ids of top-level items, which helps caching.
75 bfs(node, |it| { 75 bdfs(node, |it| match ast::Item::cast(it) {
76 if let Some(module_item) = ast::Item::cast(it) { 76 Some(module_item) => {
77 res.alloc(module_item.syntax()); 77 res.alloc(module_item.syntax());
78 true
78 } 79 }
80 None => false,
79 }); 81 });
80 res 82 res
81 } 83 }
@@ -105,14 +107,30 @@ impl AstIdMap {
105 } 107 }
106} 108}
107 109
108/// Walks the subtree in bfs order, calling `f` for each node. 110/// Walks the subtree in bdfs order, calling `f` for each node. What is bdfs
109fn bfs(node: &SyntaxNode, mut f: impl FnMut(SyntaxNode)) { 111/// order? It is a mix of breadth-first and depth first orders. Nodes for which
112/// `f` returns true are visited breadth-first, all the other nodes are explored
113/// depth-first.
114///
115/// In other words, the size of the bfs queue is bound by the number of "true"
116/// nodes.
117fn bdfs(node: &SyntaxNode, mut f: impl FnMut(SyntaxNode) -> bool) {
110 let mut curr_layer = vec![node.clone()]; 118 let mut curr_layer = vec![node.clone()];
111 let mut next_layer = vec![]; 119 let mut next_layer = vec![];
112 while !curr_layer.is_empty() { 120 while !curr_layer.is_empty() {
113 curr_layer.drain(..).for_each(|node| { 121 curr_layer.drain(..).for_each(|node| {
114 next_layer.extend(node.children()); 122 let mut preorder = node.preorder();
115 f(node); 123 while let Some(event) = preorder.next() {
124 match event {
125 syntax::WalkEvent::Enter(node) => {
126 if f(node.clone()) {
127 next_layer.extend(node.children());
128 preorder.skip_subtree();
129 }
130 }
131 syntax::WalkEvent::Leave(_) => {}
132 }
133 }
116 }); 134 });
117 std::mem::swap(&mut curr_layer, &mut next_layer); 135 std::mem::swap(&mut curr_layer, &mut next_layer);
118 } 136 }
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index c62086390..467516eb7 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -118,7 +118,7 @@ pub fn expand_hypothetical(
118 parse_macro_with_arg(db, macro_file, Some(std::sync::Arc::new((tt, tmap_1)))).value?; 118 parse_macro_with_arg(db, macro_file, Some(std::sync::Arc::new((tt, tmap_1)))).value?;
119 let token_id = macro_def.0.map_id_down(token_id); 119 let token_id = macro_def.0.map_id_down(token_id);
120 let range = tmap_2.range_by_token(token_id)?.by_kind(token_to_map.kind())?; 120 let range = tmap_2.range_by_token(token_id)?.by_kind(token_to_map.kind())?;
121 let token = syntax::algo::find_covering_element(&node.syntax_node(), range).into_token()?; 121 let token = node.syntax_node().covering_element(range).into_token()?;
122 Some((node.syntax_node(), token)) 122 Some((node.syntax_node(), token))
123} 123}
124 124
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index 3fa1b1d77..e388ddacc 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -22,7 +22,7 @@ use std::sync::Arc;
22 22
23use base_db::{impl_intern_key, salsa, CrateId, FileId, FileRange}; 23use base_db::{impl_intern_key, salsa, CrateId, FileId, FileRange};
24use syntax::{ 24use syntax::{
25 algo::{self, skip_trivia_token}, 25 algo::skip_trivia_token,
26 ast::{self, AstNode}, 26 ast::{self, AstNode},
27 Direction, SyntaxNode, SyntaxToken, TextRange, TextSize, 27 Direction, SyntaxNode, SyntaxToken, TextRange, TextSize,
28}; 28};
@@ -335,7 +335,7 @@ impl ExpansionInfo {
335 335
336 let range = self.exp_map.range_by_token(token_id)?.by_kind(token.value.kind())?; 336 let range = self.exp_map.range_by_token(token_id)?.by_kind(token.value.kind())?;
337 337
338 let token = algo::find_covering_element(&self.expanded.value, range).into_token()?; 338 let token = self.expanded.value.covering_element(range).into_token()?;
339 339
340 Some(self.expanded.with_value(token)) 340 Some(self.expanded.with_value(token))
341 } 341 }
@@ -360,8 +360,8 @@ impl ExpansionInfo {
360 }; 360 };
361 361
362 let range = token_map.range_by_token(token_id)?.by_kind(token.value.kind())?; 362 let range = token_map.range_by_token(token_id)?.by_kind(token.value.kind())?;
363 let token = algo::find_covering_element(&tt.value, range + tt.value.text_range().start()) 363 let token =
364 .into_token()?; 364 tt.value.covering_element(range + tt.value.text_range().start()).into_token()?;
365 Some((tt.with_value(token), origin)) 365 Some((tt.with_value(token), origin))
366 } 366 }
367} 367}
diff --git a/crates/hir_ty/Cargo.toml b/crates/hir_ty/Cargo.toml
index 436c1405b..a6916af90 100644
--- a/crates/hir_ty/Cargo.toml
+++ b/crates/hir_ty/Cargo.toml
@@ -20,7 +20,7 @@ scoped-tls = "1"
20chalk-solve = { version = "0.47", default-features = false } 20chalk-solve = { version = "0.47", default-features = false }
21chalk-ir = "0.47" 21chalk-ir = "0.47"
22chalk-recursive = "0.47" 22chalk-recursive = "0.47"
23la-arena = "0.1.0" 23la-arena = { version = "0.1.0", path = "../../lib/arena" }
24 24
25stdx = { path = "../stdx", version = "0.0.0" } 25stdx = { path = "../stdx", version = "0.0.0" }
26hir_def = { path = "../hir_def", version = "0.0.0" } 26hir_def = { path = "../hir_def", version = "0.0.0" }
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index f2fc69b2f..9bf3b51b0 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -367,6 +367,13 @@ impl<'a> InferenceContext<'a> {
367 } 367 }
368 Ty::simple(TypeCtor::Never) 368 Ty::simple(TypeCtor::Never)
369 } 369 }
370 Expr::Yield { expr } => {
371 // FIXME: track yield type for coercion
372 if let Some(expr) = expr {
373 self.infer_expr(*expr, &Expectation::none());
374 }
375 Ty::simple(TypeCtor::Never)
376 }
370 Expr::RecordLit { path, fields, spread } => { 377 Expr::RecordLit { path, fields, spread } => {
371 let (ty, def_id) = self.resolve_variant(path.as_ref()); 378 let (ty, def_id) = self.resolve_variant(path.as_ref());
372 if let Some(variant) = def_id { 379 if let Some(variant) = def_id {
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 055c0a79c..b35bc2bae 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -13,17 +13,16 @@ use hir::{
13 diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, 13 diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder},
14 Semantics, 14 Semantics,
15}; 15};
16use ide_db::base_db::SourceDatabase; 16use ide_db::{base_db::SourceDatabase, RootDatabase};
17use ide_db::RootDatabase;
18use itertools::Itertools; 17use itertools::Itertools;
19use rustc_hash::FxHashSet; 18use rustc_hash::FxHashSet;
20use syntax::{ 19use syntax::{
21 ast::{self, AstNode}, 20 ast::{self, AstNode},
22 SyntaxNode, TextRange, T, 21 SyntaxNode, TextRange,
23}; 22};
24use text_edit::TextEdit; 23use text_edit::TextEdit;
25 24
26use crate::{FileId, Label, SourceChange, SourceFileEdit}; 25use crate::{FileId, Label, SourceChange};
27 26
28use self::fixes::DiagnosticWithFix; 27use self::fixes::DiagnosticWithFix;
29 28
@@ -220,7 +219,7 @@ fn check_unnecessary_braces_in_use_statement(
220 Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) 219 Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string())
221 .with_fix(Some(Fix::new( 220 .with_fix(Some(Fix::new(
222 "Remove unnecessary braces", 221 "Remove unnecessary braces",
223 SourceFileEdit { file_id, edit }.into(), 222 SourceChange::from_text_edit(file_id, edit),
224 use_range, 223 use_range,
225 ))), 224 ))),
226 ); 225 );
@@ -233,7 +232,7 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
233 single_use_tree: &ast::UseTree, 232 single_use_tree: &ast::UseTree,
234) -> Option<TextEdit> { 233) -> Option<TextEdit> {
235 let use_tree_list_node = single_use_tree.syntax().parent()?; 234 let use_tree_list_node = single_use_tree.syntax().parent()?;
236 if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] { 235 if single_use_tree.path()?.segment()?.self_token().is_some() {
237 let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); 236 let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start();
238 let end = use_tree_list_node.text_range().end(); 237 let end = use_tree_list_node.text_range().end();
239 return Some(TextEdit::delete(TextRange::new(start, end))); 238 return Some(TextEdit::delete(TextRange::new(start, end)));
@@ -265,13 +264,11 @@ mod tests {
265 .unwrap(); 264 .unwrap();
266 let fix = diagnostic.fix.unwrap(); 265 let fix = diagnostic.fix.unwrap();
267 let actual = { 266 let actual = {
268 let file_id = fix.source_change.source_file_edits.first().unwrap().file_id; 267 let file_id = *fix.source_change.source_file_edits.keys().next().unwrap();
269 let mut actual = analysis.file_text(file_id).unwrap().to_string(); 268 let mut actual = analysis.file_text(file_id).unwrap().to_string();
270 269
271 // Go from the last one to the first one, so that ranges won't be affected by previous edits. 270 for edit in fix.source_change.source_file_edits.values() {
272 // FIXME: https://github.com/rust-analyzer/rust-analyzer/issues/4901#issuecomment-644675309 271 edit.apply(&mut actual);
273 for edit in fix.source_change.source_file_edits.iter().rev() {
274 edit.edit.apply(&mut actual);
275 } 272 }
276 actual 273 actual
277 }; 274 };
@@ -616,7 +613,7 @@ fn test_fn() {
616 Fix { 613 Fix {
617 label: "Create module", 614 label: "Create module",
618 source_change: SourceChange { 615 source_change: SourceChange {
619 source_file_edits: [], 616 source_file_edits: {},
620 file_system_edits: [ 617 file_system_edits: [
621 CreateFile { 618 CreateFile {
622 dst: AnchoredPathBuf { 619 dst: AnchoredPathBuf {
diff --git a/crates/ide/src/diagnostics/field_shorthand.rs b/crates/ide/src/diagnostics/field_shorthand.rs
index 16c6ea827..5c89e2170 100644
--- a/crates/ide/src/diagnostics/field_shorthand.rs
+++ b/crates/ide/src/diagnostics/field_shorthand.rs
@@ -1,8 +1,7 @@
1//! Suggests shortening `Foo { field: field }` to `Foo { field }` in both 1//! Suggests shortening `Foo { field: field }` to `Foo { field }` in both
2//! expressions and patterns. 2//! expressions and patterns.
3 3
4use ide_db::base_db::FileId; 4use ide_db::{base_db::FileId, source_change::SourceChange};
5use ide_db::source_change::SourceFileEdit;
6use syntax::{ast, match_ast, AstNode, SyntaxNode}; 5use syntax::{ast, match_ast, AstNode, SyntaxNode};
7use text_edit::TextEdit; 6use text_edit::TextEdit;
8 7
@@ -50,7 +49,7 @@ fn check_expr_field_shorthand(
50 Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()).with_fix( 49 Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()).with_fix(
51 Some(Fix::new( 50 Some(Fix::new(
52 "Use struct shorthand initialization", 51 "Use struct shorthand initialization",
53 SourceFileEdit { file_id, edit }.into(), 52 SourceChange::from_text_edit(file_id, edit),
54 field_range, 53 field_range,
55 )), 54 )),
56 ), 55 ),
@@ -89,7 +88,7 @@ fn check_pat_field_shorthand(
89 acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fix( 88 acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fix(
90 Some(Fix::new( 89 Some(Fix::new(
91 "Use struct field shorthand", 90 "Use struct field shorthand",
92 SourceFileEdit { file_id, edit }.into(), 91 SourceChange::from_text_edit(file_id, edit),
93 field_range, 92 field_range,
94 )), 93 )),
95 )); 94 ));
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index d7ad88ed5..e4335119b 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -8,9 +8,9 @@ use hir::{
8 }, 8 },
9 HasSource, HirDisplay, InFile, Semantics, VariantDef, 9 HasSource, HirDisplay, InFile, Semantics, VariantDef,
10}; 10};
11use ide_db::base_db::{AnchoredPathBuf, FileId};
12use ide_db::{ 11use ide_db::{
13 source_change::{FileSystemEdit, SourceFileEdit}, 12 base_db::{AnchoredPathBuf, FileId},
13 source_change::{FileSystemEdit, SourceChange},
14 RootDatabase, 14 RootDatabase,
15}; 15};
16use syntax::{ 16use syntax::{
@@ -88,7 +88,7 @@ impl DiagnosticWithFix for MissingFields {
88 }; 88 };
89 Some(Fix::new( 89 Some(Fix::new(
90 "Fill struct fields", 90 "Fill struct fields",
91 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(), 91 SourceChange::from_text_edit(self.file.original_file(sema.db), edit),
92 sema.original_range(&field_list_parent.syntax()).range, 92 sema.original_range(&field_list_parent.syntax()).range,
93 )) 93 ))
94 } 94 }
@@ -101,8 +101,7 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
101 let tail_expr_range = tail_expr.syntax().text_range(); 101 let tail_expr_range = tail_expr.syntax().text_range();
102 let replacement = format!("{}({})", self.required, tail_expr.syntax()); 102 let replacement = format!("{}({})", self.required, tail_expr.syntax());
103 let edit = TextEdit::replace(tail_expr_range, replacement); 103 let edit = TextEdit::replace(tail_expr_range, replacement);
104 let source_change = 104 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
105 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into();
106 let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; 105 let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
107 Some(Fix::new(name, source_change, tail_expr_range)) 106 Some(Fix::new(name, source_change, tail_expr_range))
108 } 107 }
@@ -122,8 +121,7 @@ impl DiagnosticWithFix for RemoveThisSemicolon {
122 .text_range(); 121 .text_range();
123 122
124 let edit = TextEdit::delete(semicolon); 123 let edit = TextEdit::delete(semicolon);
125 let source_change = 124 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
126 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into();
127 125
128 Some(Fix::new("Remove this semicolon", source_change, semicolon)) 126 Some(Fix::new("Remove this semicolon", source_change, semicolon))
129 } 127 }
@@ -204,15 +202,11 @@ fn missing_record_expr_field_fix(
204 new_field = format!(",{}", new_field); 202 new_field = format!(",{}", new_field);
205 } 203 }
206 204
207 let source_change = SourceFileEdit { 205 let source_change = SourceChange::from_text_edit(
208 file_id: def_file_id, 206 def_file_id,
209 edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), 207 TextEdit::insert(last_field_syntax.text_range().end(), new_field),
210 }; 208 );
211 return Some(Fix::new( 209 return Some(Fix::new("Create field", source_change, record_expr_field.syntax().text_range()));
212 "Create field",
213 source_change.into(),
214 record_expr_field.syntax().text_range(),
215 ));
216 210
217 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { 211 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
218 match field_def_list { 212 match field_def_list {
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs
index 4eecae697..685052e7f 100644
--- a/crates/ide/src/display/navigation_target.rs
+++ b/crates/ide/src/display/navigation_target.rs
@@ -400,24 +400,33 @@ impl TryToNav for hir::GenericParam {
400impl ToNav for hir::Local { 400impl ToNav for hir::Local {
401 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { 401 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
402 let src = self.source(db); 402 let src = self.source(db);
403 let node = match &src.value { 403 let (node, focus_range) = match &src.value {
404 Either::Left(bind_pat) => { 404 Either::Left(bind_pat) => (
405 bind_pat.name().map_or_else(|| bind_pat.syntax().clone(), |it| it.syntax().clone()) 405 bind_pat.syntax().clone(),
406 } 406 bind_pat
407 Either::Right(it) => it.syntax().clone(), 407 .name()
408 .map(|it| src.with_value(&it.syntax().clone()).original_file_range(db).range),
409 ),
410 Either::Right(it) => (it.syntax().clone(), it.self_token().map(|it| it.text_range())),
408 }; 411 };
409 let full_range = src.with_value(&node).original_file_range(db); 412 let full_range = src.with_value(&node).original_file_range(db);
410 let name = match self.name(db) { 413 let name = match self.name(db) {
411 Some(it) => it.to_string().into(), 414 Some(it) => it.to_string().into(),
412 None => "".into(), 415 None => "".into(),
413 }; 416 };
414 let kind = if self.is_param(db) { SymbolKind::ValueParam } else { SymbolKind::Local }; 417 let kind = if self.is_self(db) {
418 SymbolKind::SelfParam
419 } else if self.is_param(db) {
420 SymbolKind::ValueParam
421 } else {
422 SymbolKind::Local
423 };
415 NavigationTarget { 424 NavigationTarget {
416 file_id: full_range.file_id, 425 file_id: full_range.file_id,
417 name, 426 name,
418 kind: Some(kind), 427 kind: Some(kind),
419 full_range: full_range.range, 428 full_range: full_range.range,
420 focus_range: None, 429 focus_range,
421 container_name: None, 430 container_name: None,
422 description: None, 431 description: None,
423 docs: None, 432 docs: None,
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
index 56418c960..17a540972 100644
--- a/crates/ide/src/extend_selection.rs
+++ b/crates/ide/src/extend_selection.rs
@@ -3,7 +3,7 @@ use std::iter::successors;
3use hir::Semantics; 3use hir::Semantics;
4use ide_db::RootDatabase; 4use ide_db::RootDatabase;
5use syntax::{ 5use syntax::{
6 algo::{self, find_covering_element, skip_trivia_token}, 6 algo::{self, skip_trivia_token},
7 ast::{self, AstNode, AstToken}, 7 ast::{self, AstNode, AstToken},
8 Direction, NodeOrToken, 8 Direction, NodeOrToken,
9 SyntaxKind::{self, *}, 9 SyntaxKind::{self, *},
@@ -76,7 +76,7 @@ fn try_extend_selection(
76 }; 76 };
77 return Some(leaf_range); 77 return Some(leaf_range);
78 }; 78 };
79 let node = match find_covering_element(root, range) { 79 let node = match root.covering_element(range) {
80 NodeOrToken::Token(token) => { 80 NodeOrToken::Token(token) => {
81 if token.text_range() != range { 81 if token.text_range() != range {
82 return Some(token.text_range()); 82 return Some(token.text_range());
@@ -120,7 +120,7 @@ fn extend_tokens_from_range(
120 macro_call: ast::MacroCall, 120 macro_call: ast::MacroCall,
121 original_range: TextRange, 121 original_range: TextRange,
122) -> Option<TextRange> { 122) -> Option<TextRange> {
123 let src = find_covering_element(&macro_call.syntax(), original_range); 123 let src = macro_call.syntax().covering_element(original_range);
124 let (first_token, last_token) = match src { 124 let (first_token, last_token) = match src {
125 NodeOrToken::Node(it) => (it.first_token()?, it.last_token()?), 125 NodeOrToken::Node(it) => (it.first_token()?, it.last_token()?),
126 NodeOrToken::Token(it) => (it.clone(), it), 126 NodeOrToken::Token(it) => (it.clone(), it),
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index cd4afc804..988a5668f 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -1,7 +1,6 @@
1use either::Either; 1use either::Either;
2use hir::{HasAttrs, ModuleDef, Semantics}; 2use hir::{HasAttrs, ModuleDef, Semantics};
3use ide_db::{ 3use ide_db::{
4 base_db::FileId,
5 defs::{Definition, NameClass, NameRefClass}, 4 defs::{Definition, NameClass, NameRefClass},
6 symbol_index, RootDatabase, 5 symbol_index, RootDatabase,
7}; 6};
@@ -13,7 +12,7 @@ use crate::{
13 display::{ToNav, TryToNav}, 12 display::{ToNav, TryToNav},
14 doc_links::extract_definitions_from_markdown, 13 doc_links::extract_definitions_from_markdown,
15 runnables::doc_owner_to_def, 14 runnables::doc_owner_to_def,
16 FilePosition, NavigationTarget, RangeInfo, SymbolKind, 15 FilePosition, NavigationTarget, RangeInfo,
17}; 16};
18 17
19// Feature: Go to Definition 18// Feature: Go to Definition
@@ -49,19 +48,6 @@ pub(crate) fn goto_definition(
49 let nav = def.try_to_nav(sema.db)?; 48 let nav = def.try_to_nav(sema.db)?;
50 vec![nav] 49 vec![nav]
51 }, 50 },
52 ast::SelfParam(self_param) => {
53 vec![self_to_nav_target(self_param, position.file_id)?]
54 },
55 ast::PathSegment(segment) => {
56 segment.self_token()?;
57 let path = segment.parent_path();
58 if path.qualifier().is_some() && !ast::PathExpr::can_cast(path.syntax().parent()?.kind()) {
59 return None;
60 }
61 let func = segment.syntax().ancestors().find_map(ast::Fn::cast)?;
62 let self_param = func.param_list()?.self_param()?;
63 vec![self_to_nav_target(self_param, position.file_id)?]
64 },
65 ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, &lt) { 51 ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, &lt) {
66 let def = name_class.referenced_or_defined(sema.db); 52 let def = name_class.referenced_or_defined(sema.db);
67 let nav = def.try_to_nav(sema.db)?; 53 let nav = def.try_to_nav(sema.db)?;
@@ -69,6 +55,11 @@ pub(crate) fn goto_definition(
69 } else { 55 } else {
70 reference_definition(&sema, Either::Left(&lt)).to_vec() 56 reference_definition(&sema, Either::Left(&lt)).to_vec()
71 }, 57 },
58 ast::SelfParam(self_param) => {
59 let def = NameClass::classify_self_param(&sema, &self_param)?.referenced_or_defined(sema.db);
60 let nav = def.try_to_nav(sema.db)?;
61 vec![nav]
62 },
72 _ => return None, 63 _ => return None,
73 } 64 }
74 }; 65 };
@@ -134,20 +125,6 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
134 } 125 }
135} 126}
136 127
137fn self_to_nav_target(self_param: ast::SelfParam, file_id: FileId) -> Option<NavigationTarget> {
138 let self_token = self_param.self_token()?;
139 Some(NavigationTarget {
140 file_id,
141 full_range: self_param.syntax().text_range(),
142 focus_range: Some(self_token.text_range()),
143 name: self_token.text().clone(),
144 kind: Some(SymbolKind::SelfParam),
145 container_name: None,
146 description: None,
147 docs: None,
148 })
149}
150
151#[derive(Debug)] 128#[derive(Debug)]
152pub(crate) enum ReferenceResult { 129pub(crate) enum ReferenceResult {
153 Exact(NavigationTarget), 130 Exact(NavigationTarget),
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 317b6f011..6022bd275 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -98,6 +98,7 @@ pub(crate) fn hover(
98 ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), 98 ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)),
99 ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime) 99 ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime)
100 .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)), 100 .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)),
101 ast::SelfParam(self_param) => NameClass::classify_self_param(&sema, &self_param).and_then(|d| d.defined(sema.db)),
101 _ => None, 102 _ => None,
102 } 103 }
103 }; 104 };
@@ -134,17 +135,14 @@ pub(crate) fn hover(
134 return None; 135 return None;
135 } 136 }
136 137
137 let node = token.ancestors().find(|n| { 138 let node = token
138 ast::Expr::can_cast(n.kind()) 139 .ancestors()
139 || ast::Pat::can_cast(n.kind()) 140 .find(|n| ast::Expr::can_cast(n.kind()) || ast::Pat::can_cast(n.kind()))?;
140 || ast::SelfParam::can_cast(n.kind())
141 })?;
142 141
143 let ty = match_ast! { 142 let ty = match_ast! {
144 match node { 143 match node {
145 ast::Expr(it) => sema.type_of_expr(&it)?, 144 ast::Expr(it) => sema.type_of_expr(&it)?,
146 ast::Pat(it) => sema.type_of_pat(&it)?, 145 ast::Pat(it) => sema.type_of_pat(&it)?,
147 ast::SelfParam(self_param) => sema.type_of_self(&self_param)?,
148 // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. 146 // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve.
149 // (e.g expanding a builtin macro). So we give up here. 147 // (e.g expanding a builtin macro). So we give up here.
150 ast::MacroCall(_it) => return None, 148 ast::MacroCall(_it) => return None,
@@ -386,7 +384,7 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
386 return tokens.max_by_key(priority); 384 return tokens.max_by_key(priority);
387 fn priority(n: &SyntaxToken) -> usize { 385 fn priority(n: &SyntaxToken) -> usize {
388 match n.kind() { 386 match n.kind() {
389 IDENT | INT_NUMBER | LIFETIME_IDENT => 3, 387 IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] => 3,
390 T!['('] | T![')'] => 2, 388 T!['('] | T![')'] => 2,
391 kind if kind.is_trivia() => 0, 389 kind if kind.is_trivia() => 0,
392 _ => 1, 390 _ => 1,
@@ -3130,6 +3128,39 @@ fn foo<T: Foo>(t: T$0){}
3130 } 3128 }
3131 3129
3132 #[test] 3130 #[test]
3131 fn test_hover_self_has_go_to_type() {
3132 check_actions(
3133 r#"
3134struct Foo;
3135impl Foo {
3136 fn foo(&self$0) {}
3137}
3138"#,
3139 expect![[r#"
3140 [
3141 GoToType(
3142 [
3143 HoverGotoTypeData {
3144 mod_path: "test::Foo",
3145 nav: NavigationTarget {
3146 file_id: FileId(
3147 0,
3148 ),
3149 full_range: 0..11,
3150 focus_range: 7..10,
3151 name: "Foo",
3152 kind: Struct,
3153 description: "struct Foo",
3154 },
3155 },
3156 ],
3157 ),
3158 ]
3159 "#]],
3160 );
3161 }
3162
3163 #[test]
3133 fn hover_displays_normalized_crate_names() { 3164 fn hover_displays_normalized_crate_names() {
3134 check( 3165 check(
3135 r#" 3166 r#"
@@ -3193,6 +3224,7 @@ impl Foo {
3193"#, 3224"#,
3194 expect![[r#" 3225 expect![[r#"
3195 *&self* 3226 *&self*
3227
3196 ```rust 3228 ```rust
3197 &Foo 3229 &Foo
3198 ``` 3230 ```
@@ -3212,6 +3244,7 @@ impl Foo {
3212"#, 3244"#,
3213 expect![[r#" 3245 expect![[r#"
3214 *self: Arc<Foo>* 3246 *self: Arc<Foo>*
3247
3215 ```rust 3248 ```rust
3216 Arc<Foo> 3249 Arc<Foo>
3217 ``` 3250 ```
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
index 05380f2a1..981467c8d 100644
--- a/crates/ide/src/join_lines.rs
+++ b/crates/ide/src/join_lines.rs
@@ -1,7 +1,7 @@
1use assists::utils::extract_trivial_expression; 1use assists::utils::extract_trivial_expression;
2use itertools::Itertools; 2use itertools::Itertools;
3use syntax::{ 3use syntax::{
4 algo::{find_covering_element, non_trivia_sibling}, 4 algo::non_trivia_sibling,
5 ast::{self, AstNode, AstToken}, 5 ast::{self, AstNode, AstToken},
6 Direction, NodeOrToken, SourceFile, 6 Direction, NodeOrToken, SourceFile,
7 SyntaxKind::{self, USE_TREE, WHITESPACE}, 7 SyntaxKind::{self, USE_TREE, WHITESPACE},
@@ -31,7 +31,7 @@ pub(crate) fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit {
31 range 31 range
32 }; 32 };
33 33
34 let node = match find_covering_element(file.syntax(), range) { 34 let node = match file.syntax().covering_element(range) {
35 NodeOrToken::Node(node) => node, 35 NodeOrToken::Node(node) => node,
36 NodeOrToken::Token(token) => token.parent(), 36 NodeOrToken::Token(token) => token.parent(),
37 }; 37 };
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 1e03832ec..f8d69382e 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -80,7 +80,7 @@ pub use crate::{
80 HlRange, 80 HlRange,
81 }, 81 },
82}; 82};
83pub use assists::{Assist, AssistConfig, AssistId, AssistKind, InsertUseConfig}; 83pub use assists::{Assist, AssistConfig, AssistId, AssistKind};
84pub use completion::{ 84pub use completion::{
85 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, 85 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, ImportEdit,
86 InsertTextFormat, 86 InsertTextFormat,
@@ -98,7 +98,7 @@ pub use ide_db::{
98 label::Label, 98 label::Label,
99 line_index::{LineCol, LineIndex}, 99 line_index::{LineCol, LineIndex},
100 search::SearchScope, 100 search::SearchScope,
101 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 101 source_change::{FileSystemEdit, SourceChange},
102 symbol_index::Query, 102 symbol_index::Query,
103 RootDatabase, 103 RootDatabase,
104}; 104};
@@ -553,7 +553,7 @@ impl Analysis {
553 let rule: ssr::SsrRule = query.parse()?; 553 let rule: ssr::SsrRule = query.parse()?;
554 let mut match_finder = ssr::MatchFinder::in_context(db, resolve_context, selections); 554 let mut match_finder = ssr::MatchFinder::in_context(db, resolve_context, selections);
555 match_finder.add_rule(rule)?; 555 match_finder.add_rule(rule)?;
556 let edits = if parse_only { Vec::new() } else { match_finder.edits() }; 556 let edits = if parse_only { Default::default() } else { match_finder.edits() };
557 Ok(SourceChange::from(edits)) 557 Ok(SourceChange::from(edits))
558 }) 558 })
559 } 559 }
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 7d4757e02..51a2f4327 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -11,6 +11,7 @@
11 11
12pub(crate) mod rename; 12pub(crate) mod rename;
13 13
14use either::Either;
14use hir::Semantics; 15use hir::Semantics;
15use ide_db::{ 16use ide_db::{
16 base_db::FileId, 17 base_db::FileId,
@@ -21,10 +22,10 @@ use ide_db::{
21use syntax::{ 22use syntax::{
22 algo::find_node_at_offset, 23 algo::find_node_at_offset,
23 ast::{self, NameOwner}, 24 ast::{self, NameOwner},
24 match_ast, AstNode, SyntaxNode, TextRange, TokenAtOffset, T, 25 AstNode, SyntaxNode, TextRange, TokenAtOffset, T,
25}; 26};
26 27
27use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo, SymbolKind}; 28use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo};
28 29
29#[derive(Debug, Clone)] 30#[derive(Debug, Clone)]
30pub struct ReferenceSearchResult { 31pub struct ReferenceSearchResult {
@@ -90,10 +91,6 @@ pub(crate) fn find_all_refs(
90 let _p = profile::span("find_all_refs"); 91 let _p = profile::span("find_all_refs");
91 let syntax = sema.parse(position.file_id).syntax().clone(); 92 let syntax = sema.parse(position.file_id).syntax().clone();
92 93
93 if let Some(res) = try_find_self_references(&syntax, position) {
94 return Some(res);
95 }
96
97 let (opt_name, search_kind) = if let Some(name) = 94 let (opt_name, search_kind) = if let Some(name) =
98 get_struct_def_name_for_struct_literal_search(&sema, &syntax, position) 95 get_struct_def_name_for_struct_literal_search(&sema, &syntax, position)
99 { 96 {
@@ -122,13 +119,16 @@ pub(crate) fn find_all_refs(
122 119
123 let mut kind = ReferenceKind::Other; 120 let mut kind = ReferenceKind::Other;
124 if let Definition::Local(local) = def { 121 if let Definition::Local(local) = def {
125 if let either::Either::Left(pat) = local.source(sema.db).value { 122 match local.source(sema.db).value {
126 if matches!( 123 Either::Left(pat) => {
127 pat.syntax().parent().and_then(ast::RecordPatField::cast), 124 if matches!(
128 Some(pat_field) if pat_field.name_ref().is_none() 125 pat.syntax().parent().and_then(ast::RecordPatField::cast),
129 ) { 126 Some(pat_field) if pat_field.name_ref().is_none()
130 kind = ReferenceKind::FieldShorthandForLocal; 127 ) {
128 kind = ReferenceKind::FieldShorthandForLocal;
129 }
131 } 130 }
131 Either::Right(_) => kind = ReferenceKind::SelfParam,
132 } 132 }
133 } else if matches!( 133 } else if matches!(
134 def, 134 def,
@@ -251,79 +251,6 @@ fn get_enum_def_name_for_struct_literal_search(
251 None 251 None
252} 252}
253 253
254fn try_find_self_references(
255 syntax: &SyntaxNode,
256 position: FilePosition,
257) -> Option<RangeInfo<ReferenceSearchResult>> {
258 let FilePosition { file_id, offset } = position;
259 let self_token = syntax.token_at_offset(offset).find(|t| t.kind() == T![self])?;
260 let parent = self_token.parent();
261 match_ast! {
262 match parent {
263 ast::SelfParam(it) => (),
264 ast::PathSegment(segment) => {
265 segment.self_token()?;
266 let path = segment.parent_path();
267 if path.qualifier().is_some() && !ast::PathExpr::can_cast(path.syntax().parent()?.kind()) {
268 return None;
269 }
270 },
271 _ => return None,
272 }
273 };
274 let function = parent.ancestors().find_map(ast::Fn::cast)?;
275 let self_param = function.param_list()?.self_param()?;
276 let param_self_token = self_param.self_token()?;
277
278 let declaration = Declaration {
279 nav: NavigationTarget {
280 file_id,
281 full_range: self_param.syntax().text_range(),
282 focus_range: Some(param_self_token.text_range()),
283 name: param_self_token.text().clone(),
284 kind: Some(SymbolKind::SelfParam),
285 container_name: None,
286 description: None,
287 docs: None,
288 },
289 kind: ReferenceKind::SelfKw,
290 access: Some(if self_param.mut_token().is_some() {
291 ReferenceAccess::Write
292 } else {
293 ReferenceAccess::Read
294 }),
295 };
296 let refs = function
297 .body()
298 .map(|body| {
299 body.syntax()
300 .descendants()
301 .filter_map(ast::PathExpr::cast)
302 .filter_map(|expr| {
303 let path = expr.path()?;
304 if path.qualifier().is_none() {
305 path.segment()?.self_token()
306 } else {
307 None
308 }
309 })
310 .map(|token| FileReference {
311 range: token.text_range(),
312 kind: ReferenceKind::SelfKw,
313 access: declaration.access, // FIXME: properly check access kind here instead of copying it from the declaration
314 })
315 .collect()
316 })
317 .unwrap_or_default();
318 let mut references = UsageSearchResult::default();
319 references.references.insert(file_id, refs);
320
321 Some(RangeInfo::new(
322 param_self_token.text_range(),
323 ReferenceSearchResult { declaration, references },
324 ))
325}
326
327#[cfg(test)] 254#[cfg(test)]
328mod tests { 255mod tests {
329 use expect_test::{expect, Expect}; 256 use expect_test::{expect, Expect};
@@ -512,7 +439,7 @@ fn main() {
512 i = 5; 439 i = 5;
513}"#, 440}"#,
514 expect![[r#" 441 expect![[r#"
515 i Local FileId(0) 24..25 Other Write 442 i Local FileId(0) 20..25 24..25 Other Write
516 443
517 FileId(0) 50..51 Other Write 444 FileId(0) 50..51 Other Write
518 FileId(0) 54..55 Other Read 445 FileId(0) 54..55 Other Read
@@ -536,7 +463,7 @@ fn bar() {
536} 463}
537"#, 464"#,
538 expect![[r#" 465 expect![[r#"
539 spam Local FileId(0) 19..23 Other 466 spam Local FileId(0) 19..23 19..23 Other
540 467
541 FileId(0) 34..38 Other Read 468 FileId(0) 34..38 Other Read
542 FileId(0) 41..45 Other Read 469 FileId(0) 41..45 Other Read
@@ -551,7 +478,7 @@ fn bar() {
551fn foo(i : u32) -> u32 { i$0 } 478fn foo(i : u32) -> u32 { i$0 }
552"#, 479"#,
553 expect![[r#" 480 expect![[r#"
554 i ValueParam FileId(0) 7..8 Other 481 i ValueParam FileId(0) 7..8 7..8 Other
555 482
556 FileId(0) 25..26 Other Read 483 FileId(0) 25..26 Other Read
557 "#]], 484 "#]],
@@ -565,7 +492,7 @@ fn foo(i : u32) -> u32 { i$0 }
565fn foo(i$0 : u32) -> u32 { i } 492fn foo(i$0 : u32) -> u32 { i }
566"#, 493"#,
567 expect![[r#" 494 expect![[r#"
568 i ValueParam FileId(0) 7..8 Other 495 i ValueParam FileId(0) 7..8 7..8 Other
569 496
570 FileId(0) 25..26 Other Read 497 FileId(0) 25..26 Other Read
571 "#]], 498 "#]],
@@ -813,7 +740,7 @@ fn foo() {
813} 740}
814"#, 741"#,
815 expect![[r#" 742 expect![[r#"
816 i Local FileId(0) 23..24 Other Write 743 i Local FileId(0) 19..24 23..24 Other Write
817 744
818 FileId(0) 34..35 Other Write 745 FileId(0) 34..35 Other Write
819 FileId(0) 38..39 Other Read 746 FileId(0) 38..39 Other Read
@@ -853,7 +780,7 @@ fn foo() {
853} 780}
854"#, 781"#,
855 expect![[r#" 782 expect![[r#"
856 i Local FileId(0) 19..20 Other 783 i Local FileId(0) 19..20 19..20 Other
857 784
858 FileId(0) 26..27 Other Write 785 FileId(0) 26..27 Other Write
859 "#]], 786 "#]],
@@ -995,10 +922,10 @@ impl Foo {
995} 922}
996"#, 923"#,
997 expect![[r#" 924 expect![[r#"
998 self SelfParam FileId(0) 47..51 47..51 SelfKw Read 925 self SelfParam FileId(0) 47..51 47..51 SelfParam
999 926
1000 FileId(0) 71..75 SelfKw Read 927 FileId(0) 71..75 Other Read
1001 FileId(0) 152..156 SelfKw Read 928 FileId(0) 152..156 Other Read
1002 "#]], 929 "#]],
1003 ); 930 );
1004 } 931 }
@@ -1105,7 +1032,7 @@ fn main() {
1105} 1032}
1106"#, 1033"#,
1107 expect![[r#" 1034 expect![[r#"
1108 a Local FileId(0) 59..60 Other 1035 a Local FileId(0) 59..60 59..60 Other
1109 1036
1110 FileId(0) 80..81 Other Read 1037 FileId(0) 80..81 Other Read
1111 "#]], 1038 "#]],
@@ -1123,7 +1050,7 @@ fn main() {
1123} 1050}
1124"#, 1051"#,
1125 expect![[r#" 1052 expect![[r#"
1126 a Local FileId(0) 59..60 Other 1053 a Local FileId(0) 59..60 59..60 Other
1127 1054
1128 FileId(0) 80..81 Other Read 1055 FileId(0) 80..81 Other Read
1129 "#]], 1056 "#]],
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index c3ae568c2..9ac4af026 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -21,7 +21,7 @@ use text_edit::TextEdit;
21 21
22use crate::{ 22use crate::{
23 FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange, 23 FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange,
24 SourceFileEdit, TextRange, TextSize, 24 TextRange, TextSize,
25}; 25};
26 26
27type RenameResult<T> = Result<T, RenameError>; 27type RenameResult<T> = Result<T, RenameError>;
@@ -58,7 +58,7 @@ pub(crate) fn prepare_rename(
58 rename_self_to_param(&sema, position, self_token, "dummy") 58 rename_self_to_param(&sema, position, self_token, "dummy")
59 } else { 59 } else {
60 let RangeInfo { range, .. } = find_all_refs(&sema, position)?; 60 let RangeInfo { range, .. } = find_all_refs(&sema, position)?;
61 Ok(RangeInfo::new(range, SourceChange::from(vec![]))) 61 Ok(RangeInfo::new(range, SourceChange::default()))
62 } 62 }
63 .map(|info| RangeInfo::new(info.range, ())) 63 .map(|info| RangeInfo::new(info.range, ()))
64} 64}
@@ -176,7 +176,7 @@ fn source_edit_from_references(
176 file_id: FileId, 176 file_id: FileId,
177 references: &[FileReference], 177 references: &[FileReference],
178 new_name: &str, 178 new_name: &str,
179) -> SourceFileEdit { 179) -> (FileId, TextEdit) {
180 let mut edit = TextEdit::builder(); 180 let mut edit = TextEdit::builder();
181 for reference in references { 181 for reference in references {
182 let mut replacement_text = String::new(); 182 let mut replacement_text = String::new();
@@ -209,8 +209,7 @@ fn source_edit_from_references(
209 }; 209 };
210 edit.replace(range, replacement_text); 210 edit.replace(range, replacement_text);
211 } 211 }
212 212 (file_id, edit.finish())
213 SourceFileEdit { file_id, edit: edit.finish() }
214} 213}
215 214
216fn edit_text_range_for_record_field_expr_or_pat( 215fn edit_text_range_for_record_field_expr_or_pat(
@@ -250,8 +249,8 @@ fn rename_mod(
250 if IdentifierKind::Ident != check_identifier(new_name)? { 249 if IdentifierKind::Ident != check_identifier(new_name)? {
251 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); 250 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
252 } 251 }
253 let mut source_file_edits = Vec::new(); 252
254 let mut file_system_edits = Vec::new(); 253 let mut source_change = SourceChange::default();
255 254
256 let src = module.definition_source(sema.db); 255 let src = module.definition_source(sema.db);
257 let file_id = src.file_id.original_file(sema.db); 256 let file_id = src.file_id.original_file(sema.db);
@@ -265,7 +264,7 @@ fn rename_mod(
265 }; 264 };
266 let dst = AnchoredPathBuf { anchor: file_id, path }; 265 let dst = AnchoredPathBuf { anchor: file_id, path };
267 let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; 266 let move_file = FileSystemEdit::MoveFile { src: file_id, dst };
268 file_system_edits.push(move_file); 267 source_change.push_file_system_edit(move_file);
269 } 268 }
270 ModuleSource::Module(..) => {} 269 ModuleSource::Module(..) => {}
271 } 270 }
@@ -273,20 +272,19 @@ fn rename_mod(
273 if let Some(src) = module.declaration_source(sema.db) { 272 if let Some(src) = module.declaration_source(sema.db) {
274 let file_id = src.file_id.original_file(sema.db); 273 let file_id = src.file_id.original_file(sema.db);
275 let name = src.value.name().unwrap(); 274 let name = src.value.name().unwrap();
276 let edit = SourceFileEdit { 275 source_change.insert_source_edit(
277 file_id, 276 file_id,
278 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()), 277 TextEdit::replace(name.syntax().text_range(), new_name.into()),
279 }; 278 );
280 source_file_edits.push(edit);
281 } 279 }
282 280
283 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; 281 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
284 let ref_edits = refs.references().iter().map(|(&file_id, references)| { 282 let ref_edits = refs.references().iter().map(|(&file_id, references)| {
285 source_edit_from_references(sema, file_id, references, new_name) 283 source_edit_from_references(sema, file_id, references, new_name)
286 }); 284 });
287 source_file_edits.extend(ref_edits); 285 source_change.extend(ref_edits);
288 286
289 Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) 287 Ok(RangeInfo::new(range, source_change))
290} 288}
291 289
292fn rename_to_self( 290fn rename_to_self(
@@ -335,20 +333,16 @@ fn rename_to_self(
335 333
336 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; 334 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
337 335
338 let mut edits = refs 336 let mut source_change = SourceChange::default();
339 .references() 337 source_change.extend(refs.references().iter().map(|(&file_id, references)| {
340 .iter() 338 source_edit_from_references(sema, file_id, references, "self")
341 .map(|(&file_id, references)| { 339 }));
342 source_edit_from_references(sema, file_id, references, "self") 340 source_change.insert_source_edit(
343 }) 341 position.file_id,
344 .collect::<Vec<_>>(); 342 TextEdit::replace(param_range, String::from(self_param)),
345 343 );
346 edits.push(SourceFileEdit {
347 file_id: position.file_id,
348 edit: TextEdit::replace(param_range, String::from(self_param)),
349 });
350 344
351 Ok(RangeInfo::new(range, SourceChange::from(edits))) 345 Ok(RangeInfo::new(range, source_change))
352} 346}
353 347
354fn text_edit_from_self_param( 348fn text_edit_from_self_param(
@@ -402,7 +396,7 @@ fn rename_self_to_param(
402 .ok_or_else(|| format_err!("No surrounding method declaration found"))?; 396 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
403 let search_range = fn_def.syntax().text_range(); 397 let search_range = fn_def.syntax().text_range();
404 398
405 let mut edits: Vec<SourceFileEdit> = vec![]; 399 let mut source_change = SourceChange::default();
406 400
407 for (idx, _) in text.match_indices("self") { 401 for (idx, _) in text.match_indices("self") {
408 let offset: TextSize = idx.try_into().unwrap(); 402 let offset: TextSize = idx.try_into().unwrap();
@@ -416,18 +410,18 @@ fn rename_self_to_param(
416 } else { 410 } else {
417 TextEdit::replace(usage.text_range(), String::from(new_name)) 411 TextEdit::replace(usage.text_range(), String::from(new_name))
418 }; 412 };
419 edits.push(SourceFileEdit { file_id: position.file_id, edit }); 413 source_change.insert_source_edit(position.file_id, edit);
420 } 414 }
421 } 415 }
422 416
423 if edits.len() > 1 && ident_kind == IdentifierKind::Underscore { 417 if source_change.source_file_edits.len() > 1 && ident_kind == IdentifierKind::Underscore {
424 bail!("Cannot rename reference to `_` as it is being referenced multiple times"); 418 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
425 } 419 }
426 420
427 let range = ast::SelfParam::cast(self_token.parent()) 421 let range = ast::SelfParam::cast(self_token.parent())
428 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 422 .map_or(self_token.text_range(), |p| p.syntax().text_range());
429 423
430 Ok(RangeInfo::new(range, SourceChange::from(edits))) 424 Ok(RangeInfo::new(range, source_change))
431} 425}
432 426
433fn rename_reference( 427fn rename_reference(
@@ -450,7 +444,7 @@ fn rename_reference(
450 mark::hit!(rename_not_an_ident_ref); 444 mark::hit!(rename_not_an_ident_ref);
451 bail!("Invalid name `{}`: not an identifier", new_name) 445 bail!("Invalid name `{}`: not an identifier", new_name)
452 } 446 }
453 (IdentifierKind::ToSelf, ReferenceKind::SelfKw) => { 447 (IdentifierKind::ToSelf, ReferenceKind::SelfParam) => {
454 unreachable!("rename_self_to_param should've been called instead") 448 unreachable!("rename_self_to_param should've been called instead")
455 } 449 }
456 (IdentifierKind::ToSelf, _) => { 450 (IdentifierKind::ToSelf, _) => {
@@ -464,14 +458,12 @@ fn rename_reference(
464 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), 458 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
465 } 459 }
466 460
467 let edit = refs 461 let mut source_change = SourceChange::default();
468 .into_iter() 462 source_change.extend(refs.into_iter().map(|(file_id, references)| {
469 .map(|(file_id, references)| { 463 source_edit_from_references(sema, file_id, &references, new_name)
470 source_edit_from_references(sema, file_id, &references, new_name) 464 }));
471 })
472 .collect::<Vec<_>>();
473 465
474 Ok(RangeInfo::new(range, SourceChange::from(edit))) 466 Ok(RangeInfo::new(range, source_change))
475} 467}
476 468
477#[cfg(test)] 469#[cfg(test)]
@@ -494,8 +486,8 @@ mod tests {
494 let mut text_edit_builder = TextEdit::builder(); 486 let mut text_edit_builder = TextEdit::builder();
495 let mut file_id: Option<FileId> = None; 487 let mut file_id: Option<FileId> = None;
496 for edit in source_change.info.source_file_edits { 488 for edit in source_change.info.source_file_edits {
497 file_id = Some(edit.file_id); 489 file_id = Some(edit.0);
498 for indel in edit.edit.into_iter() { 490 for indel in edit.1.into_iter() {
499 text_edit_builder.replace(indel.delete, indel.insert); 491 text_edit_builder.replace(indel.delete, indel.insert);
500 } 492 }
501 } 493 }
@@ -895,21 +887,18 @@ mod foo$0;
895 RangeInfo { 887 RangeInfo {
896 range: 4..7, 888 range: 4..7,
897 info: SourceChange { 889 info: SourceChange {
898 source_file_edits: [ 890 source_file_edits: {
899 SourceFileEdit { 891 FileId(
900 file_id: FileId( 892 1,
901 1, 893 ): TextEdit {
902 ), 894 indels: [
903 edit: TextEdit { 895 Indel {
904 indels: [ 896 insert: "foo2",
905 Indel { 897 delete: 4..7,
906 insert: "foo2", 898 },
907 delete: 4..7, 899 ],
908 },
909 ],
910 },
911 }, 900 },
912 ], 901 },
913 file_system_edits: [ 902 file_system_edits: [
914 MoveFile { 903 MoveFile {
915 src: FileId( 904 src: FileId(
@@ -950,34 +939,28 @@ use crate::foo$0::FooContent;
950 RangeInfo { 939 RangeInfo {
951 range: 11..14, 940 range: 11..14,
952 info: SourceChange { 941 info: SourceChange {
953 source_file_edits: [ 942 source_file_edits: {
954 SourceFileEdit { 943 FileId(
955 file_id: FileId( 944 0,
956 0, 945 ): TextEdit {
957 ), 946 indels: [
958 edit: TextEdit { 947 Indel {
959 indels: [ 948 insert: "quux",
960 Indel { 949 delete: 8..11,
961 insert: "quux", 950 },
962 delete: 8..11, 951 ],
963 },
964 ],
965 },
966 }, 952 },
967 SourceFileEdit { 953 FileId(
968 file_id: FileId( 954 2,
969 2, 955 ): TextEdit {
970 ), 956 indels: [
971 edit: TextEdit { 957 Indel {
972 indels: [ 958 insert: "quux",
973 Indel { 959 delete: 11..14,
974 insert: "quux", 960 },
975 delete: 11..14, 961 ],
976 },
977 ],
978 },
979 }, 962 },
980 ], 963 },
981 file_system_edits: [ 964 file_system_edits: [
982 MoveFile { 965 MoveFile {
983 src: FileId( 966 src: FileId(
@@ -1012,21 +995,18 @@ mod fo$0o;
1012 RangeInfo { 995 RangeInfo {
1013 range: 4..7, 996 range: 4..7,
1014 info: SourceChange { 997 info: SourceChange {
1015 source_file_edits: [ 998 source_file_edits: {
1016 SourceFileEdit { 999 FileId(
1017 file_id: FileId( 1000 0,
1018 0, 1001 ): TextEdit {
1019 ), 1002 indels: [
1020 edit: TextEdit { 1003 Indel {
1021 indels: [ 1004 insert: "foo2",
1022 Indel { 1005 delete: 4..7,
1023 insert: "foo2", 1006 },
1024 delete: 4..7, 1007 ],
1025 },
1026 ],
1027 },
1028 }, 1008 },
1029 ], 1009 },
1030 file_system_edits: [ 1010 file_system_edits: [
1031 MoveFile { 1011 MoveFile {
1032 src: FileId( 1012 src: FileId(
@@ -1062,21 +1042,18 @@ mod outer { mod fo$0o; }
1062 RangeInfo { 1042 RangeInfo {
1063 range: 16..19, 1043 range: 16..19,
1064 info: SourceChange { 1044 info: SourceChange {
1065 source_file_edits: [ 1045 source_file_edits: {
1066 SourceFileEdit { 1046 FileId(
1067 file_id: FileId( 1047 0,
1068 0, 1048 ): TextEdit {
1069 ), 1049 indels: [
1070 edit: TextEdit { 1050 Indel {
1071 indels: [ 1051 insert: "bar",
1072 Indel { 1052 delete: 16..19,
1073 insert: "bar", 1053 },
1074 delete: 16..19, 1054 ],
1075 },
1076 ],
1077 },
1078 }, 1055 },
1079 ], 1056 },
1080 file_system_edits: [ 1057 file_system_edits: [
1081 MoveFile { 1058 MoveFile {
1082 src: FileId( 1059 src: FileId(
@@ -1135,34 +1112,28 @@ pub mod foo$0;
1135 RangeInfo { 1112 RangeInfo {
1136 range: 8..11, 1113 range: 8..11,
1137 info: SourceChange { 1114 info: SourceChange {
1138 source_file_edits: [ 1115 source_file_edits: {
1139 SourceFileEdit { 1116 FileId(
1140 file_id: FileId( 1117 0,
1141 1, 1118 ): TextEdit {
1142 ), 1119 indels: [
1143 edit: TextEdit { 1120 Indel {
1144 indels: [ 1121 insert: "foo2",
1145 Indel { 1122 delete: 27..30,
1146 insert: "foo2", 1123 },
1147 delete: 8..11, 1124 ],
1148 },
1149 ],
1150 },
1151 }, 1125 },
1152 SourceFileEdit { 1126 FileId(
1153 file_id: FileId( 1127 1,
1154 0, 1128 ): TextEdit {