aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-12-22 10:04:24 +0000
committerGitHub <[email protected]>2020-12-22 10:04:24 +0000
commitb98ee075ee8baa6dc4284f04df4c7012baccda28 (patch)
tree66904d52167d8bdb45f598499538a17ef1f906e4 /crates
parent61711d975777b176ca2a93ec1ceb2bed661fc6d7 (diff)
parent23ed33a3a6b92064ae711bac305ac163f1fda4f5 (diff)
Merge #6746
6746: Feature/add assist extract module to file r=matklad a=sasurau4 Fix #6522 ## Screenshot <img src="https://user-images.githubusercontent.com/13580199/102748269-33a44300-43a5-11eb-9e37-f5fcb8e62f73.gif" width=600 /> ## TODO - [x] Remove all TODO comment - [x] Pass the doc test Co-authored-by: Daiki Ihara <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/assist_context.rs13
-rw-r--r--crates/assists/src/handlers/extract_module_to_file.rs170
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests.rs28
-rw-r--r--crates/assists/src/tests/generated.rs15
-rw-r--r--crates/ide/src/diagnostics.rs1
-rw-r--r--crates/ide/src/diagnostics/fixes.rs1
-rw-r--r--crates/ide_db/src/source_change.rs2
-rw-r--r--crates/rust-analyzer/src/to_proto.rs37
9 files changed, 251 insertions, 18 deletions
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs
index 69499ea32..80cf9aba1 100644
--- a/crates/assists/src/assist_context.rs
+++ b/crates/assists/src/assist_context.rs
@@ -4,10 +4,10 @@ use std::mem;
4 4
5use algo::find_covering_element; 5use algo::find_covering_element;
6use hir::Semantics; 6use hir::Semantics;
7use ide_db::base_db::{FileId, FileRange}; 7use ide_db::base_db::{AnchoredPathBuf, FileId, FileRange};
8use ide_db::{ 8use ide_db::{
9 label::Label, 9 label::Label,
10 source_change::{SourceChange, SourceFileEdit}, 10 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
11 RootDatabase, 11 RootDatabase,
12}; 12};
13use syntax::{ 13use syntax::{
@@ -209,6 +209,7 @@ pub(crate) struct AssistBuilder {
209 file_id: FileId, 209 file_id: FileId,
210 is_snippet: bool, 210 is_snippet: bool,
211 source_file_edits: Vec<SourceFileEdit>, 211 source_file_edits: Vec<SourceFileEdit>,
212 file_system_edits: Vec<FileSystemEdit>,
212} 213}
213 214
214impl AssistBuilder { 215impl AssistBuilder {
@@ -218,6 +219,7 @@ impl AssistBuilder {
218 file_id, 219 file_id,
219 is_snippet: false, 220 is_snippet: false,
220 source_file_edits: Vec::default(), 221 source_file_edits: Vec::default(),
222 file_system_edits: Vec::default(),
221 } 223 }
222 } 224 }
223 225
@@ -282,12 +284,17 @@ impl AssistBuilder {
282 algo::diff(&node, &new).into_text_edit(&mut self.edit); 284 algo::diff(&node, &new).into_text_edit(&mut self.edit);
283 } 285 }
284 } 286 }
287 pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
288 let file_system_edit =
289 FileSystemEdit::CreateFile { dst: dst.clone(), initial_contents: content.into() };
290 self.file_system_edits.push(file_system_edit);
291 }
285 292
286 fn finish(mut self) -> SourceChange { 293 fn finish(mut self) -> SourceChange {
287 self.commit(); 294 self.commit();
288 SourceChange { 295 SourceChange {
289 source_file_edits: mem::take(&mut self.source_file_edits), 296 source_file_edits: mem::take(&mut self.source_file_edits),
290 file_system_edits: Default::default(), 297 file_system_edits: mem::take(&mut self.file_system_edits),
291 is_snippet: self.is_snippet, 298 is_snippet: self.is_snippet,
292 } 299 }
293 } 300 }
diff --git a/crates/assists/src/handlers/extract_module_to_file.rs b/crates/assists/src/handlers/extract_module_to_file.rs
new file mode 100644
index 000000000..5fc190fa6
--- /dev/null
+++ b/crates/assists/src/handlers/extract_module_to_file.rs
@@ -0,0 +1,170 @@
1use ast::edit::IndentLevel;
2use ide_db::base_db::{AnchoredPathBuf, SourceDatabaseExt};
3use syntax::{
4 ast::{self, edit::AstNodeEdit, NameOwner},
5 AstNode,
6};
7
8use crate::{AssistContext, AssistId, AssistKind, Assists};
9
10// Assist: extract_module_to_file
11//
12// This assist extract module to file.
13//
14// ```
15// mod foo {<|>
16// fn t() {}
17// }
18// ```
19// ->
20// ```
21// mod foo;
22// ```
23pub(crate) fn extract_module_to_file(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let assist_id = AssistId("extract_module_to_file", AssistKind::RefactorExtract);
25 let assist_label = "Extract module to file";
26 let db = ctx.db();
27 let module_ast = ctx.find_node_at_offset::<ast::Module>()?;
28 let module_items = module_ast.item_list()?;
29 let dedent_module_items_text = module_items.dedent(IndentLevel(1)).to_string();
30 let module_name = module_ast.name()?;
31 let target = module_ast.syntax().text_range();
32 let anchor_file_id = ctx.frange.file_id;
33 let sr = db.file_source_root(anchor_file_id);
34 let sr = db.source_root(sr);
35 let file_path = sr.path_for_file(&anchor_file_id)?;
36 let (file_name, file_ext) = file_path.name_and_extension()?;
37 acc.add(assist_id, assist_label, target, |builder| {
38 builder.replace(target, format!("mod {};", module_name));
39 let path = if is_main_or_lib(file_name) {
40 format!("./{}.{}", module_name, file_ext.unwrap())
41 } else {
42 format!("./{}/{}.{}", file_name, module_name, file_ext.unwrap())
43 };
44 let dst = AnchoredPathBuf { anchor: anchor_file_id, path };
45 let contents = update_module_items_string(dedent_module_items_text);
46 builder.create_file(dst, contents);
47 })
48}
49fn is_main_or_lib(file_name: &str) -> bool {
50 file_name == "main".to_string() || file_name == "lib".to_string()
51}
52fn update_module_items_string(items_str: String) -> String {
53 let mut items_string_lines: Vec<&str> = items_str.lines().collect();
54 items_string_lines.pop(); // Delete last line
55 items_string_lines.reverse();
56 items_string_lines.pop(); // Delete first line
57 items_string_lines.reverse();
58
59 let string = items_string_lines.join("\n");
60 format!("{}", string)
61}
62
63#[cfg(test)]
64mod tests {
65 use crate::tests::check_assist;
66
67 use super::*;
68
69 #[test]
70 fn extract_module_to_file_with_basic_module() {
71 check_assist(
72 extract_module_to_file,
73 r#"
74//- /foo.rs crate:foo
75mod tests {<|>
76 #[test] fn t() {}
77}
78"#,
79 r#"
80//- /foo.rs
81mod tests;
82//- /foo/tests.rs
83#[test] fn t() {}"#,
84 )
85 }
86
87 #[test]
88 fn extract_module_to_file_with_file_path() {
89 check_assist(
90 extract_module_to_file,
91 r#"
92//- /src/foo.rs crate:foo
93mod bar {<|>
94 fn f() {
95
96 }
97}
98fn main() {
99 println!("Hello, world!");
100}
101"#,
102 r#"
103//- /src/foo.rs
104mod bar;
105fn main() {
106 println!("Hello, world!");
107}
108//- /src/foo/bar.rs
109fn f() {
110
111}"#,
112 )
113 }
114
115 #[test]
116 fn extract_module_to_file_with_main_filw() {
117 check_assist(
118 extract_module_to_file,
119 r#"
120//- /main.rs
121mod foo {<|>
122 fn f() {
123
124 }
125}
126fn main() {
127 println!("Hello, world!");
128}
129"#,
130 r#"
131//- /main.rs
132mod foo;
133fn main() {
134 println!("Hello, world!");
135}
136//- /foo.rs
137fn f() {
138
139}"#,
140 )
141 }
142
143 #[test]
144 fn extract_module_to_file_with_lib_file() {
145 check_assist(
146 extract_module_to_file,
147 r#"
148//- /lib.rs
149mod foo {<|>
150 fn f() {
151
152 }
153}
154fn main() {
155 println!("Hello, world!");
156}
157"#,
158 r#"
159//- /lib.rs
160mod foo;
161fn main() {
162 println!("Hello, world!");
163}
164//- /foo.rs
165fn f() {
166
167}"#,
168 )
169 }
170}
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index 6e736ccb3..6b89b2d04 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -129,6 +129,7 @@ mod handlers {
129 mod convert_integer_literal; 129 mod convert_integer_literal;
130 mod early_return; 130 mod early_return;
131 mod expand_glob_import; 131 mod expand_glob_import;
132 mod extract_module_to_file;
132 mod extract_struct_from_enum_variant; 133 mod extract_struct_from_enum_variant;
133 mod extract_variable; 134 mod extract_variable;
134 mod fill_match_arms; 135 mod fill_match_arms;
@@ -179,6 +180,7 @@ mod handlers {
179 convert_integer_literal::convert_integer_literal, 180 convert_integer_literal::convert_integer_literal,
180 early_return::convert_to_guarded_return, 181 early_return::convert_to_guarded_return,
181 expand_glob_import::expand_glob_import, 182 expand_glob_import::expand_glob_import,
183 extract_module_to_file::extract_module_to_file,
182 extract_struct_from_enum_variant::extract_struct_from_enum_variant, 184 extract_struct_from_enum_variant::extract_struct_from_enum_variant,
183 extract_variable::extract_variable, 185 extract_variable::extract_variable,
184 fill_match_arms::fill_match_arms, 186 fill_match_arms::fill_match_arms,
diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs
index 709a34d03..b41f4874a 100644
--- a/crates/assists/src/tests.rs
+++ b/crates/assists/src/tests.rs
@@ -2,6 +2,7 @@ mod generated;
2 2
3use hir::Semantics; 3use hir::Semantics;
4use ide_db::base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; 4use ide_db::base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
5use ide_db::source_change::FileSystemEdit;
5use ide_db::RootDatabase; 6use ide_db::RootDatabase;
6use syntax::TextRange; 7use syntax::TextRange;
7use test_utils::{assert_eq_text, extract_offset, extract_range}; 8use test_utils::{assert_eq_text, extract_offset, extract_range};
@@ -47,7 +48,7 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
47 let before = db.file_text(file_id).to_string(); 48 let before = db.file_text(file_id).to_string();
48 let frange = FileRange { file_id, range: selection.into() }; 49 let frange = FileRange { file_id, range: selection.into() };
49 50
50 let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange) 51 let assist = Assist::resolved(&db, &AssistConfig::default(), frange)
51 .into_iter() 52 .into_iter()
52 .find(|assist| assist.assist.id.0 == assist_id) 53 .find(|assist| assist.assist.id.0 == assist_id)
53 .unwrap_or_else(|| { 54 .unwrap_or_else(|| {
@@ -63,9 +64,12 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
63 }); 64 });
64 65
65 let actual = { 66 let actual = {
66 let change = assist.source_change.source_file_edits.pop().unwrap();
67 let mut actual = before; 67 let mut actual = before;
68 change.edit.apply(&mut actual); 68 for source_file_edit in assist.source_change.source_file_edits {
69 if source_file_edit.file_id == file_id {
70 source_file_edit.edit.apply(&mut actual)
71 }
72 }
69 actual 73 actual
70 }; 74 };
71 assert_eq_text!(&after, &actual); 75 assert_eq_text!(&after, &actual);
@@ -99,7 +103,8 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
99 (Some(assist), ExpectedResult::After(after)) => { 103 (Some(assist), ExpectedResult::After(after)) => {
100 let mut source_change = assist.source_change; 104 let mut source_change = assist.source_change;
101 assert!(!source_change.source_file_edits.is_empty()); 105 assert!(!source_change.source_file_edits.is_empty());
102 let skip_header = source_change.source_file_edits.len() == 1; 106 let skip_header = source_change.source_file_edits.len() == 1
107 && source_change.file_system_edits.len() == 0;
103 source_change.source_file_edits.sort_by_key(|it| it.file_id); 108 source_change.source_file_edits.sort_by_key(|it| it.file_id);
104 109
105 let mut buf = String::new(); 110 let mut buf = String::new();
@@ -115,6 +120,21 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
115 buf.push_str(&text); 120 buf.push_str(&text);
116 } 121 }
117 122
123 for file_system_edit in source_change.file_system_edits.clone() {
124 match file_system_edit {
125 FileSystemEdit::CreateFile { dst, initial_contents } => {
126 let sr = db.file_source_root(dst.anchor);
127 let sr = db.source_root(sr);
128 let mut base = sr.path_for_file(&dst.anchor).unwrap().clone();
129 base.pop();
130 let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]);
131 format_to!(buf, "//- {}\n", created_file_path);
132 buf.push_str(&initial_contents);
133 }
134 _ => (),
135 }
136 }
137
118 assert_eq_text!(after, &buf); 138 assert_eq_text!(after, &buf);
119 } 139 }
120 (Some(assist), ExpectedResult::Target(target)) => { 140 (Some(assist), ExpectedResult::Target(target)) => {
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index cc7c4a343..e9093ec53 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -236,6 +236,21 @@ fn qux(bar: Bar, baz: Baz) {}
236} 236}
237 237
238#[test] 238#[test]
239fn doctest_extract_module_to_file() {
240 check_doc_test(
241 "extract_module_to_file",
242 r#####"
243mod foo {<|>
244 fn t() {}
245}
246"#####,
247 r#####"
248mod foo;
249"#####,
250 )
251}
252
253#[test]
239fn doctest_extract_struct_from_enum_variant() { 254fn doctest_extract_struct_from_enum_variant() {
240 check_doc_test( 255 check_doc_test(
241 "extract_struct_from_enum_variant", 256 "extract_struct_from_enum_variant",
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 049f808dc..3ad30f0c9 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -619,6 +619,7 @@ fn test_fn() {
619 ), 619 ),
620 path: "foo.rs", 620 path: "foo.rs",
621 }, 621 },
622 initial_contents: "",
622 }, 623 },
623 ], 624 ],
624 is_snippet: false, 625 is_snippet: false,
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index e8b896623..d79f5c170 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -40,6 +40,7 @@ impl DiagnosticWithFix for UnresolvedModule {
40 anchor: self.file.original_file(sema.db), 40 anchor: self.file.original_file(sema.db),
41 path: self.candidate.clone(), 41 path: self.candidate.clone(),
42 }, 42 },
43 initial_contents: "".to_string(),
43 } 44 }
44 .into(), 45 .into(),
45 unresolved_module.syntax().text_range(), 46 unresolved_module.syntax().text_range(),
diff --git a/crates/ide_db/src/source_change.rs b/crates/ide_db/src/source_change.rs
index e87d98dad..10c0abdac 100644
--- a/crates/ide_db/src/source_change.rs
+++ b/crates/ide_db/src/source_change.rs
@@ -44,7 +44,7 @@ impl From<Vec<SourceFileEdit>> for SourceChange {
44 44
45#[derive(Debug, Clone)] 45#[derive(Debug, Clone)]
46pub enum FileSystemEdit { 46pub enum FileSystemEdit {
47 CreateFile { dst: AnchoredPathBuf }, 47 CreateFile { dst: AnchoredPathBuf, initial_contents: String },
48 MoveFile { src: FileId, dst: AnchoredPathBuf }, 48 MoveFile { src: FileId, dst: AnchoredPathBuf },
49} 49}
50 50
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index e0561b5a7..5a1ae96aa 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -634,30 +634,47 @@ pub(crate) fn snippet_text_document_edit(
634 Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits }) 634 Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits })
635} 635}
636 636
637pub(crate) fn resource_op( 637pub(crate) fn snippet_text_document_ops(
638 snap: &GlobalStateSnapshot, 638 snap: &GlobalStateSnapshot,
639 file_system_edit: FileSystemEdit, 639 file_system_edit: FileSystemEdit,
640) -> lsp_types::ResourceOp { 640) -> Vec<lsp_ext::SnippetDocumentChangeOperation> {
641 let mut ops = Vec::new();
641 match file_system_edit { 642 match file_system_edit {
642 FileSystemEdit::CreateFile { dst } => { 643 FileSystemEdit::CreateFile { dst, initial_contents } => {
643 let uri = snap.anchored_path(&dst); 644 let uri = snap.anchored_path(&dst);
644 lsp_types::ResourceOp::Create(lsp_types::CreateFile { 645 let create_file = lsp_types::ResourceOp::Create(lsp_types::CreateFile {
645 uri, 646 uri: uri.clone(),
646 options: None, 647 options: None,
647 annotation_id: None, 648 annotation_id: None,
648 }) 649 });
650 ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(create_file));
651 if !initial_contents.is_empty() {
652 let text_document =
653 lsp_types::OptionalVersionedTextDocumentIdentifier { uri, version: None };
654 let range = range(&LineIndex::new(""), TextRange::empty(TextSize::from(0)));
655 let text_edit = lsp_ext::SnippetTextEdit {
656 range,
657 new_text: initial_contents,
658 insert_text_format: Some(lsp_types::InsertTextFormat::PlainText),
659 };
660 let edit_file =
661 lsp_ext::SnippetTextDocumentEdit { text_document, edits: vec![text_edit] };
662 ops.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit_file));
663 }
649 } 664 }
650 FileSystemEdit::MoveFile { src, dst } => { 665 FileSystemEdit::MoveFile { src, dst } => {
651 let old_uri = snap.file_id_to_url(src); 666 let old_uri = snap.file_id_to_url(src);
652 let new_uri = snap.anchored_path(&dst); 667 let new_uri = snap.anchored_path(&dst);
653 lsp_types::ResourceOp::Rename(lsp_types::RenameFile { 668 let rename_file = lsp_types::ResourceOp::Rename(lsp_types::RenameFile {
654 old_uri, 669 old_uri,
655 new_uri, 670 new_uri,
656 options: None, 671 options: None,
657 annotation_id: None, 672 annotation_id: None,
658 }) 673 });
674 ops.push(lsp_ext::SnippetDocumentChangeOperation::Op(rename_file))
659 } 675 }
660 } 676 }
677 ops
661} 678}
662 679
663pub(crate) fn snippet_workspace_edit( 680pub(crate) fn snippet_workspace_edit(
@@ -666,8 +683,8 @@ pub(crate) fn snippet_workspace_edit(
666) -> Result<lsp_ext::SnippetWorkspaceEdit> { 683) -> Result<lsp_ext::SnippetWorkspaceEdit> {
667 let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new(); 684 let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new();
668 for op in source_change.file_system_edits { 685 for op in source_change.file_system_edits {
669 let op = resource_op(&snap, op); 686 let ops = snippet_text_document_ops(snap, op);
670 document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op)); 687 document_changes.extend_from_slice(&ops);
671 } 688 }
672 for edit in source_change.source_file_edits { 689 for edit in source_change.source_file_edits {
673 let edit = snippet_text_document_edit(&snap, source_change.is_snippet, edit)?; 690 let edit = snippet_text_document_edit(&snap, source_change.is_snippet, edit)?;