Add runnable for rust main function (#13087)

Release Notes:

- N/A



https://github.com/zed-industries/zed/assets/21101490/7a57805c-1d31-48b2-bc2c-3a6f0b730d72
This commit is contained in:
Panghu 2024-06-18 23:25:20 +09:00 committed by GitHub
parent 5dc26c261d
commit 3701e190ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 157 additions and 11 deletions

View file

@ -346,10 +346,17 @@ pub(crate) struct RustContextProvider;
const RUST_PACKAGE_TASK_VARIABLE: VariableName =
VariableName::Custom(Cow::Borrowed("RUST_PACKAGE"));
/// The bin name corresponding to the current file in Cargo.toml
const RUST_BIN_NAME_TASK_VARIABLE: VariableName =
VariableName::Custom(Cow::Borrowed("RUST_BIN_NAME"));
const RUST_MAIN_FUNCTION_TASK_VARIABLE: VariableName =
VariableName::Custom(Cow::Borrowed("_rust_main_function_end"));
impl ContextProvider for RustContextProvider {
fn build_context(
&self,
_: &TaskVariables,
task_variables: &TaskVariables,
location: &Location,
cx: &mut gpui::AppContext,
) -> Result<TaskVariables> {
@ -358,17 +365,35 @@ impl ContextProvider for RustContextProvider {
.read(cx)
.file()
.and_then(|file| Some(file.as_local()?.abs_path(cx)));
Ok(
if let Some(package_name) = local_abs_path
.as_deref()
.and_then(|local_abs_path| local_abs_path.parent())
.and_then(human_readable_package_name)
let local_abs_path = local_abs_path.as_deref();
let is_main_function = task_variables
.get(&RUST_MAIN_FUNCTION_TASK_VARIABLE)
.is_some();
if is_main_function {
if let Some((package_name, bin_name)) = local_abs_path
.and_then(|local_abs_path| package_name_and_bin_name_from_abs_path(local_abs_path))
{
TaskVariables::from_iter(Some((RUST_PACKAGE_TASK_VARIABLE.clone(), package_name)))
} else {
TaskVariables::default()
},
)
return Ok(TaskVariables::from_iter([
(RUST_PACKAGE_TASK_VARIABLE.clone(), package_name),
(RUST_BIN_NAME_TASK_VARIABLE.clone(), bin_name),
]));
}
}
if let Some(package_name) = local_abs_path
.and_then(|local_abs_path| local_abs_path.parent())
.and_then(human_readable_package_name)
{
return Ok(TaskVariables::from_iter([(
RUST_PACKAGE_TASK_VARIABLE.clone(),
package_name,
)]));
}
Ok(TaskVariables::default())
}
fn associated_tasks(&self) -> Option<TaskTemplates> {
@ -426,6 +451,23 @@ impl ContextProvider for RustContextProvider {
tags: vec!["rust-mod-test".to_owned()],
..TaskTemplate::default()
},
TaskTemplate {
label: format!(
"cargo run -p {} --bin {}",
RUST_PACKAGE_TASK_VARIABLE.template_value(),
RUST_BIN_NAME_TASK_VARIABLE.template_value(),
),
command: "cargo".into(),
args: vec![
"run".into(),
"-p".into(),
RUST_PACKAGE_TASK_VARIABLE.template_value(),
"--bin".into(),
RUST_BIN_NAME_TASK_VARIABLE.template_value(),
],
tags: vec!["rust-main".to_owned()],
..TaskTemplate::default()
},
TaskTemplate {
label: format!(
"cargo test -p {}",
@ -455,6 +497,65 @@ impl ContextProvider for RustContextProvider {
}
}
/// Part of the data structure of Cargo metadata
#[derive(serde::Deserialize)]
struct CargoMetadata {
packages: Vec<CargoPackage>,
}
#[derive(serde::Deserialize)]
struct CargoPackage {
id: String,
targets: Vec<CargoTarget>,
}
#[derive(serde::Deserialize)]
struct CargoTarget {
name: String,
kind: Vec<String>,
src_path: String,
}
fn package_name_and_bin_name_from_abs_path(abs_path: &Path) -> Option<(String, String)> {
let output = std::process::Command::new("cargo")
.current_dir(abs_path.parent()?)
.arg("metadata")
.arg("--no-deps")
.arg("--format-version")
.arg("1")
.output()
.log_err()?
.stdout;
let metadata: CargoMetadata = serde_json::from_slice(&output).log_err()?;
retrieve_package_id_and_bin_name_from_metadata(metadata, abs_path).and_then(
|(package_id, bin_name)| {
let package_name = package_name_from_pkgid(&package_id);
package_name.map(|package_name| (package_name.to_owned(), bin_name))
},
)
}
fn retrieve_package_id_and_bin_name_from_metadata(
metadata: CargoMetadata,
abs_path: &Path,
) -> Option<(String, String)> {
let abs_path = abs_path.to_str()?;
for package in metadata.packages {
for target in package.targets {
let is_bin = target.kind.iter().any(|kind| kind == "bin");
if target.src_path == abs_path && is_bin {
return Some((package.id, target.name));
}
}
}
None
}
fn human_readable_package_name(package_directory: &Path) -> Option<String> {
let pkgid = String::from_utf8(
std::process::Command::new("cargo")
@ -815,4 +916,37 @@ mod tests {
assert_eq!(package_name_from_pkgid(input), Some(expected));
}
}
#[test]
fn test_retrieve_package_id_and_bin_name_from_metadata() {
for (input, absolute_path, expected) in [
(
r#"{"packages":[{"id":"path+file:///path/to/zed/crates/zed#0.131.0","targets":[{"name":"zed","kind":["bin"],"src_path":"/path/to/zed/src/main.rs"}]}]}"#,
"/path/to/zed/src/main.rs",
Some(("path+file:///path/to/zed/crates/zed#0.131.0", "zed")),
),
(
r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","targets":[{"name":"my-custom-bin","kind":["bin"],"src_path":"/path/to/custom-package/src/main.rs"}]}]}"#,
"/path/to/custom-package/src/main.rs",
Some((
"path+file:///path/to/custom-package#my-custom-package@0.1.0",
"my-custom-bin",
)),
),
(
r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","targets":[{"name":"my-custom-package","kind":["lib"],"src_path":"/path/to/custom-package/src/main.rs"}]}]}"#,
"/path/to/custom-package/src/main.rs",
None,
),
] {
let metadata: CargoMetadata = serde_json::from_str(input).unwrap();
let absolute_path = Path::new(absolute_path);
assert_eq!(
retrieve_package_id_and_bin_name_from_metadata(metadata, absolute_path),
expected.map(|(pkgid, bin)| (pkgid.to_owned(), bin.to_owned()))
);
}
}
}

View file

@ -25,3 +25,15 @@
)
(#set! tag rust-test)
)
; Rust main function
(
(
(function_item
name: (_) @run
body: _
) @_rust_main_function_end
(#eq? @run "main")
)
(#set! tag rust-main)
)