aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/doc_tests/generated.rs4
-rw-r--r--crates/ra_assists/src/handlers/add_function.rs219
-rw-r--r--crates/ra_syntax/src/ast/make.rs7
-rw-r--r--docs/user/assists.md4
4 files changed, 205 insertions, 29 deletions
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
index b39e60870..e4fa9ee36 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -66,7 +66,7 @@ fn doctest_add_function() {
66struct Baz; 66struct Baz;
67fn baz() -> Baz { Baz } 67fn baz() -> Baz { Baz }
68fn foo() { 68fn foo() {
69 bar<|>("", baz()); 69 bar<|>("", baz());
70} 70}
71 71
72"#####, 72"#####,
@@ -74,7 +74,7 @@ fn foo() {
74struct Baz; 74struct Baz;
75fn baz() -> Baz { Baz } 75fn baz() -> Baz { Baz }
76fn foo() { 76fn foo() {
77 bar("", baz()); 77 bar("", baz());
78} 78}
79 79
80fn bar(arg: &str, baz: Baz) { 80fn bar(arg: &str, baz: Baz) {
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs
index ad4ab66ed..a1261fe15 100644
--- a/crates/ra_assists/src/handlers/add_function.rs
+++ b/crates/ra_assists/src/handlers/add_function.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4}; 4};
5 5
6use crate::{Assist, AssistCtx, AssistId}; 6use crate::{Assist, AssistCtx, AssistId};
7use ast::{edit::IndentLevel, ArgListOwner, CallExpr, Expr}; 7use ast::{edit::IndentLevel, ArgListOwner, ModuleItemOwner};
8use hir::HirDisplay; 8use hir::HirDisplay;
9use rustc_hash::{FxHashMap, FxHashSet}; 9use rustc_hash::{FxHashMap, FxHashSet};
10 10
@@ -16,7 +16,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
16// struct Baz; 16// struct Baz;
17// fn baz() -> Baz { Baz } 17// fn baz() -> Baz { Baz }
18// fn foo() { 18// fn foo() {
19// bar<|>("", baz()); 19// bar<|>("", baz());
20// } 20// }
21// 21//
22// ``` 22// ```
@@ -25,7 +25,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
25// struct Baz; 25// struct Baz;
26// fn baz() -> Baz { Baz } 26// fn baz() -> Baz { Baz }
27// fn foo() { 27// fn foo() {
28// bar("", baz()); 28// bar("", baz());
29// } 29// }
30// 30//
31// fn bar(arg: &str, baz: Baz) { 31// fn bar(arg: &str, baz: Baz) {
@@ -38,16 +38,24 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> {
38 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; 38 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
39 let path = path_expr.path()?; 39 let path = path_expr.path()?;
40 40
41 if path.qualifier().is_some() {
42 return None;
43 }
44
45 if ctx.sema.resolve_path(&path).is_some() { 41 if ctx.sema.resolve_path(&path).is_some() {
46 // The function call already resolves, no need to add a function 42 // The function call already resolves, no need to add a function
47 return None; 43 return None;
48 } 44 }
49 45
50 let function_builder = FunctionBuilder::from_call(&ctx, &call)?; 46 let target_module = if let Some(qualifier) = path.qualifier() {
47 if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(resolved))) =
48 ctx.sema.resolve_path(&qualifier)
49 {
50 Some(resolved.definition_source(ctx.sema.db).value)
51 } else {
52 return None;
53 }
54 } else {
55 None
56 };
57
58 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
51 59
52 ctx.add_assist(AssistId("add_function"), "Add function", |edit| { 60 ctx.add_assist(AssistId("add_function"), "Add function", |edit| {
53 edit.target(call.syntax().text_range()); 61 edit.target(call.syntax().text_range());
@@ -66,26 +74,54 @@ struct FunctionTemplate {
66} 74}
67 75
68struct FunctionBuilder { 76struct FunctionBuilder {
69 append_fn_at: SyntaxNode, 77 target: GeneratedFunctionTarget,
70 fn_name: ast::Name, 78 fn_name: ast::Name,
71 type_params: Option<ast::TypeParamList>, 79 type_params: Option<ast::TypeParamList>,
72 params: ast::ParamList, 80 params: ast::ParamList,
73} 81}
74 82
75impl FunctionBuilder { 83impl FunctionBuilder {
76 fn from_call(ctx: &AssistCtx, call: &ast::CallExpr) -> Option<Self> { 84 /// Prepares a generated function that matches `call` in `generate_in`
77 let append_fn_at = next_space_for_fn(&call)?; 85 /// (or as close to `call` as possible, if `generate_in` is `None`)
78 let fn_name = fn_name(&call)?; 86 fn from_call(
87 ctx: &AssistCtx,
88 call: &ast::CallExpr,
89 path: &ast::Path,
90 generate_in: Option<hir::ModuleSource>,
91 ) -> Option<Self> {
92 let target = if let Some(generate_in_module) = generate_in {
93 next_space_for_fn_in_module(generate_in_module)?
94 } else {
95 next_space_for_fn_after_call_site(&call)?
96 };
97 let fn_name = fn_name(&path)?;
79 let (type_params, params) = fn_args(ctx, &call)?; 98 let (type_params, params) = fn_args(ctx, &call)?;
80 Some(Self { append_fn_at, fn_name, type_params, params }) 99 Some(Self { target, fn_name, type_params, params })
81 } 100 }
82 fn render(self) -> Option<FunctionTemplate> { 101 fn render(self) -> Option<FunctionTemplate> {
83 let placeholder_expr = ast::make::expr_todo(); 102 let placeholder_expr = ast::make::expr_todo();
84 let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr)); 103 let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr));
85 let fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body); 104 let fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body);
86 let fn_def = ast::make::add_newlines(2, fn_def); 105
87 let fn_def = IndentLevel::from_node(&self.append_fn_at).increase_indent(fn_def); 106 let (fn_def, insert_offset) = match self.target {
88 let insert_offset = self.append_fn_at.text_range().end(); 107 GeneratedFunctionTarget::BehindItem(it) => {
108 let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def);
109 let indented = IndentLevel::from_node(&it).increase_indent(with_leading_blank_line);
110 (indented, it.text_range().end())
111 }
112 GeneratedFunctionTarget::InEmptyItemList(it) => {
113 let with_leading_newline = ast::make::add_leading_newlines(1, fn_def);
114 let indent = IndentLevel::from_node(it.syntax()).indented();
115 let mut indented = indent.increase_indent(with_leading_newline);
116 if !item_list_has_whitespace(&it) {
117 // In this case we want to make sure there's a newline between the closing
118 // function brace and the closing module brace (so it doesn't end in `}}`).
119 indented = ast::make::add_trailing_newlines(1, indented);
120 }
121 (indented, it.syntax().text_range().start() + TextUnit::from_usize(1))
122 }
123 };
124
89 let cursor_offset_from_fn_start = fn_def 125 let cursor_offset_from_fn_start = fn_def
90 .syntax() 126 .syntax()
91 .descendants() 127 .descendants()
@@ -98,15 +134,25 @@ impl FunctionBuilder {
98 } 134 }
99} 135}
100 136
101fn fn_name(call: &CallExpr) -> Option<ast::Name> { 137/// Returns true if the given ItemList contains whitespace.
102 let name = call.expr()?.syntax().to_string(); 138fn item_list_has_whitespace(it: &ast::ItemList) -> bool {
139 it.syntax().descendants_with_tokens().find(|it| it.kind() == SyntaxKind::WHITESPACE).is_some()
140}
141
142enum GeneratedFunctionTarget {
143 BehindItem(SyntaxNode),
144 InEmptyItemList(ast::ItemList),
145}
146
147fn fn_name(call: &ast::Path) -> Option<ast::Name> {
148 let name = call.segment()?.syntax().to_string();
103 Some(ast::make::name(&name)) 149 Some(ast::make::name(&name))
104} 150}
105 151
106/// Computes the type variables and arguments required for the generated function 152/// Computes the type variables and arguments required for the generated function
107fn fn_args( 153fn fn_args(
108 ctx: &AssistCtx, 154 ctx: &AssistCtx,
109 call: &CallExpr, 155 call: &ast::CallExpr,
110) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { 156) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> {
111 let mut arg_names = Vec::new(); 157 let mut arg_names = Vec::new();
112 let mut arg_types = Vec::new(); 158 let mut arg_types = Vec::new();
@@ -158,9 +204,9 @@ fn deduplicate_arg_names(arg_names: &mut Vec<String>) {
158 } 204 }
159} 205}
160 206
161fn fn_arg_name(fn_arg: &Expr) -> Option<String> { 207fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
162 match fn_arg { 208 match fn_arg {
163 Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?), 209 ast::Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
164 _ => Some( 210 _ => Some(
165 fn_arg 211 fn_arg
166 .syntax() 212 .syntax()
@@ -172,7 +218,7 @@ fn fn_arg_name(fn_arg: &Expr) -> Option<String> {
172 } 218 }
173} 219}
174 220
175fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> { 221fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> {
176 let ty = ctx.sema.type_of_expr(fn_arg)?; 222 let ty = ctx.sema.type_of_expr(fn_arg)?;
177 if ty.is_unknown() { 223 if ty.is_unknown() {
178 return None; 224 return None;
@@ -184,7 +230,7 @@ fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> {
184/// directly after the current block 230/// directly after the current block
185/// We want to write the generated function directly after 231/// We want to write the generated function directly after
186/// fns, impls or macro calls, but inside mods 232/// fns, impls or macro calls, but inside mods
187fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> { 233fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFunctionTarget> {
188 let mut ancestors = expr.syntax().ancestors().peekable(); 234 let mut ancestors = expr.syntax().ancestors().peekable();
189 let mut last_ancestor: Option<SyntaxNode> = None; 235 let mut last_ancestor: Option<SyntaxNode> = None;
190 while let Some(next_ancestor) = ancestors.next() { 236 while let Some(next_ancestor) = ancestors.next() {
@@ -201,7 +247,26 @@ fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> {
201 } 247 }
202 last_ancestor = Some(next_ancestor); 248 last_ancestor = Some(next_ancestor);
203 } 249 }
204 last_ancestor 250 last_ancestor.map(GeneratedFunctionTarget::BehindItem)
251}
252
253fn next_space_for_fn_in_module(module: hir::ModuleSource) -> Option<GeneratedFunctionTarget> {
254 match module {
255 hir::ModuleSource::SourceFile(it) => {
256 if let Some(last_item) = it.items().last() {
257 Some(GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()))
258 } else {
259 Some(GeneratedFunctionTarget::BehindItem(it.syntax().clone()))
260 }
261 }
262 hir::ModuleSource::Module(it) => {
263 if let Some(last_item) = it.item_list().and_then(|it| it.items().last()) {
264 Some(GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()))
265 } else {
266 it.item_list().map(GeneratedFunctionTarget::InEmptyItemList)
267 }
268 }
269 }
205} 270}
206 271
207#[cfg(test)] 272#[cfg(test)]
@@ -714,6 +779,112 @@ fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
714 } 779 }
715 780
716 #[test] 781 #[test]
782 fn add_function_in_module() {
783 check_assist(
784 add_function,
785 r"
786mod bar {}
787
788fn foo() {
789 bar::my_fn<|>()
790}
791",
792 r"
793mod bar {
794 fn my_fn() {
795 <|>todo!()
796 }
797}
798
799fn foo() {
800 bar::my_fn()
801}
802",
803 );
804 check_assist(
805 add_function,
806 r"
807mod bar {
808}
809
810fn foo() {
811 bar::my_fn<|>()
812}
813",
814 r"
815mod bar {
816 fn my_fn() {
817 <|>todo!()
818 }
819}
820
821fn foo() {
822 bar::my_fn()
823}
824",
825 )
826 }
827
828 #[test]
829 fn add_function_in_module_containing_other_items() {
830 check_assist(
831 add_function,
832 r"
833mod bar {
834 fn something_else() {}
835}
836
837fn foo() {
838 bar::my_fn<|>()
839}
840",
841 r"
842mod bar {
843 fn something_else() {}
844
845 fn my_fn() {
846 <|>todo!()
847 }
848}
849
850fn foo() {
851 bar::my_fn()
852}
853",
854 )
855 }
856
857 #[test]
858 fn add_function_in_nested_module() {
859 check_assist(
860 add_function,
861 r"
862mod bar {
863 mod baz {
864 }
865}
866
867fn foo() {
868 bar::baz::my_fn<|>()
869}
870",
871 r"
872mod bar {
873 mod baz {
874 fn my_fn() {
875 <|>todo!()
876 }
877 }
878}
879
880fn foo() {
881 bar::baz::my_fn()
882}
883",
884 )
885 }
886
887 #[test]
717 fn add_function_not_applicable_if_function_already_exists() { 888 fn add_function_not_applicable_if_function_already_exists() {
718 check_assist_not_applicable( 889 check_assist_not_applicable(
719 add_function, 890 add_function,
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index 0f4a50be4..b0f4803f3 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -293,11 +293,16 @@ pub fn fn_def(
293 ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body)) 293 ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body))
294} 294}
295 295
296pub fn add_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { 296pub fn add_leading_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile {
297 let newlines = "\n".repeat(amount_of_newlines); 297 let newlines = "\n".repeat(amount_of_newlines);
298 ast_from_text(&format!("{}{}", newlines, t.syntax())) 298 ast_from_text(&format!("{}{}", newlines, t.syntax()))
299} 299}
300 300
301pub fn add_trailing_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile {
302 let newlines = "\n".repeat(amount_of_newlines);
303 ast_from_text(&format!("{}{}", t.syntax(), newlines))
304}
305
301fn ast_from_text<N: AstNode>(text: &str) -> N { 306fn ast_from_text<N: AstNode>(text: &str) -> N {
302 let parse = SourceFile::parse(text); 307 let parse = SourceFile::parse(text);
303 let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); 308 let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap();
diff --git a/docs/user/assists.md b/docs/user/assists.md
index 6483ba4f3..6c6943622 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -65,7 +65,7 @@ Adds a stub function with a signature matching the function under the cursor.
65struct Baz; 65struct Baz;
66fn baz() -> Baz { Baz } 66fn baz() -> Baz { Baz }
67fn foo() { 67fn foo() {
68 bar┃("", baz()); 68 bar┃("", baz());
69} 69}
70 70
71 71
@@ -73,7 +73,7 @@ fn foo() {
73struct Baz; 73struct Baz;
74fn baz() -> Baz { Baz } 74fn baz() -> Baz { Baz }
75fn foo() { 75fn foo() {
76 bar("", baz()); 76 bar("", baz());
77} 77}
78 78
79fn bar(arg: &str, baz: Baz) { 79fn bar(arg: &str, baz: Baz) {