aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/assist_ctx.rs44
-rw-r--r--crates/ra_assists/src/assists/add_derive.rs8
-rw-r--r--crates/ra_assists/src/assists/add_explicit_type.rs7
-rw-r--r--crates/ra_assists/src/assists/add_impl.rs8
-rw-r--r--crates/ra_assists/src/assists/add_import.rs (renamed from crates/ra_assists/src/assists/auto_import.rs)194
-rw-r--r--crates/ra_assists/src/assists/add_missing_impl_members.rs8
-rw-r--r--crates/ra_assists/src/assists/apply_demorgan.rs7
-rw-r--r--crates/ra_assists/src/assists/change_visibility.rs18
-rw-r--r--crates/ra_assists/src/assists/early_return.rs7
-rw-r--r--crates/ra_assists/src/assists/fill_match_arms.rs8
-rw-r--r--crates/ra_assists/src/assists/flip_binexpr.rs8
-rw-r--r--crates/ra_assists/src/assists/flip_comma.rs8
-rw-r--r--crates/ra_assists/src/assists/flip_trait_bound.rs117
-rw-r--r--crates/ra_assists/src/assists/inline_local_variable.rs8
-rw-r--r--crates/ra_assists/src/assists/introduce_variable.rs8
-rw-r--r--crates/ra_assists/src/assists/merge_match_arms.rs8
-rw-r--r--crates/ra_assists/src/assists/move_bounds.rs58
-rw-r--r--crates/ra_assists/src/assists/move_guard.rs14
-rw-r--r--crates/ra_assists/src/assists/raw_string.rs28
-rw-r--r--crates/ra_assists/src/assists/remove_dbg.rs8
-rw-r--r--crates/ra_assists/src/assists/replace_if_let_with_match.rs8
-rw-r--r--crates/ra_assists/src/assists/split_import.rs8
-rw-r--r--crates/ra_assists/src/doc_tests.rs12
-rw-r--r--crates/ra_assists/src/doc_tests/generated.rs28
-rw-r--r--crates/ra_assists/src/lib.rs91
-rw-r--r--crates/ra_ide_api/src/extend_selection.rs82
-rw-r--r--docs/user/assists.md26
-rw-r--r--docs/user/features.md20
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)]
16pub(crate) enum Assist { 16pub(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
60impl<'a, DB> Clone for AssistCtx<'a, DB> { 59impl<'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// ```
28pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 28pub(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// ```
24pub(crate) fn add_explicit_type(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 24pub(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// ```
30pub(crate) fn add_impl(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 30pub(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
3use hir::{self, db::HirDatabase}; 1use hir::{self, db::HirDatabase};
4use ra_text_edit::TextEditBuilder;
5
6use crate::{
7 assist_ctx::{Assist, AssistCtx},
8 AssistId,
9};
10use ra_syntax::{ 2use 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};
8use ra_text_edit::TextEditBuilder;
9
10use 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
18pub 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// ```
53pub(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
17fn collect_path_segments_raw( 80fn 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
508pub fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> { 571fn 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
527pub 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
549pub(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)]
597mod tests { 588mod 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 "
606std::fmt::Debug<|> 598std::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 "
620std::fmt::Debug<|> 612std::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 "
641fn main() { 633fn 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 "
662std::fmt<|>::Debug 654std::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 "
677use stdx; 669use 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 "
697impl std::fmt::Debug<|> for Foo { 689impl 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 "
731use std::fmt; 723use 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 "
750use std::fmt; 742use 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 "
769use std::fmt::Debug; 761use 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 "
788use std::fmt::{Debug, nested::{Display}}; 780use 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 "
807use std::fmt::{Debug, nested::{self, Display}}; 799use 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 "
826use std::fmt::{Debug, nested::{Display}}; 818use 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 "
845use std::fmt::Debug; 837use 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 "
864use std::fmt::nested::Debug; 856use 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 "
883use std::fmt as foo; 875use 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 "
902impl foo<|> for Foo { 894impl 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 "
913use std::fmt<|>; 905use 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 "
923mod foo { 915mod 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
93fn add_missing_impl_members_inner( 93fn 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
158fn add_body(fn_def: ast::FnDef) -> ast::FnDef { 156fn 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// ```
26pub(crate) fn apply_demorgan(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 26pub(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
32fn add_vis(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 32fn 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
69fn vis_offset(node: &SyntaxNode) -> TextUnit { 67fn 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
80fn change_vis(mut ctx: AssistCtx<impl HirDatabase>, vis: ast::Visibility) -> Option<Assist> { 78fn 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// ```
38pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 38pub(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// ```
34pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 34pub(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
75fn is_trivial(arm: &ast::MatchArm) -> bool { 73fn 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// ```
21pub(crate) fn flip_binexpr(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 21pub(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
49enum FlipAction { 47enum 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// ```
21pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 21pub(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 @@
1use hir::db::HirDatabase;
2use ra_syntax::{
3 algo::non_trivia_sibling,
4 ast::{self, AstNode},
5 Direction, T,
6};
7
8use 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// ```
21pub(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)]
44mod 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// ```
26pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 26pub(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// ```
31pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 31pub(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// ```
29pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 29pub(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// ```
25pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 25pub(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
83fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { 75fn 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// ```
35pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 35pub(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// ```
93pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 92pub(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// ```
25pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 25pub(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// ```
71pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 70pub(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// ```
102pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 100pub(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// ```
127pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 124pub(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
149fn count_hashes(s: &str) -> usize { 145fn 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// ```
24pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 24pub(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(&macro_call, "dbg")? { 27 if !is_valid_macrocall(&macro_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// ```
34pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 34pub(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
55fn build_match_expr( 53fn 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// ```
19pub(crate) fn split_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 19pub(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]
145fn doctest_add_import() {
146 check(
147 "add_import",
148 r#####"
149fn process(map: std::collections::<|>HashMap<String, String>) {}
150"#####,
151 r#####"
152use std::collections::HashMap;
153
154fn process(map: HashMap<String, String>) {}
155"#####,
156 )
157}
158
159#[test]
145fn doctest_apply_demorgan() { 160fn 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]
274fn doctest_flip_trait_bound() {
275 check(
276 "flip_trait_bound",
277 r#####"
278fn foo<T: Clone +<|> Copy>() { }
279"#####,
280 r#####"
281fn foo<T: Copy + Clone>() { }
282"#####,
283 )
284}
285
286#[test]
259fn doctest_inline_local_variable() { 287fn 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;
11mod doc_tests; 11mod doc_tests;
12 12
13use hir::db::HirDatabase; 13use hir::db::HirDatabase;
14use itertools::Itertools;
15use ra_db::FileRange; 14use ra_db::FileRange;
16use ra_syntax::{TextRange, TextUnit}; 15use ra_syntax::{TextRange, TextUnit};
17use ra_text_edit::TextEdit; 16use ra_text_edit::TextEdit;
18 17
19pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; 18pub(crate) use crate::assist_ctx::{Assist, AssistCtx};
20pub use crate::assists::auto_import::auto_import_text_edit; 19pub 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.
150fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> { 152fn 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#"
408fn 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
141Adds a use statement for a given fully-qualified path.
142
143```rust
144// BEFORE
145fn process(map: std::collections::┃HashMap<String, String>) {}
146
147// AFTER
148use std::collections::HashMap;
149
150fn process(map: HashMap<String, String>) {}
151```
152
139## `apply_demorgan` 153## `apply_demorgan`
140 154
141Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws). 155Apply [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
267Flips two trait bounds.
268
269```rust
270// BEFORE
271fn foo<T: Clone +┃ Copy>() { }
272
273// AFTER
274fn foo<T: Copy + Clone>() { }
275```
276
251## `inline_local_variable` 277## `inline_local_variable`
252 278
253Inlines local variable. 279Inlines 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
102These are triggered in a particular context via light bulb. We use custom code on 102Assists, or code actions, are small local refactorings, available in a particular context.
103the VS Code side to be able to position cursor. `<|>` signifies cursor 103They are usually triggered by a shortcut or by clicking a light bulb icon in the editor.
104 104
105See [assists.md](./assists.md) 105See [assists.md](./assists.md) for the list of available assists.
106
107- Import path
108
109```rust
110// before:
111impl std::fmt::Debug<|> for Foo {
112}
113
114// after:
115use std::fmt::Debug;
116
117impl Debug<|> for Foo {
118}
119```
120 106
121### Magic Completions 107### Magic Completions
122 108