vtcode_core/utils/
common.rs1use crate::utils::colors::style;
7use anyhow::Result;
8use std::collections::BTreeMap;
9use std::path::Path;
10
11pub use vtcode_commons::project::{ProjectOverview, build_project_overview};
12pub use vtcode_commons::utils::{
13 current_timestamp, extract_readme_excerpt, extract_toml_str, safe_replace_text,
14};
15
16pub fn merge_env_patterns(base: &[String], env_var: &str) -> Vec<String> {
19 let extra_val = std::env::var(env_var).ok();
20 let extra_count = extra_val
21 .as_ref()
22 .map(|s| s.split(',').count())
23 .unwrap_or(0);
24
25 let mut combined = Vec::with_capacity(base.len() + extra_count);
26
27 for entry in base {
28 let trimmed = entry.trim();
29 if !trimmed.is_empty() {
30 combined.push(trimmed.to_owned());
31 }
32 }
33
34 if let Some(extra) = extra_val {
35 for item in extra.split(',') {
36 let trimmed = item.trim();
37 if !trimmed.is_empty() {
38 combined.push(trimmed.to_owned());
39 }
40 }
41 }
42
43 combined
44}
45
46const WORKSPACE_LANGUAGE_SCAN_LIMIT: usize = 5_000;
47
48pub fn render_pty_output_fn(output: &str, title: &str, command: Option<&str>) -> Result<()> {
50 use std::io::Write;
51
52 let stdout = std::io::stdout();
53 let mut handle = stdout.lock();
54
55 writeln!(handle, "{}", style("=".repeat(80)).dim())?;
56 writeln!(handle, "{} {}", style("==").bold(), style(title).bold())?;
57
58 if let Some(cmd) = command {
59 writeln!(handle, "{}", style(format!("> {}", cmd)).dim())?;
60 }
61
62 writeln!(handle, "{}", style("-".repeat(80)).dim())?;
63 write!(handle, "{}", output)?;
64 writeln!(handle, "{}", style("-".repeat(80)).dim())?;
65 writeln!(handle, "{}", style("==").bold())?;
66 writeln!(handle, "{}", style("=".repeat(80)).dim())?;
67 handle.flush()?;
68
69 Ok(())
70}
71
72pub fn summarize_workspace_languages(root: &Path) -> Option<String> {
74 let counts = collect_workspace_language_counts(root);
75 if counts.is_empty() {
76 return None;
77 }
78
79 Some(
80 counts
81 .into_iter()
82 .map(|(language, count)| format!("{language}:{count}"))
83 .collect::<Vec<_>>()
84 .join(", "),
85 )
86}
87
88pub fn detect_workspace_languages(root: &Path) -> Vec<String> {
90 let mut counts = collect_workspace_language_counts(root)
91 .into_iter()
92 .collect::<Vec<_>>();
93 counts.sort_by(|(left_lang, left_count), (right_lang, right_count)| {
94 right_count
95 .cmp(left_count)
96 .then_with(|| left_lang.cmp(right_lang))
97 });
98 counts
99 .into_iter()
100 .map(|(language, _)| language)
101 .take(5)
102 .collect()
103}
104
105pub fn display_language_from_path(path: &Path) -> Option<&'static str> {
106 let extension = path.extension()?.to_str()?;
107 display_language_from_extension(extension)
108}
109
110pub fn display_language_from_editor_language_id(language_id: &str) -> Option<&'static str> {
111 match language_id.trim().to_ascii_lowercase().as_str() {
112 "rust" => Some("Rust"),
113 "python" => Some("Python"),
114 "javascript" | "javascriptreact" => Some("JavaScript"),
115 "typescript" | "typescriptreact" => Some("TypeScript"),
116 "go" => Some("Go"),
117 "java" => Some("Java"),
118 "shellscript" | "bash" | "shell" | "zsh" | "sh" => Some("Bash"),
119 "swift" => Some("Swift"),
120 "c" => Some("C"),
121 "cpp" | "c++" => Some("C++"),
122 "ruby" => Some("Ruby"),
123 "php" => Some("PHP"),
124 _ => None,
125 }
126}
127
128fn collect_workspace_language_counts(root: &Path) -> BTreeMap<String, usize> {
129 let mut counts = BTreeMap::new();
130 let mut total = 0usize;
131
132 for entry in vtcode_commons::walk::build_walker_single_threaded(root)
133 .max_depth(Some(4))
134 .build()
135 .filter_map(|entry| entry.ok())
136 {
137 let path = entry.path();
138 if path.is_file()
139 && let Some(language) = display_language_from_path(path)
140 {
141 *counts.entry(language.to_string()).or_insert(0) += 1;
142 total += 1;
143 }
144
145 if total > WORKSPACE_LANGUAGE_SCAN_LIMIT {
146 break;
147 }
148 }
149
150 counts
151}
152
153fn display_language_from_extension(extension: &str) -> Option<&'static str> {
154 match extension {
155 "rs" => Some("Rust"),
156 "py" => Some("Python"),
157 "js" | "jsx" => Some("JavaScript"),
158 "ts" | "tsx" => Some("TypeScript"),
159 "go" => Some("Go"),
160 "java" => Some("Java"),
161 "sh" | "bash" => Some("Bash"),
162 "swift" => Some("Swift"),
163 "c" | "h" => Some("C"),
164 "cpp" | "cc" | "cxx" | "hpp" => Some("C++"),
165 "rb" => Some("Ruby"),
166 "php" => Some("PHP"),
167 _ => None,
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::{
174 detect_workspace_languages, display_language_from_editor_language_id,
175 display_language_from_path, summarize_workspace_languages,
176 };
177 use std::fs;
178 use std::path::Path;
179 use tempfile::TempDir;
180
181 #[test]
182 fn detect_workspace_languages_returns_top_languages() {
183 let workspace = TempDir::new().expect("workspace tempdir");
184 fs::create_dir_all(workspace.path().join("src")).expect("create src");
185 fs::create_dir_all(workspace.path().join("web")).expect("create web");
186 fs::write(workspace.path().join("src/lib.rs"), "fn alpha() {}\n").expect("write rust");
187 fs::write(workspace.path().join("src/main.rs"), "fn main() {}\n").expect("write rust");
188 fs::write(workspace.path().join("web/app.ts"), "const app = 1;\n").expect("write ts");
189
190 let languages = detect_workspace_languages(workspace.path());
191 assert_eq!(
192 languages,
193 vec!["Rust".to_string(), "TypeScript".to_string()]
194 );
195 }
196
197 #[test]
198 fn summarize_workspace_languages_reports_counts() {
199 let workspace = TempDir::new().expect("workspace tempdir");
200 fs::create_dir_all(workspace.path().join("src")).expect("create src");
201 fs::write(workspace.path().join("src/lib.rs"), "fn alpha() {}\n").expect("write rust");
202 fs::write(workspace.path().join("src/main.rs"), "fn main() {}\n").expect("write rust");
203
204 let summary = summarize_workspace_languages(workspace.path()).expect("summary");
205 assert_eq!(summary, "Rust:2");
206 }
207
208 #[test]
209 fn display_language_helpers_cover_paths_and_editor_language_ids() {
210 assert_eq!(
211 display_language_from_path(Path::new("src/lib.rs")),
212 Some("Rust")
213 );
214 assert_eq!(
215 display_language_from_editor_language_id("typescriptreact"),
216 Some("TypeScript")
217 );
218 assert_eq!(
219 display_language_from_editor_language_id("shellscript"),
220 Some("Bash")
221 );
222 assert_eq!(display_language_from_editor_language_id("unknown"), None);
223 }
224}