diff options
Diffstat (limited to 'crates/ide')
-rw-r--r-- | crates/ide/Cargo.toml | 3 | ||||
-rw-r--r-- | crates/ide/src/diagnostics.rs | 67 | ||||
-rw-r--r-- | crates/ide/src/display/short_label.rs | 6 | ||||
-rw-r--r-- | crates/ide/src/folding_ranges.rs | 55 | ||||
-rw-r--r-- | crates/ide/src/hover.rs | 235 | ||||
-rw-r--r-- | crates/ide/src/join_lines.rs | 72 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 12 | ||||
-rw-r--r-- | crates/ide/src/matching_brace.rs | 5 | ||||
-rw-r--r-- | crates/ide/src/parent_module.rs | 6 | ||||
-rw-r--r-- | crates/ide/src/references.rs | 9 | ||||
-rw-r--r-- | crates/ide/src/references/rename.rs | 478 | ||||
-rw-r--r-- | crates/ide/src/runnables.rs | 10 | ||||
-rw-r--r-- | crates/ide/src/ssr.rs | 259 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/highlight.rs | 5 | ||||
-rw-r--r-- | crates/ide/src/typing/on_enter.rs | 8 |
15 files changed, 922 insertions, 308 deletions
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml index f6aaaeda4..107bd8432 100644 --- a/crates/ide/Cargo.toml +++ b/crates/ide/Cargo.toml | |||
@@ -10,6 +10,7 @@ edition = "2018" | |||
10 | doctest = false | 10 | doctest = false |
11 | 11 | ||
12 | [dependencies] | 12 | [dependencies] |
13 | cov-mark = "1.1" | ||
13 | either = "1.5.3" | 14 | either = "1.5.3" |
14 | indexmap = "1.4.0" | 15 | indexmap = "1.4.0" |
15 | itertools = "0.10.0" | 16 | itertools = "0.10.0" |
@@ -26,7 +27,6 @@ text_edit = { path = "../text_edit", version = "0.0.0" } | |||
26 | ide_db = { path = "../ide_db", version = "0.0.0" } | 27 | ide_db = { path = "../ide_db", version = "0.0.0" } |
27 | cfg = { path = "../cfg", version = "0.0.0" } | 28 | cfg = { path = "../cfg", version = "0.0.0" } |
28 | profile = { path = "../profile", version = "0.0.0" } | 29 | profile = { path = "../profile", version = "0.0.0" } |
29 | test_utils = { path = "../test_utils", version = "0.0.0" } | ||
30 | ide_assists = { path = "../ide_assists", version = "0.0.0" } | 30 | ide_assists = { path = "../ide_assists", version = "0.0.0" } |
31 | ide_ssr = { path = "../ide_ssr", version = "0.0.0" } | 31 | ide_ssr = { path = "../ide_ssr", version = "0.0.0" } |
32 | ide_completion = { path = "../ide_completion", version = "0.0.0" } | 32 | ide_completion = { path = "../ide_completion", version = "0.0.0" } |
@@ -36,4 +36,5 @@ ide_completion = { path = "../ide_completion", version = "0.0.0" } | |||
36 | hir = { path = "../hir", version = "0.0.0" } | 36 | hir = { path = "../hir", version = "0.0.0" } |
37 | 37 | ||
38 | [dev-dependencies] | 38 | [dev-dependencies] |
39 | test_utils = { path = "../test_utils" } | ||
39 | expect-test = "1.1" | 40 | expect-test = "1.1" |
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 8607139ba..fe32f39b6 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -10,15 +10,16 @@ mod field_shorthand; | |||
10 | use std::cell::RefCell; | 10 | use std::cell::RefCell; |
11 | 11 | ||
12 | use hir::{ | 12 | use hir::{ |
13 | db::AstDatabase, | ||
13 | diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, | 14 | diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, |
14 | Semantics, | 15 | InFile, Semantics, |
15 | }; | 16 | }; |
16 | use ide_db::{base_db::SourceDatabase, RootDatabase}; | 17 | use ide_db::{base_db::SourceDatabase, RootDatabase}; |
17 | use itertools::Itertools; | 18 | use itertools::Itertools; |
18 | use rustc_hash::FxHashSet; | 19 | use rustc_hash::FxHashSet; |
19 | use syntax::{ | 20 | use syntax::{ |
20 | ast::{self, AstNode}, | 21 | ast::{self, AstNode}, |
21 | SyntaxNode, TextRange, | 22 | SyntaxNode, SyntaxNodePtr, TextRange, |
22 | }; | 23 | }; |
23 | use text_edit::TextEdit; | 24 | use text_edit::TextEdit; |
24 | 25 | ||
@@ -147,20 +148,38 @@ pub(crate) fn diagnostics( | |||
147 | 148 | ||
148 | // Override severity and mark as unused. | 149 | // Override severity and mark as unused. |
149 | res.borrow_mut().push( | 150 | res.borrow_mut().push( |
150 | Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message()) | 151 | Diagnostic::hint( |
151 | .with_unused(true) | 152 | sema.diagnostics_display_range(d.display_source()).range, |
152 | .with_code(Some(d.code())), | 153 | d.message(), |
154 | ) | ||
155 | .with_unused(true) | ||
156 | .with_code(Some(d.code())), | ||
153 | ); | 157 | ); |
154 | }) | 158 | }) |
155 | .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| { | 159 | .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| { |
156 | // Use more accurate position if available. | 160 | // Use more accurate position if available. |
157 | let display_range = | 161 | let display_range = d |
158 | d.precise_location.unwrap_or_else(|| sema.diagnostics_display_range(d).range); | 162 | .precise_location |
163 | .unwrap_or_else(|| sema.diagnostics_display_range(d.display_source()).range); | ||
159 | 164 | ||
160 | // FIXME: it would be nice to tell the user whether proc macros are currently disabled | 165 | // FIXME: it would be nice to tell the user whether proc macros are currently disabled |
161 | res.borrow_mut() | 166 | res.borrow_mut() |
162 | .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code()))); | 167 | .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code()))); |
163 | }) | 168 | }) |
169 | .on::<hir::diagnostics::UnresolvedMacroCall, _>(|d| { | ||
170 | let last_path_segment = sema.db.parse_or_expand(d.file).and_then(|root| { | ||
171 | d.node | ||
172 | .to_node(&root) | ||
173 | .path() | ||
174 | .and_then(|it| it.segment()) | ||
175 | .and_then(|it| it.name_ref()) | ||
176 | .map(|it| InFile::new(d.file, SyntaxNodePtr::new(it.syntax()))) | ||
177 | }); | ||
178 | let diagnostics = last_path_segment.unwrap_or_else(|| d.display_source()); | ||
179 | let display_range = sema.diagnostics_display_range(diagnostics).range; | ||
180 | res.borrow_mut() | ||
181 | .push(Diagnostic::error(display_range, d.message()).with_code(Some(d.code()))); | ||
182 | }) | ||
164 | // Only collect experimental diagnostics when they're enabled. | 183 | // Only collect experimental diagnostics when they're enabled. |
165 | .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) | 184 | .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) |
166 | .filter(|diag| !config.disabled.contains(diag.code().as_str())); | 185 | .filter(|diag| !config.disabled.contains(diag.code().as_str())); |
@@ -170,8 +189,11 @@ pub(crate) fn diagnostics( | |||
170 | // Diagnostics not handled above get no fix and default treatment. | 189 | // Diagnostics not handled above get no fix and default treatment. |
171 | .build(|d| { | 190 | .build(|d| { |
172 | res.borrow_mut().push( | 191 | res.borrow_mut().push( |
173 | Diagnostic::error(sema.diagnostics_display_range(d).range, d.message()) | 192 | Diagnostic::error( |
174 | .with_code(Some(d.code())), | 193 | sema.diagnostics_display_range(d.display_source()).range, |
194 | d.message(), | ||
195 | ) | ||
196 | .with_code(Some(d.code())), | ||
175 | ); | 197 | ); |
176 | }); | 198 | }); |
177 | 199 | ||
@@ -183,13 +205,13 @@ pub(crate) fn diagnostics( | |||
183 | } | 205 | } |
184 | 206 | ||
185 | fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { | 207 | fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { |
186 | Diagnostic::error(sema.diagnostics_display_range(d).range, d.message()) | 208 | Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) |
187 | .with_fix(d.fix(&sema)) | 209 | .with_fix(d.fix(&sema)) |
188 | .with_code(Some(d.code())) | 210 | .with_code(Some(d.code())) |
189 | } | 211 | } |
190 | 212 | ||
191 | fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { | 213 | fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { |
192 | Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message()) | 214 | Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) |
193 | .with_fix(d.fix(&sema)) | 215 | .with_fix(d.fix(&sema)) |
194 | .with_code(Some(d.code())) | 216 | .with_code(Some(d.code())) |
195 | } | 217 | } |
@@ -646,6 +668,29 @@ fn test_fn() { | |||
646 | } | 668 | } |
647 | 669 | ||
648 | #[test] | 670 | #[test] |
671 | fn test_unresolved_macro_range() { | ||
672 | check_expect( | ||
673 | r#"foo::bar!(92);"#, | ||
674 | expect![[r#" | ||
675 | [ | ||
676 | Diagnostic { | ||
677 | message: "unresolved macro call", | ||
678 | range: 5..8, | ||
679 | severity: Error, | ||
680 | fix: None, | ||
681 | unused: false, | ||
682 | code: Some( | ||
683 | DiagnosticCode( | ||
684 | "unresolved-macro-call", | ||
685 | ), | ||
686 | ), | ||
687 | }, | ||
688 | ] | ||
689 | "#]], | ||
690 | ); | ||
691 | } | ||
692 | |||
693 | #[test] | ||
649 | fn range_mapping_out_of_macros() { | 694 | fn range_mapping_out_of_macros() { |
650 | // FIXME: this is very wrong, but somewhat tricky to fix. | 695 | // FIXME: this is very wrong, but somewhat tricky to fix. |
651 | check_fix( | 696 | check_fix( |
diff --git a/crates/ide/src/display/short_label.rs b/crates/ide/src/display/short_label.rs index 84b8883de..2df9266b4 100644 --- a/crates/ide/src/display/short_label.rs +++ b/crates/ide/src/display/short_label.rs | |||
@@ -71,11 +71,7 @@ impl ShortLabel for ast::TypeAlias { | |||
71 | 71 | ||
72 | impl ShortLabel for ast::Const { | 72 | impl ShortLabel for ast::Const { |
73 | fn short_label(&self) -> Option<String> { | 73 | fn short_label(&self) -> Option<String> { |
74 | let mut new_buf = short_label_from_ty(self, self.ty(), "const ")?; | 74 | short_label_from_ty(self, self.ty(), "const ") |
75 | if let Some(expr) = self.body() { | ||
76 | format_to!(new_buf, " = {}", expr.syntax()); | ||
77 | } | ||
78 | Some(new_buf) | ||
79 | } | 75 | } |
80 | } | 76 | } |
81 | 77 | ||
diff --git a/crates/ide/src/folding_ranges.rs b/crates/ide/src/folding_ranges.rs index 45170dd29..4b1b24562 100644 --- a/crates/ide/src/folding_ranges.rs +++ b/crates/ide/src/folding_ranges.rs | |||
@@ -6,7 +6,7 @@ use syntax::{ | |||
6 | ast::{self, AstNode, AstToken, VisibilityOwner}, | 6 | ast::{self, AstNode, AstToken, VisibilityOwner}, |
7 | Direction, NodeOrToken, SourceFile, | 7 | Direction, NodeOrToken, SourceFile, |
8 | SyntaxKind::{self, *}, | 8 | SyntaxKind::{self, *}, |
9 | SyntaxNode, TextRange, | 9 | SyntaxNode, TextRange, TextSize, |
10 | }; | 10 | }; |
11 | 11 | ||
12 | #[derive(Debug, PartialEq, Eq)] | 12 | #[derive(Debug, PartialEq, Eq)] |
@@ -16,6 +16,7 @@ pub enum FoldKind { | |||
16 | Mods, | 16 | Mods, |
17 | Block, | 17 | Block, |
18 | ArgList, | 18 | ArgList, |
19 | Region, | ||
19 | } | 20 | } |
20 | 21 | ||
21 | #[derive(Debug)] | 22 | #[derive(Debug)] |
@@ -29,6 +30,8 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> { | |||
29 | let mut visited_comments = FxHashSet::default(); | 30 | let mut visited_comments = FxHashSet::default(); |
30 | let mut visited_imports = FxHashSet::default(); | 31 | let mut visited_imports = FxHashSet::default(); |
31 | let mut visited_mods = FxHashSet::default(); | 32 | let mut visited_mods = FxHashSet::default(); |
33 | // regions can be nested, here is a LIFO buffer | ||
34 | let mut regions_starts: Vec<TextSize> = vec![]; | ||
32 | 35 | ||
33 | for element in file.syntax().descendants_with_tokens() { | 36 | for element in file.syntax().descendants_with_tokens() { |
34 | // Fold items that span multiple lines | 37 | // Fold items that span multiple lines |
@@ -48,10 +51,25 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> { | |||
48 | // Fold groups of comments | 51 | // Fold groups of comments |
49 | if let Some(comment) = ast::Comment::cast(token) { | 52 | if let Some(comment) = ast::Comment::cast(token) { |
50 | if !visited_comments.contains(&comment) { | 53 | if !visited_comments.contains(&comment) { |
51 | if let Some(range) = | 54 | // regions are not real comments |
52 | contiguous_range_for_comment(comment, &mut visited_comments) | 55 | if comment.text().trim().starts_with("// region:") { |
53 | { | 56 | regions_starts.push(comment.syntax().text_range().start()); |
54 | res.push(Fold { range, kind: FoldKind::Comment }) | 57 | } else if comment.text().trim().starts_with("// endregion") { |
58 | if let Some(region) = regions_starts.pop() { | ||
59 | res.push(Fold { | ||
60 | range: TextRange::new( | ||
61 | region, | ||
62 | comment.syntax().text_range().end(), | ||
63 | ), | ||
64 | kind: FoldKind::Region, | ||
65 | }) | ||
66 | } | ||
67 | } else { | ||
68 | if let Some(range) = | ||
69 | contiguous_range_for_comment(comment, &mut visited_comments) | ||
70 | { | ||
71 | res.push(Fold { range, kind: FoldKind::Comment }) | ||
72 | } | ||
55 | } | 73 | } |
56 | } | 74 | } |
57 | } | 75 | } |
@@ -175,9 +193,16 @@ fn contiguous_range_for_comment( | |||
175 | } | 193 | } |
176 | if let Some(c) = ast::Comment::cast(token) { | 194 | if let Some(c) = ast::Comment::cast(token) { |
177 | if c.kind() == group_kind { | 195 | if c.kind() == group_kind { |
178 | visited.insert(c.clone()); | 196 | // regions are not real comments |
179 | last = c; | 197 | if c.text().trim().starts_with("// region:") |
180 | continue; | 198 | || c.text().trim().starts_with("// endregion") |
199 | { | ||
200 | break; | ||
201 | } else { | ||
202 | visited.insert(c.clone()); | ||
203 | last = c; | ||
204 | continue; | ||
205 | } | ||
181 | } | 206 | } |
182 | } | 207 | } |
183 | // The comment group ends because either: | 208 | // The comment group ends because either: |
@@ -224,6 +249,7 @@ mod tests { | |||
224 | FoldKind::Mods => "mods", | 249 | FoldKind::Mods => "mods", |
225 | FoldKind::Block => "block", | 250 | FoldKind::Block => "block", |
226 | FoldKind::ArgList => "arglist", | 251 | FoldKind::ArgList => "arglist", |
252 | FoldKind::Region => "region", | ||
227 | }; | 253 | }; |
228 | assert_eq!(kind, &attr.unwrap()); | 254 | assert_eq!(kind, &attr.unwrap()); |
229 | } | 255 | } |
@@ -418,4 +444,17 @@ fn foo<fold arglist>( | |||
418 | "#, | 444 | "#, |
419 | ) | 445 | ) |
420 | } | 446 | } |
447 | |||
448 | #[test] | ||
449 | fn fold_region() { | ||
450 | check( | ||
451 | r#" | ||
452 | // 1. some normal comment | ||
453 | <fold region>// region: test | ||
454 | // 2. some normal comment | ||
455 | calling_function(x,y); | ||
456 | // endregion: test</fold> | ||
457 | "#, | ||
458 | ) | ||
459 | } | ||
421 | } | 460 | } |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 9a605b09d..ea45086ce 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -1,3 +1,4 @@ | |||
1 | use either::Either; | ||
1 | use hir::{ | 2 | use hir::{ |
2 | Adt, AsAssocItem, AssocItemContainer, FieldSource, GenericParam, HasAttrs, HasSource, | 3 | Adt, AsAssocItem, AssocItemContainer, FieldSource, GenericParam, HasAttrs, HasSource, |
3 | HirDisplay, Module, ModuleDef, ModuleSource, Semantics, | 4 | HirDisplay, Module, ModuleDef, ModuleSource, Semantics, |
@@ -5,12 +6,12 @@ use hir::{ | |||
5 | use ide_db::{ | 6 | use ide_db::{ |
6 | base_db::SourceDatabase, | 7 | base_db::SourceDatabase, |
7 | defs::{Definition, NameClass, NameRefClass}, | 8 | defs::{Definition, NameClass, NameRefClass}, |
9 | helpers::FamousDefs, | ||
8 | RootDatabase, | 10 | RootDatabase, |
9 | }; | 11 | }; |
10 | use itertools::Itertools; | 12 | use itertools::Itertools; |
11 | use stdx::format_to; | 13 | use stdx::format_to; |
12 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; | 14 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; |
13 | use test_utils::mark; | ||
14 | 15 | ||
15 | use crate::{ | 16 | use crate::{ |
16 | display::{macro_label, ShortLabel, TryToNav}, | 17 | display::{macro_label, ShortLabel, TryToNav}, |
@@ -107,16 +108,14 @@ pub(crate) fn hover( | |||
107 | } | 108 | } |
108 | }; | 109 | }; |
109 | if let Some(definition) = definition { | 110 | if let Some(definition) = definition { |
110 | if let Some(markup) = hover_for_definition(db, definition) { | 111 | let famous_defs = match &definition { |
111 | let markup = markup.as_str(); | 112 | Definition::ModuleDef(ModuleDef::BuiltinType(_)) => { |
112 | let markup = if !markdown { | 113 | Some(FamousDefs(&sema, sema.scope(&node).krate())) |
113 | remove_markdown(markup) | 114 | } |
114 | } else if links_in_hover { | 115 | _ => None, |
115 | rewrite_links(db, markup, &definition) | 116 | }; |
116 | } else { | 117 | if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref()) { |
117 | remove_links(markup) | 118 | res.markup = process_markup(sema.db, definition, &markup, links_in_hover, markdown); |
118 | }; | ||
119 | res.markup = Markup::from(markup); | ||
120 | if let Some(action) = show_implementations_action(db, definition) { | 119 | if let Some(action) = show_implementations_action(db, definition) { |
121 | res.actions.push(action); | 120 | res.actions.push(action); |
122 | } | 121 | } |
@@ -138,6 +137,9 @@ pub(crate) fn hover( | |||
138 | // don't highlight the entire parent node on comment hover | 137 | // don't highlight the entire parent node on comment hover |
139 | return None; | 138 | return None; |
140 | } | 139 | } |
140 | if let res @ Some(_) = hover_for_keyword(&sema, links_in_hover, markdown, &token) { | ||
141 | return res; | ||
142 | } | ||
141 | 143 | ||
142 | let node = token | 144 | let node = token |
143 | .ancestors() | 145 | .ancestors() |
@@ -191,8 +193,8 @@ fn runnable_action( | |||
191 | ModuleDef::Function(func) => { | 193 | ModuleDef::Function(func) => { |
192 | let src = func.source(sema.db)?; | 194 | let src = func.source(sema.db)?; |
193 | if src.file_id != file_id.into() { | 195 | if src.file_id != file_id.into() { |
194 | mark::hit!(hover_macro_generated_struct_fn_doc_comment); | 196 | cov_mark::hit!(hover_macro_generated_struct_fn_doc_comment); |
195 | mark::hit!(hover_macro_generated_struct_fn_doc_attr); | 197 | cov_mark::hit!(hover_macro_generated_struct_fn_doc_attr); |
196 | return None; | 198 | return None; |
197 | } | 199 | } |
198 | 200 | ||
@@ -272,6 +274,24 @@ fn hover_markup( | |||
272 | } | 274 | } |
273 | } | 275 | } |
274 | 276 | ||
277 | fn process_markup( | ||
278 | db: &RootDatabase, | ||
279 | def: Definition, | ||
280 | markup: &Markup, | ||
281 | links_in_hover: bool, | ||
282 | markdown: bool, | ||
283 | ) -> Markup { | ||
284 | let markup = markup.as_str(); | ||
285 | let markup = if !markdown { | ||
286 | remove_markdown(markup) | ||
287 | } else if links_in_hover { | ||
288 | rewrite_links(db, markup, &def) | ||
289 | } else { | ||
290 | remove_links(markup) | ||
291 | }; | ||
292 | Markup::from(markup) | ||
293 | } | ||
294 | |||
275 | fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> { | 295 | fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> { |
276 | match def { | 296 | match def { |
277 | Definition::Field(f) => Some(f.parent_def(db).name(db)), | 297 | Definition::Field(f) => Some(f.parent_def(db).name(db)), |
@@ -304,7 +324,11 @@ fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> { | |||
304 | def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def))) | 324 | def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def))) |
305 | } | 325 | } |
306 | 326 | ||
307 | fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> { | 327 | fn hover_for_definition( |
328 | db: &RootDatabase, | ||
329 | def: Definition, | ||
330 | famous_defs: Option<&FamousDefs>, | ||
331 | ) -> Option<Markup> { | ||
308 | let mod_path = definition_mod_path(db, &def); | 332 | let mod_path = definition_mod_path(db, &def); |
309 | return match def { | 333 | return match def { |
310 | Definition::Macro(it) => { | 334 | Definition::Macro(it) => { |
@@ -339,9 +363,11 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> { | |||
339 | ModuleDef::Static(it) => from_def_source(db, it, mod_path), | 363 | ModuleDef::Static(it) => from_def_source(db, it, mod_path), |
340 | ModuleDef::Trait(it) => from_def_source(db, it, mod_path), | 364 | ModuleDef::Trait(it) => from_def_source(db, it, mod_path), |
341 | ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), | 365 | ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), |
342 | ModuleDef::BuiltinType(it) => Some(Markup::fenced_block(&it.name())), | 366 | ModuleDef::BuiltinType(it) => famous_defs |
367 | .and_then(|fd| hover_for_builtin(fd, it)) | ||
368 | .or_else(|| Some(Markup::fenced_block(&it.name()))), | ||
343 | }, | 369 | }, |
344 | Definition::Local(it) => Some(Markup::fenced_block(&it.ty(db).display(db))), | 370 | Definition::Local(it) => hover_for_local(it, db), |
345 | Definition::SelfType(impl_def) => { | 371 | Definition::SelfType(impl_def) => { |
346 | impl_def.target_ty(db).as_adt().and_then(|adt| match adt { | 372 | impl_def.target_ty(db).as_adt().and_then(|adt| match adt { |
347 | Adt::Struct(it) => from_def_source(db, it, mod_path), | 373 | Adt::Struct(it) => from_def_source(db, it, mod_path), |
@@ -380,11 +406,75 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> { | |||
380 | } | 406 | } |
381 | } | 407 | } |
382 | 408 | ||
409 | fn hover_for_local(it: hir::Local, db: &RootDatabase) -> Option<Markup> { | ||
410 | let ty = it.ty(db); | ||
411 | let ty = ty.display(db); | ||
412 | let is_mut = if it.is_mut(db) { "mut " } else { "" }; | ||
413 | let desc = match it.source(db).value { | ||
414 | Either::Left(ident) => { | ||
415 | let name = it.name(db).unwrap(); | ||
416 | let let_kw = if ident | ||
417 | .syntax() | ||
418 | .parent() | ||
419 | .map_or(false, |p| p.kind() == LET_STMT || p.kind() == CONDITION) | ||
420 | { | ||
421 | "let " | ||
422 | } else { | ||
423 | "" | ||
424 | }; | ||
425 | format!("{}{}{}: {}", let_kw, is_mut, name, ty) | ||
426 | } | ||
427 | Either::Right(_) => format!("{}self: {}", is_mut, ty), | ||
428 | }; | ||
429 | hover_markup(None, Some(desc), None) | ||
430 | } | ||
431 | |||
432 | fn hover_for_keyword( | ||
433 | sema: &Semantics<RootDatabase>, | ||
434 | links_in_hover: bool, | ||
435 | markdown: bool, | ||
436 | token: &SyntaxToken, | ||
437 | ) -> Option<RangeInfo<HoverResult>> { | ||
438 | if !token.kind().is_keyword() { | ||
439 | return None; | ||
440 | } | ||
441 | let famous_defs = FamousDefs(&sema, sema.scope(&token.parent()).krate()); | ||
442 | // std exposes {}_keyword modules with docstrings on the root to document keywords | ||
443 | let keyword_mod = format!("{}_keyword", token.text()); | ||
444 | let doc_owner = find_std_module(&famous_defs, &keyword_mod)?; | ||
445 | let docs = doc_owner.attrs(sema.db).docs()?; | ||
446 | let markup = process_markup( | ||
447 | sema.db, | ||
448 | Definition::ModuleDef(doc_owner.into()), | ||
449 | &hover_markup(Some(docs.into()), Some(token.text().into()), None)?, | ||
450 | links_in_hover, | ||
451 | markdown, | ||
452 | ); | ||
453 | Some(RangeInfo::new(token.text_range(), HoverResult { markup, actions: Default::default() })) | ||
454 | } | ||
455 | |||
456 | fn hover_for_builtin(famous_defs: &FamousDefs, builtin: hir::BuiltinType) -> Option<Markup> { | ||
457 | // std exposes prim_{} modules with docstrings on the root to document the builtins | ||
458 | let primitive_mod = format!("prim_{}", builtin.name()); | ||
459 | let doc_owner = find_std_module(famous_defs, &primitive_mod)?; | ||
460 | let docs = doc_owner.attrs(famous_defs.0.db).docs()?; | ||
461 | hover_markup(Some(docs.into()), Some(builtin.name().to_string()), None) | ||
462 | } | ||
463 | |||
464 | fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module> { | ||
465 | let db = famous_defs.0.db; | ||
466 | let std_crate = famous_defs.std()?; | ||
467 | let std_root_module = std_crate.root_module(db); | ||
468 | std_root_module | ||
469 | .children(db) | ||
470 | .find(|module| module.name(db).map_or(false, |module| module.to_string() == name)) | ||
471 | } | ||
472 | |||
383 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 473 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
384 | return tokens.max_by_key(priority); | 474 | return tokens.max_by_key(priority); |
385 | fn priority(n: &SyntaxToken) -> usize { | 475 | fn priority(n: &SyntaxToken) -> usize { |
386 | match n.kind() { | 476 | match n.kind() { |
387 | IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] => 3, | 477 | IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3, |
388 | T!['('] | T![')'] => 2, | 478 | T!['('] | T![')'] => 2, |
389 | kind if kind.is_trivia() => 0, | 479 | kind if kind.is_trivia() => 0, |
390 | _ => 1, | 480 | _ => 1, |
@@ -508,7 +598,7 @@ fn main() { | |||
508 | *iter* | 598 | *iter* |
509 | 599 | ||
510 | ```rust | 600 | ```rust |
511 | Iter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> Option<u32>, u32>> | 601 | let mut iter: Iter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> Option<u32>, u32>> |
512 | ``` | 602 | ``` |
513 | "#]], | 603 | "#]], |
514 | ); | 604 | ); |
@@ -732,7 +822,7 @@ fn main() { | |||
732 | ``` | 822 | ``` |
733 | 823 | ||
734 | ```rust | 824 | ```rust |
735 | const foo: u32 = 123 | 825 | const foo: u32 |
736 | ``` | 826 | ``` |
737 | "#]], | 827 | "#]], |
738 | ); | 828 | ); |
@@ -765,7 +855,7 @@ fn main() { | |||
765 | *zz* | 855 | *zz* |
766 | 856 | ||
767 | ```rust | 857 | ```rust |
768 | Test<i32, u8> | 858 | let zz: Test<i32, u8> |
769 | ``` | 859 | ``` |
770 | "#]], | 860 | "#]], |
771 | ); | 861 | ); |
@@ -804,7 +894,7 @@ fn main() { let b$0ar = Some(12); } | |||
804 | *bar* | 894 | *bar* |
805 | 895 | ||
806 | ```rust | 896 | ```rust |
807 | Option<i32> | 897 | let bar: Option<i32> |
808 | ``` | 898 | ``` |
809 | "#]], | 899 | "#]], |
810 | ); | 900 | ); |
@@ -872,7 +962,7 @@ fn main() { | |||
872 | *foo* | 962 | *foo* |
873 | 963 | ||
874 | ```rust | 964 | ```rust |
875 | i32 | 965 | foo: i32 |
876 | ``` | 966 | ``` |
877 | "#]], | 967 | "#]], |
878 | ) | 968 | ) |
@@ -886,7 +976,7 @@ fn main() { | |||
886 | *foo* | 976 | *foo* |
887 | 977 | ||
888 | ```rust | 978 | ```rust |
889 | i32 | 979 | foo: i32 |
890 | ``` | 980 | ``` |
891 | "#]], | 981 | "#]], |
892 | ) | 982 | ) |
@@ -900,7 +990,7 @@ fn main() { | |||
900 | *foo* | 990 | *foo* |
901 | 991 | ||
902 | ```rust | 992 | ```rust |
903 | i32 | 993 | foo: i32 |
904 | ``` | 994 | ``` |
905 | "#]], | 995 | "#]], |
906 | ) | 996 | ) |
@@ -914,7 +1004,7 @@ fn main() { | |||
914 | *foo* | 1004 | *foo* |
915 | 1005 | ||
916 | ```rust | 1006 | ```rust |
917 | i32 | 1007 | foo: i32 |
918 | ``` | 1008 | ``` |
919 | "#]], | 1009 | "#]], |
920 | ) | 1010 | ) |
@@ -934,7 +1024,7 @@ fn main() { | |||
934 | *_x* | 1024 | *_x* |
935 | 1025 | ||
936 | ```rust | 1026 | ```rust |
937 | impl Deref<Target = u8> + DerefMut<Target = u8> | 1027 | _x: impl Deref<Target = u8> + DerefMut<Target = u8> |
938 | ``` | 1028 | ``` |
939 | "#]], | 1029 | "#]], |
940 | ) | 1030 | ) |
@@ -956,7 +1046,7 @@ fn main() { let foo_$0test = Thing::new(); } | |||
956 | *foo_test* | 1046 | *foo_test* |
957 | 1047 | ||
958 | ```rust | 1048 | ```rust |
959 | Thing | 1049 | let foo_test: Thing |
960 | ``` | 1050 | ``` |
961 | "#]], | 1051 | "#]], |
962 | ) | 1052 | ) |
@@ -1015,7 +1105,7 @@ fn main() { | |||
1015 | ``` | 1105 | ``` |
1016 | 1106 | ||
1017 | ```rust | 1107 | ```rust |
1018 | const C: u32 = 1 | 1108 | const C: u32 |
1019 | ``` | 1109 | ``` |
1020 | "#]], | 1110 | "#]], |
1021 | ) | 1111 | ) |
@@ -1116,7 +1206,7 @@ fn y() { | |||
1116 | *x* | 1206 | *x* |
1117 | 1207 | ||
1118 | ```rust | 1208 | ```rust |
1119 | i32 | 1209 | let x: i32 |
1120 | ``` | 1210 | ``` |
1121 | "#]], | 1211 | "#]], |
1122 | ) | 1212 | ) |
@@ -1193,7 +1283,7 @@ fn foo(bar:u32) { let a = id!(ba$0r); } | |||
1193 | *bar* | 1283 | *bar* |
1194 | 1284 | ||
1195 | ```rust | 1285 | ```rust |
1196 | u32 | 1286 | bar: u32 |
1197 | ``` | 1287 | ``` |
1198 | "#]], | 1288 | "#]], |
1199 | ); | 1289 | ); |
@@ -1211,7 +1301,7 @@ fn foo(bar:u32) { let a = id!(ba$0r); } | |||
1211 | *bar* | 1301 | *bar* |
1212 | 1302 | ||
1213 | ```rust | 1303 | ```rust |
1214 | u32 | 1304 | bar: u32 |
1215 | ``` | 1305 | ``` |
1216 | "#]], | 1306 | "#]], |
1217 | ); | 1307 | ); |
@@ -2034,7 +2124,7 @@ pub fn fo$0o() {} | |||
2034 | 2124 | ||
2035 | #[test] | 2125 | #[test] |
2036 | fn test_hover_macro_generated_struct_fn_doc_comment() { | 2126 | fn test_hover_macro_generated_struct_fn_doc_comment() { |
2037 | mark::check!(hover_macro_generated_struct_fn_doc_comment); | 2127 | cov_mark::check!(hover_macro_generated_struct_fn_doc_comment); |
2038 | 2128 | ||
2039 | check( | 2129 | check( |
2040 | r#" | 2130 | r#" |
@@ -2072,7 +2162,7 @@ fn foo() { let bar = Bar; bar.fo$0o(); } | |||
2072 | 2162 | ||
2073 | #[test] | 2163 | #[test] |
2074 | fn test_hover_macro_generated_struct_fn_doc_attr() { | 2164 | fn test_hover_macro_generated_struct_fn_doc_attr() { |
2075 | mark::check!(hover_macro_generated_struct_fn_doc_attr); | 2165 | cov_mark::check!(hover_macro_generated_struct_fn_doc_attr); |
2076 | 2166 | ||
2077 | check( | 2167 | check( |
2078 | r#" | 2168 | r#" |
@@ -3236,7 +3326,7 @@ fn main() { | |||
3236 | *f* | 3326 | *f* |
3237 | 3327 | ||
3238 | ```rust | 3328 | ```rust |
3239 | &i32 | 3329 | f: &i32 |
3240 | ``` | 3330 | ``` |
3241 | "#]], | 3331 | "#]], |
3242 | ); | 3332 | ); |
@@ -3255,7 +3345,7 @@ impl Foo { | |||
3255 | *self* | 3345 | *self* |
3256 | 3346 | ||
3257 | ```rust | 3347 | ```rust |
3258 | &Foo | 3348 | self: &Foo |
3259 | ``` | 3349 | ``` |
3260 | "#]], | 3350 | "#]], |
3261 | ); | 3351 | ); |
@@ -3275,7 +3365,7 @@ impl Foo { | |||
3275 | *self* | 3365 | *self* |
3276 | 3366 | ||
3277 | ```rust | 3367 | ```rust |
3278 | Arc<Foo> | 3368 | self: Arc<Foo> |
3279 | ``` | 3369 | ``` |
3280 | "#]], | 3370 | "#]], |
3281 | ); | 3371 | ); |
@@ -3471,7 +3561,7 @@ fn foo() { | |||
3471 | ``` | 3561 | ``` |
3472 | 3562 | ||
3473 | ```rust | 3563 | ```rust |
3474 | const FOO: usize = 3 | 3564 | const FOO: usize |
3475 | ``` | 3565 | ``` |
3476 | 3566 | ||
3477 | --- | 3567 | --- |
@@ -3496,4 +3586,75 @@ mod foo$0; | |||
3496 | "#]], | 3586 | "#]], |
3497 | ); | 3587 | ); |
3498 | } | 3588 | } |
3589 | |||
3590 | #[test] | ||
3591 | fn hover_self_in_use() { | ||
3592 | check( | ||
3593 | r#" | ||
3594 | //! This should not appear | ||
3595 | mod foo { | ||
3596 | /// But this should appear | ||
3597 | pub mod bar {} | ||
3598 | } | ||
3599 | use foo::bar::{self$0}; | ||
3600 | "#, | ||
3601 | expect![[r#" | ||
3602 | *self* | ||
3603 | |||
3604 | ```rust | ||
3605 | test::foo | ||
3606 | ``` | ||
3607 | |||
3608 | ```rust | ||
3609 | pub mod bar | ||
3610 | ``` | ||
3611 | |||
3612 | --- | ||
3613 | |||
3614 | But this should appear | ||
3615 | "#]], | ||
3616 | ) | ||
3617 | } | ||
3618 | |||
3619 | #[test] | ||
3620 | fn hover_keyword() { | ||
3621 | let ra_fixture = r#"//- /main.rs crate:main deps:std | ||
3622 | fn f() { retur$0n; }"#; | ||
3623 | let fixture = format!("{}\n{}", ra_fixture, FamousDefs::FIXTURE); | ||
3624 | check( | ||
3625 | &fixture, | ||
3626 | expect![[r#" | ||
3627 | *return* | ||
3628 | |||
3629 | ```rust | ||
3630 | return | ||
3631 | ``` | ||
3632 | |||
3633 | --- | ||
3634 | |||
3635 | Docs for return_keyword | ||
3636 | "#]], | ||
3637 | ); | ||
3638 | } | ||
3639 | |||
3640 | #[test] | ||
3641 | fn hover_builtin() { | ||
3642 | let ra_fixture = r#"//- /main.rs crate:main deps:std | ||
3643 | cosnt _: &str$0 = ""; }"#; | ||
3644 | let fixture = format!("{}\n{}", ra_fixture, FamousDefs::FIXTURE); | ||
3645 | check( | ||
3646 | &fixture, | ||
3647 | expect![[r#" | ||
3648 | *str* | ||
3649 | |||
3650 | ```rust | ||
3651 | str | ||
3652 | ``` | ||
3653 | |||
3654 | --- | ||
3655 | |||
3656 | Docs for prim_str | ||
3657 | "#]], | ||
3658 | ); | ||
3659 | } | ||
3499 | } | 3660 | } |
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs index 2c077ed1f..20a920ddb 100644 --- a/crates/ide/src/join_lines.rs +++ b/crates/ide/src/join_lines.rs | |||
@@ -7,6 +7,7 @@ use syntax::{ | |||
7 | SyntaxKind::{self, USE_TREE, WHITESPACE}, | 7 | SyntaxKind::{self, USE_TREE, WHITESPACE}, |
8 | SyntaxNode, SyntaxToken, TextRange, TextSize, T, | 8 | SyntaxNode, SyntaxToken, TextRange, TextSize, T, |
9 | }; | 9 | }; |
10 | |||
10 | use text_edit::{TextEdit, TextEditBuilder}; | 11 | use text_edit::{TextEdit, TextEditBuilder}; |
11 | 12 | ||
12 | // Feature: Join Lines | 13 | // Feature: Join Lines |
@@ -44,9 +45,9 @@ pub(crate) fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { | |||
44 | let text = token.text(); | 45 | let text = token.text(); |
45 | for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') { | 46 | for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') { |
46 | let pos: TextSize = (pos as u32).into(); | 47 | let pos: TextSize = (pos as u32).into(); |
47 | let off = token.text_range().start() + range.start() + pos; | 48 | let offset = token.text_range().start() + range.start() + pos; |
48 | if !edit.invalidates_offset(off) { | 49 | if !edit.invalidates_offset(offset) { |
49 | remove_newline(&mut edit, &token, off); | 50 | remove_newline(&mut edit, &token, offset); |
50 | } | 51 | } |
51 | } | 52 | } |
52 | } | 53 | } |
@@ -56,14 +57,25 @@ pub(crate) fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { | |||
56 | 57 | ||
57 | fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextSize) { | 58 | fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextSize) { |
58 | if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 { | 59 | if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 { |
59 | // The node is either the first or the last in the file | 60 | let mut string_open_quote = false; |
60 | let suff = &token.text()[TextRange::new( | 61 | if let Some(string) = ast::String::cast(token.clone()) { |
61 | offset - token.text_range().start() + TextSize::of('\n'), | 62 | if let Some(range) = string.open_quote_text_range() { |
62 | TextSize::of(token.text()), | 63 | cov_mark::hit!(join_string_literal); |
63 | )]; | 64 | string_open_quote = range.end() == offset; |
64 | let spaces = suff.bytes().take_while(|&b| b == b' ').count(); | 65 | } |
65 | 66 | } | |
66 | edit.replace(TextRange::at(offset, ((spaces + 1) as u32).into()), " ".to_string()); | 67 | |
68 | let n_spaces_after_line_break = { | ||
69 | let suff = &token.text()[TextRange::new( | ||
70 | offset - token.text_range().start() + TextSize::of('\n'), | ||
71 | TextSize::of(token.text()), | ||
72 | )]; | ||
73 | suff.bytes().take_while(|&b| b == b' ').count() | ||
74 | }; | ||
75 | |||
76 | let range = TextRange::at(offset, ((n_spaces_after_line_break + 1) as u32).into()); | ||
77 | let replace_with = if string_open_quote { "" } else { " " }; | ||
78 | edit.replace(range, replace_with.to_string()); | ||
67 | return; | 79 | return; |
68 | } | 80 | } |
69 | 81 | ||
@@ -771,4 +783,42 @@ fn foo() { | |||
771 | ", | 783 | ", |
772 | ); | 784 | ); |
773 | } | 785 | } |
786 | |||
787 | #[test] | ||
788 | fn join_string_literal() { | ||
789 | cov_mark::check!(join_string_literal); | ||
790 | check_join_lines( | ||
791 | r#" | ||
792 | fn main() { | ||
793 | $0" | ||
794 | hello | ||
795 | "; | ||
796 | } | ||
797 | "#, | ||
798 | r#" | ||
799 | fn main() { | ||
800 | $0"hello | ||
801 | "; | ||
802 | } | ||
803 | "#, | ||
804 | ); | ||
805 | |||
806 | check_join_lines( | ||
807 | r#" | ||
808 | fn main() { | ||
809 | " | ||
810 | $0hello | ||
811 | world | ||
812 | "; | ||
813 | } | ||
814 | "#, | ||
815 | r#" | ||
816 | fn main() { | ||
817 | " | ||
818 | $0hello world | ||
819 | "; | ||
820 | } | ||
821 | "#, | ||
822 | ); | ||
823 | } | ||
774 | } | 824 | } |
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index baa80cf43..a8b169e87 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -41,6 +41,7 @@ mod parent_module; | |||
41 | mod references; | 41 | mod references; |
42 | mod fn_references; | 42 | mod fn_references; |
43 | mod runnables; | 43 | mod runnables; |
44 | mod ssr; | ||
44 | mod status; | 45 | mod status; |
45 | mod syntax_highlighting; | 46 | mod syntax_highlighting; |
46 | mod syntax_tree; | 47 | mod syntax_tree; |
@@ -51,6 +52,7 @@ mod doc_links; | |||
51 | use std::sync::Arc; | 52 | use std::sync::Arc; |
52 | 53 | ||
53 | use cfg::CfgOptions; | 54 | use cfg::CfgOptions; |
55 | |||
54 | use ide_db::base_db::{ | 56 | use ide_db::base_db::{ |
55 | salsa::{self, ParallelDatabase}, | 57 | salsa::{self, ParallelDatabase}, |
56 | CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath, | 58 | CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath, |
@@ -85,7 +87,7 @@ pub use crate::{ | |||
85 | pub use hir::{Documentation, Semantics}; | 87 | pub use hir::{Documentation, Semantics}; |
86 | pub use ide_assists::{Assist, AssistConfig, AssistId, AssistKind}; | 88 | pub use ide_assists::{Assist, AssistConfig, AssistId, AssistKind}; |
87 | pub use ide_completion::{ | 89 | pub use ide_completion::{ |
88 | CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, | 90 | CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit, |
89 | InsertTextFormat, | 91 | InsertTextFormat, |
90 | }; | 92 | }; |
91 | pub use ide_db::{ | 93 | pub use ide_db::{ |
@@ -487,7 +489,6 @@ impl Analysis { | |||
487 | position: FilePosition, | 489 | position: FilePosition, |
488 | full_import_path: &str, | 490 | full_import_path: &str, |
489 | imported_name: String, | 491 | imported_name: String, |
490 | import_for_trait_assoc_item: bool, | ||
491 | ) -> Cancelable<Vec<TextEdit>> { | 492 | ) -> Cancelable<Vec<TextEdit>> { |
492 | Ok(self | 493 | Ok(self |
493 | .with_db(|db| { | 494 | .with_db(|db| { |
@@ -497,7 +498,6 @@ impl Analysis { | |||
497 | position, | 498 | position, |
498 | full_import_path, | 499 | full_import_path, |
499 | imported_name, | 500 | imported_name, |
500 | import_for_trait_assoc_item, | ||
501 | ) | 501 | ) |
502 | })? | 502 | })? |
503 | .unwrap_or_default()) | 503 | .unwrap_or_default()) |
@@ -513,7 +513,11 @@ impl Analysis { | |||
513 | resolve: bool, | 513 | resolve: bool, |
514 | frange: FileRange, | 514 | frange: FileRange, |
515 | ) -> Cancelable<Vec<Assist>> { | 515 | ) -> Cancelable<Vec<Assist>> { |
516 | self.with_db(|db| Assist::get(db, config, resolve, frange)) | 516 | self.with_db(|db| { |
517 | let mut acc = Assist::get(db, config, resolve, frange); | ||
518 | ssr::add_ssr_assist(db, &mut acc, resolve, frange); | ||
519 | acc | ||
520 | }) | ||
517 | } | 521 | } |
518 | 522 | ||
519 | /// Computes the set of diagnostics for the given file. | 523 | /// Computes the set of diagnostics for the given file. |
diff --git a/crates/ide/src/matching_brace.rs b/crates/ide/src/matching_brace.rs index 1bfa1439d..000c412d9 100644 --- a/crates/ide/src/matching_brace.rs +++ b/crates/ide/src/matching_brace.rs | |||
@@ -2,7 +2,6 @@ use syntax::{ | |||
2 | ast::{self, AstNode}, | 2 | ast::{self, AstNode}, |
3 | SourceFile, SyntaxKind, TextSize, T, | 3 | SourceFile, SyntaxKind, TextSize, T, |
4 | }; | 4 | }; |
5 | use test_utils::mark; | ||
6 | 5 | ||
7 | // Feature: Matching Brace | 6 | // Feature: Matching Brace |
8 | // | 7 | // |
@@ -28,7 +27,7 @@ pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<Text | |||
28 | .next()?; | 27 | .next()?; |
29 | let parent = brace_token.parent(); | 28 | let parent = brace_token.parent(); |
30 | if brace_token.kind() == T![|] && !ast::ParamList::can_cast(parent.kind()) { | 29 | if brace_token.kind() == T![|] && !ast::ParamList::can_cast(parent.kind()) { |
31 | mark::hit!(pipes_not_braces); | 30 | cov_mark::hit!(pipes_not_braces); |
32 | return None; | 31 | return None; |
33 | } | 32 | } |
34 | let matching_kind = BRACES[brace_idx ^ 1]; | 33 | let matching_kind = BRACES[brace_idx ^ 1]; |
@@ -63,7 +62,7 @@ mod tests { | |||
63 | do_check("fn main() { $0|x: i32| x * 2;}", "fn main() { |x: i32$0| x * 2;}"); | 62 | do_check("fn main() { $0|x: i32| x * 2;}", "fn main() { |x: i32$0| x * 2;}"); |
64 | 63 | ||
65 | { | 64 | { |
66 | mark::check!(pipes_not_braces); | 65 | cov_mark::check!(pipes_not_braces); |
67 | do_check( | 66 | do_check( |
68 | "fn main() { match 92 { 1 | 2 |$0 3 => 92 } }", | 67 | "fn main() { match 92 { 1 | 2 |$0 3 => 92 } }", |
69 | "fn main() { match 92 { 1 | 2 |$0 3 => 92 } }", | 68 | "fn main() { match 92 { 1 | 2 |$0 3 => 92 } }", |
diff --git a/crates/ide/src/parent_module.rs b/crates/ide/src/parent_module.rs index ddbaf22b7..03d71b380 100644 --- a/crates/ide/src/parent_module.rs +++ b/crates/ide/src/parent_module.rs | |||
@@ -5,7 +5,6 @@ use syntax::{ | |||
5 | algo::find_node_at_offset, | 5 | algo::find_node_at_offset, |
6 | ast::{self, AstNode}, | 6 | ast::{self, AstNode}, |
7 | }; | 7 | }; |
8 | use test_utils::mark; | ||
9 | 8 | ||
10 | use crate::NavigationTarget; | 9 | use crate::NavigationTarget; |
11 | 10 | ||
@@ -33,7 +32,7 @@ pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<Na | |||
33 | .item_list() | 32 | .item_list() |
34 | .map_or(false, |it| it.syntax().text_range().contains_inclusive(position.offset)) | 33 | .map_or(false, |it| it.syntax().text_range().contains_inclusive(position.offset)) |
35 | { | 34 | { |
36 | mark::hit!(test_resolve_parent_module_on_module_decl); | 35 | cov_mark::hit!(test_resolve_parent_module_on_module_decl); |
37 | module = m.syntax().ancestors().skip(1).find_map(ast::Module::cast); | 36 | module = m.syntax().ancestors().skip(1).find_map(ast::Module::cast); |
38 | } | 37 | } |
39 | } | 38 | } |
@@ -64,7 +63,6 @@ pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> { | |||
64 | #[cfg(test)] | 63 | #[cfg(test)] |
65 | mod tests { | 64 | mod tests { |
66 | use ide_db::base_db::FileRange; | 65 | use ide_db::base_db::FileRange; |
67 | use test_utils::mark; | ||
68 | 66 | ||
69 | use crate::fixture; | 67 | use crate::fixture; |
70 | 68 | ||
@@ -92,7 +90,7 @@ $0// empty | |||
92 | 90 | ||
93 | #[test] | 91 | #[test] |
94 | fn test_resolve_parent_module_on_module_decl() { | 92 | fn test_resolve_parent_module_on_module_decl() { |
95 | mark::check!(test_resolve_parent_module_on_module_decl); | 93 | cov_mark::check!(test_resolve_parent_module_on_module_decl); |
96 | check( | 94 | check( |
97 | r#" | 95 | r#" |
98 | //- /lib.rs | 96 | //- /lib.rs |
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 5d0449e56..fef70533d 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -39,6 +39,15 @@ pub struct Declaration { | |||
39 | pub access: Option<ReferenceAccess>, | 39 | pub access: Option<ReferenceAccess>, |
40 | } | 40 | } |
41 | 41 | ||
42 | // Feature: Find All References | ||
43 | // | ||
44 | // Shows all references of the item at the cursor location | ||
45 | // | ||
46 | // |=== | ||
47 | // | Editor | Shortcut | ||
48 | // | ||
49 | // | VS Code | kbd:[Shift+Alt+F12] | ||
50 | // |=== | ||
42 | pub(crate) fn find_all_refs( | 51 | pub(crate) fn find_all_refs( |
43 | sema: &Semantics<RootDatabase>, | 52 | sema: &Semantics<RootDatabase>, |
44 | position: FilePosition, | 53 | position: FilePosition, |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 175ddd759..1e378279d 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -14,14 +14,14 @@ use syntax::{ | |||
14 | ast::{self, NameOwner}, | 14 | ast::{self, NameOwner}, |
15 | lex_single_syntax_kind, AstNode, SyntaxKind, SyntaxNode, T, | 15 | lex_single_syntax_kind, AstNode, SyntaxKind, SyntaxNode, T, |
16 | }; | 16 | }; |
17 | use test_utils::mark; | 17 | |
18 | use text_edit::TextEdit; | 18 | use text_edit::TextEdit; |
19 | 19 | ||
20 | use crate::{display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange}; | 20 | use crate::{display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange}; |
21 | 21 | ||
22 | type RenameResult<T> = Result<T, RenameError>; | 22 | type RenameResult<T> = Result<T, RenameError>; |
23 | #[derive(Debug)] | 23 | #[derive(Debug)] |
24 | pub struct RenameError(pub(crate) String); | 24 | pub struct RenameError(String); |
25 | 25 | ||
26 | impl fmt::Display for RenameError { | 26 | impl fmt::Display for RenameError { |
27 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 27 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
@@ -47,18 +47,26 @@ pub(crate) fn prepare_rename( | |||
47 | let sema = Semantics::new(db); | 47 | let sema = Semantics::new(db); |
48 | let source_file = sema.parse(position.file_id); | 48 | let source_file = sema.parse(position.file_id); |
49 | let syntax = source_file.syntax(); | 49 | let syntax = source_file.syntax(); |
50 | let range = match &sema | 50 | let name_like = sema |
51 | .find_node_at_offset_with_descend(&syntax, position.offset) | 51 | .find_node_at_offset_with_descend(&syntax, position.offset) |
52 | .ok_or_else(|| format_err!("No references found at position"))? | 52 | .ok_or_else(|| format_err!("No references found at position"))?; |
53 | { | 53 | let node = match &name_like { |
54 | ast::NameLike::Name(it) => it.syntax(), | 54 | ast::NameLike::Name(it) => it.syntax(), |
55 | ast::NameLike::NameRef(it) => it.syntax(), | 55 | ast::NameLike::NameRef(it) => it.syntax(), |
56 | ast::NameLike::Lifetime(it) => it.syntax(), | 56 | ast::NameLike::Lifetime(it) => it.syntax(), |
57 | } | 57 | }; |
58 | .text_range(); | 58 | Ok(RangeInfo::new(sema.original_range(node).range, ())) |
59 | Ok(RangeInfo::new(range, ())) | 59 | } |
60 | } | 60 | |
61 | 61 | // Feature: Rename | |
62 | // | ||
63 | // Renames the item below the cursor and all of its references | ||
64 | // | ||
65 | // |=== | ||
66 | // | Editor | Shortcut | ||
67 | // | ||
68 | // | VS Code | kbd:[F2] | ||
69 | // |=== | ||
62 | pub(crate) fn rename( | 70 | pub(crate) fn rename( |
63 | db: &RootDatabase, | 71 | db: &RootDatabase, |
64 | position: FilePosition, | 72 | position: FilePosition, |
@@ -79,10 +87,13 @@ pub(crate) fn rename_with_semantics( | |||
79 | let def = find_definition(sema, syntax, position)?; | 87 | let def = find_definition(sema, syntax, position)?; |
80 | match def { | 88 | match def { |
81 | Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), | 89 | Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), |
90 | Definition::SelfType(_) => bail!("Cannot rename `Self`"), | ||
91 | Definition::ModuleDef(ModuleDef::BuiltinType(_)) => bail!("Cannot rename builtin type"), | ||
82 | def => rename_reference(sema, def, new_name), | 92 | def => rename_reference(sema, def, new_name), |
83 | } | 93 | } |
84 | } | 94 | } |
85 | 95 | ||
96 | /// Called by the client when it is about to rename a file. | ||
86 | pub(crate) fn will_rename_file( | 97 | pub(crate) fn will_rename_file( |
87 | db: &RootDatabase, | 98 | db: &RootDatabase, |
88 | file_id: FileId, | 99 | file_id: FileId, |
@@ -113,7 +124,7 @@ fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> { | |||
113 | Ok(IdentifierKind::Lifetime) | 124 | Ok(IdentifierKind::Lifetime) |
114 | } | 125 | } |
115 | (SyntaxKind::LIFETIME_IDENT, _) => { | 126 | (SyntaxKind::LIFETIME_IDENT, _) => { |
116 | bail!("Invalid name `{0}`: Cannot rename lifetime to {0}", new_name) | 127 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) |
117 | } | 128 | } |
118 | (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), | 129 | (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), |
119 | (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), | 130 | (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), |
@@ -153,119 +164,6 @@ fn find_definition( | |||
153 | .ok_or_else(|| format_err!("No references found at position")) | 164 | .ok_or_else(|| format_err!("No references found at position")) |
154 | } | 165 | } |
155 | 166 | ||
156 | fn source_edit_from_references( | ||
157 | _sema: &Semantics<RootDatabase>, | ||
158 | file_id: FileId, | ||
159 | references: &[FileReference], | ||
160 | def: Definition, | ||
161 | new_name: &str, | ||
162 | ) -> (FileId, TextEdit) { | ||
163 | let mut edit = TextEdit::builder(); | ||
164 | for reference in references { | ||
165 | let (range, replacement) = match &reference.name { | ||
166 | // if the ranges differ then the node is inside a macro call, we can't really attempt | ||
167 | // to make special rewrites like shorthand syntax and such, so just rename the node in | ||
168 | // the macro input | ||
169 | ast::NameLike::NameRef(name_ref) | ||
170 | if name_ref.syntax().text_range() == reference.range => | ||
171 | { | ||
172 | source_edit_from_name_ref(name_ref, new_name, def) | ||
173 | } | ||
174 | ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => { | ||
175 | source_edit_from_name(name, new_name) | ||
176 | } | ||
177 | _ => None, | ||
178 | } | ||
179 | .unwrap_or_else(|| (reference.range, new_name.to_string())); | ||
180 | edit.replace(range, replacement); | ||
181 | } | ||
182 | (file_id, edit.finish()) | ||
183 | } | ||
184 | |||
185 | fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> { | ||
186 | if let Some(_) = ast::RecordPatField::for_field_name(name) { | ||
187 | if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { | ||
188 | return Some(( | ||
189 | TextRange::empty(ident_pat.syntax().text_range().start()), | ||
190 | format!("{}: ", new_name), | ||
191 | )); | ||
192 | } | ||
193 | } | ||
194 | None | ||
195 | } | ||
196 | |||
197 | fn source_edit_from_name_ref( | ||
198 | name_ref: &ast::NameRef, | ||
199 | new_name: &str, | ||
200 | def: Definition, | ||
201 | ) -> Option<(TextRange, String)> { | ||
202 | if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { | ||
203 | let rcf_name_ref = record_field.name_ref(); | ||
204 | let rcf_expr = record_field.expr(); | ||
205 | match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) { | ||
206 | // field: init-expr, check if we can use a field init shorthand | ||
207 | (Some(field_name), Some(init)) => { | ||
208 | if field_name == *name_ref { | ||
209 | if init.text() == new_name { | ||
210 | mark::hit!(test_rename_field_put_init_shorthand); | ||
211 | // same names, we can use a shorthand here instead. | ||
212 | // we do not want to erase attributes hence this range start | ||
213 | let s = field_name.syntax().text_range().start(); | ||
214 | let e = record_field.syntax().text_range().end(); | ||
215 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
216 | } | ||
217 | } else if init == *name_ref { | ||
218 | if field_name.text() == new_name { | ||
219 | mark::hit!(test_rename_local_put_init_shorthand); | ||
220 | // same names, we can use a shorthand here instead. | ||
221 | // we do not want to erase attributes hence this range start | ||
222 | let s = field_name.syntax().text_range().start(); | ||
223 | let e = record_field.syntax().text_range().end(); | ||
224 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
225 | } | ||
226 | } | ||
227 | None | ||
228 | } | ||
229 | // init shorthand | ||
230 | // FIXME: instead of splitting the shorthand, recursively trigger a rename of the | ||
231 | // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 | ||
232 | (None, Some(_)) if matches!(def, Definition::Field(_)) => { | ||
233 | mark::hit!(test_rename_field_in_field_shorthand); | ||
234 | let s = name_ref.syntax().text_range().start(); | ||
235 | Some((TextRange::empty(s), format!("{}: ", new_name))) | ||
236 | } | ||
237 | (None, Some(_)) if matches!(def, Definition::Local(_)) => { | ||
238 | mark::hit!(test_rename_local_in_field_shorthand); | ||
239 | let s = name_ref.syntax().text_range().end(); | ||
240 | Some((TextRange::empty(s), format!(": {}", new_name))) | ||
241 | } | ||
242 | _ => None, | ||
243 | } | ||
244 | } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) { | ||
245 | let rcf_name_ref = record_field.name_ref(); | ||
246 | let rcf_pat = record_field.pat(); | ||
247 | match (rcf_name_ref, rcf_pat) { | ||
248 | // field: rename | ||
249 | (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => { | ||
250 | // field name is being renamed | ||
251 | if pat.name().map_or(false, |it| it.text() == new_name) { | ||
252 | mark::hit!(test_rename_field_put_init_shorthand_pat); | ||
253 | // same names, we can use a shorthand here instead/ | ||
254 | // we do not want to erase attributes hence this range start | ||
255 | let s = field_name.syntax().text_range().start(); | ||
256 | let e = record_field.syntax().text_range().end(); | ||
257 | Some((TextRange::new(s, e), pat.to_string())) | ||
258 | } else { | ||
259 | None | ||
260 | } | ||
261 | } | ||
262 | _ => None, | ||
263 | } | ||
264 | } else { | ||
265 | None | ||
266 | } | ||
267 | } | ||
268 | |||
269 | fn rename_mod( | 167 | fn rename_mod( |
270 | sema: &Semantics<RootDatabase>, | 168 | sema: &Semantics<RootDatabase>, |
271 | module: Module, | 169 | module: Module, |
@@ -299,18 +197,77 @@ fn rename_mod( | |||
299 | TextEdit::replace(name.syntax().text_range(), new_name.to_string()), | 197 | TextEdit::replace(name.syntax().text_range(), new_name.to_string()), |
300 | ), | 198 | ), |
301 | _ => unreachable!(), | 199 | _ => unreachable!(), |
302 | }; | 200 | } |
303 | } | 201 | } |
304 | let def = Definition::ModuleDef(ModuleDef::Module(module)); | 202 | let def = Definition::ModuleDef(ModuleDef::Module(module)); |
305 | let usages = def.usages(sema).all(); | 203 | let usages = def.usages(sema).all(); |
306 | let ref_edits = usages.iter().map(|(&file_id, references)| { | 204 | let ref_edits = usages.iter().map(|(&file_id, references)| { |
307 | source_edit_from_references(sema, file_id, references, def, new_name) | 205 | (file_id, source_edit_from_references(references, def, new_name)) |
308 | }); | 206 | }); |
309 | source_change.extend(ref_edits); | 207 | source_change.extend(ref_edits); |
310 | 208 | ||
311 | Ok(source_change) | 209 | Ok(source_change) |
312 | } | 210 | } |
313 | 211 | ||
212 | fn rename_reference( | ||
213 | sema: &Semantics<RootDatabase>, | ||
214 | def: Definition, | ||
215 | new_name: &str, | ||
216 | ) -> RenameResult<SourceChange> { | ||
217 | let ident_kind = check_identifier(new_name)?; | ||
218 | |||
219 | let def_is_lbl_or_lt = matches!( | ||
220 | def, | ||
221 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) | ||
222 | ); | ||
223 | match (ident_kind, def) { | ||
224 | (IdentifierKind::ToSelf, _) | ||
225 | | (IdentifierKind::Underscore, _) | ||
226 | | (IdentifierKind::Ident, _) | ||
227 | if def_is_lbl_or_lt => | ||
228 | { | ||
229 | cov_mark::hit!(rename_not_a_lifetime_ident_ref); | ||
230 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) | ||
231 | } | ||
232 | (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => cov_mark::hit!(rename_lifetime), | ||
233 | (IdentifierKind::Lifetime, _) => { | ||
234 | cov_mark::hit!(rename_not_an_ident_ref); | ||
235 | bail!("Invalid name `{}`: not an identifier", new_name) | ||
236 | } | ||
237 | (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => { | ||
238 | // no-op | ||
239 | cov_mark::hit!(rename_self_to_self); | ||
240 | return Ok(SourceChange::default()); | ||
241 | } | ||
242 | (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => { | ||
243 | cov_mark::hit!(rename_self_to_param); | ||
244 | return rename_self_to_param(sema, local, new_name, ident_kind); | ||
245 | } | ||
246 | (IdentifierKind::ToSelf, Definition::Local(local)) => { | ||
247 | cov_mark::hit!(rename_to_self); | ||
248 | return rename_to_self(sema, local); | ||
249 | } | ||
250 | (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
251 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => { | ||
252 | cov_mark::hit!(rename_ident) | ||
253 | } | ||
254 | } | ||
255 | |||
256 | let usages = def.usages(sema).all(); | ||
257 | if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { | ||
258 | cov_mark::hit!(rename_underscore_multiple); | ||
259 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
260 | } | ||
261 | let mut source_change = SourceChange::default(); | ||
262 | source_change.extend(usages.iter().map(|(&file_id, references)| { | ||
263 | (file_id, source_edit_from_references(&references, def, new_name)) | ||
264 | })); | ||
265 | |||
266 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; | ||
267 | source_change.insert_source_edit(file_id, edit); | ||
268 | Ok(source_change) | ||
269 | } | ||
270 | |||
314 | fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { | 271 | fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { |
315 | if never!(local.is_self(sema.db)) { | 272 | if never!(local.is_self(sema.db)) { |
316 | bail!("rename_to_self invoked on self"); | 273 | bail!("rename_to_self invoked on self"); |
@@ -375,7 +332,7 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe | |||
375 | let usages = def.usages(sema).all(); | 332 | let usages = def.usages(sema).all(); |
376 | let mut source_change = SourceChange::default(); | 333 | let mut source_change = SourceChange::default(); |
377 | source_change.extend(usages.iter().map(|(&file_id, references)| { | 334 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
378 | source_edit_from_references(sema, file_id, references, def, "self") | 335 | (file_id, source_edit_from_references(references, def, "self")) |
379 | })); | 336 | })); |
380 | source_change.insert_source_edit( | 337 | source_change.insert_source_edit( |
381 | file_id.original_file(sema.db), | 338 | file_id.original_file(sema.db), |
@@ -385,29 +342,6 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe | |||
385 | Ok(source_change) | 342 | Ok(source_change) |
386 | } | 343 | } |
387 | 344 | ||
388 | fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> { | ||
389 | fn target_type_name(impl_def: &ast::Impl) -> Option<String> { | ||
390 | if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { | ||
391 | return Some(p.path()?.segment()?.name_ref()?.text().to_string()); | ||
392 | } | ||
393 | None | ||
394 | } | ||
395 | |||
396 | let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?; | ||
397 | let type_name = target_type_name(&impl_def)?; | ||
398 | |||
399 | let mut replacement_text = String::from(new_name); | ||
400 | replacement_text.push_str(": "); | ||
401 | match (self_param.amp_token(), self_param.mut_token()) { | ||
402 | (None, None) => (), | ||
403 | (Some(_), None) => replacement_text.push('&'), | ||
404 | (_, Some(_)) => replacement_text.push_str("&mut "), | ||
405 | }; | ||
406 | replacement_text.push_str(type_name.as_str()); | ||
407 | |||
408 | Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) | ||
409 | } | ||
410 | |||
411 | fn rename_self_to_param( | 345 | fn rename_self_to_param( |
412 | sema: &Semantics<RootDatabase>, | 346 | sema: &Semantics<RootDatabase>, |
413 | local: hir::Local, | 347 | local: hir::Local, |
@@ -432,66 +366,143 @@ fn rename_self_to_param( | |||
432 | let mut source_change = SourceChange::default(); | 366 | let mut source_change = SourceChange::default(); |
433 | source_change.insert_source_edit(file_id.original_file(sema.db), edit); | 367 | source_change.insert_source_edit(file_id.original_file(sema.db), edit); |
434 | source_change.extend(usages.iter().map(|(&file_id, references)| { | 368 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
435 | source_edit_from_references(sema, file_id, &references, def, new_name) | 369 | (file_id, source_edit_from_references(&references, def, new_name)) |
436 | })); | 370 | })); |
437 | Ok(source_change) | 371 | Ok(source_change) |
438 | } | 372 | } |
439 | 373 | ||
440 | fn rename_reference( | 374 | fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> { |
441 | sema: &Semantics<RootDatabase>, | 375 | fn target_type_name(impl_def: &ast::Impl) -> Option<String> { |
376 | if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { | ||
377 | return Some(p.path()?.segment()?.name_ref()?.text().to_string()); | ||
378 | } | ||
379 | None | ||
380 | } | ||
381 | |||
382 | let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?; | ||
383 | let type_name = target_type_name(&impl_def)?; | ||
384 | |||
385 | let mut replacement_text = String::from(new_name); | ||
386 | replacement_text.push_str(": "); | ||
387 | match (self_param.amp_token(), self_param.mut_token()) { | ||
388 | (Some(_), None) => replacement_text.push('&'), | ||
389 | (Some(_), Some(_)) => replacement_text.push_str("&mut "), | ||
390 | (_, _) => (), | ||
391 | }; | ||
392 | replacement_text.push_str(type_name.as_str()); | ||
393 | |||
394 | Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) | ||
395 | } | ||
396 | |||
397 | fn source_edit_from_references( | ||
398 | references: &[FileReference], | ||
442 | def: Definition, | 399 | def: Definition, |
443 | new_name: &str, | 400 | new_name: &str, |
444 | ) -> RenameResult<SourceChange> { | 401 | ) -> TextEdit { |
445 | let ident_kind = check_identifier(new_name)?; | 402 | let mut edit = TextEdit::builder(); |
446 | 403 | for reference in references { | |
447 | let def_is_lbl_or_lt = matches!( | 404 | let (range, replacement) = match &reference.name { |
448 | def, | 405 | // if the ranges differ then the node is inside a macro call, we can't really attempt |
449 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) | 406 | // to make special rewrites like shorthand syntax and such, so just rename the node in |
450 | ); | 407 | // the macro input |
451 | match (ident_kind, def) { | 408 | ast::NameLike::NameRef(name_ref) |
452 | (IdentifierKind::ToSelf, _) | 409 | if name_ref.syntax().text_range() == reference.range => |
453 | | (IdentifierKind::Underscore, _) | 410 | { |
454 | | (IdentifierKind::Ident, _) | 411 | source_edit_from_name_ref(name_ref, new_name, def) |
455 | if def_is_lbl_or_lt => | 412 | } |
456 | { | 413 | ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => { |
457 | mark::hit!(rename_not_a_lifetime_ident_ref); | 414 | source_edit_from_name(name, new_name) |
458 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) | 415 | } |
459 | } | 416 | _ => None, |
460 | (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime), | ||
461 | (IdentifierKind::Lifetime, _) => { | ||
462 | mark::hit!(rename_not_an_ident_ref); | ||
463 | bail!("Invalid name `{}`: not an identifier", new_name) | ||
464 | } | ||
465 | (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => { | ||
466 | // no-op | ||
467 | mark::hit!(rename_self_to_self); | ||
468 | return Ok(SourceChange::default()); | ||
469 | } | ||
470 | (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => { | ||
471 | mark::hit!(rename_self_to_param); | ||
472 | return rename_self_to_param(sema, local, new_name, ident_kind); | ||
473 | } | ||
474 | (IdentifierKind::ToSelf, Definition::Local(local)) => { | ||
475 | mark::hit!(rename_to_self); | ||
476 | return rename_to_self(sema, local); | ||
477 | } | 417 | } |
478 | (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name), | 418 | .unwrap_or_else(|| (reference.range, new_name.to_string())); |
479 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), | 419 | edit.replace(range, replacement); |
480 | } | 420 | } |
421 | edit.finish() | ||
422 | } | ||
481 | 423 | ||
482 | let usages = def.usages(sema).all(); | 424 | fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> { |
483 | if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { | 425 | if let Some(_) = ast::RecordPatField::for_field_name(name) { |
484 | mark::hit!(rename_underscore_multiple); | 426 | if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { |
485 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | 427 | return Some(( |
428 | TextRange::empty(ident_pat.syntax().text_range().start()), | ||
429 | [new_name, ": "].concat(), | ||
430 | )); | ||
431 | } | ||
486 | } | 432 | } |
487 | let mut source_change = SourceChange::default(); | 433 | None |
488 | source_change.extend(usages.iter().map(|(&file_id, references)| { | 434 | } |
489 | source_edit_from_references(sema, file_id, &references, def, new_name) | ||
490 | })); | ||
491 | 435 | ||
492 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; | 436 | fn source_edit_from_name_ref( |
493 | source_change.insert_source_edit(file_id, edit); | 437 | name_ref: &ast::NameRef, |
494 | Ok(source_change) | 438 | new_name: &str, |
439 | def: Definition, | ||
440 | ) -> Option<(TextRange, String)> { | ||
441 | if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { | ||
442 | let rcf_name_ref = record_field.name_ref(); | ||
443 | let rcf_expr = record_field.expr(); | ||
444 | match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) { | ||
445 | // field: init-expr, check if we can use a field init shorthand | ||
446 | (Some(field_name), Some(init)) => { | ||
447 | if field_name == *name_ref { | ||
448 | if init.text() == new_name { | ||
449 | cov_mark::hit!(test_rename_field_put_init_shorthand); | ||
450 | // same names, we can use a shorthand here instead. | ||
451 | // we do not want to erase attributes hence this range start | ||
452 | let s = field_name.syntax().text_range().start(); | ||
453 | let e = record_field.syntax().text_range().end(); | ||
454 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
455 | } | ||
456 | } else if init == *name_ref { | ||
457 | if field_name.text() == new_name { | ||
458 | cov_mark::hit!(test_rename_local_put_init_shorthand); | ||
459 | // same names, we can use a shorthand here instead. | ||
460 | // we do not want to erase attributes hence this range start | ||
461 | let s = field_name.syntax().text_range().start(); | ||
462 | let e = record_field.syntax().text_range().end(); | ||
463 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
464 | } | ||
465 | } | ||
466 | None | ||
467 | } | ||
468 | // init shorthand | ||
469 | // FIXME: instead of splitting the shorthand, recursively trigger a rename of the | ||
470 | // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 | ||
471 | (None, Some(_)) if matches!(def, Definition::Field(_)) => { | ||
472 | cov_mark::hit!(test_rename_field_in_field_shorthand); | ||
473 | let s = name_ref.syntax().text_range().start(); | ||
474 | Some((TextRange::empty(s), format!("{}: ", new_name))) | ||
475 | } | ||
476 | (None, Some(_)) if matches!(def, Definition::Local(_)) => { | ||
477 | cov_mark::hit!(test_rename_local_in_field_shorthand); | ||
478 | let s = name_ref.syntax().text_range().end(); | ||
479 | Some((TextRange::empty(s), format!(": {}", new_name))) | ||
480 | } | ||
481 | _ => None, | ||
482 | } | ||
483 | } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) { | ||
484 | let rcf_name_ref = record_field.name_ref(); | ||
485 | let rcf_pat = record_field.pat(); | ||
486 | match (rcf_name_ref, rcf_pat) { | ||
487 | // field: rename | ||
488 | (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => { | ||
489 | // field name is being renamed | ||
490 | if pat.name().map_or(false, |it| it.text() == new_name) { | ||
491 | cov_mark::hit!(test_rename_field_put_init_shorthand_pat); | ||
492 | // same names, we can use a shorthand here instead/ | ||
493 | // we do not want to erase attributes hence this range start | ||
494 | let s = field_name.syntax().text_range().start(); | ||
495 | let e = record_field.syntax().text_range().end(); | ||
496 | Some((TextRange::new(s, e), pat.to_string())) | ||
497 | } else { | ||
498 | None | ||
499 | } | ||
500 | } | ||
501 | _ => None, | ||
502 | } | ||
503 | } else { | ||
504 | None | ||
505 | } | ||
495 | } | 506 | } |
496 | 507 | ||
497 | fn source_edit_from_def( | 508 | fn source_edit_from_def( |
@@ -529,11 +540,13 @@ fn source_edit_from_def( | |||
529 | mod tests { | 540 | mod tests { |
530 | use expect_test::{expect, Expect}; | 541 | use expect_test::{expect, Expect}; |
531 | use stdx::trim_indent; | 542 | use stdx::trim_indent; |
532 | use test_utils::{assert_eq_text, mark}; | 543 | use test_utils::assert_eq_text; |
533 | use text_edit::TextEdit; | 544 | use text_edit::TextEdit; |
534 | 545 | ||
535 | use crate::{fixture, FileId}; | 546 | use crate::{fixture, FileId}; |
536 | 547 | ||
548 | use super::{RangeInfo, RenameError}; | ||
549 | |||
537 | fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 550 | fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { |
538 | let ra_fixture_after = &trim_indent(ra_fixture_after); | 551 | let ra_fixture_after = &trim_indent(ra_fixture_after); |
539 | let (analysis, position) = fixture::position(ra_fixture_before); | 552 | let (analysis, position) = fixture::position(ra_fixture_before); |
@@ -579,6 +592,45 @@ mod tests { | |||
579 | expect.assert_debug_eq(&source_change) | 592 | expect.assert_debug_eq(&source_change) |
580 | } | 593 | } |
581 | 594 | ||
595 | fn check_prepare(ra_fixture: &str, expect: Expect) { | ||
596 | let (analysis, position) = fixture::position(ra_fixture); | ||
597 | let result = analysis | ||
598 | .prepare_rename(position) | ||
599 | .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {}", err)); | ||
600 | match result { | ||
601 | Ok(RangeInfo { range, info: () }) => { | ||
602 | let source = analysis.file_text(position.file_id).unwrap(); | ||
603 | expect.assert_eq(&format!("{:?}: {}", range, &source[range])) | ||
604 | } | ||
605 | Err(RenameError(err)) => expect.assert_eq(&err), | ||
606 | }; | ||
607 | } | ||
608 | |||
609 | #[test] | ||
610 | fn test_prepare_rename_namelikes() { | ||
611 | check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]); | ||
612 | check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"8..17: 'lifetime"#]]); | ||
613 | check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]); | ||
614 | } | ||
615 | |||
616 | #[test] | ||
617 | fn test_prepare_rename_in_macro() { | ||
618 | check_prepare( | ||
619 | r"macro_rules! foo { | ||
620 | ($ident:ident) => { | ||
621 | pub struct $ident; | ||
622 | } | ||
623 | } | ||
624 | foo!(Foo$0);", | ||
625 | expect![[r#"83..86: Foo"#]], | ||
626 | ); | ||
627 | } | ||
628 | |||
629 | #[test] | ||
630 | fn test_prepare_rename_keyword() { | ||
631 | check_prepare(r"struct$0 Foo;", expect![[r#"No references found at position"#]]); | ||
632 | } | ||
633 | |||
582 | #[test] | 634 | #[test] |
583 | fn test_rename_to_underscore() { | 635 | fn test_rename_to_underscore() { |
584 | check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#); | 636 | check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#); |
@@ -618,7 +670,7 @@ mod tests { | |||
618 | 670 | ||
619 | #[test] | 671 | #[test] |
620 | fn test_rename_to_invalid_identifier_lifetime() { | 672 | fn test_rename_to_invalid_identifier_lifetime() { |
621 | mark::check!(rename_not_an_ident_ref); | 673 | cov_mark::check!(rename_not_an_ident_ref); |
622 | check( | 674 | check( |
623 | "'foo", | 675 | "'foo", |
624 | r#"fn main() { let i$0 = 1; }"#, | 676 | r#"fn main() { let i$0 = 1; }"#, |
@@ -628,7 +680,7 @@ mod tests { | |||
628 | 680 | ||
629 | #[test] | 681 | #[test] |
630 | fn test_rename_to_invalid_identifier_lifetime2() { | 682 | fn test_rename_to_invalid_identifier_lifetime2() { |
631 | mark::check!(rename_not_a_lifetime_ident_ref); | 683 | cov_mark::check!(rename_not_a_lifetime_ident_ref); |
632 | check( | 684 | check( |
633 | "foo", | 685 | "foo", |
634 | r#"fn main<'a>(_: &'a$0 ()) {}"#, | 686 | r#"fn main<'a>(_: &'a$0 ()) {}"#, |
@@ -638,7 +690,7 @@ mod tests { | |||
638 | 690 | ||
639 | #[test] | 691 | #[test] |
640 | fn test_rename_to_underscore_invalid() { | 692 | fn test_rename_to_underscore_invalid() { |
641 | mark::check!(rename_underscore_multiple); | 693 | cov_mark::check!(rename_underscore_multiple); |
642 | check( | 694 | check( |
643 | "_", | 695 | "_", |
644 | r#"fn main(foo$0: ()) {foo;}"#, | 696 | r#"fn main(foo$0: ()) {foo;}"#, |
@@ -657,7 +709,7 @@ mod tests { | |||
657 | 709 | ||
658 | #[test] | 710 | #[test] |
659 | fn test_rename_for_local() { | 711 | fn test_rename_for_local() { |
660 | mark::check!(rename_ident); | 712 | cov_mark::check!(rename_ident); |
661 | check( | 713 | check( |
662 | "k", | 714 | "k", |
663 | r#" | 715 | r#" |
@@ -820,7 +872,7 @@ impl Foo { | |||
820 | 872 | ||
821 | #[test] | 873 | #[test] |
822 | fn test_rename_field_in_field_shorthand() { | 874 | fn test_rename_field_in_field_shorthand() { |
823 | mark::check!(test_rename_field_in_field_shorthand); | 875 | cov_mark::check!(test_rename_field_in_field_shorthand); |
824 | check( | 876 | check( |
825 | "j", | 877 | "j", |
826 | r#" | 878 | r#" |
@@ -846,7 +898,7 @@ impl Foo { | |||
846 | 898 | ||
847 | #[test] | 899 | #[test] |
848 | fn test_rename_local_in_field_shorthand() { | 900 | fn test_rename_local_in_field_shorthand() { |
849 | mark::check!(test_rename_local_in_field_shorthand); | 901 | cov_mark::check!(test_rename_local_in_field_shorthand); |
850 | check( | 902 | check( |
851 | "j", | 903 | "j", |
852 | r#" | 904 | r#" |
@@ -1252,7 +1304,7 @@ fn foo(f: foo::Foo) { | |||
1252 | 1304 | ||
1253 | #[test] | 1305 | #[test] |
1254 | fn test_parameter_to_self() { | 1306 | fn test_parameter_to_self() { |
1255 | mark::check!(rename_to_self); | 1307 | cov_mark::check!(rename_to_self); |
1256 | check( | 1308 | check( |
1257 | "self", | 1309 | "self", |
1258 | r#" | 1310 | r#" |
@@ -1392,7 +1444,7 @@ impl Foo { | |||
1392 | 1444 | ||
1393 | #[test] | 1445 | #[test] |
1394 | fn test_owned_self_to_parameter() { | 1446 | fn test_owned_self_to_parameter() { |
1395 | mark::check!(rename_self_to_param); | 1447 | cov_mark::check!(rename_self_to_param); |
1396 | check( | 1448 | check( |
1397 | "foo", | 1449 | "foo", |
1398 | r#" | 1450 | r#" |
@@ -1445,7 +1497,7 @@ impl Foo { | |||
1445 | 1497 | ||
1446 | #[test] | 1498 | #[test] |
1447 | fn test_rename_field_put_init_shorthand() { | 1499 | fn test_rename_field_put_init_shorthand() { |
1448 | mark::check!(test_rename_field_put_init_shorthand); | 1500 | cov_mark::check!(test_rename_field_put_init_shorthand); |
1449 | check( | 1501 | check( |
1450 | "bar", | 1502 | "bar", |
1451 | r#" | 1503 | r#" |
@@ -1467,7 +1519,7 @@ fn foo(bar: i32) -> Foo { | |||
1467 | 1519 | ||
1468 | #[test] | 1520 | #[test] |
1469 | fn test_rename_local_put_init_shorthand() { | 1521 | fn test_rename_local_put_init_shorthand() { |
1470 | mark::check!(test_rename_local_put_init_shorthand); | 1522 | cov_mark::check!(test_rename_local_put_init_shorthand); |
1471 | check( | 1523 | check( |
1472 | "i", | 1524 | "i", |
1473 | r#" | 1525 | r#" |
@@ -1489,7 +1541,7 @@ fn foo(i: i32) -> Foo { | |||
1489 | 1541 | ||
1490 | #[test] | 1542 | #[test] |
1491 | fn test_struct_field_pat_into_shorthand() { | 1543 | fn test_struct_field_pat_into_shorthand() { |
1492 | mark::check!(test_rename_field_put_init_shorthand_pat); | 1544 | cov_mark::check!(test_rename_field_put_init_shorthand_pat); |
1493 | check( | 1545 | check( |
1494 | "baz", | 1546 | "baz", |
1495 | r#" | 1547 | r#" |
@@ -1601,7 +1653,7 @@ fn foo(foo: Foo) { | |||
1601 | 1653 | ||
1602 | #[test] | 1654 | #[test] |
1603 | fn test_rename_lifetimes() { | 1655 | fn test_rename_lifetimes() { |
1604 | mark::check!(rename_lifetime); | 1656 | cov_mark::check!(rename_lifetime); |
1605 | check( | 1657 | check( |
1606 | "'yeeee", | 1658 | "'yeeee", |
1607 | r#" | 1659 | r#" |
@@ -1689,7 +1741,7 @@ fn foo<'a>() -> &'a () { | |||
1689 | 1741 | ||
1690 | #[test] | 1742 | #[test] |
1691 | fn test_self_to_self() { | 1743 | fn test_self_to_self() { |
1692 | mark::check!(rename_self_to_self); | 1744 | cov_mark::check!(rename_self_to_self); |
1693 | check( | 1745 | check( |
1694 | "self", | 1746 | "self", |
1695 | r#" | 1747 | r#" |
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 368bf9aa5..faa91541e 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs | |||
@@ -16,7 +16,6 @@ use syntax::{ | |||
16 | ast::{self, AstNode, AttrsOwner}, | 16 | ast::{self, AstNode, AttrsOwner}, |
17 | match_ast, SyntaxNode, | 17 | match_ast, SyntaxNode, |
18 | }; | 18 | }; |
19 | use test_utils::mark; | ||
20 | 19 | ||
21 | use crate::{ | 20 | use crate::{ |
22 | display::{ToNav, TryToNav}, | 21 | display::{ToNav, TryToNav}, |
@@ -236,7 +235,9 @@ fn runnables_mod(sema: &Semantics<RootDatabase>, acc: &mut Vec<Runnable>, module | |||
236 | if let hir::ModuleDef::Module(submodule) = def { | 235 | if let hir::ModuleDef::Module(submodule) = def { |
237 | match submodule.definition_source(sema.db).value { | 236 | match submodule.definition_source(sema.db).value { |
238 | hir::ModuleSource::Module(_) => runnables_mod(sema, acc, submodule), | 237 | hir::ModuleSource::Module(_) => runnables_mod(sema, acc, submodule), |
239 | hir::ModuleSource::SourceFile(_) => mark::hit!(dont_recurse_in_outline_submodules), | 238 | hir::ModuleSource::SourceFile(_) => { |
239 | cov_mark::hit!(dont_recurse_in_outline_submodules) | ||
240 | } | ||
240 | hir::ModuleSource::BlockExpr(_) => {} // inner items aren't runnable | 241 | hir::ModuleSource::BlockExpr(_) => {} // inner items aren't runnable |
241 | } | 242 | } |
242 | } | 243 | } |
@@ -295,7 +296,7 @@ pub(crate) fn doc_owner_to_def( | |||
295 | ) -> Option<Definition> { | 296 | ) -> Option<Definition> { |
296 | let res: hir::ModuleDef = match_ast! { | 297 | let res: hir::ModuleDef = match_ast! { |
297 | match item { | 298 | match item { |
298 | ast::SourceFile(it) => sema.scope(&item).module()?.into(), | 299 | ast::SourceFile(_it) => sema.scope(&item).module()?.into(), |
299 | ast::Fn(it) => sema.to_def(&it)?.into(), | 300 | ast::Fn(it) => sema.to_def(&it)?.into(), |
300 | ast::Struct(it) => sema.to_def(&it)?.into(), | 301 | ast::Struct(it) => sema.to_def(&it)?.into(), |
301 | ast::Enum(it) => sema.to_def(&it)?.into(), | 302 | ast::Enum(it) => sema.to_def(&it)?.into(), |
@@ -434,7 +435,6 @@ fn has_test_function_or_multiple_test_submodules( | |||
434 | #[cfg(test)] | 435 | #[cfg(test)] |
435 | mod tests { | 436 | mod tests { |
436 | use expect_test::{expect, Expect}; | 437 | use expect_test::{expect, Expect}; |
437 | use test_utils::mark; | ||
438 | 438 | ||
439 | use crate::fixture; | 439 | use crate::fixture; |
440 | 440 | ||
@@ -1168,7 +1168,7 @@ mod tests { | |||
1168 | 1168 | ||
1169 | #[test] | 1169 | #[test] |
1170 | fn dont_recurse_in_outline_submodules() { | 1170 | fn dont_recurse_in_outline_submodules() { |
1171 | mark::check!(dont_recurse_in_outline_submodules); | 1171 | cov_mark::check!(dont_recurse_in_outline_submodules); |
1172 | check( | 1172 | check( |
1173 | r#" | 1173 | r#" |
1174 | //- /lib.rs | 1174 | //- /lib.rs |
diff --git a/crates/ide/src/ssr.rs b/crates/ide/src/ssr.rs new file mode 100644 index 000000000..f3638d928 --- /dev/null +++ b/crates/ide/src/ssr.rs | |||
@@ -0,0 +1,259 @@ | |||
1 | //! This module provides an SSR assist. It is not desirable to include this | ||
2 | //! assist in ide_assists because that would require the ide_assists crate | ||
3 | //! depend on the ide_ssr crate. | ||
4 | |||
5 | use ide_assists::{Assist, AssistId, AssistKind, GroupLabel}; | ||
6 | use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase}; | ||
7 | |||
8 | pub(crate) fn add_ssr_assist( | ||
9 | db: &RootDatabase, | ||
10 | base: &mut Vec<Assist>, | ||
11 | resolve: bool, | ||
12 | frange: FileRange, | ||
13 | ) -> Option<()> { | ||
14 | let (match_finder, comment_range) = ide_ssr::ssr_from_comment(db, frange)?; | ||
15 | |||
16 | let (source_change_for_file, source_change_for_workspace) = if resolve { | ||
17 | let edits = match_finder.edits(); | ||
18 | |||
19 | let source_change_for_file = { | ||
20 | let text_edit_for_file = edits.get(&frange.file_id).cloned().unwrap_or_default(); | ||
21 | SourceChange::from_text_edit(frange.file_id, text_edit_for_file) | ||
22 | }; | ||
23 | |||
24 | let source_change_for_workspace = SourceChange::from(match_finder.edits()); | ||
25 | |||
26 | (Some(source_change_for_file), Some(source_change_for_workspace)) | ||
27 | } else { | ||
28 | (None, None) | ||
29 | }; | ||
30 | |||
31 | let assists = vec![ | ||
32 | ("Apply SSR in file", source_change_for_file), | ||
33 | ("Apply SSR in workspace", source_change_for_workspace), | ||
34 | ]; | ||
35 | |||
36 | for (label, source_change) in assists.into_iter() { | ||
37 | let assist = Assist { | ||
38 | id: AssistId("ssr", AssistKind::RefactorRewrite), | ||
39 | label: Label::new(label), | ||
40 | group: Some(GroupLabel("Apply SSR".into())), | ||
41 | target: comment_range, | ||
42 | source_change, | ||
43 | }; | ||
44 | |||
45 | base.push(assist); | ||
46 | } | ||
47 | Some(()) | ||
48 | } | ||
49 | |||
50 | #[cfg(test)] | ||
51 | mod tests { | ||
52 | use std::sync::Arc; | ||
53 | |||
54 | use expect_test::expect; | ||
55 | use ide_assists::Assist; | ||
56 | use ide_db::{ | ||
57 | base_db::{fixture::WithFixture, salsa::Durability, FileRange}, | ||
58 | symbol_index::SymbolsDatabase, | ||
59 | RootDatabase, | ||
60 | }; | ||
61 | use rustc_hash::FxHashSet; | ||
62 | |||
63 | use super::add_ssr_assist; | ||
64 | |||
65 | fn get_assists(ra_fixture: &str, resolve: bool) -> Vec<Assist> { | ||
66 | let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture); | ||
67 | let mut local_roots = FxHashSet::default(); | ||
68 | local_roots.insert(ide_db::base_db::fixture::WORKSPACE); | ||
69 | db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); | ||
70 | |||
71 | let mut assists = vec![]; | ||
72 | |||
73 | add_ssr_assist( | ||
74 | &db, | ||
75 | &mut assists, | ||
76 | resolve, | ||
77 | FileRange { file_id, range: range_or_offset.into() }, | ||
78 | ); | ||
79 | |||
80 | assists | ||
81 | } | ||
82 | |||
83 | #[test] | ||
84 | fn not_applicable_comment_not_ssr() { | ||
85 | let ra_fixture = r#" | ||
86 | //- /lib.rs | ||
87 | |||
88 | // This is foo $0 | ||
89 | fn foo() {} | ||
90 | "#; | ||
91 | let resolve = true; | ||
92 | |||
93 | let assists = get_assists(ra_fixture, resolve); | ||
94 | |||
95 | assert_eq!(0, assists.len()); | ||
96 | } | ||
97 | |||
98 | #[test] | ||
99 | fn resolve_edits_true() { | ||
100 | let resolve = true; | ||
101 | let assists = get_assists( | ||
102 | r#" | ||
103 | //- /lib.rs | ||
104 | mod bar; | ||
105 | |||
106 | // 2 ==>> 3$0 | ||
107 | fn foo() { 2 } | ||
108 | |||
109 | //- /bar.rs | ||
110 | fn bar() { 2 } | ||
111 | "#, | ||
112 | resolve, | ||
113 | ); | ||
114 | |||
115 | assert_eq!(2, assists.len()); | ||
116 | let mut assists = assists.into_iter(); | ||
117 | |||
118 | let apply_in_file_assist = assists.next().unwrap(); | ||
119 | expect![[r#" | ||
120 | Assist { | ||
121 | id: AssistId( | ||
122 | "ssr", | ||
123 | RefactorRewrite, | ||
124 | ), | ||
125 | label: "Apply SSR in file", | ||
126 | group: Some( | ||
127 | GroupLabel( | ||
128 | "Apply SSR", | ||
129 | ), | ||
130 | ), | ||
131 | target: 10..21, | ||
132 | source_change: Some( | ||
133 | SourceChange { | ||
134 | source_file_edits: { | ||
135 | FileId( | ||
136 | 0, | ||
137 | ): TextEdit { | ||
138 | indels: [ | ||
139 | Indel { | ||
140 | insert: "3", | ||
141 | delete: 33..34, | ||
142 | }, | ||
143 | ], | ||
144 | }, | ||
145 | }, | ||
146 | file_system_edits: [], | ||
147 | is_snippet: false, | ||
148 | }, | ||
149 | ), | ||
150 | } | ||
151 | "#]] | ||
152 | .assert_debug_eq(&apply_in_file_assist); | ||
153 | |||
154 | let apply_in_workspace_assist = assists.next().unwrap(); | ||
155 | expect![[r#" | ||
156 | Assist { | ||
157 | id: AssistId( | ||
158 | "ssr", | ||
159 | RefactorRewrite, | ||
160 | ), | ||
161 | label: "Apply SSR in workspace", | ||
162 | group: Some( | ||
163 | GroupLabel( | ||
164 | "Apply SSR", | ||
165 | ), | ||
166 | ), | ||
167 | target: 10..21, | ||
168 | source_change: Some( | ||
169 | SourceChange { | ||
170 | source_file_edits: { | ||
171 | FileId( | ||
172 | 0, | ||
173 | ): TextEdit { | ||
174 | indels: [ | ||
175 | Indel { | ||
176 | insert: "3", | ||
177 | delete: 33..34, | ||
178 | }, | ||
179 | ], | ||
180 | }, | ||
181 | FileId( | ||
182 | 1, | ||
183 | ): TextEdit { | ||
184 | indels: [ | ||
185 | Indel { | ||
186 | insert: "3", | ||
187 | delete: 11..12, | ||
188 | }, | ||
189 | ], | ||
190 | }, | ||
191 | }, | ||
192 | file_system_edits: [], | ||
193 | is_snippet: false, | ||
194 | }, | ||
195 | ), | ||
196 | } | ||
197 | "#]] | ||
198 | .assert_debug_eq(&apply_in_workspace_assist); | ||
199 | } | ||
200 | |||
201 | #[test] | ||
202 | fn resolve_edits_false() { | ||
203 | let resolve = false; | ||
204 | let assists = get_assists( | ||
205 | r#" | ||
206 | //- /lib.rs | ||
207 | mod bar; | ||
208 | |||
209 | // 2 ==>> 3$0 | ||
210 | fn foo() { 2 } | ||
211 | |||
212 | //- /bar.rs | ||
213 | fn bar() { 2 } | ||
214 | "#, | ||
215 | resolve, | ||
216 | ); | ||
217 | |||
218 | assert_eq!(2, assists.len()); | ||
219 | let mut assists = assists.into_iter(); | ||
220 | |||
221 | let apply_in_file_assist = assists.next().unwrap(); | ||
222 | expect![[r#" | ||
223 | Assist { | ||
224 | id: AssistId( | ||
225 | "ssr", | ||
226 | RefactorRewrite, | ||
227 | ), | ||
228 | label: "Apply SSR in file", | ||
229 | group: Some( | ||
230 | GroupLabel( | ||
231 | "Apply SSR", | ||
232 | ), | ||
233 | ), | ||
234 | target: 10..21, | ||
235 | source_change: None, | ||
236 | } | ||
237 | "#]] | ||
238 | .assert_debug_eq(&apply_in_file_assist); | ||
239 | |||
240 | let apply_in_workspace_assist = assists.next().unwrap(); | ||
241 | expect![[r#" | ||
242 | Assist { | ||
243 | id: AssistId( | ||
244 | "ssr", | ||
245 | RefactorRewrite, | ||
246 | ), | ||
247 | label: "Apply SSR in workspace", | ||
248 | group: Some( | ||
249 | GroupLabel( | ||
250 | "Apply SSR", | ||
251 | ), | ||
252 | ), | ||
253 | target: 10..21, | ||
254 | source_change: None, | ||
255 | } | ||
256 | "#]] | ||
257 | .assert_debug_eq(&apply_in_workspace_assist); | ||
258 | } | ||
259 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 24fcbb584..b0cfdd8b7 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs | |||
@@ -330,10 +330,11 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight { | |||
330 | HlTag::Symbol(SymbolKind::Local) | 330 | HlTag::Symbol(SymbolKind::Local) |
331 | }; | 331 | }; |
332 | let mut h = Highlight::new(tag); | 332 | let mut h = Highlight::new(tag); |
333 | if local.is_mut(db) || local.ty(db).is_mutable_reference() { | 333 | let ty = local.ty(db); |
334 | if local.is_mut(db) || ty.is_mutable_reference() { | ||
334 | h |= HlMod::Mutable; | 335 | h |= HlMod::Mutable; |
335 | } | 336 | } |
336 | if local.ty(db).as_callable(db).is_some() || local.ty(db).impls_fnonce(db) { | 337 | if ty.as_callable(db).is_some() || ty.impls_fnonce(db) { |
337 | h |= HlMod::Callable; | 338 | h |= HlMod::Callable; |
338 | } | 339 | } |
339 | return h; | 340 | return h; |
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs index 63cd51b69..978c479de 100644 --- a/crates/ide/src/typing/on_enter.rs +++ b/crates/ide/src/typing/on_enter.rs | |||
@@ -9,7 +9,7 @@ use syntax::{ | |||
9 | SyntaxKind::*, | 9 | SyntaxKind::*, |
10 | SyntaxToken, TextRange, TextSize, TokenAtOffset, | 10 | SyntaxToken, TextRange, TextSize, TokenAtOffset, |
11 | }; | 11 | }; |
12 | use test_utils::mark; | 12 | |
13 | use text_edit::TextEdit; | 13 | use text_edit::TextEdit; |
14 | 14 | ||
15 | // Feature: On Enter | 15 | // Feature: On Enter |
@@ -55,7 +55,7 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text | |||
55 | // Continuing single-line non-doc comments (like this one :) ) is annoying | 55 | // Continuing single-line non-doc comments (like this one :) ) is annoying |
56 | if prefix == "//" && comment_range.end() == position.offset { | 56 | if prefix == "//" && comment_range.end() == position.offset { |
57 | if comment.text().ends_with(' ') { | 57 | if comment.text().ends_with(' ') { |
58 | mark::hit!(continues_end_of_line_comment_with_space); | 58 | cov_mark::hit!(continues_end_of_line_comment_with_space); |
59 | remove_trailing_whitespace = true; | 59 | remove_trailing_whitespace = true; |
60 | } else if !followed_by_comment(&comment) { | 60 | } else if !followed_by_comment(&comment) { |
61 | return None; | 61 | return None; |
@@ -109,7 +109,7 @@ fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option<SmolStr> { | |||
109 | #[cfg(test)] | 109 | #[cfg(test)] |
110 | mod tests { | 110 | mod tests { |
111 | use stdx::trim_indent; | 111 | use stdx::trim_indent; |
112 | use test_utils::{assert_eq_text, mark}; | 112 | use test_utils::assert_eq_text; |
113 | 113 | ||
114 | use crate::fixture; | 114 | use crate::fixture; |
115 | 115 | ||
@@ -238,7 +238,7 @@ fn main() { | |||
238 | 238 | ||
239 | #[test] | 239 | #[test] |
240 | fn continues_end_of_line_comment_with_space() { | 240 | fn continues_end_of_line_comment_with_space() { |
241 | mark::check!(continues_end_of_line_comment_with_space); | 241 | cov_mark::check!(continues_end_of_line_comment_with_space); |
242 | do_check( | 242 | do_check( |
243 | r#" | 243 | r#" |
244 | fn main() { | 244 | fn main() { |