aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists')
-rw-r--r--crates/ide_assists/src/assist_context.rs10
-rw-r--r--crates/ide_assists/src/lib.rs68
-rw-r--r--crates/ide_assists/src/tests.rs262
3 files changed, 326 insertions, 14 deletions
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};
20use text_edit::{TextEdit, TextEditBuilder}; 20use text_edit::{TextEdit, TextEditBuilder};
21 21
22use crate::{assist_config::AssistConfig, Assist, AssistId, AssistKind, GroupLabel}; 22use 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
107pub(crate) struct Assists { 109pub(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
114impl Assists { 116impl 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;
17pub mod utils; 17pub mod utils;
18pub mod ast_transform; 18pub mod ast_transform;
19 19
20use std::str::FromStr;
21
20use hir::Semantics; 22use hir::Semantics;
21use ide_db::base_db::FileRange; 23use ide_db::base_db::FileRange;
22use ide_db::{label::Label, source_change::SourceChange, RootDatabase}; 24use 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
75impl 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)]
64pub struct AssistId(pub &'static str, pub AssistKind); 95pub 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)]
100pub 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)]
113pub 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
120impl 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)]
67pub struct GroupLabel(pub String); 133pub 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};
12use syntax::TextRange; 12use syntax::TextRange;
13use test_utils::{assert_eq_text, extract_offset}; 13use test_utils::{assert_eq_text, extract_offset};
14 14
15use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists}; 15use crate::{
16 handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, AssistResolveStrategy,
17 Assists, SingleResolve,
18};
16 19
17pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { 20pub(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]
282fn various_resolve_strategies() {
283 let (db, frange) = RootDatabase::with_range(
284 r#"
285pub 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}