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