1use crate::ui::UI;
4use std::collections::HashMap;
5use vx_core::{config_figment::FigmentConfigManager, Result, VxError};
6
7pub async fn handle() -> Result<()> {
8 show_config().await
9}
10
11pub async fn handle_init(tools: Vec<String>, template: Option<String>) -> Result<()> {
12 let spinner = UI::new_spinner("Initializing configuration...");
13
14 let config_content = if let Some(template) = template {
15 generate_template_config(&template, &tools)?
16 } else {
17 generate_default_config(&tools)?
18 };
19
20 std::fs::write(".vx.toml", config_content)?;
21 spinner.finish_and_clear();
22
23 UI::success("Initialized .vx.toml in current directory");
24 Ok(())
25}
26
27async fn show_config() -> Result<()> {
28 let spinner = UI::new_spinner("Loading configuration...");
29
30 let config_manager = FigmentConfigManager::new()?;
31 let status = config_manager.get_status();
32
33 spinner.finish_and_clear();
34
35 UI::header("Current Configuration");
36
37 UI::info("Configuration Layers:");
39 for layer in &status.layers {
40 let status_icon = if layer.available { "✓" } else { "✗" };
41 UI::item(&format!(
42 "{} {} (priority: {})",
43 status_icon, layer.name, layer.priority
44 ));
45 }
46
47 if let Some(project_info) = &status.project_info {
49 UI::info(&format!("Project Type: {:?}", project_info.project_type));
50 UI::info(&format!(
51 "Config File: {}",
52 project_info.config_file.display()
53 ));
54
55 if !project_info.tool_versions.is_empty() {
56 UI::info("Detected Tool Versions:");
57 for (tool, version) in &project_info.tool_versions {
58 UI::item(&format!("{}: {}", tool, version));
59 }
60 }
61 }
62
63 let config = config_manager.config();
65 if !config.tools.is_empty() {
66 UI::info("Configured Tools:");
67 for (tool_name, tool_config) in &config.tools {
68 let version = tool_config.version.as_deref().unwrap_or("not specified");
69 UI::item(&format!("{}: {}", tool_name, version));
70 }
71 }
72
73 Ok(())
74}
75
76pub async fn handle_set(key: &str, value: &str) -> Result<()> {
77 set_config(key, value).await
78}
79
80pub async fn handle_get(key: &str) -> Result<()> {
81 get_config(key).await
82}
83
84async fn set_config(key: &str, value: &str) -> Result<()> {
85 let spinner = UI::new_spinner("Updating configuration...");
86
87 let parts: Vec<&str> = key.split('.').collect();
89
90 match parts.as_slice() {
91 ["tools", tool_name, "version"] => {
92 set_tool_version(tool_name, value).await?;
94 spinner.finish_and_clear();
95 UI::success(&format!("Set {} version to {}", tool_name, value));
96 }
97 ["defaults", setting] => {
98 set_global_setting(setting, value).await?;
100 spinner.finish_and_clear();
101 UI::success(&format!("Set {} to {}", setting, value));
102 }
103 _ => {
104 spinner.finish_and_clear();
105 return Err(VxError::Other {
106 message: format!(
107 "Invalid config key: {}. Use format 'tools.<tool>.version' or 'defaults.<setting>'",
108 key
109 ),
110 });
111 }
112 }
113
114 Ok(())
115}
116
117async fn get_config(key: &str) -> Result<()> {
118 let spinner = UI::new_spinner("Loading configuration...");
119
120 let config_manager = FigmentConfigManager::new()?;
121 let config = config_manager.config();
122
123 spinner.finish_and_clear();
124
125 let parts: Vec<&str> = key.split('.').collect();
127
128 match parts.as_slice() {
129 ["tools", tool_name, "version"] => {
130 if let Some(tool_config) = config.tools.get(*tool_name) {
131 let version = tool_config.version.as_deref().unwrap_or("not specified");
132 UI::info(&format!("{}: {}", key, version));
133 } else {
134 UI::warn(&format!("Tool '{}' not configured", tool_name));
135 }
136 }
137 ["defaults", setting] => match *setting {
138 "auto_install" => UI::info(&format!("{}: {}", key, config.defaults.auto_install)),
139 "check_updates" => UI::info(&format!("{}: {}", key, config.defaults.check_updates)),
140 "update_interval" => UI::info(&format!("{}: {}", key, config.defaults.update_interval)),
141 _ => {
142 UI::warn(&format!("Unknown setting: {}", setting));
143 }
144 },
145 _ => {
146 return Err(VxError::Other {
147 message: format!(
148 "Invalid config key: {}. Use format 'tools.<tool>.version' or 'defaults.<setting>'",
149 key
150 ),
151 });
152 }
153 }
154
155 Ok(())
156}
157
158pub async fn handle_reset(key: Option<String>) -> Result<()> {
159 reset_config(key).await
160}
161
162pub async fn handle_edit() -> Result<()> {
163 edit_config().await
164}
165
166async fn reset_config(key: Option<String>) -> Result<()> {
167 let spinner = UI::new_spinner("Resetting configuration...");
168
169 match key {
170 Some(key) => {
171 let parts: Vec<&str> = key.split('.').collect();
173 match parts.as_slice() {
174 ["tools", tool_name, "version"] => {
175 remove_tool_config(tool_name).await?;
176 spinner.finish_and_clear();
177 UI::success(&format!("Reset {} configuration", tool_name));
178 }
179 _ => {
180 spinner.finish_and_clear();
181 return Err(VxError::Other {
182 message: format!("Cannot reset key: {}", key),
183 });
184 }
185 }
186 }
187 None => {
188 let config_path = std::env::current_dir()
190 .map_err(|e| VxError::Other {
191 message: format!("Failed to get current directory: {}", e),
192 })?
193 .join(".vx.toml");
194
195 if config_path.exists() {
196 std::fs::remove_file(&config_path)?;
197 spinner.finish_and_clear();
198 UI::success("Reset project configuration (.vx.toml removed)");
199 } else {
200 spinner.finish_and_clear();
201 UI::info("No project configuration to reset");
202 }
203 }
204 }
205
206 Ok(())
207}
208
209async fn edit_config() -> Result<()> {
210 let config_path = std::env::current_dir()
211 .map_err(|e| VxError::Other {
212 message: format!("Failed to get current directory: {}", e),
213 })?
214 .join(".vx.toml");
215
216 if !config_path.exists() {
218 UI::info("Creating .vx.toml...");
219 generate_default_config(&[]).and_then(|content| {
220 std::fs::write(&config_path, content).map_err(|e| VxError::Other {
221 message: format!("Failed to create .vx.toml: {}", e),
222 })
223 })?;
224 }
225
226 let editor = std::env::var("EDITOR")
228 .or_else(|_| std::env::var("VISUAL"))
229 .unwrap_or_else(|_| {
230 if cfg!(windows) {
231 "notepad".to_string()
232 } else {
233 "nano".to_string()
234 }
235 });
236
237 UI::info(&format!(
238 "Opening {} with {}...",
239 config_path.display(),
240 editor
241 ));
242
243 let status = std::process::Command::new(&editor)
244 .arg(&config_path)
245 .status()
246 .map_err(|e| VxError::Other {
247 message: format!("Failed to open editor '{}': {}", editor, e),
248 })?;
249
250 if status.success() {
251 UI::success("Configuration edited successfully");
252 } else {
253 UI::warn("Editor exited with non-zero status");
254 }
255
256 Ok(())
257}
258
259async fn remove_tool_config(tool_name: &str) -> Result<()> {
260 let config_path = std::env::current_dir()
261 .map_err(|e| VxError::Other {
262 message: format!("Failed to get current directory: {}", e),
263 })?
264 .join(".vx.toml");
265
266 if !config_path.exists() {
267 return Ok(()); }
269
270 let content = std::fs::read_to_string(&config_path)?;
271 let mut project_config =
272 toml::from_str::<vx_core::venv::ProjectConfig>(&content).map_err(|e| VxError::Other {
273 message: format!("Failed to parse .vx.toml: {}", e),
274 })?;
275
276 project_config.tools.remove(tool_name);
277
278 let toml_content = toml::to_string_pretty(&project_config).map_err(|e| VxError::Other {
279 message: format!("Failed to serialize configuration: {}", e),
280 })?;
281
282 let header = "# VX Project Configuration\n# This file defines the tools and versions required for this project.\n\n";
283 let full_content = format!("{}{}", header, toml_content);
284
285 std::fs::write(&config_path, full_content).map_err(|e| VxError::Other {
286 message: format!("Failed to write .vx.toml: {}", e),
287 })?;
288
289 Ok(())
290}
291
292fn generate_default_config(tools: &[String]) -> Result<String> {
293 let mut config = String::from("# vx configuration file\n");
294 config.push_str("# This file configures tool versions for this project\n\n");
295
296 if tools.is_empty() {
297 config.push_str("[tools.uv]\nversion = \"latest\"\n\n");
298 config.push_str("[tools.node]\nversion = \"lts\"\n");
299 } else {
300 for tool in tools {
301 config.push_str(&format!("[tools.{tool}]\nversion = \"latest\"\n\n"));
302 }
303 }
304
305 Ok(config)
306}
307
308async fn set_tool_version(tool_name: &str, version: &str) -> Result<()> {
309 let config_path = std::env::current_dir()
311 .map_err(|e| VxError::Other {
312 message: format!("Failed to get current directory: {}", e),
313 })?
314 .join(".vx.toml");
315
316 let mut tools = HashMap::new();
317
318 if config_path.exists() {
320 let content = std::fs::read_to_string(&config_path)?;
321 if let Ok(project_config) = toml::from_str::<vx_core::venv::ProjectConfig>(&content) {
322 tools = project_config.tools;
323 }
324 }
325
326 tools.insert(tool_name.to_string(), version.to_string());
328
329 let project_config = vx_core::venv::ProjectConfig {
331 tools,
332 ..Default::default()
333 };
334
335 let toml_content = toml::to_string_pretty(&project_config).map_err(|e| VxError::Other {
337 message: format!("Failed to serialize configuration: {}", e),
338 })?;
339
340 let header = "# VX Project Configuration\n# This file defines the tools and versions required for this project.\n\n";
341 let full_content = format!("{}{}", header, toml_content);
342
343 std::fs::write(&config_path, full_content).map_err(|e| VxError::Other {
344 message: format!("Failed to write .vx.toml: {}", e),
345 })?;
346
347 Ok(())
348}
349
350async fn set_global_setting(setting: &str, value: &str) -> Result<()> {
351 UI::warning(&format!(
354 "Global setting '{}' = '{}' - Global config modification not yet implemented",
355 setting, value
356 ));
357 Ok(())
358}
359
360fn generate_template_config(template: &str, additional_tools: &[String]) -> Result<String> {
361 let mut config = String::from("# vx configuration file\n");
362 config.push_str(&format!("# Generated from {template} template\n\n"));
363
364 match template {
365 "node" | "javascript" | "js" => {
366 config.push_str("[tools.node]\nversion = \"lts\"\n\n");
367 config.push_str("[tools.npm]\nversion = \"latest\"\n\n");
368 }
369 "python" | "py" => {
370 config.push_str("[tools.uv]\nversion = \"latest\"\n\n");
371 config.push_str("[tools.python]\nversion = \"3.11\"\n\n");
372 }
373 "rust" => {
374 config.push_str("[tools.rust]\nversion = \"stable\"\n\n");
375 config.push_str("[tools.cargo]\nversion = \"latest\"\n\n");
376 }
377 "go" => {
378 config.push_str("[tools.go]\nversion = \"latest\"\n\n");
379 }
380 _ => {
381 return Err(VxError::Other {
382 message: format!("Unknown template: {template}"),
383 });
384 }
385 }
386
387 for tool in additional_tools {
388 config.push_str(&format!("[tools.{tool}]\nversion = \"latest\"\n\n"));
389 }
390
391 Ok(config)
392}