aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide/src/lib.rs10
-rw-r--r--crates/ide/src/move_item.rs392
-rw-r--r--crates/rust-analyzer/src/handlers.rs19
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs22
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--crates/rust-analyzer/src/to_proto.rs12
-rw-r--r--docs/dev/lsp-extensions.md28
-rw-r--r--editors/code/package.json10
-rw-r--r--editors/code/src/commands.ts28
-rw-r--r--editors/code/src/lsp_ext.ts13
-rw-r--r--editors/code/src/main.ts2
11 files changed, 536 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;
37mod inlay_hints; 37mod inlay_hints;
38mod join_lines; 38mod join_lines;
39mod matching_brace; 39mod matching_brace;
40mod move_item;
40mod parent_module; 41mod parent_module;
41mod references; 42mod references;
42mod fn_references; 43mod 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..be62d008d
--- /dev/null
+++ b/crates/ide/src/move_item.rs
@@ -0,0 +1,392 @@
1use std::iter::once;
2
3use hir::Semantics;
4use ide_db::{base_db::FileRange, RootDatabase};
5use syntax::{algo, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode};
6use text_edit::{TextEdit, TextEditBuilder};
7
8pub enum Direction {
9 Up,
10 Down,
11}
12
13// Feature: Move Item
14//
15// Move item under cursor or selection up and down.
16//
17// |===
18// | Editor | Action Name
19//
20// | VS Code | **Rust Analyzer: Move item up**
21// | VS Code | **Rust Analyzer: Move item down**
22// |===
23pub(crate) fn move_item(
24 db: &RootDatabase,
25 range: FileRange,
26 direction: Direction,
27) -> Option<TextEdit> {
28 let sema = Semantics::new(db);
29 let file = sema.parse(range.file_id);
30
31 let item = file.syntax().covering_element(range.range);
32 find_ancestors(item, direction)
33}
34
35fn find_ancestors(item: SyntaxElement, direction: Direction) -> Option<TextEdit> {
36 let movable = [
37 SyntaxKind::MATCH_ARM,
38 // https://github.com/intellij-rust/intellij-rust/blob/master/src/main/kotlin/org/rust/ide/actions/mover/RsStatementUpDownMover.kt
39 SyntaxKind::LET_STMT,
40 SyntaxKind::EXPR_STMT,
41 SyntaxKind::MATCH_EXPR,
42 // https://github.com/intellij-rust/intellij-rust/blob/master/src/main/kotlin/org/rust/ide/actions/mover/RsItemUpDownMover.kt
43 SyntaxKind::TRAIT,
44 SyntaxKind::IMPL,
45 SyntaxKind::MACRO_CALL,
46 SyntaxKind::MACRO_DEF,
47 SyntaxKind::STRUCT,
48 SyntaxKind::ENUM,
49 SyntaxKind::MODULE,
50 SyntaxKind::USE,
51 SyntaxKind::FN,
52 SyntaxKind::CONST,
53 SyntaxKind::TYPE_ALIAS,
54 ];
55
56 let root = match item {
57 NodeOrToken::Node(node) => node,
58 NodeOrToken::Token(token) => token.parent(),
59 };
60
61 let ancestor = once(root.clone())
62 .chain(root.ancestors())
63 .filter(|ancestor| movable.contains(&ancestor.kind()))
64 .max_by_key(|ancestor| kind_priority(ancestor.kind()))?;
65
66 move_in_direction(&ancestor, direction)
67}
68
69fn kind_priority(kind: SyntaxKind) -> i32 {
70 match kind {
71 SyntaxKind::MATCH_ARM => 4,
72
73 SyntaxKind::LET_STMT | SyntaxKind::EXPR_STMT | SyntaxKind::MATCH_EXPR => 3,
74
75 SyntaxKind::TRAIT
76 | SyntaxKind::IMPL
77 | SyntaxKind::MACRO_CALL
78 | SyntaxKind::MACRO_DEF
79 | SyntaxKind::STRUCT
80 | SyntaxKind::ENUM
81 | SyntaxKind::MODULE
82 | SyntaxKind::USE
83 | SyntaxKind::FN
84 | SyntaxKind::CONST
85 | SyntaxKind::TYPE_ALIAS => 2,
86
87 // Placeholder for items, that are non-movable, and filtered even before kind_priority call
88 _ => 1,
89 }
90}
91
92fn move_in_direction(node: &SyntaxNode, direction: Direction) -> Option<TextEdit> {
93 let sibling = match direction {
94 Direction::Up => node.prev_sibling(),
95 Direction::Down => node.next_sibling(),
96 }?;
97
98 Some(replace_nodes(&sibling, node))
99}
100
101fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit {
102 let mut edit = TextEditBuilder::default();
103
104 algo::diff(first, second).into_text_edit(&mut edit);
105 algo::diff(second, first).into_text_edit(&mut edit);
106
107 edit.finish()
108}
109
110#[cfg(test)]
111mod tests {
112 use crate::fixture;
113 use expect_test::{expect, Expect};
114
115 use crate::Direction;
116
117 fn check(ra_fixture: &str, expect: Expect, direction: Direction) {
118 let (analysis, range) = fixture::range(ra_fixture);
119 let edit = analysis.move_item(range, direction).unwrap().unwrap_or_default();
120 let mut file = analysis.file_text(range.file_id).unwrap().to_string();
121 edit.apply(&mut file);
122 expect.assert_eq(&file);
123 }
124
125 #[test]
126 fn test_moves_match_arm_up() {
127 check(
128 r#"
129fn main() {
130 match true {
131 true => {
132 println!("Hello, world");
133 },
134 false =>$0$0 {
135 println!("Test");
136 }
137 };
138}
139 "#,
140 expect![[r#"
141fn main() {
142 match true {
143 false => {
144 println!("Test");
145 },
146 true => {
147 println!("Hello, world");
148 }
149 };
150}
151 "#]],
152 Direction::Up,
153 );
154 }
155
156 #[test]
157 fn test_moves_match_arm_down() {
158 check(
159 r#"
160fn main() {
161 match true {
162 true =>$0$0 {
163 println!("Hello, world");
164 },
165 false => {
166 println!("Test");
167 }
168 };
169}
170 "#,
171 expect![[r#"
172fn main() {
173 match true {
174 false => {
175 println!("Test");
176 },
177 true => {
178 println!("Hello, world");
179 }
180 };
181}
182 "#]],
183 Direction::Down,
184 );
185 }
186
187 #[test]
188 fn test_nowhere_to_move() {
189 check(
190 r#"
191fn main() {
192 match true {
193 true =>$0$0 {
194 println!("Hello, world");
195 },
196 false => {
197 println!("Test");
198 }
199 };
200}
201 "#,
202 expect![[r#"
203fn main() {
204 match true {
205 true => {
206 println!("Hello, world");
207 },
208 false => {
209 println!("Test");
210 }
211 };
212}
213 "#]],
214 Direction::Up,
215 );
216 }
217
218 #[test]
219 fn test_moves_let_stmt_up() {
220 check(
221 r#"
222fn main() {
223 let test = 123;
224 let test2$0$0 = 456;
225}
226 "#,
227 expect![[r#"
228fn main() {
229 let test2 = 456;
230 let test = 123;
231}
232 "#]],
233 Direction::Up,
234 );
235 }
236
237 #[test]
238 fn test_prioritizes_match_arm() {
239 check(
240 r#"
241fn main() {
242 match true {
243 true => {
244 let test = 123;$0$0
245 let test2 = 456;
246 },
247 false => {
248 println!("Test");
249 }
250 };
251}
252 "#,
253 expect![[r#"
254fn main() {
255 match true {
256 false => {
257 println!("Test");
258 },
259 true => {
260 let test = 123;
261 let test2 = 456;
262 }
263 };
264}
265 "#]],
266 Direction::Down,
267 );
268 }
269
270 #[test]
271 fn test_moves_expr_up() {
272 check(
273 r#"
274fn main() {
275 println!("Hello, world");
276 println!("All I want to say is...");$0$0
277}
278 "#,
279 expect![[r#"
280fn main() {
281 println!("All I want to say is...");
282 println!("Hello, world");
283}
284 "#]],
285 Direction::Up,
286 );
287 }
288
289 #[test]
290 fn test_nowhere_to_move_stmt() {
291 check(
292 r#"
293fn main() {
294 println!("All I want to say is...");$0$0
295 println!("Hello, world");
296}
297 "#,
298 expect![[r#"
299fn main() {
300 println!("All I want to say is...");
301 println!("Hello, world");
302}
303 "#]],
304 Direction::Up,
305 );
306 }
307
308 #[test]
309 fn test_move_item() {
310 check(
311 r#"
312fn main() {}
313
314fn foo() {}$0$0
315 "#,
316 expect![[r#"
317fn foo() {}
318
319fn main() {}
320 "#]],
321 Direction::Up,
322 );
323 }
324
325 #[test]
326 fn test_move_impl_up() {
327 check(
328 r#"
329struct Yay;
330
331trait Wow {}
332
333impl Wow for Yay {}$0$0
334 "#,
335 expect![[r#"
336struct Yay;
337
338impl Wow for Yay {}
339
340trait Wow {}
341 "#]],
342 Direction::Up,
343 );
344 }
345
346 #[test]
347 fn test_move_use_up() {
348 check(
349 r#"
350use std::vec::Vec;
351use std::collections::HashMap$0$0;
352 "#,
353 expect![[r#"
354use std::collections::HashMap;
355use std::vec::Vec;
356 "#]],
357 Direction::Up,
358 );
359 }
360
361 #[test]
362 fn moves_match_expr_up() {
363 check(
364 r#"
365fn main() {
366 let test = 123;
367
368 $0match test {
369 456 => {},
370 _ => {}
371 }$0;
372}
373 "#,
374 expect![[r#"
375fn main() {
376 match test {
377 456 => {},
378 _ => {}
379 };
380
381 let test = 123;
382}
383 "#]],
384 Direction::Up,
385 );
386 }
387
388 #[test]
389 fn handles_empty_file() {
390 check(r#"$0$0"#, expect![[r#""#]], Direction::Up);
391 }
392}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index b6f484e51..8daf27867 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -1424,6 +1424,25 @@ pub(crate) fn handle_open_cargo_toml(
1424 Ok(Some(res)) 1424 Ok(Some(res))
1425} 1425}
1426 1426
1427pub(crate) fn handle_move_item(
1428 snap: GlobalStateSnapshot,
1429 params: lsp_ext::MoveItemParams,
1430) -> Result<Option<lsp_types::TextDocumentEdit>> {
1431 let _p = profile::span("handle_move_item");
1432 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1433 let range = from_proto::file_range(&snap, params.text_document, params.range)?;
1434
1435 let direction = match params.direction {
1436 lsp_ext::MoveItemDirection::Up => ide::Direction::Up,
1437 lsp_ext::MoveItemDirection::Down => ide::Direction::Down,
1438 };
1439
1440 match snap.analysis.move_item(range, direction)? {
1441 Some(text_edit) => Ok(Some(to_proto::text_document_edit(&snap, file_id, text_edit)?)),
1442 None => Ok(None),
1443 }
1444}
1445
1427fn to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::CommandLink { 1446fn to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::CommandLink {
1428 lsp_ext::CommandLink { tooltip: Some(tooltip), command } 1447 lsp_ext::CommandLink { tooltip: Some(tooltip), command }
1429} 1448}
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 {
402pub fn supports_utf8(caps: &lsp_types::ClientCapabilities) -> bool { 402pub 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
406pub enum MoveItem {}
407
408impl 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")]
416pub struct MoveItemParams {
417 pub direction: MoveItemDirection,
418 pub text_document: TextDocumentIdentifier,
419 pub range: Range,
420}
421
422#[derive(Serialize, Deserialize, Debug)]
423pub 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 984790d35..022a20851 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -507,6 +507,7 @@ impl GlobalState {
507 .on::<lsp_ext::HoverRequest>(handlers::handle_hover) 507 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)
508 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs) 508 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs)
509 .on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml) 509 .on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml)
510 .on::<lsp_ext::MoveItem>(handlers::handle_move_item)
510 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting) 511 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)
511 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol) 512 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)
512 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol) 513 .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 70501618e..3171708d5 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
661pub(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
661pub(crate) fn snippet_text_document_edit( 673pub(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<!---
2lsp_ext.rs hash: 4dfa8d7035f4aee7 2lsp_ext.rs hash: e8a7502bd2b2c2f5
3 3
4If you need to change the above hash to make the test pass, please check if you 4If you need to change the above hash to make the test pass, please check if you
5need to adjust this doc as well and ping this issue: 5need 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
603This 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
613export interface MoveItemParams {
614 textDocument: lc.TextDocumentIdentifier,
615 range: lc.Range,
616 direction: Direction
617}
618
619export 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..cc90fe889 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -134,6 +134,34 @@ export function joinLines(ctx: Ctx): Cmd {
134 }; 134 };
135} 135}
136 136
137export function moveItemUp(ctx: Ctx): Cmd {
138 return moveItem(ctx, ra.Direction.Up);
139}
140
141export function moveItemDown(ctx: Ctx): Cmd {
142 return moveItem(ctx, ra.Direction.Down);
143}
144
145export 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: lc.TextDocumentEdit = 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 await editor.edit((builder) => {
158 client.protocol2CodeConverter.asTextEdits(edit.edits).forEach((edit: any) => {
159 builder.replace(edit.range, edit.newText);
160 });
161 });
162 };
163}
164
137export function onEnter(ctx: Ctx): Cmd { 165export function onEnter(ctx: Ctx): Cmd {
138 async function handleKeypress() { 166 async function handleKeypress() {
139 const editor = ctx.activeRustEditor; 167 const editor = ctx.activeRustEditor;
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index 52de29e04..9af30cfdb 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
127export interface OpenCargoTomlParams { 127export interface OpenCargoTomlParams {
128 textDocument: lc.TextDocumentIdentifier; 128 textDocument: lc.TextDocumentIdentifier;
129} 129}
130
131export const moveItem = new lc.RequestType<MoveItemParams, lc.TextDocumentEdit, void>("experimental/moveItem");
132
133export interface MoveItemParams {
134 textDocument: lc.TextDocumentIdentifier,
135 range: lc.Range,
136 direction: Direction
137}
138
139export 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);