]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | use std::{iter::once, mem}; |
2 | ||
3 | use hir::Semantics; | |
4 | use ide_db::{base_db::FileRange, helpers::pick_best_token, RootDatabase}; | |
5 | use itertools::Itertools; | |
6 | use syntax::{algo, ast, match_ast, AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange}; | |
7 | use text_edit::{TextEdit, TextEditBuilder}; | |
8 | ||
9 | #[derive(Copy, Clone, Debug)] | |
10 | pub enum Direction { | |
11 | Up, | |
12 | Down, | |
13 | } | |
14 | ||
15 | // Feature: Move Item | |
16 | // | |
17 | // Move item under cursor or selection up and down. | |
18 | // | |
19 | // |=== | |
20 | // | Editor | Action Name | |
21 | // | |
f2b60f7d FG |
22 | // | VS Code | **rust-analyzer: Move item up** |
23 | // | VS Code | **rust-analyzer: Move item down** | |
064997fb FG |
24 | // |=== |
25 | // | |
26 | // image::https://user-images.githubusercontent.com/48062697/113065576-04298180-91b1-11eb-91ce-4505e99ed598.gif[] | |
27 | pub(crate) fn move_item( | |
28 | db: &RootDatabase, | |
29 | range: FileRange, | |
30 | direction: Direction, | |
31 | ) -> Option<TextEdit> { | |
32 | let sema = Semantics::new(db); | |
33 | let file = sema.parse(range.file_id); | |
34 | ||
35 | let item = if range.range.is_empty() { | |
36 | SyntaxElement::Token(pick_best_token( | |
37 | file.syntax().token_at_offset(range.range.start()), | |
38 | |kind| match kind { | |
39 | SyntaxKind::IDENT | SyntaxKind::LIFETIME_IDENT => 2, | |
40 | kind if kind.is_trivia() => 0, | |
41 | _ => 1, | |
42 | }, | |
43 | )?) | |
44 | } else { | |
45 | file.syntax().covering_element(range.range) | |
46 | }; | |
47 | ||
48 | find_ancestors(item, direction, range.range) | |
49 | } | |
50 | ||
51 | fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> { | |
52 | let root = match item { | |
53 | SyntaxElement::Node(node) => node, | |
54 | SyntaxElement::Token(token) => token.parent()?, | |
55 | }; | |
56 | ||
57 | let movable = [ | |
58 | SyntaxKind::ARG_LIST, | |
59 | SyntaxKind::GENERIC_PARAM_LIST, | |
60 | SyntaxKind::GENERIC_ARG_LIST, | |
61 | SyntaxKind::VARIANT_LIST, | |
62 | SyntaxKind::TYPE_BOUND_LIST, | |
63 | SyntaxKind::MATCH_ARM, | |
64 | SyntaxKind::PARAM, | |
65 | SyntaxKind::LET_STMT, | |
66 | SyntaxKind::EXPR_STMT, | |
67 | SyntaxKind::IF_EXPR, | |
68 | SyntaxKind::FOR_EXPR, | |
69 | SyntaxKind::LOOP_EXPR, | |
70 | SyntaxKind::WHILE_EXPR, | |
71 | SyntaxKind::RETURN_EXPR, | |
72 | SyntaxKind::MATCH_EXPR, | |
73 | SyntaxKind::MACRO_CALL, | |
74 | SyntaxKind::TYPE_ALIAS, | |
75 | SyntaxKind::TRAIT, | |
76 | SyntaxKind::IMPL, | |
77 | SyntaxKind::MACRO_DEF, | |
78 | SyntaxKind::STRUCT, | |
79 | SyntaxKind::UNION, | |
80 | SyntaxKind::ENUM, | |
81 | SyntaxKind::FN, | |
82 | SyntaxKind::MODULE, | |
83 | SyntaxKind::USE, | |
84 | SyntaxKind::STATIC, | |
85 | SyntaxKind::CONST, | |
86 | SyntaxKind::MACRO_RULES, | |
87 | SyntaxKind::MACRO_DEF, | |
88 | ]; | |
89 | ||
90 | let ancestor = once(root.clone()) | |
91 | .chain(root.ancestors()) | |
92 | .find(|ancestor| movable.contains(&ancestor.kind()))?; | |
93 | ||
94 | move_in_direction(&ancestor, direction, range) | |
95 | } | |
96 | ||
97 | fn move_in_direction( | |
98 | node: &SyntaxNode, | |
99 | direction: Direction, | |
100 | range: TextRange, | |
101 | ) -> Option<TextEdit> { | |
102 | match_ast! { | |
103 | match node { | |
104 | ast::ArgList(it) => swap_sibling_in_list(node, it.args(), range, direction), | |
105 | ast::GenericParamList(it) => swap_sibling_in_list(node, it.generic_params(), range, direction), | |
106 | ast::GenericArgList(it) => swap_sibling_in_list(node, it.generic_args(), range, direction), | |
107 | ast::VariantList(it) => swap_sibling_in_list(node, it.variants(), range, direction), | |
108 | ast::TypeBoundList(it) => swap_sibling_in_list(node, it.bounds(), range, direction), | |
109 | _ => Some(replace_nodes(range, node, &match direction { | |
110 | Direction::Up => node.prev_sibling(), | |
111 | Direction::Down => node.next_sibling(), | |
112 | }?)) | |
113 | } | |
114 | } | |
115 | } | |
116 | ||
117 | fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( | |
118 | node: &SyntaxNode, | |
119 | list: I, | |
120 | range: TextRange, | |
121 | direction: Direction, | |
122 | ) -> Option<TextEdit> { | |
123 | let list_lookup = list.tuple_windows().find(|(l, r)| match direction { | |
124 | Direction::Up => r.syntax().text_range().contains_range(range), | |
125 | Direction::Down => l.syntax().text_range().contains_range(range), | |
126 | }); | |
127 | ||
128 | if let Some((l, r)) = list_lookup { | |
129 | Some(replace_nodes(range, l.syntax(), r.syntax())) | |
130 | } else { | |
131 | // Cursor is beyond any movable list item (for example, on curly brace in enum). | |
132 | // It's not necessary, that parent of list is movable (arg list's parent is not, for example), | |
133 | // and we have to continue tree traversal to find suitable node. | |
134 | find_ancestors(SyntaxElement::Node(node.parent()?), direction, range) | |
135 | } | |
136 | } | |
137 | ||
138 | fn replace_nodes<'a>( | |
139 | range: TextRange, | |
140 | mut first: &'a SyntaxNode, | |
141 | mut second: &'a SyntaxNode, | |
142 | ) -> TextEdit { | |
143 | let cursor_offset = if range.is_empty() { | |
144 | // FIXME: `applySnippetTextEdits` does not support non-empty selection ranges | |
145 | if first.text_range().contains_range(range) { | |
146 | Some(range.start() - first.text_range().start()) | |
147 | } else if second.text_range().contains_range(range) { | |
148 | mem::swap(&mut first, &mut second); | |
149 | Some(range.start() - first.text_range().start()) | |
150 | } else { | |
151 | None | |
152 | } | |
153 | } else { | |
154 | None | |
155 | }; | |
156 | ||
157 | let first_with_cursor = match cursor_offset { | |
158 | Some(offset) => { | |
159 | let mut item_text = first.text().to_string(); | |
160 | item_text.insert_str(offset.into(), "$0"); | |
161 | item_text | |
162 | } | |
163 | None => first.text().to_string(), | |
164 | }; | |
165 | ||
166 | let mut edit = TextEditBuilder::default(); | |
167 | ||
168 | algo::diff(first, second).into_text_edit(&mut edit); | |
169 | edit.replace(second.text_range(), first_with_cursor); | |
170 | ||
171 | edit.finish() | |
172 | } | |
173 | ||
174 | #[cfg(test)] | |
175 | mod tests { | |
176 | use crate::fixture; | |
177 | use expect_test::{expect, Expect}; | |
178 | ||
179 | use crate::Direction; | |
180 | ||
181 | fn check(ra_fixture: &str, expect: Expect, direction: Direction) { | |
182 | let (analysis, range) = fixture::range(ra_fixture); | |
183 | let edit = analysis.move_item(range, direction).unwrap().unwrap_or_default(); | |
184 | let mut file = analysis.file_text(range.file_id).unwrap().to_string(); | |
185 | edit.apply(&mut file); | |
186 | expect.assert_eq(&file); | |
187 | } | |
188 | ||
189 | #[test] | |
190 | fn test_moves_match_arm_up() { | |
191 | check( | |
192 | r#" | |
193 | fn main() { | |
194 | match true { | |
195 | true => { | |
196 | println!("Hello, world"); | |
197 | }, | |
198 | false =>$0$0 { | |
199 | println!("Test"); | |
200 | } | |
201 | }; | |
202 | } | |
203 | "#, | |
204 | expect![[r#" | |
205 | fn main() { | |
206 | match true { | |
207 | false =>$0 { | |
208 | println!("Test"); | |
209 | } | |
210 | true => { | |
211 | println!("Hello, world"); | |
212 | }, | |
213 | }; | |
214 | } | |
215 | "#]], | |
216 | Direction::Up, | |
217 | ); | |
218 | } | |
219 | ||
220 | #[test] | |
221 | fn test_moves_match_arm_down() { | |
222 | check( | |
223 | r#" | |
224 | fn main() { | |
225 | match true { | |
226 | true =>$0$0 { | |
227 | println!("Hello, world"); | |
228 | }, | |
229 | false => { | |
230 | println!("Test"); | |
231 | } | |
232 | }; | |
233 | } | |
234 | "#, | |
235 | expect![[r#" | |
236 | fn main() { | |
237 | match true { | |
238 | false => { | |
239 | println!("Test"); | |
240 | } | |
241 | true =>$0 { | |
242 | println!("Hello, world"); | |
243 | }, | |
244 | }; | |
245 | } | |
246 | "#]], | |
247 | Direction::Down, | |
248 | ); | |
249 | } | |
250 | ||
251 | #[test] | |
252 | fn test_nowhere_to_move() { | |
253 | check( | |
254 | r#" | |
255 | fn main() { | |
256 | match true { | |
257 | true =>$0$0 { | |
258 | println!("Hello, world"); | |
259 | }, | |
260 | false => { | |
261 | println!("Test"); | |
262 | } | |
263 | }; | |
264 | } | |
265 | "#, | |
266 | expect![[r#" | |
267 | fn main() { | |
268 | match true { | |
269 | true => { | |
270 | println!("Hello, world"); | |
271 | }, | |
272 | false => { | |
273 | println!("Test"); | |
274 | } | |
275 | }; | |
276 | } | |
277 | "#]], | |
278 | Direction::Up, | |
279 | ); | |
280 | } | |
281 | ||
282 | #[test] | |
283 | fn test_moves_let_stmt_up() { | |
284 | check( | |
285 | r#" | |
286 | fn main() { | |
287 | let test = 123; | |
288 | let test2$0$0 = 456; | |
289 | } | |
290 | "#, | |
291 | expect![[r#" | |
292 | fn main() { | |
293 | let test2$0 = 456; | |
294 | let test = 123; | |
295 | } | |
296 | "#]], | |
297 | Direction::Up, | |
298 | ); | |
299 | } | |
300 | ||
301 | #[test] | |
302 | fn test_moves_expr_up() { | |
303 | check( | |
304 | r#" | |
305 | fn main() { | |
306 | println!("Hello, world"); | |
307 | println!("All I want to say is...");$0$0 | |
308 | } | |
309 | "#, | |
310 | expect![[r#" | |
311 | fn main() { | |
312 | println!("All I want to say is...");$0 | |
313 | println!("Hello, world"); | |
314 | } | |
315 | "#]], | |
316 | Direction::Up, | |
317 | ); | |
318 | check( | |
319 | r#" | |
320 | fn main() { | |
321 | println!("Hello, world"); | |
322 | ||
323 | if true { | |
324 | println!("Test"); | |
325 | }$0$0 | |
326 | } | |
327 | "#, | |
328 | expect![[r#" | |
329 | fn main() { | |
330 | if true { | |
331 | println!("Test"); | |
332 | }$0 | |
333 | ||
334 | println!("Hello, world"); | |
335 | } | |
336 | "#]], | |
337 | Direction::Up, | |
338 | ); | |
339 | check( | |
340 | r#" | |
341 | fn main() { | |
342 | println!("Hello, world"); | |
343 | ||
344 | for i in 0..10 { | |
345 | println!("Test"); | |
346 | }$0$0 | |
347 | } | |
348 | "#, | |
349 | expect![[r#" | |
350 | fn main() { | |
351 | for i in 0..10 { | |
352 | println!("Test"); | |
353 | }$0 | |
354 | ||
355 | println!("Hello, world"); | |
356 | } | |
357 | "#]], | |
358 | Direction::Up, | |
359 | ); | |
360 | check( | |
361 | r#" | |
362 | fn main() { | |
363 | println!("Hello, world"); | |
364 | ||
365 | loop { | |
366 | println!("Test"); | |
367 | }$0$0 | |
368 | } | |
369 | "#, | |
370 | expect![[r#" | |
371 | fn main() { | |
372 | loop { | |
373 | println!("Test"); | |
374 | }$0 | |
375 | ||
376 | println!("Hello, world"); | |
377 | } | |
378 | "#]], | |
379 | Direction::Up, | |
380 | ); | |
381 | check( | |
382 | r#" | |
383 | fn main() { | |
384 | println!("Hello, world"); | |
385 | ||
386 | while true { | |
387 | println!("Test"); | |
388 | }$0$0 | |
389 | } | |
390 | "#, | |
391 | expect![[r#" | |
392 | fn main() { | |
393 | while true { | |
394 | println!("Test"); | |
395 | }$0 | |
396 | ||
397 | println!("Hello, world"); | |
398 | } | |
399 | "#]], | |
400 | Direction::Up, | |
401 | ); | |
402 | check( | |
403 | r#" | |
404 | fn main() { | |
405 | println!("Hello, world"); | |
406 | ||
407 | return 123;$0$0 | |
408 | } | |
409 | "#, | |
410 | expect![[r#" | |
411 | fn main() { | |
412 | return 123;$0 | |
413 | ||
414 | println!("Hello, world"); | |
415 | } | |
416 | "#]], | |
417 | Direction::Up, | |
418 | ); | |
419 | } | |
420 | ||
421 | #[test] | |
422 | fn test_nowhere_to_move_stmt() { | |
423 | check( | |
424 | r#" | |
425 | fn main() { | |
426 | println!("All I want to say is...");$0$0 | |
427 | println!("Hello, world"); | |
428 | } | |
429 | "#, | |
430 | expect![[r#" | |
431 | fn main() { | |
432 | println!("All I want to say is..."); | |
433 | println!("Hello, world"); | |
434 | } | |
435 | "#]], | |
436 | Direction::Up, | |
437 | ); | |
438 | } | |
439 | ||
440 | #[test] | |
441 | fn test_move_item() { | |
442 | check( | |
443 | r#" | |
444 | fn main() {} | |
445 | ||
446 | fn foo() {}$0$0 | |
447 | "#, | |
448 | expect![[r#" | |
449 | fn foo() {}$0 | |
450 | ||
451 | fn main() {} | |
452 | "#]], | |
453 | Direction::Up, | |
454 | ); | |
455 | } | |
456 | ||
457 | #[test] | |
458 | fn test_move_impl_up() { | |
459 | check( | |
460 | r#" | |
461 | struct Yay; | |
462 | ||
463 | trait Wow {} | |
464 | ||
465 | impl Wow for Yay $0$0{} | |
466 | "#, | |
467 | expect![[r#" | |
468 | struct Yay; | |
469 | ||
470 | impl Wow for Yay $0{} | |
471 | ||
472 | trait Wow {} | |
473 | "#]], | |
474 | Direction::Up, | |
475 | ); | |
476 | } | |
477 | ||
478 | #[test] | |
479 | fn test_move_use_up() { | |
480 | check( | |
481 | r#" | |
482 | use std::vec::Vec; | |
483 | use std::collections::HashMap$0$0; | |
484 | "#, | |
485 | expect![[r#" | |
486 | use std::collections::HashMap$0; | |
487 | use std::vec::Vec; | |
488 | "#]], | |
489 | Direction::Up, | |
490 | ); | |
491 | } | |
492 | ||
493 | #[test] | |
494 | fn test_moves_match_expr_up() { | |
495 | check( | |
496 | r#" | |
497 | fn main() { | |
498 | let test = 123; | |
499 | ||
500 | $0match test { | |
501 | 456 => {}, | |
502 | _ => {} | |
503 | };$0 | |
504 | } | |
505 | "#, | |
506 | expect![[r#" | |
507 | fn main() { | |
508 | match test { | |
509 | 456 => {}, | |
510 | _ => {} | |
511 | }; | |
512 | ||
513 | let test = 123; | |
514 | } | |
515 | "#]], | |
516 | Direction::Up, | |
517 | ); | |
518 | } | |
519 | ||
520 | #[test] | |
521 | fn test_moves_param() { | |
522 | check( | |
523 | r#" | |
524 | fn test(one: i32, two$0$0: u32) {} | |
525 | ||
526 | fn main() { | |
527 | test(123, 456); | |
528 | } | |
529 | "#, | |
530 | expect![[r#" | |
531 | fn test(two$0: u32, one: i32) {} | |
532 | ||
533 | fn main() { | |
534 | test(123, 456); | |
535 | } | |
536 | "#]], | |
537 | Direction::Up, | |
538 | ); | |
539 | check( | |
540 | r#" | |
541 | fn f($0$0arg: u8, arg2: u16) {} | |
542 | "#, | |
543 | expect![[r#" | |
544 | fn f(arg2: u16, $0arg: u8) {} | |
545 | "#]], | |
546 | Direction::Down, | |
547 | ); | |
548 | } | |
549 | ||
550 | #[test] | |
551 | fn test_moves_arg_up() { | |
552 | check( | |
553 | r#" | |
554 | fn test(one: i32, two: u32) {} | |
555 | ||
556 | fn main() { | |
557 | test(123, 456$0$0); | |
558 | } | |
559 | "#, | |
560 | expect![[r#" | |
561 | fn test(one: i32, two: u32) {} | |
562 | ||
563 | fn main() { | |
564 | test(456$0, 123); | |
565 | } | |
566 | "#]], | |
567 | Direction::Up, | |
568 | ); | |
569 | } | |
570 | ||
571 | #[test] | |
572 | fn test_moves_arg_down() { | |
573 | check( | |
574 | r#" | |
575 | fn test(one: i32, two: u32) {} | |
576 | ||
577 | fn main() { | |
578 | test(123$0$0, 456); | |
579 | } | |
580 | "#, | |
581 | expect![[r#" | |
582 | fn test(one: i32, two: u32) {} | |
583 | ||
584 | fn main() { | |
585 | test(456, 123$0); | |
586 | } | |
587 | "#]], | |
588 | Direction::Down, | |
589 | ); | |
590 | } | |
591 | ||
592 | #[test] | |
593 | fn test_nowhere_to_move_arg() { | |
594 | check( | |
595 | r#" | |
596 | fn test(one: i32, two: u32) {} | |
597 | ||
598 | fn main() { | |
599 | test(123$0$0, 456); | |
600 | } | |
601 | "#, | |
602 | expect![[r#" | |
603 | fn test(one: i32, two: u32) {} | |
604 | ||
605 | fn main() { | |
606 | test(123, 456); | |
607 | } | |
608 | "#]], | |
609 | Direction::Up, | |
610 | ); | |
611 | } | |
612 | ||
613 | #[test] | |
614 | fn test_moves_generic_param_up() { | |
615 | check( | |
616 | r#" | |
617 | struct Test<A, B$0$0>(A, B); | |
618 | ||
619 | fn main() {} | |
620 | "#, | |
621 | expect![[r#" | |
622 | struct Test<B$0, A>(A, B); | |
623 | ||
624 | fn main() {} | |
625 | "#]], | |
626 | Direction::Up, | |
627 | ); | |
628 | } | |
629 | ||
630 | #[test] | |
631 | fn test_moves_generic_arg_up() { | |
632 | check( | |
633 | r#" | |
634 | struct Test<A, B>(A, B); | |
635 | ||
636 | fn main() { | |
637 | let t = Test::<i32, &str$0$0>(123, "yay"); | |
638 | } | |
639 | "#, | |
640 | expect![[r#" | |
641 | struct Test<A, B>(A, B); | |
642 | ||
643 | fn main() { | |
644 | let t = Test::<&str$0, i32>(123, "yay"); | |
645 | } | |
646 | "#]], | |
647 | Direction::Up, | |
648 | ); | |
649 | } | |
650 | ||
651 | #[test] | |
652 | fn test_moves_variant_up() { | |
653 | check( | |
654 | r#" | |
655 | enum Hello { | |
656 | One, | |
657 | Two$0$0 | |
658 | } | |
659 | ||
660 | fn main() {} | |
661 | "#, | |
662 | expect![[r#" | |
663 | enum Hello { | |
664 | Two$0, | |
665 | One | |
666 | } | |
667 | ||
668 | fn main() {} | |
669 | "#]], | |
670 | Direction::Up, | |
671 | ); | |
672 | } | |
673 | ||
674 | #[test] | |
675 | fn test_moves_type_bound_up() { | |
676 | check( | |
677 | r#" | |
678 | trait One {} | |
679 | ||
680 | trait Two {} | |
681 | ||
682 | fn test<T: One + Two$0$0>(t: T) {} | |
683 | ||
684 | fn main() {} | |
685 | "#, | |
686 | expect![[r#" | |
687 | trait One {} | |
688 | ||
689 | trait Two {} | |
690 | ||
691 | fn test<T: Two$0 + One>(t: T) {} | |
692 | ||
693 | fn main() {} | |
694 | "#]], | |
695 | Direction::Up, | |
696 | ); | |
697 | } | |
698 | ||
699 | #[test] | |
700 | fn test_prioritizes_trait_items() { | |
701 | check( | |
702 | r#" | |
703 | struct Test; | |
704 | ||
705 | trait Yay { | |
706 | type One; | |
707 | ||
708 | type Two; | |
709 | ||
710 | fn inner(); | |
711 | } | |
712 | ||
713 | impl Yay for Test { | |
714 | type One = i32; | |
715 | ||
716 | type Two = u32; | |
717 | ||
718 | fn inner() {$0$0 | |
719 | println!("Mmmm"); | |
720 | } | |
721 | } | |
722 | "#, | |
723 | expect![[r#" | |
724 | struct Test; | |
725 | ||
726 | trait Yay { | |
727 | type One; | |
728 | ||
729 | type Two; | |
730 | ||
731 | fn inner(); | |
732 | } | |
733 | ||
734 | impl Yay for Test { | |
735 | type One = i32; | |
736 | ||
737 | fn inner() {$0 | |
738 | println!("Mmmm"); | |
739 | } | |
740 | ||
741 | type Two = u32; | |
742 | } | |
743 | "#]], | |
744 | Direction::Up, | |
745 | ); | |
746 | } | |
747 | ||
748 | #[test] | |
749 | fn test_weird_nesting() { | |
750 | check( | |
751 | r#" | |
752 | fn test() { | |
753 | mod hello { | |
754 | fn inner() {} | |
755 | } | |
756 | ||
757 | mod hi {$0$0 | |
758 | fn inner() {} | |
759 | } | |
760 | } | |
761 | "#, | |
762 | expect![[r#" | |
763 | fn test() { | |
764 | mod hi {$0 | |
765 | fn inner() {} | |
766 | } | |
767 | ||
768 | mod hello { | |
769 | fn inner() {} | |
770 | } | |
771 | } | |
772 | "#]], | |
773 | Direction::Up, | |
774 | ); | |
775 | } | |
776 | ||
777 | #[test] | |
778 | fn test_cursor_at_item_start() { | |
779 | check( | |
780 | r#" | |
781 | $0$0#[derive(Debug)] | |
782 | enum FooBar { | |
783 | Foo, | |
784 | Bar, | |
785 | } | |
786 | ||
787 | fn main() {} | |
788 | "#, | |
789 | expect![[r##" | |
790 | fn main() {} | |
791 | ||
792 | $0#[derive(Debug)] | |
793 | enum FooBar { | |
794 | Foo, | |
795 | Bar, | |
796 | } | |
797 | "##]], | |
798 | Direction::Down, | |
799 | ); | |
800 | check( | |
801 | r#" | |
802 | $0$0enum FooBar { | |
803 | Foo, | |
804 | Bar, | |
805 | } | |
806 | ||
807 | fn main() {} | |
808 | "#, | |
809 | expect![[r#" | |
810 | fn main() {} | |
811 | ||
812 | $0enum FooBar { | |
813 | Foo, | |
814 | Bar, | |
815 | } | |
816 | "#]], | |
817 | Direction::Down, | |
818 | ); | |
819 | check( | |
820 | r#" | |
821 | struct Test; | |
822 | ||
823 | trait SomeTrait {} | |
824 | ||
825 | $0$0impl SomeTrait for Test {} | |
826 | ||
827 | fn main() {} | |
828 | "#, | |
829 | expect![[r#" | |
830 | struct Test; | |
831 | ||
832 | $0impl SomeTrait for Test {} | |
833 | ||
834 | trait SomeTrait {} | |
835 | ||
836 | fn main() {} | |
837 | "#]], | |
838 | Direction::Up, | |
839 | ); | |
840 | } | |
841 | ||
842 | #[test] | |
843 | fn test_cursor_at_item_end() { | |
844 | check( | |
845 | r#" | |
846 | enum FooBar { | |
847 | Foo, | |
848 | Bar, | |
849 | }$0$0 | |
850 | ||
851 | fn main() {} | |
852 | "#, | |
853 | expect![[r#" | |
854 | fn main() {} | |
855 | ||
856 | enum FooBar { | |
857 | Foo, | |
858 | Bar, | |
859 | }$0 | |
860 | "#]], | |
861 | Direction::Down, | |
862 | ); | |
863 | check( | |
864 | r#" | |
865 | struct Test; | |
866 | ||
867 | trait SomeTrait {} | |
868 | ||
869 | impl SomeTrait for Test {}$0$0 | |
870 | ||
871 | fn main() {} | |
872 | "#, | |
873 | expect![[r#" | |
874 | struct Test; | |
875 | ||
876 | impl SomeTrait for Test {}$0 | |
877 | ||
878 | trait SomeTrait {} | |
879 | ||
880 | fn main() {} | |
881 | "#]], | |
882 | Direction::Up, | |
883 | ); | |
884 | } | |
885 | ||
886 | #[test] | |
887 | fn handles_empty_file() { | |
888 | check(r#"$0$0"#, expect![[r#""#]], Direction::Up); | |
889 | } | |
890 | } |