@@ -6,10 +6,11 @@ use ide_db::RootDatabase;
66use ide_db:: syntax_helpers:: suggest_name;
77use ide_db:: { famous_defs:: FamousDefs , helpers:: mod_path_to_ast} ;
88use itertools:: Itertools ;
9- use syntax:: ToSmolStr ;
10- use syntax:: ast:: edit:: { AstNodeEdit , IndentLevel } ;
9+ use syntax:: ast:: edit:: IndentLevel ;
1110use syntax:: ast:: syntax_factory:: SyntaxFactory ;
1211use syntax:: ast:: { self , AstNode , MatchArmList , MatchExpr , Pat , make} ;
12+ use syntax:: syntax_editor:: { Position , SyntaxEditor } ;
13+ use syntax:: { SyntaxKind , SyntaxNode , ToSmolStr } ;
1314
1415use crate :: { AssistContext , AssistId , Assists , utils} ;
1516
@@ -223,32 +224,13 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
223224 // having any hidden variants means that we need a catch-all arm
224225 needs_catch_all_arm |= has_hidden_variants;
225226
226- let missing_arms = missing_pats
227+ let mut missing_arms = missing_pats
227228 . filter ( |( _, hidden) | {
228229 // filter out hidden patterns because they're handled by the catch-all arm
229230 !hidden
230231 } )
231- . map ( |( pat, _) | make. match_arm ( pat, None , utils:: expr_fill_default ( ctx. config ) ) ) ;
232-
233- let mut arms: Vec < _ > = match_arm_list
234- . arms ( )
235- . filter ( |arm| {
236- if matches ! ( arm. pat( ) , Some ( ast:: Pat :: WildcardPat ( _) ) ) {
237- if arm. expr ( ) . is_none_or ( is_empty_expr) {
238- false
239- } else {
240- cov_mark:: hit!( add_missing_match_arms_empty_expr) ;
241- true
242- }
243- } else {
244- true
245- }
246- } )
247- . map ( |arm| arm. reset_indent ( ) . indent ( IndentLevel ( 1 ) ) )
248- . collect ( ) ;
249-
250- let first_new_arm_idx = arms. len ( ) ;
251- arms. extend ( missing_arms) ;
232+ . map ( |( pat, _) | make. match_arm ( pat, None , utils:: expr_fill_default ( ctx. config ) ) )
233+ . collect :: < Vec < _ > > ( ) ;
252234
253235 if needs_catch_all_arm && !has_catch_all_arm {
254236 cov_mark:: hit!( added_wildcard_pattern) ;
@@ -257,13 +239,11 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
257239 None ,
258240 utils:: expr_fill_default ( ctx. config ) ,
259241 ) ;
260- arms . push ( arm) ;
242+ missing_arms . push ( arm) ;
261243 }
262244
263- let new_match_arm_list = make. match_arm_list ( arms) ;
264-
265245 // FIXME: Hack for syntax trees not having great support for macros
266- // Just replace the element that the original range came from
246+ // Just edit the element that the original range came from
267247 let old_place = {
268248 // Find the original element
269249 let file = ctx. sema . parse ( arm_list_range. file_id ) ;
@@ -280,25 +260,27 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
280260 } ;
281261
282262 let mut editor = builder. make_editor ( & old_place) ;
283- let new_match_arm_list = new_match_arm_list. indent ( IndentLevel :: from_node ( & old_place) ) ;
284- editor. replace ( old_place, new_match_arm_list. syntax ( ) ) ;
263+ let mut arms_edit = ArmsEdit { match_arm_list, place : old_place, last_arm : None } ;
264+
265+ arms_edit. remove_wildcard_arms ( ctx, & mut editor) ;
266+ arms_edit. add_comma_after_last_arm ( ctx, & make, & mut editor) ;
267+ arms_edit. append_arms ( & missing_arms, & make, & mut editor) ;
285268
286269 if let Some ( cap) = ctx. config . snippet_cap {
287- if let Some ( it) = new_match_arm_list
288- . arms ( )
289- . nth ( first_new_arm_idx)
270+ if let Some ( it) = missing_arms
271+ . first ( )
290272 . and_then ( |arm| arm. syntax ( ) . descendants ( ) . find_map ( ast:: WildcardPat :: cast) )
291273 {
292274 editor. add_annotation ( it. syntax ( ) , builder. make_placeholder_snippet ( cap) ) ;
293275 }
294276
295- for arm in new_match_arm_list . arms ( ) . skip ( first_new_arm_idx ) {
277+ for arm in & missing_arms {
296278 if let Some ( expr) = arm. expr ( ) {
297279 editor. add_annotation ( expr. syntax ( ) , builder. make_placeholder_snippet ( cap) ) ;
298280 }
299281 }
300282
301- if let Some ( arm) = new_match_arm_list . arms ( ) . skip ( first_new_arm_idx ) . last ( ) {
283+ if let Some ( arm) = missing_arms . last ( ) {
302284 editor. add_annotation ( arm. syntax ( ) , builder. make_tabstop_after ( cap) ) ;
303285 }
304286 }
@@ -357,6 +339,101 @@ fn cursor_at_trivial_match_arm_list(
357339 None
358340}
359341
342+ struct ArmsEdit {
343+ match_arm_list : MatchArmList ,
344+ place : SyntaxNode ,
345+ last_arm : Option < ast:: MatchArm > ,
346+ }
347+
348+ impl ArmsEdit {
349+ fn remove_wildcard_arms ( & mut self , ctx : & AssistContext < ' _ > , editor : & mut SyntaxEditor ) {
350+ for arm in self . match_arm_list . arms ( ) {
351+ if !matches ! ( arm. pat( ) , Some ( Pat :: WildcardPat ( _) ) ) {
352+ self . last_arm = Some ( arm) ;
353+ continue ;
354+ }
355+ if !arm. expr ( ) . is_none_or ( is_empty_expr) {
356+ cov_mark:: hit!( add_missing_match_arms_empty_expr) ;
357+ self . last_arm = Some ( arm) ;
358+ continue ;
359+ }
360+ let Some ( range) = self . cover_edit_range ( ctx, & arm) else { continue } ;
361+
362+ let prev = match range. start ( ) {
363+ syntax:: NodeOrToken :: Node ( node) => {
364+ node. first_token ( ) . and_then ( |it| it. prev_token ( ) )
365+ }
366+ syntax:: NodeOrToken :: Token ( tok) => tok. prev_token ( ) ,
367+ } ;
368+ if let Some ( prev) = prev
369+ && prev. kind ( ) == SyntaxKind :: WHITESPACE
370+ {
371+ editor. delete ( prev) ;
372+ }
373+
374+ editor. delete_all ( range) ;
375+ }
376+ }
377+
378+ fn append_arms ( & self , arms : & [ ast:: MatchArm ] , make : & SyntaxFactory , editor : & mut SyntaxEditor ) {
379+ let Some ( mut before) = self . place . last_token ( ) else {
380+ stdx:: never!( "match arm list not contain any token" ) ;
381+ return ;
382+ } ;
383+ if let Some ( prev) = before. prev_token ( )
384+ && prev. kind ( ) == SyntaxKind :: WHITESPACE
385+ {
386+ before = prev;
387+ }
388+ let open_curly =
389+ !self . place . text ( ) . contains_char ( '\n' ) || before. kind ( ) == SyntaxKind :: WHITESPACE ;
390+ let indent = IndentLevel :: from_node ( & self . place ) ;
391+ let arm_indent = indent + 1 ;
392+ let indent = make. whitespace ( & format ! ( "\n {indent}" ) ) ;
393+ let arm_indent = make. whitespace ( & format ! ( "\n {arm_indent}" ) ) ;
394+ let elements = arms
395+ . iter ( )
396+ . flat_map ( |arm| [ arm_indent. clone ( ) . into ( ) , arm. syntax ( ) . clone ( ) . into ( ) ] )
397+ . chain ( open_curly. then ( || indent. clone ( ) . into ( ) ) )
398+ . collect ( ) ;
399+
400+ if before. kind ( ) == SyntaxKind :: WHITESPACE {
401+ editor. replace_with_many ( before, elements) ;
402+ } else {
403+ editor. insert_all ( Position :: before ( before) , elements) ;
404+ }
405+ }
406+
407+ fn add_comma_after_last_arm (
408+ & self ,
409+ ctx : & AssistContext < ' _ > ,
410+ make : & SyntaxFactory ,
411+ editor : & mut SyntaxEditor ,
412+ ) {
413+ if let Some ( last_arm) = & self . last_arm
414+ && last_arm. comma_token ( ) . is_none ( )
415+ && last_arm. expr ( ) . is_none_or ( |it| !it. is_block_like ( ) )
416+ && let Some ( range) = self . cover_edit_range ( ctx, last_arm)
417+ {
418+ editor. insert ( Position :: after ( range. end ( ) ) , make. token ( syntax:: T ![ , ] ) ) ;
419+ }
420+ }
421+
422+ fn cover_edit_range (
423+ & self ,
424+ ctx : & AssistContext < ' _ > ,
425+ node : & impl AstNode ,
426+ ) -> Option < std:: ops:: RangeInclusive < syntax:: SyntaxElement > > {
427+ let range = ctx. sema . original_range_opt ( node. syntax ( ) ) ?;
428+
429+ if !self . place . text_range ( ) . contains_range ( range. range ) {
430+ return None ;
431+ }
432+
433+ Some ( utils:: cover_edit_range ( & self . place , range. range ) )
434+ }
435+ }
436+
360437fn is_variant_missing ( existing_pats : & [ Pat ] , var : & Pat ) -> bool {
361438 !existing_pats. iter ( ) . any ( |pat| does_pat_match_variant ( pat, var) )
362439}
@@ -1734,7 +1811,7 @@ enum Test {
17341811
17351812fn foo(t: Test) {
17361813 m!(match t {
1737- Test::A=> (),
1814+ Test::A => (),
17381815 Test::B => ${1:todo!()},
17391816 Test::C => ${2:todo!()},$0
17401817 });
@@ -2172,6 +2249,35 @@ fn foo(t: E) {
21722249 ) ;
21732250 }
21742251
2252+ #[ test]
2253+ fn keep_comments ( ) {
2254+ check_assist (
2255+ add_missing_match_arms,
2256+ r#"
2257+ enum E { A, B, C }
2258+
2259+ fn foo(t: E) -> i32 {
2260+ match $0t {
2261+ // variant a
2262+ E::A => 2
2263+ // comment on end
2264+ }
2265+ }"# ,
2266+ r#"
2267+ enum E { A, B, C }
2268+
2269+ fn foo(t: E) -> i32 {
2270+ match t {
2271+ // variant a
2272+ E::A => 2,
2273+ // comment on end
2274+ E::B => ${1:todo!()},
2275+ E::C => ${2:todo!()},$0
2276+ }
2277+ }"# ,
2278+ ) ;
2279+ }
2280+
21752281 #[ test]
21762282 fn not_applicable_when_match_arm_list_cannot_be_upmapped ( ) {
21772283 check_assist_not_applicable (
0 commit comments