aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists')
-rw-r--r--crates/ra_assists/src/assist_ctx.rs8
-rw-r--r--crates/ra_assists/src/doc_tests/generated.rs4
-rw-r--r--crates/ra_assists/src/handlers/add_function.rs236
-rw-r--r--crates/ra_assists/src/lib.rs25
4 files changed, 240 insertions, 33 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index c3e653299..279163257 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -10,7 +10,7 @@ use ra_syntax::{
10}; 10};
11use ra_text_edit::TextEditBuilder; 11use ra_text_edit::TextEditBuilder;
12 12
13use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; 13use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
14use algo::SyntaxRewriter; 14use algo::SyntaxRewriter;
15 15
16#[derive(Clone, Debug)] 16#[derive(Clone, Debug)]
@@ -180,6 +180,7 @@ pub(crate) struct ActionBuilder {
180 edit: TextEditBuilder, 180 edit: TextEditBuilder,
181 cursor_position: Option<TextUnit>, 181 cursor_position: Option<TextUnit>,
182 target: Option<TextRange>, 182 target: Option<TextRange>,
183 file: AssistFile,
183} 184}
184 185
185impl ActionBuilder { 186impl ActionBuilder {
@@ -241,11 +242,16 @@ impl ActionBuilder {
241 algo::diff(&node, &new).into_text_edit(&mut self.edit) 242 algo::diff(&node, &new).into_text_edit(&mut self.edit)
242 } 243 }
243 244
245 pub(crate) fn set_file(&mut self, assist_file: AssistFile) {
246 self.file = assist_file
247 }
248
244 fn build(self) -> AssistAction { 249 fn build(self) -> AssistAction {
245 AssistAction { 250 AssistAction {
246 edit: self.edit.finish(), 251 edit: self.edit.finish(),
247 cursor_position: self.cursor_position, 252 cursor_position: self.cursor_position,
248 target: self.target, 253 target: self.target,
254 file: self.file,
249 } 255 }
250 } 256 }
251} 257}
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..f185cffdb 100644
--- a/crates/ra_assists/src/handlers/add_function.rs
+++ b/crates/ra_assists/src/handlers/add_function.rs
@@ -3,8 +3,8 @@ use ra_syntax::{
3 SyntaxKind, SyntaxNode, TextUnit, 3 SyntaxKind, SyntaxNode, TextUnit,
4}; 4};
5 5
6use crate::{Assist, AssistCtx, AssistId}; 6use crate::{Assist, AssistCtx, AssistFile, 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,21 +38,30 @@ 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(module))) =
48 ctx.sema.resolve_path(&qualifier)
49 {
50 Some(module.definition_source(ctx.sema.db))
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());
54 62
55 if let Some(function_template) = function_builder.render() { 63 if let Some(function_template) = function_builder.render() {
64 edit.set_file(function_template.file);
56 edit.set_cursor(function_template.cursor_offset); 65 edit.set_cursor(function_template.cursor_offset);
57 edit.insert(function_template.insert_offset, function_template.fn_def.to_string()); 66 edit.insert(function_template.insert_offset, function_template.fn_def.to_string());
58 } 67 }
@@ -63,29 +72,67 @@ struct FunctionTemplate {
63 insert_offset: TextUnit, 72 insert_offset: TextUnit,
64 cursor_offset: TextUnit, 73 cursor_offset: TextUnit,
65 fn_def: ast::SourceFile, 74 fn_def: ast::SourceFile,
75 file: AssistFile,
66} 76}
67 77
68struct FunctionBuilder { 78struct FunctionBuilder {
69 append_fn_at: SyntaxNode, 79 target: GeneratedFunctionTarget,
70 fn_name: ast::Name, 80 fn_name: ast::Name,
71 type_params: Option<ast::TypeParamList>, 81 type_params: Option<ast::TypeParamList>,
72 params: ast::ParamList, 82 params: ast::ParamList,
83 file: AssistFile,
84 needs_pub: bool,
73} 85}
74 86
75impl FunctionBuilder { 87impl FunctionBuilder {
76 fn from_call(ctx: &AssistCtx, call: &ast::CallExpr) -> Option<Self> { 88 /// Prepares a generated function that matches `call` in `generate_in`
77 let append_fn_at = next_space_for_fn(&call)?; 89 /// (or as close to `call` as possible, if `generate_in` is `None`)
78 let fn_name = fn_name(&call)?; 90 fn from_call(
91 ctx: &AssistCtx,
92 call: &ast::CallExpr,
93 path: &ast::Path,
94 target_module: Option<hir::InFile<hir::ModuleSource>>,
95 ) -> Option<Self> {
96 let needs_pub = target_module.is_some();
97 let mut file = AssistFile::default();
98 let target = if let Some(target_module) = target_module {
99 let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?;
100 file = in_file;
101 target
102 } else {
103 next_space_for_fn_after_call_site(&call)?
104 };
105 let fn_name = fn_name(&path)?;
79 let (type_params, params) = fn_args(ctx, &call)?; 106 let (type_params, params) = fn_args(ctx, &call)?;
80 Some(Self { append_fn_at, fn_name, type_params, params }) 107 Some(Self { target, fn_name, type_params, params, file, needs_pub })
81 } 108 }
109
82 fn render(self) -> Option<FunctionTemplate> { 110 fn render(self) -> Option<FunctionTemplate> {
83 let placeholder_expr = ast::make::expr_todo(); 111 let placeholder_expr = ast::make::expr_todo();
84 let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr)); 112 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); 113 let mut 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); 114 if self.needs_pub {
87 let fn_def = IndentLevel::from_node(&self.append_fn_at).increase_indent(fn_def); 115 fn_def = ast::make::add_pub_crate_modifier(fn_def);
88 let insert_offset = self.append_fn_at.text_range().end(); 116 }
117
118 let (fn_def, insert_offset) = match self.target {
119 GeneratedFunctionTarget::BehindItem(it) => {
120 let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def);
121 let indented = IndentLevel::from_node(&it).increase_indent(with_leading_blank_line);
122 (indented, it.text_range().end())
123 }
124 GeneratedFunctionTarget::InEmptyItemList(it) => {
125 let indent_once = IndentLevel(1);
126 let indent = IndentLevel::from_node(it.syntax());
127
128 let fn_def = ast::make::add_leading_newlines(1, fn_def);
129 let fn_def = indent_once.increase_indent(fn_def);
130 let fn_def = ast::make::add_trailing_newlines(1, fn_def);
131 let fn_def = indent.increase_indent(fn_def);
132 (fn_def, it.syntax().text_range().start() + TextUnit::from_usize(1))
133 }
134 };
135
89 let cursor_offset_from_fn_start = fn_def 136 let cursor_offset_from_fn_start = fn_def
90 .syntax() 137 .syntax()
91 .descendants() 138 .descendants()
@@ -94,19 +141,24 @@ impl FunctionBuilder {
94 .text_range() 141 .text_range()
95 .start(); 142 .start();
96 let cursor_offset = insert_offset + cursor_offset_from_fn_start; 143 let cursor_offset = insert_offset + cursor_offset_from_fn_start;
97 Some(FunctionTemplate { insert_offset, cursor_offset, fn_def }) 144 Some(FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file })
98 } 145 }
99} 146}
100 147
101fn fn_name(call: &CallExpr) -> Option<ast::Name> { 148enum GeneratedFunctionTarget {
102 let name = call.expr()?.syntax().to_string(); 149 BehindItem(SyntaxNode),
150 InEmptyItemList(ast::ItemList),
151}
152
153fn fn_name(call: &ast::Path) -> Option<ast::Name> {
154 let name = call.segment()?.syntax().to_string();
103 Some(ast::make::name(&name)) 155 Some(ast::make::name(&name))
104} 156}
105 157
106/// Computes the type variables and arguments required for the generated function 158/// Computes the type variables and arguments required for the generated function
107fn fn_args( 159fn fn_args(
108 ctx: &AssistCtx, 160 ctx: &AssistCtx,
109 call: &CallExpr, 161 call: &ast::CallExpr,
110) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { 162) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> {
111 let mut arg_names = Vec::new(); 163 let mut arg_names = Vec::new();
112 let mut arg_types = Vec::new(); 164 let mut arg_types = Vec::new();
@@ -158,9 +210,9 @@ fn deduplicate_arg_names(arg_names: &mut Vec<String>) {
158 } 210 }
159} 211}
160 212
161fn fn_arg_name(fn_arg: &Expr) -> Option<String> { 213fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
162 match fn_arg { 214 match fn_arg {
163 Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?), 215 ast::Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
164 _ => Some( 216 _ => Some(
165 fn_arg 217 fn_arg
166 .syntax() 218 .syntax()
@@ -172,7 +224,7 @@ fn fn_arg_name(fn_arg: &Expr) -> Option<String> {
172 } 224 }
173} 225}
174 226
175fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> { 227fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> {
176 let ty = ctx.sema.type_of_expr(fn_arg)?; 228 let ty = ctx.sema.type_of_expr(fn_arg)?;
177 if ty.is_unknown() { 229 if ty.is_unknown() {
178 return None; 230 return None;
@@ -184,7 +236,7 @@ fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> {
184/// directly after the current block 236/// directly after the current block
185/// We want to write the generated function directly after 237/// We want to write the generated function directly after
186/// fns, impls or macro calls, but inside mods 238/// fns, impls or macro calls, but inside mods
187fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> { 239fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFunctionTarget> {
188 let mut ancestors = expr.syntax().ancestors().peekable(); 240 let mut ancestors = expr.syntax().ancestors().peekable();
189 let mut last_ancestor: Option<SyntaxNode> = None; 241 let mut last_ancestor: Option<SyntaxNode> = None;
190 while let Some(next_ancestor) = ancestors.next() { 242 while let Some(next_ancestor) = ancestors.next() {
@@ -201,7 +253,32 @@ fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> {
201 } 253 }
202 last_ancestor = Some(next_ancestor); 254 last_ancestor = Some(next_ancestor);
203 } 255 }
204 last_ancestor 256 last_ancestor.map(GeneratedFunctionTarget::BehindItem)
257}
258
259fn next_space_for_fn_in_module(
260 db: &dyn hir::db::AstDatabase,
261 module: hir::InFile<hir::ModuleSource>,
262) -> Option<(AssistFile, GeneratedFunctionTarget)> {
263 let file = module.file_id.original_file(db);
264 let assist_file = AssistFile::TargetFile(file);
265 let assist_item = match module.value {
266 hir::ModuleSource::SourceFile(it) => {
267 if let Some(last_item) = it.items().last() {
268 GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
269 } else {
270 GeneratedFunctionTarget::BehindItem(it.syntax().clone())
271 }
272 }
273 hir::ModuleSource::Module(it) => {
274 if let Some(last_item) = it.item_list().and_then(|it| it.items().last()) {
275 GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
276 } else {
277 GeneratedFunctionTarget::InEmptyItemList(it.item_list()?)
278 }
279 }
280 };
281 Some((assist_file, assist_item))
205} 282}
206 283
207#[cfg(test)] 284#[cfg(test)]
@@ -714,6 +791,111 @@ fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
714 } 791 }
715 792
716 #[test] 793 #[test]
794 fn add_function_in_module() {
795 check_assist(
796 add_function,
797 r"
798mod bar {}
799
800fn foo() {
801 bar::my_fn<|>()
802}
803",
804 r"
805mod bar {
806 pub(crate) fn my_fn() {
807 <|>todo!()
808 }
809}
810
811fn foo() {
812 bar::my_fn()
813}
814",
815 )
816 }
817
818 #[test]
819 fn add_function_in_module_containing_other_items() {
820 check_assist(
821 add_function,
822 r"
823mod bar {
824 fn something_else() {}
825}
826
827fn foo() {
828 bar::my_fn<|>()
829}
830",
831 r"
832mod bar {
833 fn something_else() {}
834
835 pub(crate) fn my_fn() {
836 <|>todo!()
837 }
838}
839
840fn foo() {
841 bar::my_fn()
842}
843",
844 )
845 }
846
847 #[test]
848 fn add_function_in_nested_module() {
849 check_assist(
850 add_function,
851 r"
852mod bar {
853 mod baz {}
854}
855
856fn foo() {
857 bar::baz::my_fn<|>()
858}
859",
860 r"
861mod bar {
862 mod baz {
863 pub(crate) fn my_fn() {
864 <|>todo!()
865 }
866 }
867}
868
869fn foo() {
870 bar::baz::my_fn()
871}
872",
873 )
874 }
875
876 #[test]
877 fn add_function_in_another_file() {
878 check_assist(
879 add_function,
880 r"
881//- /main.rs
882mod foo;
883
884fn main() {
885 foo::bar<|>()
886}
887//- /foo.rs
888",
889 r"
890
891
892pub(crate) fn bar() {
893 <|>todo!()
894}",
895 )
896 }
897
898 #[test]
717 fn add_function_not_applicable_if_function_already_exists() { 899 fn add_function_not_applicable_if_function_already_exists() {
718 check_assist_not_applicable( 900 check_assist_not_applicable(
719 add_function, 901 add_function,
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index a00136da1..ccc95735f 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -17,7 +17,7 @@ mod doc_tests;
17pub mod utils; 17pub mod utils;
18pub mod ast_transform; 18pub mod ast_transform;
19 19
20use ra_db::FileRange; 20use ra_db::{FileId, FileRange};
21use ra_ide_db::RootDatabase; 21use ra_ide_db::RootDatabase;
22use ra_syntax::{TextRange, TextUnit}; 22use ra_syntax::{TextRange, TextUnit};
23use ra_text_edit::TextEdit; 23use ra_text_edit::TextEdit;
@@ -54,6 +54,7 @@ pub struct AssistAction {
54 pub cursor_position: Option<TextUnit>, 54 pub cursor_position: Option<TextUnit>,
55 // FIXME: This belongs to `AssistLabel` 55 // FIXME: This belongs to `AssistLabel`
56 pub target: Option<TextRange>, 56 pub target: Option<TextRange>,
57 pub file: AssistFile,
57} 58}
58 59
59#[derive(Debug, Clone)] 60#[derive(Debug, Clone)]
@@ -63,6 +64,18 @@ pub struct ResolvedAssist {
63 pub action: AssistAction, 64 pub action: AssistAction,
64} 65}
65 66
67#[derive(Debug, Clone, Copy)]
68pub enum AssistFile {
69 CurrentFile,
70 TargetFile(FileId),
71}
72
73impl Default for AssistFile {
74 fn default() -> Self {
75 Self::CurrentFile
76 }
77}
78
66/// Return all the assists applicable at the given position. 79/// Return all the assists applicable at the given position.
67/// 80///
68/// Assists are returned in the "unresolved" state, that is only labels are 81/// Assists are returned in the "unresolved" state, that is only labels are
@@ -184,7 +197,7 @@ mod helpers {
184 use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; 197 use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
185 use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset}; 198 use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset};
186 199
187 use crate::{AssistCtx, AssistHandler}; 200 use crate::{AssistCtx, AssistFile, AssistHandler};
188 use hir::Semantics; 201 use hir::Semantics;
189 202
190 pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { 203 pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
@@ -246,7 +259,13 @@ mod helpers {
246 (Some(assist), ExpectedResult::After(after)) => { 259 (Some(assist), ExpectedResult::After(after)) => {
247 let action = assist.0[0].action.clone().unwrap(); 260 let action = assist.0[0].action.clone().unwrap();
248 261
249 let mut actual = action.edit.apply(&text_without_caret); 262 let assisted_file_text = if let AssistFile::TargetFile(file_id) = action.file {
263 db.file_text(file_id).as_ref().to_owned()
264 } else {
265 text_without_caret
266 };
267
268 let mut actual = action.edit.apply(&assisted_file_text);
250 match action.cursor_position { 269 match action.cursor_position {
251 None => { 270 None => {
252 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { 271 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {