1
- use ide_db:: base_db:: SourceDatabase ;
2
- use syntax:: TextSize ;
3
1
use 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 ,
5
6
} ;
6
7
7
8
use crate :: { AssistContext , AssistId , AssistKind , Assists } ;
@@ -25,8 +26,6 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
25
26
let comma = ctx. find_token_syntax_at_offset ( T ! [ , ] ) ?;
26
27
let prev = non_trivia_sibling ( comma. clone ( ) . into ( ) , Direction :: Prev ) ?;
27
28
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 ( ) ) ;
30
29
31
30
// Don't apply a "flip" in case of a last comma
32
31
// that typically comes before punctuation
@@ -40,53 +39,85 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
40
39
return None ;
41
40
}
42
41
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
+ } ;
66
51
67
52
acc. add (
68
53
AssistId ( "flip_comma" , AssistKind :: RefactorRewrite ) ,
69
54
"Flip comma" ,
70
55
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) ;
74
72
} ,
75
73
)
76
74
}
77
75
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 ( ) )
90
121
}
91
122
92
123
#[ cfg( test) ]
@@ -147,4 +178,9 @@ mod tests {
147
178
r#"#[foo(bar, qux, baz(1 + 1), other)] struct Foo;"# ,
148
179
) ;
149
180
}
181
+
182
+ #[ test]
183
+ fn flip_comma_attribute_incomplete ( ) {
184
+ check_assist_not_applicable ( flip_comma, r#"#[repr(align(2),$0)] struct Foo;"# ) ;
185
+ }
150
186
}
0 commit comments