Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions crates/assists/src/assist_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ use std::mem;

use algo::find_covering_element;
use hir::Semantics;
use ide_db::base_db::{FileId, FileRange};
use ide_db::base_db::{AnchoredPathBuf, FileId, FileRange};
use ide_db::{
label::Label,
source_change::{SourceChange, SourceFileEdit},
source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
RootDatabase,
};
use syntax::{
Expand Down Expand Up @@ -209,6 +209,7 @@ pub(crate) struct AssistBuilder {
file_id: FileId,
is_snippet: bool,
source_file_edits: Vec<SourceFileEdit>,
file_system_edits: Vec<FileSystemEdit>,
}

impl AssistBuilder {
Expand All @@ -218,6 +219,7 @@ impl AssistBuilder {
file_id,
is_snippet: false,
source_file_edits: Vec::default(),
file_system_edits: Vec::default(),
}
}

Expand Down Expand Up @@ -282,12 +284,17 @@ impl AssistBuilder {
algo::diff(&node, &new).into_text_edit(&mut self.edit);
}
}
pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
let file_system_edit =
FileSystemEdit::CreateFile { dst: dst.clone(), initial_contents: content.into() };
self.file_system_edits.push(file_system_edit);
}

fn finish(mut self) -> SourceChange {
self.commit();
SourceChange {
source_file_edits: mem::take(&mut self.source_file_edits),
file_system_edits: Default::default(),
file_system_edits: mem::take(&mut self.file_system_edits),
is_snippet: self.is_snippet,
}
}
Expand Down
170 changes: 170 additions & 0 deletions crates/assists/src/handlers/extract_module_to_file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
use ast::edit::IndentLevel;
use ide_db::base_db::{AnchoredPathBuf, SourceDatabaseExt};
use syntax::{
ast::{self, edit::AstNodeEdit, NameOwner},
AstNode,
};

use crate::{AssistContext, AssistId, AssistKind, Assists};

// Assist: extract_module_to_file
//
// This assist extract module to file.
//
// ```
// mod foo {<|>
// fn t() {}
// }
// ```
// ->
// ```
// mod foo;
// ```
pub(crate) fn extract_module_to_file(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let assist_id = AssistId("extract_module_to_file", AssistKind::RefactorExtract);
let assist_label = "Extract module to file";
let db = ctx.db();
let module_ast = ctx.find_node_at_offset::<ast::Module>()?;
let module_items = module_ast.item_list()?;
let dedent_module_items_text = module_items.dedent(IndentLevel(1)).to_string();
let module_name = module_ast.name()?;
let target = module_ast.syntax().text_range();
let anchor_file_id = ctx.frange.file_id;
let sr = db.file_source_root(anchor_file_id);
let sr = db.source_root(sr);
let file_path = sr.path_for_file(&anchor_file_id)?;
let (file_name, file_ext) = file_path.name_and_extension()?;
acc.add(assist_id, assist_label, target, |builder| {
builder.replace(target, format!("mod {};", module_name));
let path = if is_main_or_lib(file_name) {
format!("./{}.{}", module_name, file_ext.unwrap())
} else {
format!("./{}/{}.{}", file_name, module_name, file_ext.unwrap())
};
let dst = AnchoredPathBuf { anchor: anchor_file_id, path };
let contents = update_module_items_string(dedent_module_items_text);
builder.create_file(dst, contents);
})
}
fn is_main_or_lib(file_name: &str) -> bool {
file_name == "main".to_string() || file_name == "lib".to_string()
}
fn update_module_items_string(items_str: String) -> String {
let mut items_string_lines: Vec<&str> = items_str.lines().collect();
items_string_lines.pop(); // Delete last line
items_string_lines.reverse();
items_string_lines.pop(); // Delete first line
items_string_lines.reverse();

let string = items_string_lines.join("\n");
format!("{}", string)
}

#[cfg(test)]
mod tests {
use crate::tests::check_assist;

use super::*;

#[test]
fn extract_module_to_file_with_basic_module() {
check_assist(
extract_module_to_file,
r#"
//- /foo.rs crate:foo
mod tests {<|>
#[test] fn t() {}
}
"#,
r#"
//- /foo.rs
mod tests;
//- /foo/tests.rs
#[test] fn t() {}"#,
)
}

#[test]
fn extract_module_to_file_with_file_path() {
check_assist(
extract_module_to_file,
r#"
//- /src/foo.rs crate:foo
mod bar {<|>
fn f() {

}
}
fn main() {
println!("Hello, world!");
}
"#,
r#"
//- /src/foo.rs
mod bar;
fn main() {
println!("Hello, world!");
}
//- /src/foo/bar.rs
fn f() {

}"#,
)
}

#[test]
fn extract_module_to_file_with_main_filw() {
check_assist(
extract_module_to_file,
r#"
//- /main.rs
mod foo {<|>
fn f() {

}
}
fn main() {
println!("Hello, world!");
}
"#,
r#"
//- /main.rs
mod foo;
fn main() {
println!("Hello, world!");
}
//- /foo.rs
fn f() {

}"#,
)
}

#[test]
fn extract_module_to_file_with_lib_file() {
check_assist(
extract_module_to_file,
r#"
//- /lib.rs
mod foo {<|>
fn f() {

}
}
fn main() {
println!("Hello, world!");
}
"#,
r#"
//- /lib.rs
mod foo;
fn main() {
println!("Hello, world!");
}
//- /foo.rs
fn f() {

}"#,
)
}
}
2 changes: 2 additions & 0 deletions crates/assists/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ mod handlers {
mod convert_integer_literal;
mod early_return;
mod expand_glob_import;
mod extract_module_to_file;
mod extract_struct_from_enum_variant;
mod extract_variable;
mod fill_match_arms;
Expand Down Expand Up @@ -179,6 +180,7 @@ mod handlers {
convert_integer_literal::convert_integer_literal,
early_return::convert_to_guarded_return,
expand_glob_import::expand_glob_import,
extract_module_to_file::extract_module_to_file,
extract_struct_from_enum_variant::extract_struct_from_enum_variant,
extract_variable::extract_variable,
fill_match_arms::fill_match_arms,
Expand Down
28 changes: 24 additions & 4 deletions crates/assists/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod generated;

use hir::Semantics;
use ide_db::base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
use ide_db::source_change::FileSystemEdit;
use ide_db::RootDatabase;
use syntax::TextRange;
use test_utils::{assert_eq_text, extract_offset, extract_range};
Expand Down Expand Up @@ -47,7 +48,7 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
let before = db.file_text(file_id).to_string();
let frange = FileRange { file_id, range: selection.into() };

let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange)
let assist = Assist::resolved(&db, &AssistConfig::default(), frange)
.into_iter()
.find(|assist| assist.assist.id.0 == assist_id)
.unwrap_or_else(|| {
Expand All @@ -63,9 +64,12 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
});

let actual = {
let change = assist.source_change.source_file_edits.pop().unwrap();
let mut actual = before;
change.edit.apply(&mut actual);
for source_file_edit in assist.source_change.source_file_edits {
if source_file_edit.file_id == file_id {
source_file_edit.edit.apply(&mut actual)
}
}
actual
};
assert_eq_text!(&after, &actual);
Expand Down Expand Up @@ -99,7 +103,8 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
(Some(assist), ExpectedResult::After(after)) => {
let mut source_change = assist.source_change;
assert!(!source_change.source_file_edits.is_empty());
let skip_header = source_change.source_file_edits.len() == 1;
let skip_header = source_change.source_file_edits.len() == 1
&& source_change.file_system_edits.len() == 0;
source_change.source_file_edits.sort_by_key(|it| it.file_id);

let mut buf = String::new();
Expand All @@ -115,6 +120,21 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
buf.push_str(&text);
}

for file_system_edit in source_change.file_system_edits.clone() {
match file_system_edit {
FileSystemEdit::CreateFile { dst, initial_contents } => {
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);
}
_ => (),
}
}

assert_eq_text!(after, &buf);
}
(Some(assist), ExpectedResult::Target(target)) => {
Expand Down
15 changes: 15 additions & 0 deletions crates/assists/src/tests/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,21 @@ fn qux(bar: Bar, baz: Baz) {}
)
}

#[test]
fn doctest_extract_module_to_file() {
check_doc_test(
"extract_module_to_file",
r#####"
mod foo {<|>
fn t() {}
}
"#####,
r#####"
mod foo;
"#####,
)
}

#[test]
fn doctest_extract_struct_from_enum_variant() {
check_doc_test(
Expand Down
1 change: 1 addition & 0 deletions crates/ide/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@ fn test_fn() {
),
path: "foo.rs",
},
initial_contents: "",
},
],
is_snippet: false,
Expand Down
1 change: 1 addition & 0 deletions crates/ide/src/diagnostics/fixes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ impl DiagnosticWithFix for UnresolvedModule {
anchor: self.file.original_file(sema.db),
path: self.candidate.clone(),
},
initial_contents: "".to_string(),
}
.into(),
unresolved_module.syntax().text_range(),
Expand Down
2 changes: 1 addition & 1 deletion crates/ide_db/src/source_change.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl From<Vec<SourceFileEdit>> for SourceChange {

#[derive(Debug, Clone)]
pub enum FileSystemEdit {
CreateFile { dst: AnchoredPathBuf },
CreateFile { dst: AnchoredPathBuf, initial_contents: String },
MoveFile { src: FileId, dst: AnchoredPathBuf },
}

Expand Down
Loading