cargo_e/e_findmain.rs
1// src/e_findmain.rs
2
3use 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
11/// Given an Example, attempts to locate the main file.
12///
13/// For **extended samples** (i.e. sample.extended is true), it first checks for a file at:
14/// 1. `<manifest_dir>/src/main.rs`
15/// 2. `<manifest_dir>/main.rs`
16/// and if found returns that path.
17///
18/// Otherwise (or if the above do not exist), it falls back to parsing the Cargo.toml:
19/// - For binaries: it looks in the `[[bin]]` section.
20/// - For examples: it first checks the `[[example]]` section, and if not found, falls back to `[[bin]]`.
21/// If a target matching the sample name is found, it uses the provided `"path"` (if any)
22/// or defaults to `"src/main.rs"`.
23/// - Returns Some(candidate) if the file exists.
24pub fn find_main_file(sample: &CargoTarget) -> Option<PathBuf> {
25 let manifest_path = Path::new(&sample.manifest_path);
26
27 // Determine the base directory.
28 let base = if is_workspace_manifest(manifest_path) {
29 // Try to locate a workspace member manifest matching the sample name.
30 if let Some(members) = get_workspace_member_manifest_paths(manifest_path) {
31 if let Some((_, member_manifest)) = members
32 .into_iter()
33 .find(|(member_name, _)| member_name == &sample.name)
34 {
35 member_manifest.parent().map(|p| p.to_path_buf())?
36 } else {
37 // No matching member found; use the workspace manifest's parent.
38 manifest_path.parent().map(|p| p.to_path_buf())?
39 }
40 } else {
41 manifest_path.parent().map(|p| p.to_path_buf())?
42 }
43 } else {
44 manifest_path.parent()?.to_path_buf()
45 };
46
47 // Check conventional locations for extended samples.
48 let candidate_src = base.join("src").join("main.rs");
49 println!("DEBUG: candidate_src: {:?}", candidate_src);
50 if candidate_src.exists() {
51 return Some(candidate_src);
52 }
53 let candidate_main = base.join("main.rs");
54 println!("DEBUG: candidate_src: {:?}", candidate_main);
55 if candidate_main.exists() {
56 return Some(candidate_main);
57 }
58 let candidate_main = base.join(format!("{}.rs", sample.name));
59 println!("DEBUG: candidate_src: {:?}", candidate_main);
60 if candidate_main.exists() {
61 return Some(candidate_main);
62 }
63 // Check conventional location src\bin samples.
64 let candidate_src = base
65 .join("src")
66 .join("bin")
67 .join(format!("{}.rs", sample.name));
68 println!("DEBUG: candidate_src: {:?}", candidate_src);
69 if candidate_src.exists() {
70 return Some(candidate_src);
71 }
72 // If neither conventional file exists, fall through to Cargo.toml parsing.
73
74 let contents = fs::read_to_string(manifest_path).ok()?;
75 let value: Value = contents.parse().ok()?;
76 let targets = if sample.kind == TargetKind::Binary {
77 value.get("bin")
78 } else {
79 value.get("example").or_else(|| value.get("bin"))
80 }?;
81 if let Some(arr) = targets.as_array() {
82 for target in arr {
83 if let Some(name) = target.get("name").and_then(|v| v.as_str()) {
84 if name == sample.name {
85 let relative = target
86 .get("path")
87 .and_then(|v| v.as_str())
88 .unwrap_or("src/main.rs");
89 let base = manifest_path.parent()?;
90 let candidate = base.join(relative);
91 if candidate.exists() {
92 return Some(candidate);
93 }
94 }
95 }
96 }
97 }
98 None
99}
100
101/// Searches the given file for "fn main" and returns (line, column) where it is first found.
102/// Both line and column are 1-indexed.
103pub fn find_main_line(file: &Path) -> Option<(usize, usize)> {
104 let content = fs::read_to_string(file).ok()?;
105 for (i, line) in content.lines().enumerate() {
106 if let Some(col) = line.find("fn main") {
107 return Some((i + 1, col + 1));
108 }
109 }
110 None
111}
112
113/// Computes the arguments for VSCode given a sample target.
114/// Returns a tuple (folder_str, goto_arg).
115/// - `folder_str` is the folder that will be opened in VSCode.
116/// - `goto_arg` is an optional string of the form "\<file\>:\<line\>:\<column\>"
117/// determined by searching for "fn main" in the candidate file.
118///
119/// For extended samples, it checks first for "src/main.rs", then "main.rs".
120/// For non-extended examples, it assumes the file is at "examples/\<name\>.rs" relative to cwd.
121pub fn compute_vscode_args(sample: &CargoTarget) -> (String, Option<String>) {
122 let manifest_path = Path::new(&sample.manifest_path);
123 // Debug print
124 println!("DEBUG: manifest_path: {:?}", manifest_path);
125
126 let candidate_file: Option<PathBuf> = find_main_file(sample).or_else(|| {
127 if sample.kind == TargetKind::Binary
128 || (sample.kind == TargetKind::Example && sample.extended)
129 {
130 // Fallback to "src/main.rs" in the manifest's folder.
131 let base = manifest_path.parent()?;
132 let fallback = base.join("src/main.rs");
133 if fallback.exists() {
134 Some(fallback)
135 } else {
136 None
137 }
138 } else if sample.kind == TargetKind::Example && !sample.extended {
139 // For built-in examples, assume the file is "examples/<name>.rs" relative to the current directory.
140 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
141 let fallback = cwd.join("examples").join(format!("{}.rs", sample.name));
142 if fallback.exists() {
143 Some(fallback)
144 } else {
145 None
146 }
147 } else {
148 None
149 }
150 });
151
152 println!("DEBUG: candidate_file: {:?}", candidate_file);
153
154 let (folder, goto_arg) = if let Some(file) = candidate_file {
155 let folder = file.parent().unwrap_or(&file).to_path_buf();
156 let goto_arg = if let Some((line, col)) = find_main_line(&file) {
157 Some(format!(
158 "{}:{}:{}",
159 file.to_str().unwrap_or_default(),
160 line,
161 col
162 ))
163 } else {
164 Some(file.to_str().unwrap_or_default().to_string())
165 };
166 (folder, goto_arg)
167 } else {
168 (
169 manifest_path
170 .parent()
171 .unwrap_or(manifest_path)
172 .to_path_buf(),
173 None,
174 )
175 };
176
177 let folder_str = folder.to_str().unwrap_or_default().to_string();
178 println!("DEBUG: folder_str: {}", folder_str);
179 println!("DEBUG: goto_arg: {:?}", goto_arg);
180
181 (folder_str, goto_arg)
182}
183
184/// Asynchronously opens VSCode for the given sample target.
185/// It computes the VSCode arguments using `compute_vscode_args` and then launches VSCode.
186pub async fn open_vscode_for_sample(sample: &CargoTarget) {
187 let (folder_str, goto_arg) = compute_vscode_args(sample);
188
189 let output = if cfg!(target_os = "windows") {
190 if let Some(ref goto) = goto_arg {
191 Command::new("cmd")
192 .args(["/C", "code", folder_str.as_str(), "--goto", goto.as_str()])
193 .output()
194 } else {
195 Command::new("cmd")
196 .args(["/C", "code", folder_str.as_str()])
197 .output()
198 }
199 } else {
200 let mut cmd = Command::new("code");
201 cmd.arg(folder_str.as_str());
202 if let Some(goto) = goto_arg {
203 cmd.args(["--goto", goto.as_str()]);
204 }
205 cmd.output()
206 };
207
208 match output {
209 Ok(output) if output.status.success() => {
210 // VSCode opened successfully.
211 println!("DEBUG: VSCode command output: {:?}", output);
212 }
213 Ok(output) => {
214 let msg = format!(
215 "Error opening VSCode:\nstdout: {}\nstderr: {}",
216 String::from_utf8_lossy(&output.stdout),
217 String::from_utf8_lossy(&output.stderr)
218 );
219 error!("{}", msg);
220 }
221 Err(e) => {
222 let msg = format!("Failed to execute VSCode command: {}", e);
223 error!("{}", msg);
224 }
225 }
226}
227
228// /// Opens Vim for the given sample target.
229// /// It computes the file and (optionally) the line and column to jump to.
230// /// If the goto argument is in the format "file:line:column", it spawns Vim with a command to move the cursor.
231// pub fn open_vim_for_sample(sample: &Example) {
232// let (folder_str, goto_arg) = compute_vscode_args(sample);
233
234// // Determine the file to open and optionally extract line and column.
235// let (file, line, col) = if let Some(goto) = goto_arg {
236// // Split into parts and convert each to an owned String.
237// let parts: Vec<String> = goto.split(':').map(|s| s.to_string()).collect();
238// if parts.len() >= 3 {
239// (parts[0].clone(), parts[1].clone(), parts[2].clone())
240// } else {
241// (goto.clone(), "1".to_string(), "1".to_string())
242// }
243// } else {
244// (folder_str.clone(), "1".to_string(), "1".to_string())
245// };
246
247// // Prepare Vim command arguments.
248// // We use the Vim command to jump to the given line and column:
249// // +":call cursor(line, col)"
250// let cursor_cmd = format!("+call cursor({}, {})", line, col);
251// println!("nvim {}",&file);
252// // Spawn the Vim process.
253// let output = if cfg!(target_os = "windows") {
254// let args=&["/C", "nvim", &cursor_cmd, &file];
255// println!("{:?}",args);
256// // On Windows, we might need to run via cmd.
257// Command::new("cmd")
258// .args(args)
259// .output()
260// } else {
261// let args=&[&cursor_cmd, &file];
262// println!("{:?}",args);
263// Command::new("nvim")
264// .args(args)
265// .output()
266// };
267
268// match output {
269// Ok(output) if output.status.success() => {
270// println!("DEBUG: Vim opened successfully.");
271// }
272// Ok(output) => {
273// let msg = format!(
274// "Error opening Vim:\nstdout: {}\nstderr: {}",
275// String::from_utf8_lossy(&output.stdout),
276// String::from_utf8_lossy(&output.stderr)
277// );
278// error!("{}", msg);
279// }
280// Err(e) => {
281// let msg = format!("Failed to execute Vim command: {}", e);
282// error!("{}", msg);
283// }
284// }
285// }
286
287// /// Opens Emacs (via emacsclient) for the given sample target.
288// ///
289// /// It computes the file and (optionally) the line (and column) where "fn main"
290// /// is located. Emacsclient supports jumping to a line with `+<line>`, so we use that.
291// /// Note: column information is not used by default.
292// pub fn open_emacs_for_sample(sample: &Example) {
293// let (folder_str, goto_arg) = compute_vscode_args(sample);
294
295// // Parse the goto argument if available.
296// let (file, line, _col) = if let Some(goto) = goto_arg {
297// // Expect the format "file:line:column". Convert each to an owned String.
298// let parts: Vec<String> = goto.split(':').map(|s| s.to_string()).collect();
299// if parts.len() >= 3 {
300// (parts[0].clone(), parts[1].clone(), parts[2].clone())
301// } else {
302// (goto.clone(), "1".to_string(), "1".to_string())
303// }
304// } else {
305// (folder_str.clone(), "1".to_string(), "1".to_string())
306// };
307
308// // Create the line argument for emacsclient: "+<line>"
309// let line_arg = format!("+{}", line);
310
311// // Spawn emacsclient to open the file at the desired line.
312// let output = if cfg!(target_os = "windows") {
313// Command::new("cmd")
314// .args(&["/C", "emacsclient", "-n", &line_arg, &file])
315// .output()
316// } else {
317// Command::new("emacsclient")
318// .args(&["-n", &line_arg, &file])
319// .output()
320// };
321
322// match output {
323// Ok(output) if output.status.success() => {
324// println!("DEBUG: Emacs opened successfully.");
325// }
326// Ok(output) => {
327// let msg = format!(
328// "Error opening Emacs:\nstdout: {}\nstderr: {}",
329// String::from_utf8_lossy(&output.stdout),
330// String::from_utf8_lossy(&output.stderr)
331// );
332// error!("{}", msg);
333// }
334// Err(e) => {
335// let msg = format!("Failed to execute Emacs command: {}", e);
336// error!("{}", msg);
337// }
338// }
339// }
340
341#[cfg(test)]
342mod tests {
343 use crate::e_target::TargetOrigin;
344
345 use super::*;
346 use std::fs;
347 use tempfile::tempdir;
348 // use std::io::Write;
349
350 // #[test]
351 // fn test_find_main_file_default() -> Result<(), Box<dyn std::error::Error>> {
352 // // Create a temporary directory.
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
359 // // Write a Cargo.toml with a [[bin]] table without an explicit "path".
360 // let toml_contents = r#"
361 // [package]
362 // name = "dummy"
363 // version = "0.1.0"
364 // edition = "2021"
365
366 // [[bin]]
367 // name = "sample1"
368 // "#;
369 // fs::write(&manifest_path, toml_contents)?;
370
371 // let sample = Example {
372 // name: "sample1".to_string(),
373 // display_name: "dummy".to_string(),
374 // manifest_path: manifest_path.to_string_lossy().to_string(),
375 // kind: TargetKind::Binary,
376 // extended: false,
377 // };
378
379 // let found = find_main_file(&sample).expect("Should find main file");
380 // assert_eq!(found, main_rs);
381 // dir.close()?;
382 // Ok(())
383 // }
384
385 // #[test]
386 // fn test_find_main_file_with_explicit_path() -> Result<(), Box<dyn std::error::Error>> {
387 // let dir = tempdir()?;
388 // let manifest_path = dir.path().join("Cargo.toml");
389 // let custom_main = dir.path().join("custom/main.rs");
390 // fs::create_dir_all(custom_main.parent().unwrap())?;
391 // fs::write(&custom_main, "fn main() {}")?;
392
393 // let toml_contents = format!(
394 // r#"
395 // [package]
396 // name = "dummy"
397 // version = "0.1.0"
398 // edition = "2021"
399
400 // [[bin]]
401 // name = "sample2"
402 // path = "{}"
403 // "#,
404 // custom_main.strip_prefix(dir.path()).unwrap().to_str().unwrap()
405 // );
406 // fs::write(&manifest_path, toml_contents)?;
407
408 // let sample = Example {
409 // name: "sample2".to_string(),
410 // display_name: "dummy".to_string(),
411 // manifest_path: manifest_path.to_string_lossy().to_string(),
412 // kind: TargetKind::Binary,
413 // extended: false,
414 // };
415
416 // let found = find_main_file(&sample).expect("Should find custom main file");
417 // assert_eq!(found, custom_main);
418 // dir.close()?;
419 // Ok(())
420 // }
421
422 // #[test]
423 // fn test_find_main_line() -> Result<(), Box<dyn std::error::Error>> {
424 // let dir = tempdir()?;
425 // let file_path = dir.path().join("src/main.rs");
426 // fs::create_dir_all(file_path.parent().unwrap())?;
427 // let content = "\n\nfn helper() {}\nfn main() { println!(\"Hello\"); }\n";
428 // fs::write(&file_path, content)?;
429 // let pos = find_main_line(&file_path).expect("Should find fn main");
430 // assert_eq!(pos.0, 4); // Line 4 should contain fn main.
431 // dir.close()?;
432 // Ok(())
433 // }
434 // Test for a non-extended sample with no explicit path in Cargo.toml (should fallback to "src/main.rs").
435 #[test]
436 fn test_find_main_file_default() -> Result<(), Box<dyn std::error::Error>> {
437 let dir = tempdir()?;
438 let manifest_path = dir.path().join("Cargo.toml");
439 let main_rs = dir.path().join("src/main.rs");
440 fs::create_dir_all(main_rs.parent().unwrap())?;
441 fs::write(&main_rs, "fn main() {}")?;
442 let toml_contents = r#"
443 [package]
444 name = "dummy"
445 version = "0.1.0"
446 edition = "2021"
447
448 [[bin]]
449 name = "sample1"
450 "#;
451 fs::write(&manifest_path, toml_contents)?;
452 let sample = CargoTarget {
453 name: "sample1".to_string(),
454 display_name: "dummy".to_string(),
455 manifest_path: manifest_path,
456 kind: TargetKind::Binary,
457 extended: false,
458 toml_specified: false,
459 origin: Some(TargetOrigin::Named("sample1".into())),
460 };
461 let found = find_main_file(&sample).expect("Should find main file");
462 assert_eq!(found, main_rs);
463 dir.close()?;
464 Ok(())
465 }
466
467 // Test for a non-extended sample with an explicit "path" in Cargo.toml.
468 #[test]
469 fn test_find_main_file_with_explicit_path() -> Result<(), Box<dyn std::error::Error>> {
470 let dir = tempdir()?;
471 let manifest_path = dir.path().join("Cargo.toml");
472 let custom_main = dir.path().join("custom/main.rs");
473 fs::create_dir_all(custom_main.parent().unwrap())?;
474 fs::write(&custom_main, "fn main() {}")?;
475 let toml_contents = format!(
476 r#"
477 [package]
478 name = "dummy"
479 version = "0.1.0"
480 edition = "2021"
481
482 [[bin]]
483 name = "sample2"
484 path = "{}"
485 "#,
486 custom_main
487 .strip_prefix(dir.path())
488 .unwrap()
489 .to_str()
490 .unwrap()
491 );
492 fs::write(&manifest_path, toml_contents)?;
493 let sample = CargoTarget {
494 name: "sample2".to_string(),
495 display_name: "dummy".to_string(),
496 manifest_path: manifest_path,
497 kind: TargetKind::Binary,
498 origin: Some(TargetOrigin::Named("sample2".into())),
499 toml_specified: false,
500 extended: false,
501 };
502 let found = find_main_file(&sample).expect("Should find custom main file");
503 assert_eq!(found, custom_main);
504 dir.close()?;
505 Ok(())
506 }
507
508 // Test for an extended sample where "src/main.rs" exists.
509 #[test]
510 fn test_extended_sample_src_main() -> Result<(), Box<dyn std::error::Error>> {
511 let dir = tempdir()?;
512 // Simulate an extended sample folder (e.g. "examples/sample_ext")
513 let sample_dir = dir.path().join("examples").join("sample_ext");
514 fs::create_dir_all(sample_dir.join("src"))?;
515 let main_rs = sample_dir.join("src/main.rs");
516 fs::write(&main_rs, "fn main() {}")?;
517 // Write a Cargo.toml in the sample folder.
518 let manifest_path = sample_dir.join("Cargo.toml");
519 let toml_contents = r#"
520 [package]
521 name = "sample_ext"
522 version = "0.1.0"
523 edition = "2021"
524 "#;
525 fs::write(&manifest_path, toml_contents)?;
526
527 let sample = CargoTarget {
528 name: "sample_ext".to_string(),
529 display_name: "extended sample".to_string(),
530 manifest_path: manifest_path.clone(),
531 kind: TargetKind::Example,
532 origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
533 toml_specified: false,
534 extended: true,
535 };
536
537 // For extended samples, our function should find "src/main.rs" first.
538 let found = find_main_file(&sample).expect("Should find src/main.rs in extended sample");
539 assert_eq!(found, main_rs);
540 dir.close()?;
541 Ok(())
542 }
543
544 // Test for an extended sample where "src/main.rs" does not exist but "main.rs" exists.
545 #[test]
546 fn test_extended_sample_main_rs() -> Result<(), Box<dyn std::error::Error>> {
547 let dir = tempdir()?;
548 let sample_dir = dir.path().join("examples").join("sample_ext2");
549 fs::create_dir_all(&sample_dir)?;
550 let main_rs = sample_dir.join("main.rs");
551 fs::write(&main_rs, "fn main() {}")?;
552 let manifest_path = sample_dir.join("Cargo.toml");
553 let toml_contents = r#"
554 [package]
555 name = "sample_ext2"
556 version = "0.1.0"
557 edition = "2021"
558 "#;
559 fs::write(&manifest_path, toml_contents)?;
560 let sample = CargoTarget {
561 name: "sample_ext2".to_string(),
562 display_name: "extended sample 2".to_string(),
563 manifest_path: manifest_path.clone(),
564 kind: TargetKind::Example,
565 origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
566 toml_specified: false,
567 extended: true,
568 };
569 let found = find_main_file(&sample).expect("Should find main.rs in extended sample");
570 assert_eq!(found, main_rs);
571 dir.close()?;
572 Ok(())
573 }
574
575 // Test for find_main_line: it should locate "fn main" and return the correct (line, column).
576 #[test]
577 fn test_find_main_line() -> Result<(), Box<dyn std::error::Error>> {
578 let dir = tempdir()?;
579 let file_path = dir.path().join("src/main.rs");
580 fs::create_dir_all(file_path.parent().unwrap())?;
581 // Create a file with some lines and a line with "fn main"
582 let content = "\n\nfn helper() {}\nfn main() { println!(\"Hello\"); }\n";
583 fs::write(&file_path, content)?;
584 let pos = find_main_line(&file_path).expect("Should find fn main");
585 // "fn main" should appear on line 4 (1-indexed)
586 assert_eq!(pos.0, 4);
587 dir.close()?;
588 Ok(())
589 }
590
591 #[test]
592 fn test_compute_vscode_args_non_extended() -> Result<(), Box<dyn std::error::Error>> {
593 // Create a temporary directory and change the current working directory to it.
594 let dir = tempdir()?;
595 let temp_path = dir.path();
596 env::set_current_dir(temp_path)?;
597
598 // Create the examples directory and a dummy example file.
599 let examples_dir = temp_path.join("examples");
600 fs::create_dir_all(&examples_dir)?;
601 let sample_file = examples_dir.join("sample_non_ext.rs");
602 fs::write(&sample_file, "fn main() { println!(\"non-ext\"); }")?;
603
604 // Create a dummy Cargo.toml in the temporary directory.
605 let manifest_path = temp_path.join("Cargo.toml");
606 fs::write(
607 &manifest_path,
608 "[package]\nname = \"dummy\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
609 )?;
610
611 // Construct the sample object using the temp folder's Cargo.toml path.
612 let sample = CargoTarget {
613 name: "sample_non_ext".to_string(),
614 display_name: "non-extended".to_string(),
615 manifest_path: manifest_path.clone(),
616 kind: TargetKind::Example,
617 toml_specified: false,
618 origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
619 extended: false,
620 };
621
622 let (folder_str, goto_arg) = compute_vscode_args(&sample);
623 // In this case, we expect folder_str to contain "examples" and goto_arg to refer to sample_non_ext.rs.
624 assert!(folder_str.contains("examples"));
625 assert!(goto_arg.unwrap().contains("sample_non_ext.rs"));
626
627 // Cleanup is not required because the tempdir will be dropped,
628 // which deletes all files inside the temporary directory.
629 Ok(())
630 }
631
632 #[test]
633 fn test_compute_vscode_args_extended_src_main() -> Result<(), Box<dyn std::error::Error>> {
634 // Simulate an extended sample where Cargo.toml is in the sample folder and "src/main.rs" exists.
635 let dir = tempdir()?;
636 let sample_dir = dir.path().join("extended_sample");
637 fs::create_dir_all(sample_dir.join("src"))?;
638 let main_rs = sample_dir.join("src/main.rs");
639 fs::write(&main_rs, "fn main() { println!(\"extended src main\"); }")?;
640 let manifest_path = sample_dir.join("Cargo.toml");
641 fs::write(
642 &manifest_path,
643 "[package]\nname = \"extended_sample\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
644 )?;
645
646 let sample = CargoTarget {
647 name: "extended_sample".to_string(),
648 display_name: "extended".to_string(),
649 manifest_path: manifest_path.clone(),
650 kind: TargetKind::Example,
651 toml_specified: false,
652 origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
653 extended: true,
654 };
655
656 let (folder_str, goto_arg) = compute_vscode_args(&sample);
657 // The folder should be sample_dir/src since that's where main.rs is.
658 assert!(folder_str.ends_with("src"));
659 let goto = goto_arg.unwrap();
660 // The goto argument should contain main.rs.
661 assert!(goto.contains("main.rs"));
662 dir.close()?;
663 Ok(())
664 }
665
666 #[test]
667 fn test_compute_vscode_args_extended_main_rs() -> Result<(), Box<dyn std::error::Error>> {
668 // Simulate an extended sample where "src/main.rs" does not exist, but "main.rs" exists.
669 let dir = tempdir()?;
670 let sample_dir = dir.path().join("extended_sample2");
671 fs::create_dir_all(&sample_dir)?;
672 let main_rs = sample_dir.join("main.rs");
673 fs::write(&main_rs, "fn main() { println!(\"extended main\"); }")?;
674 let manifest_path = sample_dir.join("Cargo.toml");
675 fs::write(
676 &manifest_path,
677 "[package]\nname = \"extended_sample2\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
678 )?;
679
680 let sample = CargoTarget {
681 name: "extended_sample2".to_string(),
682 display_name: "extended2".to_string(),
683 manifest_path: manifest_path.clone(),
684 kind: TargetKind::Example,
685 toml_specified: false,
686 origin: Some(TargetOrigin::SubProject(manifest_path.to_path_buf())),
687 extended: true,
688 };
689
690 let (folder_str, goto_arg) = compute_vscode_args(&sample);
691 // The folder should be the sample_dir (since main.rs is directly in it).
692 assert!(folder_str.ends_with("extended_sample2"));
693 let goto = goto_arg.unwrap();
694 assert!(goto.contains("main.rs"));
695 dir.close()?;
696 Ok(())
697 }
698}