aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src
diff options
context:
space:
mode:
authorSeivan Heidari <[email protected]>2019-10-31 08:43:20 +0000
committerSeivan Heidari <[email protected]>2019-10-31 08:43:20 +0000
commit8edda0e7b164009d6c03bb3d4be603fb38ad2e2a (patch)
tree744cf81075d394e2f9c06afb07642a2601800dda /crates/ra_assists/src
parent49562d36b97ddde34cf7585a8c2e8f232519b657 (diff)
parentd067afb064a7fa67b172abf561b7d80740cd6f18 (diff)
Merge branch 'master' into feature/themes
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r--crates/ra_assists/src/assist_ctx.rs52
-rw-r--r--crates/ra_assists/src/assists/add_derive.rs30
-rw-r--r--crates/ra_assists/src/assists/add_explicit_type.rs27
-rw-r--r--crates/ra_assists/src/assists/add_impl.rs31
-rw-r--r--crates/ra_assists/src/assists/add_import.rs (renamed from crates/ra_assists/src/assists/auto_import.rs)249
-rw-r--r--crates/ra_assists/src/assists/add_missing_impl_members.rs72
-rw-r--r--crates/ra_assists/src/assists/apply_demorgan.rs37
-rw-r--r--crates/ra_assists/src/assists/change_visibility.rs33
-rw-r--r--crates/ra_assists/src/assists/early_return.rs56
-rw-r--r--crates/ra_assists/src/assists/fill_match_arms.rs38
-rw-r--r--crates/ra_assists/src/assists/flip_binexpr.rs28
-rw-r--r--crates/ra_assists/src/assists/flip_comma.rs27
-rw-r--r--crates/ra_assists/src/assists/flip_trait_bound.rs117
-rw-r--r--crates/ra_assists/src/assists/inline_local_variable.rs34
-rw-r--r--crates/ra_assists/src/assists/introduce_variable.rs26
-rw-r--r--crates/ra_assists/src/assists/merge_match_arms.rs36
-rw-r--r--crates/ra_assists/src/assists/move_bounds.rs77
-rw-r--r--crates/ra_assists/src/assists/move_guard.rs70
-rw-r--r--crates/ra_assists/src/assists/raw_string.rs227
-rw-r--r--crates/ra_assists/src/assists/remove_dbg.rs30
-rw-r--r--crates/ra_assists/src/assists/replace_if_let_with_match.rs38
-rw-r--r--crates/ra_assists/src/assists/split_import.rs23
-rw-r--r--crates/ra_assists/src/doc_tests.rs34
-rw-r--r--crates/ra_assists/src/doc_tests/generated.rs526
-rw-r--r--crates/ra_assists/src/lib.rs95
25 files changed, 1521 insertions, 492 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index e270c5d60..1908bdec9 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -1,11 +1,11 @@
1//! FIXME: write short doc here 1//! This module defines `AssistCtx` -- the API surface that is exposed to assists.
2 2
3use hir::db::HirDatabase; 3use hir::db::HirDatabase;
4use ra_db::FileRange; 4use ra_db::FileRange;
5use ra_fmt::{leading_indent, reindent}; 5use ra_fmt::{leading_indent, reindent};
6use ra_syntax::{ 6use ra_syntax::{
7 algo::{self, find_covering_element, find_node_at_offset}, 7 algo::{self, find_covering_element, find_node_at_offset},
8 AstNode, SourceFile, SyntaxElement, SyntaxNode, SyntaxToken, TextRange, TextUnit, 8 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextUnit,
9 TokenAtOffset, 9 TokenAtOffset,
10}; 10};
11use ra_text_edit::TextEditBuilder; 11use ra_text_edit::TextEditBuilder;
@@ -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,43 +73,41 @@ 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 }
103 self
104 }
105 98
106 pub(crate) fn build(self) -> Option<Assist> { 99 Some(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> {
111 self.source_file.syntax().token_at_offset(self.frange.range.start()) 103 self.source_file.syntax().token_at_offset(self.frange.range.start())
112 } 104 }
113 105
114 pub(crate) fn node_at_offset<N: AstNode>(&self) -> Option<N> { 106 pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
107 self.token_at_offset().find(|it| it.kind() == kind)
108 }
109
110 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
115 find_node_at_offset(self.source_file.syntax(), self.frange.range.start()) 111 find_node_at_offset(self.source_file.syntax(), self.frange.range.start())
116 } 112 }
117 pub(crate) fn covering_element(&self) -> SyntaxElement { 113 pub(crate) fn covering_element(&self) -> SyntaxElement {
diff --git a/crates/ra_assists/src/assists/add_derive.rs b/crates/ra_assists/src/assists/add_derive.rs
index 77ecc33c9..764b17bd8 100644
--- a/crates/ra_assists/src/assists/add_derive.rs
+++ b/crates/ra_assists/src/assists/add_derive.rs
@@ -1,5 +1,3 @@
1//! FIXME: write short doc here
2
3use hir::db::HirDatabase; 1use hir::db::HirDatabase;
4use ra_syntax::{ 2use ra_syntax::{
5 ast::{self, AstNode, AttrsOwner}, 3 ast::{self, AstNode, AttrsOwner},
@@ -9,10 +7,28 @@ use ra_syntax::{
9 7
10use crate::{Assist, AssistCtx, AssistId}; 8use crate::{Assist, AssistCtx, AssistId};
11 9
12pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 10// Assist: add_derive
13 let nominal = ctx.node_at_offset::<ast::NominalDef>()?; 11//
12// Adds a new `#[derive()]` clause to a struct or enum.
13//
14// ```
15// struct Point {
16// x: u32,
17// y: u32,<|>
18// }
19// ```
20// ->
21// ```
22// #[derive()]
23// struct Point {
24// x: u32,
25// y: u32,
26// }
27// ```
28pub(crate) fn add_derive(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
29 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
14 let node_start = derive_insertion_offset(&nominal)?; 30 let node_start = derive_insertion_offset(&nominal)?;
15 ctx.add_action(AssistId("add_derive"), "add `#[derive]`", |edit| { 31 ctx.add_assist(AssistId("add_derive"), "add `#[derive]`", |edit| {
16 let derive_attr = nominal 32 let derive_attr = nominal
17 .attrs() 33 .attrs()
18 .filter_map(|x| x.as_simple_call()) 34 .filter_map(|x| x.as_simple_call())
@@ -28,9 +44,7 @@ pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist>
28 }; 44 };
29 edit.target(nominal.syntax().text_range()); 45 edit.target(nominal.syntax().text_range());
30 edit.set_cursor(offset) 46 edit.set_cursor(offset)
31 }); 47 })
32
33 ctx.build()
34} 48}
35 49
36// 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 8c83dc987..ddda1a0f2 100644
--- a/crates/ra_assists/src/assists/add_explicit_type.rs
+++ b/crates/ra_assists/src/assists/add_explicit_type.rs
@@ -1,5 +1,3 @@
1//! FIXME: write short doc here
2
3use hir::{db::HirDatabase, HirDisplay, Ty}; 1use hir::{db::HirDatabase, HirDisplay, Ty};
4use ra_syntax::{ 2use ra_syntax::{
5 ast::{self, AstNode, LetStmt, NameOwner}, 3 ast::{self, AstNode, LetStmt, NameOwner},
@@ -8,9 +6,23 @@ use ra_syntax::{
8 6
9use crate::{Assist, AssistCtx, AssistId}; 7use crate::{Assist, AssistCtx, AssistId};
10 8
11/// Add explicit type assist. 9// Assist: add_explicit_type
12pub(crate) fn add_explicit_type(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 10//
13 let stmt = ctx.node_at_offset::<LetStmt>()?; 11// Specify type for a let binding.
12//
13// ```
14// fn main() {
15// let x<|> = 92;
16// }
17// ```
18// ->
19// ```
20// fn main() {
21// let x: i32 = 92;
22// }
23// ```
24pub(crate) fn add_explicit_type(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
25 let stmt = ctx.find_node_at_offset::<LetStmt>()?;
14 let expr = stmt.initializer()?; 26 let expr = stmt.initializer()?;
15 let pat = stmt.pat()?; 27 let pat = stmt.pat()?;
16 // Must be a binding 28 // Must be a binding
@@ -35,11 +47,10 @@ pub(crate) fn add_explicit_type(mut ctx: AssistCtx<impl HirDatabase>) -> Option<
35 return None; 47 return None;
36 } 48 }
37 49
38 ctx.add_action(AssistId("add_explicit_type"), "add explicit type", |edit| { 50 ctx.add_assist(AssistId("add_explicit_type"), "add explicit type", |edit| {
39 edit.target(pat_range); 51 edit.target(pat_range);
40 edit.insert(name_range.end(), format!(": {}", ty.display(db))); 52 edit.insert(name_range.end(), format!(": {}", ty.display(db)));
41 }); 53 })
42 ctx.build()
43} 54}
44 55
45/// 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 94801fbc9..7da0cfd0d 100644
--- a/crates/ra_assists/src/assists/add_impl.rs
+++ b/crates/ra_assists/src/assists/add_impl.rs
@@ -1,5 +1,3 @@
1//! FIXME: write short doc here
2
3use format_buf::format; 1use format_buf::format;
4use hir::db::HirDatabase; 2use hir::db::HirDatabase;
5use join_to_string::join; 3use join_to_string::join;
@@ -10,10 +8,29 @@ use ra_syntax::{
10 8
11use crate::{Assist, AssistCtx, AssistId}; 9use crate::{Assist, AssistCtx, AssistId};
12 10
13pub(crate) fn add_impl(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 11// Assist: add_impl
14 let nominal = ctx.node_at_offset::<ast::NominalDef>()?; 12//
13// Adds a new inherent impl for a type.
14//
15// ```
16// struct Ctx<T: Clone> {
17// data: T,<|>
18// }
19// ```
20// ->
21// ```
22// struct Ctx<T: Clone> {
23// data: T,
24// }
25//
26// impl<T: Clone> Ctx<T> {
27//
28// }
29// ```
30pub(crate) fn add_impl(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
31 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
15 let name = nominal.name()?; 32 let name = nominal.name()?;
16 ctx.add_action(AssistId("add_impl"), "add impl", |edit| { 33 ctx.add_assist(AssistId("add_impl"), "add impl", |edit| {
17 edit.target(nominal.syntax().text_range()); 34 edit.target(nominal.syntax().text_range());
18 let type_params = nominal.type_param_list(); 35 let type_params = nominal.type_param_list();
19 let start_offset = nominal.syntax().text_range().end(); 36 let start_offset = nominal.syntax().text_range().end();
@@ -37,9 +54,7 @@ pub(crate) fn add_impl(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
37 edit.set_cursor(start_offset + TextUnit::of_str(&buf)); 54 edit.set_cursor(start_offset + TextUnit::of_str(&buf));
38 buf.push_str("\n}"); 55 buf.push_str("\n}");
39 edit.insert(start_offset, buf); 56 edit.insert(start_offset, buf);
40 }); 57 })
41
42 ctx.build()
43} 58}
44 59
45#[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 02c58e7c6..363ade016 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>,
@@ -61,9 +124,9 @@ fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) {
61 } 124 }
62} 125}
63 126
64// Returns the numeber of common segments. 127/// Returns the number of common segments.
65fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize { 128fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize {
66 left.iter().zip(right).filter(|(l, r)| compare_path_segment(l, r)).count() 129 left.iter().zip(right).take_while(|(l, r)| compare_path_segment(l, r)).count()
67} 130}
68 131
69fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool { 132fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool {
@@ -84,7 +147,7 @@ fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool {
84 a == b.text() 147 a == b.text()
85} 148}
86 149
87#[derive(Clone)] 150#[derive(Clone, Debug)]
88enum ImportAction { 151enum ImportAction {
89 Nothing, 152 Nothing,
90 // Add a brand new use statement. 153 // Add a brand new use statement.
@@ -154,9 +217,17 @@ impl ImportAction {
154 ( 217 (
155 ImportAction::AddNestedImport { common_segments: n, .. }, 218 ImportAction::AddNestedImport { common_segments: n, .. },
156 ImportAction::AddInTreeList { common_segments: m, .. }, 219 ImportAction::AddInTreeList { common_segments: m, .. },
157 ) => n > m, 220 )
158 ( 221 | (
222 ImportAction::AddInTreeList { common_segments: n, .. },
223 ImportAction::AddNestedImport { common_segments: m, .. },
224 )
225 | (
159 ImportAction::AddInTreeList { common_segments: n, .. }, 226 ImportAction::AddInTreeList { common_segments: n, .. },
227 ImportAction::AddInTreeList { common_segments: m, .. },
228 )
229 | (
230 ImportAction::AddNestedImport { common_segments: n, .. },
160 ImportAction::AddNestedImport { common_segments: m, .. }, 231 ImportAction::AddNestedImport { common_segments: m, .. },
161 ) => n > m, 232 ) => n > m,
162 (ImportAction::AddInTreeList { .. }, _) => true, 233 (ImportAction::AddInTreeList { .. }, _) => true,
@@ -226,7 +297,7 @@ fn walk_use_tree_for_best_action(
226 common if common == left.len() && left.len() == right.len() => { 297 common if common == left.len() && left.len() == right.len() => {
227 // e.g: target is std::fmt and we can have 298 // e.g: target is std::fmt and we can have
228 // 1- use std::fmt; 299 // 1- use std::fmt;
229 // 2- use std::fmt:{ ... } 300 // 2- use std::fmt::{ ... }
230 if let Some(list) = tree_list { 301 if let Some(list) = tree_list {
231 // In case 2 we need to add self to the nested list 302 // In case 2 we need to add self to the nested list
232 // unless it's already there 303 // unless it's already there
@@ -474,7 +545,7 @@ fn make_assist_add_nested_import(
474 if add_colon_colon { 545 if add_colon_colon {
475 buf.push_str("::"); 546 buf.push_str("::");
476 } 547 }
477 buf.push_str("{ "); 548 buf.push_str("{");
478 if add_self { 549 if add_self {
479 buf.push_str("self, "); 550 buf.push_str("self, ");
480 } 551 }
@@ -505,7 +576,7 @@ fn apply_auto_import(
505 } 576 }
506} 577}
507 578
508pub fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> { 579fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
509 let mut ps = Vec::<SmolStr>::with_capacity(10); 580 let mut ps = Vec::<SmolStr>::with_capacity(10);
510 match path.kind { 581 match path.kind {
511 hir::PathKind::Abs => ps.push("".into()), 582 hir::PathKind::Abs => ps.push("".into()),
@@ -521,87 +592,16 @@ pub fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
521 Some(ps) 592 Some(ps)
522} 593}
523 594
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.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)] 595#[cfg(test)]
597mod tests { 596mod tests {
598 use super::*;
599 use crate::helpers::{check_assist, check_assist_not_applicable}; 597 use crate::helpers::{check_assist, check_assist_not_applicable};
600 598
599 use super::*;
600
601 #[test] 601 #[test]
602 fn test_auto_import_add_use_no_anchor() { 602 fn test_auto_import_add_use_no_anchor() {
603 check_assist( 603 check_assist(
604 auto_import, 604 add_import,
605 " 605 "
606std::fmt::Debug<|> 606std::fmt::Debug<|>
607 ", 607 ",
@@ -615,7 +615,7 @@ Debug<|>
615 #[test] 615 #[test]
616 fn test_auto_import_add_use_no_anchor_with_item_below() { 616 fn test_auto_import_add_use_no_anchor_with_item_below() {
617 check_assist( 617 check_assist(
618 auto_import, 618 add_import,
619 " 619 "
620std::fmt::Debug<|> 620std::fmt::Debug<|>
621 621
@@ -636,7 +636,7 @@ fn main() {
636 #[test] 636 #[test]
637 fn test_auto_import_add_use_no_anchor_with_item_above() { 637 fn test_auto_import_add_use_no_anchor_with_item_above() {
638 check_assist( 638 check_assist(
639 auto_import, 639 add_import,
640 " 640 "
641fn main() { 641fn main() {
642} 642}
@@ -657,7 +657,7 @@ Debug<|>
657 #[test] 657 #[test]
658 fn test_auto_import_add_use_no_anchor_2seg() { 658 fn test_auto_import_add_use_no_anchor_2seg() {
659 check_assist( 659 check_assist(
660 auto_import, 660 add_import,
661 " 661 "
662std::fmt<|>::Debug 662std::fmt<|>::Debug
663 ", 663 ",
@@ -672,7 +672,7 @@ fmt<|>::Debug
672 #[test] 672 #[test]
673 fn test_auto_import_add_use() { 673 fn test_auto_import_add_use() {
674 check_assist( 674 check_assist(
675 auto_import, 675 add_import,
676 " 676 "
677use stdx; 677use stdx;
678 678
@@ -692,7 +692,7 @@ impl Debug<|> for Foo {
692 #[test] 692 #[test]
693 fn test_auto_import_file_use_other_anchor() { 693 fn test_auto_import_file_use_other_anchor() {
694 check_assist( 694 check_assist(
695 auto_import, 695 add_import,
696 " 696 "
697impl std::fmt::Debug<|> for Foo { 697impl std::fmt::Debug<|> for Foo {
698} 698}
@@ -709,7 +709,7 @@ impl Debug<|> for Foo {
709 #[test] 709 #[test]
710 fn test_auto_import_add_use_other_anchor_indent() { 710 fn test_auto_import_add_use_other_anchor_indent() {
711 check_assist( 711 check_assist(
712 auto_import, 712 add_import,
713 " 713 "
714 impl std::fmt::Debug<|> for Foo { 714 impl std::fmt::Debug<|> for Foo {
715 } 715 }
@@ -726,7 +726,7 @@ impl Debug<|> for Foo {
726 #[test] 726 #[test]
727 fn test_auto_import_split_different() { 727 fn test_auto_import_split_different() {
728 check_assist( 728 check_assist(
729 auto_import, 729 add_import,
730 " 730 "
731use std::fmt; 731use std::fmt;
732 732
@@ -734,7 +734,7 @@ impl std::io<|> for Foo {
734} 734}
735 ", 735 ",
736 " 736 "
737use std::{ io, fmt}; 737use std::{io, fmt};
738 738
739impl io<|> for Foo { 739impl io<|> for Foo {
740} 740}
@@ -745,7 +745,7 @@ impl io<|> for Foo {
745 #[test] 745 #[test]
746 fn test_auto_import_split_self_for_use() { 746 fn test_auto_import_split_self_for_use() {
747 check_assist( 747 check_assist(
748 auto_import, 748 add_import,
749 " 749 "
750use std::fmt; 750use std::fmt;
751 751
@@ -753,7 +753,7 @@ impl std::fmt::Debug<|> for Foo {
753} 753}
754 ", 754 ",
755 " 755 "
756use std::fmt::{ self, Debug, }; 756use std::fmt::{self, Debug, };
757 757
758impl Debug<|> for Foo { 758impl Debug<|> for Foo {
759} 759}
@@ -764,7 +764,7 @@ impl Debug<|> for Foo {
764 #[test] 764 #[test]
765 fn test_auto_import_split_self_for_target() { 765 fn test_auto_import_split_self_for_target() {
766 check_assist( 766 check_assist(
767 auto_import, 767 add_import,
768 " 768 "
769use std::fmt::Debug; 769use std::fmt::Debug;
770 770
@@ -772,7 +772,7 @@ impl std::fmt<|> for Foo {
772} 772}
773 ", 773 ",
774 " 774 "
775use std::fmt::{ self, Debug}; 775use std::fmt::{self, Debug};
776 776
777impl fmt<|> for Foo { 777impl fmt<|> for Foo {
778} 778}
@@ -783,7 +783,7 @@ impl fmt<|> for Foo {
783 #[test] 783 #[test]
784 fn test_auto_import_add_to_nested_self_nested() { 784 fn test_auto_import_add_to_nested_self_nested() {
785 check_assist( 785 check_assist(
786 auto_import, 786 add_import,
787 " 787 "
788use std::fmt::{Debug, nested::{Display}}; 788use std::fmt::{Debug, nested::{Display}};
789 789
@@ -802,7 +802,7 @@ impl nested<|> for Foo {
802 #[test] 802 #[test]
803 fn test_auto_import_add_to_nested_self_already_included() { 803 fn test_auto_import_add_to_nested_self_already_included() {
804 check_assist( 804 check_assist(
805 auto_import, 805 add_import,
806 " 806 "
807use std::fmt::{Debug, nested::{self, Display}}; 807use std::fmt::{Debug, nested::{self, Display}};
808 808
@@ -821,7 +821,7 @@ impl nested<|> for Foo {
821 #[test] 821 #[test]
822 fn test_auto_import_add_to_nested_nested() { 822 fn test_auto_import_add_to_nested_nested() {
823 check_assist( 823 check_assist(
824 auto_import, 824 add_import,
825 " 825 "
826use std::fmt::{Debug, nested::{Display}}; 826use std::fmt::{Debug, nested::{Display}};
827 827
@@ -840,7 +840,7 @@ impl Debug<|> for Foo {
840 #[test] 840 #[test]
841 fn test_auto_import_split_common_target_longer() { 841 fn test_auto_import_split_common_target_longer() {
842 check_assist( 842 check_assist(
843 auto_import, 843 add_import,
844 " 844 "
845use std::fmt::Debug; 845use std::fmt::Debug;
846 846
@@ -848,7 +848,7 @@ impl std::fmt::nested::Display<|> for Foo {
848} 848}
849", 849",
850 " 850 "
851use std::fmt::{ nested::Display, Debug}; 851use std::fmt::{nested::Display, Debug};
852 852
853impl Display<|> for Foo { 853impl Display<|> for Foo {
854} 854}
@@ -859,7 +859,7 @@ impl Display<|> for Foo {
859 #[test] 859 #[test]
860 fn test_auto_import_split_common_use_longer() { 860 fn test_auto_import_split_common_use_longer() {
861 check_assist( 861 check_assist(
862 auto_import, 862 add_import,
863 " 863 "
864use std::fmt::nested::Debug; 864use std::fmt::nested::Debug;
865 865
@@ -867,7 +867,7 @@ impl std::fmt::Display<|> for Foo {
867} 867}
868", 868",
869 " 869 "
870use std::fmt::{ Display, nested::Debug}; 870use std::fmt::{Display, nested::Debug};
871 871
872impl Display<|> for Foo { 872impl Display<|> for Foo {
873} 873}
@@ -876,9 +876,32 @@ impl Display<|> for Foo {
876 } 876 }
877 877
878 #[test] 878 #[test]
879 fn test_auto_import_use_nested_import() {
880 check_assist(
881 add_import,
882 "
883use crate::{
884 ty::{Substs, Ty},
885 AssocItem,
886};
887
888fn foo() { crate::ty::lower<|>::trait_env() }
889",
890 "
891use crate::{
892 ty::{Substs, Ty, lower},
893 AssocItem,
894};
895
896fn foo() { lower<|>::trait_env() }
897",
898 );
899 }
900
901 #[test]
879 fn test_auto_import_alias() { 902 fn test_auto_import_alias() {
880 check_assist( 903 check_assist(
881 auto_import, 904 add_import,
882 " 905 "
883use std::fmt as foo; 906use std::fmt as foo;
884 907
@@ -897,7 +920,7 @@ impl Debug<|> for Foo {
897 #[test] 920 #[test]
898 fn test_auto_import_not_applicable_one_segment() { 921 fn test_auto_import_not_applicable_one_segment() {
899 check_assist_not_applicable( 922 check_assist_not_applicable(
900 auto_import, 923 add_import,
901 " 924 "
902impl foo<|> for Foo { 925impl foo<|> for Foo {
903} 926}
@@ -908,7 +931,7 @@ impl foo<|> for Foo {
908 #[test] 931 #[test]
909 fn test_auto_import_not_applicable_in_use() { 932 fn test_auto_import_not_applicable_in_use() {
910 check_assist_not_applicable( 933 check_assist_not_applicable(
911 auto_import, 934 add_import,
912 " 935 "
913use std::fmt<|>; 936use std::fmt<|>;
914", 937",
@@ -918,7 +941,7 @@ use std::fmt<|>;
918 #[test] 941 #[test]
919 fn test_auto_import_add_use_no_anchor_in_mod_mod() { 942 fn test_auto_import_add_use_no_anchor_in_mod_mod() {
920 check_assist( 943 check_assist(
921 auto_import, 944 add_import,
922 " 945 "
923mod foo { 946mod foo {
924 mod bar { 947 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 565b96fb5..41de23921 100644
--- a/crates/ra_assists/src/assists/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs
@@ -1,5 +1,3 @@
1//! FIXME: write short doc here
2
3use hir::{db::HirDatabase, HasSource}; 1use hir::{db::HirDatabase, HasSource};
4use ra_syntax::{ 2use ra_syntax::{
5 ast::{self, edit, make, AstNode, NameOwner}, 3 ast::{self, edit, make, AstNode, NameOwner},
@@ -14,6 +12,34 @@ enum AddMissingImplMembersMode {
14 NoDefaultMethods, 12 NoDefaultMethods,
15} 13}
16 14
15// Assist: add_impl_missing_members
16//
17// Adds scaffold for required impl members.
18//
19// ```
20// trait T {
21// Type X;
22// fn foo(&self);
23// fn bar(&self) {}
24// }
25//
26// impl T for () {<|>
27//
28// }
29// ```
30// ->
31// ```
32// trait T {
33// Type X;
34// fn foo(&self);
35// fn bar(&self) {}
36// }
37//
38// impl T for () {
39// fn foo(&self) { unimplemented!() }
40//
41// }
42// ```
17pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 43pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
18 add_missing_impl_members_inner( 44 add_missing_impl_members_inner(
19 ctx, 45 ctx,
@@ -23,6 +49,38 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Opti
23 ) 49 )
24} 50}
25 51
52// Assist: add_impl_default_members
53//
54// Adds scaffold for overriding default impl members.
55//
56// ```
57// trait T {
58// Type X;
59// fn foo(&self);
60// fn bar(&self) {}
61// }
62//
63// impl T for () {
64// Type X = ();
65// fn foo(&self) {}<|>
66//
67// }
68// ```
69// ->
70// ```
71// trait T {
72// Type X;
73// fn foo(&self);
74// fn bar(&self) {}
75// }
76//
77// impl T for () {
78// Type X = ();
79// fn foo(&self) {}
80// fn bar(&self) {}
81//
82// }
83// ```
26pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 84pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
27 add_missing_impl_members_inner( 85 add_missing_impl_members_inner(
28 ctx, 86 ctx,
@@ -33,12 +91,12 @@ pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> O
33} 91}
34 92
35fn add_missing_impl_members_inner( 93fn add_missing_impl_members_inner(
36 mut ctx: AssistCtx<impl HirDatabase>, 94 ctx: AssistCtx<impl HirDatabase>,
37 mode: AddMissingImplMembersMode, 95 mode: AddMissingImplMembersMode,
38 assist_id: &'static str, 96 assist_id: &'static str,
39 label: &'static str, 97 label: &'static str,
40) -> Option<Assist> { 98) -> Option<Assist> {
41 let impl_node = ctx.node_at_offset::<ast::ImplBlock>()?; 99 let impl_node = ctx.find_node_at_offset::<ast::ImplBlock>()?;
42 let impl_item_list = impl_node.item_list()?; 100 let impl_item_list = impl_node.item_list()?;
43 101
44 let trait_def = { 102 let trait_def = {
@@ -75,7 +133,7 @@ fn add_missing_impl_members_inner(
75 return None; 133 return None;
76 } 134 }
77 135
78 ctx.add_action(AssistId(assist_id), label, |edit| { 136 ctx.add_assist(AssistId(assist_id), label, |edit| {
79 let n_existing_items = impl_item_list.impl_items().count(); 137 let n_existing_items = impl_item_list.impl_items().count();
80 let items = missing_items 138 let items = missing_items
81 .into_iter() 139 .into_iter()
@@ -92,9 +150,7 @@ fn add_missing_impl_members_inner(
92 150
93 edit.replace_ast(impl_item_list, new_impl_item_list); 151 edit.replace_ast(impl_item_list, new_impl_item_list);
94 edit.set_cursor(cursor_position); 152 edit.set_cursor(cursor_position);
95 }); 153 })
96
97 ctx.build()
98} 154}
99 155
100fn 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 5f2b0dd18..068da1774 100644
--- a/crates/ra_assists/src/assists/apply_demorgan.rs
+++ b/crates/ra_assists/src/assists/apply_demorgan.rs
@@ -1,20 +1,30 @@
1//! This contains the functions associated with the demorgan assist.
2//! This assist transforms boolean expressions of the form `!a || !b` into
3//! `!(a && b)`.
4use hir::db::HirDatabase; 1use hir::db::HirDatabase;
5use ra_syntax::ast::{self, AstNode}; 2use ra_syntax::ast::{self, AstNode};
6use ra_syntax::SyntaxNode; 3use ra_syntax::SyntaxNode;
7 4
8use crate::{Assist, AssistCtx, AssistId}; 5use crate::{Assist, AssistCtx, AssistId};
9 6
10/// Assist for applying demorgan's law 7// Assist: apply_demorgan
11/// 8//
12/// This transforms expressions of the form `!l || !r` into `!(l && r)`. 9// Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws).
13/// This also works with `&&`. This assist can only be applied with the cursor 10// This transforms expressions of the form `!l || !r` into `!(l && r)`.
14/// on either `||` or `&&`, with both operands being a negation of some kind. 11// This also works with `&&`. This assist can only be applied with the cursor
15/// This means something of the form `!x` or `x != y`. 12// on either `||` or `&&`, with both operands being a negation of some kind.
16pub(crate) fn apply_demorgan(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 13// This means something of the form `!x` or `x != y`.
17 let expr = ctx.node_at_offset::<ast::BinExpr>()?; 14//
15// ```
16// fn main() {
17// if x != 4 ||<|> !y {}
18// }
19// ```
20// ->
21// ```
22// fn main() {
23// if !(x == 4 && y) {}
24// }
25// ```
26pub(crate) fn apply_demorgan(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
27 let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
18 let op = expr.op_kind()?; 28 let op = expr.op_kind()?;
19 let op_range = expr.op_token()?.text_range(); 29 let op_range = expr.op_token()?.text_range();
20 let opposite_op = opposite_logic_op(op)?; 30 let opposite_op = opposite_logic_op(op)?;
@@ -29,13 +39,12 @@ pub(crate) fn apply_demorgan(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Ass
29 let not_lhs = undo_negation(lhs)?; 39 let not_lhs = undo_negation(lhs)?;
30 let not_rhs = undo_negation(rhs)?; 40 let not_rhs = undo_negation(rhs)?;
31 41
32 ctx.add_action(AssistId("apply_demorgan"), "apply demorgan's law", |edit| { 42 ctx.add_assist(AssistId("apply_demorgan"), "apply demorgan's law", |edit| {
33 edit.target(op_range); 43 edit.target(op_range);
34 edit.replace(op_range, opposite_op); 44 edit.replace(op_range, opposite_op);
35 edit.replace(lhs_range, format!("!({}", not_lhs)); 45 edit.replace(lhs_range, format!("!({}", not_lhs));
36 edit.replace(rhs_range, format!("{})", not_rhs)); 46 edit.replace(rhs_range, format!("{})", not_rhs));
37 }); 47 })
38 ctx.build()
39} 48}
40 49
41// 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 df92c6b67..132c9dc1d 100644
--- a/crates/ra_assists/src/assists/change_visibility.rs
+++ b/crates/ra_assists/src/assists/change_visibility.rs
@@ -1,5 +1,3 @@
1//! FIXME: write short doc here
2
3use hir::db::HirDatabase; 1use hir::db::HirDatabase;
4use ra_syntax::{ 2use ra_syntax::{
5 ast::{self, NameOwner, VisibilityOwner}, 3 ast::{self, NameOwner, VisibilityOwner},
@@ -13,14 +11,25 @@ use ra_syntax::{
13 11
14use crate::{Assist, AssistCtx, AssistId}; 12use crate::{Assist, AssistCtx, AssistId};
15 13
14// Assist: change_visibility
15//
16// Adds or changes existing visibility specifier.
17//
18// ```
19// <|>fn frobnicate() {}
20// ```
21// ->
22// ```
23// pub(crate) fn frobnicate() {}
24// ```
16pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 25pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
17 if let Some(vis) = ctx.node_at_offset::<ast::Visibility>() { 26 if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
18 return change_vis(ctx, vis); 27 return change_vis(ctx, vis);
19 } 28 }
20 add_vis(ctx) 29 add_vis(ctx)
21} 30}
22 31
23fn add_vis(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 32fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
24 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() {
25 T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, 34 T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true,
26 _ => false, 35 _ => false,
@@ -48,13 +57,11 @@ fn add_vis(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
48 (vis_offset(field.syntax()), ident.text_range()) 57 (vis_offset(field.syntax()), ident.text_range())
49 }; 58 };
50 59
51 ctx.add_action(AssistId("change_visibility"), "make pub(crate)", |edit| { 60 ctx.add_assist(AssistId("change_visibility"), "make pub(crate)", |edit| {
52 edit.target(target); 61 edit.target(target);
53 edit.insert(offset, "pub(crate) "); 62 edit.insert(offset, "pub(crate) ");
54 edit.set_cursor(offset); 63 edit.set_cursor(offset);
55 }); 64 })
56
57 ctx.build()
58} 65}
59 66
60fn vis_offset(node: &SyntaxNode) -> TextUnit { 67fn vis_offset(node: &SyntaxNode) -> TextUnit {
@@ -68,24 +75,20 @@ fn vis_offset(node: &SyntaxNode) -> TextUnit {
68 .unwrap_or_else(|| node.text_range().start()) 75 .unwrap_or_else(|| node.text_range().start())
69} 76}
70 77
71fn change_vis(mut ctx: AssistCtx<impl HirDatabase>, vis: ast::Visibility) -> Option<Assist> { 78fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: ast::Visibility) -> Option<Assist> {
72 if vis.syntax().text() == "pub" { 79 if vis.syntax().text() == "pub" {
73 ctx.add_action(AssistId("change_visibility"), "change to pub(crate)", |edit| { 80 return ctx.add_assist(AssistId("change_visibility"), "change to pub(crate)", |edit| {
74 edit.target(vis.syntax().text_range()); 81 edit.target(vis.syntax().text_range());
75 edit.replace(vis.syntax().text_range(), "pub(crate)"); 82 edit.replace(vis.syntax().text_range(), "pub(crate)");
76 edit.set_cursor(vis.syntax().text_range().start()) 83 edit.set_cursor(vis.syntax().text_range().start())
77 }); 84 });
78
79 return ctx.build();
80 } 85 }
81 if vis.syntax().text() == "pub(crate)" { 86 if vis.syntax().text() == "pub(crate)" {
82 ctx.add_action(AssistId("change_visibility"), "change to pub", |edit| { 87 return ctx.add_assist(AssistId("change_visibility"), "change to pub", |edit| {
83 edit.target(vis.syntax().text_range()); 88 edit.target(vis.syntax().text_range());
84 edit.replace(vis.syntax().text_range(), "pub"); 89 edit.replace(vis.syntax().text_range(), "pub");
85 edit.set_cursor(vis.syntax().text_range().start()); 90 edit.set_cursor(vis.syntax().text_range().start());
86 }); 91 });
87
88 return ctx.build();
89 } 92 }
90 None 93 None
91} 94}
diff --git a/crates/ra_assists/src/assists/early_return.rs b/crates/ra_assists/src/assists/early_return.rs
index f7d7e12e7..ad6c5695a 100644
--- a/crates/ra_assists/src/assists/early_return.rs
+++ b/crates/ra_assists/src/assists/early_return.rs
@@ -1,26 +1,3 @@
1//! Assist: `convert_to_guarded_return`
2//!
3//! Replace a large conditional with a guarded return.
4//!
5//! ```text
6//! fn <|>main() {
7//! if cond {
8//! foo();
9//! bar();
10//! }
11//! }
12//! ```
13//! ->
14//! ```text
15//! fn main() {
16//! if !cond {
17//! return;
18//! }
19//! foo();
20//! bar();
21//! }
22//! ```
23
24use std::ops::RangeInclusive; 1use std::ops::RangeInclusive;
25 2
26use hir::db::HirDatabase; 3use hir::db::HirDatabase;
@@ -36,8 +13,30 @@ use crate::{
36 AssistId, 13 AssistId,
37}; 14};
38 15
39pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 16// Assist: convert_to_guarded_return
40 let if_expr: ast::IfExpr = ctx.node_at_offset()?; 17//
18// Replace a large conditional with a guarded return.
19//
20// ```
21// fn main() {
22// <|>if cond {
23// foo();
24// bar();
25// }
26// }
27// ```
28// ->
29// ```
30// fn main() {
31// if !cond {
32// return;
33// }
34// foo();
35// bar();
36// }
37// ```
38pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
39 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
41 let expr = if_expr.condition()?.expr()?; 40 let expr = if_expr.condition()?.expr()?;
42 let then_block = if_expr.then_branch()?.block()?; 41 let then_block = if_expr.then_branch()?.block()?;
43 if if_expr.else_branch().is_some() { 42 if if_expr.else_branch().is_some() {
@@ -51,7 +50,7 @@ pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) ->
51 } 50 }
52 51
53 // check for early return and continue 52 // check for early return and continue
54 let first_in_then_block = then_block.syntax().first_child()?.clone(); 53 let first_in_then_block = then_block.syntax().first_child()?;
55 if ast::ReturnExpr::can_cast(first_in_then_block.kind()) 54 if ast::ReturnExpr::can_cast(first_in_then_block.kind())
56 || ast::ContinueExpr::can_cast(first_in_then_block.kind()) 55 || ast::ContinueExpr::can_cast(first_in_then_block.kind())
57 || first_in_then_block 56 || first_in_then_block
@@ -76,7 +75,7 @@ pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) ->
76 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)?;
77 let cursor_position = ctx.frange.range.start(); 76 let cursor_position = ctx.frange.range.start();
78 77
79 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| {
80 let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); 79 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
81 let new_if_expr = 80 let new_if_expr =
82 if_indent_level.increase_indent(make::if_expression(&expr, early_expression)); 81 if_indent_level.increase_indent(make::if_expression(&expr, early_expression));
@@ -106,8 +105,7 @@ pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) ->
106 edit.target(if_expr.syntax().text_range()); 105 edit.target(if_expr.syntax().text_range());
107 edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap()); 106 edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap());
108 edit.set_cursor(cursor_position); 107 edit.set_cursor(cursor_position);
109 }); 108 })
110 ctx.build()
111} 109}
112 110
113#[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 e3f30b5de..2b74f355c 100644
--- a/crates/ra_assists/src/assists/fill_match_arms.rs
+++ b/crates/ra_assists/src/assists/fill_match_arms.rs
@@ -7,8 +7,32 @@ use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner};
7 7
8use crate::{Assist, AssistCtx, AssistId}; 8use crate::{Assist, AssistCtx, AssistId};
9 9
10pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 10// Assist: fill_match_arms
11 let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?; 11//
12// Adds missing clauses to a `match` expression.
13//
14// ```
15// enum Action { Move { distance: u32 }, Stop }
16//
17// fn handle(action: Action) {
18// match action {
19// <|>
20// }
21// }
22// ```
23// ->
24// ```
25// enum Action { Move { distance: u32 }, Stop }
26//
27// fn handle(action: Action) {
28// match action {
29// Action::Move { distance } => (),
30// Action::Stop => (),
31// }
32// }
33// ```
34pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
35 let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
12 let match_arm_list = match_expr.match_arm_list()?; 36 let match_arm_list = match_expr.match_arm_list()?;
13 37
14 // We already have some match arms, so we don't provide any assists. 38 // We already have some match arms, so we don't provide any assists.
@@ -29,7 +53,7 @@ pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As
29 }; 53 };
30 let variant_list = enum_def.variant_list()?; 54 let variant_list = enum_def.variant_list()?;
31 55
32 ctx.add_action(AssistId("fill_match_arms"), "fill match arms", |edit| { 56 ctx.add_assist(AssistId("fill_match_arms"), "fill match arms", |edit| {
33 let indent_level = IndentLevel::from_node(match_arm_list.syntax()); 57 let indent_level = IndentLevel::from_node(match_arm_list.syntax());
34 58
35 let new_arm_list = { 59 let new_arm_list = {
@@ -43,9 +67,7 @@ pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As
43 edit.target(match_expr.syntax().text_range()); 67 edit.target(match_expr.syntax().text_range());
44 edit.set_cursor(expr.syntax().text_range().start()); 68 edit.set_cursor(expr.syntax().text_range().start());
45 edit.replace_ast(match_arm_list, new_arm_list); 69 edit.replace_ast(match_arm_list, new_arm_list);
46 }); 70 })
47
48 ctx.build()
49} 71}
50 72
51fn is_trivial(arm: &ast::MatchArm) -> bool { 73fn is_trivial(arm: &ast::MatchArm) -> bool {
@@ -130,7 +152,7 @@ mod tests {
130 A::Bs => (), 152 A::Bs => (),
131 A::Cs(_) => (), 153 A::Cs(_) => (),
132 A::Ds(_, _) => (), 154 A::Ds(_, _) => (),
133 A::Es{ x, y } => (), 155 A::Es { x, y } => (),
134 } 156 }
135 } 157 }
136 "#, 158 "#,
@@ -183,7 +205,7 @@ mod tests {
183 205
184 fn foo(a: &mut A) { 206 fn foo(a: &mut A) {
185 match <|>a { 207 match <|>a {
186 A::Es{ x, y } => (), 208 A::Es { x, y } => (),
187 } 209 }
188 } 210 }
189 "#, 211 "#,
diff --git a/crates/ra_assists/src/assists/flip_binexpr.rs b/crates/ra_assists/src/assists/flip_binexpr.rs
index c51035282..386045eb0 100644
--- a/crates/ra_assists/src/assists/flip_binexpr.rs
+++ b/crates/ra_assists/src/assists/flip_binexpr.rs
@@ -1,13 +1,25 @@
1//! FIXME: write short doc here
2
3use hir::db::HirDatabase; 1use hir::db::HirDatabase;
4use ra_syntax::ast::{AstNode, BinExpr, BinOp}; 2use ra_syntax::ast::{AstNode, BinExpr, BinOp};
5 3
6use crate::{Assist, AssistCtx, AssistId}; 4use crate::{Assist, AssistCtx, AssistId};
7 5
8/// Flip binary expression assist. 6// Assist: flip_binexpr
9pub(crate) fn flip_binexpr(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 7//
10 let expr = ctx.node_at_offset::<BinExpr>()?; 8// Flips operands of a binary expression.
9//
10// ```
11// fn main() {
12// let _ = 90 +<|> 2;
13// }
14// ```
15// ->
16// ```
17// fn main() {
18// let _ = 2 + 90;
19// }
20// ```
21pub(crate) fn flip_binexpr(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
22 let expr = ctx.find_node_at_offset::<BinExpr>()?;
11 let lhs = expr.lhs()?.syntax().clone(); 23 let lhs = expr.lhs()?.syntax().clone();
12 let rhs = expr.rhs()?.syntax().clone(); 24 let rhs = expr.rhs()?.syntax().clone();
13 let op_range = expr.op_token()?.text_range(); 25 let op_range = expr.op_token()?.text_range();
@@ -22,16 +34,14 @@ pub(crate) fn flip_binexpr(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assis
22 return None; 34 return None;
23 } 35 }
24 36
25 ctx.add_action(AssistId("flip_binexpr"), "flip binary expression", |edit| { 37 ctx.add_assist(AssistId("flip_binexpr"), "flip binary expression", |edit| {
26 edit.target(op_range); 38 edit.target(op_range);
27 if let FlipAction::FlipAndReplaceOp(new_op) = action { 39 if let FlipAction::FlipAndReplaceOp(new_op) = action {
28 edit.replace(op_range, new_op); 40 edit.replace(op_range, new_op);
29 } 41 }
30 edit.replace(lhs.text_range(), rhs.text()); 42 edit.replace(lhs.text_range(), rhs.text());
31 edit.replace(rhs.text_range(), lhs.text()); 43 edit.replace(rhs.text_range(), lhs.text());
32 }); 44 })
33
34 ctx.build()
35} 45}
36 46
37enum FlipAction { 47enum FlipAction {
diff --git a/crates/ra_assists/src/assists/flip_comma.rs b/crates/ra_assists/src/assists/flip_comma.rs
index e31cc5e7d..9be1c1dc6 100644
--- a/crates/ra_assists/src/assists/flip_comma.rs
+++ b/crates/ra_assists/src/assists/flip_comma.rs
@@ -1,12 +1,25 @@
1//! FIXME: write short doc here
2
3use hir::db::HirDatabase; 1use hir::db::HirDatabase;
4use ra_syntax::{algo::non_trivia_sibling, Direction, T}; 2use ra_syntax::{algo::non_trivia_sibling, Direction, T};
5 3
6use crate::{Assist, AssistCtx, AssistId}; 4use crate::{Assist, AssistCtx, AssistId};
7 5
8pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 6// Assist: flip_comma
9 let comma = ctx.token_at_offset().find(|leaf| leaf.kind() == T![,])?; 7//
8// Flips two comma-separated items.
9//
10// ```
11// fn main() {
12// ((1, 2),<|> (3, 4));
13// }
14// ```
15// ->
16// ```
17// fn main() {
18// ((3, 4), (1, 2));
19// }
20// ```
21pub(crate) fn flip_comma(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
22 let comma = ctx.find_token_at_offset(T![,])?;
10 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; 23 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
11 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; 24 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
12 25
@@ -16,13 +29,11 @@ pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist>
16 return None; 29 return None;
17 } 30 }
18 31
19 ctx.add_action(AssistId("flip_comma"), "flip comma", |edit| { 32 ctx.add_assist(AssistId("flip_comma"), "flip comma", |edit| {
20 edit.target(comma.text_range()); 33 edit.target(comma.text_range());
21 edit.replace(prev.text_range(), next.to_string()); 34 edit.replace(prev.text_range(), next.to_string());
22 edit.replace(next.text_range(), prev.to_string()); 35 edit.replace(next.text_range(), prev.to_string());
23 }); 36 })
24
25 ctx.build()
26} 37}
27 38
28#[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 9bd64decc..a7fd9b6d2 100644
--- a/crates/ra_assists/src/assists/inline_local_variable.rs
+++ b/crates/ra_assists/src/assists/inline_local_variable.rs
@@ -1,5 +1,3 @@
1//! FIXME: write short doc here
2
3use hir::db::HirDatabase; 1use hir::db::HirDatabase;
4use ra_syntax::{ 2use ra_syntax::{
5 ast::{self, AstNode, AstToken}, 3 ast::{self, AstNode, AstToken},
@@ -9,8 +7,24 @@ use ra_syntax::{
9use crate::assist_ctx::AssistBuilder; 7use crate::assist_ctx::AssistBuilder;
10use crate::{Assist, AssistCtx, AssistId}; 8use crate::{Assist, AssistCtx, AssistId};
11 9
12pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 10// Assist: inline_local_variable
13 let let_stmt = ctx.node_at_offset::<ast::LetStmt>()?; 11//
12// Inlines local variable.
13//
14// ```
15// fn main() {
16// let x<|> = 1 + 2;
17// x * 4;
18// }
19// ```
20// ->
21// ```
22// fn main() {
23// (1 + 2) * 4;
24// }
25// ```
26pub(crate) fn inline_local_varialbe(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
27 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
14 let bind_pat = match let_stmt.pat()? { 28 let bind_pat = match let_stmt.pat()? {
15 ast::Pat::BindPat(pat) => pat, 29 ast::Pat::BindPat(pat) => pat,
16 _ => return None, 30 _ => return None,
@@ -37,10 +51,8 @@ pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Opt
37 let mut wrap_in_parens = vec![true; refs.len()]; 51 let mut wrap_in_parens = vec![true; refs.len()];
38 52
39 for (i, desc) in refs.iter().enumerate() { 53 for (i, desc) in refs.iter().enumerate() {
40 let usage_node = ctx 54 let usage_node =
41 .covering_node_for_range(desc.range) 55 ctx.covering_node_for_range(desc.range).ancestors().find_map(ast::PathExpr::cast)?;
42 .ancestors()
43 .find_map(|node| ast::PathExpr::cast(node))?;
44 let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast); 56 let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast);
45 let usage_parent = match usage_parent_option { 57 let usage_parent = match usage_parent_option {
46 Some(u) => u, 58 Some(u) => u,
@@ -79,7 +91,7 @@ pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Opt
79 let init_str = initializer_expr.syntax().text().to_string(); 91 let init_str = initializer_expr.syntax().text().to_string();
80 let init_in_paren = format!("({})", &init_str); 92 let init_in_paren = format!("({})", &init_str);
81 93
82 ctx.add_action( 94 ctx.add_assist(
83 AssistId("inline_local_variable"), 95 AssistId("inline_local_variable"),
84 "inline local variable", 96 "inline local variable",
85 move |edit: &mut AssistBuilder| { 97 move |edit: &mut AssistBuilder| {
@@ -93,9 +105,7 @@ pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Opt
93 } 105 }
94 edit.set_cursor(delete_range.start()) 106 edit.set_cursor(delete_range.start())
95 }, 107 },
96 ); 108 )
97
98 ctx.build()
99} 109}
100 110
101#[cfg(test)] 111#[cfg(test)]
diff --git a/crates/ra_assists/src/assists/introduce_variable.rs b/crates/ra_assists/src/assists/introduce_variable.rs
index 43378c4b0..0623d4475 100644
--- a/crates/ra_assists/src/assists/introduce_variable.rs
+++ b/crates/ra_assists/src/assists/introduce_variable.rs
@@ -1,5 +1,3 @@
1//! FIXME: write short doc here
2
3use format_buf::format; 1use format_buf::format;
4use hir::db::HirDatabase; 2use hir::db::HirDatabase;
5use ra_syntax::{ 3use ra_syntax::{
@@ -14,7 +12,23 @@ use test_utils::tested_by;
14 12
15use crate::{Assist, AssistCtx, AssistId}; 13use crate::{Assist, AssistCtx, AssistId};
16 14
17pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 15// Assist: introduce_variable
16//
17// Extracts subexpression into a variable.
18//
19// ```
20// fn main() {
21// <|>(1 + 2)<|> * 4;
22// }
23// ```
24// ->
25// ```
26// fn main() {
27// let var_name = (1 + 2);
28// var_name * 4;
29// }
30// ```
31pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
18 if ctx.frange.range.is_empty() { 32 if ctx.frange.range.is_empty() {
19 return None; 33 return None;
20 } 34 }
@@ -29,7 +43,7 @@ pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option
29 if indent.kind() != WHITESPACE { 43 if indent.kind() != WHITESPACE {
30 return None; 44 return None;
31 } 45 }
32 ctx.add_action(AssistId("introduce_variable"), "introduce variable", move |edit| { 46 ctx.add_assist(AssistId("introduce_variable"), "introduce variable", move |edit| {
33 let mut buf = String::new(); 47 let mut buf = String::new();
34 48
35 let cursor_offset = if wrap_in_block { 49 let cursor_offset = if wrap_in_block {
@@ -74,9 +88,7 @@ pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option
74 } 88 }
75 } 89 }
76 edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset); 90 edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset);
77 }); 91 })
78
79 ctx.build()
80} 92}
81 93
82/// 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 17baa98f9..e9f2cae91 100644
--- a/crates/ra_assists/src/assists/merge_match_arms.rs
+++ b/crates/ra_assists/src/assists/merge_match_arms.rs
@@ -1,11 +1,33 @@
1//! FIXME: write short doc here
2
3use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; 1use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit};
4use hir::db::HirDatabase; 2use hir::db::HirDatabase;
5use ra_syntax::ast::{AstNode, MatchArm}; 3use ra_syntax::ast::{AstNode, MatchArm};
6 4
7pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 5// Assist: merge_match_arms
8 let current_arm = ctx.node_at_offset::<MatchArm>()?; 6//
7// Merges identical match arms.
8//
9// ```
10// enum Action { Move { distance: u32 }, Stop }
11//
12// fn handle(action: Action) {
13// match action {
14// <|>Action::Move(..) => foo(),
15// Action::Stop => foo(),
16// }
17// }
18// ```
19// ->
20// ```
21// enum Action { Move { distance: u32 }, Stop }
22//
23// fn handle(action: Action) {
24// match action {
25// Action::Move(..) | Action::Stop => foo(),
26// }
27// }
28// ```
29pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
30 let current_arm = ctx.find_node_at_offset::<MatchArm>()?;
9 31
10 // 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,
11 // compare to the previous match arm as well. 33 // compare to the previous match arm as well.
@@ -30,7 +52,7 @@ pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<A
30 52
31 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();
32 54
33 ctx.add_action(AssistId("merge_match_arms"), "merge match arms", |edit| { 55 ctx.add_assist(AssistId("merge_match_arms"), "merge match arms", |edit| {
34 fn contains_placeholder(a: &MatchArm) -> bool { 56 fn contains_placeholder(a: &MatchArm) -> bool {
35 a.pats().any(|x| match x { 57 a.pats().any(|x| match x {
36 ra_syntax::ast::Pat::PlaceholderPat(..) => true, 58 ra_syntax::ast::Pat::PlaceholderPat(..) => true,
@@ -58,9 +80,7 @@ pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<A
58 edit.target(current_arm.syntax().text_range()); 80 edit.target(current_arm.syntax().text_range());
59 edit.replace(TextRange::from_to(start, end), arm); 81 edit.replace(TextRange::from_to(start, end), arm);
60 edit.set_cursor(start + offset); 82 edit.set_cursor(start + offset);
61 }); 83 })
62
63 ctx.build()
64} 84}
65 85
66#[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 d2444b6b9..3145d7625 100644
--- a/crates/ra_assists/src/assists/move_bounds.rs
+++ b/crates/ra_assists/src/assists/move_bounds.rs
@@ -1,5 +1,3 @@
1//! FIXME: write short doc here
2
3use hir::db::HirDatabase; 1use hir::db::HirDatabase;
4use ra_syntax::{ 2use ra_syntax::{
5 ast::{self, edit, make, AstNode, NameOwner, TypeBoundsOwner}, 3 ast::{self, edit, make, AstNode, NameOwner, TypeBoundsOwner},
@@ -9,8 +7,23 @@ use ra_syntax::{
9 7
10use crate::{Assist, AssistCtx, AssistId}; 8use crate::{Assist, AssistCtx, AssistId};
11 9
12pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 10// Assist: move_bounds_to_where_clause
13 let type_param_list = ctx.node_at_offset::<ast::TypeParamList>()?; 11//
12// Moves inline type bounds to a where clause.
13//
14// ```
15// fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U {
16// f(x)
17// }
18// ```
19// ->
20// ```
21// fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
22// f(x)
23// }
24// ```
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>()?;
14 27
15 let mut type_params = type_param_list.type_params(); 28 let mut type_params = type_param_list.type_params();
16 if type_params.all(|p| p.type_bound_list().is_none()) { 29 if type_params.all(|p| p.type_bound_list().is_none()) {
@@ -33,38 +46,30 @@ pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>)
33 _ => return None, 46 _ => return None,
34 }; 47 };
35 48
36 ctx.add_action( 49 ctx.add_assist(AssistId("move_bounds_to_where_clause"), "move_bounds_to_where_clause", |edit| {
37 AssistId("move_bounds_to_where_clause"), 50 let new_params = type_param_list
38 "move_bounds_to_where_clause", 51 .type_params()
39 |edit| { 52 .filter(|it| it.type_bound_list().is_some())
40 let new_params = type_param_list 53 .map(|type_param| {
41 .type_params() 54 let without_bounds = type_param.remove_bounds();
42 .filter(|it| it.type_bound_list().is_some()) 55 (type_param, without_bounds)
43 .map(|type_param| { 56 });
44 let without_bounds = type_param.remove_bounds(); 57
45 (type_param, without_bounds) 58 let new_type_param_list = edit::replace_descendants(&type_param_list, new_params);
46 }); 59 edit.replace_ast(type_param_list.clone(), new_type_param_list);
47 60
48 let new_type_param_list = edit::replace_descendants(&type_param_list, new_params); 61 let where_clause = {
49 edit.replace_ast(type_param_list.clone(), new_type_param_list); 62 let predicates = type_param_list.type_params().filter_map(build_predicate);
50 63 make::where_clause(predicates)
51 let where_clause = { 64 };
52 let predicates = type_param_list.type_params().filter_map(build_predicate); 65
53 make::where_clause(predicates) 66 let to_insert = match anchor.prev_sibling_or_token() {
54 }; 67 Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()),
55 68 _ => format!(" {}", where_clause.syntax()),
56 let to_insert = match anchor.prev_sibling_or_token() { 69 };
57 Some(ref elem) if elem.kind() == WHITESPACE => { 70 edit.insert(anchor.text_range().start(), to_insert);
58 format!("{} ", where_clause.syntax()) 71 edit.target(type_param_list.syntax().text_range());
59 } 72 })
60 _ => format!(" {}", where_clause.syntax()),
61 };
62 edit.insert(anchor.text_range().start(), to_insert);
63 edit.target(type_param_list.syntax().text_range());
64 },
65 );
66
67 ctx.build()
68} 73}
69 74
70fn 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 51aea6334..b49ec6172 100644
--- a/crates/ra_assists/src/assists/move_guard.rs
+++ b/crates/ra_assists/src/assists/move_guard.rs
@@ -1,5 +1,3 @@
1//! FIXME: write short doc here
2
3use hir::db::HirDatabase; 1use hir::db::HirDatabase;
4use ra_syntax::{ 2use ra_syntax::{
5 ast, 3 ast,
@@ -9,8 +7,33 @@ use ra_syntax::{
9 7
10use crate::{Assist, AssistCtx, AssistId}; 8use crate::{Assist, AssistCtx, AssistId};
11 9
12pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 10// Assist: move_guard_to_arm_body
13 let match_arm = ctx.node_at_offset::<MatchArm>()?; 11//
12// Moves match guard into match arm body.
13//
14// ```
15// enum Action { Move { distance: u32 }, Stop }
16//
17// fn handle(action: Action) {
18// match action {
19// Action::Move { distance } <|>if distance > 10 => foo(),
20// _ => (),
21// }
22// }
23// ```
24// ->
25// ```
26// enum Action { Move { distance: u32 }, Stop }
27//
28// fn handle(action: Action) {
29// match action {
30// Action::Move { distance } => if distance > 10 { foo() },
31// _ => (),
32// }
33// }
34// ```
35pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
36 let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
14 let guard = match_arm.guard()?; 37 let guard = match_arm.guard()?;
15 let space_before_guard = guard.syntax().prev_sibling_or_token(); 38 let space_before_guard = guard.syntax().prev_sibling_or_token();
16 39
@@ -18,7 +41,7 @@ pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Op
18 let arm_expr = match_arm.expr()?; 41 let arm_expr = match_arm.expr()?;
19 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());
20 43
21 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| {
22 edit.target(guard.syntax().text_range()); 45 edit.target(guard.syntax().text_range());
23 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()) {
24 Some(tok) => { 47 Some(tok) => {
@@ -38,12 +61,36 @@ pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Op
38 edit.set_cursor( 61 edit.set_cursor(
39 arm_expr.syntax().text_range().start() + TextUnit::from(3) - offseting_amount, 62 arm_expr.syntax().text_range().start() + TextUnit::from(3) - offseting_amount,
40 ); 63 );
41 }); 64 })
42 ctx.build()
43} 65}
44 66
45pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 67// Assist: move_arm_cond_to_match_guard
46 let match_arm: MatchArm = ctx.node_at_offset::<MatchArm>()?; 68//
69// Moves if expression from match arm body into a guard.
70//
71// ```
72// enum Action { Move { distance: u32 }, Stop }
73//
74// fn handle(action: Action) {
75// match action {
76// Action::Move { distance } => <|>if distance > 10 { foo() },
77// _ => (),
78// }
79// }
80// ```
81// ->
82// ```
83// enum Action { Move { distance: u32 }, Stop }
84//
85// fn handle(action: Action) {
86// match action {
87// Action::Move { distance } if distance > 10 => foo(),
88// _ => (),
89// }
90// }
91// ```
92pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
93 let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
47 let last_match_pat = match_arm.pats().last()?; 94 let last_match_pat = match_arm.pats().last()?;
48 95
49 let arm_body = match_arm.expr()?; 96 let arm_body = match_arm.expr()?;
@@ -62,7 +109,7 @@ pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx<impl HirDatabase>)
62 109
63 let buf = format!(" if {}", cond.syntax().text()); 110 let buf = format!(" if {}", cond.syntax().text());
64 111
65 ctx.add_action( 112 ctx.add_assist(
66 AssistId("move_arm_cond_to_match_guard"), 113 AssistId("move_arm_cond_to_match_guard"),
67 "move condition to match guard", 114 "move condition to match guard",
68 |edit| { 115 |edit| {
@@ -79,8 +126,7 @@ pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx<impl HirDatabase>)
79 edit.insert(last_match_pat.syntax().text_range().end(), buf); 126 edit.insert(last_match_pat.syntax().text_range().end(), buf);
80 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));
81 }, 128 },
82 ); 129 )
83 ctx.build()
84} 130}
85 131
86#[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 2d2e31e51..58f7157ae 100644
--- a/crates/ra_assists/src/assists/raw_string.rs
+++ b/crates/ra_assists/src/assists/raw_string.rs
@@ -1,17 +1,29 @@
1//! FIXME: write short doc here
2
3use hir::db::HirDatabase; 1use hir::db::HirDatabase;
4use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; 2use ra_syntax::{
3 SyntaxKind::{RAW_STRING, STRING},
4 TextRange, TextUnit,
5};
5use rustc_lexer; 6use rustc_lexer;
6 7
7use crate::{Assist, AssistCtx, AssistId}; 8use crate::{Assist, AssistCtx, AssistId};
8 9
9pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 10// Assist: make_raw_string
10 let literal = ctx.node_at_offset::<Literal>()?; 11//
11 if literal.token().kind() != ra_syntax::SyntaxKind::STRING { 12// Adds `r#` to a plain string literal.
12 return None; 13//
13 } 14// ```
14 let token = literal.token(); 15// fn main() {
16// "Hello,<|> World!";
17// }
18// ```
19// ->
20// ```
21// fn main() {
22// r#"Hello, World!"#;
23// }
24// ```
25pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
26 let token = ctx.find_token_at_offset(STRING)?;
15 let text = token.text().as_str(); 27 let text = token.text().as_str();
16 let usual_string_range = find_usual_string_range(text)?; 28 let usual_string_range = find_usual_string_range(text)?;
17 let start_of_inside = usual_string_range.start().to_usize() + 1; 29 let start_of_inside = usual_string_range.start().to_usize() + 1;
@@ -29,97 +41,131 @@ pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As
29 if error.is_err() { 41 if error.is_err() {
30 return None; 42 return None;
31 } 43 }
32 ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { 44 ctx.add_assist(AssistId("make_raw_string"), "make raw string", |edit| {
33 edit.target(literal.syntax().text_range()); 45 edit.target(token.text_range());
34 let max_hash_streak = count_hashes(&unescaped); 46 let max_hash_streak = count_hashes(&unescaped);
35 let mut hashes = String::with_capacity(max_hash_streak + 1); 47 let mut hashes = String::with_capacity(max_hash_streak + 1);
36 for _ in 0..hashes.capacity() { 48 for _ in 0..hashes.capacity() {
37 hashes.push('#'); 49 hashes.push('#');
38 } 50 }
39 edit.replace( 51 edit.replace(token.text_range(), format!("r{}\"{}\"{}", hashes, unescaped, hashes));
40 literal.syntax().text_range(), 52 })
41 format!("r{}\"{}\"{}", hashes, unescaped, hashes),
42 );
43 });
44 ctx.build()
45}
46
47fn count_hashes(s: &str) -> usize {
48 let mut max_hash_streak = 0usize;
49 for idx in s.match_indices("\"#").map(|(i, _)| i) {
50 let (_, sub) = s.split_at(idx + 1);
51 let nb_hash = sub.chars().take_while(|c| *c == '#').count();
52 if nb_hash > max_hash_streak {
53 max_hash_streak = nb_hash;
54 }
55 }
56 max_hash_streak
57}
58
59fn find_usual_string_range(s: &str) -> Option<TextRange> {
60 Some(TextRange::from_to(
61 TextUnit::from(s.find('"')? as u32),
62 TextUnit::from(s.rfind('"')? as u32),
63 ))
64} 53}
65 54
66pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 55// Assist: make_usual_string
67 let literal = ctx.node_at_offset::<Literal>()?; 56//
68 if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { 57// Turns a raw string into a plain string.
69 return None; 58//
70 } 59// ```
71 let token = literal.token(); 60// fn main() {
61// r#"Hello,<|> "World!""#;
62// }
63// ```
64// ->
65// ```
66// fn main() {
67// "Hello, \"World!\"";
68// }
69// ```
70pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
71 let token = ctx.find_token_at_offset(RAW_STRING)?;
72 let text = token.text().as_str(); 72 let text = token.text().as_str();
73 let usual_string_range = find_usual_string_range(text)?; 73 let usual_string_range = find_usual_string_range(text)?;
74 ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| { 74 ctx.add_assist(AssistId("make_usual_string"), "make usual string", |edit| {
75 edit.target(literal.syntax().text_range()); 75 edit.target(token.text_range());
76 // parse inside string to escape `"` 76 // parse inside string to escape `"`
77 let start_of_inside = usual_string_range.start().to_usize() + 1; 77 let start_of_inside = usual_string_range.start().to_usize() + 1;
78 let end_of_inside = usual_string_range.end().to_usize(); 78 let end_of_inside = usual_string_range.end().to_usize();
79 let inside_str = &text[start_of_inside..end_of_inside]; 79 let inside_str = &text[start_of_inside..end_of_inside];
80 let escaped = inside_str.escape_default().to_string(); 80 let escaped = inside_str.escape_default().to_string();
81 edit.replace(literal.syntax().text_range(), format!("\"{}\"", escaped)); 81 edit.replace(token.text_range(), format!("\"{}\"", escaped));
82 }); 82 })
83 ctx.build()
84} 83}
85 84
86pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 85// Assist: add_hash
87 let literal = ctx.node_at_offset::<Literal>()?; 86//
88 if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { 87// Adds a hash to a raw string literal.
89 return None; 88//
90 } 89// ```
91 ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| { 90// fn main() {
92 edit.target(literal.syntax().text_range()); 91// r#"Hello,<|> World!"#;
93 edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#"); 92// }
94 edit.insert(literal.syntax().text_range().end(), "#"); 93// ```
95 }); 94// ->
96 ctx.build() 95// ```
96// fn main() {
97// r##"Hello, World!"##;
98// }
99// ```
100pub(crate) fn add_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
101 let token = ctx.find_token_at_offset(RAW_STRING)?;
102 ctx.add_assist(AssistId("add_hash"), "add hash to raw string", |edit| {
103 edit.target(token.text_range());
104 edit.insert(token.text_range().start() + TextUnit::of_char('r'), "#");
105 edit.insert(token.text_range().end(), "#");
106 })
97} 107}
98 108
99pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 109// Assist: remove_hash
100 let literal = ctx.node_at_offset::<Literal>()?; 110//
101 if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { 111// Removes a hash from a raw string literal.
102 return None; 112//
103 } 113// ```
104 let token = literal.token(); 114// fn main() {
115// r#"Hello,<|> World!"#;
116// }
117// ```
118// ->
119// ```
120// fn main() {
121// r"Hello, World!";
122// }
123// ```
124pub(crate) fn remove_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
125 let token = ctx.find_token_at_offset(RAW_STRING)?;
105 let text = token.text().as_str(); 126 let text = token.text().as_str();
106 if text.starts_with("r\"") { 127 if text.starts_with("r\"") {
107 // no hash to remove 128 // no hash to remove
108 return None; 129 return None;
109 } 130 }
110 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| {
111 edit.target(literal.syntax().text_range()); 132 edit.target(token.text_range());
112 let result = &text[2..text.len() - 1]; 133 let result = &text[2..text.len() - 1];
113 let result = if result.starts_with("\"") { 134 let result = if result.starts_with('\"') {
114 // no more hash, escape 135 // no more hash, escape
115 let internal_str = &result[1..result.len() - 1]; 136 let internal_str = &result[1..result.len() - 1];
116 format!("\"{}\"", internal_str.escape_default().to_string()) 137 format!("\"{}\"", internal_str.escape_default().to_string())
117 } else { 138 } else {
118 result.to_owned() 139 result.to_owned()
119 }; 140 };
120 edit.replace(literal.syntax().text_range(), format!("r{}", result)); 141 edit.replace(token.text_range(), format!("r{}", result));
121 }); 142 })
122 ctx.build() 143}
144
145fn count_hashes(s: &str) -> usize {
146 let mut max_hash_streak = 0usize;
147 for idx in s.match_indices("\"#").map(|(i, _)| i) {
148 let (_, sub) = s.split_at(idx + 1);
149 let nb_hash = sub.chars().take_while(|c| *c == '#').count();
150 if nb_hash > max_hash_streak {
151 max_hash_streak = nb_hash;
152 }
153 }
154 max_hash_streak
155}
156
157fn find_usual_string_range(s: &str) -> Option<TextRange> {
158 let left_quote = s.find('"')?;
159 let right_quote = s.rfind('"')?;
160 if left_quote == right_quote {
161 // `s` only contains one quote
162 None
163 } else {
164 Some(TextRange::from_to(
165 TextUnit::from(left_quote as u32),
166 TextUnit::from(right_quote as u32),
167 ))
168 }
123} 169}
124 170
125#[cfg(test)] 171#[cfg(test)]
@@ -159,6 +205,23 @@ string"#;
159 } 205 }
160 206
161 #[test] 207 #[test]
208 fn make_raw_string_works_inside_macros() {
209 check_assist(
210 make_raw_string,
211 r#"
212 fn f() {
213 format!(<|>"x = {}", 92)
214 }
215 "#,
216 r##"
217 fn f() {
218 format!(<|>r#"x = {}"#, 92)
219 }
220 "##,
221 )
222 }
223
224 #[test]
162 fn make_raw_string_hashes_inside_works() { 225 fn make_raw_string_hashes_inside_works() {
163 check_assist( 226 check_assist(
164 make_raw_string, 227 make_raw_string,
@@ -212,6 +275,30 @@ string"###;
212 } 275 }
213 276
214 #[test] 277 #[test]
278 fn make_raw_string_not_works_on_partial_string() {
279 check_assist_not_applicable(
280 make_raw_string,
281 r#"
282 fn f() {
283 let s = "foo<|>
284 }
285 "#,
286 )
287 }
288
289 #[test]
290 fn make_usual_string_not_works_on_partial_string() {
291 check_assist_not_applicable(
292 make_usual_string,
293 r#"
294 fn main() {
295 let s = r#"bar<|>
296 }
297 "#,
298 )
299 }
300
301 #[test]
215 fn add_hash_target() { 302 fn add_hash_target() {
216 check_assist_target( 303 check_assist_target(
217 add_hash, 304 add_hash,
diff --git a/crates/ra_assists/src/assists/remove_dbg.rs b/crates/ra_assists/src/assists/remove_dbg.rs
index 1a7e2b305..aedf8747f 100644
--- a/crates/ra_assists/src/assists/remove_dbg.rs
+++ b/crates/ra_assists/src/assists/remove_dbg.rs
@@ -1,14 +1,28 @@
1//! FIXME: write short doc here
2
3use crate::{Assist, AssistCtx, AssistId};
4use hir::db::HirDatabase; 1use hir::db::HirDatabase;
5use ra_syntax::{ 2use ra_syntax::{
6 ast::{self, AstNode}, 3 ast::{self, AstNode},
7 TextUnit, T, 4 TextUnit, T,
8}; 5};
9 6
10pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 7use crate::{Assist, AssistCtx, AssistId};
11 let macro_call = ctx.node_at_offset::<ast::MacroCall>()?; 8
9// Assist: remove_dbg
10//
11// Removes `dbg!()` macro call.
12//
13// ```
14// fn main() {
15// <|>dbg!(92);
16// }
17// ```
18// ->
19// ```
20// fn main() {
21// 92;
22// }
23// ```
24pub(crate) fn remove_dbg(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
25 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
12 26
13 if !is_valid_macrocall(&macro_call, "dbg")? { 27 if !is_valid_macrocall(&macro_call, "dbg")? {
14 return None; 28 return None;
@@ -44,13 +58,11 @@ pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist>
44 text.slice(without_parens).to_string() 58 text.slice(without_parens).to_string()
45 }; 59 };
46 60
47 ctx.add_action(AssistId("remove_dbg"), "remove dbg!()", |edit| { 61 ctx.add_assist(AssistId("remove_dbg"), "remove dbg!()", |edit| {
48 edit.target(macro_call.syntax().text_range()); 62 edit.target(macro_call.syntax().text_range());
49 edit.replace(macro_range, macro_content); 63 edit.replace(macro_range, macro_content);
50 edit.set_cursor(cursor_pos); 64 edit.set_cursor(cursor_pos);
51 }); 65 })
52
53 ctx.build()
54} 66}
55 67
56/// 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 749ff338a..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
@@ -1,5 +1,3 @@
1//! FIXME: write short doc here
2
3use format_buf::format; 1use format_buf::format;
4use hir::db::HirDatabase; 2use hir::db::HirDatabase;
5use ra_fmt::extract_trivial_expression; 3use ra_fmt::extract_trivial_expression;
@@ -7,8 +5,34 @@ use ra_syntax::{ast, AstNode};
7 5
8use crate::{Assist, AssistCtx, AssistId}; 6use crate::{Assist, AssistCtx, AssistId};
9 7
10pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 8// Assist: replace_if_let_with_match
11 let if_expr: ast::IfExpr = ctx.node_at_offset()?; 9//
10// Replaces `if let` with an else branch with a `match` expression.
11//
12// ```
13// enum Action { Move { distance: u32 }, Stop }
14//
15// fn handle(action: Action) {
16// <|>if let Action::Move { distance } = action {
17// foo(distance)
18// } else {
19// bar()
20// }
21// }
22// ```
23// ->
24// ```
25// enum Action { Move { distance: u32 }, Stop }
26//
27// fn handle(action: Action) {
28// match action {
29// Action::Move { distance } => foo(distance),
30// _ => bar(),
31// }
32// }
33// ```
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()?;
12 let cond = if_expr.condition()?; 36 let cond = if_expr.condition()?;
13 let pat = cond.pat()?; 37 let pat = cond.pat()?;
14 let expr = cond.expr()?; 38 let expr = cond.expr()?;
@@ -18,14 +42,12 @@ pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx<impl HirDatabase>) ->
18 ast::ElseBranch::IfExpr(_) => return None, 42 ast::ElseBranch::IfExpr(_) => return None,
19 }; 43 };
20 44
21 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| {
22 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);
23 edit.target(if_expr.syntax().text_range()); 47 edit.target(if_expr.syntax().text_range());
24 edit.replace_node_and_indent(if_expr.syntax(), match_expr); 48 edit.replace_node_and_indent(if_expr.syntax(), match_expr);
25 edit.set_cursor(if_expr.syntax().text_range().start()) 49 edit.set_cursor(if_expr.syntax().text_range().start())
26 }); 50 })
27
28 ctx.build()
29} 51}
30 52
31fn 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 fe3e64af5..5f8d6b0be 100644
--- a/crates/ra_assists/src/assists/split_import.rs
+++ b/crates/ra_assists/src/assists/split_import.rs
@@ -1,5 +1,3 @@
1//! FIXME: write short doc here
2
3use std::iter::successors; 1use std::iter::successors;
4 2
5use hir::db::HirDatabase; 3use hir::db::HirDatabase;
@@ -7,8 +5,19 @@ use ra_syntax::{ast, AstNode, TextUnit, T};
7 5
8use crate::{Assist, AssistCtx, AssistId}; 6use crate::{Assist, AssistCtx, AssistId};
9 7
10pub(crate) fn split_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 8// Assist: split_import
11 let colon_colon = ctx.token_at_offset().find(|leaf| leaf.kind() == T![::])?; 9//
10// Wraps the tail of import into braces.
11//
12// ```
13// use std::<|>collections::HashMap;
14// ```
15// ->
16// ```
17// use std::{collections::HashMap};
18// ```
19pub(crate) fn split_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
20 let colon_colon = ctx.find_token_at_offset(T![::])?;
12 let path = ast::Path::cast(colon_colon.parent())?; 21 let path = ast::Path::cast(colon_colon.parent())?;
13 let top_path = successors(Some(path), |it| it.parent_path()).last()?; 22 let top_path = successors(Some(path), |it| it.parent_path()).last()?;
14 23
@@ -23,14 +32,12 @@ pub(crate) fn split_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assis
23 None => top_path.syntax().text_range().end(), 32 None => top_path.syntax().text_range().end(),
24 }; 33 };
25 34
26 ctx.add_action(AssistId("split_import"), "split import", |edit| { 35 ctx.add_assist(AssistId("split_import"), "split import", |edit| {
27 edit.target(colon_colon.text_range()); 36 edit.target(colon_colon.text_range());
28 edit.insert(l_curly, "{"); 37 edit.insert(l_curly, "{");
29 edit.insert(r_curly, "}"); 38 edit.insert(r_curly, "}");
30 edit.set_cursor(l_curly + TextUnit::of_str("{")); 39 edit.set_cursor(l_curly + TextUnit::of_str("{"));
31 }); 40 })
32
33 ctx.build()
34} 41}
35 42
36#[cfg(test)] 43#[cfg(test)]
diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs
new file mode 100644
index 000000000..6e1e3de84
--- /dev/null
+++ b/crates/ra_assists/src/doc_tests.rs
@@ -0,0 +1,34 @@
1//! Each assist definition has a special comment, which specifies docs and
2//! example.
3//!
4//! We collect all the example and write the as tests in this module.
5
6mod generated;
7
8use hir::mock::MockDatabase;
9use ra_db::FileRange;
10use test_utils::{assert_eq_text, extract_range_or_offset};
11
12fn check(assist_id: &str, before: &str, after: &str) {
13 let (selection, before) = extract_range_or_offset(before);
14 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
15 let frange = FileRange { file_id, range: selection.into() };
16
17 let (_assist_id, action) = crate::assists(&db, frange)
18 .into_iter()
19 .find(|(id, _)| id.id.0 == 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 });
31
32 let actual = action.edit.apply(&before);
33 assert_eq_text!(after, &actual);
34}
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
new file mode 100644
index 000000000..1bee76f59
--- /dev/null
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -0,0 +1,526 @@
1//! Generated file, do not edit by hand, see `crate/ra_tools/src/codegen`
2
3use super::check;
4
5#[test]
6fn doctest_add_derive() {
7 check(
8 "add_derive",
9 r#####"
10struct Point {
11 x: u32,
12 y: u32,<|>
13}
14"#####,
15 r#####"
16#[derive()]
17struct Point {
18 x: u32,
19 y: u32,
20}
21"#####,
22 )
23}
24
25#[test]
26fn doctest_add_explicit_type() {
27 check(
28 "add_explicit_type",
29 r#####"
30fn main() {
31 let x<|> = 92;
32}
33"#####,
34 r#####"
35fn main() {
36 let x: i32 = 92;
37}
38"#####,
39 )
40}
41
42#[test]
43fn doctest_add_hash() {
44 check(
45 "add_hash",
46 r#####"
47fn main() {
48 r#"Hello,<|> World!"#;
49}
50"#####,
51 r#####"
52fn main() {
53 r##"Hello, World!"##;
54}
55"#####,
56 )
57}
58
59#[test]
60fn doctest_add_impl() {
61 check(
62 "add_impl",
63 r#####"
64struct Ctx<T: Clone> {
65 data: T,<|>
66}
67"#####,
68 r#####"
69struct Ctx<T: Clone> {
70 data: T,
71}
72
73impl<T: Clone> Ctx<T> {
74
75}
76"#####,
77 )
78}
79
80#[test]
81fn doctest_add_impl_default_members() {
82 check(
83 "add_impl_default_members",
84 r#####"
85trait T {
86 Type X;
87 fn foo(&self);
88 fn bar(&self) {}
89}
90
91impl T for () {
92 Type X = ();
93 fn foo(&self) {}<|>
94
95}
96"#####,
97 r#####"
98trait T {
99 Type X;
100 fn foo(&self);
101 fn bar(&self) {}
102}
103
104impl T for () {
105 Type X = ();
106 fn foo(&self) {}
107 fn bar(&self) {}
108
109}
110"#####,
111 )
112}
113
114#[test]
115fn doctest_add_impl_missing_members() {
116 check(
117 "add_impl_missing_members",
118 r#####"
119trait T {
120 Type X;
121 fn foo(&self);
122 fn bar(&self) {}
123}
124
125impl T for () {<|>
126
127}
128"#####,
129 r#####"
130trait T {
131 Type X;
132 fn foo(&self);
133 fn bar(&self) {}
134}
135
136impl T for () {
137 fn foo(&self) { unimplemented!() }
138
139}
140"#####,
141 )
142}
143
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]
160fn doctest_apply_demorgan() {
161 check(
162 "apply_demorgan",
163 r#####"
164fn main() {
165 if x != 4 ||<|> !y {}
166}
167"#####,
168 r#####"
169fn main() {
170 if !(x == 4 && y) {}
171}
172"#####,
173 )
174}
175
176#[test]
177fn doctest_change_visibility() {
178 check(
179 "change_visibility",
180 r#####"
181<|>fn frobnicate() {}
182"#####,
183 r#####"
184pub(crate) fn frobnicate() {}
185"#####,
186 )
187}
188
189#[test]
190fn doctest_convert_to_guarded_return() {
191 check(
192 "convert_to_guarded_return",
193 r#####"
194fn main() {
195 <|>if cond {
196 foo();
197 bar();
198 }
199}
200"#####,
201 r#####"
202fn main() {
203 if !cond {
204 return;
205 }
206 foo();
207 bar();
208}
209"#####,
210 )
211}
212
213#[test]
214fn doctest_fill_match_arms() {
215 check(
216 "fill_match_arms",
217 r#####"
218enum Action { Move { distance: u32 }, Stop }
219
220fn handle(action: Action) {
221 match action {
222 <|>
223 }
224}
225"#####,
226 r#####"
227enum Action { Move { distance: u32 }, Stop }
228
229fn handle(action: Action) {
230 match action {
231 Action::Move { distance } => (),
232 Action::Stop => (),
233 }
234}
235"#####,
236 )
237}
238
239#[test]
240fn doctest_flip_binexpr() {
241 check(
242 "flip_binexpr",
243 r#####"
244fn main() {
245 let _ = 90 +<|> 2;
246}
247"#####,
248 r#####"
249fn main() {
250 let _ = 2 + 90;
251}
252"#####,
253 )
254}
255
256#[test]
257fn doctest_flip_comma() {
258 check(
259 "flip_comma",
260 r#####"
261fn main() {
262 ((1, 2),<|> (3, 4));
263}
264"#####,
265 r#####"
266fn main() {
267 ((3, 4), (1, 2));
268}
269"#####,
270 )
271}
272
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]
287fn doctest_inline_local_variable() {
288 check(
289 "inline_local_variable",
290 r#####"
291fn main() {
292 let x<|> = 1 + 2;
293 x * 4;
294}
295"#####,
296 r#####"
297fn main() {
298 (1 + 2) * 4;
299}
300"#####,
301 )
302}
303
304#[test]
305fn doctest_introduce_variable() {
306 check(
307 "introduce_variable",
308 r#####"
309fn main() {
310 <|>(1 + 2)<|> * 4;
311}
312"#####,
313 r#####"
314fn main() {
315 let var_name = (1 + 2);
316 var_name * 4;
317}
318"#####,
319 )
320}
321
322#[test]
323fn doctest_make_raw_string() {
324 check(
325 "make_raw_string",
326 r#####"
327fn main() {
328 "Hello,<|> World!";
329}
330"#####,
331 r#####"
332fn main() {
333 r#"Hello, World!"#;
334}
335"#####,
336 )
337}
338
339#[test]
340fn doctest_make_usual_string() {
341 check(
342 "make_usual_string",
343 r#####"
344fn main() {
345 r#"Hello,<|> "World!""#;
346}
347"#####,
348 r#####"
349fn main() {
350 "Hello, \"World!\"";
351}
352"#####,
353 )
354}
355
356#[test]
357fn doctest_merge_match_arms() {
358 check(
359 "merge_match_arms",
360 r#####"
361enum Action { Move { distance: u32 }, Stop }
362
363fn handle(action: Action) {
364 match action {
365 <|>Action::Move(..) => foo(),
366 Action::Stop => foo(),
367 }
368}
369"#####,
370 r#####"
371enum Action { Move { distance: u32 }, Stop }
372
373fn handle(action: Action) {
374 match action {
375 Action::Move(..) | Action::Stop => foo(),
376 }
377}
378"#####,
379 )
380}
381
382#[test]
383fn doctest_move_arm_cond_to_match_guard() {
384 check(
385 "move_arm_cond_to_match_guard",
386 r#####"
387enum Action { Move { distance: u32 }, Stop }
388
389fn handle(action: Action) {
390 match action {
391 Action::Move { distance } => <|>if distance > 10 { foo() },
392 _ => (),
393 }
394}
395"#####,
396 r#####"
397enum Action { Move { distance: u32 }, Stop }
398
399fn handle(action: Action) {
400 match action {
401 Action::Move { distance } if distance > 10 => foo(),
402 _ => (),
403 }
404}
405"#####,
406 )
407}
408
409#[test]
410fn doctest_move_bounds_to_where_clause() {
411 check(
412 "move_bounds_to_where_clause",
413 r#####"
414fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U {
415 f(x)
416}
417"#####,
418 r#####"
419fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
420 f(x)
421}
422"#####,
423 )
424}
425
426#[test]
427fn doctest_move_guard_to_arm_body() {
428 check(
429 "move_guard_to_arm_body",
430 r#####"
431enum Action { Move { distance: u32 }, Stop }
432
433fn handle(action: Action) {
434 match action {
435 Action::Move { distance } <|>if distance > 10 => foo(),
436 _ => (),
437 }
438}
439"#####,
440 r#####"
441enum Action { Move { distance: u32 }, Stop }
442
443fn handle(action: Action) {
444 match action {
445 Action::Move { distance } => if distance > 10 { foo() },
446 _ => (),
447 }
448}
449"#####,
450 )
451}
452
453#[test]
454fn doctest_remove_dbg() {
455 check(
456 "remove_dbg",
457 r#####"
458fn main() {
459 <|>dbg!(92);
460}
461"#####,
462 r#####"
463fn main() {
464 92;
465}
466"#####,
467 )
468}
469
470#[test]
471fn doctest_remove_hash() {
472 check(
473 "remove_hash",
474 r#####"
475fn main() {
476 r#"Hello,<|> World!"#;
477}
478"#####,
479 r#####"
480fn main() {
481 r"Hello, World!";
482}
483"#####,
484 )
485}
486
487#[test]
488fn doctest_replace_if_let_with_match() {
489 check(
490 "replace_if_let_with_match",
491 r#####"
492enum Action { Move { distance: u32 }, Stop }
493
494fn handle(action: Action) {
495 <|>if let Action::Move { distance } = action {
496 foo(distance)
497 } else {
498 bar()
499 }
500}
501"#####,
502 r#####"
503enum Action { Move { distance: u32 }, Stop }
504
505fn handle(action: Action) {
506 match action {
507 Action::Move { distance } => foo(distance),
508 _ => bar(),
509 }
510}
511"#####,
512 )
513}
514
515#[test]
516fn doctest_split_import() {
517 check(
518 "split_import",
519 r#####"
520use std::<|>collections::HashMap;
521"#####,
522 r#####"
523use std::{collections::HashMap};
524"#####,
525 )
526}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index ab77b46a9..38599d4f1 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -7,15 +7,16 @@
7 7
8mod assist_ctx; 8mod assist_ctx;
9mod marks; 9mod marks;
10#[cfg(test)]
11mod doc_tests;
10 12
11use hir::db::HirDatabase; 13use hir::db::HirDatabase;
12use itertools::Itertools;
13use ra_db::FileRange; 14use ra_db::FileRange;
14use ra_syntax::{TextRange, TextUnit}; 15use ra_syntax::{TextRange, TextUnit};
15use ra_text_edit::TextEdit; 16use ra_text_edit::TextEdit;
16 17
17pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; 18pub(crate) use crate::assist_ctx::{Assist, AssistCtx};
18pub use crate::assists::auto_import::auto_import_text_edit; 19pub use crate::assists::add_import::auto_import_text_edit;
19 20
20/// 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
21/// directly. 22/// directly.
@@ -36,7 +37,7 @@ pub struct AssistAction {
36 pub target: Option<TextRange>, 37 pub target: Option<TextRange>,
37} 38}
38 39
39/// Return all the assists eapplicable at the given position. 40/// Return all the assists applicable at the given position.
40/// 41///
41/// Assists are returned in the "unresolved" state, that is only labels are 42/// Assists are returned in the "unresolved" state, that is only labels are
42/// returned, without actual edits. 43/// returned, without actual edits.
@@ -49,10 +50,10 @@ where
49 .iter() 50 .iter()
50 .filter_map(|f| f(ctx.clone())) 51 .filter_map(|f| f(ctx.clone()))
51 .map(|a| match a { 52 .map(|a| match a {
52 Assist::Unresolved(labels) => labels, 53 Assist::Unresolved { label } => label,
53 Assist::Resolved(..) => unreachable!(), 54 Assist::Resolved { .. } => unreachable!(),
54 }) 55 })
55 .concat() 56 .collect()
56 }) 57 })
57} 58}
58 59
@@ -71,10 +72,10 @@ where
71 .iter() 72 .iter()
72 .filter_map(|f| f(ctx.clone())) 73 .filter_map(|f| f(ctx.clone()))
73 .map(|a| match a { 74 .map(|a| match a {
74 Assist::Resolved(labels_actions) => labels_actions, 75 Assist::Resolved { label, action } => (label, action),
75 Assist::Unresolved(..) => unreachable!(), 76 Assist::Unresolved { .. } => unreachable!(),
76 }) 77 })
77 .concat(); 78 .collect::<Vec<_>>();
78 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) {
79 (Some(a), Some(b)) => a.len().cmp(&b.len()), 80 (Some(a), Some(b)) => a.len().cmp(&b.len()),
80 (Some(_), None) => Ordering::Less, 81 (Some(_), None) => Ordering::Less,
@@ -95,6 +96,7 @@ mod assists {
95 mod apply_demorgan; 96 mod apply_demorgan;
96 mod flip_comma; 97 mod flip_comma;
97 mod flip_binexpr; 98 mod flip_binexpr;
99 mod flip_trait_bound;
98 mod change_visibility; 100 mod change_visibility;
99 mod fill_match_arms; 101 mod fill_match_arms;
100 mod merge_match_arms; 102 mod merge_match_arms;
@@ -104,7 +106,7 @@ mod assists {
104 mod replace_if_let_with_match; 106 mod replace_if_let_with_match;
105 mod split_import; 107 mod split_import;
106 mod remove_dbg; 108 mod remove_dbg;
107 pub(crate) mod auto_import; 109 pub(crate) mod add_import;
108 mod add_missing_impl_members; 110 mod add_missing_impl_members;
109 mod move_guard; 111 mod move_guard;
110 mod move_bounds; 112 mod move_bounds;
@@ -121,11 +123,12 @@ mod assists {
121 merge_match_arms::merge_match_arms, 123 merge_match_arms::merge_match_arms,
122 flip_comma::flip_comma, 124 flip_comma::flip_comma,
123 flip_binexpr::flip_binexpr, 125 flip_binexpr::flip_binexpr,
126 flip_trait_bound::flip_trait_bound,
124 introduce_variable::introduce_variable, 127 introduce_variable::introduce_variable,
125 replace_if_let_with_match::replace_if_let_with_match, 128 replace_if_let_with_match::replace_if_let_with_match,
126 split_import::split_import, 129 split_import::split_import,
127 remove_dbg::remove_dbg, 130 remove_dbg::remove_dbg,
128 auto_import::auto_import, 131 add_import::add_import,
129 add_missing_impl_members::add_missing_impl_members, 132 add_missing_impl_members::add_missing_impl_members,
130 add_missing_impl_members::add_missing_default_members, 133 add_missing_impl_members::add_missing_default_members,
131 inline_local_variable::inline_local_varialbe, 134 inline_local_variable::inline_local_varialbe,
@@ -155,51 +158,17 @@ mod helpers {
155 before: &str, 158 before: &str,
156 after: &str, 159 after: &str,
157 ) { 160 ) {
158 check_assist_nth_action(assist, before, after, 0)
159 }
160
161 pub(crate) fn check_assist_range(
162 assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
163 before: &str,
164 after: &str,
165 ) {
166 check_assist_range_nth_action(assist, before, after, 0)
167 }
168
169 pub(crate) fn check_assist_target(
170 assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
171 before: &str,
172 target: &str,
173 ) {
174 check_assist_target_nth_action(assist, before, target, 0)
175 }
176
177 pub(crate) fn check_assist_range_target(
178 assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
179 before: &str,
180 target: &str,
181 ) {
182 check_assist_range_target_nth_action(assist, before, target, 0)
183 }
184
185 pub(crate) fn check_assist_nth_action(
186 assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
187 before: &str,
188 after: &str,
189 index: usize,
190 ) {
191 let (before_cursor_pos, before) = extract_offset(before); 161 let (before_cursor_pos, before) = extract_offset(before);
192 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); 162 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
193 let frange = 163 let frange =
194 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()) };
195 let assist = 165 let assist =
196 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");
197 let labels_actions = match assist { 167 let action = match assist {
198 Assist::Unresolved(_) => unreachable!(), 168 Assist::Unresolved { .. } => unreachable!(),
199 Assist::Resolved(labels_actions) => labels_actions, 169 Assist::Resolved { action, .. } => action,
200 }; 170 };
201 171
202 let (_, action) = labels_actions.get(index).expect("expect assist action at index");
203 let actual = action.edit.apply(&before); 172 let actual = action.edit.apply(&before);
204 let actual_cursor_pos = match action.cursor_position { 173 let actual_cursor_pos = match action.cursor_position {
205 None => action 174 None => action
@@ -212,23 +181,21 @@ mod helpers {
212 assert_eq_text!(after, &actual); 181 assert_eq_text!(after, &actual);
213 } 182 }
214 183
215 pub(crate) fn check_assist_range_nth_action( 184 pub(crate) fn check_assist_range(
216 assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, 185 assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
217 before: &str, 186 before: &str,
218 after: &str, 187 after: &str,
219 index: usize,
220 ) { 188 ) {
221 let (range, before) = extract_range(before); 189 let (range, before) = extract_range(before);
222 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); 190 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
223 let frange = FileRange { file_id, range }; 191 let frange = FileRange { file_id, range };
224 let assist = 192 let assist =
225 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");
226 let labels_actions = match assist { 194 let action = match assist {
227 Assist::Unresolved(_) => unreachable!(), 195 Assist::Unresolved { .. } => unreachable!(),
228 Assist::Resolved(labels_actions) => labels_actions, 196 Assist::Resolved { action, .. } => action,
229 }; 197 };
230 198
231 let (_, action) = labels_actions.get(index).expect("expect assist action at index");
232 let mut actual = action.edit.apply(&before); 199 let mut actual = action.edit.apply(&before);
233 if let Some(pos) = action.cursor_position { 200 if let Some(pos) = action.cursor_position {
234 actual = add_cursor(&actual, pos); 201 actual = add_cursor(&actual, pos);
@@ -236,11 +203,10 @@ mod helpers {
236 assert_eq_text!(after, &actual); 203 assert_eq_text!(after, &actual);
237 } 204 }
238 205
239 pub(crate) fn check_assist_target_nth_action( 206 pub(crate) fn check_assist_target(
240 assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, 207 assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
241 before: &str, 208 before: &str,
242 target: &str, 209 target: &str,
243 index: usize,
244 ) { 210 ) {
245 let (before_cursor_pos, before) = extract_offset(before); 211 let (before_cursor_pos, before) = extract_offset(before);
246 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); 212 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
@@ -248,33 +214,30 @@ mod helpers {
248 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()) };
249 let assist = 215 let assist =
250 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");
251 let labels_actions = match assist { 217 let action = match assist {
252 Assist::Unresolved(_) => unreachable!(), 218 Assist::Unresolved { .. } => unreachable!(),
253 Assist::Resolved(labels_actions) => labels_actions, 219 Assist::Resolved { action, .. } => action,
254 }; 220 };
255 221
256 let (_, action) = labels_actions.get(index).expect("expect assist action at index");
257 let range = action.target.expect("expected target on action"); 222 let range = action.target.expect("expected target on action");
258 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);
259 } 224 }
260 225
261 pub(crate) fn check_assist_range_target_nth_action( 226 pub(crate) fn check_assist_range_target(
262 assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>, 227 assist: fn(AssistCtx<MockDatabase>) -> Option<Assist>,
263 before: &str, 228 before: &str,
264 target: &str, 229 target: &str,
265 index: usize,
266 ) { 230 ) {
267 let (range, before) = extract_range(before); 231 let (range, before) = extract_range(before);
268 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); 232 let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
269 let frange = FileRange { file_id, range }; 233 let frange = FileRange { file_id, range };
270 let assist = 234 let assist =
271 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");
272 let labels_actions = match assist { 236 let action = match assist {
273 Assist::Unresolved(_) => unreachable!(), 237 Assist::Unresolved { .. } => unreachable!(),
274 Assist::Resolved(labels_actions) => labels_actions, 238 Assist::Resolved { action, .. } => action,
275 }; 239 };
276 240
277 let (_, action) = labels_actions.get(index).expect("expect assist action at index");
278 let range = action.target.expect("expected target on action"); 241 let range = action.target.expect("expected target on action");
279 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);
280 } 243 }