diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-12-22 10:04:24 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-12-22 10:04:24 +0000 |
commit | b98ee075ee8baa6dc4284f04df4c7012baccda28 (patch) | |
tree | 66904d52167d8bdb45f598499538a17ef1f906e4 /crates/assists/src | |
parent | 61711d975777b176ca2a93ec1ceb2bed661fc6d7 (diff) | |
parent | 23ed33a3a6b92064ae711bac305ac163f1fda4f5 (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/assists/src')
-rw-r--r-- | crates/assists/src/assist_context.rs | 13 | ||||
-rw-r--r-- | crates/assists/src/handlers/extract_module_to_file.rs | 170 | ||||
-rw-r--r-- | crates/assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/assists/src/tests.rs | 28 | ||||
-rw-r--r-- | crates/assists/src/tests/generated.rs | 15 |
5 files changed, 221 insertions, 7 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 | ||
5 | use algo::find_covering_element; | 5 | use algo::find_covering_element; |
6 | use hir::Semantics; | 6 | use hir::Semantics; |
7 | use ide_db::base_db::{FileId, FileRange}; | 7 | use ide_db::base_db::{AnchoredPathBuf, FileId, FileRange}; |
8 | use ide_db::{ | 8 | use 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 | }; |
13 | use syntax::{ | 13 | use 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 | ||
214 | impl AssistBuilder { | 215 | impl 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 @@ | |||
1 | use ast::edit::IndentLevel; | ||
2 | use ide_db::base_db::{AnchoredPathBuf, SourceDatabaseExt}; | ||
3 | use syntax::{ | ||
4 | ast::{self, edit::AstNodeEdit, NameOwner}, | ||
5 | AstNode, | ||
6 | }; | ||
7 | |||
8 | use 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 | // ``` | ||
23 | pub(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 | } | ||
49 | fn is_main_or_lib(file_name: &str) -> bool { | ||
50 | file_name == "main".to_string() || file_name == "lib".to_string() | ||
51 | } | ||
52 | fn 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)] | ||
64 | mod 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 | ||
75 | mod tests {<|> | ||
76 | #[test] fn t() {} | ||
77 | } | ||
78 | "#, | ||
79 | r#" | ||
80 | //- /foo.rs | ||
81 | mod 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 | ||
93 | mod bar {<|> | ||
94 | fn f() { | ||
95 | |||
96 | } | ||
97 | } | ||
98 | fn main() { | ||
99 | println!("Hello, world!"); | ||
100 | } | ||
101 | "#, | ||
102 | r#" | ||
103 | //- /src/foo.rs | ||
104 | mod bar; | ||
105 | fn main() { | ||
106 | println!("Hello, world!"); | ||
107 | } | ||
108 | //- /src/foo/bar.rs | ||
109 | fn 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 | ||
121 | mod foo {<|> | ||
122 | fn f() { | ||
123 | |||
124 | } | ||
125 | } | ||
126 | fn main() { | ||
127 | println!("Hello, world!"); | ||
128 | } | ||
129 | "#, | ||
130 | r#" | ||
131 | //- /main.rs | ||
132 | mod foo; | ||
133 | fn main() { | ||
134 | println!("Hello, world!"); | ||
135 | } | ||
136 | //- /foo.rs | ||
137 | fn 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 | ||
149 | mod foo {<|> | ||
150 | fn f() { | ||
151 | |||
152 | } | ||
153 | } | ||
154 | fn main() { | ||
155 | println!("Hello, world!"); | ||
156 | } | ||
157 | "#, | ||
158 | r#" | ||
159 | //- /lib.rs | ||
160 | mod foo; | ||
161 | fn main() { | ||
162 | println!("Hello, world!"); | ||
163 | } | ||
164 | //- /foo.rs | ||
165 | fn 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 | ||
3 | use hir::Semantics; | 3 | use hir::Semantics; |
4 | use ide_db::base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; | 4 | use ide_db::base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; |
5 | use ide_db::source_change::FileSystemEdit; | ||
5 | use ide_db::RootDatabase; | 6 | use ide_db::RootDatabase; |
6 | use syntax::TextRange; | 7 | use syntax::TextRange; |
7 | use test_utils::{assert_eq_text, extract_offset, extract_range}; | 8 | use 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] |
239 | fn doctest_extract_module_to_file() { | ||
240 | check_doc_test( | ||
241 | "extract_module_to_file", | ||
242 | r#####" | ||
243 | mod foo {<|> | ||
244 | fn t() {} | ||
245 | } | ||
246 | "#####, | ||
247 | r#####" | ||
248 | mod foo; | ||
249 | "#####, | ||
250 | ) | ||
251 | } | ||
252 | |||
253 | #[test] | ||
239 | fn doctest_extract_struct_from_enum_variant() { | 254 | fn 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", |