diff options
28 files changed, 488 insertions, 361 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index c52736679..1908bdec9 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -14,8 +14,8 @@ use crate::{AssistAction, AssistId, AssistLabel}; | |||
14 | 14 | ||
15 | #[derive(Clone, Debug)] | 15 | #[derive(Clone, Debug)] |
16 | pub(crate) enum Assist { | 16 | pub(crate) enum Assist { |
17 | Unresolved(Vec<AssistLabel>), | 17 | Unresolved { label: AssistLabel }, |
18 | Resolved(Vec<(AssistLabel, AssistAction)>), | 18 | Resolved { label: AssistLabel, action: AssistAction }, |
19 | } | 19 | } |
20 | 20 | ||
21 | /// `AssistCtx` allows to apply an assist or check if it could be applied. | 21 | /// `AssistCtx` allows to apply an assist or check if it could be applied. |
@@ -54,7 +54,6 @@ pub(crate) struct AssistCtx<'a, DB> { | |||
54 | pub(crate) frange: FileRange, | 54 | pub(crate) frange: FileRange, |
55 | source_file: SourceFile, | 55 | source_file: SourceFile, |
56 | should_compute_edit: bool, | 56 | should_compute_edit: bool, |
57 | assist: Assist, | ||
58 | } | 57 | } |
59 | 58 | ||
60 | impl<'a, DB> Clone for AssistCtx<'a, DB> { | 59 | impl<'a, DB> Clone for AssistCtx<'a, DB> { |
@@ -64,7 +63,6 @@ impl<'a, DB> Clone for AssistCtx<'a, DB> { | |||
64 | frange: self.frange, | 63 | frange: self.frange, |
65 | source_file: self.source_file.clone(), | 64 | source_file: self.source_file.clone(), |
66 | should_compute_edit: self.should_compute_edit, | 65 | should_compute_edit: self.should_compute_edit, |
67 | assist: self.assist.clone(), | ||
68 | } | 66 | } |
69 | } | 67 | } |
70 | } | 68 | } |
@@ -75,36 +73,30 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { | |||
75 | F: FnOnce(AssistCtx<DB>) -> T, | 73 | F: FnOnce(AssistCtx<DB>) -> T, |
76 | { | 74 | { |
77 | let parse = db.parse(frange.file_id); | 75 | let parse = db.parse(frange.file_id); |
78 | let assist = | ||
79 | if should_compute_edit { Assist::Resolved(vec![]) } else { Assist::Unresolved(vec![]) }; | ||
80 | 76 | ||
81 | let ctx = AssistCtx { db, frange, source_file: parse.tree(), should_compute_edit, assist }; | 77 | let ctx = AssistCtx { db, frange, source_file: parse.tree(), should_compute_edit }; |
82 | f(ctx) | 78 | f(ctx) |
83 | } | 79 | } |
84 | 80 | ||
85 | pub(crate) fn add_action( | 81 | pub(crate) fn add_assist( |
86 | &mut self, | 82 | self, |
87 | id: AssistId, | 83 | id: AssistId, |
88 | label: impl Into<String>, | 84 | label: impl Into<String>, |
89 | f: impl FnOnce(&mut AssistBuilder), | 85 | f: impl FnOnce(&mut AssistBuilder), |
90 | ) -> &mut Self { | 86 | ) -> Option<Assist> { |
91 | let label = AssistLabel { label: label.into(), id }; | 87 | let label = AssistLabel { label: label.into(), id }; |
92 | match &mut self.assist { | 88 | let assist = if self.should_compute_edit { |
93 | Assist::Unresolved(labels) => labels.push(label), | 89 | let action = { |
94 | Assist::Resolved(labels_actions) => { | 90 | let mut edit = AssistBuilder::default(); |
95 | let action = { | 91 | f(&mut edit); |
96 | let mut edit = AssistBuilder::default(); | 92 | edit.build() |
97 | f(&mut edit); | 93 | }; |
98 | edit.build() | 94 | Assist::Resolved { label, action } |
99 | }; | 95 | } else { |
100 | labels_actions.push((label, action)); | 96 | Assist::Unresolved { label } |
101 | } | 97 | }; |
102 | } | 98 | |
103 | self | 99 | Some(assist) |
104 | } | ||
105 | |||
106 | pub(crate) fn build(self) -> Option<Assist> { | ||
107 | Some(self.assist) | ||
108 | } | 100 | } |
109 | 101 | ||
110 | pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { | 102 | pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { |
diff --git a/crates/ra_assists/src/assists/add_derive.rs b/crates/ra_assists/src/assists/add_derive.rs index d1e925b71..764b17bd8 100644 --- a/crates/ra_assists/src/assists/add_derive.rs +++ b/crates/ra_assists/src/assists/add_derive.rs | |||
@@ -25,10 +25,10 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
25 | // y: u32, | 25 | // y: u32, |
26 | // } | 26 | // } |
27 | // ``` | 27 | // ``` |
28 | pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 28 | pub(crate) fn add_derive(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
29 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; | 29 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; |
30 | let node_start = derive_insertion_offset(&nominal)?; | 30 | let node_start = derive_insertion_offset(&nominal)?; |
31 | ctx.add_action(AssistId("add_derive"), "add `#[derive]`", |edit| { | 31 | ctx.add_assist(AssistId("add_derive"), "add `#[derive]`", |edit| { |
32 | let derive_attr = nominal | 32 | let derive_attr = nominal |
33 | .attrs() | 33 | .attrs() |
34 | .filter_map(|x| x.as_simple_call()) | 34 | .filter_map(|x| x.as_simple_call()) |
@@ -44,9 +44,7 @@ pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> | |||
44 | }; | 44 | }; |
45 | edit.target(nominal.syntax().text_range()); | 45 | edit.target(nominal.syntax().text_range()); |
46 | edit.set_cursor(offset) | 46 | edit.set_cursor(offset) |
47 | }); | 47 | }) |
48 | |||
49 | ctx.build() | ||
50 | } | 48 | } |
51 | 49 | ||
52 | // Insert `derive` after doc comments. | 50 | // Insert `derive` after doc comments. |
diff --git a/crates/ra_assists/src/assists/add_explicit_type.rs b/crates/ra_assists/src/assists/add_explicit_type.rs index ffbdc0b62..ddda1a0f2 100644 --- a/crates/ra_assists/src/assists/add_explicit_type.rs +++ b/crates/ra_assists/src/assists/add_explicit_type.rs | |||
@@ -21,7 +21,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
21 | // let x: i32 = 92; | 21 | // let x: i32 = 92; |
22 | // } | 22 | // } |
23 | // ``` | 23 | // ``` |
24 | pub(crate) fn add_explicit_type(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 24 | pub(crate) fn add_explicit_type(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
25 | let stmt = ctx.find_node_at_offset::<LetStmt>()?; | 25 | let stmt = ctx.find_node_at_offset::<LetStmt>()?; |
26 | let expr = stmt.initializer()?; | 26 | let expr = stmt.initializer()?; |
27 | let pat = stmt.pat()?; | 27 | let pat = stmt.pat()?; |
@@ -47,11 +47,10 @@ pub(crate) fn add_explicit_type(mut ctx: AssistCtx<impl HirDatabase>) -> Option< | |||
47 | return None; | 47 | return None; |
48 | } | 48 | } |
49 | 49 | ||
50 | ctx.add_action(AssistId("add_explicit_type"), "add explicit type", |edit| { | 50 | ctx.add_assist(AssistId("add_explicit_type"), "add explicit type", |edit| { |
51 | edit.target(pat_range); | 51 | edit.target(pat_range); |
52 | edit.insert(name_range.end(), format!(": {}", ty.display(db))); | 52 | edit.insert(name_range.end(), format!(": {}", ty.display(db))); |
53 | }); | 53 | }) |
54 | ctx.build() | ||
55 | } | 54 | } |
56 | 55 | ||
57 | /// Returns true if any type parameter is unknown | 56 | /// Returns true if any type parameter is unknown |
diff --git a/crates/ra_assists/src/assists/add_impl.rs b/crates/ra_assists/src/assists/add_impl.rs index fd3588d24..7da0cfd0d 100644 --- a/crates/ra_assists/src/assists/add_impl.rs +++ b/crates/ra_assists/src/assists/add_impl.rs | |||
@@ -27,10 +27,10 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
27 | // | 27 | // |
28 | // } | 28 | // } |
29 | // ``` | 29 | // ``` |
30 | pub(crate) fn add_impl(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 30 | pub(crate) fn add_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
31 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; | 31 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; |
32 | let name = nominal.name()?; | 32 | let name = nominal.name()?; |
33 | ctx.add_action(AssistId("add_impl"), "add impl", |edit| { | 33 | ctx.add_assist(AssistId("add_impl"), "add impl", |edit| { |
34 | edit.target(nominal.syntax().text_range()); | 34 | edit.target(nominal.syntax().text_range()); |
35 | let type_params = nominal.type_param_list(); | 35 | let type_params = nominal.type_param_list(); |
36 | let start_offset = nominal.syntax().text_range().end(); | 36 | let start_offset = nominal.syntax().text_range().end(); |
@@ -54,9 +54,7 @@ pub(crate) fn add_impl(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
54 | edit.set_cursor(start_offset + TextUnit::of_str(&buf)); | 54 | edit.set_cursor(start_offset + TextUnit::of_str(&buf)); |
55 | buf.push_str("\n}"); | 55 | buf.push_str("\n}"); |
56 | edit.insert(start_offset, buf); | 56 | edit.insert(start_offset, buf); |
57 | }); | 57 | }) |
58 | |||
59 | ctx.build() | ||
60 | } | 58 | } |
61 | 59 | ||
62 | #[cfg(test)] | 60 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/assists/auto_import.rs b/crates/ra_assists/src/assists/add_import.rs index a1c2aaa72..e87fae1af 100644 --- a/crates/ra_assists/src/assists/auto_import.rs +++ b/crates/ra_assists/src/assists/add_import.rs | |||
@@ -1,18 +1,81 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::{self, db::HirDatabase}; | 1 | use hir::{self, db::HirDatabase}; |
4 | use ra_text_edit::TextEditBuilder; | ||
5 | |||
6 | use crate::{ | ||
7 | assist_ctx::{Assist, AssistCtx}, | ||
8 | AssistId, | ||
9 | }; | ||
10 | use ra_syntax::{ | 2 | use ra_syntax::{ |
11 | ast::{self, NameOwner}, | 3 | ast::{self, NameOwner}, |
12 | AstNode, Direction, SmolStr, | 4 | AstNode, Direction, SmolStr, |
13 | SyntaxKind::{PATH, PATH_SEGMENT}, | 5 | SyntaxKind::{PATH, PATH_SEGMENT}, |
14 | SyntaxNode, TextRange, T, | 6 | SyntaxNode, TextRange, T, |
15 | }; | 7 | }; |
8 | use ra_text_edit::TextEditBuilder; | ||
9 | |||
10 | use crate::{ | ||
11 | assist_ctx::{Assist, AssistCtx}, | ||
12 | AssistId, | ||
13 | }; | ||
14 | |||
15 | /// This function produces sequence of text edits into edit | ||
16 | /// to import the target path in the most appropriate scope given | ||
17 | /// the cursor position | ||
18 | pub fn auto_import_text_edit( | ||
19 | // Ideally the position of the cursor, used to | ||
20 | position: &SyntaxNode, | ||
21 | // The statement to use as anchor (last resort) | ||
22 | anchor: &SyntaxNode, | ||
23 | // The path to import as a sequence of strings | ||
24 | target: &[SmolStr], | ||
25 | edit: &mut TextEditBuilder, | ||
26 | ) { | ||
27 | let container = position.ancestors().find_map(|n| { | ||
28 | if let Some(module) = ast::Module::cast(n.clone()) { | ||
29 | return module.item_list().map(|it| it.syntax().clone()); | ||
30 | } | ||
31 | ast::SourceFile::cast(n).map(|it| it.syntax().clone()) | ||
32 | }); | ||
33 | |||
34 | if let Some(container) = container { | ||
35 | let action = best_action_for_target(container, anchor.clone(), target); | ||
36 | make_assist(&action, target, edit); | ||
37 | } | ||
38 | } | ||
39 | |||
40 | // Assist: add_import | ||
41 | // | ||
42 | // Adds a use statement for a given fully-qualified path. | ||
43 | // | ||
44 | // ``` | ||
45 | // fn process(map: std::collections::<|>HashMap<String, String>) {} | ||
46 | // ``` | ||
47 | // -> | ||
48 | // ``` | ||
49 | // use std::collections::HashMap; | ||
50 | // | ||
51 | // fn process(map: HashMap<String, String>) {} | ||
52 | // ``` | ||
53 | pub(crate) fn add_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
54 | let path: ast::Path = ctx.find_node_at_offset()?; | ||
55 | // We don't want to mess with use statements | ||
56 | if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { | ||
57 | return None; | ||
58 | } | ||
59 | |||
60 | let hir_path = hir::Path::from_ast(path.clone())?; | ||
61 | let segments = collect_hir_path_segments(&hir_path)?; | ||
62 | if segments.len() < 2 { | ||
63 | return None; | ||
64 | } | ||
65 | |||
66 | let module = path.syntax().ancestors().find_map(ast::Module::cast); | ||
67 | let position = match module.and_then(|it| it.item_list()) { | ||
68 | Some(item_list) => item_list.syntax().clone(), | ||
69 | None => { | ||
70 | let current_file = path.syntax().ancestors().find_map(ast::SourceFile::cast)?; | ||
71 | current_file.syntax().clone() | ||
72 | } | ||
73 | }; | ||
74 | |||
75 | ctx.add_assist(AssistId("add_import"), format!("import {}", fmt_segments(&segments)), |edit| { | ||
76 | apply_auto_import(&position, &path, &segments, edit.text_edit_builder()); | ||
77 | }) | ||
78 | } | ||
16 | 79 | ||
17 | fn collect_path_segments_raw( | 80 | fn collect_path_segments_raw( |
18 | segments: &mut Vec<ast::PathSegment>, | 81 | segments: &mut Vec<ast::PathSegment>, |
@@ -505,7 +568,7 @@ fn apply_auto_import( | |||
505 | } | 568 | } |
506 | } | 569 | } |
507 | 570 | ||
508 | pub fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> { | 571 | fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> { |
509 | let mut ps = Vec::<SmolStr>::with_capacity(10); | 572 | let mut ps = Vec::<SmolStr>::with_capacity(10); |
510 | match path.kind { | 573 | match path.kind { |
511 | hir::PathKind::Abs => ps.push("".into()), | 574 | hir::PathKind::Abs => ps.push("".into()), |
@@ -521,87 +584,16 @@ pub fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> { | |||
521 | Some(ps) | 584 | Some(ps) |
522 | } | 585 | } |
523 | 586 | ||
524 | // This function produces sequence of text edits into edit | ||
525 | // to import the target path in the most appropriate scope given | ||
526 | // the cursor position | ||
527 | pub fn auto_import_text_edit( | ||
528 | // Ideally the position of the cursor, used to | ||
529 | position: &SyntaxNode, | ||
530 | // The statement to use as anchor (last resort) | ||
531 | anchor: &SyntaxNode, | ||
532 | // The path to import as a sequence of strings | ||
533 | target: &[SmolStr], | ||
534 | edit: &mut TextEditBuilder, | ||
535 | ) { | ||
536 | let container = position.ancestors().find_map(|n| { | ||
537 | if let Some(module) = ast::Module::cast(n.clone()) { | ||
538 | return module.item_list().map(|it| it.syntax().clone()); | ||
539 | } | ||
540 | ast::SourceFile::cast(n).map(|it| it.syntax().clone()) | ||
541 | }); | ||
542 | |||
543 | if let Some(container) = container { | ||
544 | let action = best_action_for_target(container, anchor.clone(), target); | ||
545 | make_assist(&action, target, edit); | ||
546 | } | ||
547 | } | ||
548 | |||
549 | pub(crate) fn auto_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
550 | let path: ast::Path = ctx.find_node_at_offset()?; | ||
551 | // We don't want to mess with use statements | ||
552 | if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { | ||
553 | return None; | ||
554 | } | ||
555 | |||
556 | let hir_path = hir::Path::from_ast(path.clone())?; | ||
557 | let segments = collect_hir_path_segments(&hir_path)?; | ||
558 | if segments.len() < 2 { | ||
559 | return None; | ||
560 | } | ||
561 | |||
562 | if let Some(module) = path.syntax().ancestors().find_map(ast::Module::cast) { | ||
563 | if let (Some(item_list), Some(name)) = (module.item_list(), module.name()) { | ||
564 | ctx.add_action( | ||
565 | AssistId("auto_import"), | ||
566 | format!("import {} in mod {}", fmt_segments(&segments), name.text()), | ||
567 | |edit| { | ||
568 | apply_auto_import( | ||
569 | item_list.syntax(), | ||
570 | &path, | ||
571 | &segments, | ||
572 | edit.text_edit_builder(), | ||
573 | ); | ||
574 | }, | ||
575 | ); | ||
576 | } | ||
577 | } else { | ||
578 | let current_file = path.syntax().ancestors().find_map(ast::SourceFile::cast)?; | ||
579 | ctx.add_action( | ||
580 | AssistId("auto_import"), | ||
581 | format!("import {} in the current file", fmt_segments(&segments)), | ||
582 | |edit| { | ||
583 | apply_auto_import( | ||
584 | current_file.syntax(), | ||
585 | &path, | ||
586 | &segments, | ||
587 | edit.text_edit_builder(), | ||
588 | ); | ||
589 | }, | ||
590 | ); | ||
591 | } | ||
592 | |||
593 | ctx.build() | ||
594 | } | ||
595 | |||
596 | #[cfg(test)] | 587 | #[cfg(test)] |
597 | mod tests { | 588 | mod tests { |
598 | use super::*; | ||
599 | use crate::helpers::{check_assist, check_assist_not_applicable}; | 589 | use crate::helpers::{check_assist, check_assist_not_applicable}; |
600 | 590 | ||
591 | use super::*; | ||
592 | |||
601 | #[test] | 593 | #[test] |
602 | fn test_auto_import_add_use_no_anchor() { | 594 | fn test_auto_import_add_use_no_anchor() { |
603 | check_assist( | 595 | check_assist( |
604 | auto_import, | 596 | add_import, |
605 | " | 597 | " |
606 | std::fmt::Debug<|> | 598 | std::fmt::Debug<|> |
607 | ", | 599 | ", |
@@ -615,7 +607,7 @@ Debug<|> | |||
615 | #[test] | 607 | #[test] |
616 | fn test_auto_import_add_use_no_anchor_with_item_below() { | 608 | fn test_auto_import_add_use_no_anchor_with_item_below() { |
617 | check_assist( | 609 | check_assist( |
618 | auto_import, | 610 | add_import, |
619 | " | 611 | " |
620 | std::fmt::Debug<|> | 612 | std::fmt::Debug<|> |
621 | 613 | ||
@@ -636,7 +628,7 @@ fn main() { | |||
636 | #[test] | 628 | #[test] |
637 | fn test_auto_import_add_use_no_anchor_with_item_above() { | 629 | fn test_auto_import_add_use_no_anchor_with_item_above() { |
638 | check_assist( | 630 | check_assist( |
639 | auto_import, | 631 | add_import, |
640 | " | 632 | " |
641 | fn main() { | 633 | fn main() { |
642 | } | 634 | } |
@@ -657,7 +649,7 @@ Debug<|> | |||
657 | #[test] | 649 | #[test] |
658 | fn test_auto_import_add_use_no_anchor_2seg() { | 650 | fn test_auto_import_add_use_no_anchor_2seg() { |
659 | check_assist( | 651 | check_assist( |
660 | auto_import, | 652 | add_import, |
661 | " | 653 | " |
662 | std::fmt<|>::Debug | 654 | std::fmt<|>::Debug |
663 | ", | 655 | ", |
@@ -672,7 +664,7 @@ fmt<|>::Debug | |||
672 | #[test] | 664 | #[test] |
673 | fn test_auto_import_add_use() { | 665 | fn test_auto_import_add_use() { |
674 | check_assist( | 666 | check_assist( |
675 | auto_import, | 667 | add_import, |
676 | " | 668 | " |
677 | use stdx; | 669 | use stdx; |
678 | 670 | ||
@@ -692,7 +684,7 @@ impl Debug<|> for Foo { | |||
692 | #[test] | 684 | #[test] |
693 | fn test_auto_import_file_use_other_anchor() { | 685 | fn test_auto_import_file_use_other_anchor() { |
694 | check_assist( | 686 | check_assist( |
695 | auto_import, | 687 | add_import, |
696 | " | 688 | " |
697 | impl std::fmt::Debug<|> for Foo { | 689 | impl std::fmt::Debug<|> for Foo { |
698 | } | 690 | } |
@@ -709,7 +701,7 @@ impl Debug<|> for Foo { | |||
709 | #[test] | 701 | #[test] |
710 | fn test_auto_import_add_use_other_anchor_indent() { | 702 | fn test_auto_import_add_use_other_anchor_indent() { |
711 | check_assist( | 703 | check_assist( |
712 | auto_import, | 704 | add_import, |
713 | " | 705 | " |
714 | impl std::fmt::Debug<|> for Foo { | 706 | impl std::fmt::Debug<|> for Foo { |
715 | } | 707 | } |
@@ -726,7 +718,7 @@ impl Debug<|> for Foo { | |||
726 | #[test] | 718 | #[test] |
727 | fn test_auto_import_split_different() { | 719 | fn test_auto_import_split_different() { |
728 | check_assist( | 720 | check_assist( |
729 | auto_import, | 721 | add_import, |
730 | " | 722 | " |
731 | use std::fmt; | 723 | use std::fmt; |
732 | 724 | ||
@@ -745,7 +737,7 @@ impl io<|> for Foo { | |||
745 | #[test] | 737 | #[test] |
746 | fn test_auto_import_split_self_for_use() { | 738 | fn test_auto_import_split_self_for_use() { |
747 | check_assist( | 739 | check_assist( |
748 | auto_import, | 740 | add_import, |
749 | " | 741 | " |
750 | use std::fmt; | 742 | use std::fmt; |
751 | 743 | ||
@@ -764,7 +756,7 @@ impl Debug<|> for Foo { | |||
764 | #[test] | 756 | #[test] |
765 | fn test_auto_import_split_self_for_target() { | 757 | fn test_auto_import_split_self_for_target() { |
766 | check_assist( | 758 | check_assist( |
767 | auto_import, | 759 | add_import, |
768 | " | 760 | " |
769 | use std::fmt::Debug; | 761 | use std::fmt::Debug; |
770 | 762 | ||
@@ -783,7 +775,7 @@ impl fmt<|> for Foo { | |||
783 | #[test] | 775 | #[test] |
784 | fn test_auto_import_add_to_nested_self_nested() { | 776 | fn test_auto_import_add_to_nested_self_nested() { |
785 | check_assist( | 777 | check_assist( |
786 | auto_import, | 778 | add_import, |
787 | " | 779 | " |
788 | use std::fmt::{Debug, nested::{Display}}; | 780 | use std::fmt::{Debug, nested::{Display}}; |
789 | 781 | ||
@@ -802,7 +794,7 @@ impl nested<|> for Foo { | |||
802 | #[test] | 794 | #[test] |
803 | fn test_auto_import_add_to_nested_self_already_included() { | 795 | fn test_auto_import_add_to_nested_self_already_included() { |
804 | check_assist( | 796 | check_assist( |
805 | auto_import, | 797 | add_import, |
806 | " | 798 | " |
807 | use std::fmt::{Debug, nested::{self, Display}}; | 799 | use std::fmt::{Debug, nested::{self, Display}}; |
808 | 800 | ||
@@ -821,7 +813,7 @@ impl nested<|> for Foo { | |||
821 | #[test] | 813 | #[test] |
822 | fn test_auto_import_add_to_nested_nested() { | 814 | fn test_auto_import_add_to_nested_nested() { |
823 | check_assist( | 815 | check_assist( |
824 | auto_import, | 816 | add_import, |
825 | " | 817 | " |
826 | use std::fmt::{Debug, nested::{Display}}; | 818 | use std::fmt::{Debug, nested::{Display}}; |
827 | 819 | ||
@@ -840,7 +832,7 @@ impl Debug<|> for Foo { | |||
840 | #[test] | 832 | #[test] |
841 | fn test_auto_import_split_common_target_longer() { | 833 | fn test_auto_import_split_common_target_longer() { |
842 | check_assist( | 834 | check_assist( |
843 | auto_import, | 835 | add_import, |
844 | " | 836 | " |
845 | use std::fmt::Debug; | 837 | use std::fmt::Debug; |
846 | 838 | ||
@@ -859,7 +851,7 @@ impl Display<|> for Foo { | |||
859 | #[test] | 851 | #[test] |
860 | fn test_auto_import_split_common_use_longer() { | 852 | fn test_auto_import_split_common_use_longer() { |
861 | check_assist( | 853 | check_assist( |
862 | auto_import, | 854 | add_import, |
863 | " | 855 | " |
864 | use std::fmt::nested::Debug; | 856 | use std::fmt::nested::Debug; |
865 | 857 | ||
@@ -878,7 +870,7 @@ impl Display<|> for Foo { | |||
878 | #[test] | 870 | #[test] |
879 | fn test_auto_import_alias() { | 871 | fn test_auto_import_alias() { |
880 | check_assist( | 872 | check_assist( |
881 | auto_import, | 873 | add_import, |
882 | " | 874 | " |
883 | use std::fmt as foo; | 875 | use std::fmt as foo; |
884 | 876 | ||
@@ -897,7 +889,7 @@ impl Debug<|> for Foo { | |||
897 | #[test] | 889 | #[test] |
898 | fn test_auto_import_not_applicable_one_segment() { | 890 | fn test_auto_import_not_applicable_one_segment() { |
899 | check_assist_not_applicable( | 891 | check_assist_not_applicable( |
900 | auto_import, | 892 | add_import, |
901 | " | 893 | " |
902 | impl foo<|> for Foo { | 894 | impl foo<|> for Foo { |
903 | } | 895 | } |
@@ -908,7 +900,7 @@ impl foo<|> for Foo { | |||
908 | #[test] | 900 | #[test] |
909 | fn test_auto_import_not_applicable_in_use() { | 901 | fn test_auto_import_not_applicable_in_use() { |
910 | check_assist_not_applicable( | 902 | check_assist_not_applicable( |
911 | auto_import, | 903 | add_import, |
912 | " | 904 | " |
913 | use std::fmt<|>; | 905 | use std::fmt<|>; |
914 | ", | 906 | ", |
@@ -918,7 +910,7 @@ use std::fmt<|>; | |||
918 | #[test] | 910 | #[test] |
919 | fn test_auto_import_add_use_no_anchor_in_mod_mod() { | 911 | fn test_auto_import_add_use_no_anchor_in_mod_mod() { |
920 | check_assist( | 912 | check_assist( |
921 | auto_import, | 913 | add_import, |
922 | " | 914 | " |
923 | mod foo { | 915 | mod foo { |
924 | mod bar { | 916 | mod bar { |
diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs index 2585f3045..41de23921 100644 --- a/crates/ra_assists/src/assists/add_missing_impl_members.rs +++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs | |||
@@ -91,7 +91,7 @@ pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> O | |||
91 | } | 91 | } |
92 | 92 | ||
93 | fn add_missing_impl_members_inner( | 93 | fn add_missing_impl_members_inner( |
94 | mut ctx: AssistCtx<impl HirDatabase>, | 94 | ctx: AssistCtx<impl HirDatabase>, |
95 | mode: AddMissingImplMembersMode, | 95 | mode: AddMissingImplMembersMode, |
96 | assist_id: &'static str, | 96 | assist_id: &'static str, |
97 | label: &'static str, | 97 | label: &'static str, |
@@ -133,7 +133,7 @@ fn add_missing_impl_members_inner( | |||
133 | return None; | 133 | return None; |
134 | } | 134 | } |
135 | 135 | ||
136 | ctx.add_action(AssistId(assist_id), label, |edit| { | 136 | ctx.add_assist(AssistId(assist_id), label, |edit| { |
137 | let n_existing_items = impl_item_list.impl_items().count(); | 137 | let n_existing_items = impl_item_list.impl_items().count(); |
138 | let items = missing_items | 138 | let items = missing_items |
139 | .into_iter() | 139 | .into_iter() |
@@ -150,9 +150,7 @@ fn add_missing_impl_members_inner( | |||
150 | 150 | ||
151 | edit.replace_ast(impl_item_list, new_impl_item_list); | 151 | edit.replace_ast(impl_item_list, new_impl_item_list); |
152 | edit.set_cursor(cursor_position); | 152 | edit.set_cursor(cursor_position); |
153 | }); | 153 | }) |
154 | |||
155 | ctx.build() | ||
156 | } | 154 | } |
157 | 155 | ||
158 | fn add_body(fn_def: ast::FnDef) -> ast::FnDef { | 156 | fn add_body(fn_def: ast::FnDef) -> ast::FnDef { |
diff --git a/crates/ra_assists/src/assists/apply_demorgan.rs b/crates/ra_assists/src/assists/apply_demorgan.rs index 8d5984a58..068da1774 100644 --- a/crates/ra_assists/src/assists/apply_demorgan.rs +++ b/crates/ra_assists/src/assists/apply_demorgan.rs | |||
@@ -23,7 +23,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
23 | // if !(x == 4 && y) {} | 23 | // if !(x == 4 && y) {} |
24 | // } | 24 | // } |
25 | // ``` | 25 | // ``` |
26 | pub(crate) fn apply_demorgan(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 26 | pub(crate) fn apply_demorgan(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
27 | let expr = ctx.find_node_at_offset::<ast::BinExpr>()?; | 27 | let expr = ctx.find_node_at_offset::<ast::BinExpr>()?; |
28 | let op = expr.op_kind()?; | 28 | let op = expr.op_kind()?; |
29 | let op_range = expr.op_token()?.text_range(); | 29 | let op_range = expr.op_token()?.text_range(); |
@@ -39,13 +39,12 @@ pub(crate) fn apply_demorgan(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Ass | |||
39 | let not_lhs = undo_negation(lhs)?; | 39 | let not_lhs = undo_negation(lhs)?; |
40 | let not_rhs = undo_negation(rhs)?; | 40 | let not_rhs = undo_negation(rhs)?; |
41 | 41 | ||
42 | ctx.add_action(AssistId("apply_demorgan"), "apply demorgan's law", |edit| { | 42 | ctx.add_assist(AssistId("apply_demorgan"), "apply demorgan's law", |edit| { |
43 | edit.target(op_range); | 43 | edit.target(op_range); |
44 | edit.replace(op_range, opposite_op); | 44 | edit.replace(op_range, opposite_op); |
45 | edit.replace(lhs_range, format!("!({}", not_lhs)); | 45 | edit.replace(lhs_range, format!("!({}", not_lhs)); |
46 | edit.replace(rhs_range, format!("{})", not_rhs)); | 46 | edit.replace(rhs_range, format!("{})", not_rhs)); |
47 | }); | 47 | }) |
48 | ctx.build() | ||
49 | } | 48 | } |
50 | 49 | ||
51 | // Return the opposite text for a given logical operator, if it makes sense | 50 | // Return the opposite text for a given logical operator, if it makes sense |
diff --git a/crates/ra_assists/src/assists/change_visibility.rs b/crates/ra_assists/src/assists/change_visibility.rs index 770ea04fa..132c9dc1d 100644 --- a/crates/ra_assists/src/assists/change_visibility.rs +++ b/crates/ra_assists/src/assists/change_visibility.rs | |||
@@ -29,7 +29,7 @@ pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assi | |||
29 | add_vis(ctx) | 29 | add_vis(ctx) |
30 | } | 30 | } |
31 | 31 | ||
32 | fn add_vis(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 32 | fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
33 | let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { | 33 | let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { |
34 | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, | 34 | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, |
35 | _ => false, | 35 | _ => false, |
@@ -57,13 +57,11 @@ fn add_vis(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
57 | (vis_offset(field.syntax()), ident.text_range()) | 57 | (vis_offset(field.syntax()), ident.text_range()) |
58 | }; | 58 | }; |
59 | 59 | ||
60 | ctx.add_action(AssistId("change_visibility"), "make pub(crate)", |edit| { | 60 | ctx.add_assist(AssistId("change_visibility"), "make pub(crate)", |edit| { |
61 | edit.target(target); | 61 | edit.target(target); |
62 | edit.insert(offset, "pub(crate) "); | 62 | edit.insert(offset, "pub(crate) "); |
63 | edit.set_cursor(offset); | 63 | edit.set_cursor(offset); |
64 | }); | 64 | }) |
65 | |||
66 | ctx.build() | ||
67 | } | 65 | } |
68 | 66 | ||
69 | fn vis_offset(node: &SyntaxNode) -> TextUnit { | 67 | fn vis_offset(node: &SyntaxNode) -> TextUnit { |
@@ -77,24 +75,20 @@ fn vis_offset(node: &SyntaxNode) -> TextUnit { | |||
77 | .unwrap_or_else(|| node.text_range().start()) | 75 | .unwrap_or_else(|| node.text_range().start()) |
78 | } | 76 | } |
79 | 77 | ||
80 | fn change_vis(mut ctx: AssistCtx<impl HirDatabase>, vis: ast::Visibility) -> Option<Assist> { | 78 | fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: ast::Visibility) -> Option<Assist> { |
81 | if vis.syntax().text() == "pub" { | 79 | if vis.syntax().text() == "pub" { |
82 | ctx.add_action(AssistId("change_visibility"), "change to pub(crate)", |edit| { | 80 | return ctx.add_assist(AssistId("change_visibility"), "change to pub(crate)", |edit| { |
83 | edit.target(vis.syntax().text_range()); | 81 | edit.target(vis.syntax().text_range()); |
84 | edit.replace(vis.syntax().text_range(), "pub(crate)"); | 82 | edit.replace(vis.syntax().text_range(), "pub(crate)"); |
85 | edit.set_cursor(vis.syntax().text_range().start()) | 83 | edit.set_cursor(vis.syntax().text_range().start()) |
86 | }); | 84 | }); |
87 | |||
88 | return ctx.build(); | ||
89 | } | 85 | } |
90 | if vis.syntax().text() == "pub(crate)" { | 86 | if vis.syntax().text() == "pub(crate)" { |
91 | ctx.add_action(AssistId("change_visibility"), "change to pub", |edit| { | 87 | return ctx.add_assist(AssistId("change_visibility"), "change to pub", |edit| { |
92 | edit.target(vis.syntax().text_range()); | 88 | edit.target(vis.syntax().text_range()); |
93 | edit.replace(vis.syntax().text_range(), "pub"); | 89 | edit.replace(vis.syntax().text_range(), "pub"); |
94 | edit.set_cursor(vis.syntax().text_range().start()); | 90 | edit.set_cursor(vis.syntax().text_range().start()); |
95 | }); | 91 | }); |
96 | |||
97 | return ctx.build(); | ||
98 | } | 92 | } |
99 | None | 93 | None |
100 | } | 94 | } |
diff --git a/crates/ra_assists/src/assists/early_return.rs b/crates/ra_assists/src/assists/early_return.rs index 75822dc65..e839d831e 100644 --- a/crates/ra_assists/src/assists/early_return.rs +++ b/crates/ra_assists/src/assists/early_return.rs | |||
@@ -35,7 +35,7 @@ use crate::{ | |||
35 | // bar(); | 35 | // bar(); |
36 | // } | 36 | // } |
37 | // ``` | 37 | // ``` |
38 | pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 38 | pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
39 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; | 39 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; |
40 | let expr = if_expr.condition()?.expr()?; | 40 | let expr = if_expr.condition()?.expr()?; |
41 | let then_block = if_expr.then_branch()?.block()?; | 41 | let then_block = if_expr.then_branch()?.block()?; |
@@ -75,7 +75,7 @@ pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) -> | |||
75 | then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; | 75 | then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; |
76 | let cursor_position = ctx.frange.range.start(); | 76 | let cursor_position = ctx.frange.range.start(); |
77 | 77 | ||
78 | ctx.add_action(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| { | 78 | ctx.add_assist(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| { |
79 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); | 79 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); |
80 | let new_if_expr = | 80 | let new_if_expr = |
81 | if_indent_level.increase_indent(make::if_expression(&expr, early_expression)); | 81 | if_indent_level.increase_indent(make::if_expression(&expr, early_expression)); |
@@ -105,8 +105,7 @@ pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) -> | |||
105 | edit.target(if_expr.syntax().text_range()); | 105 | edit.target(if_expr.syntax().text_range()); |
106 | edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap()); | 106 | edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap()); |
107 | edit.set_cursor(cursor_position); | 107 | edit.set_cursor(cursor_position); |
108 | }); | 108 | }) |
109 | ctx.build() | ||
110 | } | 109 | } |
111 | 110 | ||
112 | #[cfg(test)] | 111 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/assists/fill_match_arms.rs index c62c0efbe..2b74f355c 100644 --- a/crates/ra_assists/src/assists/fill_match_arms.rs +++ b/crates/ra_assists/src/assists/fill_match_arms.rs | |||
@@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
31 | // } | 31 | // } |
32 | // } | 32 | // } |
33 | // ``` | 33 | // ``` |
34 | pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 34 | pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
35 | let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; | 35 | let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; |
36 | let match_arm_list = match_expr.match_arm_list()?; | 36 | let match_arm_list = match_expr.match_arm_list()?; |
37 | 37 | ||
@@ -53,7 +53,7 @@ pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As | |||
53 | }; | 53 | }; |
54 | let variant_list = enum_def.variant_list()?; | 54 | let variant_list = enum_def.variant_list()?; |
55 | 55 | ||
56 | ctx.add_action(AssistId("fill_match_arms"), "fill match arms", |edit| { | 56 | ctx.add_assist(AssistId("fill_match_arms"), "fill match arms", |edit| { |
57 | let indent_level = IndentLevel::from_node(match_arm_list.syntax()); | 57 | let indent_level = IndentLevel::from_node(match_arm_list.syntax()); |
58 | 58 | ||
59 | let new_arm_list = { | 59 | let new_arm_list = { |
@@ -67,9 +67,7 @@ pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As | |||
67 | edit.target(match_expr.syntax().text_range()); | 67 | edit.target(match_expr.syntax().text_range()); |
68 | edit.set_cursor(expr.syntax().text_range().start()); | 68 | edit.set_cursor(expr.syntax().text_range().start()); |
69 | edit.replace_ast(match_arm_list, new_arm_list); | 69 | edit.replace_ast(match_arm_list, new_arm_list); |
70 | }); | 70 | }) |
71 | |||
72 | ctx.build() | ||
73 | } | 71 | } |
74 | 72 | ||
75 | fn is_trivial(arm: &ast::MatchArm) -> bool { | 73 | fn is_trivial(arm: &ast::MatchArm) -> bool { |
diff --git a/crates/ra_assists/src/assists/flip_binexpr.rs b/crates/ra_assists/src/assists/flip_binexpr.rs index 9765d5ddd..386045eb0 100644 --- a/crates/ra_assists/src/assists/flip_binexpr.rs +++ b/crates/ra_assists/src/assists/flip_binexpr.rs | |||
@@ -18,7 +18,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
18 | // let _ = 2 + 90; | 18 | // let _ = 2 + 90; |
19 | // } | 19 | // } |
20 | // ``` | 20 | // ``` |
21 | pub(crate) fn flip_binexpr(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 21 | pub(crate) fn flip_binexpr(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
22 | let expr = ctx.find_node_at_offset::<BinExpr>()?; | 22 | let expr = ctx.find_node_at_offset::<BinExpr>()?; |
23 | let lhs = expr.lhs()?.syntax().clone(); | 23 | let lhs = expr.lhs()?.syntax().clone(); |
24 | let rhs = expr.rhs()?.syntax().clone(); | 24 | let rhs = expr.rhs()?.syntax().clone(); |
@@ -34,16 +34,14 @@ pub(crate) fn flip_binexpr(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assis | |||
34 | return None; | 34 | return None; |
35 | } | 35 | } |
36 | 36 | ||
37 | ctx.add_action(AssistId("flip_binexpr"), "flip binary expression", |edit| { | 37 | ctx.add_assist(AssistId("flip_binexpr"), "flip binary expression", |edit| { |
38 | edit.target(op_range); | 38 | edit.target(op_range); |
39 | if let FlipAction::FlipAndReplaceOp(new_op) = action { | 39 | if let FlipAction::FlipAndReplaceOp(new_op) = action { |
40 | edit.replace(op_range, new_op); | 40 | edit.replace(op_range, new_op); |
41 | } | 41 | } |
42 | edit.replace(lhs.text_range(), rhs.text()); | 42 | edit.replace(lhs.text_range(), rhs.text()); |
43 | edit.replace(rhs.text_range(), lhs.text()); | 43 | edit.replace(rhs.text_range(), lhs.text()); |
44 | }); | 44 | }) |
45 | |||
46 | ctx.build() | ||
47 | } | 45 | } |
48 | 46 | ||
49 | enum FlipAction { | 47 | enum FlipAction { |
diff --git a/crates/ra_assists/src/assists/flip_comma.rs b/crates/ra_assists/src/assists/flip_comma.rs index 53ba8011d..9be1c1dc6 100644 --- a/crates/ra_assists/src/assists/flip_comma.rs +++ b/crates/ra_assists/src/assists/flip_comma.rs | |||
@@ -18,7 +18,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
18 | // ((3, 4), (1, 2)); | 18 | // ((3, 4), (1, 2)); |
19 | // } | 19 | // } |
20 | // ``` | 20 | // ``` |
21 | pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 21 | pub(crate) fn flip_comma(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
22 | let comma = ctx.find_token_at_offset(T![,])?; | 22 | let comma = ctx.find_token_at_offset(T![,])?; |
23 | let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; | 23 | let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; |
24 | let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; | 24 | let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; |
@@ -29,13 +29,11 @@ pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> | |||
29 | return None; | 29 | return None; |
30 | } | 30 | } |
31 | 31 | ||
32 | ctx.add_action(AssistId("flip_comma"), "flip comma", |edit| { | 32 | ctx.add_assist(AssistId("flip_comma"), "flip comma", |edit| { |
33 | edit.target(comma.text_range()); | 33 | edit.target(comma.text_range()); |
34 | edit.replace(prev.text_range(), next.to_string()); | 34 | edit.replace(prev.text_range(), next.to_string()); |
35 | edit.replace(next.text_range(), prev.to_string()); | 35 | edit.replace(next.text_range(), prev.to_string()); |
36 | }); | 36 | }) |
37 | |||
38 | ctx.build() | ||
39 | } | 37 | } |
40 | 38 | ||
41 | #[cfg(test)] | 39 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/assists/flip_trait_bound.rs b/crates/ra_assists/src/assists/flip_trait_bound.rs new file mode 100644 index 000000000..6017b39dd --- /dev/null +++ b/crates/ra_assists/src/assists/flip_trait_bound.rs | |||
@@ -0,0 +1,117 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | ||
3 | algo::non_trivia_sibling, | ||
4 | ast::{self, AstNode}, | ||
5 | Direction, T, | ||
6 | }; | ||
7 | |||
8 | use crate::{Assist, AssistCtx, AssistId}; | ||
9 | |||
10 | // Assist: flip_trait_bound | ||
11 | // | ||
12 | // Flips two trait bounds. | ||
13 | // | ||
14 | // ``` | ||
15 | // fn foo<T: Clone +<|> Copy>() { } | ||
16 | // ``` | ||
17 | // -> | ||
18 | // ``` | ||
19 | // fn foo<T: Copy + Clone>() { } | ||
20 | // ``` | ||
21 | pub(crate) fn flip_trait_bound(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
22 | // We want to replicate the behavior of `flip_binexpr` by only suggesting | ||
23 | // the assist when the cursor is on a `+` | ||
24 | let plus = ctx.find_token_at_offset(T![+])?; | ||
25 | |||
26 | // Make sure we're in a `TypeBoundList` | ||
27 | if ast::TypeBoundList::cast(plus.parent()).is_none() { | ||
28 | return None; | ||
29 | } | ||
30 | |||
31 | let (before, after) = ( | ||
32 | non_trivia_sibling(plus.clone().into(), Direction::Prev)?, | ||
33 | non_trivia_sibling(plus.clone().into(), Direction::Next)?, | ||
34 | ); | ||
35 | |||
36 | ctx.add_assist(AssistId("flip_trait_bound"), "flip trait bound", |edit| { | ||
37 | edit.target(plus.text_range()); | ||
38 | edit.replace(before.text_range(), after.to_string()); | ||
39 | edit.replace(after.text_range(), before.to_string()); | ||
40 | }) | ||
41 | } | ||
42 | |||
43 | #[cfg(test)] | ||
44 | mod tests { | ||
45 | use super::*; | ||
46 | |||
47 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
48 | |||
49 | #[test] | ||
50 | fn flip_trait_bound_assist_available() { | ||
51 | check_assist_target(flip_trait_bound, "struct S<T> where T: A <|>+ B + C { }", "+") | ||
52 | } | ||
53 | |||
54 | #[test] | ||
55 | fn flip_trait_bound_not_applicable_for_single_trait_bound() { | ||
56 | check_assist_not_applicable(flip_trait_bound, "struct S<T> where T: <|>A { }") | ||
57 | } | ||
58 | |||
59 | #[test] | ||
60 | fn flip_trait_bound_works_for_struct() { | ||
61 | check_assist( | ||
62 | flip_trait_bound, | ||
63 | "struct S<T> where T: A <|>+ B { }", | ||
64 | "struct S<T> where T: B <|>+ A { }", | ||
65 | ) | ||
66 | } | ||
67 | |||
68 | #[test] | ||
69 | fn flip_trait_bound_works_for_trait_impl() { | ||
70 | check_assist( | ||
71 | flip_trait_bound, | ||
72 | "impl X for S<T> where T: A +<|> B { }", | ||
73 | "impl X for S<T> where T: B +<|> A { }", | ||
74 | ) | ||
75 | } | ||
76 | |||
77 | #[test] | ||
78 | fn flip_trait_bound_works_for_fn() { | ||
79 | check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B <|>+ A>(t: T) { }") | ||
80 | } | ||
81 | |||
82 | #[test] | ||
83 | fn flip_trait_bound_works_for_fn_where_clause() { | ||
84 | check_assist( | ||
85 | flip_trait_bound, | ||
86 | "fn f<T>(t: T) where T: A +<|> B { }", | ||
87 | "fn f<T>(t: T) where T: B +<|> A { }", | ||
88 | ) | ||
89 | } | ||
90 | |||
91 | #[test] | ||
92 | fn flip_trait_bound_works_for_lifetime() { | ||
93 | check_assist( | ||
94 | flip_trait_bound, | ||
95 | "fn f<T>(t: T) where T: A <|>+ 'static { }", | ||
96 | "fn f<T>(t: T) where T: 'static <|>+ A { }", | ||
97 | ) | ||
98 | } | ||
99 | |||
100 | #[test] | ||
101 | fn flip_trait_bound_works_for_complex_bounds() { | ||
102 | check_assist( | ||
103 | flip_trait_bound, | ||
104 | "struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }", | ||
105 | "struct S<T> where T: b_mod::B<T> <|>+ A<T> + C<T> { }", | ||
106 | ) | ||
107 | } | ||
108 | |||
109 | #[test] | ||
110 | fn flip_trait_bound_works_for_long_bounds() { | ||
111 | check_assist( | ||
112 | flip_trait_bound, | ||
113 | "struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }", | ||
114 | "struct S<T> where T: A + B + C + D + E + G +<|> F + H + I + J { }", | ||
115 | ) | ||
116 | } | ||
117 | } | ||
diff --git a/crates/ra_assists/src/assists/inline_local_variable.rs b/crates/ra_assists/src/assists/inline_local_variable.rs index fe8fa2a86..f43910574 100644 --- a/crates/ra_assists/src/assists/inline_local_variable.rs +++ b/crates/ra_assists/src/assists/inline_local_variable.rs | |||
@@ -23,7 +23,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
23 | // (1 + 2) * 4; | 23 | // (1 + 2) * 4; |
24 | // } | 24 | // } |
25 | // ``` | 25 | // ``` |
26 | pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 26 | pub(crate) fn inline_local_varialbe(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
27 | let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; | 27 | let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; |
28 | let bind_pat = match let_stmt.pat()? { | 28 | let bind_pat = match let_stmt.pat()? { |
29 | ast::Pat::BindPat(pat) => pat, | 29 | ast::Pat::BindPat(pat) => pat, |
@@ -93,7 +93,7 @@ pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Opt | |||
93 | let init_str = initializer_expr.syntax().text().to_string(); | 93 | let init_str = initializer_expr.syntax().text().to_string(); |
94 | let init_in_paren = format!("({})", &init_str); | 94 | let init_in_paren = format!("({})", &init_str); |
95 | 95 | ||
96 | ctx.add_action( | 96 | ctx.add_assist( |
97 | AssistId("inline_local_variable"), | 97 | AssistId("inline_local_variable"), |
98 | "inline local variable", | 98 | "inline local variable", |
99 | move |edit: &mut AssistBuilder| { | 99 | move |edit: &mut AssistBuilder| { |
@@ -107,9 +107,7 @@ pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Opt | |||
107 | } | 107 | } |
108 | edit.set_cursor(delete_range.start()) | 108 | edit.set_cursor(delete_range.start()) |
109 | }, | 109 | }, |
110 | ); | 110 | ) |
111 | |||
112 | ctx.build() | ||
113 | } | 111 | } |
114 | 112 | ||
115 | #[cfg(test)] | 113 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/assists/introduce_variable.rs b/crates/ra_assists/src/assists/introduce_variable.rs index 8245dc99f..0623d4475 100644 --- a/crates/ra_assists/src/assists/introduce_variable.rs +++ b/crates/ra_assists/src/assists/introduce_variable.rs | |||
@@ -28,7 +28,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
28 | // var_name * 4; | 28 | // var_name * 4; |
29 | // } | 29 | // } |
30 | // ``` | 30 | // ``` |
31 | pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 31 | pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
32 | if ctx.frange.range.is_empty() { | 32 | if ctx.frange.range.is_empty() { |
33 | return None; | 33 | return None; |
34 | } | 34 | } |
@@ -43,7 +43,7 @@ pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option | |||
43 | if indent.kind() != WHITESPACE { | 43 | if indent.kind() != WHITESPACE { |
44 | return None; | 44 | return None; |
45 | } | 45 | } |
46 | ctx.add_action(AssistId("introduce_variable"), "introduce variable", move |edit| { | 46 | ctx.add_assist(AssistId("introduce_variable"), "introduce variable", move |edit| { |
47 | let mut buf = String::new(); | 47 | let mut buf = String::new(); |
48 | 48 | ||
49 | let cursor_offset = if wrap_in_block { | 49 | let cursor_offset = if wrap_in_block { |
@@ -88,9 +88,7 @@ pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option | |||
88 | } | 88 | } |
89 | } | 89 | } |
90 | edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset); | 90 | edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset); |
91 | }); | 91 | }) |
92 | |||
93 | ctx.build() | ||
94 | } | 92 | } |
95 | 93 | ||
96 | /// Check whether the node is a valid expression which can be extracted to a variable. | 94 | /// Check whether the node is a valid expression which can be extracted to a variable. |
diff --git a/crates/ra_assists/src/assists/merge_match_arms.rs b/crates/ra_assists/src/assists/merge_match_arms.rs index b0c4ee78b..e9f2cae91 100644 --- a/crates/ra_assists/src/assists/merge_match_arms.rs +++ b/crates/ra_assists/src/assists/merge_match_arms.rs | |||
@@ -26,7 +26,7 @@ use ra_syntax::ast::{AstNode, MatchArm}; | |||
26 | // } | 26 | // } |
27 | // } | 27 | // } |
28 | // ``` | 28 | // ``` |
29 | pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 29 | pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
30 | let current_arm = ctx.find_node_at_offset::<MatchArm>()?; | 30 | let current_arm = ctx.find_node_at_offset::<MatchArm>()?; |
31 | 31 | ||
32 | // We check if the following match arm matches this one. We could, but don't, | 32 | // We check if the following match arm matches this one. We could, but don't, |
@@ -52,7 +52,7 @@ pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<A | |||
52 | 52 | ||
53 | let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start(); | 53 | let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start(); |
54 | 54 | ||
55 | ctx.add_action(AssistId("merge_match_arms"), "merge match arms", |edit| { | 55 | ctx.add_assist(AssistId("merge_match_arms"), "merge match arms", |edit| { |
56 | fn contains_placeholder(a: &MatchArm) -> bool { | 56 | fn contains_placeholder(a: &MatchArm) -> bool { |
57 | a.pats().any(|x| match x { | 57 | a.pats().any(|x| match x { |
58 | ra_syntax::ast::Pat::PlaceholderPat(..) => true, | 58 | ra_syntax::ast::Pat::PlaceholderPat(..) => true, |
@@ -80,9 +80,7 @@ pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<A | |||
80 | edit.target(current_arm.syntax().text_range()); | 80 | edit.target(current_arm.syntax().text_range()); |
81 | edit.replace(TextRange::from_to(start, end), arm); | 81 | edit.replace(TextRange::from_to(start, end), arm); |
82 | edit.set_cursor(start + offset); | 82 | edit.set_cursor(start + offset); |
83 | }); | 83 | }) |
84 | |||
85 | ctx.build() | ||
86 | } | 84 | } |
87 | 85 | ||
88 | #[cfg(test)] | 86 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/assists/move_bounds.rs b/crates/ra_assists/src/assists/move_bounds.rs index edf2897c6..3145d7625 100644 --- a/crates/ra_assists/src/assists/move_bounds.rs +++ b/crates/ra_assists/src/assists/move_bounds.rs | |||
@@ -22,7 +22,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
22 | // f(x) | 22 | // f(x) |
23 | // } | 23 | // } |
24 | // ``` | 24 | // ``` |
25 | pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 25 | pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
26 | let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?; | 26 | let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?; |
27 | 27 | ||
28 | let mut type_params = type_param_list.type_params(); | 28 | let mut type_params = type_param_list.type_params(); |
@@ -46,38 +46,30 @@ pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) | |||
46 | _ => return None, | 46 | _ => return None, |
47 | }; | 47 | }; |
48 | 48 | ||
49 | ctx.add_action( | 49 | ctx.add_assist(AssistId("move_bounds_to_where_clause"), "move_bounds_to_where_clause", |edit| { |
50 | AssistId("move_bounds_to_where_clause"), | 50 | let new_params = type_param_list |
51 | "move_bounds_to_where_clause", | 51 | .type_params() |
52 | |edit| { | 52 | .filter(|it| it.type_bound_list().is_some()) |
53 | let new_params = type_param_list | 53 | .map(|type_param| { |
54 | .type_params() | 54 | let without_bounds = type_param.remove_bounds(); |
55 | .filter(|it| it.type_bound_list().is_some()) | 55 | (type_param, without_bounds) |
56 | .map(|type_param| { | 56 | }); |
57 | let without_bounds = type_param.remove_bounds(); | 57 | |
58 | (type_param, without_bounds) | 58 | let new_type_param_list = edit::replace_descendants(&type_param_list, new_params); |
59 | }); | 59 | edit.replace_ast(type_param_list.clone(), new_type_param_list); |
60 | 60 | ||
61 | let new_type_param_list = edit::replace_descendants(&type_param_list, new_params); | 61 | let where_clause = { |
62 | edit.replace_ast(type_param_list.clone(), new_type_param_list); | 62 | let predicates = type_param_list.type_params().filter_map(build_predicate); |
63 | 63 | make::where_clause(predicates) | |
64 | let where_clause = { | 64 | }; |
65 | let predicates = type_param_list.type_params().filter_map(build_predicate); | 65 | |
66 | make::where_clause(predicates) | 66 | let to_insert = match anchor.prev_sibling_or_token() { |
67 | }; | 67 | Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()), |
68 | 68 | _ => format!(" {}", where_clause.syntax()), | |
69 | let to_insert = match anchor.prev_sibling_or_token() { | 69 | }; |
70 | Some(ref elem) if elem.kind() == WHITESPACE => { | 70 | edit.insert(anchor.text_range().start(), to_insert); |
71 | format!("{} ", where_clause.syntax()) | 71 | edit.target(type_param_list.syntax().text_range()); |
72 | } | 72 | }) |
73 | _ => format!(" {}", where_clause.syntax()), | ||
74 | }; | ||
75 | edit.insert(anchor.text_range().start(), to_insert); | ||
76 | edit.target(type_param_list.syntax().text_range()); | ||
77 | }, | ||
78 | ); | ||
79 | |||
80 | ctx.build() | ||
81 | } | 73 | } |
82 | 74 | ||
83 | fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { | 75 | fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { |
diff --git a/crates/ra_assists/src/assists/move_guard.rs b/crates/ra_assists/src/assists/move_guard.rs index e820a73c8..b49ec6172 100644 --- a/crates/ra_assists/src/assists/move_guard.rs +++ b/crates/ra_assists/src/assists/move_guard.rs | |||
@@ -32,7 +32,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
32 | // } | 32 | // } |
33 | // } | 33 | // } |
34 | // ``` | 34 | // ``` |
35 | pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 35 | pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
36 | let match_arm = ctx.find_node_at_offset::<MatchArm>()?; | 36 | let match_arm = ctx.find_node_at_offset::<MatchArm>()?; |
37 | let guard = match_arm.guard()?; | 37 | let guard = match_arm.guard()?; |
38 | let space_before_guard = guard.syntax().prev_sibling_or_token(); | 38 | let space_before_guard = guard.syntax().prev_sibling_or_token(); |
@@ -41,7 +41,7 @@ pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Op | |||
41 | let arm_expr = match_arm.expr()?; | 41 | let arm_expr = match_arm.expr()?; |
42 | let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); | 42 | let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); |
43 | 43 | ||
44 | ctx.add_action(AssistId("move_guard_to_arm_body"), "move guard to arm body", |edit| { | 44 | ctx.add_assist(AssistId("move_guard_to_arm_body"), "move guard to arm body", |edit| { |
45 | edit.target(guard.syntax().text_range()); | 45 | edit.target(guard.syntax().text_range()); |
46 | let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { | 46 | let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { |
47 | Some(tok) => { | 47 | Some(tok) => { |
@@ -61,8 +61,7 @@ pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Op | |||
61 | edit.set_cursor( | 61 | edit.set_cursor( |
62 | arm_expr.syntax().text_range().start() + TextUnit::from(3) - offseting_amount, | 62 | arm_expr.syntax().text_range().start() + TextUnit::from(3) - offseting_amount, |
63 | ); | 63 | ); |
64 | }); | 64 | }) |
65 | ctx.build() | ||
66 | } | 65 | } |
67 | 66 | ||
68 | // Assist: move_arm_cond_to_match_guard | 67 | // Assist: move_arm_cond_to_match_guard |
@@ -90,7 +89,7 @@ pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Op | |||
90 | // } | 89 | // } |
91 | // } | 90 | // } |
92 | // ``` | 91 | // ``` |
93 | pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 92 | pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
94 | let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?; | 93 | let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?; |
95 | let last_match_pat = match_arm.pats().last()?; | 94 | let last_match_pat = match_arm.pats().last()?; |
96 | 95 | ||
@@ -110,7 +109,7 @@ pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx<impl HirDatabase>) | |||
110 | 109 | ||
111 | let buf = format!(" if {}", cond.syntax().text()); | 110 | let buf = format!(" if {}", cond.syntax().text()); |
112 | 111 | ||
113 | ctx.add_action( | 112 | ctx.add_assist( |
114 | AssistId("move_arm_cond_to_match_guard"), | 113 | AssistId("move_arm_cond_to_match_guard"), |
115 | "move condition to match guard", | 114 | "move condition to match guard", |
116 | |edit| { | 115 | |edit| { |
@@ -127,8 +126,7 @@ pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx<impl HirDatabase>) | |||
127 | edit.insert(last_match_pat.syntax().text_range().end(), buf); | 126 | edit.insert(last_match_pat.syntax().text_range().end(), buf); |
128 | edit.set_cursor(last_match_pat.syntax().text_range().end() + TextUnit::from(1)); | 127 | edit.set_cursor(last_match_pat.syntax().text_range().end() + TextUnit::from(1)); |
129 | }, | 128 | }, |
130 | ); | 129 | ) |
131 | ctx.build() | ||
132 | } | 130 | } |
133 | 131 | ||
134 | #[cfg(test)] | 132 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs index cb3a1e6e9..6f4b66c31 100644 --- a/crates/ra_assists/src/assists/raw_string.rs +++ b/crates/ra_assists/src/assists/raw_string.rs | |||
@@ -22,7 +22,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
22 | // r#"Hello, World!"#; | 22 | // r#"Hello, World!"#; |
23 | // } | 23 | // } |
24 | // ``` | 24 | // ``` |
25 | pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 25 | pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
26 | let token = ctx.find_token_at_offset(STRING)?; | 26 | let token = ctx.find_token_at_offset(STRING)?; |
27 | let text = token.text().as_str(); | 27 | let text = token.text().as_str(); |
28 | let usual_string_range = find_usual_string_range(text)?; | 28 | let usual_string_range = find_usual_string_range(text)?; |
@@ -41,7 +41,7 @@ pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As | |||
41 | if error.is_err() { | 41 | if error.is_err() { |
42 | return None; | 42 | return None; |
43 | } | 43 | } |
44 | ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { | 44 | ctx.add_assist(AssistId("make_raw_string"), "make raw string", |edit| { |
45 | edit.target(token.text_range()); | 45 | edit.target(token.text_range()); |
46 | let max_hash_streak = count_hashes(&unescaped); | 46 | let max_hash_streak = count_hashes(&unescaped); |
47 | let mut hashes = String::with_capacity(max_hash_streak + 1); | 47 | let mut hashes = String::with_capacity(max_hash_streak + 1); |
@@ -49,8 +49,7 @@ pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As | |||
49 | hashes.push('#'); | 49 | hashes.push('#'); |
50 | } | 50 | } |
51 | edit.replace(token.text_range(), format!("r{}\"{}\"{}", hashes, unescaped, hashes)); | 51 | edit.replace(token.text_range(), format!("r{}\"{}\"{}", hashes, unescaped, hashes)); |
52 | }); | 52 | }) |
53 | ctx.build() | ||
54 | } | 53 | } |
55 | 54 | ||
56 | // Assist: make_usual_string | 55 | // Assist: make_usual_string |
@@ -68,11 +67,11 @@ pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As | |||
68 | // "Hello, \"World!\""; | 67 | // "Hello, \"World!\""; |
69 | // } | 68 | // } |
70 | // ``` | 69 | // ``` |
71 | pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 70 | pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
72 | let token = ctx.find_token_at_offset(RAW_STRING)?; | 71 | let token = ctx.find_token_at_offset(RAW_STRING)?; |
73 | let text = token.text().as_str(); | 72 | let text = token.text().as_str(); |
74 | let usual_string_range = find_usual_string_range(text)?; | 73 | let usual_string_range = find_usual_string_range(text)?; |
75 | ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| { | 74 | ctx.add_assist(AssistId("make_usual_string"), "make usual string", |edit| { |
76 | edit.target(token.text_range()); | 75 | edit.target(token.text_range()); |
77 | // parse inside string to escape `"` | 76 | // parse inside string to escape `"` |
78 | let start_of_inside = usual_string_range.start().to_usize() + 1; | 77 | let start_of_inside = usual_string_range.start().to_usize() + 1; |
@@ -80,8 +79,7 @@ pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option< | |||
80 | let inside_str = &text[start_of_inside..end_of_inside]; | 79 | let inside_str = &text[start_of_inside..end_of_inside]; |
81 | let escaped = inside_str.escape_default().to_string(); | 80 | let escaped = inside_str.escape_default().to_string(); |
82 | edit.replace(token.text_range(), format!("\"{}\"", escaped)); | 81 | edit.replace(token.text_range(), format!("\"{}\"", escaped)); |
83 | }); | 82 | }) |
84 | ctx.build() | ||
85 | } | 83 | } |
86 | 84 | ||
87 | // Assist: add_hash | 85 | // Assist: add_hash |
@@ -99,14 +97,13 @@ pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option< | |||
99 | // r##"Hello, World!"##; | 97 | // r##"Hello, World!"##; |
100 | // } | 98 | // } |
101 | // ``` | 99 | // ``` |
102 | pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 100 | pub(crate) fn add_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
103 | let token = ctx.find_token_at_offset(RAW_STRING)?; | 101 | let token = ctx.find_token_at_offset(RAW_STRING)?; |
104 | ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| { | 102 | ctx.add_assist(AssistId("add_hash"), "add hash to raw string", |edit| { |
105 | edit.target(token.text_range()); | 103 | edit.target(token.text_range()); |
106 | edit.insert(token.text_range().start() + TextUnit::of_char('r'), "#"); | 104 | edit.insert(token.text_range().start() + TextUnit::of_char('r'), "#"); |
107 | edit.insert(token.text_range().end(), "#"); | 105 | edit.insert(token.text_range().end(), "#"); |
108 | }); | 106 | }) |
109 | ctx.build() | ||
110 | } | 107 | } |
111 | 108 | ||
112 | // Assist: remove_hash | 109 | // Assist: remove_hash |
@@ -124,14 +121,14 @@ pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
124 | // r"Hello, World!"; | 121 | // r"Hello, World!"; |
125 | // } | 122 | // } |
126 | // ``` | 123 | // ``` |
127 | pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 124 | pub(crate) fn remove_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
128 | let token = ctx.find_token_at_offset(RAW_STRING)?; | 125 | let token = ctx.find_token_at_offset(RAW_STRING)?; |
129 | let text = token.text().as_str(); | 126 | let text = token.text().as_str(); |
130 | if text.starts_with("r\"") { | 127 | if text.starts_with("r\"") { |
131 | // no hash to remove | 128 | // no hash to remove |
132 | return None; | 129 | return None; |
133 | } | 130 | } |
134 | ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| { | 131 | ctx.add_assist(AssistId("remove_hash"), "remove hash from raw string", |edit| { |
135 | edit.target(token.text_range()); | 132 | edit.target(token.text_range()); |
136 | let result = &text[2..text.len() - 1]; | 133 | let result = &text[2..text.len() - 1]; |
137 | let result = if result.starts_with("\"") { | 134 | let result = if result.starts_with("\"") { |
@@ -142,8 +139,7 @@ pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist | |||
142 | result.to_owned() | 139 | result.to_owned() |
143 | }; | 140 | }; |
144 | edit.replace(token.text_range(), format!("r{}", result)); | 141 | edit.replace(token.text_range(), format!("r{}", result)); |
145 | }); | 142 | }) |
146 | ctx.build() | ||
147 | } | 143 | } |
148 | 144 | ||
149 | fn count_hashes(s: &str) -> usize { | 145 | fn count_hashes(s: &str) -> usize { |
diff --git a/crates/ra_assists/src/assists/remove_dbg.rs b/crates/ra_assists/src/assists/remove_dbg.rs index 44b8de814..aedf8747f 100644 --- a/crates/ra_assists/src/assists/remove_dbg.rs +++ b/crates/ra_assists/src/assists/remove_dbg.rs | |||
@@ -21,7 +21,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
21 | // 92; | 21 | // 92; |
22 | // } | 22 | // } |
23 | // ``` | 23 | // ``` |
24 | pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 24 | pub(crate) fn remove_dbg(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
25 | let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; | 25 | let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; |
26 | 26 | ||
27 | if !is_valid_macrocall(¯o_call, "dbg")? { | 27 | if !is_valid_macrocall(¯o_call, "dbg")? { |
@@ -58,13 +58,11 @@ pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> | |||
58 | text.slice(without_parens).to_string() | 58 | text.slice(without_parens).to_string() |
59 | }; | 59 | }; |
60 | 60 | ||
61 | ctx.add_action(AssistId("remove_dbg"), "remove dbg!()", |edit| { | 61 | ctx.add_assist(AssistId("remove_dbg"), "remove dbg!()", |edit| { |
62 | edit.target(macro_call.syntax().text_range()); | 62 | edit.target(macro_call.syntax().text_range()); |
63 | edit.replace(macro_range, macro_content); | 63 | edit.replace(macro_range, macro_content); |
64 | edit.set_cursor(cursor_pos); | 64 | edit.set_cursor(cursor_pos); |
65 | }); | 65 | }) |
66 | |||
67 | ctx.build() | ||
68 | } | 66 | } |
69 | 67 | ||
70 | /// Verifies that the given macro_call actually matches the given name | 68 | /// Verifies that the given macro_call actually matches the given name |
diff --git a/crates/ra_assists/src/assists/replace_if_let_with_match.rs b/crates/ra_assists/src/assists/replace_if_let_with_match.rs index 58ef2ff20..dff84d865 100644 --- a/crates/ra_assists/src/assists/replace_if_let_with_match.rs +++ b/crates/ra_assists/src/assists/replace_if_let_with_match.rs | |||
@@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
31 | // } | 31 | // } |
32 | // } | 32 | // } |
33 | // ``` | 33 | // ``` |
34 | pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 34 | pub(crate) fn replace_if_let_with_match(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
35 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; | 35 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; |
36 | let cond = if_expr.condition()?; | 36 | let cond = if_expr.condition()?; |
37 | let pat = cond.pat()?; | 37 | let pat = cond.pat()?; |
@@ -42,14 +42,12 @@ pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx<impl HirDatabase>) -> | |||
42 | ast::ElseBranch::IfExpr(_) => return None, | 42 | ast::ElseBranch::IfExpr(_) => return None, |
43 | }; | 43 | }; |
44 | 44 | ||
45 | ctx.add_action(AssistId("replace_if_let_with_match"), "replace with match", |edit| { | 45 | ctx.add_assist(AssistId("replace_if_let_with_match"), "replace with match", |edit| { |
46 | let match_expr = build_match_expr(expr, pat, then_block, else_block); | 46 | let match_expr = build_match_expr(expr, pat, then_block, else_block); |
47 | edit.target(if_expr.syntax().text_range()); | 47 | edit.target(if_expr.syntax().text_range()); |
48 | edit.replace_node_and_indent(if_expr.syntax(), match_expr); | 48 | edit.replace_node_and_indent(if_expr.syntax(), match_expr); |
49 | edit.set_cursor(if_expr.syntax().text_range().start()) | 49 | edit.set_cursor(if_expr.syntax().text_range().start()) |
50 | }); | 50 | }) |
51 | |||
52 | ctx.build() | ||
53 | } | 51 | } |
54 | 52 | ||
55 | fn build_match_expr( | 53 | fn build_match_expr( |
diff --git a/crates/ra_assists/src/assists/split_import.rs b/crates/ra_assists/src/assists/split_import.rs index 8d8a28987..5f8d6b0be 100644 --- a/crates/ra_assists/src/assists/split_import.rs +++ b/crates/ra_assists/src/assists/split_import.rs | |||
@@ -16,7 +16,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
16 | // ``` | 16 | // ``` |
17 | // use std::{collections::HashMap}; | 17 | // use std::{collections::HashMap}; |
18 | // ``` | 18 | // ``` |
19 | pub(crate) fn split_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 19 | pub(crate) fn split_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
20 | let colon_colon = ctx.find_token_at_offset(T![::])?; | 20 | let colon_colon = ctx.find_token_at_offset(T![::])?; |
21 | let path = ast::Path::cast(colon_colon.parent())?; | 21 | let path = ast::Path::cast(colon_colon.parent())?; |
22 | let top_path = successors(Some(path), |it| it.parent_path()).last()?; | 22 | let top_path = successors(Some(path), |it| it.parent_path()).last()?; |
@@ -32,14 +32,12 @@ pub(crate) fn split_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assis | |||
32 | None => top_path.syntax().text_range().end(), | 32 | None => top_path.syntax().text_range().end(), |
33 | }; | 33 | }; |
34 | 34 | ||
35 | ctx.add_action(AssistId("split_import"), "split import", |edit| { | 35 | ctx.add_assist(AssistId("split_import"), "split import", |edit| { |
36 | edit.target(colon_colon.text_range()); | 36 | edit.target(colon_colon.text_range()); |
37 | edit.insert(l_curly, "{"); | 37 | edit.insert(l_curly, "{"); |
38 | edit.insert(r_curly, "}"); | 38 | edit.insert(r_curly, "}"); |
39 | edit.set_cursor(l_curly + TextUnit::of_str("{")); | 39 | edit.set_cursor(l_curly + TextUnit::of_str("{")); |
40 | }); | 40 | }) |
41 | |||
42 | ctx.build() | ||
43 | } | 41 | } |
44 | 42 | ||
45 | #[cfg(test)] | 43 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs index 0ccf9d730..6e1e3de84 100644 --- a/crates/ra_assists/src/doc_tests.rs +++ b/crates/ra_assists/src/doc_tests.rs | |||
@@ -17,7 +17,17 @@ fn check(assist_id: &str, before: &str, after: &str) { | |||
17 | let (_assist_id, action) = crate::assists(&db, frange) | 17 | let (_assist_id, action) = crate::assists(&db, frange) |
18 | .into_iter() | 18 | .into_iter() |
19 | .find(|(id, _)| id.id.0 == assist_id) | 19 | .find(|(id, _)| id.id.0 == assist_id) |
20 | .unwrap_or_else(|| panic!("Assist {:?} is not applicable", assist_id)); | 20 | .unwrap_or_else(|| { |
21 | panic!( | ||
22 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", | ||
23 | assist_id, | ||
24 | crate::assists(&db, frange) | ||
25 | .into_iter() | ||
26 | .map(|(id, _)| id.id.0) | ||
27 | .collect::<Vec<_>>() | ||
28 | .join(", ") | ||
29 | ) | ||
30 | }); | ||
21 | 31 | ||
22 | let actual = action.edit.apply(&before); | 32 | let actual = action.edit.apply(&before); |
23 | assert_eq_text!(after, &actual); | 33 | assert_eq_text!(after, &actual); |
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs index b8d335911..1bee76f59 100644 --- a/crates/ra_assists/src/doc_tests/generated.rs +++ b/crates/ra_assists/src/doc_tests/generated.rs | |||
@@ -142,6 +142,21 @@ impl T for () { | |||
142 | } | 142 | } |
143 | 143 | ||
144 | #[test] | 144 | #[test] |
145 | fn doctest_add_import() { | ||
146 | check( | ||
147 | "add_import", | ||
148 | r#####" | ||
149 | fn process(map: std::collections::<|>HashMap<String, String>) {} | ||
150 | "#####, | ||
151 | r#####" | ||
152 | use std::collections::HashMap; | ||
153 | |||
154 | fn process(map: HashMap<String, String>) {} | ||
155 | "#####, | ||
156 | ) | ||
157 | } | ||
158 | |||
159 | #[test] | ||
145 | fn doctest_apply_demorgan() { | 160 | fn doctest_apply_demorgan() { |
146 | check( | 161 | check( |
147 | "apply_demorgan", | 162 | "apply_demorgan", |
@@ -256,6 +271,19 @@ fn main() { | |||
256 | } | 271 | } |
257 | 272 | ||
258 | #[test] | 273 | #[test] |
274 | fn doctest_flip_trait_bound() { | ||
275 | check( | ||
276 | "flip_trait_bound", | ||
277 | r#####" | ||
278 | fn foo<T: Clone +<|> Copy>() { } | ||
279 | "#####, | ||
280 | r#####" | ||
281 | fn foo<T: Copy + Clone>() { } | ||
282 | "#####, | ||
283 | ) | ||
284 | } | ||
285 | |||
286 | #[test] | ||
259 | fn doctest_inline_local_variable() { | 287 | fn doctest_inline_local_variable() { |
260 | check( | 288 | check( |
261 | "inline_local_variable", | 289 | "inline_local_variable", |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index de576324f..38599d4f1 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -11,13 +11,12 @@ mod marks; | |||
11 | mod doc_tests; | 11 | mod doc_tests; |
12 | 12 | ||
13 | use hir::db::HirDatabase; | 13 | use hir::db::HirDatabase; |
14 | use itertools::Itertools; | ||
15 | use ra_db::FileRange; | 14 | use ra_db::FileRange; |
16 | use ra_syntax::{TextRange, TextUnit}; | 15 | use ra_syntax::{TextRange, TextUnit}; |
17 | use ra_text_edit::TextEdit; | 16 | use ra_text_edit::TextEdit; |
18 | 17 | ||
19 | pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; | 18 | pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; |
20 | pub use crate::assists::auto_import::auto_import_text_edit; | 19 | pub use crate::assists::add_import::auto_import_text_edit; |
21 | 20 | ||
22 | /// Unique identifier of the assist, should not be shown to the user | 21 | /// Unique identifier of the assist, should not be shown to the user |
23 | /// directly. | 22 | /// directly. |
@@ -51,10 +50,10 @@ where | |||
51 | .iter() | 50 | .iter() |
52 | .filter_map(|f| f(ctx.clone())) | 51 | .filter_map(|f| f(ctx.clone())) |
53 | .map(|a| match a { | 52 | .map(|a| match a { |
54 | Assist::Unresolved(labels) => labels, | 53 | Assist::Unresolved { label } => label, |
55 | Assist::Resolved(..) => unreachable!(), | 54 | Assist::Resolved { .. } => unreachable!(), |
56 | }) | 55 | }) |
57 | .concat() | 56 | .collect() |
58 | }) | 57 | }) |
59 | } | 58 | } |
60 | 59 | ||
@@ -73,10 +72,10 @@ where | |||
73 | .iter() | 72 | .iter() |
74 | .filter_map(|f| f(ctx.clone())) | 73 | .filter_map(|f| f(ctx.clone())) |
75 | .map(|a| match a { | 74 | .map(|a| match a { |
76 | Assist::Resolved(labels_actions) => labels_actions, | 75 | Assist::Resolved { label, action } => (label, action), |
77 | Assist::Unresolved(..) => unreachable!(), | 76 | Assist::Unresolved { .. } => unreachable!(), |
78 | }) | 77 | }) |
79 | .concat(); | 78 | .collect::<Vec<_>>(); |
80 | a.sort_by(|a, b| match (a.1.target, b.1.target) { | 79 | a.sort_by(|a, b| match (a.1.target, b.1.target) { |
81 | (Some(a), Some(b)) => a.len().cmp(&b.len()), | 80 | (Some(a), Some(b)) => a.len().cmp(&b.len()), |
82 | (Some(_), None) => Ordering::Less, | 81 | (Some(_), None) => Ordering::Less, |
@@ -97,6 +96,7 @@ mod assists { | |||
97 | mod apply_demorgan; | 96 | mod apply_demorgan; |
98 | mod flip_comma; | 97 | mod flip_comma; |
99 | mod flip_binexpr; | 98 | mod flip_binexpr; |
99 | mod flip_trait_bound; | ||
100 | mod change_visibility; | 100 | mod change_visibility; |
101 | mod fill_match_arms; | 101 | mod fill_match_arms; |
102 | mod merge_match_arms; | 102 | mod merge_match_arms; |
@@ -106,7 +106,7 @@ mod assists { | |||
106 | mod replace_if_let_with_match; | 106 | mod replace_if_let_with_match; |
107 | mod split_import; | 107 | mod split_import; |
108 | mod remove_dbg; | 108 | mod remove_dbg; |
109 | pub(crate) mod auto_import; | 109 | pub(crate) mod add_import; |
110 | mod add_missing_impl_members; | 110 | mod add_missing_impl_members; |
111 | mod move_guard; | 111 | mod move_guard; |
112 | mod move_bounds; | 112 | mod move_bounds; |
@@ -123,11 +123,12 @@ mod assists { | |||
123 | merge_match_arms::merge_match_arms, | 123 | merge_match_arms::merge_match_arms, |
124 | flip_comma::flip_comma, | 124 | flip_comma::flip_comma, |
125 | flip_binexpr::flip_binexpr, | 125 | flip_binexpr::flip_binexpr, |
126 | flip_trait_bound::flip_trait_bound, | ||
126 | introduce_variable::introduce_variable, | 127 | introduce_variable::introduce_variable, |
127 | replace_if_let_with_match::replace_if_let_with_match, | 128 | replace_if_let_with_match::replace_if_let_with_match, |
128 | split_import::split_import, | 129 | split_import::split_import, |
129 | remove_dbg::remove_dbg, | 130 | remove_dbg::remove_dbg, |
130 | auto_import::auto_import, | 131 | add_import::add_import, |
131 | add_missing_impl_members::add_missing_impl_members, | 132 | add_missing_impl_members::add_missing_impl_members, |
132 | add_missing_impl_members::add_missing_default_members, | 133 | add_missing_impl_members::add_missing_default_members, |
133 | inline_local_variable::inline_local_varialbe, | 134 | inline_local_variable::inline_local_varialbe, |
@@ -157,51 +158,17 @@ mod helpers { | |||
157 | before: &str, | 158 | before: &str, |
158 | after: &str, | 159 | after: &str, |
159 | ) { | 160 | ) { |
160 | check_assist_nth_action(assist, before, after, 0) | ||
161 | } | ||
162 | |||
163 | pub(crate) fn check_assist_range( | ||
164 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | ||
165 | before: &str, | ||
166 | after: &str, | ||
167 | ) { | ||
168 | check_assist_range_nth_action(assist, before, after, 0) | ||
169 | } | ||
170 | |||
171 | pub(crate) fn check_assist_target( | ||
172 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | ||
173 | before: &str, | ||
174 | target: &str, | ||
175 | ) { | ||
176 | check_assist_target_nth_action(assist, before, target, 0) | ||
177 | } | ||
178 | |||
179 | pub(crate) fn check_assist_range_target( | ||
180 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | ||
181 | before: &str, | ||
182 | target: &str, | ||
183 | ) { | ||
184 | check_assist_range_target_nth_action(assist, before, target, 0) | ||
185 | } | ||
186 | |||
187 | pub(crate) fn check_assist_nth_action( | ||
188 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | ||
189 | before: &str, | ||
190 | after: &str, | ||
191 | index: usize, | ||
192 | ) { | ||
193 | let (before_cursor_pos, before) = extract_offset(before); | 161 | let (before_cursor_pos, before) = extract_offset(before); |
194 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | 162 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); |
195 | let frange = | 163 | let frange = |
196 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; | 164 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; |
197 | let assist = | 165 | let assist = |
198 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 166 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
199 | let labels_actions = match assist { | 167 | let action = match assist { |
200 | Assist::Unresolved(_) => unreachable!(), | 168 | Assist::Unresolved { .. } => unreachable!(), |
201 | Assist::Resolved(labels_actions) => labels_actions, | 169 | Assist::Resolved { action, .. } => action, |
202 | }; | 170 | }; |
203 | 171 | ||
204 | let (_, action) = labels_actions.get(index).expect("expect assist action at index"); | ||
205 | let actual = action.edit.apply(&before); | 172 | let actual = action.edit.apply(&before); |
206 | let actual_cursor_pos = match action.cursor_position { | 173 | let actual_cursor_pos = match action.cursor_position { |
207 | None => action | 174 | None => action |
@@ -214,23 +181,21 @@ mod helpers { | |||
214 | assert_eq_text!(after, &actual); | 181 | assert_eq_text!(after, &actual); |
215 | } | 182 | } |
216 | 183 | ||
217 | pub(crate) fn check_assist_range_nth_action( | 184 | pub(crate) fn check_assist_range( |
218 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | 185 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, |
219 | before: &str, | 186 | before: &str, |
220 | after: &str, | 187 | after: &str, |
221 | index: usize, | ||
222 | ) { | 188 | ) { |
223 | let (range, before) = extract_range(before); | 189 | let (range, before) = extract_range(before); |
224 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | 190 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); |
225 | let frange = FileRange { file_id, range }; | 191 | let frange = FileRange { file_id, range }; |
226 | let assist = | 192 | let assist = |
227 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 193 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
228 | let labels_actions = match assist { | 194 | let action = match assist { |
229 | Assist::Unresolved(_) => unreachable!(), | 195 | Assist::Unresolved { .. } => unreachable!(), |
230 | Assist::Resolved(labels_actions) => labels_actions, | 196 | Assist::Resolved { action, .. } => action, |
231 | }; | 197 | }; |
232 | 198 | ||
233 | let (_, action) = labels_actions.get(index).expect("expect assist action at index"); | ||
234 | let mut actual = action.edit.apply(&before); | 199 | let mut actual = action.edit.apply(&before); |
235 | if let Some(pos) = action.cursor_position { | 200 | if let Some(pos) = action.cursor_position { |
236 | actual = add_cursor(&actual, pos); | 201 | actual = add_cursor(&actual, pos); |
@@ -238,11 +203,10 @@ mod helpers { | |||
238 | assert_eq_text!(after, &actual); | 203 | assert_eq_text!(after, &actual); |
239 | } | 204 | } |
240 | 205 | ||
241 | pub(crate) fn check_assist_target_nth_action( | 206 | pub(crate) fn check_assist_target( |
242 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | 207 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, |
243 | before: &str, | 208 | before: &str, |
244 | target: &str, | 209 | target: &str, |
245 | index: usize, | ||
246 | ) { | 210 | ) { |
247 | let (before_cursor_pos, before) = extract_offset(before); | 211 | let (before_cursor_pos, before) = extract_offset(before); |
248 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | 212 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); |
@@ -250,33 +214,30 @@ mod helpers { | |||
250 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; | 214 | FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; |
251 | let assist = | 215 | let assist = |
252 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 216 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
253 | let labels_actions = match assist { | 217 | let action = match assist { |
254 | Assist::Unresolved(_) => unreachable!(), | 218 | Assist::Unresolved { .. } => unreachable!(), |
255 | Assist::Resolved(labels_actions) => labels_actions, | 219 | Assist::Resolved { action, .. } => action, |
256 | }; | 220 | }; |
257 | 221 | ||
258 | let (_, action) = labels_actions.get(index).expect("expect assist action at index"); | ||
259 | let range = action.target.expect("expected target on action"); | 222 | let range = action.target.expect("expected target on action"); |
260 | assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); | 223 | assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); |
261 | } | 224 | } |
262 | 225 | ||
263 | pub(crate) fn check_assist_range_target_nth_action( | 226 | pub(crate) fn check_assist_range_target( |
264 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, | 227 | assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, |
265 | before: &str, | 228 | before: &str, |
266 | target: &str, | 229 | target: &str, |
267 | index: usize, | ||
268 | ) { | 230 | ) { |
269 | let (range, before) = extract_range(before); | 231 | let (range, before) = extract_range(before); |
270 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); | 232 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); |
271 | let frange = FileRange { file_id, range }; | 233 | let frange = FileRange { file_id, range }; |
272 | let assist = | 234 | let assist = |
273 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); | 235 | AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); |
274 | let labels_actions = match assist { | 236 | let action = match assist { |
275 | Assist::Unresolved(_) => unreachable!(), | 237 | Assist::Unresolved { .. } => unreachable!(), |
276 | Assist::Resolved(labels_actions) => labels_actions, | 238 | Assist::Resolved { action, .. } => action, |
277 | }; | 239 | }; |
278 | 240 | ||
279 | let (_, action) = labels_actions.get(index).expect("expect assist action at index"); | ||
280 | let range = action.target.expect("expected target on action"); | 241 | let range = action.target.expect("expected target on action"); |
281 | assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); | 242 | assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); |
282 | } | 243 | } |
diff --git a/crates/ra_ide_api/src/extend_selection.rs b/crates/ra_ide_api/src/extend_selection.rs index 602757e92..4b7bfc0b1 100644 --- a/crates/ra_ide_api/src/extend_selection.rs +++ b/crates/ra_ide_api/src/extend_selection.rs | |||
@@ -5,7 +5,7 @@ use ra_syntax::{ | |||
5 | algo::find_covering_element, | 5 | algo::find_covering_element, |
6 | ast::{self, AstNode, AstToken}, | 6 | ast::{self, AstNode, AstToken}, |
7 | Direction, NodeOrToken, | 7 | Direction, NodeOrToken, |
8 | SyntaxKind::*, | 8 | SyntaxKind::{self, *}, |
9 | SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, | 9 | SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, |
10 | }; | 10 | }; |
11 | 11 | ||
@@ -29,10 +29,12 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange | |||
29 | USE_TREE_LIST, | 29 | USE_TREE_LIST, |
30 | TYPE_PARAM_LIST, | 30 | TYPE_PARAM_LIST, |
31 | TYPE_ARG_LIST, | 31 | TYPE_ARG_LIST, |
32 | TYPE_BOUND_LIST, | ||
32 | PARAM_LIST, | 33 | PARAM_LIST, |
33 | ARG_LIST, | 34 | ARG_LIST, |
34 | ARRAY_EXPR, | 35 | ARRAY_EXPR, |
35 | TUPLE_EXPR, | 36 | TUPLE_EXPR, |
37 | WHERE_CLAUSE, | ||
36 | ]; | 38 | ]; |
37 | 39 | ||
38 | if range.is_empty() { | 40 | if range.is_empty() { |
@@ -146,13 +148,17 @@ fn pick_best<'a>(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken { | |||
146 | } | 148 | } |
147 | } | 149 | } |
148 | 150 | ||
149 | /// Extend list item selection to include nearby comma and whitespace. | 151 | /// Extend list item selection to include nearby delimiter and whitespace. |
150 | fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> { | 152 | fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> { |
151 | fn is_single_line_ws(node: &SyntaxToken) -> bool { | 153 | fn is_single_line_ws(node: &SyntaxToken) -> bool { |
152 | node.kind() == WHITESPACE && !node.text().contains('\n') | 154 | node.kind() == WHITESPACE && !node.text().contains('\n') |
153 | } | 155 | } |
154 | 156 | ||
155 | fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option<SyntaxToken> { | 157 | fn nearby_delimiter( |
158 | delimiter_kind: SyntaxKind, | ||
159 | node: &SyntaxNode, | ||
160 | dir: Direction, | ||
161 | ) -> Option<SyntaxToken> { | ||
156 | node.siblings_with_tokens(dir) | 162 | node.siblings_with_tokens(dir) |
157 | .skip(1) | 163 | .skip(1) |
158 | .skip_while(|node| match node { | 164 | .skip_while(|node| match node { |
@@ -161,19 +167,26 @@ fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> { | |||
161 | }) | 167 | }) |
162 | .next() | 168 | .next() |
163 | .and_then(|it| it.into_token()) | 169 | .and_then(|it| it.into_token()) |
164 | .filter(|node| node.kind() == T![,]) | 170 | .filter(|node| node.kind() == delimiter_kind) |
165 | } | 171 | } |
166 | 172 | ||
167 | if let Some(comma_node) = nearby_comma(node, Direction::Prev) { | 173 | let delimiter = match node.kind() { |
168 | return Some(TextRange::from_to(comma_node.text_range().start(), node.text_range().end())); | 174 | TYPE_BOUND => T![+], |
175 | _ => T![,], | ||
176 | }; | ||
177 | if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) { | ||
178 | return Some(TextRange::from_to( | ||
179 | delimiter_node.text_range().start(), | ||
180 | node.text_range().end(), | ||
181 | )); | ||
169 | } | 182 | } |
170 | if let Some(comma_node) = nearby_comma(node, Direction::Next) { | 183 | if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) { |
171 | // Include any following whitespace when comma if after list item. | 184 | // Include any following whitespace when delimiter is after list item. |
172 | let final_node = comma_node | 185 | let final_node = delimiter_node |
173 | .next_sibling_or_token() | 186 | .next_sibling_or_token() |
174 | .and_then(|it| it.into_token()) | 187 | .and_then(|it| it.into_token()) |
175 | .filter(|node| is_single_line_ws(node)) | 188 | .filter(|node| is_single_line_ws(node)) |
176 | .unwrap_or(comma_node); | 189 | .unwrap_or(delimiter_node); |
177 | 190 | ||
178 | return Some(TextRange::from_to(node.text_range().start(), final_node.text_range().end())); | 191 | return Some(TextRange::from_to(node.text_range().start(), final_node.text_range().end())); |
179 | } | 192 | } |
@@ -387,4 +400,53 @@ fn bar(){} | |||
387 | &["foo", "\" fn foo() {\""], | 400 | &["foo", "\" fn foo() {\""], |
388 | ); | 401 | ); |
389 | } | 402 | } |
403 | |||
404 | #[test] | ||
405 | fn test_extend_trait_bounds_list_in_where_clause() { | ||
406 | do_check( | ||
407 | r#" | ||
408 | fn foo<R>() | ||
409 | where | ||
410 | R: req::Request + 'static, | ||
411 | R::Params: DeserializeOwned<|> + panic::UnwindSafe + 'static, | ||
412 | R::Result: Serialize + 'static, | ||
413 | "#, | ||
414 | &[ | ||
415 | "DeserializeOwned", | ||
416 | "DeserializeOwned + ", | ||
417 | "DeserializeOwned + panic::UnwindSafe + 'static", | ||
418 | "R::Params: DeserializeOwned + panic::UnwindSafe + 'static", | ||
419 | "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,", | ||
420 | ], | ||
421 | ); | ||
422 | do_check(r#"fn foo<T>() where T: <|>Copy"#, &["Copy"]); | ||
423 | do_check(r#"fn foo<T>() where T: <|>Copy + Display"#, &["Copy", "Copy + "]); | ||
424 | do_check(r#"fn foo<T>() where T: <|>Copy +Display"#, &["Copy", "Copy +"]); | ||
425 | do_check(r#"fn foo<T>() where T: <|>Copy+Display"#, &["Copy", "Copy+"]); | ||
426 | do_check(r#"fn foo<T>() where T: Copy + <|>Display"#, &["Display", "+ Display"]); | ||
427 | do_check(r#"fn foo<T>() where T: Copy + <|>Display + Sync"#, &["Display", "+ Display"]); | ||
428 | do_check(r#"fn foo<T>() where T: Copy +<|>Display"#, &["Display", "+Display"]); | ||
429 | } | ||
430 | |||
431 | #[test] | ||
432 | fn test_extend_trait_bounds_list_inline() { | ||
433 | do_check(r#"fn foo<T: <|>Copy>() {}"#, &["Copy"]); | ||
434 | do_check(r#"fn foo<T: <|>Copy + Display>() {}"#, &["Copy", "Copy + "]); | ||
435 | do_check(r#"fn foo<T: <|>Copy +Display>() {}"#, &["Copy", "Copy +"]); | ||
436 | do_check(r#"fn foo<T: <|>Copy+Display>() {}"#, &["Copy", "Copy+"]); | ||
437 | do_check(r#"fn foo<T: Copy + <|>Display>() {}"#, &["Display", "+ Display"]); | ||
438 | do_check(r#"fn foo<T: Copy + <|>Display + Sync>() {}"#, &["Display", "+ Display"]); | ||
439 | do_check(r#"fn foo<T: Copy +<|>Display>() {}"#, &["Display", "+Display"]); | ||
440 | do_check( | ||
441 | r#"fn foo<T: Copy<|> + Display, U: Copy>() {}"#, | ||
442 | &[ | ||
443 | "Copy", | ||
444 | "Copy + ", | ||
445 | "Copy + Display", | ||
446 | "T: Copy + Display", | ||
447 | "T: Copy + Display, ", | ||
448 | "<T: Copy + Display, U: Copy>", | ||
449 | ], | ||
450 | ); | ||
451 | } | ||
390 | } | 452 | } |
diff --git a/docs/user/assists.md b/docs/user/assists.md index e4d08a7dc..303353e74 100644 --- a/docs/user/assists.md +++ b/docs/user/assists.md | |||
@@ -136,6 +136,20 @@ impl T for () { | |||
136 | } | 136 | } |
137 | ``` | 137 | ``` |
138 | 138 | ||
139 | ## `add_import` | ||
140 | |||
141 | Adds a use statement for a given fully-qualified path. | ||
142 | |||
143 | ```rust | ||
144 | // BEFORE | ||
145 | fn process(map: std::collections::┃HashMap<String, String>) {} | ||
146 | |||
147 | // AFTER | ||
148 | use std::collections::HashMap; | ||
149 | |||
150 | fn process(map: HashMap<String, String>) {} | ||
151 | ``` | ||
152 | |||
139 | ## `apply_demorgan` | 153 | ## `apply_demorgan` |
140 | 154 | ||
141 | Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws). | 155 | Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws). |
@@ -248,6 +262,18 @@ fn main() { | |||
248 | } | 262 | } |
249 | ``` | 263 | ``` |
250 | 264 | ||
265 | ## `flip_trait_bound` | ||
266 | |||
267 | Flips two trait bounds. | ||
268 | |||
269 | ```rust | ||
270 | // BEFORE | ||
271 | fn foo<T: Clone +┃ Copy>() { } | ||
272 | |||
273 | // AFTER | ||
274 | fn foo<T: Copy + Clone>() { } | ||
275 | ``` | ||
276 | |||
251 | ## `inline_local_variable` | 277 | ## `inline_local_variable` |
252 | 278 | ||
253 | Inlines local variable. | 279 | Inlines local variable. |
diff --git a/docs/user/features.md b/docs/user/features.md index 7ae2ca7b6..c160dd70b 100644 --- a/docs/user/features.md +++ b/docs/user/features.md | |||
@@ -99,24 +99,10 @@ Stop `cargo watch` | |||
99 | 99 | ||
100 | ### Assists (Code Actions) | 100 | ### Assists (Code Actions) |
101 | 101 | ||
102 | These are triggered in a particular context via light bulb. We use custom code on | 102 | Assists, or code actions, are small local refactorings, available in a particular context. |
103 | the VS Code side to be able to position cursor. `<|>` signifies cursor | 103 | They are usually triggered by a shortcut or by clicking a light bulb icon in the editor. |
104 | 104 | ||
105 | See [assists.md](./assists.md) | 105 | See [assists.md](./assists.md) for the list of available assists. |
106 | |||
107 | - Import path | ||
108 | |||
109 | ```rust | ||
110 | // before: | ||
111 | impl std::fmt::Debug<|> for Foo { | ||
112 | } | ||
113 | |||
114 | // after: | ||
115 | use std::fmt::Debug; | ||
116 | |||
117 | impl Debug<|> for Foo { | ||
118 | } | ||
119 | ``` | ||
120 | 106 | ||
121 | ### Magic Completions | 107 | ### Magic Completions |
122 | 108 | ||