From fede1a3beb17ae65194bf000586a062724bf47cf Mon Sep 17 00:00:00 2001 From: longfangsong <longfangsong@icloud.com> Date: Sun, 12 Sep 2021 02:30:56 +0800 Subject: [PATCH 1/5] Add promote_mod_file assist --- crates/ide_assists/src/assist_context.rs | 4 + .../src/handlers/promote_mod_file.rs | 148 ++++++++++++++++++ crates/ide_assists/src/lib.rs | 2 + crates/ide_assists/src/tests.rs | 23 +-- 4 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 crates/ide_assists/src/handlers/promote_mod_file.rs diff --git a/crates/ide_assists/src/assist_context.rs b/crates/ide_assists/src/assist_context.rs index 0244f5fb291..11e19304f1e 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/promote_mod_file.rs b/crates/ide_assists/src/handlers/promote_mod_file.rs new file mode 100644 index 00000000000..c5c37def079 --- /dev/null +++ b/crates/ide_assists/src/handlers/promote_mod_file.rs @@ -0,0 +1,148 @@ +use ide_db::{ + assists::{AssistId, AssistKind}, + base_db::AnchoredPathBuf, +}; +use syntax::{ + ast::{self}, + AstNode, TextRange, +}; + +use crate::assist_context::{AssistContext, Assists}; + +pub(crate) fn promote_mod_file(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)?; + if module.is_mod_rs(ctx.db()) { + return None; + } + let target = TextRange::new( + source_file.syntax().text_range().start(), + source_file.syntax().text_range().end(), + ); + let path = format!("./{}/mod.rs", module.name(ctx.db())?.to_string()); + let dst = AnchoredPathBuf { anchor: ctx.frange.file_id, path }; + acc.add( + AssistId("promote_mod_file", AssistKind::Refactor), + "Promote Module to directory", + 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( + promote_mod_file, + r#" +//- /main.rs +mod a; +//- /a.rs +$0fn t() {} +"#, + r#" +//- /a/mod.rs +fn t() {} +"#, + ); + } + + #[test] + fn cursor_can_be_putted_anywhere() { + check_assist( + promote_mod_file, + r#" +//- /main.rs +mod a; +//- /a.rs +fn t() {}$0 +"#, + r#" +//- /a/mod.rs +fn t() {} +"#, + ); + check_assist( + promote_mod_file, + r#" +//- /main.rs +mod a; +//- /a.rs +fn t()$0 {} +"#, + r#" +//- /a/mod.rs +fn t() {} +"#, + ); + check_assist( + promote_mod_file, + r#" +//- /main.rs +mod a; +//- /a.rs +fn t($0) {} +"#, + r#" +//- /a/mod.rs +fn t() {} +"#, + ); + } + + #[test] + fn cannot_promote_mod_rs() { + check_assist_not_applicable( + promote_mod_file, + r#"//- /main.rs +mod a; +//- /a/mod.rs +$0fn t() {} +"#, + ); + } + + #[test] + fn cannot_promote_main_and_lib_rs() { + check_assist_not_applicable( + promote_mod_file, + r#"//- /main.rs +$0fn t() {} +"#, + ); + check_assist_not_applicable( + promote_mod_file, + r#"//- /lib.rs +$0fn t() {} +"#, + ); + } + + #[test] + fn works_in_mod() { + // note: /a/b.rs remains untouched + check_assist( + promote_mod_file, + r#"//- /main.rs +mod a; +//- /a.rs +mod b; +$0fn t() {} +//- /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 7c074a4f632..57007691b5e 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs @@ -96,6 +96,7 @@ mod handlers { mod move_bounds; mod move_guard; mod move_module_to_file; + mod promote_mod_file; mod pull_assignment_up; mod qualify_path; mod raw_string; @@ -167,6 +168,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, + promote_mod_file::promote_mod_file, 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); From 3edc25dc2699c90621d20d85e7883d24edbf1794 Mon Sep 17 00:00:00 2001 From: longfangsong <longfangsong@icloud.com> Date: Sun, 12 Sep 2021 10:53:56 +0800 Subject: [PATCH 2/5] Add docs strings --- crates/ide_assists/src/handlers/promote_mod_file.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/ide_assists/src/handlers/promote_mod_file.rs b/crates/ide_assists/src/handlers/promote_mod_file.rs index c5c37def079..2ba51d16992 100644 --- a/crates/ide_assists/src/handlers/promote_mod_file.rs +++ b/crates/ide_assists/src/handlers/promote_mod_file.rs @@ -9,6 +9,19 @@ use syntax::{ use crate::assist_context::{AssistContext, Assists}; +// Assist: promote_mod_file +// +// Moves inline module's contents to a separate file. +// +// ``` +// // a.rs +// $0fn t() {} +// ``` +// -> +// ``` +// // /a/mod.rs +// fn t() {} +// ``` pub(crate) fn promote_mod_file(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)?; From cafc7e350132f8121c45f863f601eba44392c63a Mon Sep 17 00:00:00 2001 From: longfangsong <longfangsong@icloud.com> Date: Sun, 12 Sep 2021 11:20:28 +0800 Subject: [PATCH 3/5] Fix doc test --- .../ide_assists/src/handlers/promote_mod_file.rs | 5 +++-- crates/ide_assists/src/tests/generated.rs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/ide_assists/src/handlers/promote_mod_file.rs b/crates/ide_assists/src/handlers/promote_mod_file.rs index 2ba51d16992..5991c0989fe 100644 --- a/crates/ide_assists/src/handlers/promote_mod_file.rs +++ b/crates/ide_assists/src/handlers/promote_mod_file.rs @@ -14,12 +14,13 @@ use crate::assist_context::{AssistContext, Assists}; // Moves inline module's contents to a separate file. // // ``` -// // a.rs +// //- /main.rs +// mod a; +// //- /a.rs // $0fn t() {} // ``` // -> // ``` -// // /a/mod.rs // fn t() {} // ``` pub(crate) fn promote_mod_file(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 39ab8c7b74e..98e0bd67489 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs @@ -1226,6 +1226,22 @@ mod foo; ) } +#[test] +fn doctest_promote_mod_file() { + check_doc_test( + "promote_mod_file", + r#####" +//- /main.rs +mod a; +//- /a.rs +$0fn t() {} +"#####, + r#####" +fn t() {} +"#####, + ) +} + #[test] fn doctest_pull_assignment_up() { check_doc_test( From cd599ec202d49d044d6d71fd3471e3c5199476e1 Mon Sep 17 00:00:00 2001 From: longfangsong <longfangsong@icloud.com> Date: Mon, 20 Sep 2021 20:43:13 +0800 Subject: [PATCH 4/5] Address comments --- .../src/handlers/promote_mod_file.rs | 78 +++++++++++-------- crates/ide_assists/src/tests/generated.rs | 2 +- 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/crates/ide_assists/src/handlers/promote_mod_file.rs b/crates/ide_assists/src/handlers/promote_mod_file.rs index 5991c0989fe..1a177893f0c 100644 --- a/crates/ide_assists/src/handlers/promote_mod_file.rs +++ b/crates/ide_assists/src/handlers/promote_mod_file.rs @@ -3,12 +3,38 @@ use ide_db::{ base_db::AnchoredPathBuf, }; use syntax::{ - ast::{self}, - AstNode, TextRange, + 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: promote_mod_file // // Moves inline module's contents to a separate file. @@ -17,7 +43,7 @@ use crate::assist_context::{AssistContext, Assists}; // //- /main.rs // mod a; // //- /a.rs -// $0fn t() {} +// $0fn t() {}$0 // ``` // -> // ``` @@ -26,18 +52,23 @@ use crate::assist_context::{AssistContext, Assists}; pub(crate) fn promote_mod_file(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)?; - if module.is_mod_rs(ctx.db()) { + // 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()) || trimmed_selected_range != trimmed_file_range { return None; } + let target = TextRange::new( source_file.syntax().text_range().start(), source_file.syntax().text_range().end(), ); - let path = format!("./{}/mod.rs", module.name(ctx.db())?.to_string()); + 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("promote_mod_file", AssistKind::Refactor), - "Promote Module to directory", + format!("Turn {}.rs to {}/mod.rs", module_name, module_name), target, |builder| { builder.move_file(ctx.frange.file_id, dst); @@ -60,7 +91,7 @@ mod tests { mod a; //- /a.rs $0fn t() {} -"#, +$0"#, r#" //- /a/mod.rs fn t() {} @@ -69,44 +100,23 @@ fn t() {} } #[test] - fn cursor_can_be_putted_anywhere() { - check_assist( + fn must_select_all_file() { + check_assist_not_applicable( promote_mod_file, r#" //- /main.rs mod a; //- /a.rs fn t() {}$0 -"#, - r#" -//- /a/mod.rs -fn t() {} "#, ); - check_assist( + check_assist_not_applicable( promote_mod_file, r#" //- /main.rs mod a; //- /a.rs -fn t()$0 {} -"#, - r#" -//- /a/mod.rs -fn t() {} -"#, - ); - check_assist( - promote_mod_file, - r#" -//- /main.rs -mod a; -//- /a.rs -fn t($0) {} -"#, - r#" -//- /a/mod.rs -fn t() {} +$0fn$0 t() {} "#, ); } @@ -147,8 +157,8 @@ $0fn t() {} r#"//- /main.rs mod a; //- /a.rs -mod b; -$0fn t() {} +$0mod b; +fn t() {}$0 //- /a/b.rs fn t1() {} "#, diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 98e0bd67489..4ec3e0bb1bd 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs @@ -1234,7 +1234,7 @@ fn doctest_promote_mod_file() { //- /main.rs mod a; //- /a.rs -$0fn t() {} +$0fn t() {}$0 "#####, r#####" fn t() {} From 22abbe86f343a2c3ce639a15ba88502cf6eebfe6 Mon Sep 17 00:00:00 2001 From: longfangsong <longfangsong@icloud.com> Date: Sat, 25 Sep 2021 22:47:20 +0800 Subject: [PATCH 5/5] Address comments --- ...{promote_mod_file.rs => move_to_mod_rs.rs} | 38 +++++++++++-------- crates/ide_assists/src/lib.rs | 4 +- crates/ide_assists/src/tests/generated.rs | 4 +- 3 files changed, 27 insertions(+), 19 deletions(-) rename crates/ide_assists/src/handlers/{promote_mod_file.rs => move_to_mod_rs.rs} (81%) diff --git a/crates/ide_assists/src/handlers/promote_mod_file.rs b/crates/ide_assists/src/handlers/move_to_mod_rs.rs similarity index 81% rename from crates/ide_assists/src/handlers/promote_mod_file.rs rename to crates/ide_assists/src/handlers/move_to_mod_rs.rs index 1a177893f0c..9b060bb710f 100644 --- a/crates/ide_assists/src/handlers/promote_mod_file.rs +++ b/crates/ide_assists/src/handlers/move_to_mod_rs.rs @@ -35,9 +35,9 @@ fn trimmed_text_range(source_file: &SourceFile, initial_range: TextRange) -> Tex trimmed_range } -// Assist: promote_mod_file +// Assist: move_to_mod_rs // -// Moves inline module's contents to a separate file. +// Moves xxx.rs to xxx/mod.rs. // // ``` // //- /main.rs @@ -49,13 +49,18 @@ fn trimmed_text_range(source_file: &SourceFile, initial_range: TextRange) -> Tex // ``` // fn t() {} // ``` -pub(crate) fn promote_mod_file(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { +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()) || trimmed_selected_range != trimmed_file_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; } @@ -67,7 +72,7 @@ pub(crate) fn promote_mod_file(acc: &mut Assists, ctx: &AssistContext) -> Option let path = format!("./{}/mod.rs", module_name); let dst = AnchoredPathBuf { anchor: ctx.frange.file_id, path }; acc.add( - AssistId("promote_mod_file", AssistKind::Refactor), + AssistId("move_to_mod_rs", AssistKind::Refactor), format!("Turn {}.rs to {}/mod.rs", module_name, module_name), target, |builder| { @@ -85,7 +90,7 @@ mod tests { #[test] fn trivial() { check_assist( - promote_mod_file, + move_to_mod_rs, r#" //- /main.rs mod a; @@ -101,8 +106,9 @@ fn t() {} #[test] fn must_select_all_file() { + cov_mark::check!(not_all_selected); check_assist_not_applicable( - promote_mod_file, + move_to_mod_rs, r#" //- /main.rs mod a; @@ -110,8 +116,9 @@ mod a; fn t() {}$0 "#, ); + cov_mark::check!(not_all_selected); check_assist_not_applicable( - promote_mod_file, + move_to_mod_rs, r#" //- /main.rs mod a; @@ -123,12 +130,13 @@ $0fn$0 t() {} #[test] fn cannot_promote_mod_rs() { + cov_mark::check!(already_mod_rs); check_assist_not_applicable( - promote_mod_file, + move_to_mod_rs, r#"//- /main.rs mod a; //- /a/mod.rs -$0fn t() {} +$0fn t() {}$0 "#, ); } @@ -136,15 +144,15 @@ $0fn t() {} #[test] fn cannot_promote_main_and_lib_rs() { check_assist_not_applicable( - promote_mod_file, + move_to_mod_rs, r#"//- /main.rs -$0fn t() {} +$0fn t() {}$0 "#, ); check_assist_not_applicable( - promote_mod_file, + move_to_mod_rs, r#"//- /lib.rs -$0fn t() {} +$0fn t() {}$0 "#, ); } @@ -153,7 +161,7 @@ $0fn t() {} fn works_in_mod() { // note: /a/b.rs remains untouched check_assist( - promote_mod_file, + move_to_mod_rs, r#"//- /main.rs mod a; //- /a.rs diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 57007691b5e..1f5e2f036e0 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs @@ -96,7 +96,7 @@ mod handlers { mod move_bounds; mod move_guard; mod move_module_to_file; - mod promote_mod_file; + mod move_to_mod_rs; mod pull_assignment_up; mod qualify_path; mod raw_string; @@ -168,7 +168,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, - promote_mod_file::promote_mod_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/generated.rs b/crates/ide_assists/src/tests/generated.rs index 4ec3e0bb1bd..f371ca80178 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs @@ -1227,9 +1227,9 @@ mod foo; } #[test] -fn doctest_promote_mod_file() { +fn doctest_move_to_mod_rs() { check_doc_test( - "promote_mod_file", + "move_to_mod_rs", r#####" //- /main.rs mod a;