diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_assists/src/handlers/add_function.rs | 115 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 25 |
2 files changed, 73 insertions, 67 deletions
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs index f6721d9df..488bae08f 100644 --- a/crates/ra_assists/src/handlers/add_function.rs +++ b/crates/ra_assists/src/handlers/add_function.rs | |||
@@ -1,12 +1,11 @@ | |||
1 | use ra_syntax::{ | 1 | use ra_syntax::{ |
2 | ast::{self, AstNode}, | 2 | ast::{self, AstNode}, |
3 | SmolStr, SyntaxKind, SyntaxNode, TextUnit, | 3 | SyntaxKind, SyntaxNode, TextUnit, |
4 | }; | 4 | }; |
5 | 5 | ||
6 | use crate::{Assist, AssistCtx, AssistId}; | 6 | use crate::{Assist, AssistCtx, AssistId}; |
7 | use ast::{ArgListOwner, CallExpr, Expr}; | 7 | use ast::{edit::IndentLevel, ArgListOwner, CallExpr, Expr}; |
8 | use hir::HirDisplay; | 8 | use hir::HirDisplay; |
9 | use ra_fmt::leading_indent; | ||
10 | use rustc_hash::{FxHashMap, FxHashSet}; | 9 | use rustc_hash::{FxHashMap, FxHashSet}; |
11 | 10 | ||
12 | // Assist: add_function | 11 | // Assist: add_function |
@@ -53,73 +52,62 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> { | |||
53 | ctx.add_assist(AssistId("add_function"), "Add function", |edit| { | 52 | ctx.add_assist(AssistId("add_function"), "Add function", |edit| { |
54 | edit.target(call.syntax().text_range()); | 53 | edit.target(call.syntax().text_range()); |
55 | 54 | ||
56 | let function_template = function_builder.render(); | 55 | if let Some(function_template) = function_builder.render() { |
57 | edit.set_cursor(function_template.cursor_offset); | 56 | edit.set_cursor(function_template.cursor_offset); |
58 | edit.insert(function_template.insert_offset, function_template.fn_text); | 57 | edit.insert(function_template.insert_offset, function_template.fn_def.to_string()); |
58 | } | ||
59 | }) | 59 | }) |
60 | } | 60 | } |
61 | 61 | ||
62 | struct FunctionTemplate { | 62 | struct FunctionTemplate { |
63 | insert_offset: TextUnit, | 63 | insert_offset: TextUnit, |
64 | cursor_offset: TextUnit, | 64 | cursor_offset: TextUnit, |
65 | fn_text: String, | 65 | fn_def: ast::SourceFile, |
66 | } | 66 | } |
67 | 67 | ||
68 | struct FunctionBuilder { | 68 | struct FunctionBuilder { |
69 | start_offset: TextUnit, | 69 | append_fn_at: SyntaxNode, |
70 | fn_name: String, | 70 | fn_name: ast::Name, |
71 | fn_generics: String, | 71 | type_params: Option<ast::TypeParamList>, |
72 | fn_args: String, | 72 | params: ast::ParamList, |
73 | indent: String, | ||
74 | } | 73 | } |
75 | 74 | ||
76 | impl FunctionBuilder { | 75 | impl FunctionBuilder { |
77 | fn from_call(ctx: &AssistCtx, call: &ast::CallExpr) -> Option<Self> { | 76 | fn from_call(ctx: &AssistCtx, call: &ast::CallExpr) -> Option<Self> { |
78 | let (start, indent) = next_space_for_fn(&call)?; | 77 | let append_fn_at = next_space_for_fn(&call)?; |
79 | let fn_name = fn_name(&call)?; | 78 | let fn_name = fn_name(&call)?; |
80 | let fn_generics = fn_generics(&call)?; | 79 | let (type_params, params) = fn_args(ctx, &call)?; |
81 | let fn_args = fn_args(ctx, &call)?; | 80 | Some(Self { append_fn_at, fn_name, type_params, params }) |
82 | let indent = if let Some(i) = &indent { i.to_string() } else { String::new() }; | 81 | } |
83 | Some(Self { start_offset: start, fn_name, fn_generics, fn_args, indent }) | 82 | fn render(self) -> Option<FunctionTemplate> { |
84 | } | 83 | let placeholder_expr = ast::make::expr_unimplemented(); |
85 | fn render(&self) -> FunctionTemplate { | 84 | let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr)); |
86 | let mut fn_buf = String::with_capacity(128); | 85 | let fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body); |
87 | fn_buf.push_str("\n\n"); | 86 | let fn_def = ast::make::add_newlines(2, fn_def); |
88 | fn_buf.push_str(&self.indent); | 87 | let fn_def = IndentLevel::from_node(&self.append_fn_at).increase_indent(fn_def); |
89 | fn_buf.push_str("fn "); | 88 | let insert_offset = self.append_fn_at.text_range().end(); |
90 | fn_buf.push_str(&self.fn_name); | 89 | let cursor_offset_from_fn_start = fn_def |
91 | fn_buf.push_str(&self.fn_generics); | 90 | .syntax() |
92 | fn_buf.push_str(&self.fn_args); | 91 | .descendants() |
93 | fn_buf.push_str(" {\n"); | 92 | .find_map(ast::MacroCall::cast)? |
94 | fn_buf.push_str(&self.indent); | 93 | .syntax() |
95 | fn_buf.push_str(" "); | 94 | .text_range() |
96 | 95 | .start(); | |
97 | // We take the offset here to put the cursor in front of the `unimplemented!()` body | 96 | let cursor_offset = insert_offset + cursor_offset_from_fn_start; |
98 | let offset = TextUnit::of_str(&fn_buf); | 97 | Some(FunctionTemplate { insert_offset, cursor_offset, fn_def }) |
99 | 98 | } | |
100 | fn_buf.push_str("unimplemented!()\n"); | 99 | } |
101 | fn_buf.push_str(&self.indent); | 100 | |
102 | fn_buf.push_str("}"); | 101 | fn fn_name(call: &CallExpr) -> Option<ast::Name> { |
103 | 102 | let name = call.expr()?.syntax().to_string(); | |
104 | let cursor_pos = self.start_offset + offset; | 103 | Some(ast::make::name(&name)) |
105 | FunctionTemplate { | 104 | } |
106 | fn_text: fn_buf, | 105 | |
107 | cursor_offset: cursor_pos, | 106 | /// Computes the type variables and arguments required for the generated function |
108 | insert_offset: self.start_offset, | 107 | fn fn_args( |
109 | } | 108 | ctx: &AssistCtx, |
110 | } | 109 | call: &CallExpr, |
111 | } | 110 | ) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { |
112 | |||
113 | fn fn_name(call: &CallExpr) -> Option<String> { | ||
114 | Some(call.expr()?.syntax().to_string()) | ||
115 | } | ||
116 | |||
117 | fn fn_generics(_call: &CallExpr) -> Option<String> { | ||
118 | // TODO | ||
119 | Some("".into()) | ||
120 | } | ||
121 | |||
122 | fn fn_args(ctx: &AssistCtx, call: &CallExpr) -> Option<String> { | ||
123 | let mut arg_names = Vec::new(); | 111 | let mut arg_names = Vec::new(); |
124 | let mut arg_types = Vec::new(); | 112 | let mut arg_types = Vec::new(); |
125 | for arg in call.arg_list()?.args() { | 113 | for arg in call.arg_list()?.args() { |
@@ -134,15 +122,8 @@ fn fn_args(ctx: &AssistCtx, call: &CallExpr) -> Option<String> { | |||
134 | }); | 122 | }); |
135 | } | 123 | } |
136 | deduplicate_arg_names(&mut arg_names); | 124 | deduplicate_arg_names(&mut arg_names); |
137 | Some(format!( | 125 | let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| ast::make::param(name, ty)); |
138 | "({})", | 126 | Some((None, ast::make::param_list(params))) |
139 | arg_names | ||
140 | .into_iter() | ||
141 | .zip(arg_types) | ||
142 | .map(|(name, ty)| format!("{}: {}", name, ty)) | ||
143 | .collect::<Vec<_>>() | ||
144 | .join(", ") | ||
145 | )) | ||
146 | } | 127 | } |
147 | 128 | ||
148 | /// Makes duplicate argument names unique by appending incrementing numbers. | 129 | /// Makes duplicate argument names unique by appending incrementing numbers. |
@@ -203,7 +184,7 @@ fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> { | |||
203 | /// directly after the current block | 184 | /// directly after the current block |
204 | /// We want to write the generated function directly after | 185 | /// We want to write the generated function directly after |
205 | /// fns, impls or macro calls, but inside mods | 186 | /// fns, impls or macro calls, but inside mods |
206 | fn next_space_for_fn(expr: &CallExpr) -> Option<(TextUnit, Option<SmolStr>)> { | 187 | fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> { |
207 | let mut ancestors = expr.syntax().ancestors().peekable(); | 188 | let mut ancestors = expr.syntax().ancestors().peekable(); |
208 | let mut last_ancestor: Option<SyntaxNode> = None; | 189 | let mut last_ancestor: Option<SyntaxNode> = None; |
209 | while let Some(next_ancestor) = ancestors.next() { | 190 | while let Some(next_ancestor) = ancestors.next() { |
@@ -220,7 +201,7 @@ fn next_space_for_fn(expr: &CallExpr) -> Option<(TextUnit, Option<SmolStr>)> { | |||
220 | } | 201 | } |
221 | last_ancestor = Some(next_ancestor); | 202 | last_ancestor = Some(next_ancestor); |
222 | } | 203 | } |
223 | last_ancestor.map(|a| (a.text_range().end(), leading_indent(&a))) | 204 | last_ancestor |
224 | } | 205 | } |
225 | 206 | ||
226 | #[cfg(test)] | 207 | #[cfg(test)] |
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 0c908573d..dbb9c8a9b 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -269,6 +269,31 @@ pub fn unreachable_macro_call() -> ast::MacroCall { | |||
269 | ast_from_text(&format!("unreachable!()")) | 269 | ast_from_text(&format!("unreachable!()")) |
270 | } | 270 | } |
271 | 271 | ||
272 | pub fn param(name: String, ty: String) -> ast::Param { | ||
273 | ast_from_text(&format!("fn f({}: {}) {{ }}", name, ty)) | ||
274 | } | ||
275 | |||
276 | pub fn param_list(pats: impl IntoIterator<Item = ast::Param>) -> ast::ParamList { | ||
277 | let args = pats.into_iter().join(", "); | ||
278 | ast_from_text(&format!("fn f({}) {{ }}", args)) | ||
279 | } | ||
280 | |||
281 | pub fn fn_def( | ||
282 | fn_name: ast::Name, | ||
283 | type_params: Option<ast::TypeParamList>, | ||
284 | params: ast::ParamList, | ||
285 | body: ast::BlockExpr, | ||
286 | ) -> ast::FnDef { | ||
287 | let type_params = | ||
288 | if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() }; | ||
289 | ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body)) | ||
290 | } | ||
291 | |||
292 | pub fn add_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { | ||
293 | let newlines = "\n".repeat(amount_of_newlines); | ||
294 | ast_from_text(&format!("{}{}", newlines, t.syntax())) | ||
295 | } | ||
296 | |||
272 | fn ast_from_text<N: AstNode>(text: &str) -> N { | 297 | fn ast_from_text<N: AstNode>(text: &str) -> N { |
273 | let parse = SourceFile::parse(text); | 298 | let parse = SourceFile::parse(text); |
274 | let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); | 299 | let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); |