Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/convert_range_for_to_while.rs')
| -rw-r--r-- | crates/ide-assists/src/handlers/convert_range_for_to_while.rs | 157 |
1 files changed, 151 insertions, 6 deletions
diff --git a/crates/ide-assists/src/handlers/convert_range_for_to_while.rs b/crates/ide-assists/src/handlers/convert_range_for_to_while.rs index ba577b217d..2e649f14be 100644 --- a/crates/ide-assists/src/handlers/convert_range_for_to_while.rs +++ b/crates/ide-assists/src/handlers/convert_range_for_to_while.rs @@ -1,13 +1,15 @@ use ide_db::assists::AssistId; use itertools::Itertools; use syntax::{ - AstNode, T, + AstNode, SyntaxElement, + SyntaxKind::WHITESPACE, + T, algo::previous_non_trivia_token, ast::{ self, HasArgList, HasLoopBody, HasName, RangeItem, edit::AstNodeEdit, make, syntax_factory::SyntaxFactory, }, - syntax_editor::{Element, Position}, + syntax_editor::{Element, Position, SyntaxEditor}, }; use crate::assist_context::{AssistContext, Assists}; @@ -40,8 +42,8 @@ pub(crate) fn convert_range_for_to_while(acc: &mut Assists, ctx: &AssistContext< let iterable = for_.iterable()?; let (start, end, step, inclusive) = extract_range(&iterable)?; let name = pat.name()?; - let body = for_.loop_body()?; - let last = previous_non_trivia_token(body.stmt_list()?.r_curly_token()?)?; + let body = for_.loop_body()?.stmt_list()?; + let label = for_.label(); let description = if end.is_some() { "Replace with while expression" @@ -90,8 +92,10 @@ pub(crate) fn convert_range_for_to_while(acc: &mut Assists, ctx: &AssistContext< ); let op = ast::BinaryOp::Assignment { op: Some(ast::ArithOp::Add) }; - edit.insert_all( - Position::after(last), + process_loop_body( + body, + label, + &mut edit, vec![ make.whitespace(&format!("\n{}", indent + 1)).syntax_element(), make.expr_bin(var_expr, op, step).syntax().syntax_element(), @@ -121,6 +125,86 @@ fn extract_range(iterable: &ast::Expr) -> Option<(ast::Expr, Option<ast::Expr>, }) } +fn process_loop_body( + body: ast::StmtList, + label: Option<ast::Label>, + edit: &mut SyntaxEditor, + incrementer: Vec<SyntaxElement>, +) -> Option<()> { + let last = previous_non_trivia_token(body.r_curly_token()?)?.syntax_element(); + + let new_body = body.indent(1.into()).clone_subtree(); + let mut continues = vec![]; + collect_continue_to( + &mut continues, + &label.and_then(|it| it.lifetime()), + new_body.syntax(), + false, + ); + + if continues.is_empty() { + edit.insert_all(Position::after(last), incrementer); + return Some(()); + } + + let mut children = body + .syntax() + .children_with_tokens() + .filter(|it| !matches!(it.kind(), WHITESPACE | T!['{'] | T!['}'])); + let first = children.next()?; + let block_content = first.clone()..=children.last().unwrap_or(first); + + let continue_label = make::lifetime("'cont"); + let break_expr = make::expr_break(Some(continue_label.clone()), None).clone_for_update(); + let mut new_edit = SyntaxEditor::new(new_body.syntax().clone()); + for continue_expr in &continues { + new_edit.replace(continue_expr.syntax(), break_expr.syntax()); + } + let new_body = new_edit.finish().new_root().clone(); + let elements = itertools::chain( + [ + continue_label.syntax().clone_for_update().syntax_element(), + make::token(T![:]).syntax_element(), + make::tokens::single_space().syntax_element(), + new_body.syntax_element(), + ], + incrementer, + ); + edit.replace_all(block_content, elements.collect()); + + Some(()) +} + +fn collect_continue_to( + acc: &mut Vec<ast::ContinueExpr>, + label: &Option<ast::Lifetime>, + node: &syntax::SyntaxNode, + only_label: bool, +) { + let match_label = |it: &Option<ast::Lifetime>, label: &Option<ast::Lifetime>| match (it, label) + { + (None, _) => !only_label, + (Some(a), Some(b)) if a.text() == b.text() => true, + _ => false, + }; + if let Some(expr) = ast::ContinueExpr::cast(node.clone()) + && match_label(&expr.lifetime(), label) + { + acc.push(expr); + } else if let Some(any_loop) = ast::AnyHasLoopBody::cast(node.clone()) { + if match_label(label, &any_loop.label().and_then(|it| it.lifetime())) { + return; + } + for children in node.children() { + collect_continue_to(acc, label, &children, true); + } + } else { + for children in node.children() { + collect_continue_to(acc, label, &children, only_label); + } + } +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; @@ -220,6 +304,67 @@ fn foo() { } #[test] + fn test_convert_range_for_to_while_with_continue() { + check_assist( + convert_range_for_to_while, + " +fn foo() { + $0for mut i in 3..7 { + foo(i); + continue; + loop { break; continue } + bar(i); + } +} + ", + " +fn foo() { + let mut i = 3; + while i < 7 { + 'cont: { + foo(i); + break 'cont; + loop { break; continue } + bar(i); + } + i += 1; + } +} + ", + ); + + check_assist( + convert_range_for_to_while, + " +fn foo() { + 'x: $0for mut i in 3..7 { + foo(i); + continue 'x; + loop { break; continue 'x } + 'x: loop { continue 'x } + bar(i); + } +} + ", + " +fn foo() { + let mut i = 3; + 'x: while i < 7 { + 'cont: { + foo(i); + break 'cont; + loop { break; break 'cont } + 'x: loop { continue 'x } + bar(i); + } + i += 1; + } +} + ", + ); + } + + #[test] fn test_convert_range_for_to_while_step_by() { check_assist( convert_range_for_to_while, |