diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/assists/src/handlers/extract_function.rs | 246 |
1 files changed, 112 insertions, 134 deletions
diff --git a/crates/assists/src/handlers/extract_function.rs b/crates/assists/src/handlers/extract_function.rs index d876eabca..4bddd4eec 100644 --- a/crates/assists/src/handlers/extract_function.rs +++ b/crates/assists/src/handlers/extract_function.rs | |||
@@ -1,3 +1,6 @@ | |||
1 | use std::{fmt, iter}; | ||
2 | |||
3 | use ast::make; | ||
1 | use either::Either; | 4 | use either::Either; |
2 | use hir::{HirDisplay, Local}; | 5 | use hir::{HirDisplay, Local}; |
3 | use ide_db::{ | 6 | use ide_db::{ |
@@ -82,10 +85,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
82 | return None; | 85 | return None; |
83 | } | 86 | } |
84 | 87 | ||
85 | let target_range = match &body { | 88 | let target_range = body.text_range(); |
86 | FunctionBody::Expr(expr) => expr.syntax().text_range(), | ||
87 | FunctionBody::Span { .. } => ctx.frange.range, | ||
88 | }; | ||
89 | 89 | ||
90 | acc.add( | 90 | acc.add( |
91 | AssistId("extract_function", crate::AssistKind::RefactorExtract), | 91 | AssistId("extract_function", crate::AssistKind::RefactorExtract), |
@@ -209,7 +209,7 @@ impl RetType { | |||
209 | #[derive(Debug)] | 209 | #[derive(Debug)] |
210 | enum FunctionBody { | 210 | enum FunctionBody { |
211 | Expr(ast::Expr), | 211 | Expr(ast::Expr), |
212 | Span { elements: Vec<SyntaxElement>, leading_indent: String }, | 212 | Span { parent: ast::BlockExpr, text_range: TextRange }, |
213 | } | 213 | } |
214 | 214 | ||
215 | impl FunctionBody { | 215 | impl FunctionBody { |
@@ -226,58 +226,28 @@ impl FunctionBody { | |||
226 | } | 226 | } |
227 | } | 227 | } |
228 | 228 | ||
229 | fn from_range(node: &SyntaxNode, range: TextRange) -> Option<FunctionBody> { | 229 | fn from_range(node: SyntaxNode, text_range: TextRange) -> Option<FunctionBody> { |
230 | let mut first = node.token_at_offset(range.start()).left_biased()?; | 230 | let block = ast::BlockExpr::cast(node)?; |
231 | let last = node.token_at_offset(range.end()).right_biased()?; | 231 | Some(Self::Span { parent: block, text_range }) |
232 | |||
233 | let mut leading_indent = String::new(); | ||
234 | |||
235 | let leading_trivia = first | ||
236 | .siblings_with_tokens(Direction::Prev) | ||
237 | .skip(1) | ||
238 | .take_while(|e| e.kind() == SyntaxKind::WHITESPACE && e.as_token().is_some()); | ||
239 | |||
240 | for e in leading_trivia { | ||
241 | let token = e.as_token().unwrap(); | ||
242 | let text = token.text(); | ||
243 | match text.rfind('\n') { | ||
244 | Some(pos) => { | ||
245 | leading_indent = text[pos..].to_owned(); | ||
246 | break; | ||
247 | } | ||
248 | None => first = token.clone(), | ||
249 | } | ||
250 | } | ||
251 | |||
252 | let mut elements: Vec<_> = first | ||
253 | .siblings_with_tokens(Direction::Next) | ||
254 | .take_while(|e| e.as_token() != Some(&last)) | ||
255 | .collect(); | ||
256 | |||
257 | if !(last.kind() == SyntaxKind::WHITESPACE && last.text().lines().count() <= 2) { | ||
258 | elements.push(last.into()); | ||
259 | } | ||
260 | |||
261 | Some(FunctionBody::Span { elements, leading_indent }) | ||
262 | } | 232 | } |
263 | 233 | ||
264 | fn indent_level(&self) -> IndentLevel { | 234 | fn indent_level(&self) -> IndentLevel { |
265 | match &self { | 235 | match &self { |
266 | FunctionBody::Expr(expr) => IndentLevel::from_node(expr.syntax()), | 236 | FunctionBody::Expr(expr) => IndentLevel::from_node(expr.syntax()), |
267 | FunctionBody::Span { elements, .. } => elements | 237 | FunctionBody::Span { parent, .. } => IndentLevel::from_node(parent.syntax()), |
268 | .iter() | ||
269 | .filter_map(SyntaxElement::as_node) | ||
270 | .map(IndentLevel::from_node) | ||
271 | .min_by_key(|level| level.0) | ||
272 | .expect("body must contain at least one node"), | ||
273 | } | 238 | } |
274 | } | 239 | } |
275 | 240 | ||
276 | fn tail_expr(&self) -> Option<ast::Expr> { | 241 | fn tail_expr(&self) -> Option<ast::Expr> { |
277 | match &self { | 242 | match &self { |
278 | FunctionBody::Expr(expr) => Some(expr.clone()), | 243 | FunctionBody::Expr(expr) => Some(expr.clone()), |
279 | FunctionBody::Span { elements, .. } => { | 244 | FunctionBody::Span { parent, text_range } => { |
280 | elements.iter().rev().find_map(|e| e.as_node()).cloned().and_then(ast::Expr::cast) | 245 | let tail_expr = parent.tail_expr()?; |
246 | if text_range.contains_range(tail_expr.syntax().text_range()) { | ||
247 | Some(tail_expr) | ||
248 | } else { | ||
249 | None | ||
250 | } | ||
281 | } | 251 | } |
282 | } | 252 | } |
283 | } | 253 | } |
@@ -285,11 +255,11 @@ impl FunctionBody { | |||
285 | fn descendants(&self) -> impl Iterator<Item = SyntaxNode> + '_ { | 255 | fn descendants(&self) -> impl Iterator<Item = SyntaxNode> + '_ { |
286 | match self { | 256 | match self { |
287 | FunctionBody::Expr(expr) => Either::Right(expr.syntax().descendants()), | 257 | FunctionBody::Expr(expr) => Either::Right(expr.syntax().descendants()), |
288 | FunctionBody::Span { elements, .. } => Either::Left( | 258 | FunctionBody::Span { parent, text_range } => Either::Left( |
289 | elements | 259 | parent |
290 | .iter() | 260 | .syntax() |
291 | .filter_map(SyntaxElement::as_node) | 261 | .descendants() |
292 | .flat_map(SyntaxNode::descendants), | 262 | .filter(move |it| text_range.contains_range(it.text_range())), |
293 | ), | 263 | ), |
294 | } | 264 | } |
295 | } | 265 | } |
@@ -297,10 +267,7 @@ impl FunctionBody { | |||
297 | fn text_range(&self) -> TextRange { | 267 | fn text_range(&self) -> TextRange { |
298 | match self { | 268 | match self { |
299 | FunctionBody::Expr(expr) => expr.syntax().text_range(), | 269 | FunctionBody::Expr(expr) => expr.syntax().text_range(), |
300 | FunctionBody::Span { elements, .. } => TextRange::new( | 270 | FunctionBody::Span { parent: _, text_range } => *text_range, |
301 | elements.first().unwrap().text_range().start(), | ||
302 | elements.last().unwrap().text_range().end(), | ||
303 | ), | ||
304 | } | 271 | } |
305 | } | 272 | } |
306 | 273 | ||
@@ -321,30 +288,27 @@ impl HasTokenAtOffset for FunctionBody { | |||
321 | fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset<SyntaxToken> { | 288 | fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset<SyntaxToken> { |
322 | match self { | 289 | match self { |
323 | FunctionBody::Expr(expr) => expr.syntax().token_at_offset(offset), | 290 | FunctionBody::Expr(expr) => expr.syntax().token_at_offset(offset), |
324 | FunctionBody::Span { elements, .. } => { | 291 | FunctionBody::Span { parent, text_range } => { |
325 | stdx::always!(self.text_range().contains(offset)); | 292 | match parent.syntax().token_at_offset(offset) { |
326 | let mut iter = elements | 293 | TokenAtOffset::None => TokenAtOffset::None, |
327 | .iter() | 294 | TokenAtOffset::Single(t) => { |
328 | .filter(|element| element.text_range().contains_inclusive(offset)); | 295 | if text_range.contains_range(t.text_range()) { |
329 | let element1 = iter.next().expect("offset does not fall into body"); | 296 | TokenAtOffset::Single(t) |
330 | let element2 = iter.next(); | 297 | } else { |
331 | stdx::always!(iter.next().is_none(), "> 2 tokens at offset"); | 298 | TokenAtOffset::None |
332 | let t1 = match element1 { | 299 | } |
333 | syntax::NodeOrToken::Node(node) => node.token_at_offset(offset), | 300 | } |
334 | syntax::NodeOrToken::Token(token) => TokenAtOffset::Single(token.clone()), | 301 | TokenAtOffset::Between(a, b) => { |
335 | }; | 302 | match ( |
336 | let t2 = element2.map(|e| match e { | 303 | text_range.contains_range(a.text_range()), |
337 | syntax::NodeOrToken::Node(node) => node.token_at_offset(offset), | 304 | text_range.contains_range(b.text_range()), |
338 | syntax::NodeOrToken::Token(token) => TokenAtOffset::Single(token.clone()), | 305 | ) { |
339 | }); | 306 | (true, true) => TokenAtOffset::Between(a, b), |
340 | 307 | (true, false) => TokenAtOffset::Single(a), | |
341 | match t2 { | 308 | (false, true) => TokenAtOffset::Single(b), |
342 | Some(t2) => match (t1.clone().right_biased(), t2.clone().left_biased()) { | 309 | (false, false) => TokenAtOffset::None, |
343 | (Some(e1), Some(e2)) => TokenAtOffset::Between(e1, e2), | 310 | } |
344 | (Some(_), None) => t1, | 311 | } |
345 | (None, _) => t2, | ||
346 | }, | ||
347 | None => t1, | ||
348 | } | 312 | } |
349 | } | 313 | } |
350 | } | 314 | } |
@@ -389,7 +353,7 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu | |||
389 | // we have selected a few statements in a block | 353 | // we have selected a few statements in a block |
390 | // so covering_element returns the whole block | 354 | // so covering_element returns the whole block |
391 | if node.kind() == BLOCK_EXPR { | 355 | if node.kind() == BLOCK_EXPR { |
392 | let body = FunctionBody::from_range(&node, selection_range); | 356 | let body = FunctionBody::from_range(node.clone(), selection_range); |
393 | if body.is_some() { | 357 | if body.is_some() { |
394 | return body; | 358 | return body; |
395 | } | 359 | } |
@@ -400,7 +364,7 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu | |||
400 | // so we try to expand covering_element to parent and repeat the previous | 364 | // so we try to expand covering_element to parent and repeat the previous |
401 | if let Some(parent) = node.parent() { | 365 | if let Some(parent) = node.parent() { |
402 | if parent.kind() == BLOCK_EXPR { | 366 | if parent.kind() == BLOCK_EXPR { |
403 | let body = FunctionBody::from_range(&parent, selection_range); | 367 | let body = FunctionBody::from_range(parent, selection_range); |
404 | if body.is_some() { | 368 | if body.is_some() { |
405 | return body; | 369 | return body; |
406 | } | 370 | } |
@@ -642,13 +606,9 @@ fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode { | |||
642 | 606 | ||
643 | /// checks if local variable is used after(outside of) body | 607 | /// checks if local variable is used after(outside of) body |
644 | fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool { | 608 | fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool { |
645 | let usages = Definition::Local(*var) | 609 | let usages = LocalUsages::find(ctx, *var); |
646 | .usages(&ctx.sema) | 610 | let has_usages = usages.iter().any(|reference| body.preceedes_range(reference.range)); |
647 | .in_scope(SearchScope::single_file(ctx.frange.file_id)) | 611 | has_usages |
648 | .all(); | ||
649 | let mut usages = usages.iter().flat_map(|(_, rs)| rs.iter()); | ||
650 | |||
651 | usages.any(|reference| body.preceedes_range(reference.range)) | ||
652 | } | 612 | } |
653 | 613 | ||
654 | fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> { | 614 | fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> { |
@@ -675,10 +635,7 @@ enum Anchor { | |||
675 | fn scope_for_fn_insertion(body: &FunctionBody, anchor: Anchor) -> Option<SyntaxNode> { | 635 | fn scope_for_fn_insertion(body: &FunctionBody, anchor: Anchor) -> Option<SyntaxNode> { |
676 | match body { | 636 | match body { |
677 | FunctionBody::Expr(e) => scope_for_fn_insertion_node(e.syntax(), anchor), | 637 | FunctionBody::Expr(e) => scope_for_fn_insertion_node(e.syntax(), anchor), |
678 | FunctionBody::Span { elements, .. } => { | 638 | FunctionBody::Span { parent, .. } => scope_for_fn_insertion_node(parent.syntax(), anchor), |
679 | let node = elements.iter().find_map(|e| e.as_node())?; | ||
680 | scope_for_fn_insertion_node(&node, anchor) | ||
681 | } | ||
682 | } | 639 | } |
683 | } | 640 | } |
684 | 641 | ||
@@ -768,9 +725,8 @@ fn format_function( | |||
768 | format_function_param_list_to(&mut fn_def, ctx, module, fun); | 725 | format_function_param_list_to(&mut fn_def, ctx, module, fun); |
769 | fn_def.push(')'); | 726 | fn_def.push(')'); |
770 | format_function_ret_to(&mut fn_def, ctx, module, fun); | 727 | format_function_ret_to(&mut fn_def, ctx, module, fun); |
771 | fn_def.push_str(" {"); | 728 | fn_def.push(' '); |
772 | format_function_body_to(&mut fn_def, ctx, old_indent, new_indent, fun); | 729 | format_function_body_to(&mut fn_def, ctx, old_indent, new_indent, fun); |
773 | format_to!(fn_def, "{}}}", new_indent); | ||
774 | 730 | ||
775 | fn_def | 731 | fn_def |
776 | } | 732 | } |
@@ -836,60 +792,82 @@ fn format_function_body_to( | |||
836 | new_indent: IndentLevel, | 792 | new_indent: IndentLevel, |
837 | fun: &Function, | 793 | fun: &Function, |
838 | ) { | 794 | ) { |
839 | match &fun.body { | 795 | let block = match &fun.body { |
840 | FunctionBody::Expr(expr) => { | 796 | FunctionBody::Expr(expr) => { |
841 | fn_def.push('\n'); | 797 | let expr = rewrite_body_segment(ctx, &fun.params, expr.syntax()); |
842 | let expr = expr.dedent(old_indent).indent(new_indent + 1); | 798 | let expr = ast::Expr::cast(expr).unwrap(); |
843 | let expr = fix_param_usages(ctx, &fun.params, expr.syntax()); | 799 | let expr = expr.dedent(old_indent).indent(IndentLevel(1)); |
844 | format_to!(fn_def, "{}{}", new_indent + 1, expr); | 800 | let block = make::block_expr(Vec::new(), Some(expr)); |
845 | fn_def.push('\n'); | 801 | block.indent(new_indent) |
846 | } | 802 | } |
847 | FunctionBody::Span { elements, leading_indent } => { | 803 | FunctionBody::Span { parent, text_range } => { |
848 | format_to!(fn_def, "{}", leading_indent); | 804 | let mut elements: Vec<_> = parent |
849 | let new_indent_str = format!("\n{}", new_indent + 1); | 805 | .syntax() |
850 | for mut element in elements { | 806 | .children() |
851 | let new_ws; | 807 | .filter(|it| text_range.contains_range(it.text_range())) |
852 | if let Some(ws) = element.as_token().cloned().and_then(ast::Whitespace::cast) { | 808 | .map(|it| rewrite_body_segment(ctx, &fun.params, &it)) |
853 | let text = ws.syntax().text(); | 809 | .collect(); |
854 | if text.contains('\n') { | 810 | |
855 | let new_text = text.replace(&format!("\n{}", old_indent), &new_indent_str); | 811 | let mut tail_expr = match elements.pop() { |
856 | new_ws = ast::make::tokens::whitespace(&new_text).into(); | 812 | Some(node) => ast::Expr::cast(node.clone()).or_else(|| { |
857 | element = &new_ws; | 813 | elements.push(node); |
858 | } | 814 | None |
859 | } | 815 | }), |
816 | None => None, | ||
817 | }; | ||
860 | 818 | ||
861 | match element { | 819 | if tail_expr.is_none() { |
862 | syntax::NodeOrToken::Node(node) => { | 820 | match fun.vars_defined_in_body_and_outlive.as_slice() { |
863 | format_to!(fn_def, "{}", fix_param_usages(ctx, &fun.params, node)); | 821 | [] => {} |
864 | } | 822 | [var] => { |
865 | syntax::NodeOrToken::Token(token) => { | 823 | tail_expr = Some(path_expr_from_local(ctx, *var)); |
866 | format_to!(fn_def, "{}", token); | 824 | }, |
825 | vars => { | ||
826 | let exprs = vars.iter() | ||
827 | .map(|var| path_expr_from_local(ctx, *var)); | ||
828 | let expr = make::expr_tuple(exprs); | ||
829 | tail_expr = Some(expr); | ||
867 | } | 830 | } |
868 | } | 831 | } |
869 | } | 832 | } |
870 | if !fn_def.ends_with('\n') { | 833 | |
871 | fn_def.push('\n'); | 834 | let elements = elements.into_iter().filter_map(|node| match ast::Stmt::cast(node) { |
872 | } | 835 | Some(stmt) => Some(stmt), |
836 | None => { | ||
837 | stdx::never!("block contains non-statement"); | ||
838 | None | ||
839 | } | ||
840 | }); | ||
841 | |||
842 | |||
843 | let block = make::block_expr(elements, tail_expr); | ||
844 | block.dedent(old_indent).indent(new_indent) | ||
873 | } | 845 | } |
874 | } | 846 | }; |
875 | 847 | ||
876 | match fun.vars_defined_in_body_and_outlive.as_slice() { | ||
877 | [] => {} | ||
878 | [var] => format_to!(fn_def, "{}{}\n", new_indent + 1, var.name(ctx.db()).unwrap()), | ||
879 | [v0, vs @ ..] => { | ||
880 | format_to!(fn_def, "{}({}", new_indent + 1, v0.name(ctx.db()).unwrap()); | ||
881 | for var in vs { | ||
882 | format_to!(fn_def, ", {}", var.name(ctx.db()).unwrap()); | ||
883 | } | ||
884 | fn_def.push_str(")\n"); | ||
885 | } | 848 | } |
886 | } | 849 | |
850 | format_to!(fn_def, "{}", block); | ||
851 | } | ||
852 | |||
853 | fn path_expr_from_local(ctx: &AssistContext, var: Local) -> ast::Expr { | ||
854 | let name = var.name(ctx.db()).unwrap().to_string(); | ||
855 | let path = make::path_unqualified(make::path_segment(make::name_ref(&name))); | ||
856 | make::expr_path(path) | ||
887 | } | 857 | } |
888 | 858 | ||
889 | fn format_type(ty: &hir::Type, ctx: &AssistContext, module: hir::Module) -> String { | 859 | fn format_type(ty: &hir::Type, ctx: &AssistContext, module: hir::Module) -> String { |
890 | ty.display_source_code(ctx.db(), module.into()).ok().unwrap_or_else(|| "()".to_string()) | 860 | ty.display_source_code(ctx.db(), module.into()).ok().unwrap_or_else(|| "()".to_string()) |
891 | } | 861 | } |
892 | 862 | ||
863 | fn rewrite_body_segment( | ||
864 | ctx: &AssistContext, | ||
865 | params: &[Param], | ||
866 | syntax: &SyntaxNode, | ||
867 | ) -> SyntaxNode { | ||
868 | fix_param_usages(ctx, params, syntax) | ||
869 | } | ||
870 | |||
893 | /// change all usages to account for added `&`/`&mut` for some params | 871 | /// change all usages to account for added `&`/`&mut` for some params |
894 | fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode { | 872 | fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode { |
895 | let mut rewriter = SyntaxRewriter::default(); | 873 | let mut rewriter = SyntaxRewriter::default(); |
@@ -919,7 +897,7 @@ fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) | |||
919 | rewriter.replace_ast(&node.clone().into(), &node.expr().unwrap()); | 897 | rewriter.replace_ast(&node.clone().into(), &node.expr().unwrap()); |
920 | } | 898 | } |
921 | Some(_) | None => { | 899 | Some(_) | None => { |
922 | rewriter.replace_ast(&path, &ast::make::expr_prefix(T![*], path.clone())); | 900 | rewriter.replace_ast(&path, &make::expr_prefix(T![*], path.clone())); |
923 | } | 901 | } |
924 | }; | 902 | }; |
925 | } | 903 | } |