aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-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
6 files changed, 456 insertions, 0 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,