ib_shell_verb/
workspace.rs1use std::{
2 path::{Path, PathBuf},
3 process::{Command, Stdio},
4 sync::OnceLock,
5};
6
7use anyhow::Context;
8use bon::Builder;
9
10use crate::OpenVerb;
11
12#[derive(Builder)]
13pub struct OpenFileInWorkspace {
14 parent_as_workspace: bool,
15 vscode: Option<OnceLock<PathBuf>>,
16}
17
18impl OpenFileInWorkspace {
19 fn is_cargo_workspace(dir: &Path) -> bool {
20 dir.join("Cargo.lock").exists()
21 }
22
23 fn is_git_repo(dir: &Path) -> bool {
24 dir.join(".git").exists()
25 }
26
27 #[cfg(test)]
28 fn find_git_repo(p: &Path) -> Option<&Path> {
29 for p in p.ancestors() {
30 if Self::is_git_repo(p) {
31 return Some(p);
32 }
33 }
34 None
35 }
36
37 fn find_workspace<'p>(&self, p: &'p Path) -> Option<&'p Path> {
38 for p in p.ancestors() {
39 if Self::is_cargo_workspace(p) {
40 return Some(p);
41 }
42 if Self::is_git_repo(p) {
43 return Some(p);
44 }
45 }
46 if self.parent_as_workspace {
47 return p.parent();
48 }
49 None
50 }
51
52 fn find_vscode() -> PathBuf {
53 if let Ok(p) = which::which_global("code") {
54 if p.file_name().is_some_and(|name| name == "code.cmd") {
70 let exe = p
71 .parent()
72 .and_then(Path::parent)
73 .map(|p| p.join("Code.exe"));
74 if let Some(exe) = exe {
75 return exe;
76 }
77 }
78 return p;
79 }
80 "code".into()
81 }
82}
83
84impl OpenVerb for OpenFileInWorkspace {
85 fn handle(&self, path: &Path) -> Option<Result<(), anyhow::Error>> {
86 let workspace = self.find_workspace(path)?;
87 if let Some(ref vscode) = self.vscode {
88 let vscode = vscode.get_or_init(Self::find_vscode);
89 let r = Command::new(vscode)
90 .arg("-n")
91 .arg(workspace)
92 .arg("-g")
93 .arg(path)
94 .stdin(Stdio::null())
95 .stdout(Stdio::null())
96 .stderr(Stdio::null())
97 .spawn()
98 .map(|_| ());
99 return Some(r.context("vscode"));
100 }
101 None
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn find_git_repo_none() {
111 let manifest = std::env::var("CARGO_MANIFEST_PATH").unwrap();
113 let manifest_dir = Path::new(&manifest).parent().unwrap();
114
115 for ancestor in manifest_dir.ancestors() {
118 let git_path = ancestor.join(".git");
119 if git_path.exists() {
120 let result = OpenFileInWorkspace::find_git_repo(ancestor.parent().unwrap());
122 assert!(result.is_none(), "Expected none for non-git directory");
123 return;
124 }
125 }
126 let result = OpenFileInWorkspace::find_git_repo(manifest_dir);
128 assert!(result.is_none(), "Expected none for non-git directory");
129 }
130
131 #[test]
132 fn find_git_repo_some() {
133 let manifest = std::env::var("CARGO_MANIFEST_PATH").unwrap();
134 let manifest_dir = Path::new(&manifest).parent().unwrap();
135
136 for ancestor in manifest_dir.ancestors() {
138 let git_path = ancestor.join(".git");
139 if git_path.exists() {
140 let deep_path = ancestor.join("src").join("workspace.rs");
142 let result = OpenFileInWorkspace::find_git_repo(deep_path.parent().unwrap());
143 assert_eq!(
144 result.map(|p| p.to_path_buf()),
145 Some(ancestor.to_path_buf())
146 );
147 return;
148 }
149 }
150 }
151
152 #[test]
153 fn find_vscode() {
154 let p = OpenFileInWorkspace::find_vscode();
155 dbg!(p);
156 }
157}