diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-03-22 13:08:45 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2021-03-22 13:08:45 +0000 |
commit | d4fa6721afacec78a750df1bb1f0e7e950eaf73c (patch) | |
tree | efabf84f708868484e0dac7893f77ddfba6d9c21 | |
parent | 3af1885bd2c4d3470d203a216488946ee8572970 (diff) | |
parent | d331155f8db056a0f7a406498c96f759f620d2c7 (diff) |
Merge #8054
8054: Item movers r=matklad a=ivan770
Closes #6823
https://user-images.githubusercontent.com/14003886/111331579-b4f43480-8679-11eb-9af0-e4dabacc4923.mp4
Implementation issues:
- [ ] Most of items are non-movable, since _movability_ of any item has to be determined manually. Common ones are movable though
- [x] Cursor should move with the item
Co-authored-by: ivan770 <[email protected]>
-rw-r--r-- | crates/ide/src/lib.rs | 10 | ||||
-rw-r--r-- | crates/ide/src/move_item.rs | 620 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 19 | ||||
-rw-r--r-- | crates/rust-analyzer/src/lsp_ext.rs | 22 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 1 | ||||
-rw-r--r-- | crates/rust-analyzer/src/to_proto.rs | 12 | ||||
-rw-r--r-- | docs/dev/lsp-extensions.md | 28 | ||||
-rw-r--r-- | editors/code/package.json | 10 | ||||
-rw-r--r-- | editors/code/src/commands.ts | 45 | ||||
-rw-r--r-- | editors/code/src/lsp_ext.ts | 13 | ||||
-rw-r--r-- | editors/code/src/main.ts | 2 |
11 files changed, 781 insertions, 1 deletions
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 662da5a96..3f73c0632 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -37,6 +37,7 @@ mod hover; | |||
37 | mod inlay_hints; | 37 | mod inlay_hints; |
38 | mod join_lines; | 38 | mod join_lines; |
39 | mod matching_brace; | 39 | mod matching_brace; |
40 | mod move_item; | ||
40 | mod parent_module; | 41 | mod parent_module; |
41 | mod references; | 42 | mod references; |
42 | mod fn_references; | 43 | mod fn_references; |
@@ -76,6 +77,7 @@ pub use crate::{ | |||
76 | hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult}, | 77 | hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult}, |
77 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, | 78 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, |
78 | markup::Markup, | 79 | markup::Markup, |
80 | move_item::Direction, | ||
79 | prime_caches::PrimeCachesProgress, | 81 | prime_caches::PrimeCachesProgress, |
80 | references::{rename::RenameError, ReferenceSearchResult}, | 82 | references::{rename::RenameError, ReferenceSearchResult}, |
81 | runnables::{Runnable, RunnableKind, TestId}, | 83 | runnables::{Runnable, RunnableKind, TestId}, |
@@ -583,6 +585,14 @@ impl Analysis { | |||
583 | self.with_db(|db| annotations::resolve_annotation(db, annotation)) | 585 | self.with_db(|db| annotations::resolve_annotation(db, annotation)) |
584 | } | 586 | } |
585 | 587 | ||
588 | pub fn move_item( | ||
589 | &self, | ||
590 | range: FileRange, | ||
591 | direction: Direction, | ||
592 | ) -> Cancelable<Option<TextEdit>> { | ||
593 | self.with_db(|db| move_item::move_item(db, range, direction)) | ||
594 | } | ||
595 | |||
586 | /// Performs an operation on that may be Canceled. | 596 | /// Performs an operation on that may be Canceled. |
587 | fn with_db<F, T>(&self, f: F) -> Cancelable<T> | 597 | fn with_db<F, T>(&self, f: F) -> Cancelable<T> |
588 | where | 598 | where |
diff --git a/crates/ide/src/move_item.rs b/crates/ide/src/move_item.rs new file mode 100644 index 000000000..48690b073 --- /dev/null +++ b/crates/ide/src/move_item.rs | |||
@@ -0,0 +1,620 @@ | |||
1 | use std::iter::once; | ||
2 | |||
3 | use hir::Semantics; | ||
4 | use ide_db::{base_db::FileRange, RootDatabase}; | ||
5 | use itertools::Itertools; | ||
6 | use syntax::{ | ||
7 | algo, ast, match_ast, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, | ||
8 | }; | ||
9 | use text_edit::{TextEdit, TextEditBuilder}; | ||
10 | |||
11 | pub enum Direction { | ||
12 | Up, | ||
13 | Down, | ||
14 | } | ||
15 | |||
16 | // Feature: Move Item | ||
17 | // | ||
18 | // Move item under cursor or selection up and down. | ||
19 | // | ||
20 | // |=== | ||
21 | // | Editor | Action Name | ||
22 | // | ||
23 | // | VS Code | **Rust Analyzer: Move item up** | ||
24 | // | VS Code | **Rust Analyzer: Move item down** | ||
25 | // |=== | ||
26 | pub(crate) fn move_item( | ||
27 | db: &RootDatabase, | ||
28 | range: FileRange, | ||
29 | direction: Direction, | ||
30 | ) -> Option<TextEdit> { | ||
31 | let sema = Semantics::new(db); | ||
32 | let file = sema.parse(range.file_id); | ||
33 | |||
34 | let item = file.syntax().covering_element(range.range); | ||
35 | find_ancestors(item, direction, range.range) | ||
36 | } | ||
37 | |||
38 | fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> { | ||
39 | let root = match item { | ||
40 | NodeOrToken::Node(node) => node, | ||
41 | NodeOrToken::Token(token) => token.parent()?, | ||
42 | }; | ||
43 | |||
44 | let movable = [ | ||
45 | SyntaxKind::ARG_LIST, | ||
46 | SyntaxKind::GENERIC_PARAM_LIST, | ||
47 | SyntaxKind::GENERIC_ARG_LIST, | ||
48 | SyntaxKind::VARIANT_LIST, | ||
49 | SyntaxKind::TYPE_BOUND_LIST, | ||
50 | SyntaxKind::MATCH_ARM, | ||
51 | SyntaxKind::PARAM, | ||
52 | SyntaxKind::LET_STMT, | ||
53 | SyntaxKind::EXPR_STMT, | ||
54 | SyntaxKind::MATCH_EXPR, | ||
55 | SyntaxKind::MACRO_CALL, | ||
56 | SyntaxKind::TYPE_ALIAS, | ||
57 | SyntaxKind::TRAIT, | ||
58 | SyntaxKind::IMPL, | ||
59 | SyntaxKind::MACRO_DEF, | ||
60 | SyntaxKind::STRUCT, | ||
61 | SyntaxKind::UNION, | ||
62 | SyntaxKind::ENUM, | ||
63 | SyntaxKind::FN, | ||
64 | SyntaxKind::MODULE, | ||
65 | SyntaxKind::USE, | ||
66 | SyntaxKind::STATIC, | ||
67 | SyntaxKind::CONST, | ||
68 | SyntaxKind::MACRO_RULES, | ||
69 | ]; | ||
70 | |||
71 | let ancestor = once(root.clone()) | ||
72 | .chain(root.ancestors()) | ||
73 | .find(|ancestor| movable.contains(&ancestor.kind()))?; | ||
74 | |||
75 | move_in_direction(&ancestor, direction, range) | ||
76 | } | ||
77 | |||
78 | fn move_in_direction( | ||
79 | node: &SyntaxNode, | ||
80 | direction: Direction, | ||
81 | range: TextRange, | ||
82 | ) -> Option<TextEdit> { | ||
83 | match_ast! { | ||
84 | match node { | ||
85 | ast::ArgList(it) => swap_sibling_in_list(it.args(), range, direction), | ||
86 | ast::GenericParamList(it) => swap_sibling_in_list(it.generic_params(), range, direction), | ||
87 | ast::GenericArgList(it) => swap_sibling_in_list(it.generic_args(), range, direction), | ||
88 | ast::VariantList(it) => swap_sibling_in_list(it.variants(), range, direction), | ||
89 | ast::TypeBoundList(it) => swap_sibling_in_list(it.bounds(), range, direction), | ||
90 | _ => Some(replace_nodes(node, &match direction { | ||
91 | Direction::Up => node.prev_sibling(), | ||
92 | Direction::Down => node.next_sibling(), | ||
93 | }?)) | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | |||
98 | fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( | ||
99 | list: I, | ||
100 | range: TextRange, | ||
101 | direction: Direction, | ||
102 | ) -> Option<TextEdit> { | ||
103 | let (l, r) = list | ||
104 | .tuple_windows() | ||
105 | .filter(|(l, r)| match direction { | ||
106 | Direction::Up => r.syntax().text_range().contains_range(range), | ||
107 | Direction::Down => l.syntax().text_range().contains_range(range), | ||
108 | }) | ||
109 | .next()?; | ||
110 | |||
111 | Some(replace_nodes(l.syntax(), r.syntax())) | ||
112 | } | ||
113 | |||
114 | fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit { | ||
115 | let mut edit = TextEditBuilder::default(); | ||
116 | |||
117 | algo::diff(first, second).into_text_edit(&mut edit); | ||
118 | algo::diff(second, first).into_text_edit(&mut edit); | ||
119 | |||
120 | edit.finish() | ||
121 | } | ||
122 | |||
123 | #[cfg(test)] | ||
124 | mod tests { | ||
125 | use crate::fixture; | ||
126 | use expect_test::{expect, Expect}; | ||
127 | |||
128 | use crate::Direction; | ||
129 | |||
130 | fn check(ra_fixture: &str, expect: Expect, direction: Direction) { | ||
131 | let (analysis, range) = fixture::range(ra_fixture); | ||
132 | let edit = analysis.move_item(range, direction).unwrap().unwrap_or_default(); | ||
133 | let mut file = analysis.file_text(range.file_id).unwrap().to_string(); | ||
134 | edit.apply(&mut file); | ||
135 | expect.assert_eq(&file); | ||
136 | } | ||
137 | |||
138 | #[test] | ||
139 | fn test_moves_match_arm_up() { | ||
140 | check( | ||
141 | r#" | ||
142 | fn main() { | ||
143 | match true { | ||
144 | true => { | ||
145 | println!("Hello, world"); | ||
146 | }, | ||
147 | false =>$0$0 { | ||
148 | println!("Test"); | ||
149 | } | ||
150 | }; | ||
151 | } | ||
152 | "#, | ||
153 | expect![[r#" | ||
154 | fn main() { | ||
155 | match true { | ||
156 | false => { | ||
157 | println!("Test"); | ||
158 | }, | ||
159 | true => { | ||
160 | println!("Hello, world"); | ||
161 | } | ||
162 | }; | ||
163 | } | ||
164 | "#]], | ||
165 | Direction::Up, | ||
166 | ); | ||
167 | } | ||
168 | |||
169 | #[test] | ||
170 | fn test_moves_match_arm_down() { | ||
171 | check( | ||
172 | r#" | ||
173 | fn main() { | ||
174 | match true { | ||
175 | true =>$0$0 { | ||
176 | println!("Hello, world"); | ||
177 | }, | ||
178 | false => { | ||
179 | println!("Test"); | ||
180 | } | ||
181 | }; | ||
182 | } | ||
183 | "#, | ||
184 | expect![[r#" | ||
185 | fn main() { | ||
186 | match true { | ||
187 | false => { | ||
188 | println!("Test"); | ||
189 | }, | ||
190 | true => { | ||
191 | println!("Hello, world"); | ||
192 | } | ||
193 | }; | ||
194 | } | ||
195 | "#]], | ||
196 | Direction::Down, | ||
197 | ); | ||
198 | } | ||
199 | |||
200 | #[test] | ||
201 | fn test_nowhere_to_move() { | ||
202 | check( | ||
203 | r#" | ||
204 | fn main() { | ||
205 | match true { | ||
206 | true =>$0$0 { | ||
207 | println!("Hello, world"); | ||
208 | }, | ||
209 | false => { | ||
210 | println!("Test"); | ||
211 | } | ||
212 | }; | ||
213 | } | ||
214 | "#, | ||
215 | expect![[r#" | ||
216 | fn main() { | ||
217 | match true { | ||
218 | true => { | ||
219 | println!("Hello, world"); | ||
220 | }, | ||
221 | false => { | ||
222 | println!("Test"); | ||
223 | } | ||
224 | }; | ||
225 | } | ||
226 | "#]], | ||
227 | Direction::Up, | ||
228 | ); | ||
229 | } | ||
230 | |||
231 | #[test] | ||
232 | fn test_moves_let_stmt_up() { | ||
233 | check( | ||
234 | r#" | ||
235 | fn main() { | ||
236 | let test = 123; | ||
237 | let test2$0$0 = 456; | ||
238 | } | ||
239 | "#, | ||
240 | expect![[r#" | ||
241 | fn main() { | ||
242 | let test2 = 456; | ||
243 | let test = 123; | ||
244 | } | ||
245 | "#]], | ||
246 | Direction::Up, | ||
247 | ); | ||
248 | } | ||
249 | |||
250 | #[test] | ||
251 | fn test_moves_expr_up() { | ||
252 | check( | ||
253 | r#" | ||
254 | fn main() { | ||
255 | println!("Hello, world"); | ||
256 | println!("All I want to say is...");$0$0 | ||
257 | } | ||
258 | "#, | ||
259 | expect![[r#" | ||
260 | fn main() { | ||
261 | println!("All I want to say is..."); | ||
262 | println!("Hello, world"); | ||
263 | } | ||
264 | "#]], | ||
265 | Direction::Up, | ||
266 | ); | ||
267 | } | ||
268 | |||
269 | #[test] | ||
270 | fn test_nowhere_to_move_stmt() { | ||
271 | check( | ||
272 | r#" | ||
273 | fn main() { | ||
274 | println!("All I want to say is...");$0$0 | ||
275 | println!("Hello, world"); | ||
276 | } | ||
277 | "#, | ||
278 | expect![[r#" | ||
279 | fn main() { | ||
280 | println!("All I want to say is..."); | ||
281 | println!("Hello, world"); | ||
282 | } | ||
283 | "#]], | ||
284 | Direction::Up, | ||
285 | ); | ||
286 | } | ||
287 | |||
288 | #[test] | ||
289 | fn test_move_item() { | ||
290 | check( | ||
291 | r#" | ||
292 | fn main() {} | ||
293 | |||
294 | fn foo() {}$0$0 | ||
295 | "#, | ||
296 | expect![[r#" | ||
297 | fn foo() {} | ||
298 | |||
299 | fn main() {} | ||
300 | "#]], | ||
301 | Direction::Up, | ||
302 | ); | ||
303 | } | ||
304 | |||
305 | #[test] | ||
306 | fn test_move_impl_up() { | ||
307 | check( | ||
308 | r#" | ||
309 | struct Yay; | ||
310 | |||
311 | trait Wow {} | ||
312 | |||
313 | impl Wow for Yay $0$0{} | ||
314 | "#, | ||
315 | expect![[r#" | ||
316 | struct Yay; | ||
317 | |||
318 | impl Wow for Yay {} | ||
319 | |||
320 | trait Wow {} | ||
321 | "#]], | ||
322 | Direction::Up, | ||
323 | ); | ||
324 | } | ||
325 | |||
326 | #[test] | ||
327 | fn test_move_use_up() { | ||
328 | check( | ||
329 | r#" | ||
330 | use std::vec::Vec; | ||
331 | use std::collections::HashMap$0$0; | ||
332 | "#, | ||
333 | expect![[r#" | ||
334 | use std::collections::HashMap; | ||
335 | use std::vec::Vec; | ||
336 | "#]], | ||
337 | Direction::Up, | ||
338 | ); | ||
339 | } | ||
340 | |||
341 | #[test] | ||
342 | fn test_moves_match_expr_up() { | ||
343 | check( | ||
344 | r#" | ||
345 | fn main() { | ||
346 | let test = 123; | ||
347 | |||
348 | $0match test { | ||
349 | 456 => {}, | ||
350 | _ => {} | ||
351 | };$0 | ||
352 | } | ||
353 | "#, | ||
354 | expect![[r#" | ||
355 | fn main() { | ||
356 | match test { | ||
357 | 456 => {}, | ||
358 | _ => {} | ||
359 | }; | ||
360 | |||
361 | let test = 123; | ||
362 | } | ||
363 | "#]], | ||
364 | Direction::Up, | ||
365 | ); | ||
366 | } | ||
367 | |||
368 | #[test] | ||
369 | fn test_moves_param_up() { | ||
370 | check( | ||
371 | r#" | ||
372 | fn test(one: i32, two$0$0: u32) {} | ||
373 | |||
374 | fn main() { | ||
375 | test(123, 456); | ||
376 | } | ||
377 | "#, | ||
378 | expect![[r#" | ||
379 | fn test(two: u32, one: i32) {} | ||
380 | |||
381 | fn main() { | ||
382 | test(123, 456); | ||
383 | } | ||
384 | "#]], | ||
385 | Direction::Up, | ||
386 | ); | ||
387 | } | ||
388 | |||
389 | #[test] | ||
390 | fn test_moves_arg_up() { | ||
391 | check( | ||
392 | r#" | ||
393 | fn test(one: i32, two: u32) {} | ||
394 | |||
395 | fn main() { | ||
396 | test(123, 456$0$0); | ||
397 | } | ||
398 | "#, | ||
399 | expect![[r#" | ||
400 | fn test(one: i32, two: u32) {} | ||
401 | |||
402 | fn main() { | ||
403 | test(456, 123); | ||
404 | } | ||
405 | "#]], | ||
406 | Direction::Up, | ||
407 | ); | ||
408 | } | ||
409 | |||
410 | #[test] | ||
411 | fn test_moves_arg_down() { | ||
412 | check( | ||
413 | r#" | ||
414 | fn test(one: i32, two: u32) {} | ||
415 | |||
416 | fn main() { | ||
417 | test(123$0$0, 456); | ||
418 | } | ||
419 | "#, | ||
420 | expect![[r#" | ||
421 | fn test(one: i32, two: u32) {} | ||
422 | |||
423 | fn main() { | ||
424 | test(456, 123); | ||
425 | } | ||
426 | "#]], | ||
427 | Direction::Down, | ||
428 | ); | ||
429 | } | ||
430 | |||
431 | #[test] | ||
432 | fn test_nowhere_to_move_arg() { | ||
433 | check( | ||
434 | r#" | ||
435 | fn test(one: i32, two: u32) {} | ||
436 | |||
437 | fn main() { | ||
438 | test(123$0$0, 456); | ||
439 | } | ||
440 | "#, | ||
441 | expect![[r#" | ||
442 | fn test(one: i32, two: u32) {} | ||
443 | |||
444 | fn main() { | ||
445 | test(123, 456); | ||
446 | } | ||
447 | "#]], | ||
448 | Direction::Up, | ||
449 | ); | ||
450 | } | ||
451 | |||
452 | #[test] | ||
453 | fn test_moves_generic_param_up() { | ||
454 | check( | ||
455 | r#" | ||
456 | struct Test<A, B$0$0>(A, B); | ||
457 | |||
458 | fn main() {} | ||
459 | "#, | ||
460 | expect![[r#" | ||
461 | struct Test<B, A>(A, B); | ||
462 | |||
463 | fn main() {} | ||
464 | "#]], | ||
465 | Direction::Up, | ||
466 | ); | ||
467 | } | ||
468 | |||
469 | #[test] | ||
470 | fn test_moves_generic_arg_up() { | ||
471 | check( | ||
472 | r#" | ||
473 | struct Test<A, B>(A, B); | ||
474 | |||
475 | fn main() { | ||
476 | let t = Test::<i32, &str$0$0>(123, "yay"); | ||
477 | } | ||
478 | "#, | ||
479 | expect![[r#" | ||
480 | struct Test<A, B>(A, B); | ||
481 | |||
482 | fn main() { | ||
483 | let t = Test::<&str, i32>(123, "yay"); | ||
484 | } | ||
485 | "#]], | ||
486 | Direction::Up, | ||
487 | ); | ||
488 | } | ||
489 | |||
490 | #[test] | ||
491 | fn test_moves_variant_up() { | ||
492 | check( | ||
493 | r#" | ||
494 | enum Hello { | ||
495 | One, | ||
496 | Two$0$0 | ||
497 | } | ||
498 | |||
499 | fn main() {} | ||
500 | "#, | ||
501 | expect![[r#" | ||
502 | enum Hello { | ||
503 | Two, | ||
504 | One | ||
505 | } | ||
506 | |||
507 | fn main() {} | ||
508 | "#]], | ||
509 | Direction::Up, | ||
510 | ); | ||
511 | } | ||
512 | |||
513 | #[test] | ||
514 | fn test_moves_type_bound_up() { | ||
515 | check( | ||
516 | r#" | ||
517 | trait One {} | ||
518 | |||
519 | trait Two {} | ||
520 | |||
521 | fn test<T: One + Two$0$0>(t: T) {} | ||
522 | |||
523 | fn main() {} | ||
524 | "#, | ||
525 | expect![[r#" | ||
526 | trait One {} | ||
527 | |||
528 | trait Two {} | ||
529 | |||
530 | fn test<T: Two + One>(t: T) {} | ||
531 | |||
532 | fn main() {} | ||
533 | "#]], | ||
534 | Direction::Up, | ||
535 | ); | ||
536 | } | ||
537 | |||
538 | #[test] | ||
539 | fn test_prioritizes_trait_items() { | ||
540 | check( | ||
541 | r#" | ||
542 | struct Test; | ||
543 | |||
544 | trait Yay { | ||
545 | type One; | ||
546 | |||
547 | type Two; | ||
548 | |||
549 | fn inner(); | ||
550 | } | ||
551 | |||
552 | impl Yay for Test { | ||
553 | type One = i32; | ||
554 | |||
555 | type Two = u32; | ||
556 | |||
557 | fn inner() {$0$0 | ||
558 | println!("Mmmm"); | ||
559 | } | ||
560 | } | ||
561 | "#, | ||
562 | expect![[r#" | ||
563 | struct Test; | ||
564 | |||
565 | trait Yay { | ||
566 | type One; | ||
567 | |||
568 | type Two; | ||
569 | |||
570 | fn inner(); | ||
571 | } | ||
572 | |||
573 | impl Yay for Test { | ||
574 | type One = i32; | ||
575 | |||
576 | fn inner() { | ||
577 | println!("Mmmm"); | ||
578 | } | ||
579 | |||
580 | type Two = u32; | ||
581 | } | ||
582 | "#]], | ||
583 | Direction::Up, | ||
584 | ); | ||
585 | } | ||
586 | |||
587 | #[test] | ||
588 | fn test_weird_nesting() { | ||
589 | check( | ||
590 | r#" | ||
591 | fn test() { | ||
592 | mod hello { | ||
593 | fn inner() {} | ||
594 | } | ||
595 | |||
596 | mod hi {$0$0 | ||
597 | fn inner() {} | ||
598 | } | ||
599 | } | ||
600 | "#, | ||
601 | expect![[r#" | ||
602 | fn test() { | ||
603 | mod hi { | ||
604 | fn inner() {} | ||
605 | } | ||
606 | |||
607 | mod hello { | ||
608 | fn inner() {} | ||
609 | } | ||
610 | } | ||
611 | "#]], | ||
612 | Direction::Up, | ||
613 | ); | ||
614 | } | ||
615 | |||
616 | #[test] | ||
617 | fn handles_empty_file() { | ||
618 | check(r#"$0$0"#, expect![[r#""#]], Direction::Up); | ||
619 | } | ||
620 | } | ||
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 880fea622..85e67554c 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -1427,6 +1427,25 @@ pub(crate) fn handle_open_cargo_toml( | |||
1427 | Ok(Some(res)) | 1427 | Ok(Some(res)) |
1428 | } | 1428 | } |
1429 | 1429 | ||
1430 | pub(crate) fn handle_move_item( | ||
1431 | snap: GlobalStateSnapshot, | ||
1432 | params: lsp_ext::MoveItemParams, | ||
1433 | ) -> Result<Option<lsp_types::TextDocumentEdit>> { | ||
1434 | let _p = profile::span("handle_move_item"); | ||
1435 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | ||
1436 | let range = from_proto::file_range(&snap, params.text_document, params.range)?; | ||
1437 | |||
1438 | let direction = match params.direction { | ||
1439 | lsp_ext::MoveItemDirection::Up => ide::Direction::Up, | ||
1440 | lsp_ext::MoveItemDirection::Down => ide::Direction::Down, | ||
1441 | }; | ||
1442 | |||
1443 | match snap.analysis.move_item(range, direction)? { | ||
1444 | Some(text_edit) => Ok(Some(to_proto::text_document_edit(&snap, file_id, text_edit)?)), | ||
1445 | None => Ok(None), | ||
1446 | } | ||
1447 | } | ||
1448 | |||
1430 | fn to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::CommandLink { | 1449 | fn to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::CommandLink { |
1431 | lsp_ext::CommandLink { tooltip: Some(tooltip), command } | 1450 | lsp_ext::CommandLink { tooltip: Some(tooltip), command } |
1432 | } | 1451 | } |
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index efcdcd1d9..0e1fec209 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs | |||
@@ -402,3 +402,25 @@ pub(crate) enum CodeLensResolveData { | |||
402 | pub fn supports_utf8(caps: &lsp_types::ClientCapabilities) -> bool { | 402 | pub fn supports_utf8(caps: &lsp_types::ClientCapabilities) -> bool { |
403 | caps.offset_encoding.as_deref().unwrap_or_default().iter().any(|it| it == "utf-8") | 403 | caps.offset_encoding.as_deref().unwrap_or_default().iter().any(|it| it == "utf-8") |
404 | } | 404 | } |
405 | |||
406 | pub enum MoveItem {} | ||
407 | |||
408 | impl Request for MoveItem { | ||
409 | type Params = MoveItemParams; | ||
410 | type Result = Option<lsp_types::TextDocumentEdit>; | ||
411 | const METHOD: &'static str = "experimental/moveItem"; | ||
412 | } | ||
413 | |||
414 | #[derive(Serialize, Deserialize, Debug)] | ||
415 | #[serde(rename_all = "camelCase")] | ||
416 | pub struct MoveItemParams { | ||
417 | pub direction: MoveItemDirection, | ||
418 | pub text_document: TextDocumentIdentifier, | ||
419 | pub range: Range, | ||
420 | } | ||
421 | |||
422 | #[derive(Serialize, Deserialize, Debug)] | ||
423 | pub enum MoveItemDirection { | ||
424 | Up, | ||
425 | Down, | ||
426 | } | ||
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index c63a0eaea..e88f16cc1 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -504,6 +504,7 @@ impl GlobalState { | |||
504 | .on::<lsp_ext::HoverRequest>(handlers::handle_hover) | 504 | .on::<lsp_ext::HoverRequest>(handlers::handle_hover) |
505 | .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs) | 505 | .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs) |
506 | .on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml) | 506 | .on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml) |
507 | .on::<lsp_ext::MoveItem>(handlers::handle_move_item) | ||
507 | .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting) | 508 | .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting) |
508 | .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol) | 509 | .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol) |
509 | .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol) | 510 | .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol) |
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index c1ca7ff9b..25169005f 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -658,6 +658,18 @@ pub(crate) fn goto_definition_response( | |||
658 | } | 658 | } |
659 | } | 659 | } |
660 | 660 | ||
661 | pub(crate) fn text_document_edit( | ||
662 | snap: &GlobalStateSnapshot, | ||
663 | file_id: FileId, | ||
664 | edit: TextEdit, | ||
665 | ) -> Result<lsp_types::TextDocumentEdit> { | ||
666 | let text_document = optional_versioned_text_document_identifier(snap, file_id); | ||
667 | let line_index = snap.file_line_index(file_id)?; | ||
668 | let edits = | ||
669 | edit.into_iter().map(|it| lsp_types::OneOf::Left(text_edit(&line_index, it))).collect(); | ||
670 | Ok(lsp_types::TextDocumentEdit { text_document, edits }) | ||
671 | } | ||
672 | |||
661 | pub(crate) fn snippet_text_document_edit( | 673 | pub(crate) fn snippet_text_document_edit( |
662 | snap: &GlobalStateSnapshot, | 674 | snap: &GlobalStateSnapshot, |
663 | is_snippet: bool, | 675 | is_snippet: bool, |
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index 694fafcd5..8a6f9f06e 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md | |||
@@ -1,5 +1,5 @@ | |||
1 | <!--- | 1 | <!--- |
2 | lsp_ext.rs hash: 4dfa8d7035f4aee7 | 2 | lsp_ext.rs hash: e8a7502bd2b2c2f5 |
3 | 3 | ||
4 | If you need to change the above hash to make the test pass, please check if you | 4 | If you need to change the above hash to make the test pass, please check if you |
5 | need to adjust this doc as well and ping this issue: | 5 | need to adjust this doc as well and ping this issue: |
@@ -595,3 +595,29 @@ interface TestInfo { | |||
595 | runnable: Runnable; | 595 | runnable: Runnable; |
596 | } | 596 | } |
597 | ``` | 597 | ``` |
598 | |||
599 | ## Hover Actions | ||
600 | |||
601 | **Issue:** https://github.com/rust-analyzer/rust-analyzer/issues/6823 | ||
602 | |||
603 | This request is sent from client to server to move item under cursor or selection in some direction. | ||
604 | |||
605 | **Method:** `experimental/moveItemUp` | ||
606 | **Method:** `experimental/moveItemDown` | ||
607 | |||
608 | **Request:** `MoveItemParams` | ||
609 | |||
610 | **Response:** `TextDocumentEdit | null` | ||
611 | |||
612 | ```typescript | ||
613 | export interface MoveItemParams { | ||
614 | textDocument: lc.TextDocumentIdentifier, | ||
615 | range: lc.Range, | ||
616 | direction: Direction | ||
617 | } | ||
618 | |||
619 | export const enum Direction { | ||
620 | Up = "Up", | ||
621 | Down = "Down" | ||
622 | } | ||
623 | ``` | ||
diff --git a/editors/code/package.json b/editors/code/package.json index a2ed9b2d5..faec45276 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -208,6 +208,16 @@ | |||
208 | "command": "rust-analyzer.peekTests", | 208 | "command": "rust-analyzer.peekTests", |
209 | "title": "Peek related tests", | 209 | "title": "Peek related tests", |
210 | "category": "Rust Analyzer" | 210 | "category": "Rust Analyzer" |
211 | }, | ||
212 | { | ||
213 | "command": "rust-analyzer.moveItemUp", | ||
214 | "title": "Move item up", | ||
215 | "category": "Rust Analyzer" | ||
216 | }, | ||
217 | { | ||
218 | "command": "rust-analyzer.moveItemDown", | ||
219 | "title": "Move item down", | ||
220 | "category": "Rust Analyzer" | ||
211 | } | 221 | } |
212 | ], | 222 | ], |
213 | "keybindings": [ | 223 | "keybindings": [ |
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index bed1f0116..1a0805bd3 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts | |||
@@ -134,6 +134,51 @@ export function joinLines(ctx: Ctx): Cmd { | |||
134 | }; | 134 | }; |
135 | } | 135 | } |
136 | 136 | ||
137 | export function moveItemUp(ctx: Ctx): Cmd { | ||
138 | return moveItem(ctx, ra.Direction.Up); | ||
139 | } | ||
140 | |||
141 | export function moveItemDown(ctx: Ctx): Cmd { | ||
142 | return moveItem(ctx, ra.Direction.Down); | ||
143 | } | ||
144 | |||
145 | export function moveItem(ctx: Ctx, direction: ra.Direction): Cmd { | ||
146 | return async () => { | ||
147 | const editor = ctx.activeRustEditor; | ||
148 | const client = ctx.client; | ||
149 | if (!editor || !client) return; | ||
150 | |||
151 | const edit = await client.sendRequest(ra.moveItem, { | ||
152 | range: client.code2ProtocolConverter.asRange(editor.selection), | ||
153 | textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document), | ||
154 | direction | ||
155 | }); | ||
156 | |||
157 | if (!edit) return; | ||
158 | |||
159 | let cursor: vscode.Position | null = null; | ||
160 | |||
161 | await editor.edit((builder) => { | ||
162 | client.protocol2CodeConverter.asTextEdits(edit.edits).forEach((edit: any) => { | ||
163 | builder.replace(edit.range, edit.newText); | ||
164 | |||
165 | if (direction === ra.Direction.Up) { | ||
166 | if (!cursor || edit.range.end.isBeforeOrEqual(cursor)) { | ||
167 | cursor = edit.range.end; | ||
168 | } | ||
169 | } else { | ||
170 | if (!cursor || edit.range.end.isAfterOrEqual(cursor)) { | ||
171 | cursor = edit.range.end; | ||
172 | } | ||
173 | } | ||
174 | }); | ||
175 | }).then(() => { | ||
176 | const newPosition = cursor ?? editor.selection.start; | ||
177 | editor.selection = new vscode.Selection(newPosition, newPosition); | ||
178 | }); | ||
179 | }; | ||
180 | } | ||
181 | |||
137 | export function onEnter(ctx: Ctx): Cmd { | 182 | export function onEnter(ctx: Ctx): Cmd { |
138 | async function handleKeypress() { | 183 | async function handleKeypress() { |
139 | const editor = ctx.activeRustEditor; | 184 | const editor = ctx.activeRustEditor; |
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 52de29e04..00e128b8c 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts | |||
@@ -127,3 +127,16 @@ export const openCargoToml = new lc.RequestType<OpenCargoTomlParams, lc.Location | |||
127 | export interface OpenCargoTomlParams { | 127 | export interface OpenCargoTomlParams { |
128 | textDocument: lc.TextDocumentIdentifier; | 128 | textDocument: lc.TextDocumentIdentifier; |
129 | } | 129 | } |
130 | |||
131 | export const moveItem = new lc.RequestType<MoveItemParams, lc.TextDocumentEdit | void, void>("experimental/moveItem"); | ||
132 | |||
133 | export interface MoveItemParams { | ||
134 | textDocument: lc.TextDocumentIdentifier; | ||
135 | range: lc.Range; | ||
136 | direction: Direction; | ||
137 | } | ||
138 | |||
139 | export const enum Direction { | ||
140 | Up = "Up", | ||
141 | Down = "Down" | ||
142 | } | ||
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 925103f56..643fb643f 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -114,6 +114,8 @@ async function tryActivate(context: vscode.ExtensionContext) { | |||
114 | ctx.registerCommand('openDocs', commands.openDocs); | 114 | ctx.registerCommand('openDocs', commands.openDocs); |
115 | ctx.registerCommand('openCargoToml', commands.openCargoToml); | 115 | ctx.registerCommand('openCargoToml', commands.openCargoToml); |
116 | ctx.registerCommand('peekTests', commands.peekTests); | 116 | ctx.registerCommand('peekTests', commands.peekTests); |
117 | ctx.registerCommand('moveItemUp', commands.moveItemUp); | ||
118 | ctx.registerCommand('moveItemDown', commands.moveItemDown); | ||
117 | 119 | ||
118 | defaultOnEnter.dispose(); | 120 | defaultOnEnter.dispose(); |
119 | ctx.registerCommand('onEnter', commands.onEnter); | 121 | ctx.registerCommand('onEnter', commands.onEnter); |