1
- use syntax:: { algo:: non_trivia_sibling, Direction , SyntaxKind , T } ;
1
+ use ide_db:: base_db:: SourceDatabase ;
2
+ use syntax:: TextSize ;
3
+ use syntax:: {
4
+ algo:: non_trivia_sibling, ast, AstNode , Direction , SyntaxKind , SyntaxToken , TextRange , T ,
5
+ } ;
2
6
3
7
use crate :: { AssistContext , AssistId , AssistKind , Assists } ;
4
8
@@ -21,6 +25,8 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
21
25
let comma = ctx. find_token_syntax_at_offset ( T ! [ , ] ) ?;
22
26
let prev = non_trivia_sibling ( comma. clone ( ) . into ( ) , Direction :: Prev ) ?;
23
27
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 ( ) ) ;
24
30
25
31
// Don't apply a "flip" in case of a last comma
26
32
// that typically comes before punctuation
@@ -34,17 +40,55 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
34
40
return None ;
35
41
}
36
42
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
+ }
66
+
37
67
acc. add (
38
68
AssistId ( "flip_comma" , AssistKind :: RefactorRewrite ) ,
39
69
"Flip comma" ,
40
70
comma. text_range ( ) ,
41
71
|edit| {
42
- edit. replace ( prev . text_range ( ) , next . to_string ( ) ) ;
43
- edit. replace ( next . text_range ( ) , prev . to_string ( ) ) ;
72
+ edit. replace ( prev_range , next_text ) ;
73
+ edit. replace ( next_range , prev_text ) ;
44
74
} ,
45
75
)
46
76
}
47
77
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
+ }
90
+ }
91
+
48
92
#[ cfg( test) ]
49
93
mod tests {
50
94
use super :: * ;
@@ -89,4 +133,18 @@ mod tests {
89
133
// See https://github.com/rust-lang/rust-analyzer/issues/7693
90
134
check_assist_not_applicable ( flip_comma, r#"bar!(a,$0 b)"# ) ;
91
135
}
136
+
137
+ #[ test]
138
+ fn flip_comma_attribute ( ) {
139
+ check_assist (
140
+ flip_comma,
141
+ r#"#[repr(align(2),$0 C)] struct Foo;"# ,
142
+ r#"#[repr(C, align(2))] struct Foo;"# ,
143
+ ) ;
144
+ check_assist (
145
+ flip_comma,
146
+ r#"#[foo(bar, baz(1 + 1),$0 qux, other)] struct Foo;"# ,
147
+ r#"#[foo(bar, qux, baz(1 + 1), other)] struct Foo;"# ,
148
+ ) ;
149
+ }
92
150
}
0 commit comments