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