diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ide/src/diagnostics.rs | 43 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes.rs | 51 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/unlinked_file.rs | 7 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 39 | ||||
-rw-r--r-- | crates/ide/src/ssr.rs | 54 | ||||
-rw-r--r-- | crates/ide_assists/src/assist_context.rs | 10 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 68 | ||||
-rw-r--r-- | crates/ide_assists/src/tests.rs | 262 | ||||
-rw-r--r-- | crates/rust-analyzer/src/cli/diagnostics.rs | 7 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 72 | ||||
-rw-r--r-- | crates/rust-analyzer/src/to_proto.rs | 2 |
11 files changed, 518 insertions, 97 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 1c911a8b2..b14f908b7 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -15,6 +15,7 @@ use hir::{ | |||
15 | diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, | 15 | diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, |
16 | InFile, Semantics, | 16 | InFile, Semantics, |
17 | }; | 17 | }; |
18 | use ide_assists::AssistResolveStrategy; | ||
18 | use ide_db::{base_db::SourceDatabase, RootDatabase}; | 19 | use ide_db::{base_db::SourceDatabase, RootDatabase}; |
19 | use itertools::Itertools; | 20 | use itertools::Itertools; |
20 | use rustc_hash::FxHashSet; | 21 | use rustc_hash::FxHashSet; |
@@ -84,7 +85,7 @@ pub struct DiagnosticsConfig { | |||
84 | pub(crate) fn diagnostics( | 85 | pub(crate) fn diagnostics( |
85 | db: &RootDatabase, | 86 | db: &RootDatabase, |
86 | config: &DiagnosticsConfig, | 87 | config: &DiagnosticsConfig, |
87 | resolve: bool, | 88 | resolve: &AssistResolveStrategy, |
88 | file_id: FileId, | 89 | file_id: FileId, |
89 | ) -> Vec<Diagnostic> { | 90 | ) -> Vec<Diagnostic> { |
90 | let _p = profile::span("diagnostics"); | 91 | let _p = profile::span("diagnostics"); |
@@ -212,7 +213,7 @@ pub(crate) fn diagnostics( | |||
212 | fn diagnostic_with_fix<D: DiagnosticWithFix>( | 213 | fn diagnostic_with_fix<D: DiagnosticWithFix>( |
213 | d: &D, | 214 | d: &D, |
214 | sema: &Semantics<RootDatabase>, | 215 | sema: &Semantics<RootDatabase>, |
215 | resolve: bool, | 216 | resolve: &AssistResolveStrategy, |
216 | ) -> Diagnostic { | 217 | ) -> Diagnostic { |
217 | Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) | 218 | Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) |
218 | .with_fix(d.fix(&sema, resolve)) | 219 | .with_fix(d.fix(&sema, resolve)) |
@@ -222,7 +223,7 @@ fn diagnostic_with_fix<D: DiagnosticWithFix>( | |||
222 | fn warning_with_fix<D: DiagnosticWithFix>( | 223 | fn warning_with_fix<D: DiagnosticWithFix>( |
223 | d: &D, | 224 | d: &D, |
224 | sema: &Semantics<RootDatabase>, | 225 | sema: &Semantics<RootDatabase>, |
225 | resolve: bool, | 226 | resolve: &AssistResolveStrategy, |
226 | ) -> Diagnostic { | 227 | ) -> Diagnostic { |
227 | Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) | 228 | Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) |
228 | .with_fix(d.fix(&sema, resolve)) | 229 | .with_fix(d.fix(&sema, resolve)) |
@@ -299,6 +300,7 @@ fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist { | |||
299 | #[cfg(test)] | 300 | #[cfg(test)] |
300 | mod tests { | 301 | mod tests { |
301 | use expect_test::{expect, Expect}; | 302 | use expect_test::{expect, Expect}; |
303 | use ide_assists::AssistResolveStrategy; | ||
302 | use stdx::trim_indent; | 304 | use stdx::trim_indent; |
303 | use test_utils::assert_eq_text; | 305 | use test_utils::assert_eq_text; |
304 | 306 | ||
@@ -314,7 +316,11 @@ mod tests { | |||
314 | 316 | ||
315 | let (analysis, file_position) = fixture::position(ra_fixture_before); | 317 | let (analysis, file_position) = fixture::position(ra_fixture_before); |
316 | let diagnostic = analysis | 318 | let diagnostic = analysis |
317 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) | 319 | .diagnostics( |
320 | &DiagnosticsConfig::default(), | ||
321 | AssistResolveStrategy::All, | ||
322 | file_position.file_id, | ||
323 | ) | ||
318 | .unwrap() | 324 | .unwrap() |
319 | .pop() | 325 | .pop() |
320 | .unwrap(); | 326 | .unwrap(); |
@@ -343,7 +349,11 @@ mod tests { | |||
343 | fn check_no_fix(ra_fixture: &str) { | 349 | fn check_no_fix(ra_fixture: &str) { |
344 | let (analysis, file_position) = fixture::position(ra_fixture); | 350 | let (analysis, file_position) = fixture::position(ra_fixture); |
345 | let diagnostic = analysis | 351 | let diagnostic = analysis |
346 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) | 352 | .diagnostics( |
353 | &DiagnosticsConfig::default(), | ||
354 | AssistResolveStrategy::All, | ||
355 | file_position.file_id, | ||
356 | ) | ||
347 | .unwrap() | 357 | .unwrap() |
348 | .pop() | 358 | .pop() |
349 | .unwrap(); | 359 | .unwrap(); |
@@ -357,7 +367,9 @@ mod tests { | |||
357 | let diagnostics = files | 367 | let diagnostics = files |
358 | .into_iter() | 368 | .into_iter() |
359 | .flat_map(|file_id| { | 369 | .flat_map(|file_id| { |
360 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap() | 370 | analysis |
371 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) | ||
372 | .unwrap() | ||
361 | }) | 373 | }) |
362 | .collect::<Vec<_>>(); | 374 | .collect::<Vec<_>>(); |
363 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); | 375 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); |
@@ -365,8 +377,9 @@ mod tests { | |||
365 | 377 | ||
366 | fn check_expect(ra_fixture: &str, expect: Expect) { | 378 | fn check_expect(ra_fixture: &str, expect: Expect) { |
367 | let (analysis, file_id) = fixture::file(ra_fixture); | 379 | let (analysis, file_id) = fixture::file(ra_fixture); |
368 | let diagnostics = | 380 | let diagnostics = analysis |
369 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); | 381 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) |
382 | .unwrap(); | ||
370 | expect.assert_debug_eq(&diagnostics) | 383 | expect.assert_debug_eq(&diagnostics) |
371 | } | 384 | } |
372 | 385 | ||
@@ -911,11 +924,13 @@ struct Foo { | |||
911 | 924 | ||
912 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); | 925 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); |
913 | 926 | ||
914 | let diagnostics = analysis.diagnostics(&config, true, file_id).unwrap(); | 927 | let diagnostics = |
928 | analysis.diagnostics(&config, AssistResolveStrategy::All, file_id).unwrap(); | ||
915 | assert!(diagnostics.is_empty()); | 929 | assert!(diagnostics.is_empty()); |
916 | 930 | ||
917 | let diagnostics = | 931 | let diagnostics = analysis |
918 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); | 932 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) |
933 | .unwrap(); | ||
919 | assert!(!diagnostics.is_empty()); | 934 | assert!(!diagnostics.is_empty()); |
920 | } | 935 | } |
921 | 936 | ||
@@ -1022,7 +1037,11 @@ impl TestStruct { | |||
1022 | 1037 | ||
1023 | let (analysis, file_position) = fixture::position(input); | 1038 | let (analysis, file_position) = fixture::position(input); |
1024 | let diagnostics = analysis | 1039 | let diagnostics = analysis |
1025 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) | 1040 | .diagnostics( |
1041 | &DiagnosticsConfig::default(), | ||
1042 | AssistResolveStrategy::All, | ||
1043 | file_position.file_id, | ||
1044 | ) | ||
1026 | .unwrap(); | 1045 | .unwrap(); |
1027 | assert_eq!(diagnostics.len(), 1); | 1046 | assert_eq!(diagnostics.len(), 1); |
1028 | 1047 | ||
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs index 7be8b3459..15821500f 100644 --- a/crates/ide/src/diagnostics/fixes.rs +++ b/crates/ide/src/diagnostics/fixes.rs | |||
@@ -8,6 +8,7 @@ use hir::{ | |||
8 | }, | 8 | }, |
9 | HasSource, HirDisplay, InFile, Semantics, VariantDef, | 9 | HasSource, HirDisplay, InFile, Semantics, VariantDef, |
10 | }; | 10 | }; |
11 | use ide_assists::AssistResolveStrategy; | ||
11 | use ide_db::{ | 12 | use ide_db::{ |
12 | base_db::{AnchoredPathBuf, FileId}, | 13 | base_db::{AnchoredPathBuf, FileId}, |
13 | source_change::{FileSystemEdit, SourceChange}, | 14 | source_change::{FileSystemEdit, SourceChange}, |
@@ -35,11 +36,19 @@ pub(crate) trait DiagnosticWithFix: Diagnostic { | |||
35 | /// | 36 | /// |
36 | /// If `resolve` is false, the edit will be computed later, on demand, and | 37 | /// If `resolve` is false, the edit will be computed later, on demand, and |
37 | /// can be omitted. | 38 | /// can be omitted. |
38 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist>; | 39 | fn fix( |
40 | &self, | ||
41 | sema: &Semantics<RootDatabase>, | ||
42 | _resolve: &AssistResolveStrategy, | ||
43 | ) -> Option<Assist>; | ||
39 | } | 44 | } |
40 | 45 | ||
41 | impl DiagnosticWithFix for UnresolvedModule { | 46 | impl DiagnosticWithFix for UnresolvedModule { |
42 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { | 47 | fn fix( |
48 | &self, | ||
49 | sema: &Semantics<RootDatabase>, | ||
50 | _resolve: &AssistResolveStrategy, | ||
51 | ) -> Option<Assist> { | ||
43 | let root = sema.db.parse_or_expand(self.file)?; | 52 | let root = sema.db.parse_or_expand(self.file)?; |
44 | let unresolved_module = self.decl.to_node(&root); | 53 | let unresolved_module = self.decl.to_node(&root); |
45 | Some(fix( | 54 | Some(fix( |
@@ -59,7 +68,11 @@ impl DiagnosticWithFix for UnresolvedModule { | |||
59 | } | 68 | } |
60 | 69 | ||
61 | impl DiagnosticWithFix for NoSuchField { | 70 | impl DiagnosticWithFix for NoSuchField { |
62 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { | 71 | fn fix( |
72 | &self, | ||
73 | sema: &Semantics<RootDatabase>, | ||
74 | _resolve: &AssistResolveStrategy, | ||
75 | ) -> Option<Assist> { | ||
63 | let root = sema.db.parse_or_expand(self.file)?; | 76 | let root = sema.db.parse_or_expand(self.file)?; |
64 | missing_record_expr_field_fix( | 77 | missing_record_expr_field_fix( |
65 | &sema, | 78 | &sema, |
@@ -70,7 +83,11 @@ impl DiagnosticWithFix for NoSuchField { | |||
70 | } | 83 | } |
71 | 84 | ||
72 | impl DiagnosticWithFix for MissingFields { | 85 | impl DiagnosticWithFix for MissingFields { |
73 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { | 86 | fn fix( |
87 | &self, | ||
88 | sema: &Semantics<RootDatabase>, | ||
89 | _resolve: &AssistResolveStrategy, | ||
90 | ) -> Option<Assist> { | ||
74 | // Note that although we could add a diagnostics to | 91 | // Note that although we could add a diagnostics to |
75 | // fill the missing tuple field, e.g : | 92 | // fill the missing tuple field, e.g : |
76 | // `struct A(usize);` | 93 | // `struct A(usize);` |
@@ -106,7 +123,11 @@ impl DiagnosticWithFix for MissingFields { | |||
106 | } | 123 | } |
107 | 124 | ||
108 | impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { | 125 | impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { |
109 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { | 126 | fn fix( |
127 | &self, | ||
128 | sema: &Semantics<RootDatabase>, | ||
129 | _resolve: &AssistResolveStrategy, | ||
130 | ) -> Option<Assist> { | ||
110 | let root = sema.db.parse_or_expand(self.file)?; | 131 | let root = sema.db.parse_or_expand(self.file)?; |
111 | let tail_expr = self.expr.to_node(&root); | 132 | let tail_expr = self.expr.to_node(&root); |
112 | let tail_expr_range = tail_expr.syntax().text_range(); | 133 | let tail_expr_range = tail_expr.syntax().text_range(); |
@@ -119,7 +140,11 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { | |||
119 | } | 140 | } |
120 | 141 | ||
121 | impl DiagnosticWithFix for RemoveThisSemicolon { | 142 | impl DiagnosticWithFix for RemoveThisSemicolon { |
122 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { | 143 | fn fix( |
144 | &self, | ||
145 | sema: &Semantics<RootDatabase>, | ||
146 | _resolve: &AssistResolveStrategy, | ||
147 | ) -> Option<Assist> { | ||
123 | let root = sema.db.parse_or_expand(self.file)?; | 148 | let root = sema.db.parse_or_expand(self.file)?; |
124 | 149 | ||
125 | let semicolon = self | 150 | let semicolon = self |
@@ -139,7 +164,11 @@ impl DiagnosticWithFix for RemoveThisSemicolon { | |||
139 | } | 164 | } |
140 | 165 | ||
141 | impl DiagnosticWithFix for IncorrectCase { | 166 | impl DiagnosticWithFix for IncorrectCase { |
142 | fn fix(&self, sema: &Semantics<RootDatabase>, resolve: bool) -> Option<Assist> { | 167 | fn fix( |
168 | &self, | ||
169 | sema: &Semantics<RootDatabase>, | ||
170 | resolve: &AssistResolveStrategy, | ||
171 | ) -> Option<Assist> { | ||
143 | let root = sema.db.parse_or_expand(self.file)?; | 172 | let root = sema.db.parse_or_expand(self.file)?; |
144 | let name_node = self.ident.to_node(&root); | 173 | let name_node = self.ident.to_node(&root); |
145 | 174 | ||
@@ -149,7 +178,7 @@ impl DiagnosticWithFix for IncorrectCase { | |||
149 | 178 | ||
150 | let label = format!("Rename to {}", self.suggested_text); | 179 | let label = format!("Rename to {}", self.suggested_text); |
151 | let mut res = unresolved_fix("change_case", &label, frange.range); | 180 | let mut res = unresolved_fix("change_case", &label, frange.range); |
152 | if resolve { | 181 | if resolve.should_resolve(&res.id) { |
153 | let source_change = rename_with_semantics(sema, file_position, &self.suggested_text); | 182 | let source_change = rename_with_semantics(sema, file_position, &self.suggested_text); |
154 | res.source_change = Some(source_change.ok().unwrap_or_default()); | 183 | res.source_change = Some(source_change.ok().unwrap_or_default()); |
155 | } | 184 | } |
@@ -159,7 +188,11 @@ impl DiagnosticWithFix for IncorrectCase { | |||
159 | } | 188 | } |
160 | 189 | ||
161 | impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { | 190 | impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { |
162 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { | 191 | fn fix( |
192 | &self, | ||
193 | sema: &Semantics<RootDatabase>, | ||
194 | _resolve: &AssistResolveStrategy, | ||
195 | ) -> Option<Assist> { | ||
163 | let root = sema.db.parse_or_expand(self.file)?; | 196 | let root = sema.db.parse_or_expand(self.file)?; |
164 | let next_expr = self.next_expr.to_node(&root); | 197 | let next_expr = self.next_expr.to_node(&root); |
165 | let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; | 198 | let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; |
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide/src/diagnostics/unlinked_file.rs index 7d39f4fbe..93fd25dea 100644 --- a/crates/ide/src/diagnostics/unlinked_file.rs +++ b/crates/ide/src/diagnostics/unlinked_file.rs | |||
@@ -5,6 +5,7 @@ use hir::{ | |||
5 | diagnostics::{Diagnostic, DiagnosticCode}, | 5 | diagnostics::{Diagnostic, DiagnosticCode}, |
6 | InFile, | 6 | InFile, |
7 | }; | 7 | }; |
8 | use ide_assists::AssistResolveStrategy; | ||
8 | use ide_db::{ | 9 | use ide_db::{ |
9 | base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt}, | 10 | base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt}, |
10 | source_change::SourceChange, | 11 | source_change::SourceChange, |
@@ -50,7 +51,11 @@ impl Diagnostic for UnlinkedFile { | |||
50 | } | 51 | } |
51 | 52 | ||
52 | impl DiagnosticWithFix for UnlinkedFile { | 53 | impl DiagnosticWithFix for UnlinkedFile { |
53 | fn fix(&self, sema: &hir::Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { | 54 | fn fix( |
55 | &self, | ||
56 | sema: &hir::Semantics<RootDatabase>, | ||
57 | _resolve: &AssistResolveStrategy, | ||
58 | ) -> Option<Assist> { | ||
54 | // If there's an existing module that could add a `mod` item to include the unlinked file, | 59 | // If there's an existing module that could add a `mod` item to include the unlinked file, |
55 | // suggest that as a fix. | 60 | // suggest that as a fix. |
56 | 61 | ||
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 99e45633e..8e5b72044 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -87,7 +87,9 @@ pub use crate::{ | |||
87 | }, | 87 | }, |
88 | }; | 88 | }; |
89 | pub use hir::{Documentation, Semantics}; | 89 | pub use hir::{Documentation, Semantics}; |
90 | pub use ide_assists::{Assist, AssistConfig, AssistId, AssistKind}; | 90 | pub use ide_assists::{ |
91 | Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve, | ||
92 | }; | ||
91 | pub use ide_completion::{ | 93 | pub use ide_completion::{ |
92 | CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit, | 94 | CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit, |
93 | InsertTextFormat, | 95 | InsertTextFormat, |
@@ -518,12 +520,13 @@ impl Analysis { | |||
518 | pub fn assists( | 520 | pub fn assists( |
519 | &self, | 521 | &self, |
520 | config: &AssistConfig, | 522 | config: &AssistConfig, |
521 | resolve: bool, | 523 | resolve: AssistResolveStrategy, |
522 | frange: FileRange, | 524 | frange: FileRange, |
523 | ) -> Cancelable<Vec<Assist>> { | 525 | ) -> Cancelable<Vec<Assist>> { |
524 | self.with_db(|db| { | 526 | self.with_db(|db| { |
527 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); | ||
525 | let mut acc = Assist::get(db, config, resolve, frange); | 528 | let mut acc = Assist::get(db, config, resolve, frange); |
526 | ssr::add_ssr_assist(db, &mut acc, resolve, frange); | 529 | acc.extend(ssr_assists.into_iter()); |
527 | acc | 530 | acc |
528 | }) | 531 | }) |
529 | } | 532 | } |
@@ -532,10 +535,10 @@ impl Analysis { | |||
532 | pub fn diagnostics( | 535 | pub fn diagnostics( |
533 | &self, | 536 | &self, |
534 | config: &DiagnosticsConfig, | 537 | config: &DiagnosticsConfig, |
535 | resolve: bool, | 538 | resolve: AssistResolveStrategy, |
536 | file_id: FileId, | 539 | file_id: FileId, |
537 | ) -> Cancelable<Vec<Diagnostic>> { | 540 | ) -> Cancelable<Vec<Diagnostic>> { |
538 | self.with_db(|db| diagnostics::diagnostics(db, config, resolve, file_id)) | 541 | self.with_db(|db| diagnostics::diagnostics(db, config, &resolve, file_id)) |
539 | } | 542 | } |
540 | 543 | ||
541 | /// Convenience function to return assists + quick fixes for diagnostics | 544 | /// Convenience function to return assists + quick fixes for diagnostics |
@@ -543,7 +546,7 @@ impl Analysis { | |||
543 | &self, | 546 | &self, |
544 | assist_config: &AssistConfig, | 547 | assist_config: &AssistConfig, |
545 | diagnostics_config: &DiagnosticsConfig, | 548 | diagnostics_config: &DiagnosticsConfig, |
546 | resolve: bool, | 549 | resolve: AssistResolveStrategy, |
547 | frange: FileRange, | 550 | frange: FileRange, |
548 | ) -> Cancelable<Vec<Assist>> { | 551 | ) -> Cancelable<Vec<Assist>> { |
549 | let include_fixes = match &assist_config.allowed { | 552 | let include_fixes = match &assist_config.allowed { |
@@ -552,17 +555,21 @@ impl Analysis { | |||
552 | }; | 555 | }; |
553 | 556 | ||
554 | self.with_db(|db| { | 557 | self.with_db(|db| { |
558 | let ssr_assists = ssr::ssr_assists(db, &resolve, frange); | ||
559 | let diagnostic_assists = if include_fixes { | ||
560 | diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id) | ||
561 | .into_iter() | ||
562 | .filter_map(|it| it.fix) | ||
563 | .filter(|it| it.target.intersect(frange.range).is_some()) | ||
564 | .collect() | ||
565 | } else { | ||
566 | Vec::new() | ||
567 | }; | ||
568 | |||
555 | let mut res = Assist::get(db, assist_config, resolve, frange); | 569 | let mut res = Assist::get(db, assist_config, resolve, frange); |
556 | ssr::add_ssr_assist(db, &mut res, resolve, frange); | 570 | res.extend(ssr_assists.into_iter()); |
557 | 571 | res.extend(diagnostic_assists.into_iter()); | |
558 | if include_fixes { | 572 | |
559 | res.extend( | ||
560 | diagnostics::diagnostics(db, diagnostics_config, resolve, frange.file_id) | ||
561 | .into_iter() | ||
562 | .filter_map(|it| it.fix) | ||
563 | .filter(|it| it.target.intersect(frange.range).is_some()), | ||
564 | ); | ||
565 | } | ||
566 | res | 573 | res |
567 | }) | 574 | }) |
568 | } | 575 | } |
diff --git a/crates/ide/src/ssr.rs b/crates/ide/src/ssr.rs index f3638d928..57ec80261 100644 --- a/crates/ide/src/ssr.rs +++ b/crates/ide/src/ssr.rs | |||
@@ -2,18 +2,23 @@ | |||
2 | //! assist in ide_assists because that would require the ide_assists crate | 2 | //! assist in ide_assists because that would require the ide_assists crate |
3 | //! depend on the ide_ssr crate. | 3 | //! depend on the ide_ssr crate. |
4 | 4 | ||
5 | use ide_assists::{Assist, AssistId, AssistKind, GroupLabel}; | 5 | use ide_assists::{Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel}; |
6 | use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase}; | 6 | use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase}; |
7 | 7 | ||
8 | pub(crate) fn add_ssr_assist( | 8 | pub(crate) fn ssr_assists( |
9 | db: &RootDatabase, | 9 | db: &RootDatabase, |
10 | base: &mut Vec<Assist>, | 10 | resolve: &AssistResolveStrategy, |
11 | resolve: bool, | ||
12 | frange: FileRange, | 11 | frange: FileRange, |
13 | ) -> Option<()> { | 12 | ) -> Vec<Assist> { |
14 | let (match_finder, comment_range) = ide_ssr::ssr_from_comment(db, frange)?; | 13 | let mut ssr_assists = Vec::with_capacity(2); |
15 | 14 | ||
16 | let (source_change_for_file, source_change_for_workspace) = if resolve { | 15 | let (match_finder, comment_range) = match ide_ssr::ssr_from_comment(db, frange) { |
16 | Some(ssr_data) => ssr_data, | ||
17 | None => return ssr_assists, | ||
18 | }; | ||
19 | let id = AssistId("ssr", AssistKind::RefactorRewrite); | ||
20 | |||
21 | let (source_change_for_file, source_change_for_workspace) = if resolve.should_resolve(&id) { | ||
17 | let edits = match_finder.edits(); | 22 | let edits = match_finder.edits(); |
18 | 23 | ||
19 | let source_change_for_file = { | 24 | let source_change_for_file = { |
@@ -35,16 +40,17 @@ pub(crate) fn add_ssr_assist( | |||
35 | 40 | ||
36 | for (label, source_change) in assists.into_iter() { | 41 | for (label, source_change) in assists.into_iter() { |
37 | let assist = Assist { | 42 | let assist = Assist { |
38 | id: AssistId("ssr", AssistKind::RefactorRewrite), | 43 | id, |
39 | label: Label::new(label), | 44 | label: Label::new(label), |
40 | group: Some(GroupLabel("Apply SSR".into())), | 45 | group: Some(GroupLabel("Apply SSR".into())), |
41 | target: comment_range, | 46 | target: comment_range, |
42 | source_change, | 47 | source_change, |
43 | }; | 48 | }; |
44 | 49 | ||
45 | base.push(assist); | 50 | ssr_assists.push(assist); |
46 | } | 51 | } |
47 | Some(()) | 52 | |
53 | ssr_assists | ||
48 | } | 54 | } |
49 | 55 | ||
50 | #[cfg(test)] | 56 | #[cfg(test)] |
@@ -52,7 +58,7 @@ mod tests { | |||
52 | use std::sync::Arc; | 58 | use std::sync::Arc; |
53 | 59 | ||
54 | use expect_test::expect; | 60 | use expect_test::expect; |
55 | use ide_assists::Assist; | 61 | use ide_assists::{Assist, AssistResolveStrategy}; |
56 | use ide_db::{ | 62 | use ide_db::{ |
57 | base_db::{fixture::WithFixture, salsa::Durability, FileRange}, | 63 | base_db::{fixture::WithFixture, salsa::Durability, FileRange}, |
58 | symbol_index::SymbolsDatabase, | 64 | symbol_index::SymbolsDatabase, |
@@ -60,24 +66,14 @@ mod tests { | |||
60 | }; | 66 | }; |
61 | use rustc_hash::FxHashSet; | 67 | use rustc_hash::FxHashSet; |
62 | 68 | ||
63 | use super::add_ssr_assist; | 69 | use super::ssr_assists; |
64 | 70 | ||
65 | fn get_assists(ra_fixture: &str, resolve: bool) -> Vec<Assist> { | 71 | fn get_assists(ra_fixture: &str, resolve: AssistResolveStrategy) -> Vec<Assist> { |
66 | let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture); | 72 | let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture); |
67 | let mut local_roots = FxHashSet::default(); | 73 | let mut local_roots = FxHashSet::default(); |
68 | local_roots.insert(ide_db::base_db::fixture::WORKSPACE); | 74 | local_roots.insert(ide_db::base_db::fixture::WORKSPACE); |
69 | db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); | 75 | db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); |
70 | 76 | ssr_assists(&db, &resolve, FileRange { file_id, range: range_or_offset.into() }) | |
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 | } | 77 | } |
82 | 78 | ||
83 | #[test] | 79 | #[test] |
@@ -88,16 +84,13 @@ mod tests { | |||
88 | // This is foo $0 | 84 | // This is foo $0 |
89 | fn foo() {} | 85 | fn foo() {} |
90 | "#; | 86 | "#; |
91 | let resolve = true; | 87 | let assists = get_assists(ra_fixture, AssistResolveStrategy::All); |
92 | |||
93 | let assists = get_assists(ra_fixture, resolve); | ||
94 | 88 | ||
95 | assert_eq!(0, assists.len()); | 89 | assert_eq!(0, assists.len()); |
96 | } | 90 | } |
97 | 91 | ||
98 | #[test] | 92 | #[test] |
99 | fn resolve_edits_true() { | 93 | fn resolve_edits_true() { |
100 | let resolve = true; | ||
101 | let assists = get_assists( | 94 | let assists = get_assists( |
102 | r#" | 95 | r#" |
103 | //- /lib.rs | 96 | //- /lib.rs |
@@ -109,7 +102,7 @@ mod tests { | |||
109 | //- /bar.rs | 102 | //- /bar.rs |
110 | fn bar() { 2 } | 103 | fn bar() { 2 } |
111 | "#, | 104 | "#, |
112 | resolve, | 105 | AssistResolveStrategy::All, |
113 | ); | 106 | ); |
114 | 107 | ||
115 | assert_eq!(2, assists.len()); | 108 | assert_eq!(2, assists.len()); |
@@ -200,7 +193,6 @@ mod tests { | |||
200 | 193 | ||
201 | #[test] | 194 | #[test] |
202 | fn resolve_edits_false() { | 195 | fn resolve_edits_false() { |
203 | let resolve = false; | ||
204 | let assists = get_assists( | 196 | let assists = get_assists( |
205 | r#" | 197 | r#" |
206 | //- /lib.rs | 198 | //- /lib.rs |
@@ -212,7 +204,7 @@ mod tests { | |||
212 | //- /bar.rs | 204 | //- /bar.rs |
213 | fn bar() { 2 } | 205 | fn bar() { 2 } |
214 | "#, | 206 | "#, |
215 | resolve, | 207 | AssistResolveStrategy::None, |
216 | ); | 208 | ); |
217 | 209 | ||
218 | assert_eq!(2, assists.len()); | 210 | assert_eq!(2, assists.len()); |
diff --git a/crates/ide_assists/src/assist_context.rs b/crates/ide_assists/src/assist_context.rs index 4b0bba2ab..112939948 100644 --- a/crates/ide_assists/src/assist_context.rs +++ b/crates/ide_assists/src/assist_context.rs | |||
@@ -19,7 +19,9 @@ use syntax::{ | |||
19 | }; | 19 | }; |
20 | use text_edit::{TextEdit, TextEditBuilder}; | 20 | use text_edit::{TextEdit, TextEditBuilder}; |
21 | 21 | ||
22 | use crate::{assist_config::AssistConfig, Assist, AssistId, AssistKind, GroupLabel}; | 22 | use crate::{ |
23 | assist_config::AssistConfig, Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel, | ||
24 | }; | ||
23 | 25 | ||
24 | /// `AssistContext` allows to apply an assist or check if it could be applied. | 26 | /// `AssistContext` allows to apply an assist or check if it could be applied. |
25 | /// | 27 | /// |
@@ -105,14 +107,14 @@ impl<'a> AssistContext<'a> { | |||
105 | } | 107 | } |
106 | 108 | ||
107 | pub(crate) struct Assists { | 109 | pub(crate) struct Assists { |
108 | resolve: bool, | ||
109 | file: FileId, | 110 | file: FileId, |
111 | resolve: AssistResolveStrategy, | ||
110 | buf: Vec<Assist>, | 112 | buf: Vec<Assist>, |
111 | allowed: Option<Vec<AssistKind>>, | 113 | allowed: Option<Vec<AssistKind>>, |
112 | } | 114 | } |
113 | 115 | ||
114 | impl Assists { | 116 | impl Assists { |
115 | pub(crate) fn new(ctx: &AssistContext, resolve: bool) -> Assists { | 117 | pub(crate) fn new(ctx: &AssistContext, resolve: AssistResolveStrategy) -> Assists { |
116 | Assists { | 118 | Assists { |
117 | resolve, | 119 | resolve, |
118 | file: ctx.frange.file_id, | 120 | file: ctx.frange.file_id, |
@@ -158,7 +160,7 @@ impl Assists { | |||
158 | } | 160 | } |
159 | 161 | ||
160 | fn add_impl(&mut self, mut assist: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { | 162 | fn add_impl(&mut self, mut assist: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { |
161 | let source_change = if self.resolve { | 163 | let source_change = if self.resolve.should_resolve(&assist.id) { |
162 | let mut builder = AssistBuilder::new(self.file); | 164 | let mut builder = AssistBuilder::new(self.file); |
163 | f(&mut builder); | 165 | f(&mut builder); |
164 | Some(builder.finish()) | 166 | Some(builder.finish()) |
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 88ae5c9a9..2e0c58504 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -17,6 +17,8 @@ mod tests; | |||
17 | pub mod utils; | 17 | pub mod utils; |
18 | pub mod ast_transform; | 18 | pub mod ast_transform; |
19 | 19 | ||
20 | use std::str::FromStr; | ||
21 | |||
20 | use hir::Semantics; | 22 | use hir::Semantics; |
21 | use ide_db::base_db::FileRange; | 23 | use ide_db::base_db::FileRange; |
22 | use ide_db::{label::Label, source_change::SourceChange, RootDatabase}; | 24 | use ide_db::{label::Label, source_change::SourceChange, RootDatabase}; |
@@ -56,6 +58,35 @@ impl AssistKind { | |||
56 | _ => return false, | 58 | _ => return false, |
57 | } | 59 | } |
58 | } | 60 | } |
61 | |||
62 | pub fn name(&self) -> &str { | ||
63 | match self { | ||
64 | AssistKind::None => "None", | ||
65 | AssistKind::QuickFix => "QuickFix", | ||
66 | AssistKind::Generate => "Generate", | ||
67 | AssistKind::Refactor => "Refactor", | ||
68 | AssistKind::RefactorExtract => "RefactorExtract", | ||
69 | AssistKind::RefactorInline => "RefactorInline", | ||
70 | AssistKind::RefactorRewrite => "RefactorRewrite", | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | |||
75 | impl FromStr for AssistKind { | ||
76 | type Err = String; | ||
77 | |||
78 | fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
79 | match s { | ||
80 | "None" => Ok(AssistKind::None), | ||
81 | "QuickFix" => Ok(AssistKind::QuickFix), | ||
82 | "Generate" => Ok(AssistKind::Generate), | ||
83 | "Refactor" => Ok(AssistKind::Refactor), | ||
84 | "RefactorExtract" => Ok(AssistKind::RefactorExtract), | ||
85 | "RefactorInline" => Ok(AssistKind::RefactorInline), | ||
86 | "RefactorRewrite" => Ok(AssistKind::RefactorRewrite), | ||
87 | unknown => Err(format!("Unknown AssistKind: '{}'", unknown)), | ||
88 | } | ||
89 | } | ||
59 | } | 90 | } |
60 | 91 | ||
61 | /// Unique identifier of the assist, should not be shown to the user | 92 | /// Unique identifier of the assist, should not be shown to the user |
@@ -63,6 +94,41 @@ impl AssistKind { | |||
63 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 94 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
64 | pub struct AssistId(pub &'static str, pub AssistKind); | 95 | pub struct AssistId(pub &'static str, pub AssistKind); |
65 | 96 | ||
97 | /// A way to control how many asssist to resolve during the assist resolution. | ||
98 | /// When an assist is resolved, its edits are calculated that might be costly to always do by default. | ||
99 | #[derive(Debug)] | ||
100 | pub enum AssistResolveStrategy { | ||
101 | /// No assists should be resolved. | ||
102 | None, | ||
103 | /// All assists should be resolved. | ||
104 | All, | ||
105 | /// Only a certain assist should be resolved. | ||
106 | Single(SingleResolve), | ||
107 | } | ||
108 | |||
109 | /// Hold the [`AssistId`] data of a certain assist to resolve. | ||
110 | /// The original id object cannot be used due to a `'static` lifetime | ||
111 | /// and the requirement to construct this struct dynamically during the resolve handling. | ||
112 | #[derive(Debug)] | ||
113 | pub struct SingleResolve { | ||
114 | /// The id of the assist. | ||
115 | pub assist_id: String, | ||
116 | // The kind of the assist. | ||
117 | pub assist_kind: AssistKind, | ||
118 | } | ||
119 | |||
120 | impl AssistResolveStrategy { | ||
121 | pub fn should_resolve(&self, id: &AssistId) -> bool { | ||
122 | match self { | ||
123 | AssistResolveStrategy::None => false, | ||
124 | AssistResolveStrategy::All => true, | ||
125 | AssistResolveStrategy::Single(single_resolve) => { | ||
126 | single_resolve.assist_id == id.0 && single_resolve.assist_kind == id.1 | ||
127 | } | ||
128 | } | ||
129 | } | ||
130 | } | ||
131 | |||
66 | #[derive(Clone, Debug)] | 132 | #[derive(Clone, Debug)] |
67 | pub struct GroupLabel(pub String); | 133 | pub struct GroupLabel(pub String); |
68 | 134 | ||
@@ -91,7 +157,7 @@ impl Assist { | |||
91 | pub fn get( | 157 | pub fn get( |
92 | db: &RootDatabase, | 158 | db: &RootDatabase, |
93 | config: &AssistConfig, | 159 | config: &AssistConfig, |
94 | resolve: bool, | 160 | resolve: AssistResolveStrategy, |
95 | range: FileRange, | 161 | range: FileRange, |
96 | ) -> Vec<Assist> { | 162 | ) -> Vec<Assist> { |
97 | let sema = Semantics::new(db); | 163 | let sema = Semantics::new(db); |
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs index 6f4f97361..9c2847998 100644 --- a/crates/ide_assists/src/tests.rs +++ b/crates/ide_assists/src/tests.rs | |||
@@ -12,7 +12,10 @@ use stdx::{format_to, trim_indent}; | |||
12 | use syntax::TextRange; | 12 | use syntax::TextRange; |
13 | use test_utils::{assert_eq_text, extract_offset}; | 13 | use test_utils::{assert_eq_text, extract_offset}; |
14 | 14 | ||
15 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists}; | 15 | use crate::{ |
16 | handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, AssistResolveStrategy, | ||
17 | Assists, SingleResolve, | ||
18 | }; | ||
16 | 19 | ||
17 | pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { | 20 | pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { |
18 | snippet_cap: SnippetCap::new(true), | 21 | snippet_cap: SnippetCap::new(true), |
@@ -65,14 +68,14 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { | |||
65 | let before = db.file_text(file_id).to_string(); | 68 | let before = db.file_text(file_id).to_string(); |
66 | let frange = FileRange { file_id, range: selection.into() }; | 69 | let frange = FileRange { file_id, range: selection.into() }; |
67 | 70 | ||
68 | let assist = Assist::get(&db, &TEST_CONFIG, true, frange) | 71 | let assist = Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::All, frange) |
69 | .into_iter() | 72 | .into_iter() |
70 | .find(|assist| assist.id.0 == assist_id) | 73 | .find(|assist| assist.id.0 == assist_id) |
71 | .unwrap_or_else(|| { | 74 | .unwrap_or_else(|| { |
72 | panic!( | 75 | panic!( |
73 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", | 76 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", |
74 | assist_id, | 77 | assist_id, |
75 | Assist::get(&db, &TEST_CONFIG, false, frange) | 78 | Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange) |
76 | .into_iter() | 79 | .into_iter() |
77 | .map(|assist| assist.id.0) | 80 | .map(|assist| assist.id.0) |
78 | .collect::<Vec<_>>() | 81 | .collect::<Vec<_>>() |
@@ -108,7 +111,7 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: | |||
108 | let sema = Semantics::new(&db); | 111 | let sema = Semantics::new(&db); |
109 | let config = TEST_CONFIG; | 112 | let config = TEST_CONFIG; |
110 | let ctx = AssistContext::new(sema, &config, frange); | 113 | let ctx = AssistContext::new(sema, &config, frange); |
111 | let mut acc = Assists::new(&ctx, true); | 114 | let mut acc = Assists::new(&ctx, AssistResolveStrategy::All); |
112 | handler(&mut acc, &ctx); | 115 | handler(&mut acc, &ctx); |
113 | let mut res = acc.finish(); | 116 | let mut res = acc.finish(); |
114 | 117 | ||
@@ -186,7 +189,7 @@ fn assist_order_field_struct() { | |||
186 | let (before_cursor_pos, before) = extract_offset(before); | 189 | let (before_cursor_pos, before) = extract_offset(before); |
187 | let (db, file_id) = with_single_file(&before); | 190 | let (db, file_id) = with_single_file(&before); |
188 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; | 191 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; |
189 | let assists = Assist::get(&db, &TEST_CONFIG, false, frange); | 192 | let assists = Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange); |
190 | let mut assists = assists.iter(); | 193 | let mut assists = assists.iter(); |
191 | 194 | ||
192 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); | 195 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); |
@@ -211,7 +214,7 @@ pub fn test_some_range(a: int) -> bool { | |||
211 | "#, | 214 | "#, |
212 | ); | 215 | ); |
213 | 216 | ||
214 | let assists = Assist::get(&db, &TEST_CONFIG, false, frange); | 217 | let assists = Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange); |
215 | let expected = labels(&assists); | 218 | let expected = labels(&assists); |
216 | 219 | ||
217 | expect![[r#" | 220 | expect![[r#" |
@@ -240,7 +243,7 @@ pub fn test_some_range(a: int) -> bool { | |||
240 | let mut cfg = TEST_CONFIG; | 243 | let mut cfg = TEST_CONFIG; |
241 | cfg.allowed = Some(vec![AssistKind::Refactor]); | 244 | cfg.allowed = Some(vec![AssistKind::Refactor]); |
242 | 245 | ||
243 | let assists = Assist::get(&db, &cfg, false, frange); | 246 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange); |
244 | let expected = labels(&assists); | 247 | let expected = labels(&assists); |
245 | 248 | ||
246 | expect![[r#" | 249 | expect![[r#" |
@@ -255,7 +258,7 @@ pub fn test_some_range(a: int) -> bool { | |||
255 | { | 258 | { |
256 | let mut cfg = TEST_CONFIG; | 259 | let mut cfg = TEST_CONFIG; |
257 | cfg.allowed = Some(vec![AssistKind::RefactorExtract]); | 260 | cfg.allowed = Some(vec![AssistKind::RefactorExtract]); |
258 | let assists = Assist::get(&db, &cfg, false, frange); | 261 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange); |
259 | let expected = labels(&assists); | 262 | let expected = labels(&assists); |
260 | 263 | ||
261 | expect![[r#" | 264 | expect![[r#" |
@@ -268,9 +271,250 @@ pub fn test_some_range(a: int) -> bool { | |||
268 | { | 271 | { |
269 | let mut cfg = TEST_CONFIG; | 272 | let mut cfg = TEST_CONFIG; |
270 | cfg.allowed = Some(vec![AssistKind::QuickFix]); | 273 | cfg.allowed = Some(vec![AssistKind::QuickFix]); |
271 | let assists = Assist::get(&db, &cfg, false, frange); | 274 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange); |
272 | let expected = labels(&assists); | 275 | let expected = labels(&assists); |
273 | 276 | ||
274 | expect![[r#""#]].assert_eq(&expected); | 277 | expect![[r#""#]].assert_eq(&expected); |
275 | } | 278 | } |
276 | } | 279 | } |
280 | |||
281 | #[test] | ||
282 | fn various_resolve_strategies() { | ||
283 | let (db, frange) = RootDatabase::with_range( | ||
284 | r#" | ||
285 | pub fn test_some_range(a: int) -> bool { | ||
286 | if let 2..6 = $05$0 { | ||
287 | true | ||
288 | } else { | ||
289 | false | ||
290 | } | ||
291 | } | ||
292 | "#, | ||
293 | ); | ||
294 | |||
295 | let mut cfg = TEST_CONFIG; | ||
296 | cfg.allowed = Some(vec![AssistKind::RefactorExtract]); | ||
297 | |||
298 | { | ||
299 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange); | ||
300 | assert_eq!(2, assists.len()); | ||
301 | let mut assists = assists.into_iter(); | ||
302 | |||
303 | let extract_into_variable_assist = assists.next().unwrap(); | ||
304 | expect![[r#" | ||
305 | Assist { | ||
306 | id: AssistId( | ||
307 | "extract_variable", | ||
308 | RefactorExtract, | ||
309 | ), | ||
310 | label: "Extract into variable", | ||
311 | group: None, | ||
312 | target: 59..60, | ||
313 | source_change: None, | ||
314 | } | ||
315 | "#]] | ||
316 | .assert_debug_eq(&extract_into_variable_assist); | ||
317 | |||
318 | let extract_into_function_assist = assists.next().unwrap(); | ||
319 | expect![[r#" | ||
320 | Assist { | ||
321 | id: AssistId( | ||
322 | "extract_function", | ||
323 | RefactorExtract, | ||
324 | ), | ||
325 | label: "Extract into function", | ||
326 | group: None, | ||
327 | target: 59..60, | ||
328 | source_change: None, | ||
329 | } | ||
330 | "#]] | ||
331 | .assert_debug_eq(&extract_into_function_assist); | ||
332 | } | ||
333 | |||
334 | { | ||
335 | let assists = Assist::get( | ||
336 | &db, | ||
337 | &cfg, | ||
338 | AssistResolveStrategy::Single(SingleResolve { | ||
339 | assist_id: "SOMETHING_MISMATCHING".to_string(), | ||
340 | assist_kind: AssistKind::RefactorExtract, | ||
341 | }), | ||
342 | frange, | ||
343 | ); | ||
344 | assert_eq!(2, assists.len()); | ||
345 | let mut assists = assists.into_iter(); | ||
346 | |||
347 | let extract_into_variable_assist = assists.next().unwrap(); | ||
348 | expect![[r#" | ||
349 | Assist { | ||
350 | id: AssistId( | ||
351 | "extract_variable", | ||
352 | RefactorExtract, | ||
353 | ), | ||
354 | label: "Extract into variable", | ||
355 | group: None, | ||
356 | target: 59..60, | ||
357 | source_change: None, | ||
358 | } | ||
359 | "#]] | ||
360 | .assert_debug_eq(&extract_into_variable_assist); | ||
361 | |||
362 | let extract_into_function_assist = assists.next().unwrap(); | ||
363 | expect![[r#" | ||
364 | Assist { | ||
365 | id: AssistId( | ||
366 | "extract_function", | ||
367 | RefactorExtract, | ||
368 | ), | ||
369 | label: "Extract into function", | ||
370 | group: None, | ||
371 | target: 59..60, | ||
372 | source_change: None, | ||
373 | } | ||
374 | "#]] | ||
375 | .assert_debug_eq(&extract_into_function_assist); | ||
376 | } | ||
377 | |||
378 | { | ||
379 | let assists = Assist::get( | ||
380 | &db, | ||
381 | &cfg, | ||
382 | AssistResolveStrategy::Single(SingleResolve { | ||
383 | assist_id: "extract_variable".to_string(), | ||
384 | assist_kind: AssistKind::RefactorExtract, | ||
385 | }), | ||
386 | frange, | ||
387 | ); | ||
388 | assert_eq!(2, assists.len()); | ||
389 | let mut assists = assists.into_iter(); | ||
390 | |||
391 | let extract_into_variable_assist = assists.next().unwrap(); | ||
392 | expect![[r#" | ||
393 | Assist { | ||
394 | id: AssistId( | ||
395 | "extract_variable", | ||
396 | RefactorExtract, | ||
397 | ), | ||
398 | label: "Extract into variable", | ||
399 | group: None, | ||
400 | target: 59..60, | ||
401 | source_change: Some( | ||
402 | SourceChange { | ||
403 | source_file_edits: { | ||
404 | FileId( | ||
405 | 0, | ||
406 | ): TextEdit { | ||
407 | indels: [ | ||
408 | Indel { | ||
409 | insert: "let $0var_name = 5;\n ", | ||
410 | delete: 45..45, | ||
411 | }, | ||
412 | Indel { | ||
413 | insert: "var_name", | ||
414 | delete: 59..60, | ||
415 | }, | ||
416 | ], | ||
417 | }, | ||
418 | }, | ||
419 | file_system_edits: [], | ||
420 | is_snippet: true, | ||
421 | }, | ||
422 | ), | ||
423 | } | ||
424 | "#]] | ||
425 | .assert_debug_eq(&extract_into_variable_assist); | ||
426 | |||
427 | let extract_into_function_assist = assists.next().unwrap(); | ||
428 | expect![[r#" | ||
429 | Assist { | ||
430 | id: AssistId( | ||
431 | "extract_function", | ||
432 | RefactorExtract, | ||
433 | ), | ||
434 | label: "Extract into function", | ||
435 | group: None, | ||
436 | target: 59..60, | ||
437 | source_change: None, | ||
438 | } | ||
439 | "#]] | ||
440 | .assert_debug_eq(&extract_into_function_assist); | ||
441 | } | ||
442 | |||
443 | { | ||
444 | let assists = Assist::get(&db, &cfg, AssistResolveStrategy::All, frange); | ||
445 | assert_eq!(2, assists.len()); | ||
446 | let mut assists = assists.into_iter(); | ||
447 | |||
448 | let extract_into_variable_assist = assists.next().unwrap(); | ||
449 | expect![[r#" | ||
450 | Assist { | ||
451 | id: AssistId( | ||
452 | "extract_variable", | ||
453 | RefactorExtract, | ||
454 | ), | ||
455 | label: "Extract into variable", | ||
456 | group: None, | ||
457 | target: 59..60, | ||
458 | source_change: Some( | ||
459 | SourceChange { | ||
460 | source_file_edits: { | ||
461 | FileId( | ||
462 | 0, | ||
463 | ): TextEdit { | ||
464 | indels: [ | ||
465 | Indel { | ||
466 | insert: "let $0var_name = 5;\n ", | ||
467 | delete: 45..45, | ||
468 | }, | ||
469 | Indel { | ||
470 | insert: "var_name", | ||
471 | delete: 59..60, | ||
472 | }, | ||
473 | ], | ||
474 | }, | ||
475 | }, | ||
476 | file_system_edits: [], | ||
477 | is_snippet: true, | ||
478 | }, | ||
479 | ), | ||
480 | } | ||
481 | "#]] | ||
482 | .assert_debug_eq(&extract_into_variable_assist); | ||
483 | |||
484 | let extract_into_function_assist = assists.next().unwrap(); | ||
485 | expect![[r#" | ||
486 | Assist { | ||
487 | id: AssistId( | ||
488 | "extract_function", | ||
489 | RefactorExtract, | ||
490 | ), | ||
491 | label: "Extract into function", | ||
492 | group: None, | ||
493 | target: 59..60, | ||
494 | source_change: Some( | ||
495 | SourceChange { | ||
496 | source_file_edits: { | ||
497 | FileId( | ||
498 | 0, | ||
499 | ): TextEdit { | ||
500 | indels: [ | ||
501 | Indel { | ||
502 | insert: "fun_name()", | ||
503 | delete: 59..60, | ||
504 | }, | ||
505 | Indel { | ||
506 | insert: "\n\nfn $0fun_name() -> i32 {\n 5\n}", | ||
507 | delete: 110..110, | ||
508 | }, | ||
509 | ], | ||
510 | }, | ||
511 | }, | ||
512 | file_system_edits: [], | ||
513 | is_snippet: true, | ||
514 | }, | ||
515 | ), | ||
516 | } | ||
517 | "#]] | ||
518 | .assert_debug_eq(&extract_into_function_assist); | ||
519 | } | ||
520 | } | ||
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs index 74f784338..c33c8179c 100644 --- a/crates/rust-analyzer/src/cli/diagnostics.rs +++ b/crates/rust-analyzer/src/cli/diagnostics.rs | |||
@@ -7,7 +7,7 @@ use anyhow::anyhow; | |||
7 | use rustc_hash::FxHashSet; | 7 | use rustc_hash::FxHashSet; |
8 | 8 | ||
9 | use hir::{db::HirDatabase, Crate, Module}; | 9 | use hir::{db::HirDatabase, Crate, Module}; |
10 | use ide::{DiagnosticsConfig, Severity}; | 10 | use ide::{AssistResolveStrategy, DiagnosticsConfig, Severity}; |
11 | use ide_db::base_db::SourceDatabaseExt; | 11 | use ide_db::base_db::SourceDatabaseExt; |
12 | 12 | ||
13 | use crate::cli::{ | 13 | use crate::cli::{ |
@@ -57,8 +57,9 @@ pub fn diagnostics( | |||
57 | let crate_name = | 57 | let crate_name = |
58 | module.krate().display_name(db).as_deref().unwrap_or("unknown").to_string(); | 58 | module.krate().display_name(db).as_deref().unwrap_or("unknown").to_string(); |
59 | println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id)); | 59 | println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id)); |
60 | for diagnostic in | 60 | for diagnostic in analysis |
61 | analysis.diagnostics(&DiagnosticsConfig::default(), false, file_id).unwrap() | 61 | .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::None, file_id) |
62 | .unwrap() | ||
62 | { | 63 | { |
63 | if matches!(diagnostic.severity, Severity::Error) { | 64 | if matches!(diagnostic.severity, Severity::Error) { |
64 | found_error = true; | 65 | found_error = true; |
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 1f59402e5..f6e40f872 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -8,8 +8,9 @@ use std::{ | |||
8 | }; | 8 | }; |
9 | 9 | ||
10 | use ide::{ | 10 | use ide::{ |
11 | AnnotationConfig, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, Query, | 11 | AnnotationConfig, AssistKind, AssistResolveStrategy, FileId, FilePosition, FileRange, |
12 | RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange, TextEdit, | 12 | HoverAction, HoverGotoTypeData, Query, RangeInfo, Runnable, RunnableKind, SearchScope, |
13 | SingleResolve, SourceChange, TextEdit, | ||
13 | }; | 14 | }; |
14 | use ide_db::SymbolKind; | 15 | use ide_db::SymbolKind; |
15 | use itertools::Itertools; | 16 | use itertools::Itertools; |
@@ -27,7 +28,7 @@ use lsp_types::{ | |||
27 | use project_model::TargetKind; | 28 | use project_model::TargetKind; |
28 | use serde::{Deserialize, Serialize}; | 29 | use serde::{Deserialize, Serialize}; |
29 | use serde_json::to_value; | 30 | use serde_json::to_value; |
30 | use stdx::{format_to, split_once}; | 31 | use stdx::format_to; |
31 | use syntax::{algo, ast, AstNode, TextRange, TextSize}; | 32 | use syntax::{algo, ast, AstNode, TextRange, TextSize}; |
32 | 33 | ||
33 | use crate::{ | 34 | use crate::{ |
@@ -1004,10 +1005,15 @@ pub(crate) fn handle_code_action( | |||
1004 | let mut res: Vec<lsp_ext::CodeAction> = Vec::new(); | 1005 | let mut res: Vec<lsp_ext::CodeAction> = Vec::new(); |
1005 | 1006 | ||
1006 | let code_action_resolve_cap = snap.config.code_action_resolve(); | 1007 | let code_action_resolve_cap = snap.config.code_action_resolve(); |
1008 | let resolve = if code_action_resolve_cap { | ||
1009 | AssistResolveStrategy::None | ||
1010 | } else { | ||
1011 | AssistResolveStrategy::All | ||
1012 | }; | ||
1007 | let assists = snap.analysis.assists_with_fixes( | 1013 | let assists = snap.analysis.assists_with_fixes( |
1008 | &assists_config, | 1014 | &assists_config, |
1009 | &snap.config.diagnostics(), | 1015 | &snap.config.diagnostics(), |
1010 | !code_action_resolve_cap, | 1016 | resolve, |
1011 | frange, | 1017 | frange, |
1012 | )?; | 1018 | )?; |
1013 | for (index, assist) in assists.into_iter().enumerate() { | 1019 | for (index, assist) in assists.into_iter().enumerate() { |
@@ -1052,22 +1058,68 @@ pub(crate) fn handle_code_action_resolve( | |||
1052 | .only | 1058 | .only |
1053 | .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect()); | 1059 | .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect()); |
1054 | 1060 | ||
1061 | let (assist_index, assist_resolve) = match parse_action_id(¶ms.id) { | ||
1062 | Ok(parsed_data) => parsed_data, | ||
1063 | Err(e) => { | ||
1064 | return Err(LspError::new( | ||
1065 | ErrorCode::InvalidParams as i32, | ||
1066 | format!("Failed to parse action id string '{}': {}", params.id, e), | ||
1067 | ) | ||
1068 | .into()) | ||
1069 | } | ||
1070 | }; | ||
1071 | |||
1072 | let expected_assist_id = assist_resolve.assist_id.clone(); | ||
1073 | let expected_kind = assist_resolve.assist_kind; | ||
1074 | |||
1055 | let assists = snap.analysis.assists_with_fixes( | 1075 | let assists = snap.analysis.assists_with_fixes( |
1056 | &assists_config, | 1076 | &assists_config, |
1057 | &snap.config.diagnostics(), | 1077 | &snap.config.diagnostics(), |
1058 | true, | 1078 | AssistResolveStrategy::Single(assist_resolve), |
1059 | frange, | 1079 | frange, |
1060 | )?; | 1080 | )?; |
1061 | 1081 | ||
1062 | let (id, index) = split_once(¶ms.id, ':').unwrap(); | 1082 | let assist = match assists.get(assist_index) { |
1063 | let index = index.parse::<usize>().unwrap(); | 1083 | Some(assist) => assist, |
1064 | let assist = &assists[index]; | 1084 | None => return Err(LspError::new( |
1065 | assert!(assist.id.0 == id); | 1085 | ErrorCode::InvalidParams as i32, |
1086 | format!( | ||
1087 | "Failed to find the assist for index {} provided by the resolve request. Resolve request assist id: {}", | ||
1088 | assist_index, params.id, | ||
1089 | ), | ||
1090 | ) | ||
1091 | .into()) | ||
1092 | }; | ||
1093 | if assist.id.0 != expected_assist_id || assist.id.1 != expected_kind { | ||
1094 | return Err(LspError::new( | ||
1095 | ErrorCode::InvalidParams as i32, | ||
1096 | format!( | ||
1097 | "Mismatching assist at index {} for the resolve parameters given. Resolve request assist id: {}, actual id: {:?}.", | ||
1098 | assist_index, params.id, assist.id | ||
1099 | ), | ||
1100 | ) | ||
1101 | .into()); | ||
1102 | } | ||
1066 | let edit = to_proto::code_action(&snap, assist.clone(), None)?.edit; | 1103 | let edit = to_proto::code_action(&snap, assist.clone(), None)?.edit; |
1067 | code_action.edit = edit; | 1104 | code_action.edit = edit; |
1068 | Ok(code_action) | 1105 | Ok(code_action) |
1069 | } | 1106 | } |
1070 | 1107 | ||
1108 | fn parse_action_id(action_id: &str) -> Result<(usize, SingleResolve), String> { | ||
1109 | let id_parts = action_id.split(':').collect_vec(); | ||
1110 | match id_parts.as_slice() { | ||
1111 | &[assist_id_string, assist_kind_string, index_string] => { | ||
1112 | let assist_kind: AssistKind = assist_kind_string.parse()?; | ||
1113 | let index: usize = match index_string.parse() { | ||
1114 | Ok(index) => index, | ||
1115 | Err(e) => return Err(format!("Incorrect index string: {}", e)), | ||
1116 | }; | ||
1117 | Ok((index, SingleResolve { assist_id: assist_id_string.to_string(), assist_kind })) | ||
1118 | } | ||
1119 | _ => Err("Action id contains incorrect number of segments".to_string()), | ||
1120 | } | ||
1121 | } | ||
1122 | |||
1071 | pub(crate) fn handle_code_lens( | 1123 | pub(crate) fn handle_code_lens( |
1072 | snap: GlobalStateSnapshot, | 1124 | snap: GlobalStateSnapshot, |
1073 | params: lsp_types::CodeLensParams, | 1125 | params: lsp_types::CodeLensParams, |
@@ -1182,7 +1234,7 @@ pub(crate) fn publish_diagnostics( | |||
1182 | 1234 | ||
1183 | let diagnostics: Vec<Diagnostic> = snap | 1235 | let diagnostics: Vec<Diagnostic> = snap |
1184 | .analysis | 1236 | .analysis |
1185 | .diagnostics(&snap.config.diagnostics(), false, file_id)? | 1237 | .diagnostics(&snap.config.diagnostics(), AssistResolveStrategy::None, file_id)? |
1186 | .into_iter() | 1238 | .into_iter() |
1187 | .map(|d| Diagnostic { | 1239 | .map(|d| Diagnostic { |
1188 | range: to_proto::range(&line_index, d.range), | 1240 | range: to_proto::range(&line_index, d.range), |
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index c2361b32e..1d27aa7b3 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -897,7 +897,7 @@ pub(crate) fn code_action( | |||
897 | (Some(it), _) => res.edit = Some(snippet_workspace_edit(snap, it)?), | 897 | (Some(it), _) => res.edit = Some(snippet_workspace_edit(snap, it)?), |
898 | (None, Some((index, code_action_params))) => { | 898 | (None, Some((index, code_action_params))) => { |
899 | res.data = Some(lsp_ext::CodeActionData { | 899 | res.data = Some(lsp_ext::CodeActionData { |
900 | id: format!("{}:{}", assist.id.0, index.to_string()), | 900 | id: format!("{}:{}:{}", assist.id.0, assist.id.1.name(), index), |
901 | code_action_params, | 901 | code_action_params, |
902 | }); | 902 | }); |
903 | } | 903 | } |