11use std:: iter;
22
3- use either:: Either ;
43use ide_db:: syntax_helpers:: node_ext:: is_pattern_cond;
54use syntax:: {
65 AstNode , T ,
@@ -9,6 +8,7 @@ use syntax::{
98 edit:: { AstNodeEdit , IndentLevel } ,
109 make,
1110 } ,
11+ syntax_editor:: { Element , Position } ,
1212} ;
1313
1414use crate :: {
@@ -44,43 +44,53 @@ pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>)
4444 let while_expr = while_kw. parent ( ) . and_then ( ast:: WhileExpr :: cast) ?;
4545 let while_body = while_expr. loop_body ( ) ?;
4646 let while_cond = while_expr. condition ( ) ?;
47+ let l_curly = while_body. stmt_list ( ) ?. l_curly_token ( ) ?;
4748
4849 let target = while_expr. syntax ( ) . text_range ( ) ;
4950 acc. add (
5051 AssistId :: refactor_rewrite ( "convert_while_to_loop" ) ,
5152 "Convert while to loop" ,
5253 target,
53- |edit| {
54+ |builder| {
55+ let mut edit = builder. make_editor ( while_expr. syntax ( ) ) ;
5456 let while_indent_level = IndentLevel :: from_node ( while_expr. syntax ( ) ) ;
5557
5658 let break_block = make:: block_expr (
5759 iter:: once ( make:: expr_stmt ( make:: expr_break ( None , None ) ) . into ( ) ) ,
5860 None ,
5961 )
60- . indent ( while_indent_level) ;
61- let block_expr = if is_pattern_cond ( while_cond. clone ( ) ) {
62- let if_expr = make:: expr_if ( while_cond, while_body, Some ( break_block. into ( ) ) ) ;
62+ . indent ( IndentLevel ( 1 ) ) ;
63+
64+ edit. replace_all (
65+ while_kw. syntax_element ( ) ..=while_cond. syntax ( ) . syntax_element ( ) ,
66+ vec ! [ make:: token( T ![ loop ] ) . syntax_element( ) ] ,
67+ ) ;
68+
69+ if is_pattern_cond ( while_cond. clone ( ) ) {
70+ let then_branch = while_body. reset_indent ( ) . indent ( IndentLevel ( 1 ) ) ;
71+ let if_expr = make:: expr_if ( while_cond, then_branch, Some ( break_block. into ( ) ) ) ;
6372 let stmts = iter:: once ( make:: expr_stmt ( if_expr. into ( ) ) . into ( ) ) ;
64- make:: block_expr ( stmts, None )
73+ let block_expr = make:: block_expr ( stmts, None ) ;
74+ edit. replace ( while_body. syntax ( ) , block_expr. indent ( while_indent_level) . syntax ( ) ) ;
6575 } else {
6676 let if_cond = invert_boolean_expression_legacy ( while_cond) ;
67- let if_expr = make:: expr_if ( if_cond, break_block, None ) . syntax ( ) . clone ( ) . into ( ) ;
68- let elements = while_body. stmt_list ( ) . map_or_else (
69- || Either :: Left ( iter:: empty ( ) ) ,
70- |stmts| {
71- Either :: Right ( stmts. syntax ( ) . children_with_tokens ( ) . filter ( |node_or_tok| {
72- // Filter out the trailing expr
73- !node_or_tok
74- . as_node ( )
75- . is_some_and ( |node| ast:: Expr :: can_cast ( node. kind ( ) ) )
76- } ) )
77- } ,
77+ let if_expr = make:: expr_if ( if_cond, break_block, None ) . indent ( while_indent_level) ;
78+ if !while_body. syntax ( ) . text ( ) . contains_char ( '\n' ) {
79+ edit. insert (
80+ Position :: after ( & l_curly) ,
81+ make:: tokens:: whitespace ( & format ! ( "\n {while_indent_level}" ) ) ,
82+ ) ;
83+ }
84+ edit. insert_all (
85+ Position :: after ( & l_curly) ,
86+ vec ! [
87+ make:: tokens:: whitespace( & format!( "\n {}" , while_indent_level + 1 ) ) . into( ) ,
88+ if_expr. syntax( ) . syntax_element( ) ,
89+ ] ,
7890 ) ;
79- make:: hacky_block_expr ( iter:: once ( if_expr) . chain ( elements) , while_body. tail_expr ( ) )
8091 } ;
8192
82- let replacement = make:: expr_loop ( block_expr. indent ( while_indent_level) ) ;
83- edit. replace ( target, replacement. syntax ( ) . text ( ) )
93+ builder. add_file_edits ( ctx. vfs_file_id ( ) , edit) ;
8494 } ,
8595 )
8696}
@@ -115,6 +125,110 @@ fn main() {
115125 ) ;
116126 }
117127
128+ #[ test]
129+ fn convert_with_label ( ) {
130+ check_assist (
131+ convert_while_to_loop,
132+ r#"
133+ fn main() {
134+ 'x: while$0 cond {
135+ foo();
136+ break 'x
137+ }
138+ }
139+ "# ,
140+ r#"
141+ fn main() {
142+ 'x: loop {
143+ if !cond {
144+ break;
145+ }
146+ foo();
147+ break 'x
148+ }
149+ }
150+ "# ,
151+ ) ;
152+
153+ check_assist (
154+ convert_while_to_loop,
155+ r#"
156+ fn main() {
157+ 'x: while$0 let Some(x) = cond {
158+ foo();
159+ break 'x
160+ }
161+ }
162+ "# ,
163+ r#"
164+ fn main() {
165+ 'x: loop {
166+ if let Some(x) = cond {
167+ foo();
168+ break 'x
169+ } else {
170+ break;
171+ }
172+ }
173+ }
174+ "# ,
175+ ) ;
176+ }
177+
178+ #[ test]
179+ fn convert_with_attributes ( ) {
180+ check_assist (
181+ convert_while_to_loop,
182+ r#"
183+ fn main() {
184+ #[allow(unused)]
185+ while$0 cond {
186+ foo();
187+ break 'x
188+ }
189+ }
190+ "# ,
191+ r#"
192+ fn main() {
193+ #[allow(unused)]
194+ loop {
195+ if !cond {
196+ break;
197+ }
198+ foo();
199+ break 'x
200+ }
201+ }
202+ "# ,
203+ ) ;
204+
205+ check_assist (
206+ convert_while_to_loop,
207+ r#"
208+ fn main() {
209+ #[allow(unused)]
210+ #[deny(unsafe_code)]
211+ while$0 let Some(x) = cond {
212+ foo();
213+ }
214+ }
215+ "# ,
216+ r#"
217+ fn main() {
218+ #[allow(unused)]
219+ #[deny(unsafe_code)]
220+ loop {
221+ if let Some(x) = cond {
222+ foo();
223+ } else {
224+ break;
225+ }
226+ }
227+ }
228+ "# ,
229+ ) ;
230+ }
231+
118232 #[ test]
119233 fn convert_busy_wait ( ) {
120234 check_assist (
@@ -185,6 +299,76 @@ fn main() {
185299 ) ;
186300 }
187301
302+ #[ test]
303+ fn indentation ( ) {
304+ check_assist (
305+ convert_while_to_loop,
306+ r#"
307+ fn main() {
308+ {
309+ {
310+ while$0 cond {
311+ foo(
312+ "xxx",
313+ );
314+ }
315+ }
316+ }
317+ }
318+ "# ,
319+ r#"
320+ fn main() {
321+ {
322+ {
323+ loop {
324+ if !cond {
325+ break;
326+ }
327+ foo(
328+ "xxx",
329+ );
330+ }
331+ }
332+ }
333+ }
334+ "# ,
335+ ) ;
336+
337+ check_assist (
338+ convert_while_to_loop,
339+ r#"
340+ fn main() {
341+ {
342+ {
343+ while$0 let Some(_) = foo() {
344+ bar(
345+ "xxx",
346+ );
347+ }
348+ }
349+ }
350+ }
351+ "# ,
352+ r#"
353+ fn main() {
354+ {
355+ {
356+ loop {
357+ if let Some(_) = foo() {
358+ bar(
359+ "xxx",
360+ );
361+ } else {
362+ break;
363+ }
364+ }
365+ }
366+ }
367+ }
368+ "# ,
369+ ) ;
370+ }
371+
188372 #[ test]
189373 fn ignore_cursor_in_body ( ) {
190374 check_assist_not_applicable (
0 commit comments