vtcode_core/tools/pty/
command_utils.rs1use std::path::Path;
2
3pub fn is_cargo_command(program: &str) -> bool {
5 let name = Path::new(program)
6 .file_name()
7 .and_then(|value| value.to_str())
8 .unwrap_or(program)
9 .to_ascii_lowercase();
10 name == "cargo"
11}
12
13pub fn is_cargo_command_string(command: &str) -> bool {
15 let trimmed = command.trim();
16 trimmed.starts_with("cargo ") || trimmed == "cargo"
17}
18
19const LOCKFILE_COMMANDS: &[&str] = &[
23 "cargo", "npm", "pnpm", "yarn", "bun", "go", "gradle", "mvn", "pip", "pip3", "poetry", "composer", "bundler", "bundle", ];
38
39fn normalize_program_name(program: &str) -> String {
40 Path::new(program)
41 .file_name()
42 .and_then(|value| value.to_str())
43 .unwrap_or(program)
44 .to_ascii_lowercase()
45}
46
47pub fn is_lockfile_command(program: &str) -> bool {
51 let name = normalize_program_name(program);
52 LOCKFILE_COMMANDS.contains(&name.as_str())
53}
54
55pub fn is_long_running_command(program: &str) -> bool {
57 let name = normalize_program_name(program);
58 is_development_toolchain_command(&name) || is_lockfile_command(&name)
59}
60
61pub fn is_lockfile_command_string(command: &str) -> bool {
63 let trimmed = command.trim();
64 LOCKFILE_COMMANDS
65 .iter()
66 .any(|&cmd| trimmed.starts_with(&format!("{cmd} ")) || trimmed == cmd)
67}
68
69pub fn is_long_running_command_string(command: &str) -> bool {
71 let trimmed = command.trim();
72 if trimmed.is_empty() {
73 return false;
74 }
75 if is_lockfile_command_string(trimmed) {
76 return true;
77 }
78 let first = trimmed.split_whitespace().next().unwrap_or_default();
79 is_development_toolchain_command(first)
80}
81
82pub(super) fn is_shell_program(program: &str) -> bool {
83 let name = Path::new(program)
84 .file_name()
85 .and_then(|value| value.to_str())
86 .unwrap_or(program)
87 .to_ascii_lowercase();
88 matches!(
89 name.as_str(),
90 "bash" | "sh" | "zsh" | "fish" | "dash" | "ash" | "busybox"
91 )
92}
93
94pub(super) fn is_sandbox_wrapper_program(program: &str, args: &[String]) -> bool {
95 let name = Path::new(program)
96 .file_name()
97 .and_then(|value| value.to_str())
98 .unwrap_or(program)
99 .to_ascii_lowercase();
100 if name == "sandbox-exec" {
101 return true;
102 }
103
104 args.iter().any(|arg| {
105 matches!(
106 arg.as_str(),
107 "--sandbox-policy" | "--sandbox-policy-cwd" | "--seccomp-profile" | "--resource-limits"
108 )
109 })
110}
111
112pub fn is_development_toolchain_command(program: &str) -> bool {
117 let name = Path::new(program)
118 .file_name()
119 .and_then(|value| value.to_str())
120 .unwrap_or(program)
121 .to_ascii_lowercase();
122 matches!(
123 name.as_str(),
124 "cargo"
125 | "rustc"
126 | "rustup"
127 | "rustfmt"
128 | "clippy"
129 | "npm"
130 | "node"
131 | "yarn"
132 | "pnpm"
133 | "bun"
134 | "go"
135 | "python"
136 | "python3"
137 | "pip"
138 | "pip3"
139 | "java"
140 | "javac"
141 | "mvn"
142 | "gradle"
143 | "make"
144 | "cmake"
145 | "gcc"
146 | "g++"
147 | "clang"
148 | "clang++"
149 | "which"
150 )
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn test_is_cargo_command() {
159 assert!(is_cargo_command("cargo"));
160 assert!(is_cargo_command("Cargo"));
161 assert!(is_cargo_command("/usr/bin/cargo"));
162 assert!(is_cargo_command("~/.cargo/bin/cargo"));
163 assert!(!is_cargo_command("rustc"));
164 assert!(!is_cargo_command("npm"));
165 assert!(!is_cargo_command("cargo-watch"));
166 }
167
168 #[test]
169 fn test_is_cargo_command_string() {
170 assert!(is_cargo_command_string("cargo check"));
171 assert!(is_cargo_command_string("cargo test --lib"));
172 assert!(is_cargo_command_string("cargo build --release"));
173 assert!(is_cargo_command_string(" cargo clippy "));
174 assert!(is_cargo_command_string("cargo"));
175 assert!(!is_cargo_command_string("rustc --version"));
176 assert!(!is_cargo_command_string("npm install"));
177 assert!(!is_cargo_command_string("echo cargo"));
178 }
179
180 #[test]
181 fn test_is_long_running_command() {
182 assert!(is_long_running_command("cargo"));
183 assert!(is_long_running_command("cmake"));
184 assert!(is_long_running_command("composer"));
185 assert!(is_long_running_command("bundle"));
186 assert!(is_long_running_command("poetry"));
187 assert!(!is_long_running_command("rg"));
188 }
189
190 #[test]
191 fn test_is_long_running_command_string() {
192 assert!(is_long_running_command_string("cargo test"));
193 assert!(is_long_running_command_string(" cmake --build ."));
194 assert!(is_long_running_command_string("composer install"));
195 assert!(is_long_running_command_string("bundle exec rake"));
196 assert!(is_long_running_command_string("poetry install"));
197 assert!(!is_long_running_command_string("rg \"fn main\" ."));
198 }
199
200 #[test]
201 fn test_is_sandbox_wrapper_program() {
202 assert!(is_sandbox_wrapper_program("sandbox-exec", &[]));
203 assert!(is_sandbox_wrapper_program(
204 "vtcode-linux-sandbox",
205 &["--sandbox-policy".to_string()]
206 ));
207 assert!(!is_sandbox_wrapper_program(
208 "bash",
209 &["-lc".to_string(), "ls".to_string()]
210 ));
211 }
212}