Skip to content

Commit d401f2a

Browse files
Merge #10211
10211: assists: Promote module to folder r=jonas-schievink a=longfangsong Close part of #10143. This PR adds a assist to promote module to directory, which means make a .rs file module into a directory style module with the same name. ![未命名(1)](https://user-images.githubusercontent.com/13777628/132958377-14555d6f-a64a-4b9b-9154-90a3b86fd685.gif) Co-authored-by: longfangsong <[email protected]>
2 parents 2a970ba + 22abbe8 commit d401f2a

File tree

5 files changed

+215
-10
lines changed

5 files changed

+215
-10
lines changed

crates/ide_assists/src/assist_context.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,10 @@ impl AssistBuilder {
294294
let file_system_edit = FileSystemEdit::CreateFile { dst, initial_contents: content.into() };
295295
self.source_change.push_file_system_edit(file_system_edit);
296296
}
297+
pub(crate) fn move_file(&mut self, src: FileId, dst: AnchoredPathBuf) {
298+
let file_system_edit = FileSystemEdit::MoveFile { src, dst };
299+
self.source_change.push_file_system_edit(file_system_edit);
300+
}
297301

298302
fn finish(mut self) -> SourceChange {
299303
self.commit();
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
use ide_db::{
2+
assists::{AssistId, AssistKind},
3+
base_db::AnchoredPathBuf,
4+
};
5+
use syntax::{
6+
ast::{self, Whitespace},
7+
AstNode, AstToken, SourceFile, TextRange, TextSize,
8+
};
9+
10+
use crate::assist_context::{AssistContext, Assists};
11+
12+
/// Trim(remove leading and trailing whitespace) `initial_range` in `source_file`, return the trimmed range.
13+
fn trimmed_text_range(source_file: &SourceFile, initial_range: TextRange) -> TextRange {
14+
let mut trimmed_range = initial_range;
15+
while source_file
16+
.syntax()
17+
.token_at_offset(trimmed_range.start())
18+
.find_map(Whitespace::cast)
19+
.is_some()
20+
&& trimmed_range.start() < trimmed_range.end()
21+
{
22+
let start = trimmed_range.start() + TextSize::from(1);
23+
trimmed_range = TextRange::new(start, trimmed_range.end());
24+
}
25+
while source_file
26+
.syntax()
27+
.token_at_offset(trimmed_range.end())
28+
.find_map(Whitespace::cast)
29+
.is_some()
30+
&& trimmed_range.start() < trimmed_range.end()
31+
{
32+
let end = trimmed_range.end() - TextSize::from(1);
33+
trimmed_range = TextRange::new(trimmed_range.start(), end);
34+
}
35+
trimmed_range
36+
}
37+
38+
// Assist: move_to_mod_rs
39+
//
40+
// Moves xxx.rs to xxx/mod.rs.
41+
//
42+
// ```
43+
// //- /main.rs
44+
// mod a;
45+
// //- /a.rs
46+
// $0fn t() {}$0
47+
// ```
48+
// ->
49+
// ```
50+
// fn t() {}
51+
// ```
52+
pub(crate) fn move_to_mod_rs(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
53+
let source_file = ctx.find_node_at_offset::<ast::SourceFile>()?;
54+
let module = ctx.sema.to_module_def(ctx.frange.file_id)?;
55+
// Enable this assist if the user select all "meaningful" content in the source file
56+
let trimmed_selected_range = trimmed_text_range(&source_file, ctx.frange.range);
57+
let trimmed_file_range = trimmed_text_range(&source_file, source_file.syntax().text_range());
58+
if module.is_mod_rs(ctx.db()) {
59+
cov_mark::hit!(already_mod_rs);
60+
return None;
61+
}
62+
if trimmed_selected_range != trimmed_file_range {
63+
cov_mark::hit!(not_all_selected);
64+
return None;
65+
}
66+
67+
let target = TextRange::new(
68+
source_file.syntax().text_range().start(),
69+
source_file.syntax().text_range().end(),
70+
);
71+
let module_name = module.name(ctx.db())?.to_string();
72+
let path = format!("./{}/mod.rs", module_name);
73+
let dst = AnchoredPathBuf { anchor: ctx.frange.file_id, path };
74+
acc.add(
75+
AssistId("move_to_mod_rs", AssistKind::Refactor),
76+
format!("Turn {}.rs to {}/mod.rs", module_name, module_name),
77+
target,
78+
|builder| {
79+
builder.move_file(ctx.frange.file_id, dst);
80+
},
81+
)
82+
}
83+
84+
#[cfg(test)]
85+
mod tests {
86+
use crate::tests::{check_assist, check_assist_not_applicable};
87+
88+
use super::*;
89+
90+
#[test]
91+
fn trivial() {
92+
check_assist(
93+
move_to_mod_rs,
94+
r#"
95+
//- /main.rs
96+
mod a;
97+
//- /a.rs
98+
$0fn t() {}
99+
$0"#,
100+
r#"
101+
//- /a/mod.rs
102+
fn t() {}
103+
"#,
104+
);
105+
}
106+
107+
#[test]
108+
fn must_select_all_file() {
109+
cov_mark::check!(not_all_selected);
110+
check_assist_not_applicable(
111+
move_to_mod_rs,
112+
r#"
113+
//- /main.rs
114+
mod a;
115+
//- /a.rs
116+
fn t() {}$0
117+
"#,
118+
);
119+
cov_mark::check!(not_all_selected);
120+
check_assist_not_applicable(
121+
move_to_mod_rs,
122+
r#"
123+
//- /main.rs
124+
mod a;
125+
//- /a.rs
126+
$0fn$0 t() {}
127+
"#,
128+
);
129+
}
130+
131+
#[test]
132+
fn cannot_promote_mod_rs() {
133+
cov_mark::check!(already_mod_rs);
134+
check_assist_not_applicable(
135+
move_to_mod_rs,
136+
r#"//- /main.rs
137+
mod a;
138+
//- /a/mod.rs
139+
$0fn t() {}$0
140+
"#,
141+
);
142+
}
143+
144+
#[test]
145+
fn cannot_promote_main_and_lib_rs() {
146+
check_assist_not_applicable(
147+
move_to_mod_rs,
148+
r#"//- /main.rs
149+
$0fn t() {}$0
150+
"#,
151+
);
152+
check_assist_not_applicable(
153+
move_to_mod_rs,
154+
r#"//- /lib.rs
155+
$0fn t() {}$0
156+
"#,
157+
);
158+
}
159+
160+
#[test]
161+
fn works_in_mod() {
162+
// note: /a/b.rs remains untouched
163+
check_assist(
164+
move_to_mod_rs,
165+
r#"//- /main.rs
166+
mod a;
167+
//- /a.rs
168+
$0mod b;
169+
fn t() {}$0
170+
//- /a/b.rs
171+
fn t1() {}
172+
"#,
173+
r#"
174+
//- /a/mod.rs
175+
mod b;
176+
fn t() {}
177+
"#,
178+
);
179+
}
180+
}

crates/ide_assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ mod handlers {
153153
mod move_bounds;
154154
mod move_guard;
155155
mod move_module_to_file;
156+
mod move_to_mod_rs;
156157
mod pull_assignment_up;
157158
mod qualify_path;
158159
mod raw_string;
@@ -226,6 +227,7 @@ mod handlers {
226227
move_guard::move_arm_cond_to_match_guard,
227228
move_guard::move_guard_to_arm_body,
228229
move_module_to_file::move_module_to_file,
230+
move_to_mod_rs::move_to_mod_rs,
229231
pull_assignment_up::pull_assignment_up,
230232
qualify_path::qualify_path,
231233
raw_string::add_hash,

crates/ide_assists/src/tests.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
142142
(Some(assist), ExpectedResult::After(after)) => {
143143
let source_change =
144144
assist.source_change.expect("Assist did not contain any source changes");
145-
assert!(!source_change.source_file_edits.is_empty());
146145
let skip_header = source_change.source_file_edits.len() == 1
147146
&& source_change.file_system_edits.len() == 0;
148147

@@ -160,15 +159,19 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
160159
}
161160

162161
for file_system_edit in source_change.file_system_edits {
163-
if let FileSystemEdit::CreateFile { dst, initial_contents } = file_system_edit {
164-
let sr = db.file_source_root(dst.anchor);
165-
let sr = db.source_root(sr);
166-
let mut base = sr.path_for_file(&dst.anchor).unwrap().clone();
167-
base.pop();
168-
let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]);
169-
format_to!(buf, "//- {}\n", created_file_path);
170-
buf.push_str(&initial_contents);
171-
}
162+
let (dst, contents) = match file_system_edit {
163+
FileSystemEdit::CreateFile { dst, initial_contents } => (dst, initial_contents),
164+
FileSystemEdit::MoveFile { src, dst } => {
165+
(dst, db.file_text(src).as_ref().to_owned())
166+
}
167+
};
168+
let sr = db.file_source_root(dst.anchor);
169+
let sr = db.source_root(sr);
170+
let mut base = sr.path_for_file(&dst.anchor).unwrap().clone();
171+
base.pop();
172+
let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]);
173+
format_to!(buf, "//- {}\n", created_file_path);
174+
buf.push_str(&contents);
172175
}
173176

174177
assert_eq_text!(after, &buf);

crates/ide_assists/src/tests/generated.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,22 @@ mod foo;
13031303
)
13041304
}
13051305

1306+
#[test]
1307+
fn doctest_move_to_mod_rs() {
1308+
check_doc_test(
1309+
"move_to_mod_rs",
1310+
r#####"
1311+
//- /main.rs
1312+
mod a;
1313+
//- /a.rs
1314+
$0fn t() {}$0
1315+
"#####,
1316+
r#####"
1317+
fn t() {}
1318+
"#####,
1319+
)
1320+
}
1321+
13061322
#[test]
13071323
fn doctest_pull_assignment_up() {
13081324
check_doc_test(

0 commit comments

Comments
 (0)