@@ -12,18 +12,32 @@ use syntax::{
12
12
13
13
use text_edit:: { TextEdit , TextEditBuilder } ;
14
14
15
+ pub struct JoinLinesConfig {
16
+ pub join_else_if : bool ,
17
+ pub remove_trailing_comma : bool ,
18
+ pub unwrap_trivial_blocks : bool ,
19
+ }
20
+
15
21
// Feature: Join Lines
16
22
//
17
23
// Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces.
18
24
//
25
+ // See
26
+ // https://user-images.githubusercontent.com/1711539/124515923-4504e800-dde9-11eb-8d58-d97945a1a785.gif[this gif]
27
+ // for the cases handled specially by joined lines.
28
+ //
19
29
// |===
20
30
// | Editor | Action Name
21
31
//
22
32
// | VS Code | **Rust Analyzer: Join lines**
23
33
// |===
24
34
//
25
35
// image::https://user-images.githubusercontent.com/48062697/113020661-b6922200-917a-11eb-87c4-b75acc028f11.gif[]
26
- pub ( crate ) fn join_lines ( file : & SourceFile , range : TextRange ) -> TextEdit {
36
+ pub ( crate ) fn join_lines (
37
+ config : & JoinLinesConfig ,
38
+ file : & SourceFile ,
39
+ range : TextRange ,
40
+ ) -> TextEdit {
27
41
let range = if range. is_empty ( ) {
28
42
let syntax = file. syntax ( ) ;
29
43
let text = syntax. text ( ) . slice ( range. start ( ) ..) ;
@@ -40,15 +54,20 @@ pub(crate) fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit {
40
54
match file. syntax ( ) . covering_element ( range) {
41
55
NodeOrToken :: Node ( node) => {
42
56
for token in node. descendants_with_tokens ( ) . filter_map ( |it| it. into_token ( ) ) {
43
- remove_newlines ( & mut edit, & token, range)
57
+ remove_newlines ( config , & mut edit, & token, range)
44
58
}
45
59
}
46
- NodeOrToken :: Token ( token) => remove_newlines ( & mut edit, & token, range) ,
60
+ NodeOrToken :: Token ( token) => remove_newlines ( config , & mut edit, & token, range) ,
47
61
} ;
48
62
edit. finish ( )
49
63
}
50
64
51
- fn remove_newlines ( edit : & mut TextEditBuilder , token : & SyntaxToken , range : TextRange ) {
65
+ fn remove_newlines (
66
+ config : & JoinLinesConfig ,
67
+ edit : & mut TextEditBuilder ,
68
+ token : & SyntaxToken ,
69
+ range : TextRange ,
70
+ ) {
52
71
let intersection = match range. intersect ( token. text_range ( ) ) {
53
72
Some ( range) => range,
54
73
None => return ,
@@ -60,12 +79,17 @@ fn remove_newlines(edit: &mut TextEditBuilder, token: &SyntaxToken, range: TextR
60
79
let pos: TextSize = ( pos as u32 ) . into ( ) ;
61
80
let offset = token. text_range ( ) . start ( ) + range. start ( ) + pos;
62
81
if !edit. invalidates_offset ( offset) {
63
- remove_newline ( edit, token, offset) ;
82
+ remove_newline ( config , edit, token, offset) ;
64
83
}
65
84
}
66
85
}
67
86
68
- fn remove_newline ( edit : & mut TextEditBuilder , token : & SyntaxToken , offset : TextSize ) {
87
+ fn remove_newline (
88
+ config : & JoinLinesConfig ,
89
+ edit : & mut TextEditBuilder ,
90
+ token : & SyntaxToken ,
91
+ offset : TextSize ,
92
+ ) {
69
93
if token. kind ( ) != WHITESPACE || token. text ( ) . bytes ( ) . filter ( |& b| b == b'\n' ) . count ( ) != 1 {
70
94
let n_spaces_after_line_break = {
71
95
let suff = & token. text ( ) [ TextRange :: new (
@@ -102,24 +126,66 @@ fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextS
102
126
_ => return ,
103
127
} ;
104
128
105
- if is_trailing_comma ( prev. kind ( ) , next. kind ( ) ) {
106
- // Removes: trailing comma, newline (incl. surrounding whitespace)
107
- edit. delete ( TextRange :: new ( prev. text_range ( ) . start ( ) , token. text_range ( ) . end ( ) ) ) ;
108
- return ;
129
+ if config. remove_trailing_comma && prev. kind ( ) == T ! [ , ] {
130
+ match next. kind ( ) {
131
+ T ! [ ')' ] | T ! [ ']' ] => {
132
+ // Removes: trailing comma, newline (incl. surrounding whitespace)
133
+ edit. delete ( TextRange :: new ( prev. text_range ( ) . start ( ) , token. text_range ( ) . end ( ) ) ) ;
134
+ return ;
135
+ }
136
+ T ! [ '}' ] => {
137
+ // Removes: comma, newline (incl. surrounding whitespace)
138
+ let space = if let Some ( left) = prev. prev_sibling_or_token ( ) {
139
+ compute_ws ( left. kind ( ) , next. kind ( ) )
140
+ } else {
141
+ " "
142
+ } ;
143
+ edit. replace (
144
+ TextRange :: new ( prev. text_range ( ) . start ( ) , token. text_range ( ) . end ( ) ) ,
145
+ space. to_string ( ) ,
146
+ ) ;
147
+ return ;
148
+ }
149
+ _ => ( ) ,
150
+ }
109
151
}
110
152
111
- if prev. kind ( ) == T ! [ , ] && next. kind ( ) == T ! [ '}' ] {
112
- // Removes: comma, newline (incl. surrounding whitespace)
113
- let space = if let Some ( left) = prev. prev_sibling_or_token ( ) {
114
- compute_ws ( left. kind ( ) , next. kind ( ) )
115
- } else {
116
- " "
117
- } ;
118
- edit. replace (
119
- TextRange :: new ( prev. text_range ( ) . start ( ) , token. text_range ( ) . end ( ) ) ,
120
- space. to_string ( ) ,
121
- ) ;
122
- return ;
153
+ if config. join_else_if {
154
+ if let ( Some ( prev) , Some ( _next) ) = ( as_if_expr ( & prev) , as_if_expr ( & next) ) {
155
+ match prev. else_token ( ) {
156
+ Some ( _) => cov_mark:: hit!( join_two_ifs_with_existing_else) ,
157
+ None => {
158
+ cov_mark:: hit!( join_two_ifs) ;
159
+ edit. replace ( token. text_range ( ) , " else " . to_string ( ) ) ;
160
+ return ;
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ if config. unwrap_trivial_blocks {
167
+ // Special case that turns something like:
168
+ //
169
+ // ```
170
+ // my_function({$0
171
+ // <some-expr>
172
+ // })
173
+ // ```
174
+ //
175
+ // into `my_function(<some-expr>)`
176
+ if join_single_expr_block ( edit, token) . is_some ( ) {
177
+ return ;
178
+ }
179
+ // ditto for
180
+ //
181
+ // ```
182
+ // use foo::{$0
183
+ // bar
184
+ // };
185
+ // ```
186
+ if join_single_use_tree ( edit, token) . is_some ( ) {
187
+ return ;
188
+ }
123
189
}
124
190
125
191
if let ( Some ( _) , Some ( next) ) = (
@@ -134,40 +200,6 @@ fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextS
134
200
return ;
135
201
}
136
202
137
- if let ( Some ( prev) , Some ( _next) ) = ( as_if_expr ( & prev) , as_if_expr ( & next) ) {
138
- match prev. else_token ( ) {
139
- Some ( _) => cov_mark:: hit!( join_two_ifs_with_existing_else) ,
140
- None => {
141
- cov_mark:: hit!( join_two_ifs) ;
142
- edit. replace ( token. text_range ( ) , " else " . to_string ( ) ) ;
143
- return ;
144
- }
145
- }
146
- }
147
-
148
- // Special case that turns something like:
149
- //
150
- // ```
151
- // my_function({$0
152
- // <some-expr>
153
- // })
154
- // ```
155
- //
156
- // into `my_function(<some-expr>)`
157
- if join_single_expr_block ( edit, token) . is_some ( ) {
158
- return ;
159
- }
160
- // ditto for
161
- //
162
- // ```
163
- // use foo::{$0
164
- // bar
165
- // };
166
- // ```
167
- if join_single_use_tree ( edit, token) . is_some ( ) {
168
- return ;
169
- }
170
-
171
203
// Remove newline but add a computed amount of whitespace characters
172
204
edit. replace ( token. text_range ( ) , compute_ws ( prev. kind ( ) , next. kind ( ) ) . to_string ( ) ) ;
173
205
}
@@ -208,10 +240,6 @@ fn join_single_use_tree(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Opti
208
240
Some ( ( ) )
209
241
}
210
242
211
- fn is_trailing_comma ( left : SyntaxKind , right : SyntaxKind ) -> bool {
212
- matches ! ( ( left, right) , ( T ![ , ] , T ![ ')' ] | T ![ ']' ] ) )
213
- }
214
-
215
243
fn as_if_expr ( element : & SyntaxElement ) -> Option < ast:: IfExpr > {
216
244
let mut node = element. as_node ( ) ?. clone ( ) ;
217
245
if let Some ( stmt) = ast:: ExprStmt :: cast ( node. clone ( ) ) {
@@ -251,11 +279,17 @@ mod tests {
251
279
use super :: * ;
252
280
253
281
fn check_join_lines ( ra_fixture_before : & str , ra_fixture_after : & str ) {
282
+ let config = JoinLinesConfig {
283
+ join_else_if : true ,
284
+ remove_trailing_comma : true ,
285
+ unwrap_trivial_blocks : true ,
286
+ } ;
287
+
254
288
let ( before_cursor_pos, before) = extract_offset ( ra_fixture_before) ;
255
289
let file = SourceFile :: parse ( & before) . ok ( ) . unwrap ( ) ;
256
290
257
291
let range = TextRange :: empty ( before_cursor_pos) ;
258
- let result = join_lines ( & file, range) ;
292
+ let result = join_lines ( & config , & file, range) ;
259
293
260
294
let actual = {
261
295
let mut actual = before;
@@ -269,6 +303,24 @@ mod tests {
269
303
assert_eq_text ! ( ra_fixture_after, & actual) ;
270
304
}
271
305
306
+ fn check_join_lines_sel ( ra_fixture_before : & str , ra_fixture_after : & str ) {
307
+ let config = JoinLinesConfig {
308
+ join_else_if : true ,
309
+ remove_trailing_comma : true ,
310
+ unwrap_trivial_blocks : true ,
311
+ } ;
312
+
313
+ let ( sel, before) = extract_range ( ra_fixture_before) ;
314
+ let parse = SourceFile :: parse ( & before) ;
315
+ let result = join_lines ( & config, & parse. tree ( ) , sel) ;
316
+ let actual = {
317
+ let mut actual = before;
318
+ result. apply ( & mut actual) ;
319
+ actual
320
+ } ;
321
+ assert_eq_text ! ( ra_fixture_after, & actual) ;
322
+ }
323
+
272
324
#[ test]
273
325
fn test_join_lines_comma ( ) {
274
326
check_join_lines (
@@ -657,18 +709,6 @@ fn foo() {
657
709
) ;
658
710
}
659
711
660
- fn check_join_lines_sel ( ra_fixture_before : & str , ra_fixture_after : & str ) {
661
- let ( sel, before) = extract_range ( ra_fixture_before) ;
662
- let parse = SourceFile :: parse ( & before) ;
663
- let result = join_lines ( & parse. tree ( ) , sel) ;
664
- let actual = {
665
- let mut actual = before;
666
- result. apply ( & mut actual) ;
667
- actual
668
- } ;
669
- assert_eq_text ! ( ra_fixture_after, & actual) ;
670
- }
671
-
672
712
#[ test]
673
713
fn test_join_lines_selection_fn_args ( ) {
674
714
check_join_lines_sel (
0 commit comments