diff --git a/Cargo.lock b/Cargo.lock index cea85409369..78ad6dc07db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -611,6 +611,7 @@ dependencies = [ "syntax", "test_utils", "text_edit", + "toolchain", "tracing", "url", ] diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml index cd11b2801d7..02eb46333be 100644 --- a/crates/ide/Cargo.toml +++ b/crates/ide/Cargo.toml @@ -32,6 +32,7 @@ ide_assists = { path = "../ide_assists", version = "0.0.0" } ide_diagnostics = { path = "../ide_diagnostics", version = "0.0.0" } ide_ssr = { path = "../ide_ssr", version = "0.0.0" } ide_completion = { path = "../ide_completion", version = "0.0.0" } +toolchain = { path = "../toolchain", version = "0.0.0" } # ide should depend only on the top-level `hir` package. if you need # something from some `hir_xxx` subpackage, reexport the API via `hir`. diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs index b4608d63c6c..41984a55c6f 100644 --- a/crates/ide/src/expand_macro.rs +++ b/crates/ide/src/expand_macro.rs @@ -1,7 +1,7 @@ use hir::Semantics; use ide_db::{ - helpers::pick_best_token, syntax_helpers::insert_whitespace_into_node::insert_ws_into, - RootDatabase, + base_db::FileId, helpers::pick_best_token, + syntax_helpers::insert_whitespace_into_node::insert_ws_into, RootDatabase, }; use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T}; @@ -58,10 +58,9 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< .take_while(|it| it != &token) .filter(|it| it.kind() == T![,]) .count(); - Some(ExpandedMacro { - name, - expansion: expansions.get(idx).cloned().map(insert_ws_into)?.to_string(), - }) + let expansion = + format(db, SyntaxKind::MACRO_ITEMS, position.file_id, expansions.get(idx).cloned()?); + Some(ExpandedMacro { name, expansion }) }); if derive.is_some() { @@ -72,17 +71,20 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< // currently we only recursively expand one of the two types let mut expanded = None; let mut name = None; + let mut kind = SyntaxKind::ERROR; for node in tok.ancestors() { if let Some(item) = ast::Item::cast(node.clone()) { if let Some(def) = sema.resolve_attr_macro_call(&item) { name = Some(def.name(db).to_string()); expanded = expand_attr_macro_recur(&sema, &item); + kind = SyntaxKind::MACRO_ITEMS; break; } } if let Some(mac) = ast::MacroCall::cast(node) { name = Some(mac.path()?.segment()?.name_ref()?.to_string()); expanded = expand_macro_recur(&sema, &mac); + kind = mac.syntax().parent().map(|it| it.kind()).unwrap_or(SyntaxKind::MACRO_ITEMS); break; } } @@ -90,7 +92,8 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< // FIXME: // macro expansion may lose all white space information // But we hope someday we can use ra_fmt for that - let expansion = insert_ws_into(expanded?).to_string(); + let expansion = format(db, kind, position.file_id, expanded?); + Some(ExpandedMacro { name: name.unwrap_or_else(|| "???".to_owned()), expansion }) } @@ -130,6 +133,70 @@ fn expand( Some(expanded) } +fn format(db: &RootDatabase, kind: SyntaxKind, file_id: FileId, expanded: SyntaxNode) -> String { + let expansion = insert_ws_into(expanded).to_string(); + + _format(db, kind, file_id, &expansion).unwrap_or(expansion) +} + +#[cfg(test)] +fn _format( + _db: &RootDatabase, + _kind: SyntaxKind, + _file_id: FileId, + _expansion: &str, +) -> Option { + None +} + +#[cfg(not(test))] +fn _format( + db: &RootDatabase, + kind: SyntaxKind, + file_id: FileId, + expansion: &str, +) -> Option { + use ide_db::base_db::{FileLoader, SourceDatabase}; + // hack until we get hygiene working (same character amount to preserve formatting as much as possible) + const DOLLAR_CRATE_REPLACE: &str = &"__r_a_"; + let expansion = expansion.replace("$crate", DOLLAR_CRATE_REPLACE); + let (prefix, suffix) = match kind { + SyntaxKind::MACRO_PAT => ("fn __(", ": u32);"), + SyntaxKind::MACRO_EXPR | SyntaxKind::MACRO_STMTS => ("fn __() {", "}"), + SyntaxKind::MACRO_TYPE => ("type __ =", ";"), + _ => ("", ""), + }; + let expansion = format!("{prefix}{expansion}{suffix}"); + + let &crate_id = db.relevant_crates(file_id).iter().next()?; + let edition = db.crate_graph()[crate_id].edition; + + let mut cmd = std::process::Command::new(toolchain::rustfmt()); + cmd.arg("--edition"); + cmd.arg(edition.to_string()); + + let mut rustfmt = cmd + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .ok()?; + + std::io::Write::write_all(&mut rustfmt.stdin.as_mut()?, expansion.as_bytes()).ok()?; + + let output = rustfmt.wait_with_output().ok()?; + let captured_stdout = String::from_utf8(output.stdout).ok()?; + + if output.status.success() && !captured_stdout.trim().is_empty() { + let foo = captured_stdout.replace(DOLLAR_CRATE_REPLACE, "$crate"); + let trim_indent = stdx::trim_indent(foo.trim().strip_prefix(prefix)?.strip_suffix(suffix)?); + tracing::debug!("expand_macro: formatting succeeded"); + Some(trim_indent) + } else { + None + } +} + #[cfg(test)] mod tests { use expect_test::{expect, Expect}; diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs index 55bb10d5e86..1fb3b5ec3f3 100644 --- a/crates/ide/src/navigation_target.rs +++ b/crates/ide/src/navigation_target.rs @@ -340,7 +340,6 @@ impl TryToNav for hir::Macro { Either::Left(it) => it, Either::Right(it) => it, }; - tracing::debug!("nav target {:#?}", name_owner.syntax()); let mut res = NavigationTarget::from_named( db, src.as_ref().with_value(name_owner),