1use crate::{
4 e_target::{CargoTarget, TargetKind},
5 prelude::*,
6};
7use toml::Value;
8
9use crate::e_workspace::{get_workspace_member_manifest_paths, is_workspace_manifest};
10
11pub fn find_main_file(sample: &CargoTarget) -> Option<PathBuf> {
25 let manifest_path = Path::new(&sample.manifest_path);
26
27 let base = if is_workspace_manifest(manifest_path) {
29 match get_workspace_member_manifest_paths(manifest_path) {
31 Some(members) => {
32 if let Some((_, member_manifest)) = members
33 .into_iter()
34 .find(|(member_name, _)| member_name == &sample.name)
35 {
36 member_manifest.parent().map(|p| p.to_path_buf())?
37 } else {
38 manifest_path.parent().map(|p| p.to_path_buf())?
40 }
41 }
42 None => manifest_path.parent().map(|p| p.to_path_buf())?,
43 }
44 } else {
45 manifest_path.parent()?.to_path_buf()
46 };
47
48 let candidate_src = base.join("src").join("main.rs");
50 println!("DEBUG: candidate_src: {:?}", candidate_src);
51 if candidate_src.exists() {
52 return Some(candidate_src);
53 }
54 let candidate_main = base.join("main.rs");
55 println!("DEBUG: candidate_src: {:?}", candidate_main);
56 if candidate_main.exists() {
57 return Some(candidate_main);
58 }
59 let candidate_main = base.join(format!("{}.rs", sample.name));
60 println!("DEBUG: candidate_src: {:?}", candidate_main);
61 if candidate_main.exists() {
62 return Some(candidate_main);
63 }
64 let candidate_src = base
66 .join("src")
67 .join("bin")
68 .join(format!("{}.rs", sample.name));
69 println!("DEBUG: candidate_src: {:?}", candidate_src);
70 if candidate_src.exists() {
71 return Some(candidate_src);
72 }
73 let contents = fs::read_to_string(manifest_path).ok()?;
76 let value: Value = contents.parse().ok()?;
77 let targets = if sample.kind == TargetKind::Binary {
78 value.get("bin")
79 } else {
80 value.get("example").or_else(|| value.get("bin"))
81 }?;
82 if let Some(arr) = targets.as_array() {
83 for target in arr {
84 if let Some(name) = target.get("name").and_then(|v| v.as_str()) {
85 if name == sample.name {
86 let relative = target
87 .get("path")
88 .and_then(|v| v.as_str())
89 .unwrap_or("src/main.rs");
90 let base = manifest_path.parent()?;
91 let candidate = base.join(relative);
92 if candidate.exists() {
93 return Some(candidate);
94 }
95 }
96 }
97 }
98 }
99 None
100}
101
102pub fn find_main_line(file: &Path) -> Option<(usize, usize)> {
105 let content = fs::read_to_string(file).ok()?;
106 for (i, line) in content.lines().enumerate() {
107 if let Some(col) = line.find("fn main") {
108 return Some((i + 1, col + 1));
109 }
110 }
111 None
112}
113
114pub fn compute_vscode_args(sample: &CargoTarget) -> (String, Option<String>) {
123 let manifest_path = Path::new(&sample.manifest_path);
124 println!("DEBUG: manifest_path: {:?}", manifest_path);
126
127 let candidate_file: Option<PathBuf> = find_main_file(sample).or_else(|| {
128 if sample.kind == TargetKind::Binary
129 || (sample.kind == TargetKind::Example && sample.extended)
130 {
131 let base = manifest_path.parent()?;
133 let fallback = base.join("src/main.rs");
134 if fallback.exists() {
135 Some(fallback)
136 } else {
137 None
138 }
139 } else if sample.kind == TargetKind::Example && !sample.extended {
140 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
142 let fallback = cwd.join("examples").join(format!("{}.rs", sample.name));
143 if fallback.exists() {
144 Some(fallback)
145 } else {
146 None
147 }
148 } else {
149 None
150 }
151 });
152
153 println!("DEBUG: candidate_file: {:?}", candidate_file);
154
155 let (folder, goto_arg) = if let Some(file) = candidate_file {
156 let folder = file.parent().unwrap_or(&file).to_path_buf();
157 let goto_arg = if let Some((line, col)) = find_main_line(&file) {
158 Some(format!(
159 "{}:{}:{}",
160 file.to_str().unwrap_or_default(),
161 line,
162 col
163 ))
164 } else {
165 Some(file.to_str().unwrap_or_default().to_string())
166 };
167 (folder, goto_arg)
168 } else {
169 (
170 manifest_path
171 .parent()
172 .unwrap_or(manifest_path)
173 .to_path_buf(),
174 None,
175 )
176 };
177
178 let folder_str = folder.to_str().unwrap_or_default().to_string();
179 println!("DEBUG: folder_str: {}", folder_str);
180 println!("DEBUG: goto_arg: {:?}", goto_arg);
181
182 (folder_str, goto_arg)
183}
184
185pub async fn open_vscode_for_sample(sample: &CargoTarget) {
188 let (folder_str, goto_arg) = compute_vscode_args(sample);
189
190 let output = if cfg!(target_os = "windows") {
191 if let Some(ref goto) = goto_arg {
192 Command::new("cmd")
193 .args(["/C", "code", folder_str.as_str(), "--goto", goto.as_str()])
194 .output()
195 } else {
196 Command::new("cmd")
197 .args(["/C", "code", folder_str.as_str()])
198 .output()
199 }
200 } else {
201 let mut cmd = Command::new("code");
202 cmd.arg(folder_str.as_str());
203 if let Some(goto) = goto_arg {
204 cmd.args(["--goto", goto.as_str()]);
205 }
206 cmd.output()
207 };
208
209 match output {
210 Ok(output) if output.status.success() => {
211 println!("DEBUG: VSCode command output: {:?}", output);
213 }
214 Ok(output) => {
215 let msg = format!(
216 "Error opening VSCode:\nstdout: {}\nstderr: {}",
217 String::from_utf8_lossy(&output.stdout),
218 String::from_utf8_lossy(&output.stderr)
219 );
220 error!("{}", msg);
221 }
222 Err(e) => {
223 let msg = format!("Failed to execute VSCode command: {}", e);
224 error!("{}", msg);
225 }
226 }
227}
228
229#[cfg(test)]
343mod tests {
344 use crate::e_target::TargetOrigin;
345
346 use super::*;
347 use std::fs;
348 use tempfile::tempdir;
349
350 #[test]
352 fn test_find_main_file_default() -> Result<(), Box<dyn std::error::Error>> {
353 let dir = tempdir()?;
354 let manifest_path = dir.path().join("Cargo.toml");
355 let main_rs = dir.path().join("src/main.rs");
356 fs::create_dir_all(main_rs.parent().unwrap())?;
357 fs::write(&main_rs, "fn main() {}")?;
358 let toml_contents = r#"
359 [package]
360 name = "dummy"
361 version = "0.1.0"
362 edition = "2021"
363
364 [[bin]]
365 name = "sample1"
366 "#;
367 fs::write(&manifest_path, toml_contents)?;
368 let sample = CargoTarget {
369 name: "sample1".to_string(),
370 display_name: "dummy".to_string(),
371 manifest_path,
372 kind: TargetKind::Binary,
373 extended: false,
374 toml_specified: false,
375 origin: Some(TargetOrigin::Named("sample1".into())),
376 };
377 let found = find_main_file(&sample).expect("Should find main file");
378 assert_eq!(found, main_rs);
379 dir.close()?;
380 Ok(())
381 }
382
383 #[test]
385 fn test_find_main_file_with_explicit_path() -> Result<(), Box<dyn std::error::Error>> {
386 let dir = tempdir()?;
387 let manifest_path = dir.path().join("Cargo.toml");
388 let custom_main = dir.path().join("src/main.rs");
389 fs::create_dir_all(custom_main.parent().unwrap())?;
390 fs::write(&custom_main, "fn main() {}")?;
391 let toml_contents = format!(
392 r#"
393 [package]
394 name = "dummy"
395 version = "0.1.0"
396 edition = "2021"
397
398 [[bin]]
399 name = "sample2"
400 path = "{}"
401 "#,
402 custom_main
403 .strip_prefix(dir.path())
404 .unwrap()
405 .to_str()
406 .unwrap()
407 );
408 fs::write(&manifest_path, toml_contents)?;
409 let sample = CargoTarget {
410 name: "sample2".to_string(),
411 display_name: "dummy".to_string(),
412 manifest_path,
413 kind: TargetKind::Binary,
414 origin: Some(TargetOrigin::Named("sample2".into())),
415 toml_specified: false,
416 extended: false,
417 };
418 let found = find_main_file(&sample).expect("Should find custom main file");
419 assert_eq!(found, custom_main);
420 dir.close()?;
421 Ok(())
422 }
423
424 #[test]
426 fn test_extended_sample_src_main() -> Result<(), Box<dyn std::error::Error>> {
427 let dir = tempdir()?;
428 let sample_dir = dir.path().join("examples").join("sample_ext");
430 fs::create_dir_all(sample_dir.join("src"))?;
431 let main_rs = sample_dir.join("src/main.rs");
432 fs::write(&main_rs, "fn main() {}")?;
433 let manifest_path = sample_dir.join("Cargo.toml");
435 let toml_contents = r#"
436 [package]
437 name = "sample_ext"
438 version = "0.1.0"
439 edition = "2021"
440 "#;
441 fs::write(&manifest_path, toml_contents)?;
442
443 let sample = CargoTarget {
444 name: "sample_ext".to_string(),
445 display_name: "extended sample".to_string(),
446 manifest_path: manifest_path.clone(),
447 kind: TargetKind::Example,
448 origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
449 toml_specified: false,
450 extended: true,
451 };
452
453 let found = find_main_file(&sample).expect("Should find src/main.rs in extended sample");
455 assert_eq!(found, main_rs);
456 dir.close()?;
457 Ok(())
458 }
459
460 #[test]
462 fn test_extended_sample_main_rs() -> Result<(), Box<dyn std::error::Error>> {
463 let dir = tempdir()?;
464 let sample_dir = dir.path().join("examples").join("sample_ext2");
465 fs::create_dir_all(&sample_dir)?;
466 let main_rs = sample_dir.join("main.rs");
467 fs::write(&main_rs, "fn main() {}")?;
468 let manifest_path = sample_dir.join("Cargo.toml");
469 let toml_contents = r#"
470 [package]
471 name = "sample_ext2"
472 version = "0.1.0"
473 edition = "2021"
474 "#;
475 fs::write(&manifest_path, toml_contents)?;
476 let sample = CargoTarget {
477 name: "sample_ext2".to_string(),
478 display_name: "extended sample 2".to_string(),
479 manifest_path: manifest_path.clone(),
480 kind: TargetKind::Example,
481 origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
482 toml_specified: false,
483 extended: true,
484 };
485 let found = find_main_file(&sample).expect("Should find main.rs in extended sample");
486 assert_eq!(found, main_rs);
487 dir.close()?;
488 Ok(())
489 }
490
491 #[test]
493 fn test_find_main_line() -> Result<(), Box<dyn std::error::Error>> {
494 let dir = tempdir()?;
495 let file_path = dir.path().join("src/main.rs");
496 fs::create_dir_all(file_path.parent().unwrap())?;
497 let content = "\n\nfn helper() {}\nfn main() { println!(\"Hello\"); }\n";
499 fs::write(&file_path, content)?;
500 let pos = find_main_line(&file_path).expect("Should find fn main");
501 assert_eq!(pos.0, 4);
503 dir.close()?;
504 Ok(())
505 }
506
507 #[test]
508 fn test_compute_vscode_args_non_extended() -> Result<(), Box<dyn std::error::Error>> {
509 let dir = tempdir()?;
511 let temp_path = dir.path();
512 env::set_current_dir(temp_path)?;
513
514 let examples_dir = temp_path.join("examples");
516 fs::create_dir_all(&examples_dir)?;
517 let sample_file = examples_dir.join("sample_non_ext.rs");
518 fs::write(&sample_file, "fn main() { println!(\"non-ext\"); }")?;
519
520 let manifest_path = temp_path.join("Cargo.toml");
522 fs::write(
523 &manifest_path,
524 "[package]\nname = \"dummy\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
525 )?;
526
527 let sample = CargoTarget {
529 name: "sample_non_ext".to_string(),
530 display_name: "non-extended".to_string(),
531 manifest_path: manifest_path.clone(),
532 kind: TargetKind::Example,
533 toml_specified: false,
534 origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
535 extended: false,
536 };
537
538 let (folder_str, goto_arg) = compute_vscode_args(&sample);
539 assert!(folder_str.contains("examples"));
541 assert!(goto_arg.unwrap().contains("sample_non_ext.rs"));
542
543 Ok(())
546 }
547
548 #[test]
549 fn test_compute_vscode_args_extended_src_main() -> Result<(), Box<dyn std::error::Error>> {
550 let dir = tempdir()?;
552 let sample_dir = dir.path().join("extended_sample");
553 fs::create_dir_all(sample_dir.join("src"))?;
554 let main_rs = sample_dir.join("src/main.rs");
555 fs::write(&main_rs, "fn main() { println!(\"extended src main\"); }")?;
556 let manifest_path = sample_dir.join("Cargo.toml");
557 fs::write(
558 &manifest_path,
559 "[package]\nname = \"extended_sample\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
560 )?;
561
562 let sample = CargoTarget {
563 name: "extended_sample".to_string(),
564 display_name: "extended".to_string(),
565 manifest_path: manifest_path.clone(),
566 kind: TargetKind::Example,
567 toml_specified: false,
568 origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
569 extended: true,
570 };
571
572 let (folder_str, goto_arg) = compute_vscode_args(&sample);
573 assert!(folder_str.ends_with("src"));
575 let goto = goto_arg.unwrap();
576 assert!(goto.contains("main.rs"));
578 dir.close()?;
579 Ok(())
580 }
581
582 #[test]
583 fn test_compute_vscode_args_extended_main_rs() -> Result<(), Box<dyn std::error::Error>> {
584 let dir = tempdir()?;
586 let sample_dir = dir.path().join("extended_sample2");
587 fs::create_dir_all(&sample_dir)?;
588 let main_rs = sample_dir.join("main.rs");
589 fs::write(&main_rs, "fn main() { println!(\"extended main\"); }")?;
590 let manifest_path = sample_dir.join("Cargo.toml");
591 fs::write(
592 &manifest_path,
593 "[package]\nname = \"extended_sample2\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
594 )?;
595
596 let sample = CargoTarget {
597 name: "extended_sample2".to_string(),
598 display_name: "extended2".to_string(),
599 manifest_path: manifest_path.clone(),
600 kind: TargetKind::Example,
601 toml_specified: false,
602 origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
603 extended: true,
604 };
605
606 let (folder_str, goto_arg) = compute_vscode_args(&sample);
607 assert!(folder_str.ends_with("extended_sample2"));
609 let goto = goto_arg.unwrap();
610 assert!(goto.contains("main.rs"));
611 dir.close()?;
612 Ok(())
613 }
614}