1use syntax::{
2 AstNode, Direction, NodeOrToken, SyntaxKind, SyntaxToken, T,
3 algo::non_trivia_sibling,
4 ast::{self, syntax_factory::SyntaxFactory},
5};
6
7use crate::{AssistContext, AssistId, Assists};
8
9pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
25 let comma = ctx.find_token_syntax_at_offset(T![,])?;
26 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
27 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
28
29 if next.kind().is_punct() {
32 return None;
33 }
34
35 if comma.parent_ancestors().any(|it| it.kind() == SyntaxKind::MACRO_CALL) {
38 return None;
39 }
40
41 let target = comma.text_range();
42 acc.add(AssistId::refactor_rewrite("flip_comma"), "Flip comma", target, |builder| {
43 let parent = comma.parent().unwrap();
44 let editor = builder.make_editor(&parent);
45
46 if let Some(parent) = ast::TokenTree::cast(parent) {
47 let new_tree = flip_tree(parent.clone(), comma, editor.make());
50 editor.replace(parent.syntax(), new_tree.syntax());
51 } else {
52 editor.replace(prev.clone(), next.clone());
53 editor.replace(next.clone(), prev.clone());
54 }
55
56 builder.add_file_edits(ctx.vfs_file_id(), editor);
57 })
58}
59
60fn flip_tree(tree: ast::TokenTree, comma: SyntaxToken, make: &SyntaxFactory) -> ast::TokenTree {
61 let mut tree_iter = tree.token_trees_and_tokens();
62 let before: Vec<_> =
63 tree_iter.by_ref().take_while(|it| it.as_token() != Some(&comma)).collect();
64 let after: Vec<_> = tree_iter.collect();
65
66 let not_ws = |element: &NodeOrToken<_, SyntaxToken>| match element {
67 NodeOrToken::Token(token) => token.kind() != SyntaxKind::WHITESPACE,
68 NodeOrToken::Node(_) => true,
69 };
70
71 let is_comma = |element: &NodeOrToken<_, SyntaxToken>| match element {
72 NodeOrToken::Token(token) => token.kind() == T![,],
73 NodeOrToken::Node(_) => false,
74 };
75
76 let prev_start_untrimmed = match before.iter().rposition(is_comma) {
77 Some(pos) => pos + 1,
78 None => 1,
79 };
80 let prev_end = 1 + before.iter().rposition(not_ws).unwrap();
81 let prev_start = prev_start_untrimmed
82 + before[prev_start_untrimmed..prev_end].iter().position(not_ws).unwrap();
83
84 let next_start = after.iter().position(not_ws).unwrap();
85 let next_end_untrimmed = match after.iter().position(is_comma) {
86 Some(pos) => pos,
87 None => after.len() - 1,
88 };
89 let next_end = 1 + after[..next_end_untrimmed].iter().rposition(not_ws).unwrap();
90
91 let result = [
92 &before[1..prev_start],
93 &after[next_start..next_end],
94 &before[prev_end..],
95 &[NodeOrToken::Token(comma)],
96 &after[..next_start],
97 &before[prev_start..prev_end],
98 &after[next_end..after.len() - 1],
99 ]
100 .concat();
101 make.token_tree(tree.left_delimiter_token().unwrap().kind(), result)
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
109
110 #[test]
111 fn flip_comma_works_for_function_parameters() {
112 check_assist(
113 flip_comma,
114 r#"fn foo(x: i32,$0 y: Result<(), ()>) {}"#,
115 r#"fn foo(y: Result<(), ()>, x: i32) {}"#,
116 )
117 }
118
119 #[test]
120 fn flip_comma_target() {
121 check_assist_target(flip_comma, r#"fn foo(x: i32,$0 y: Result<(), ()>) {}"#, ",")
122 }
123
124 #[test]
125 fn flip_comma_before_punct() {
126 check_assist_not_applicable(flip_comma, "pub enum Test { A,$0 }");
130 check_assist_not_applicable(flip_comma, "pub struct Test { foo: usize,$0 }");
131 }
132
133 #[test]
134 fn flip_comma_works() {
135 check_assist(
136 flip_comma,
137 r#"fn main() {((1, 2),$0 (3, 4));}"#,
138 r#"fn main() {((3, 4), (1, 2));}"#,
139 )
140 }
141
142 #[test]
143 fn flip_comma_not_applicable_for_macro_input() {
144 check_assist_not_applicable(flip_comma, r#"bar!(a,$0 b)"#);
147 }
148
149 #[test]
150 fn flip_comma_attribute() {
151 check_assist(
152 flip_comma,
153 r#"#[repr(align(2),$0 C)] struct Foo;"#,
154 r#"#[repr(C, align(2))] struct Foo;"#,
155 );
156 check_assist(
157 flip_comma,
158 r#"#[foo(bar, baz(1 + 1),$0 qux, other)] struct Foo;"#,
159 r#"#[foo(bar, qux, baz(1 + 1), other)] struct Foo;"#,
160 );
161 }
162
163 #[test]
164 fn flip_comma_attribute_incomplete() {
165 check_assist_not_applicable(flip_comma, r#"#[repr(align(2),$0)] struct Foo;"#);
166 }
167}