aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r--crates/ra_assists/src/assist_config.rs27
-rw-r--r--crates/ra_assists/src/assist_context.rs51
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs51
-rw-r--r--crates/ra_assists/src/handlers/add_derive.rs28
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs5
-rw-r--r--crates/ra_assists/src/handlers/add_from_impl_for_enum.rs7
-rw-r--r--crates/ra_assists/src/handlers/add_function.rs145
-rw-r--r--crates/ra_assists/src/handlers/add_impl.rs34
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs98
-rw-r--r--crates/ra_assists/src/handlers/add_turbo_fish.rs134
-rw-r--r--crates/ra_assists/src/handlers/replace_unwrap_with_match.rs2
-rw-r--r--crates/ra_assists/src/lib.rs17
-rw-r--r--crates/ra_assists/src/marks.rs2
-rw-r--r--crates/ra_assists/src/tests.rs38
-rw-r--r--crates/ra_assists/src/tests/generated.rs35
-rw-r--r--crates/ra_assists/src/utils.rs43
16 files changed, 520 insertions, 197 deletions
diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs
new file mode 100644
index 000000000..c0a0226fb
--- /dev/null
+++ b/crates/ra_assists/src/assist_config.rs
@@ -0,0 +1,27 @@
1//! Settings for tweaking assists.
2//!
3//! The fun thing here is `SnippetCap` -- this type can only be created in this
4//! module, and we use to statically check that we only produce snippet
5//! assists if we are allowed to.
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct AssistConfig {
9 pub snippet_cap: Option<SnippetCap>,
10}
11
12impl AssistConfig {
13 pub fn allow_snippets(&mut self, yes: bool) {
14 self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None }
15 }
16}
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct SnippetCap {
20 _private: (),
21}
22
23impl Default for AssistConfig {
24 fn default() -> Self {
25 AssistConfig { snippet_cap: Some(SnippetCap { _private: () }) }
26 }
27}
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs
index a680f752b..005c17776 100644
--- a/crates/ra_assists/src/assist_context.rs
+++ b/crates/ra_assists/src/assist_context.rs
@@ -15,7 +15,10 @@ use ra_syntax::{
15}; 15};
16use ra_text_edit::TextEditBuilder; 16use ra_text_edit::TextEditBuilder;
17 17
18use crate::{Assist, AssistId, GroupLabel, ResolvedAssist}; 18use crate::{
19 assist_config::{AssistConfig, SnippetCap},
20 Assist, AssistId, GroupLabel, ResolvedAssist,
21};
19 22
20/// `AssistContext` allows to apply an assist or check if it could be applied. 23/// `AssistContext` allows to apply an assist or check if it could be applied.
21/// 24///
@@ -48,6 +51,7 @@ use crate::{Assist, AssistId, GroupLabel, ResolvedAssist};
48/// moment, because the LSP API is pretty awkward in this place, and it's much 51/// moment, because the LSP API is pretty awkward in this place, and it's much
49/// easier to just compute the edit eagerly :-) 52/// easier to just compute the edit eagerly :-)
50pub(crate) struct AssistContext<'a> { 53pub(crate) struct AssistContext<'a> {
54 pub(crate) config: &'a AssistConfig,
51 pub(crate) sema: Semantics<'a, RootDatabase>, 55 pub(crate) sema: Semantics<'a, RootDatabase>,
52 pub(crate) db: &'a RootDatabase, 56 pub(crate) db: &'a RootDatabase,
53 pub(crate) frange: FileRange, 57 pub(crate) frange: FileRange,
@@ -55,10 +59,14 @@ pub(crate) struct AssistContext<'a> {
55} 59}
56 60
57impl<'a> AssistContext<'a> { 61impl<'a> AssistContext<'a> {
58 pub fn new(sema: Semantics<'a, RootDatabase>, frange: FileRange) -> AssistContext<'a> { 62 pub(crate) fn new(
63 sema: Semantics<'a, RootDatabase>,
64 config: &'a AssistConfig,
65 frange: FileRange,
66 ) -> AssistContext<'a> {
59 let source_file = sema.parse(frange.file_id); 67 let source_file = sema.parse(frange.file_id);
60 let db = sema.db; 68 let db = sema.db;
61 AssistContext { sema, db, frange, source_file } 69 AssistContext { config, sema, db, frange, source_file }
62 } 70 }
63 71
64 // NB, this ignores active selection. 72 // NB, this ignores active selection.
@@ -165,11 +173,17 @@ pub(crate) struct AssistBuilder {
165 edit: TextEditBuilder, 173 edit: TextEditBuilder,
166 cursor_position: Option<TextSize>, 174 cursor_position: Option<TextSize>,
167 file: FileId, 175 file: FileId,
176 is_snippet: bool,
168} 177}
169 178
170impl AssistBuilder { 179impl AssistBuilder {
171 pub(crate) fn new(file: FileId) -> AssistBuilder { 180 pub(crate) fn new(file: FileId) -> AssistBuilder {
172 AssistBuilder { edit: TextEditBuilder::default(), cursor_position: None, file } 181 AssistBuilder {
182 edit: TextEditBuilder::default(),
183 cursor_position: None,
184 file,
185 is_snippet: false,
186 }
173 } 187 }
174 188
175 /// Remove specified `range` of text. 189 /// Remove specified `range` of text.
@@ -180,10 +194,30 @@ impl AssistBuilder {
180 pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) { 194 pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
181 self.edit.insert(offset, text.into()) 195 self.edit.insert(offset, text.into())
182 } 196 }
197 /// Append specified `snippet` at the given `offset`
198 pub(crate) fn insert_snippet(
199 &mut self,
200 _cap: SnippetCap,
201 offset: TextSize,
202 snippet: impl Into<String>,
203 ) {
204 self.is_snippet = true;
205 self.insert(offset, snippet);
206 }
183 /// Replaces specified `range` of text with a given string. 207 /// Replaces specified `range` of text with a given string.
184 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { 208 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
185 self.edit.replace(range, replace_with.into()) 209 self.edit.replace(range, replace_with.into())
186 } 210 }
211 /// Replaces specified `range` of text with a given `snippet`.
212 pub(crate) fn replace_snippet(
213 &mut self,
214 _cap: SnippetCap,
215 range: TextRange,
216 snippet: impl Into<String>,
217 ) {
218 self.is_snippet = true;
219 self.replace(range, snippet);
220 }
187 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { 221 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
188 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) 222 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
189 } 223 }
@@ -227,7 +261,12 @@ impl AssistBuilder {
227 if edit.is_empty() && self.cursor_position.is_none() { 261 if edit.is_empty() && self.cursor_position.is_none() {
228 panic!("Only call `add_assist` if the assist can be applied") 262 panic!("Only call `add_assist` if the assist can be applied")
229 } 263 }
230 SingleFileChange { label: change_label, edit, cursor_position: self.cursor_position } 264 let mut res =
231 .into_source_change(self.file) 265 SingleFileChange { label: change_label, edit, cursor_position: self.cursor_position }
266 .into_source_change(self.file);
267 if self.is_snippet {
268 res.is_snippet = true;
269 }
270 res
232 } 271 }
233} 272}
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
index 2baeb8607..fa70c8496 100644
--- a/crates/ra_assists/src/handlers/add_custom_impl.rs
+++ b/crates/ra_assists/src/handlers/add_custom_impl.rs
@@ -25,7 +25,7 @@ use crate::{
25// struct S; 25// struct S;
26// 26//
27// impl Debug for S { 27// impl Debug for S {
28// 28// $0
29// } 29// }
30// ``` 30// ```
31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@@ -52,7 +52,7 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); 52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);
53 53
54 let target = attr.syntax().text_range(); 54 let target = attr.syntax().text_range();
55 acc.add(AssistId("add_custom_impl"), label, target, |edit| { 55 acc.add(AssistId("add_custom_impl"), label, target, |builder| {
56 let new_attr_input = input 56 let new_attr_input = input
57 .syntax() 57 .syntax()
58 .descendants_with_tokens() 58 .descendants_with_tokens()
@@ -63,20 +63,11 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
63 let has_more_derives = !new_attr_input.is_empty(); 63 let has_more_derives = !new_attr_input.is_empty();
64 let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string(); 64 let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string();
65 65
66 let mut buf = String::new(); 66 if has_more_derives {
67 buf.push_str("\n\nimpl "); 67 builder.replace(input.syntax().text_range(), new_attr_input);
68 buf.push_str(trait_token.text().as_str());
69 buf.push_str(" for ");
70 buf.push_str(annotated_name.as_str());
71 buf.push_str(" {\n");
72
73 let cursor_delta = if has_more_derives {
74 let delta = input.syntax().text_range().len() - TextSize::of(&new_attr_input);
75 edit.replace(input.syntax().text_range(), new_attr_input);
76 delta
77 } else { 68 } else {
78 let attr_range = attr.syntax().text_range(); 69 let attr_range = attr.syntax().text_range();
79 edit.delete(attr_range); 70 builder.delete(attr_range);
80 71
81 let line_break_range = attr 72 let line_break_range = attr
82 .syntax() 73 .syntax()
@@ -84,14 +75,24 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
84 .filter(|t| t.kind() == WHITESPACE) 75 .filter(|t| t.kind() == WHITESPACE)
85 .map(|t| t.text_range()) 76 .map(|t| t.text_range())
86 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0))); 77 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
87 edit.delete(line_break_range); 78 builder.delete(line_break_range);
88 79 }
89 attr_range.len() + line_break_range.len() 80
90 }; 81 match ctx.config.snippet_cap {
91 82 Some(cap) => {
92 edit.set_cursor(start_offset + TextSize::of(&buf) - cursor_delta); 83 builder.insert_snippet(
93 buf.push_str("\n}"); 84 cap,
94 edit.insert(start_offset, buf); 85 start_offset,
86 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name),
87 );
88 }
89 None => {
90 builder.insert(
91 start_offset,
92 format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name),
93 );
94 }
95 }
95 }) 96 })
96} 97}
97 98
@@ -117,7 +118,7 @@ struct Foo {
117} 118}
118 119
119impl Debug for Foo { 120impl Debug for Foo {
120<|> 121 $0
121} 122}
122 ", 123 ",
123 ) 124 )
@@ -139,7 +140,7 @@ pub struct Foo {
139} 140}
140 141
141impl Debug for Foo { 142impl Debug for Foo {
142<|> 143 $0
143} 144}
144 ", 145 ",
145 ) 146 )
@@ -158,7 +159,7 @@ struct Foo {}
158struct Foo {} 159struct Foo {}
159 160
160impl Debug for Foo { 161impl Debug for Foo {
161<|> 162 $0
162} 163}
163 ", 164 ",
164 ) 165 )
diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs
index fb08c19e9..b123b8498 100644
--- a/crates/ra_assists/src/handlers/add_derive.rs
+++ b/crates/ra_assists/src/handlers/add_derive.rs
@@ -18,31 +18,37 @@ use crate::{AssistContext, AssistId, Assists};
18// ``` 18// ```
19// -> 19// ->
20// ``` 20// ```
21// #[derive()] 21// #[derive($0)]
22// struct Point { 22// struct Point {
23// x: u32, 23// x: u32,
24// y: u32, 24// y: u32,
25// } 25// }
26// ``` 26// ```
27pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 27pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let cap = ctx.config.snippet_cap?;
28 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; 29 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
29 let node_start = derive_insertion_offset(&nominal)?; 30 let node_start = derive_insertion_offset(&nominal)?;
30 let target = nominal.syntax().text_range(); 31 let target = nominal.syntax().text_range();
31 acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |edit| { 32 acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |builder| {
32 let derive_attr = nominal 33 let derive_attr = nominal
33 .attrs() 34 .attrs()
34 .filter_map(|x| x.as_simple_call()) 35 .filter_map(|x| x.as_simple_call())
35 .filter(|(name, _arg)| name == "derive") 36 .filter(|(name, _arg)| name == "derive")
36 .map(|(_name, arg)| arg) 37 .map(|(_name, arg)| arg)
37 .next(); 38 .next();
38 let offset = match derive_attr { 39 match derive_attr {
39 None => { 40 None => {
40 edit.insert(node_start, "#[derive()]\n"); 41 builder.insert_snippet(cap, node_start, "#[derive($0)]\n");
41 node_start + TextSize::of("#[derive(") 42 }
43 Some(tt) => {
44 // Just move the cursor.
45 builder.insert_snippet(
46 cap,
47 tt.syntax().text_range().end() - TextSize::of(')'),
48 "$0",
49 )
42 } 50 }
43 Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'),
44 }; 51 };
45 edit.set_cursor(offset)
46 }) 52 })
47} 53}
48 54
@@ -66,12 +72,12 @@ mod tests {
66 check_assist( 72 check_assist(
67 add_derive, 73 add_derive,
68 "struct Foo { a: i32, <|>}", 74 "struct Foo { a: i32, <|>}",
69 "#[derive(<|>)]\nstruct Foo { a: i32, }", 75 "#[derive($0)]\nstruct Foo { a: i32, }",
70 ); 76 );
71 check_assist( 77 check_assist(
72 add_derive, 78 add_derive,
73 "struct Foo { <|> a: i32, }", 79 "struct Foo { <|> a: i32, }",
74 "#[derive(<|>)]\nstruct Foo { a: i32, }", 80 "#[derive($0)]\nstruct Foo { a: i32, }",
75 ); 81 );
76 } 82 }
77 83
@@ -80,7 +86,7 @@ mod tests {
80 check_assist( 86 check_assist(
81 add_derive, 87 add_derive,
82 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", 88 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
83 "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", 89 "#[derive(Clone$0)]\nstruct Foo { a: i32, }",
84 ); 90 );
85 } 91 }
86 92
@@ -96,7 +102,7 @@ struct Foo { a: i32<|>, }
96 " 102 "
97/// `Foo` is a pretty important struct. 103/// `Foo` is a pretty important struct.
98/// It does stuff. 104/// It does stuff.
99#[derive(<|>)] 105#[derive($0)]
100struct Foo { a: i32, } 106struct Foo { a: i32, }
101 ", 107 ",
102 ); 108 );
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs
index 0c7d5e355..770049212 100644
--- a/crates/ra_assists/src/handlers/add_explicit_type.rs
+++ b/crates/ra_assists/src/handlers/add_explicit_type.rs
@@ -25,9 +25,8 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio
25 let stmt = ctx.find_node_at_offset::<LetStmt>()?; 25 let stmt = ctx.find_node_at_offset::<LetStmt>()?;
26 let module = ctx.sema.scope(stmt.syntax()).module()?; 26 let module = ctx.sema.scope(stmt.syntax()).module()?;
27 let expr = stmt.initializer()?; 27 let expr = stmt.initializer()?;
28 let pat = stmt.pat()?;
29 // Must be a binding 28 // Must be a binding
30 let pat = match pat { 29 let pat = match stmt.pat()? {
31 ast::Pat::BindPat(bind_pat) => bind_pat, 30 ast::Pat::BindPat(bind_pat) => bind_pat,
32 _ => return None, 31 _ => return None,
33 }; 32 };
@@ -46,7 +45,7 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio
46 // Assist not applicable if the type has already been specified 45 // Assist not applicable if the type has already been specified
47 // and it has no placeholders 46 // and it has no placeholders
48 let ascribed_ty = stmt.ascribed_type(); 47 let ascribed_ty = stmt.ascribed_type();
49 if let Some(ref ty) = ascribed_ty { 48 if let Some(ty) = &ascribed_ty {
50 if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() { 49 if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() {
51 return None; 50 return None;
52 } 51 }
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
index 6a49b7dbd..eb57b7231 100644
--- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
+++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
@@ -1,6 +1,5 @@
1use ra_ide_db::RootDatabase; 1use ra_ide_db::RootDatabase;
2use ra_syntax::ast::{self, AstNode, NameOwner}; 2use ra_syntax::ast::{self, AstNode, NameOwner};
3use stdx::format_to;
4use test_utils::tested_by; 3use test_utils::tested_by;
5 4
6use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; 5use crate::{utils::FamousDefs, AssistContext, AssistId, Assists};
@@ -35,7 +34,7 @@ pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) ->
35 } 34 }
36 let field_type = field_list.fields().next()?.type_ref()?; 35 let field_type = field_list.fields().next()?.type_ref()?;
37 let path = match field_type { 36 let path = match field_type {
38 ast::TypeRef::PathType(p) => p, 37 ast::TypeRef::PathType(it) => it,
39 _ => return None, 38 _ => return None,
40 }; 39 };
41 40
@@ -51,9 +50,7 @@ pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) ->
51 target, 50 target,
52 |edit| { 51 |edit| {
53 let start_offset = variant.parent_enum().syntax().text_range().end(); 52 let start_offset = variant.parent_enum().syntax().text_range().end();
54 let mut buf = String::new(); 53 let buf = format!(
55 format_to!(
56 buf,
57 r#" 54 r#"
58 55
59impl From<{0}> for {1} {{ 56impl From<{0}> for {1} {{
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs
index de016ae4e..24f931a85 100644
--- a/crates/ra_assists/src/handlers/add_function.rs
+++ b/crates/ra_assists/src/handlers/add_function.rs
@@ -4,13 +4,17 @@ use ra_syntax::{
4 ast::{ 4 ast::{
5 self, 5 self,
6 edit::{AstNodeEdit, IndentLevel}, 6 edit::{AstNodeEdit, IndentLevel},
7 ArgListOwner, AstNode, ModuleItemOwner, 7 make, ArgListOwner, AstNode, ModuleItemOwner,
8 }, 8 },
9 SyntaxKind, SyntaxNode, TextSize, 9 SyntaxKind, SyntaxNode, TextSize,
10}; 10};
11use rustc_hash::{FxHashMap, FxHashSet}; 11use rustc_hash::{FxHashMap, FxHashSet};
12 12
13use crate::{AssistContext, AssistId, Assists}; 13use crate::{
14 assist_config::SnippetCap,
15 utils::{render_snippet, Cursor},
16 AssistContext, AssistId, Assists,
17};
14 18
15// Assist: add_function 19// Assist: add_function
16// 20//
@@ -33,7 +37,7 @@ use crate::{AssistContext, AssistId, Assists};
33// } 37// }
34// 38//
35// fn bar(arg: &str, baz: Baz) { 39// fn bar(arg: &str, baz: Baz) {
36// todo!() 40// ${0:todo!()}
37// } 41// }
38// 42//
39// ``` 43// ```
@@ -58,21 +62,40 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
58 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; 62 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
59 63
60 let target = call.syntax().text_range(); 64 let target = call.syntax().text_range();
61 acc.add(AssistId("add_function"), "Add function", target, |edit| { 65 acc.add(AssistId("add_function"), "Add function", target, |builder| {
62 let function_template = function_builder.render(); 66 let function_template = function_builder.render();
63 edit.set_file(function_template.file); 67 builder.set_file(function_template.file);
64 edit.set_cursor(function_template.cursor_offset); 68 let new_fn = function_template.to_string(ctx.config.snippet_cap);
65 edit.insert(function_template.insert_offset, function_template.fn_def.to_string()); 69 match ctx.config.snippet_cap {
70 Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn),
71 None => builder.insert(function_template.insert_offset, new_fn),
72 }
66 }) 73 })
67} 74}
68 75
69struct FunctionTemplate { 76struct FunctionTemplate {
70 insert_offset: TextSize, 77 insert_offset: TextSize,
71 cursor_offset: TextSize, 78 placeholder_expr: ast::MacroCall,
72 fn_def: ast::SourceFile, 79 leading_ws: String,
80 fn_def: ast::FnDef,
81 trailing_ws: String,
73 file: FileId, 82 file: FileId,
74} 83}
75 84
85impl FunctionTemplate {
86 fn to_string(&self, cap: Option<SnippetCap>) -> String {
87 let f = match cap {
88 Some(cap) => render_snippet(
89 cap,
90 self.fn_def.syntax(),
91 Cursor::Replace(self.placeholder_expr.syntax()),
92 ),
93 None => self.fn_def.to_string(),
94 };
95 format!("{}{}{}", self.leading_ws, f, self.trailing_ws)
96 }
97}
98
76struct FunctionBuilder { 99struct FunctionBuilder {
77 target: GeneratedFunctionTarget, 100 target: GeneratedFunctionTarget,
78 fn_name: ast::Name, 101 fn_name: ast::Name,
@@ -110,35 +133,41 @@ impl FunctionBuilder {
110 } 133 }
111 134
112 fn render(self) -> FunctionTemplate { 135 fn render(self) -> FunctionTemplate {
113 let placeholder_expr = ast::make::expr_todo(); 136 let placeholder_expr = make::expr_todo();
114 let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr)); 137 let fn_body = make::block_expr(vec![], Some(placeholder_expr));
115 let mut fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body); 138 let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None };
116 if self.needs_pub { 139 let mut fn_def =
117 fn_def = ast::make::add_pub_crate_modifier(fn_def); 140 make::fn_def(visibility, self.fn_name, self.type_params, self.params, fn_body);
118 } 141 let leading_ws;
119 142 let trailing_ws;
120 let (fn_def, insert_offset) = match self.target { 143
144 let insert_offset = match self.target {
121 GeneratedFunctionTarget::BehindItem(it) => { 145 GeneratedFunctionTarget::BehindItem(it) => {
122 let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def); 146 let indent = IndentLevel::from_node(&it);
123 let indented = with_leading_blank_line.indent(IndentLevel::from_node(&it)); 147 leading_ws = format!("\n\n{}", indent);
124 (indented, it.text_range().end()) 148 fn_def = fn_def.indent(indent);
149 trailing_ws = String::new();
150 it.text_range().end()
125 } 151 }
126 GeneratedFunctionTarget::InEmptyItemList(it) => { 152 GeneratedFunctionTarget::InEmptyItemList(it) => {
127 let indent_once = IndentLevel(1);
128 let indent = IndentLevel::from_node(it.syntax()); 153 let indent = IndentLevel::from_node(it.syntax());
129 let fn_def = ast::make::add_leading_newlines(1, fn_def); 154 leading_ws = format!("\n{}", indent + 1);
130 let fn_def = fn_def.indent(indent_once); 155 fn_def = fn_def.indent(indent + 1);
131 let fn_def = ast::make::add_trailing_newlines(1, fn_def); 156 trailing_ws = format!("\n{}", indent);
132 let fn_def = fn_def.indent(indent); 157 it.syntax().text_range().start() + TextSize::of('{')
133 (fn_def, it.syntax().text_range().start() + TextSize::of('{'))
134 } 158 }
135 }; 159 };
136 160
137 let placeholder_expr = 161 let placeholder_expr =
138 fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); 162 fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap();
139 let cursor_offset_from_fn_start = placeholder_expr.syntax().text_range().start(); 163 FunctionTemplate {
140 let cursor_offset = insert_offset + cursor_offset_from_fn_start; 164 insert_offset,
141 FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file } 165 placeholder_expr,
166 leading_ws,
167 fn_def,
168 trailing_ws,
169 file: self.file,
170 }
142 } 171 }
143} 172}
144 173
@@ -158,7 +187,7 @@ impl GeneratedFunctionTarget {
158 187
159fn fn_name(call: &ast::Path) -> Option<ast::Name> { 188fn fn_name(call: &ast::Path) -> Option<ast::Name> {
160 let name = call.segment()?.syntax().to_string(); 189 let name = call.segment()?.syntax().to_string();
161 Some(ast::make::name(&name)) 190 Some(make::name(&name))
162} 191}
163 192
164/// Computes the type variables and arguments required for the generated function 193/// Computes the type variables and arguments required for the generated function
@@ -180,8 +209,8 @@ fn fn_args(
180 }); 209 });
181 } 210 }
182 deduplicate_arg_names(&mut arg_names); 211 deduplicate_arg_names(&mut arg_names);
183 let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| ast::make::param(name, ty)); 212 let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| make::param(name, ty));
184 Some((None, ast::make::param_list(params))) 213 Some((None, make::param_list(params)))
185} 214}
186 215
187/// Makes duplicate argument names unique by appending incrementing numbers. 216/// Makes duplicate argument names unique by appending incrementing numbers.
@@ -316,7 +345,7 @@ fn foo() {
316} 345}
317 346
318fn bar() { 347fn bar() {
319 <|>todo!() 348 ${0:todo!()}
320} 349}
321", 350",
322 ) 351 )
@@ -343,7 +372,7 @@ impl Foo {
343} 372}
344 373
345fn bar() { 374fn bar() {
346 <|>todo!() 375 ${0:todo!()}
347} 376}
348", 377",
349 ) 378 )
@@ -367,7 +396,7 @@ fn foo1() {
367} 396}
368 397
369fn bar() { 398fn bar() {
370 <|>todo!() 399 ${0:todo!()}
371} 400}
372 401
373fn foo2() {} 402fn foo2() {}
@@ -393,7 +422,7 @@ mod baz {
393 } 422 }
394 423
395 fn bar() { 424 fn bar() {
396 <|>todo!() 425 ${0:todo!()}
397 } 426 }
398} 427}
399", 428",
@@ -419,7 +448,7 @@ fn foo() {
419} 448}
420 449
421fn bar(baz: Baz) { 450fn bar(baz: Baz) {
422 <|>todo!() 451 ${0:todo!()}
423} 452}
424", 453",
425 ); 454 );
@@ -452,7 +481,7 @@ impl Baz {
452} 481}
453 482
454fn bar(baz: Baz) { 483fn bar(baz: Baz) {
455 <|>todo!() 484 ${0:todo!()}
456} 485}
457", 486",
458 ) 487 )
@@ -473,7 +502,7 @@ fn foo() {
473} 502}
474 503
475fn bar(arg: &str) { 504fn bar(arg: &str) {
476 <|>todo!() 505 ${0:todo!()}
477} 506}
478"#, 507"#,
479 ) 508 )
@@ -494,7 +523,7 @@ fn foo() {
494} 523}
495 524
496fn bar(arg: char) { 525fn bar(arg: char) {
497 <|>todo!() 526 ${0:todo!()}
498} 527}
499"#, 528"#,
500 ) 529 )
@@ -515,7 +544,7 @@ fn foo() {
515} 544}
516 545
517fn bar(arg: i32) { 546fn bar(arg: i32) {
518 <|>todo!() 547 ${0:todo!()}
519} 548}
520", 549",
521 ) 550 )
@@ -536,7 +565,7 @@ fn foo() {
536} 565}
537 566
538fn bar(arg: u8) { 567fn bar(arg: u8) {
539 <|>todo!() 568 ${0:todo!()}
540} 569}
541", 570",
542 ) 571 )
@@ -561,7 +590,7 @@ fn foo() {
561} 590}
562 591
563fn bar(x: u8) { 592fn bar(x: u8) {
564 <|>todo!() 593 ${0:todo!()}
565} 594}
566", 595",
567 ) 596 )
@@ -584,7 +613,7 @@ fn foo() {
584} 613}
585 614
586fn bar(worble: ()) { 615fn bar(worble: ()) {
587 <|>todo!() 616 ${0:todo!()}
588} 617}
589", 618",
590 ) 619 )
@@ -613,7 +642,7 @@ fn baz() {
613} 642}
614 643
615fn bar(foo: impl Foo) { 644fn bar(foo: impl Foo) {
616 <|>todo!() 645 ${0:todo!()}
617} 646}
618", 647",
619 ) 648 )
@@ -640,7 +669,7 @@ fn foo() {
640} 669}
641 670
642fn bar(baz: &Baz) { 671fn bar(baz: &Baz) {
643 <|>todo!() 672 ${0:todo!()}
644} 673}
645", 674",
646 ) 675 )
@@ -669,7 +698,7 @@ fn foo() {
669} 698}
670 699
671fn bar(baz: Baz::Bof) { 700fn bar(baz: Baz::Bof) {
672 <|>todo!() 701 ${0:todo!()}
673} 702}
674", 703",
675 ) 704 )
@@ -692,7 +721,7 @@ fn foo<T>(t: T) {
692} 721}
693 722
694fn bar<T>(t: T) { 723fn bar<T>(t: T) {
695 <|>todo!() 724 ${0:todo!()}
696} 725}
697", 726",
698 ) 727 )
@@ -723,7 +752,7 @@ fn foo() {
723} 752}
724 753
725fn bar(arg: fn() -> Baz) { 754fn bar(arg: fn() -> Baz) {
726 <|>todo!() 755 ${0:todo!()}
727} 756}
728", 757",
729 ) 758 )
@@ -748,7 +777,7 @@ fn foo() {
748} 777}
749 778
750fn bar(closure: impl Fn(i64) -> i64) { 779fn bar(closure: impl Fn(i64) -> i64) {
751 <|>todo!() 780 ${0:todo!()}
752} 781}
753", 782",
754 ) 783 )
@@ -769,7 +798,7 @@ fn foo() {
769} 798}
770 799
771fn bar(baz: ()) { 800fn bar(baz: ()) {
772 <|>todo!() 801 ${0:todo!()}
773} 802}
774", 803",
775 ) 804 )
@@ -794,7 +823,7 @@ fn foo() {
794} 823}
795 824
796fn bar(baz_1: Baz, baz_2: Baz) { 825fn bar(baz_1: Baz, baz_2: Baz) {
797 <|>todo!() 826 ${0:todo!()}
798} 827}
799", 828",
800 ) 829 )
@@ -819,7 +848,7 @@ fn foo() {
819} 848}
820 849
821fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) { 850fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
822 <|>todo!() 851 ${0:todo!()}
823} 852}
824"#, 853"#,
825 ) 854 )
@@ -839,7 +868,7 @@ fn foo() {
839 r" 868 r"
840mod bar { 869mod bar {
841 pub(crate) fn my_fn() { 870 pub(crate) fn my_fn() {
842 <|>todo!() 871 ${0:todo!()}
843 } 872 }
844} 873}
845 874
@@ -878,7 +907,7 @@ fn bar() {
878} 907}
879 908
880fn baz(foo: foo::Foo) { 909fn baz(foo: foo::Foo) {
881 <|>todo!() 910 ${0:todo!()}
882} 911}
883", 912",
884 ) 913 )
@@ -902,7 +931,7 @@ mod bar {
902 fn something_else() {} 931 fn something_else() {}
903 932
904 pub(crate) fn my_fn() { 933 pub(crate) fn my_fn() {
905 <|>todo!() 934 ${0:todo!()}
906 } 935 }
907} 936}
908 937
@@ -930,7 +959,7 @@ fn foo() {
930mod bar { 959mod bar {
931 mod baz { 960 mod baz {
932 pub(crate) fn my_fn() { 961 pub(crate) fn my_fn() {
933 <|>todo!() 962 ${0:todo!()}
934 } 963 }
935 } 964 }
936} 965}
@@ -959,7 +988,7 @@ fn main() {
959 988
960 989
961pub(crate) fn bar() { 990pub(crate) fn bar() {
962 <|>todo!() 991 ${0:todo!()}
963}", 992}",
964 ) 993 )
965 } 994 }
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs
index df114a0d8..eceba7d0a 100644
--- a/crates/ra_assists/src/handlers/add_impl.rs
+++ b/crates/ra_assists/src/handlers/add_impl.rs
@@ -1,7 +1,4 @@
1use ra_syntax::{ 1use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner};
2 ast::{self, AstNode, NameOwner, TypeParamsOwner},
3 TextSize,
4};
5use stdx::{format_to, SepBy}; 2use stdx::{format_to, SepBy};
6 3
7use crate::{AssistContext, AssistId, Assists}; 4use crate::{AssistContext, AssistId, Assists};
@@ -12,17 +9,17 @@ use crate::{AssistContext, AssistId, Assists};
12// 9//
13// ``` 10// ```
14// struct Ctx<T: Clone> { 11// struct Ctx<T: Clone> {
15// data: T,<|> 12// data: T,<|>
16// } 13// }
17// ``` 14// ```
18// -> 15// ->
19// ``` 16// ```
20// struct Ctx<T: Clone> { 17// struct Ctx<T: Clone> {
21// data: T, 18// data: T,
22// } 19// }
23// 20//
24// impl<T: Clone> Ctx<T> { 21// impl<T: Clone> Ctx<T> {
25// 22// $0
26// } 23// }
27// ``` 24// ```
28pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 25pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@@ -50,30 +47,37 @@ pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
50 let generic_params = lifetime_params.chain(type_params).sep_by(", "); 47 let generic_params = lifetime_params.chain(type_params).sep_by(", ");
51 format_to!(buf, "<{}>", generic_params) 48 format_to!(buf, "<{}>", generic_params)
52 } 49 }
53 buf.push_str(" {\n"); 50 match ctx.config.snippet_cap {
54 edit.set_cursor(start_offset + TextSize::of(&buf)); 51 Some(cap) => {
55 buf.push_str("\n}"); 52 buf.push_str(" {\n $0\n}");
56 edit.insert(start_offset, buf); 53 edit.insert_snippet(cap, start_offset, buf);
54 }
55 None => {
56 buf.push_str(" {\n}");
57 edit.insert(start_offset, buf);
58 }
59 }
57 }) 60 })
58} 61}
59 62
60#[cfg(test)] 63#[cfg(test)]
61mod tests { 64mod tests {
62 use super::*;
63 use crate::tests::{check_assist, check_assist_target}; 65 use crate::tests::{check_assist, check_assist_target};
64 66
67 use super::*;
68
65 #[test] 69 #[test]
66 fn test_add_impl() { 70 fn test_add_impl() {
67 check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n<|>\n}\n"); 71 check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n $0\n}\n");
68 check_assist( 72 check_assist(
69 add_impl, 73 add_impl,
70 "struct Foo<T: Clone> {<|>}", 74 "struct Foo<T: Clone> {<|>}",
71 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", 75 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}",
72 ); 76 );
73 check_assist( 77 check_assist(
74 add_impl, 78 add_impl,
75 "struct Foo<'a, T: Foo<'a>> {<|>}", 79 "struct Foo<'a, T: Foo<'a>> {<|>}",
76 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", 80 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
77 ); 81 );
78 } 82 }
79 83
diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
index 22e1156d2..abacd4065 100644
--- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
@@ -11,7 +11,7 @@ use ra_syntax::{
11use crate::{ 11use crate::{
12 assist_context::{AssistContext, Assists}, 12 assist_context::{AssistContext, Assists},
13 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, 13 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
14 utils::{get_missing_assoc_items, resolve_target_trait}, 14 utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor},
15 AssistId, 15 AssistId,
16}; 16};
17 17
@@ -46,7 +46,7 @@ enum AddMissingImplMembersMode {
46// 46//
47// impl Trait<u32> for () { 47// impl Trait<u32> for () {
48// fn foo(&self) -> u32 { 48// fn foo(&self) -> u32 {
49// todo!() 49// ${0:todo!()}
50// } 50// }
51// 51//
52// } 52// }
@@ -89,7 +89,7 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -
89// impl Trait for () { 89// impl Trait for () {
90// Type X = (); 90// Type X = ();
91// fn foo(&self) {} 91// fn foo(&self) {}
92// fn bar(&self) {} 92// $0fn bar(&self) {}
93// 93//
94// } 94// }
95// ``` 95// ```
@@ -147,7 +147,7 @@ fn add_missing_impl_members_inner(
147 } 147 }
148 148
149 let target = impl_def.syntax().text_range(); 149 let target = impl_def.syntax().text_range();
150 acc.add(AssistId(assist_id), label, target, |edit| { 150 acc.add(AssistId(assist_id), label, target, |builder| {
151 let n_existing_items = impl_item_list.assoc_items().count(); 151 let n_existing_items = impl_item_list.assoc_items().count();
152 let source_scope = ctx.sema.scope_for_def(trait_); 152 let source_scope = ctx.sema.scope_for_def(trait_);
153 let target_scope = ctx.sema.scope(impl_item_list.syntax()); 153 let target_scope = ctx.sema.scope(impl_item_list.syntax());
@@ -162,13 +162,29 @@ fn add_missing_impl_members_inner(
162 }) 162 })
163 .map(|it| edit::remove_attrs_and_docs(&it)); 163 .map(|it| edit::remove_attrs_and_docs(&it));
164 let new_impl_item_list = impl_item_list.append_items(items); 164 let new_impl_item_list = impl_item_list.append_items(items);
165 let cursor_position = { 165 let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap();
166 let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap(); 166
167 first_new_item.syntax().text_range().start() 167 let original_range = impl_item_list.syntax().text_range();
168 match ctx.config.snippet_cap {
169 None => builder.replace(original_range, new_impl_item_list.to_string()),
170 Some(cap) => {
171 let mut cursor = Cursor::Before(first_new_item.syntax());
172 let placeholder;
173 if let ast::AssocItem::FnDef(func) = &first_new_item {
174 if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
175 if m.syntax().text() == "todo!()" {
176 placeholder = m;
177 cursor = Cursor::Replace(placeholder.syntax());
178 }
179 }
180 }
181 builder.replace_snippet(
182 cap,
183 original_range,
184 render_snippet(cap, new_impl_item_list.syntax(), cursor),
185 )
186 }
168 }; 187 };
169
170 edit.replace_ast(impl_item_list, new_impl_item_list);
171 edit.set_cursor(cursor_position);
172 }) 188 })
173} 189}
174 190
@@ -222,7 +238,7 @@ struct S;
222 238
223impl Foo for S { 239impl Foo for S {
224 fn bar(&self) {} 240 fn bar(&self) {}
225 <|>type Output; 241 $0type Output;
226 const CONST: usize = 42; 242 const CONST: usize = 42;
227 fn foo(&self) { 243 fn foo(&self) {
228 todo!() 244 todo!()
@@ -263,8 +279,8 @@ struct S;
263 279
264impl Foo for S { 280impl Foo for S {
265 fn bar(&self) {} 281 fn bar(&self) {}
266 <|>fn foo(&self) { 282 fn foo(&self) {
267 todo!() 283 ${0:todo!()}
268 } 284 }
269 285
270}"#, 286}"#,
@@ -283,8 +299,8 @@ impl Foo for S { <|> }"#,
283trait Foo { fn foo(&self); } 299trait Foo { fn foo(&self); }
284struct S; 300struct S;
285impl Foo for S { 301impl Foo for S {
286 <|>fn foo(&self) { 302 fn foo(&self) {
287 todo!() 303 ${0:todo!()}
288 } 304 }
289}"#, 305}"#,
290 ); 306 );
@@ -302,8 +318,8 @@ impl Foo<u32> for S { <|> }"#,
302trait Foo<T> { fn foo(&self, t: T) -> &T; } 318trait Foo<T> { fn foo(&self, t: T) -> &T; }
303struct S; 319struct S;
304impl Foo<u32> for S { 320impl Foo<u32> for S {
305 <|>fn foo(&self, t: u32) -> &u32 { 321 fn foo(&self, t: u32) -> &u32 {
306 todo!() 322 ${0:todo!()}
307 } 323 }
308}"#, 324}"#,
309 ); 325 );
@@ -321,8 +337,8 @@ impl<U> Foo<U> for S { <|> }"#,
321trait Foo<T> { fn foo(&self, t: T) -> &T; } 337trait Foo<T> { fn foo(&self, t: T) -> &T; }
322struct S; 338struct S;
323impl<U> Foo<U> for S { 339impl<U> Foo<U> for S {
324 <|>fn foo(&self, t: U) -> &U { 340 fn foo(&self, t: U) -> &U {
325 todo!() 341 ${0:todo!()}
326 } 342 }
327}"#, 343}"#,
328 ); 344 );
@@ -340,8 +356,8 @@ impl Foo for S {}<|>"#,
340trait Foo { fn foo(&self); } 356trait Foo { fn foo(&self); }
341struct S; 357struct S;
342impl Foo for S { 358impl Foo for S {
343 <|>fn foo(&self) { 359 fn foo(&self) {
344 todo!() 360 ${0:todo!()}
345 } 361 }
346}"#, 362}"#,
347 ) 363 )
@@ -365,8 +381,8 @@ mod foo {
365} 381}
366struct S; 382struct S;
367impl foo::Foo for S { 383impl foo::Foo for S {
368 <|>fn foo(&self, bar: foo::Bar) { 384 fn foo(&self, bar: foo::Bar) {
369 todo!() 385 ${0:todo!()}
370 } 386 }
371}"#, 387}"#,
372 ); 388 );
@@ -390,8 +406,8 @@ mod foo {
390} 406}
391struct S; 407struct S;
392impl foo::Foo for S { 408impl foo::Foo for S {
393 <|>fn foo(&self, bar: foo::Bar<u32>) { 409 fn foo(&self, bar: foo::Bar<u32>) {
394 todo!() 410 ${0:todo!()}
395 } 411 }
396}"#, 412}"#,
397 ); 413 );
@@ -415,8 +431,8 @@ mod foo {
415} 431}
416struct S; 432struct S;
417impl foo::Foo<u32> for S { 433impl foo::Foo<u32> for S {
418 <|>fn foo(&self, bar: foo::Bar<u32>) { 434 fn foo(&self, bar: foo::Bar<u32>) {
419 todo!() 435 ${0:todo!()}
420 } 436 }
421}"#, 437}"#,
422 ); 438 );
@@ -443,8 +459,8 @@ mod foo {
443struct Param; 459struct Param;
444struct S; 460struct S;
445impl foo::Foo<Param> for S { 461impl foo::Foo<Param> for S {
446 <|>fn foo(&self, bar: Param) { 462 fn foo(&self, bar: Param) {
447 todo!() 463 ${0:todo!()}
448 } 464 }
449}"#, 465}"#,
450 ); 466 );
@@ -470,8 +486,8 @@ mod foo {
470} 486}
471struct S; 487struct S;
472impl foo::Foo for S { 488impl foo::Foo for S {
473 <|>fn foo(&self, bar: foo::Bar<u32>::Assoc) { 489 fn foo(&self, bar: foo::Bar<u32>::Assoc) {
474 todo!() 490 ${0:todo!()}
475 } 491 }
476}"#, 492}"#,
477 ); 493 );
@@ -497,8 +513,8 @@ mod foo {
497} 513}
498struct S; 514struct S;
499impl foo::Foo for S { 515impl foo::Foo for S {
500 <|>fn foo(&self, bar: foo::Bar<foo::Baz>) { 516 fn foo(&self, bar: foo::Bar<foo::Baz>) {
501 todo!() 517 ${0:todo!()}
502 } 518 }
503}"#, 519}"#,
504 ); 520 );
@@ -522,8 +538,8 @@ mod foo {
522} 538}
523struct S; 539struct S;
524impl foo::Foo for S { 540impl foo::Foo for S {
525 <|>fn foo(&self, bar: dyn Fn(u32) -> i32) { 541 fn foo(&self, bar: dyn Fn(u32) -> i32) {
526 todo!() 542 ${0:todo!()}
527 } 543 }
528}"#, 544}"#,
529 ); 545 );
@@ -580,7 +596,7 @@ trait Foo {
580} 596}
581struct S; 597struct S;
582impl Foo for S { 598impl Foo for S {
583 <|>type Output; 599 $0type Output;
584 fn foo(&self) { 600 fn foo(&self) {
585 todo!() 601 todo!()
586 } 602 }
@@ -614,7 +630,7 @@ trait Foo {
614} 630}
615struct S; 631struct S;
616impl Foo for S { 632impl Foo for S {
617 <|>fn valid(some: u32) -> bool { false } 633 $0fn valid(some: u32) -> bool { false }
618}"#, 634}"#,
619 ) 635 )
620 } 636 }
@@ -637,8 +653,8 @@ trait Foo<T = Self> {
637 653
638struct S; 654struct S;
639impl Foo for S { 655impl Foo for S {
640 <|>fn bar(&self, other: &Self) { 656 fn bar(&self, other: &Self) {
641 todo!() 657 ${0:todo!()}
642 } 658 }
643}"#, 659}"#,
644 ) 660 )
@@ -662,8 +678,8 @@ trait Foo<T1, T2 = Self> {
662 678
663struct S<T>; 679struct S<T>;
664impl Foo<T> for S<T> { 680impl Foo<T> for S<T> {
665 <|>fn bar(&self, this: &T, that: &Self) { 681 fn bar(&self, this: &T, that: &Self) {
666 todo!() 682 ${0:todo!()}
667 } 683 }
668}"#, 684}"#,
669 ) 685 )
diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs
new file mode 100644
index 000000000..a0363bc78
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_turbo_fish.rs
@@ -0,0 +1,134 @@
1use ra_ide_db::defs::{classify_name_ref, Definition, NameRefClass};
2use ra_syntax::{ast, AstNode, SyntaxKind, T};
3
4use crate::{
5 assist_context::{AssistContext, Assists},
6 AssistId,
7};
8use test_utils::tested_by;
9
10// Assist: add_turbo_fish
11//
12// Adds `::<_>` to a call of a generic method or function.
13//
14// ```
15// fn make<T>() -> T { todo!() }
16// fn main() {
17// let x = make<|>();
18// }
19// ```
20// ->
21// ```
22// fn make<T>() -> T { todo!() }
23// fn main() {
24// let x = make::<${0:_}>();
25// }
26// ```
27pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let ident = ctx.find_token_at_offset(SyntaxKind::IDENT)?;
29 let next_token = ident.next_token()?;
30 if next_token.kind() == T![::] {
31 tested_by!(add_turbo_fish_one_fish_is_enough);
32 return None;
33 }
34 let name_ref = ast::NameRef::cast(ident.parent())?;
35 let def = match classify_name_ref(&ctx.sema, &name_ref)? {
36 NameRefClass::Definition(def) => def,
37 NameRefClass::FieldShorthand { .. } => return None,
38 };
39 let fun = match def {
40 Definition::ModuleDef(hir::ModuleDef::Function(it)) => it,
41 _ => return None,
42 };
43 let generics = hir::GenericDef::Function(fun).params(ctx.sema.db);
44 if generics.is_empty() {
45 tested_by!(add_turbo_fish_non_generic);
46 return None;
47 }
48 acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| {
49 match ctx.config.snippet_cap {
50 Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"),
51 None => builder.insert(ident.text_range().end(), "::<_>"),
52 }
53 })
54}
55
56#[cfg(test)]
57mod tests {
58 use crate::tests::{check_assist, check_assist_not_applicable};
59
60 use super::*;
61 use test_utils::covers;
62
63 #[test]
64 fn add_turbo_fish_function() {
65 check_assist(
66 add_turbo_fish,
67 r#"
68fn make<T>() -> T {}
69fn main() {
70 make<|>();
71}
72"#,
73 r#"
74fn make<T>() -> T {}
75fn main() {
76 make::<${0:_}>();
77}
78"#,
79 );
80 }
81
82 #[test]
83 fn add_turbo_fish_method() {
84 check_assist(
85 add_turbo_fish,
86 r#"
87struct S;
88impl S {
89 fn make<T>(&self) -> T {}
90}
91fn main() {
92 S.make<|>();
93}
94"#,
95 r#"
96struct S;
97impl S {
98 fn make<T>(&self) -> T {}
99}
100fn main() {
101 S.make::<${0:_}>();
102}
103"#,
104 );
105 }
106
107 #[test]
108 fn add_turbo_fish_one_fish_is_enough() {
109 covers!(add_turbo_fish_one_fish_is_enough);
110 check_assist_not_applicable(
111 add_turbo_fish,
112 r#"
113fn make<T>() -> T {}
114fn main() {
115 make<|>::<()>();
116}
117"#,
118 );
119 }
120
121 #[test]
122 fn add_turbo_fish_non_generic() {
123 covers!(add_turbo_fish_non_generic);
124 check_assist_not_applicable(
125 add_turbo_fish,
126 r#"
127fn make() -> () {}
128fn main() {
129 make<|>();
130}
131"#,
132 );
133 }
134}
diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
index c4b56f6e9..b379b55a8 100644
--- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
@@ -51,7 +51,7 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext)
51 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); 51 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
52 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); 52 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
53 53
54 let unreachable_call = make::unreachable_macro_call().into(); 54 let unreachable_call = make::expr_unreachable();
55 let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); 55 let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
56 56
57 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); 57 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index b6dc7cb1b..339f24100 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -10,6 +10,7 @@ macro_rules! eprintln {
10 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; 10 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
11} 11}
12 12
13mod assist_config;
13mod assist_context; 14mod assist_context;
14mod marks; 15mod marks;
15#[cfg(test)] 16#[cfg(test)]
@@ -24,6 +25,8 @@ use ra_syntax::TextRange;
24 25
25pub(crate) use crate::assist_context::{AssistContext, Assists}; 26pub(crate) use crate::assist_context::{AssistContext, Assists};
26 27
28pub use assist_config::AssistConfig;
29
27/// Unique identifier of the assist, should not be shown to the user 30/// Unique identifier of the assist, should not be shown to the user
28/// directly. 31/// directly.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)] 32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -54,9 +57,9 @@ impl Assist {
54 /// 57 ///
55 /// Assists are returned in the "unresolved" state, that is only labels are 58 /// Assists are returned in the "unresolved" state, that is only labels are
56 /// returned, without actual edits. 59 /// returned, without actual edits.
57 pub fn unresolved(db: &RootDatabase, range: FileRange) -> Vec<Assist> { 60 pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec<Assist> {
58 let sema = Semantics::new(db); 61 let sema = Semantics::new(db);
59 let ctx = AssistContext::new(sema, range); 62 let ctx = AssistContext::new(sema, config, range);
60 let mut acc = Assists::new_unresolved(&ctx); 63 let mut acc = Assists::new_unresolved(&ctx);
61 handlers::all().iter().for_each(|handler| { 64 handlers::all().iter().for_each(|handler| {
62 handler(&mut acc, &ctx); 65 handler(&mut acc, &ctx);
@@ -68,9 +71,13 @@ impl Assist {
68 /// 71 ///
69 /// Assists are returned in the "resolved" state, that is with edit fully 72 /// Assists are returned in the "resolved" state, that is with edit fully
70 /// computed. 73 /// computed.
71 pub fn resolved(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> { 74 pub fn resolved(
75 db: &RootDatabase,
76 config: &AssistConfig,
77 range: FileRange,
78 ) -> Vec<ResolvedAssist> {
72 let sema = Semantics::new(db); 79 let sema = Semantics::new(db);
73 let ctx = AssistContext::new(sema, range); 80 let ctx = AssistContext::new(sema, config, range);
74 let mut acc = Assists::new_resolved(&ctx); 81 let mut acc = Assists::new_resolved(&ctx);
75 handlers::all().iter().for_each(|handler| { 82 handlers::all().iter().for_each(|handler| {
76 handler(&mut acc, &ctx); 83 handler(&mut acc, &ctx);
@@ -103,6 +110,7 @@ mod handlers {
103 mod add_impl; 110 mod add_impl;
104 mod add_missing_impl_members; 111 mod add_missing_impl_members;
105 mod add_new; 112 mod add_new;
113 mod add_turbo_fish;
106 mod apply_demorgan; 114 mod apply_demorgan;
107 mod auto_import; 115 mod auto_import;
108 mod change_return_type_to_result; 116 mod change_return_type_to_result;
@@ -140,6 +148,7 @@ mod handlers {
140 add_function::add_function, 148 add_function::add_function,
141 add_impl::add_impl, 149 add_impl::add_impl,
142 add_new::add_new, 150 add_new::add_new,
151 add_turbo_fish::add_turbo_fish,
143 apply_demorgan::apply_demorgan, 152 apply_demorgan::apply_demorgan,
144 auto_import::auto_import, 153 auto_import::auto_import,
145 change_return_type_to_result::change_return_type_to_result, 154 change_return_type_to_result::change_return_type_to_result,
diff --git a/crates/ra_assists/src/marks.rs b/crates/ra_assists/src/marks.rs
index 8d910205f..d579e627f 100644
--- a/crates/ra_assists/src/marks.rs
+++ b/crates/ra_assists/src/marks.rs
@@ -9,4 +9,6 @@ test_utils::marks![
9 test_not_applicable_if_variable_unused 9 test_not_applicable_if_variable_unused
10 change_visibility_field_false_positive 10 change_visibility_field_false_positive
11 test_add_from_impl_already_exists 11 test_add_from_impl_already_exists
12 add_turbo_fish_one_fish_is_enough
13 add_turbo_fish_non_generic
12]; 14];
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs
index a3eacb8f1..9ba3da786 100644
--- a/crates/ra_assists/src/tests.rs
+++ b/crates/ra_assists/src/tests.rs
@@ -11,7 +11,7 @@ use test_utils::{
11 RangeOrOffset, 11 RangeOrOffset,
12}; 12};
13 13
14use crate::{handlers::Handler, Assist, AssistContext, Assists}; 14use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists};
15 15
16pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { 16pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
17 let (mut db, file_id) = RootDatabase::with_single_file(text); 17 let (mut db, file_id) = RootDatabase::with_single_file(text);
@@ -41,14 +41,14 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
41 let (db, file_id) = crate::tests::with_single_file(&before); 41 let (db, file_id) = crate::tests::with_single_file(&before);
42 let frange = FileRange { file_id, range: selection.into() }; 42 let frange = FileRange { file_id, range: selection.into() };
43 43
44 let mut assist = Assist::resolved(&db, frange) 44 let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange)
45 .into_iter() 45 .into_iter()
46 .find(|assist| assist.assist.id.0 == assist_id) 46 .find(|assist| assist.assist.id.0 == assist_id)
47 .unwrap_or_else(|| { 47 .unwrap_or_else(|| {
48 panic!( 48 panic!(
49 "\n\nAssist is not applicable: {}\nAvailable assists: {}", 49 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
50 assist_id, 50 assist_id,
51 Assist::resolved(&db, frange) 51 Assist::resolved(&db, &AssistConfig::default(), frange)
52 .into_iter() 52 .into_iter()
53 .map(|assist| assist.assist.id.0) 53 .map(|assist| assist.assist.id.0)
54 .collect::<Vec<_>>() 54 .collect::<Vec<_>>()
@@ -90,7 +90,8 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) {
90 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; 90 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
91 91
92 let sema = Semantics::new(&db); 92 let sema = Semantics::new(&db);
93 let ctx = AssistContext::new(sema, frange); 93 let config = AssistConfig::default();
94 let ctx = AssistContext::new(sema, &config, frange);
94 let mut acc = Assists::new_resolved(&ctx); 95 let mut acc = Assists::new_resolved(&ctx);
95 handler(&mut acc, &ctx); 96 handler(&mut acc, &ctx);
96 let mut res = acc.finish_resolved(); 97 let mut res = acc.finish_resolved();
@@ -103,19 +104,20 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) {
103 let mut actual = db.file_text(change.file_id).as_ref().to_owned(); 104 let mut actual = db.file_text(change.file_id).as_ref().to_owned();
104 change.edit.apply(&mut actual); 105 change.edit.apply(&mut actual);
105 106
106 match source_change.cursor_position { 107 if !source_change.is_snippet {
107 None => { 108 match source_change.cursor_position {
108 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { 109 None => {
109 let off = change 110 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
110 .edit 111 let off = change
111 .apply_to_offset(before_cursor_pos) 112 .edit
112 .expect("cursor position is affected by the edit"); 113 .apply_to_offset(before_cursor_pos)
113 actual = add_cursor(&actual, off) 114 .expect("cursor position is affected by the edit");
115 actual = add_cursor(&actual, off)
116 }
114 } 117 }
115 } 118 Some(off) => actual = add_cursor(&actual, off.offset),
116 Some(off) => actual = add_cursor(&actual, off.offset), 119 };
117 }; 120 }
118
119 assert_eq_text!(after, &actual); 121 assert_eq_text!(after, &actual);
120 } 122 }
121 (Some(assist), ExpectedResult::Target(target)) => { 123 (Some(assist), ExpectedResult::Target(target)) => {
@@ -136,7 +138,7 @@ fn assist_order_field_struct() {
136 let (before_cursor_pos, before) = extract_offset(before); 138 let (before_cursor_pos, before) = extract_offset(before);
137 let (db, file_id) = with_single_file(&before); 139 let (db, file_id) = with_single_file(&before);
138 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; 140 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
139 let assists = Assist::resolved(&db, frange); 141 let assists = Assist::resolved(&db, &AssistConfig::default(), frange);
140 let mut assists = assists.iter(); 142 let mut assists = assists.iter();
141 143
142 assert_eq!( 144 assert_eq!(
@@ -159,7 +161,7 @@ fn assist_order_if_expr() {
159 let (range, before) = extract_range(before); 161 let (range, before) = extract_range(before);
160 let (db, file_id) = with_single_file(&before); 162 let (db, file_id) = with_single_file(&before);
161 let frange = FileRange { file_id, range }; 163 let frange = FileRange { file_id, range };
162 let assists = Assist::resolved(&db, frange); 164 let assists = Assist::resolved(&db, &AssistConfig::default(), frange);
163 let mut assists = assists.iter(); 165 let mut assists = assists.iter();
164 166
165 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); 167 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs
index 972dbd251..3808aded1 100644
--- a/crates/ra_assists/src/tests/generated.rs
+++ b/crates/ra_assists/src/tests/generated.rs
@@ -15,7 +15,7 @@ struct S;
15struct S; 15struct S;
16 16
17impl Debug for S { 17impl Debug for S {
18 18 $0
19} 19}
20"#####, 20"#####,
21 ) 21 )
@@ -32,7 +32,7 @@ struct Point {
32} 32}
33"#####, 33"#####,
34 r#####" 34 r#####"
35#[derive()] 35#[derive($0)]
36struct Point { 36struct Point {
37 x: u32, 37 x: u32,
38 y: u32, 38 y: u32,
@@ -78,7 +78,7 @@ fn foo() {
78} 78}
79 79
80fn bar(arg: &str, baz: Baz) { 80fn bar(arg: &str, baz: Baz) {
81 todo!() 81 ${0:todo!()}
82} 82}
83 83
84"#####, 84"#####,
@@ -108,16 +108,16 @@ fn doctest_add_impl() {
108 "add_impl", 108 "add_impl",
109 r#####" 109 r#####"
110struct Ctx<T: Clone> { 110struct Ctx<T: Clone> {
111 data: T,<|> 111 data: T,<|>
112} 112}
113"#####, 113"#####,
114 r#####" 114 r#####"
115struct Ctx<T: Clone> { 115struct Ctx<T: Clone> {
116 data: T, 116 data: T,
117} 117}
118 118
119impl<T: Clone> Ctx<T> { 119impl<T: Clone> Ctx<T> {
120 120 $0
121} 121}
122"#####, 122"#####,
123 ) 123 )
@@ -150,7 +150,7 @@ trait Trait {
150impl Trait for () { 150impl Trait for () {
151 Type X = (); 151 Type X = ();
152 fn foo(&self) {} 152 fn foo(&self) {}
153 fn bar(&self) {} 153 $0fn bar(&self) {}
154 154
155} 155}
156"#####, 156"#####,
@@ -181,7 +181,7 @@ trait Trait<T> {
181 181
182impl Trait<u32> for () { 182impl Trait<u32> for () {
183 fn foo(&self) -> u32 { 183 fn foo(&self) -> u32 {
184 todo!() 184 ${0:todo!()}
185 } 185 }
186 186
187} 187}
@@ -212,6 +212,25 @@ impl<T: Clone> Ctx<T> {
212} 212}
213 213
214#[test] 214#[test]
215fn doctest_add_turbo_fish() {
216 check_doc_test(
217 "add_turbo_fish",
218 r#####"
219fn make<T>() -> T { todo!() }
220fn main() {
221 let x = make<|>();
222}
223"#####,
224 r#####"
225fn make<T>() -> T { todo!() }
226fn main() {
227 let x = make::<${0:_}>();
228}
229"#####,
230 )
231}
232
233#[test]
215fn doctest_apply_demorgan() { 234fn doctest_apply_demorgan() {
216 check_doc_test( 235 check_doc_test(
217 "apply_demorgan", 236 "apply_demorgan",
diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs
index f3fc92ebf..9af27180b 100644
--- a/crates/ra_assists/src/utils.rs
+++ b/crates/ra_assists/src/utils.rs
@@ -1,18 +1,57 @@
1//! Assorted functions shared by several assists. 1//! Assorted functions shared by several assists.
2pub(crate) mod insert_use; 2pub(crate) mod insert_use;
3 3
4use std::iter; 4use std::{iter, ops};
5 5
6use hir::{Adt, Crate, Semantics, Trait, Type}; 6use hir::{Adt, Crate, Semantics, Trait, Type};
7use ra_ide_db::RootDatabase; 7use ra_ide_db::RootDatabase;
8use ra_syntax::{ 8use ra_syntax::{
9 ast::{self, make, NameOwner}, 9 ast::{self, make, NameOwner},
10 AstNode, T, 10 AstNode, SyntaxNode, T,
11}; 11};
12use rustc_hash::FxHashSet; 12use rustc_hash::FxHashSet;
13 13
14use crate::assist_config::SnippetCap;
15
14pub(crate) use insert_use::insert_use_statement; 16pub(crate) use insert_use::insert_use_statement;
15 17
18#[derive(Clone, Copy, Debug)]
19pub(crate) enum Cursor<'a> {
20 Replace(&'a SyntaxNode),
21 Before(&'a SyntaxNode),
22}
23
24impl<'a> Cursor<'a> {
25 fn node(self) -> &'a SyntaxNode {
26 match self {
27 Cursor::Replace(node) | Cursor::Before(node) => node,
28 }
29 }
30}
31
32pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor) -> String {
33 assert!(cursor.node().ancestors().any(|it| it == *node));
34 let range = cursor.node().text_range() - node.text_range().start();
35 let range: ops::Range<usize> = range.into();
36
37 let mut placeholder = cursor.node().to_string();
38 escape(&mut placeholder);
39 let tab_stop = match cursor {
40 Cursor::Replace(placeholder) => format!("${{0:{}}}", placeholder),
41 Cursor::Before(placeholder) => format!("$0{}", placeholder),
42 };
43
44 let mut buf = node.to_string();
45 buf.replace_range(range, &tab_stop);
46 return buf;
47
48 fn escape(buf: &mut String) {
49 stdx::replace(buf, '{', r"\{");
50 stdx::replace(buf, '}', r"\}");
51 stdx::replace(buf, '$', r"\$");
52 }
53}
54
16pub fn get_missing_assoc_items( 55pub fn get_missing_assoc_items(
17 sema: &Semantics<RootDatabase>, 56 sema: &Semantics<RootDatabase>,
18 impl_def: &ast::ImplDef, 57 impl_def: &ast::ImplDef,