aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api_light/src
diff options
context:
space:
mode:
authorWilco Kusee <[email protected]>2019-03-21 18:51:42 +0000
committerWilco Kusee <[email protected]>2019-03-22 16:12:31 +0000
commit58e77660deae23f0a2f5a7c42f52ec7cab707e57 (patch)
tree282de62356b8019bee8b8b5784aaca0e01e5e81b /crates/ra_ide_api_light/src
parent6e324d38d6ef3e250ff32a397f4777699e006f7f (diff)
Move join_lines to ra_ide_api
Diffstat (limited to 'crates/ra_ide_api_light/src')
-rw-r--r--crates/ra_ide_api_light/src/join_lines.rs589
-rw-r--r--crates/ra_ide_api_light/src/lib.rs6
-rw-r--r--crates/ra_ide_api_light/src/test_utils.rs24
-rw-r--r--crates/ra_ide_api_light/src/typing.rs2
4 files changed, 2 insertions, 619 deletions
diff --git a/crates/ra_ide_api_light/src/join_lines.rs b/crates/ra_ide_api_light/src/join_lines.rs
deleted file mode 100644
index b5bcd62fb..000000000
--- a/crates/ra_ide_api_light/src/join_lines.rs
+++ /dev/null
@@ -1,589 +0,0 @@
1use itertools::Itertools;
2use ra_syntax::{
3 SourceFile, TextRange, TextUnit, AstNode, SyntaxNode,
4 SyntaxKind::{self, WHITESPACE, COMMA, R_CURLY, R_PAREN, R_BRACK},
5 algo::{find_covering_node, non_trivia_sibling},
6 ast,
7 Direction,
8};
9use ra_fmt::{
10 compute_ws, extract_trivial_expression
11};
12use crate::{
13 LocalEdit, TextEditBuilder,
14};
15
16pub fn join_lines(file: &SourceFile, range: TextRange) -> LocalEdit {
17 let range = if range.is_empty() {
18 let syntax = file.syntax();
19 let text = syntax.text().slice(range.start()..);
20 let pos = match text.find('\n') {
21 None => {
22 return LocalEdit {
23 label: "join lines".to_string(),
24 edit: TextEditBuilder::default().finish(),
25 cursor_position: None,
26 };
27 }
28 Some(pos) => pos,
29 };
30 TextRange::offset_len(range.start() + pos, TextUnit::of_char('\n'))
31 } else {
32 range
33 };
34
35 let node = find_covering_node(file.syntax(), range);
36 let mut edit = TextEditBuilder::default();
37 for node in node.descendants() {
38 let text = match node.leaf_text() {
39 Some(text) => text,
40 None => continue,
41 };
42 let range = match range.intersection(&node.range()) {
43 Some(range) => range,
44 None => continue,
45 } - node.range().start();
46 for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') {
47 let pos: TextUnit = (pos as u32).into();
48 let off = node.range().start() + range.start() + pos;
49 if !edit.invalidates_offset(off) {
50 remove_newline(&mut edit, node, text.as_str(), off);
51 }
52 }
53 }
54
55 LocalEdit { label: "join lines".to_string(), edit: edit.finish(), cursor_position: None }
56}
57
58fn remove_newline(
59 edit: &mut TextEditBuilder,
60 node: &SyntaxNode,
61 node_text: &str,
62 offset: TextUnit,
63) {
64 if node.kind() != WHITESPACE || node_text.bytes().filter(|&b| b == b'\n').count() != 1 {
65 // The node is either the first or the last in the file
66 let suff = &node_text[TextRange::from_to(
67 offset - node.range().start() + TextUnit::of_char('\n'),
68 TextUnit::of_str(node_text),
69 )];
70 let spaces = suff.bytes().take_while(|&b| b == b' ').count();
71
72 edit.replace(TextRange::offset_len(offset, ((spaces + 1) as u32).into()), " ".to_string());
73 return;
74 }
75
76 // Special case that turns something like:
77 //
78 // ```
79 // my_function({<|>
80 // <some-expr>
81 // })
82 // ```
83 //
84 // into `my_function(<some-expr>)`
85 if join_single_expr_block(edit, node).is_some() {
86 return;
87 }
88 // ditto for
89 //
90 // ```
91 // use foo::{<|>
92 // bar
93 // };
94 // ```
95 if join_single_use_tree(edit, node).is_some() {
96 return;
97 }
98
99 // The node is between two other nodes
100 let prev = node.prev_sibling().unwrap();
101 let next = node.next_sibling().unwrap();
102 if is_trailing_comma(prev.kind(), next.kind()) {
103 // Removes: trailing comma, newline (incl. surrounding whitespace)
104 edit.delete(TextRange::from_to(prev.range().start(), node.range().end()));
105 } else if prev.kind() == COMMA && next.kind() == R_CURLY {
106 // Removes: comma, newline (incl. surrounding whitespace)
107 let space = if let Some(left) = prev.prev_sibling() { compute_ws(left, next) } else { " " };
108 edit.replace(
109 TextRange::from_to(prev.range().start(), node.range().end()),
110 space.to_string(),
111 );
112 } else if let (Some(_), Some(next)) = (ast::Comment::cast(prev), ast::Comment::cast(next)) {
113 // Removes: newline (incl. surrounding whitespace), start of the next comment
114 edit.delete(TextRange::from_to(
115 node.range().start(),
116 next.syntax().range().start() + TextUnit::of_str(next.prefix()),
117 ));
118 } else {
119 // Remove newline but add a computed amount of whitespace characters
120 edit.replace(node.range(), compute_ws(prev, next).to_string());
121 }
122}
123
124fn has_comma_after(node: &SyntaxNode) -> bool {
125 match non_trivia_sibling(node, Direction::Next) {
126 Some(n) => n.kind() == COMMA,
127 _ => false,
128 }
129}
130
131fn join_single_expr_block(edit: &mut TextEditBuilder, node: &SyntaxNode) -> Option<()> {
132 let block = ast::Block::cast(node.parent()?)?;
133 let block_expr = ast::BlockExpr::cast(block.syntax().parent()?)?;
134 let expr = extract_trivial_expression(block)?;
135
136 let block_range = block_expr.syntax().range();
137 let mut buf = expr.syntax().text().to_string();
138
139 // Match block needs to have a comma after the block
140 if let Some(match_arm) = block_expr.syntax().parent().and_then(ast::MatchArm::cast) {
141 if !has_comma_after(match_arm.syntax()) {
142 buf.push(',');
143 }
144 }
145
146 edit.replace(block_range, buf);
147
148 Some(())
149}
150
151fn join_single_use_tree(edit: &mut TextEditBuilder, node: &SyntaxNode) -> Option<()> {
152 let use_tree_list = ast::UseTreeList::cast(node.parent()?)?;
153 let (tree,) = use_tree_list.use_trees().collect_tuple()?;
154 edit.replace(use_tree_list.syntax().range(), tree.syntax().text().to_string());
155 Some(())
156}
157
158fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool {
159 match (left, right) {
160 (COMMA, R_PAREN) | (COMMA, R_BRACK) => true,
161 _ => false,
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use crate::test_utils::{assert_eq_text, check_action, extract_range};
168
169 use super::*;
170
171 fn check_join_lines(before: &str, after: &str) {
172 check_action(before, after, |file, offset| {
173 let range = TextRange::offset_len(offset, 0.into());
174 let res = join_lines(file, range);
175 Some(res)
176 })
177 }
178
179 #[test]
180 fn test_join_lines_comma() {
181 check_join_lines(
182 r"
183fn foo() {
184 <|>foo(1,
185 )
186}
187",
188 r"
189fn foo() {
190 <|>foo(1)
191}
192",
193 );
194 }
195
196 #[test]
197 fn test_join_lines_lambda_block() {
198 check_join_lines(
199 r"
200pub fn reparse(&self, edit: &AtomTextEdit) -> File {
201 <|>self.incremental_reparse(edit).unwrap_or_else(|| {
202 self.full_reparse(edit)
203 })
204}
205",
206 r"
207pub fn reparse(&self, edit: &AtomTextEdit) -> File {
208 <|>self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit))
209}
210",
211 );
212 }
213
214 #[test]
215 fn test_join_lines_block() {
216 check_join_lines(
217 r"
218fn foo() {
219 foo(<|>{
220 92
221 })
222}",
223 r"
224fn foo() {
225 foo(<|>92)
226}",
227 );
228 }
229
230 #[test]
231 fn join_lines_adds_comma_for_block_in_match_arm() {
232 check_join_lines(
233 r"
234fn foo(e: Result<U, V>) {
235 match e {
236 Ok(u) => <|>{
237 u.foo()
238 }
239 Err(v) => v,
240 }
241}",
242 r"
243fn foo(e: Result<U, V>) {
244 match e {
245 Ok(u) => <|>u.foo(),
246 Err(v) => v,
247 }
248}",
249 );
250 }
251
252 #[test]
253 fn join_lines_keeps_comma_for_block_in_match_arm() {
254 // We already have a comma
255 check_join_lines(
256 r"
257fn foo(e: Result<U, V>) {
258 match e {
259 Ok(u) => <|>{
260 u.foo()
261 },
262 Err(v) => v,
263 }
264}",
265 r"
266fn foo(e: Result<U, V>) {
267 match e {
268 Ok(u) => <|>u.foo(),
269 Err(v) => v,
270 }
271}",
272 );
273
274 // comma with whitespace between brace and ,
275 check_join_lines(
276 r"
277fn foo(e: Result<U, V>) {
278 match e {
279 Ok(u) => <|>{
280 u.foo()
281 } ,
282 Err(v) => v,
283 }
284}",
285 r"
286fn foo(e: Result<U, V>) {
287 match e {
288 Ok(u) => <|>u.foo() ,
289 Err(v) => v,
290 }
291}",
292 );
293
294 // comma with newline between brace and ,
295 check_join_lines(
296 r"
297fn foo(e: Result<U, V>) {
298 match e {
299 Ok(u) => <|>{
300 u.foo()
301 }
302 ,
303 Err(v) => v,
304 }
305}",
306 r"
307fn foo(e: Result<U, V>) {
308 match e {
309 Ok(u) => <|>u.foo()
310 ,
311 Err(v) => v,
312 }
313}",
314 );
315 }
316
317 #[test]
318 fn join_lines_keeps_comma_with_single_arg_tuple() {
319 // A single arg tuple
320 check_join_lines(
321 r"
322fn foo() {
323 let x = (<|>{
324 4
325 },);
326}",
327 r"
328fn foo() {
329 let x = (<|>4,);
330}",
331 );
332
333 // single arg tuple with whitespace between brace and comma
334 check_join_lines(
335 r"
336fn foo() {
337 let x = (<|>{
338 4
339 } ,);
340}",
341 r"
342fn foo() {
343 let x = (<|>4 ,);
344}",
345 );
346
347 // single arg tuple with newline between brace and comma
348 check_join_lines(
349 r"
350fn foo() {
351 let x = (<|>{
352 4
353 }
354 ,);
355}",
356 r"
357fn foo() {
358 let x = (<|>4
359 ,);
360}",
361 );
362 }
363
364 #[test]
365 fn test_join_lines_use_items_left() {
366 // No space after the '{'
367 check_join_lines(
368 r"
369<|>use ra_syntax::{
370 TextUnit, TextRange,
371};",
372 r"
373<|>use ra_syntax::{TextUnit, TextRange,
374};",
375 );
376 }
377
378 #[test]
379 fn test_join_lines_use_items_right() {
380 // No space after the '}'
381 check_join_lines(
382 r"
383use ra_syntax::{
384<|> TextUnit, TextRange
385};",
386 r"
387use ra_syntax::{
388<|> TextUnit, TextRange};",
389 );
390 }
391
392 #[test]
393 fn test_join_lines_use_items_right_comma() {
394 // No space after the '}'
395 check_join_lines(
396 r"
397use ra_syntax::{
398<|> TextUnit, TextRange,
399};",
400 r"
401use ra_syntax::{
402<|> TextUnit, TextRange};",
403 );
404 }
405
406 #[test]
407 fn test_join_lines_use_tree() {
408 check_join_lines(
409 r"
410use ra_syntax::{
411 algo::<|>{
412 find_leaf_at_offset,
413 },
414 ast,
415};",
416 r"
417use ra_syntax::{
418 algo::<|>find_leaf_at_offset,
419 ast,
420};",
421 );
422 }
423
424 #[test]
425 fn test_join_lines_normal_comments() {
426 check_join_lines(
427 r"
428fn foo() {
429 // Hello<|>
430 // world!
431}
432",
433 r"
434fn foo() {
435 // Hello<|> world!
436}
437",
438 );
439 }
440
441 #[test]
442 fn test_join_lines_doc_comments() {
443 check_join_lines(
444 r"
445fn foo() {
446 /// Hello<|>
447 /// world!
448}
449",
450 r"
451fn foo() {
452 /// Hello<|> world!
453}
454",
455 );
456 }
457
458 #[test]
459 fn test_join_lines_mod_comments() {
460 check_join_lines(
461 r"
462fn foo() {
463 //! Hello<|>
464 //! world!
465}
466",
467 r"
468fn foo() {
469 //! Hello<|> world!
470}
471",
472 );
473 }
474
475 #[test]
476 fn test_join_lines_multiline_comments_1() {
477 check_join_lines(
478 r"
479fn foo() {
480 // Hello<|>
481 /* world! */
482}
483",
484 r"
485fn foo() {
486 // Hello<|> world! */
487}
488",
489 );
490 }
491
492 #[test]
493 fn test_join_lines_multiline_comments_2() {
494 check_join_lines(
495 r"
496fn foo() {
497 // The<|>
498 /* quick
499 brown
500 fox! */
501}
502",
503 r"
504fn foo() {
505 // The<|> quick
506 brown
507 fox! */
508}
509",
510 );
511 }
512
513 fn check_join_lines_sel(before: &str, after: &str) {
514 let (sel, before) = extract_range(before);
515 let file = SourceFile::parse(&before);
516 let result = join_lines(&file, sel);
517 let actual = result.edit.apply(&before);
518 assert_eq_text!(after, &actual);
519 }
520
521 #[test]
522 fn test_join_lines_selection_fn_args() {
523 check_join_lines_sel(
524 r"
525fn foo() {
526 <|>foo(1,
527 2,
528 3,
529 <|>)
530}
531 ",
532 r"
533fn foo() {
534 foo(1, 2, 3)
535}
536 ",
537 );
538 }
539
540 #[test]
541 fn test_join_lines_selection_struct() {
542 check_join_lines_sel(
543 r"
544struct Foo <|>{
545 f: u32,
546}<|>
547 ",
548 r"
549struct Foo { f: u32 }
550 ",
551 );
552 }
553
554 #[test]
555 fn test_join_lines_selection_dot_chain() {
556 check_join_lines_sel(
557 r"
558fn foo() {
559 join(<|>type_params.type_params()
560 .filter_map(|it| it.name())
561 .map(|it| it.text())<|>)
562}",
563 r"
564fn foo() {
565 join(type_params.type_params().filter_map(|it| it.name()).map(|it| it.text()))
566}",
567 );
568 }
569
570 #[test]
571 fn test_join_lines_selection_lambda_block_body() {
572 check_join_lines_sel(
573 r"
574pub fn handle_find_matching_brace() {
575 params.offsets
576 .map(|offset| <|>{
577 world.analysis().matching_brace(&file, offset).unwrap_or(offset)
578 }<|>)
579 .collect();
580}",
581 r"
582pub fn handle_find_matching_brace() {
583 params.offsets
584 .map(|offset| world.analysis().matching_brace(&file, offset).unwrap_or(offset))
585 .collect();
586}",
587 );
588 }
589}
diff --git a/crates/ra_ide_api_light/src/lib.rs b/crates/ra_ide_api_light/src/lib.rs
index 4036a598e..f21a91e18 100644
--- a/crates/ra_ide_api_light/src/lib.rs
+++ b/crates/ra_ide_api_light/src/lib.rs
@@ -4,9 +4,6 @@
4//! an edit or some auxiliary info. 4//! an edit or some auxiliary info.
5 5
6mod structure; 6mod structure;
7#[cfg(test)]
8mod test_utils;
9mod join_lines;
10mod typing; 7mod typing;
11 8
12use rustc_hash::FxHashSet; 9use rustc_hash::FxHashSet;
@@ -20,7 +17,6 @@ use ra_syntax::{
20 17
21pub use crate::{ 18pub use crate::{
22 structure::{file_structure, StructureNode}, 19 structure::{file_structure, StructureNode},
23 join_lines::join_lines,
24 typing::{on_enter, on_dot_typed, on_eq_typed}, 20 typing::{on_enter, on_dot_typed, on_eq_typed},
25}; 21};
26 22
@@ -118,7 +114,7 @@ mod tests {
118 use ra_syntax::AstNode; 114 use ra_syntax::AstNode;
119 use insta::assert_debug_snapshot_matches; 115 use insta::assert_debug_snapshot_matches;
120 116
121 use crate::test_utils::{add_cursor, assert_eq_text, extract_offset}; 117 use test_utils::{add_cursor, assert_eq_text, extract_offset};
122 118
123 use super::*; 119 use super::*;
124 120
diff --git a/crates/ra_ide_api_light/src/test_utils.rs b/crates/ra_ide_api_light/src/test_utils.rs
deleted file mode 100644
index bfac0fce3..000000000
--- a/crates/ra_ide_api_light/src/test_utils.rs
+++ /dev/null
@@ -1,24 +0,0 @@
1use ra_syntax::{SourceFile, TextUnit};
2
3use crate::LocalEdit;
4pub use test_utils::*;
5
6pub fn check_action<F: Fn(&SourceFile, TextUnit) -> Option<LocalEdit>>(
7 before: &str,
8 after: &str,
9 f: F,
10) {
11 let (before_cursor_pos, before) = extract_offset(before);
12 let file = SourceFile::parse(&before);
13 let result = f(&file, before_cursor_pos).expect("code action is not applicable");
14 let actual = result.edit.apply(&before);
15 let actual_cursor_pos = match result.cursor_position {
16 None => result
17 .edit
18 .apply_to_offset(before_cursor_pos)
19 .expect("cursor position is affected by the edit"),
20 Some(off) => off,
21 };
22 let actual = add_cursor(&actual, actual_cursor_pos);
23 assert_eq_text!(after, &actual);
24}
diff --git a/crates/ra_ide_api_light/src/typing.rs b/crates/ra_ide_api_light/src/typing.rs
index 9dd9f1c1d..c69270333 100644
--- a/crates/ra_ide_api_light/src/typing.rs
+++ b/crates/ra_ide_api_light/src/typing.rs
@@ -118,7 +118,7 @@ pub fn on_dot_typed(file: &SourceFile, dot_offset: TextUnit) -> Option<LocalEdit
118 118
119#[cfg(test)] 119#[cfg(test)]
120mod tests { 120mod tests {
121 use crate::test_utils::{add_cursor, assert_eq_text, extract_offset}; 121 use test_utils::{add_cursor, assert_eq_text, extract_offset};
122 122
123 use super::*; 123 use super::*;
124 124