use crate::{
e_target::{CargoTarget, TargetKind},
prelude::*,
};
use toml::Value;
use crate::e_workspace::{get_workspace_member_manifest_paths, is_workspace_manifest};
pub fn find_main_file(sample: &CargoTarget) -> Option<PathBuf> {
let manifest_path = Path::new(&sample.manifest_path);
let base = if is_workspace_manifest(manifest_path) {
match get_workspace_member_manifest_paths(manifest_path) {
Some(members) => {
if let Some((_, member_manifest)) = members
.into_iter()
.find(|(member_name, _)| member_name == &sample.name)
{
member_manifest.parent().map(|p| p.to_path_buf())?
} else {
manifest_path.parent().map(|p| p.to_path_buf())?
}
}
None => manifest_path.parent().map(|p| p.to_path_buf())?,
}
} else {
manifest_path.parent()?.to_path_buf()
};
let candidate_src = base.join("src").join("main.rs");
println!("DEBUG: candidate_src: {:?}", candidate_src);
if candidate_src.exists() {
return Some(candidate_src);
}
let candidate_main = base.join("main.rs");
println!("DEBUG: candidate_src: {:?}", candidate_main);
if candidate_main.exists() {
return Some(candidate_main);
}
let candidate_main = base.join(format!("{}.rs", sample.name));
println!("DEBUG: candidate_src: {:?}", candidate_main);
if candidate_main.exists() {
return Some(candidate_main);
}
let candidate_src = base
.join("src")
.join("bin")
.join(format!("{}.rs", sample.name));
println!("DEBUG: candidate_src: {:?}", candidate_src);
if candidate_src.exists() {
return Some(candidate_src);
}
let contents = fs::read_to_string(manifest_path).ok()?;
let value: Value = contents.parse().ok()?;
let targets = if sample.kind == TargetKind::Binary {
value.get("bin")
} else {
value.get("example").or_else(|| value.get("bin"))
}?;
if let Some(arr) = targets.as_array() {
for target in arr {
if let Some(name) = target.get("name").and_then(|v| v.as_str()) {
if name == sample.name {
let relative = target
.get("path")
.and_then(|v| v.as_str())
.unwrap_or("src/main.rs");
let base = manifest_path.parent()?;
let candidate = base.join(relative);
if candidate.exists() {
return Some(candidate);
}
}
}
}
}
None
}
pub fn find_main_line(file: &Path) -> Option<(usize, usize)> {
let content = fs::read_to_string(file).ok()?;
for (i, line) in content.lines().enumerate() {
if let Some(col) = line.find("fn main") {
return Some((i + 1, col + 1));
}
}
None
}
pub fn compute_vscode_args(sample: &CargoTarget) -> (String, Option<String>) {
let manifest_path = Path::new(&sample.manifest_path);
println!("DEBUG: manifest_path: {:?}", manifest_path);
let candidate_file: Option<PathBuf> = find_main_file(sample).or_else(|| {
if sample.kind == TargetKind::Binary
|| (sample.kind == TargetKind::Example && sample.extended)
{
let base = manifest_path.parent()?;
let fallback = base.join("src/main.rs");
if fallback.exists() {
Some(fallback)
} else {
None
}
} else if sample.kind == TargetKind::Example && !sample.extended {
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let fallback = cwd.join("examples").join(format!("{}.rs", sample.name));
if fallback.exists() {
Some(fallback)
} else {
None
}
} else {
None
}
});
println!("DEBUG: candidate_file: {:?}", candidate_file);
let (folder, goto_arg) = if let Some(file) = candidate_file {
let folder = file.parent().unwrap_or(&file).to_path_buf();
let goto_arg = if let Some((line, col)) = find_main_line(&file) {
Some(format!(
"{}:{}:{}",
file.to_str().unwrap_or_default(),
line,
col
))
} else {
Some(file.to_str().unwrap_or_default().to_string())
};
(folder, goto_arg)
} else {
(
manifest_path
.parent()
.unwrap_or(manifest_path)
.to_path_buf(),
None,
)
};
let folder_str = folder.to_str().unwrap_or_default().to_string();
println!("DEBUG: folder_str: {}", folder_str);
println!("DEBUG: goto_arg: {:?}", goto_arg);
(folder_str, goto_arg)
}
pub async fn open_vscode_for_sample(sample: &CargoTarget) {
let (folder_str, goto_arg) = compute_vscode_args(sample);
let output = if cfg!(target_os = "windows") {
if let Some(ref goto) = goto_arg {
Command::new("cmd")
.args(["/C", "code", folder_str.as_str(), "--goto", goto.as_str()])
.output()
} else {
Command::new("cmd")
.args(["/C", "code", folder_str.as_str()])
.output()
}
} else {
let mut cmd = Command::new("code");
cmd.arg(folder_str.as_str());
if let Some(goto) = goto_arg {
cmd.args(["--goto", goto.as_str()]);
}
cmd.output()
};
match output {
Ok(output) if output.status.success() => {
println!("DEBUG: VSCode command output: {:?}", output);
}
Ok(output) => {
let msg = format!(
"Error opening VSCode:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
error!("{}", msg);
}
Err(e) => {
let msg = format!("Failed to execute VSCode command: {}", e);
error!("{}", msg);
}
}
}
#[cfg(test)]
mod tests {
use crate::e_target::TargetOrigin;
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_find_main_file_default() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let manifest_path = dir.path().join("Cargo.toml");
let main_rs = dir.path().join("src/main.rs");
fs::create_dir_all(main_rs.parent().unwrap())?;
fs::write(&main_rs, "fn main() {}")?;
let toml_contents = r#"
[package]
name = "dummy"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "sample1"
"#;
fs::write(&manifest_path, toml_contents)?;
let sample = CargoTarget {
name: "sample1".to_string(),
display_name: "dummy".to_string(),
manifest_path,
kind: TargetKind::Binary,
extended: false,
toml_specified: false,
origin: Some(TargetOrigin::Named("sample1".into())),
};
let found = find_main_file(&sample).expect("Should find main file");
assert_eq!(found, main_rs);
dir.close()?;
Ok(())
}
#[test]
fn test_find_main_file_with_explicit_path() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let manifest_path = dir.path().join("Cargo.toml");
let custom_main = dir.path().join("src/main.rs");
fs::create_dir_all(custom_main.parent().unwrap())?;
fs::write(&custom_main, "fn main() {}")?;
let toml_contents = format!(
r#"
[package]
name = "dummy"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "sample2"
path = "{}"
"#,
custom_main
.strip_prefix(dir.path())
.unwrap()
.to_str()
.unwrap()
);
fs::write(&manifest_path, toml_contents)?;
let sample = CargoTarget {
name: "sample2".to_string(),
display_name: "dummy".to_string(),
manifest_path,
kind: TargetKind::Binary,
origin: Some(TargetOrigin::Named("sample2".into())),
toml_specified: false,
extended: false,
};
let found = find_main_file(&sample).expect("Should find custom main file");
assert_eq!(found, custom_main);
dir.close()?;
Ok(())
}
#[test]
fn test_extended_sample_src_main() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let sample_dir = dir.path().join("examples").join("sample_ext");
fs::create_dir_all(sample_dir.join("src"))?;
let main_rs = sample_dir.join("src/main.rs");
fs::write(&main_rs, "fn main() {}")?;
let manifest_path = sample_dir.join("Cargo.toml");
let toml_contents = r#"
[package]
name = "sample_ext"
version = "0.1.0"
edition = "2021"
"#;
fs::write(&manifest_path, toml_contents)?;
let sample = CargoTarget {
name: "sample_ext".to_string(),
display_name: "extended sample".to_string(),
manifest_path: manifest_path.clone(),
kind: TargetKind::Example,
origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
toml_specified: false,
extended: true,
};
let found = find_main_file(&sample).expect("Should find src/main.rs in extended sample");
assert_eq!(found, main_rs);
dir.close()?;
Ok(())
}
#[test]
fn test_extended_sample_main_rs() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let sample_dir = dir.path().join("examples").join("sample_ext2");
fs::create_dir_all(&sample_dir)?;
let main_rs = sample_dir.join("main.rs");
fs::write(&main_rs, "fn main() {}")?;
let manifest_path = sample_dir.join("Cargo.toml");
let toml_contents = r#"
[package]
name = "sample_ext2"
version = "0.1.0"
edition = "2021"
"#;
fs::write(&manifest_path, toml_contents)?;
let sample = CargoTarget {
name: "sample_ext2".to_string(),
display_name: "extended sample 2".to_string(),
manifest_path: manifest_path.clone(),
kind: TargetKind::Example,
origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
toml_specified: false,
extended: true,
};
let found = find_main_file(&sample).expect("Should find main.rs in extended sample");
assert_eq!(found, main_rs);
dir.close()?;
Ok(())
}
#[test]
fn test_find_main_line() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let file_path = dir.path().join("src/main.rs");
fs::create_dir_all(file_path.parent().unwrap())?;
let content = "\n\nfn helper() {}\nfn main() { println!(\"Hello\"); }\n";
fs::write(&file_path, content)?;
let pos = find_main_line(&file_path).expect("Should find fn main");
assert_eq!(pos.0, 4);
dir.close()?;
Ok(())
}
#[test]
fn test_compute_vscode_args_non_extended() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let temp_path = dir.path();
env::set_current_dir(temp_path)?;
let examples_dir = temp_path.join("examples");
fs::create_dir_all(&examples_dir)?;
let sample_file = examples_dir.join("sample_non_ext.rs");
fs::write(&sample_file, "fn main() { println!(\"non-ext\"); }")?;
let manifest_path = temp_path.join("Cargo.toml");
fs::write(
&manifest_path,
"[package]\nname = \"dummy\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
)?;
let sample = CargoTarget {
name: "sample_non_ext".to_string(),
display_name: "non-extended".to_string(),
manifest_path: manifest_path.clone(),
kind: TargetKind::Example,
toml_specified: false,
origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
extended: false,
};
let (folder_str, goto_arg) = compute_vscode_args(&sample);
assert!(folder_str.contains("examples"));
assert!(goto_arg.unwrap().contains("sample_non_ext.rs"));
Ok(())
}
#[test]
fn test_compute_vscode_args_extended_src_main() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let sample_dir = dir.path().join("extended_sample");
fs::create_dir_all(sample_dir.join("src"))?;
let main_rs = sample_dir.join("src/main.rs");
fs::write(&main_rs, "fn main() { println!(\"extended src main\"); }")?;
let manifest_path = sample_dir.join("Cargo.toml");
fs::write(
&manifest_path,
"[package]\nname = \"extended_sample\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
)?;
let sample = CargoTarget {
name: "extended_sample".to_string(),
display_name: "extended".to_string(),
manifest_path: manifest_path.clone(),
kind: TargetKind::Example,
toml_specified: false,
origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
extended: true,
};
let (folder_str, goto_arg) = compute_vscode_args(&sample);
assert!(folder_str.ends_with("src"));
let goto = goto_arg.unwrap();
assert!(goto.contains("main.rs"));
dir.close()?;
Ok(())
}
#[test]
fn test_compute_vscode_args_extended_main_rs() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let sample_dir = dir.path().join("extended_sample2");
fs::create_dir_all(&sample_dir)?;
let main_rs = sample_dir.join("main.rs");
fs::write(&main_rs, "fn main() { println!(\"extended main\"); }")?;
let manifest_path = sample_dir.join("Cargo.toml");
fs::write(
&manifest_path,
"[package]\nname = \"extended_sample2\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
)?;
let sample = CargoTarget {
name: "extended_sample2".to_string(),
display_name: "extended2".to_string(),
manifest_path: manifest_path.clone(),
kind: TargetKind::Example,
toml_specified: false,
origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
extended: true,
};
let (folder_str, goto_arg) = compute_vscode_args(&sample);
assert!(folder_str.ends_with("extended_sample2"));
let goto = goto_arg.unwrap();
assert!(goto.contains("main.rs"));
dir.close()?;
Ok(())
}
}