about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-09-25 15:32:00 +0000
committerGitHub <noreply@github.com>2021-09-25 15:32:00 +0000
commitd401f2a062ffc4b458f158f535478d6cc8d09f44 (patch)
tree5cc17ed5dd9d11729ab73b2ce4bc0df514dde3ef
parent2a970bac37c930f72a483653271ecac323fd0d38 (diff)
parent22abbe86f343a2c3ce639a15ba88502cf6eebfe6 (diff)
downloadrust-d401f2a062ffc4b458f158f535478d6cc8d09f44.tar.gz
rust-d401f2a062ffc4b458f158f535478d6cc8d09f44.zip
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 <longfangsong@icloud.com>
-rw-r--r--crates/ide_assists/src/assist_context.rs4
-rw-r--r--crates/ide_assists/src/handlers/move_to_mod_rs.rs180
-rw-r--r--crates/ide_assists/src/lib.rs2
-rw-r--r--crates/ide_assists/src/tests.rs23
-rw-r--r--crates/ide_assists/src/tests/generated.rs16
5 files changed, 215 insertions, 10 deletions
diff --git a/crates/ide_assists/src/assist_context.rs b/crates/ide_assists/src/assist_context.rs
index 2b1f3d1633e..671a945a449 100644
--- a/crates/ide_assists/src/assist_context.rs
+++ b/crates/ide_assists/src/assist_context.rs
@@ -294,6 +294,10 @@ impl AssistBuilder {
         let file_system_edit = FileSystemEdit::CreateFile { dst, initial_contents: content.into() };
         self.source_change.push_file_system_edit(file_system_edit);
     }
+    pub(crate) fn move_file(&mut self, src: FileId, dst: AnchoredPathBuf) {
+        let file_system_edit = FileSystemEdit::MoveFile { src, dst };
+        self.source_change.push_file_system_edit(file_system_edit);
+    }
 
     fn finish(mut self) -> SourceChange {
         self.commit();
diff --git a/crates/ide_assists/src/handlers/move_to_mod_rs.rs b/crates/ide_assists/src/handlers/move_to_mod_rs.rs
new file mode 100644
index 00000000000..9b060bb710f
--- /dev/null
+++ b/crates/ide_assists/src/handlers/move_to_mod_rs.rs
@@ -0,0 +1,180 @@
+use ide_db::{
+    assists::{AssistId, AssistKind},
+    base_db::AnchoredPathBuf,
+};
+use syntax::{
+    ast::{self, Whitespace},
+    AstNode, AstToken, SourceFile, TextRange, TextSize,
+};
+
+use crate::assist_context::{AssistContext, Assists};
+
+/// Trim(remove leading and trailing whitespace) `initial_range` in `source_file`, return the trimmed range.
+fn trimmed_text_range(source_file: &SourceFile, initial_range: TextRange) -> TextRange {
+    let mut trimmed_range = initial_range;
+    while source_file
+        .syntax()
+        .token_at_offset(trimmed_range.start())
+        .find_map(Whitespace::cast)
+        .is_some()
+        && trimmed_range.start() < trimmed_range.end()
+    {
+        let start = trimmed_range.start() + TextSize::from(1);
+        trimmed_range = TextRange::new(start, trimmed_range.end());
+    }
+    while source_file
+        .syntax()
+        .token_at_offset(trimmed_range.end())
+        .find_map(Whitespace::cast)
+        .is_some()
+        && trimmed_range.start() < trimmed_range.end()
+    {
+        let end = trimmed_range.end() - TextSize::from(1);
+        trimmed_range = TextRange::new(trimmed_range.start(), end);
+    }
+    trimmed_range
+}
+
+// Assist: move_to_mod_rs
+//
+// Moves xxx.rs to xxx/mod.rs.
+//
+// ```
+// //- /main.rs
+// mod a;
+// //- /a.rs
+// $0fn t() {}$0
+// ```
+// ->
+// ```
+// fn t() {}
+// ```
+pub(crate) fn move_to_mod_rs(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+    let source_file = ctx.find_node_at_offset::<ast::SourceFile>()?;
+    let module = ctx.sema.to_module_def(ctx.frange.file_id)?;
+    // Enable this assist if the user select all "meaningful" content in the source file
+    let trimmed_selected_range = trimmed_text_range(&source_file, ctx.frange.range);
+    let trimmed_file_range = trimmed_text_range(&source_file, source_file.syntax().text_range());
+    if module.is_mod_rs(ctx.db()) {
+        cov_mark::hit!(already_mod_rs);
+        return None;
+    }
+    if trimmed_selected_range != trimmed_file_range {
+        cov_mark::hit!(not_all_selected);
+        return None;
+    }
+
+    let target = TextRange::new(
+        source_file.syntax().text_range().start(),
+        source_file.syntax().text_range().end(),
+    );
+    let module_name = module.name(ctx.db())?.to_string();
+    let path = format!("./{}/mod.rs", module_name);
+    let dst = AnchoredPathBuf { anchor: ctx.frange.file_id, path };
+    acc.add(
+        AssistId("move_to_mod_rs", AssistKind::Refactor),
+        format!("Turn {}.rs to {}/mod.rs", module_name, module_name),
+        target,
+        |builder| {
+            builder.move_file(ctx.frange.file_id, dst);
+        },
+    )
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::{check_assist, check_assist_not_applicable};
+
+    use super::*;
+
+    #[test]
+    fn trivial() {
+        check_assist(
+            move_to_mod_rs,
+            r#"
+//- /main.rs
+mod a;
+//- /a.rs
+$0fn t() {}
+$0"#,
+            r#"
+//- /a/mod.rs
+fn t() {}
+"#,
+        );
+    }
+
+    #[test]
+    fn must_select_all_file() {
+        cov_mark::check!(not_all_selected);
+        check_assist_not_applicable(
+            move_to_mod_rs,
+            r#"
+//- /main.rs
+mod a;
+//- /a.rs
+fn t() {}$0
+"#,
+        );
+        cov_mark::check!(not_all_selected);
+        check_assist_not_applicable(
+            move_to_mod_rs,
+            r#"
+//- /main.rs
+mod a;
+//- /a.rs
+$0fn$0 t() {}
+"#,
+        );
+    }
+
+    #[test]
+    fn cannot_promote_mod_rs() {
+        cov_mark::check!(already_mod_rs);
+        check_assist_not_applicable(
+            move_to_mod_rs,
+            r#"//- /main.rs
+mod a;
+//- /a/mod.rs
+$0fn t() {}$0
+"#,
+        );
+    }
+
+    #[test]
+    fn cannot_promote_main_and_lib_rs() {
+        check_assist_not_applicable(
+            move_to_mod_rs,
+            r#"//- /main.rs
+$0fn t() {}$0
+"#,
+        );
+        check_assist_not_applicable(
+            move_to_mod_rs,
+            r#"//- /lib.rs
+$0fn t() {}$0
+"#,
+        );
+    }
+
+    #[test]
+    fn works_in_mod() {
+        // note: /a/b.rs remains untouched
+        check_assist(
+            move_to_mod_rs,
+            r#"//- /main.rs
+mod a;
+//- /a.rs
+$0mod b;
+fn t() {}$0
+//- /a/b.rs
+fn t1() {}
+"#,
+            r#"
+//- /a/mod.rs
+mod b;
+fn t() {}
+"#,
+        );
+    }
+}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index b2c8cd76941..53348c26d6f 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -153,6 +153,7 @@ mod handlers {
     mod move_bounds;
     mod move_guard;
     mod move_module_to_file;
+    mod move_to_mod_rs;
     mod pull_assignment_up;
     mod qualify_path;
     mod raw_string;
@@ -226,6 +227,7 @@ mod handlers {
             move_guard::move_arm_cond_to_match_guard,
             move_guard::move_guard_to_arm_body,
             move_module_to_file::move_module_to_file,
+            move_to_mod_rs::move_to_mod_rs,
             pull_assignment_up::pull_assignment_up,
             qualify_path::qualify_path,
             raw_string::add_hash,
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs
index 5cd3642ce0d..e211b09987b 100644
--- a/crates/ide_assists/src/tests.rs
+++ b/crates/ide_assists/src/tests.rs
@@ -142,7 +142,6 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
         (Some(assist), ExpectedResult::After(after)) => {
             let source_change =
                 assist.source_change.expect("Assist did not contain any source changes");
-            assert!(!source_change.source_file_edits.is_empty());
             let skip_header = source_change.source_file_edits.len() == 1
                 && source_change.file_system_edits.len() == 0;
 
@@ -160,15 +159,19 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
             }
 
             for file_system_edit in source_change.file_system_edits {
-                if let FileSystemEdit::CreateFile { dst, initial_contents } = file_system_edit {
-                    let sr = db.file_source_root(dst.anchor);
-                    let sr = db.source_root(sr);
-                    let mut base = sr.path_for_file(&dst.anchor).unwrap().clone();
-                    base.pop();
-                    let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]);
-                    format_to!(buf, "//- {}\n", created_file_path);
-                    buf.push_str(&initial_contents);
-                }
+                let (dst, contents) = match file_system_edit {
+                    FileSystemEdit::CreateFile { dst, initial_contents } => (dst, initial_contents),
+                    FileSystemEdit::MoveFile { src, dst } => {
+                        (dst, db.file_text(src).as_ref().to_owned())
+                    }
+                };
+                let sr = db.file_source_root(dst.anchor);
+                let sr = db.source_root(sr);
+                let mut base = sr.path_for_file(&dst.anchor).unwrap().clone();
+                base.pop();
+                let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]);
+                format_to!(buf, "//- {}\n", created_file_path);
+                buf.push_str(&contents);
             }
 
             assert_eq_text!(after, &buf);
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 95a68ca9893..831f63d4762 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -1304,6 +1304,22 @@ mod foo;
 }
 
 #[test]
+fn doctest_move_to_mod_rs() {
+    check_doc_test(
+        "move_to_mod_rs",
+        r#####"
+//- /main.rs
+mod a;
+//- /a.rs
+$0fn t() {}$0
+"#####,
+        r#####"
+fn t() {}
+"#####,
+    )
+}
+
+#[test]
 fn doctest_pull_assignment_up() {
     check_doc_test(
         "pull_assignment_up",