1- use ide_db:: base_db:: SourceDatabase ;
2- use syntax:: TextSize ;
31use syntax:: {
4- algo:: non_trivia_sibling, ast, AstNode , Direction , SyntaxKind , SyntaxToken , TextRange , T ,
2+ algo:: non_trivia_sibling,
3+ ast:: { self , syntax_factory:: SyntaxFactory } ,
4+ syntax_editor:: { Element , SyntaxMapping } ,
5+ AstNode , Direction , NodeOrToken , SyntaxElement , SyntaxKind , SyntaxToken , T ,
56} ;
67
78use crate :: { AssistContext , AssistId , AssistKind , Assists } ;
@@ -25,8 +26,6 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
2526 let comma = ctx. find_token_syntax_at_offset ( T ! [ , ] ) ?;
2627 let prev = non_trivia_sibling ( comma. clone ( ) . into ( ) , Direction :: Prev ) ?;
2728 let next = non_trivia_sibling ( comma. clone ( ) . into ( ) , Direction :: Next ) ?;
28- let ( mut prev_text, mut next_text) = ( prev. to_string ( ) , next. to_string ( ) ) ;
29- let ( mut prev_range, mut next_range) = ( prev. text_range ( ) , next. text_range ( ) ) ;
3029
3130 // Don't apply a "flip" in case of a last comma
3231 // that typically comes before punctuation
@@ -40,53 +39,85 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
4039 return None ;
4140 }
4241
43- if let Some ( parent) = comma. parent ( ) . and_then ( ast:: TokenTree :: cast) {
44- // An attribute. It often contains a path followed by a token tree (e.g. `align(2)`), so we have
45- // to be smarter.
46- let prev_start =
47- match comma. siblings_with_tokens ( Direction :: Prev ) . skip ( 1 ) . find ( |it| it. kind ( ) == T ! [ , ] )
48- {
49- Some ( it) => position_after_token ( it. as_token ( ) . unwrap ( ) ) ,
50- None => position_after_token ( & parent. left_delimiter_token ( ) ?) ,
51- } ;
52- let prev_end = prev. text_range ( ) . end ( ) ;
53- let next_start = next. text_range ( ) . start ( ) ;
54- let next_end =
55- match comma. siblings_with_tokens ( Direction :: Next ) . skip ( 1 ) . find ( |it| it. kind ( ) == T ! [ , ] )
56- {
57- Some ( it) => position_before_token ( it. as_token ( ) . unwrap ( ) ) ,
58- None => position_before_token ( & parent. right_delimiter_token ( ) ?) ,
59- } ;
60- prev_range = TextRange :: new ( prev_start, prev_end) ;
61- next_range = TextRange :: new ( next_start, next_end) ;
62- let file_text = ctx. db ( ) . file_text ( ctx. file_id ( ) . file_id ( ) ) ;
63- prev_text = file_text[ prev_range] . to_owned ( ) ;
64- next_text = file_text[ next_range] . to_owned ( ) ;
65- }
42+ // FIXME: remove `clone_for_update` when `SyntaxEditor` handles it for us
43+ let prev = match prev {
44+ SyntaxElement :: Node ( node) => node. clone_for_update ( ) . syntax_element ( ) ,
45+ _ => prev,
46+ } ;
47+ let next = match next {
48+ SyntaxElement :: Node ( node) => node. clone_for_update ( ) . syntax_element ( ) ,
49+ _ => next,
50+ } ;
6651
6752 acc. add (
6853 AssistId ( "flip_comma" , AssistKind :: RefactorRewrite ) ,
6954 "Flip comma" ,
7055 comma. text_range ( ) ,
71- |edit| {
72- edit. replace ( prev_range, next_text) ;
73- edit. replace ( next_range, prev_text) ;
56+ |builder| {
57+ let parent = comma. parent ( ) . unwrap ( ) ;
58+ let mut editor = builder. make_editor ( & parent) ;
59+
60+ if let Some ( parent) = ast:: TokenTree :: cast ( parent) {
61+ // An attribute. It often contains a path followed by a
62+ // token tree (e.g. `align(2)`), so we have to be smarter.
63+ let ( new_tree, mapping) = flip_tree ( parent. clone ( ) , comma) ;
64+ editor. replace ( parent. syntax ( ) , new_tree. syntax ( ) ) ;
65+ editor. add_mappings ( mapping) ;
66+ } else {
67+ editor. replace ( prev. clone ( ) , next. clone ( ) ) ;
68+ editor. replace ( next. clone ( ) , prev. clone ( ) ) ;
69+ }
70+
71+ builder. add_file_edits ( ctx. file_id ( ) , editor) ;
7472 } ,
7573 )
7674}
7775
78- fn position_before_token ( token : & SyntaxToken ) -> TextSize {
79- match non_trivia_sibling ( token. clone ( ) . into ( ) , Direction :: Prev ) {
80- Some ( prev_token) => prev_token. text_range ( ) . end ( ) ,
81- None => token. text_range ( ) . start ( ) ,
82- }
83- }
84-
85- fn position_after_token ( token : & SyntaxToken ) -> TextSize {
86- match non_trivia_sibling ( token. clone ( ) . into ( ) , Direction :: Next ) {
87- Some ( prev_token) => prev_token. text_range ( ) . start ( ) ,
88- None => token. text_range ( ) . end ( ) ,
89- }
76+ fn flip_tree ( tree : ast:: TokenTree , comma : SyntaxToken ) -> ( ast:: TokenTree , SyntaxMapping ) {
77+ let mut tree_iter = tree. token_trees_and_tokens ( ) ;
78+ let before: Vec < _ > =
79+ tree_iter. by_ref ( ) . take_while ( |it| it. as_token ( ) != Some ( & comma) ) . collect ( ) ;
80+ let after: Vec < _ > = tree_iter. collect ( ) ;
81+
82+ let not_ws = |element : & NodeOrToken < _ , SyntaxToken > | match element {
83+ NodeOrToken :: Token ( token) => token. kind ( ) != SyntaxKind :: WHITESPACE ,
84+ NodeOrToken :: Node ( _) => true ,
85+ } ;
86+
87+ let is_comma = |element : & NodeOrToken < _ , SyntaxToken > | match element {
88+ NodeOrToken :: Token ( token) => token. kind ( ) == T ! [ , ] ,
89+ NodeOrToken :: Node ( _) => false ,
90+ } ;
91+
92+ let prev_start_untrimmed = match before. iter ( ) . rposition ( is_comma) {
93+ Some ( pos) => pos + 1 ,
94+ None => 1 ,
95+ } ;
96+ let prev_end = 1 + before. iter ( ) . rposition ( not_ws) . unwrap ( ) ;
97+ let prev_start = prev_start_untrimmed
98+ + before[ prev_start_untrimmed..prev_end] . iter ( ) . position ( not_ws) . unwrap ( ) ;
99+
100+ let next_start = after. iter ( ) . position ( not_ws) . unwrap ( ) ;
101+ let next_end_untrimmed = match after. iter ( ) . position ( is_comma) {
102+ Some ( pos) => pos,
103+ None => after. len ( ) - 1 ,
104+ } ;
105+ let next_end = 1 + after[ ..next_end_untrimmed] . iter ( ) . rposition ( not_ws) . unwrap ( ) ;
106+
107+ let result = [
108+ & before[ 1 ..prev_start] ,
109+ & after[ next_start..next_end] ,
110+ & before[ prev_end..] ,
111+ & [ NodeOrToken :: Token ( comma) ] ,
112+ & after[ ..next_start] ,
113+ & before[ prev_start..prev_end] ,
114+ & after[ next_end..after. len ( ) - 1 ] ,
115+ ]
116+ . concat ( ) ;
117+
118+ let make = SyntaxFactory :: new ( ) ;
119+ let new_token_tree = make. token_tree ( tree. left_delimiter_token ( ) . unwrap ( ) . kind ( ) , result) ;
120+ ( new_token_tree, make. finish_with_mappings ( ) )
90121}
91122
92123#[ cfg( test) ]
@@ -147,4 +178,9 @@ mod tests {
147178 r#"#[foo(bar, qux, baz(1 + 1), other)] struct Foo;"# ,
148179 ) ;
149180 }
181+
182+ #[ test]
183+ fn flip_comma_attribute_incomplete ( ) {
184+ check_assist_not_applicable ( flip_comma, r#"#[repr(align(2),$0)] struct Foo;"# ) ;
185+ }
150186}
0 commit comments