vx_cli/commands/
venv_cmd.rs

1// Virtual environment CLI commands
2
3use crate::ui::UI;
4use clap::{Args, Subcommand};
5use vx_core::{Result, VenvManager};
6
7#[derive(Args)]
8pub struct VenvArgs {
9    #[command(subcommand)]
10    pub command: VenvCommand,
11}
12
13#[derive(Subcommand, Clone)]
14pub enum VenvCommand {
15    /// Create a new virtual environment
16    Create {
17        /// Name of the virtual environment
18        name: String,
19        /// Tools to install (format: tool@version)
20        #[arg(short, long, value_delimiter = ',')]
21        tools: Vec<String>,
22    },
23    /// List all virtual environments
24    List,
25    /// Activate a virtual environment
26    Activate {
27        /// Name of the virtual environment
28        name: String,
29    },
30    /// Deactivate current virtual environment
31    Deactivate,
32    /// Remove a virtual environment
33    Remove {
34        /// Name of the virtual environment
35        name: String,
36        /// Force removal without confirmation
37        #[arg(short, long)]
38        force: bool,
39    },
40    /// Show current virtual environment
41    Current,
42}
43
44pub async fn handle(command: VenvCommand) -> Result<()> {
45    let manager = VenvManager::new()?;
46
47    match command {
48        VenvCommand::Create { name, tools } => create_venv(&manager, &name, &tools).await,
49        VenvCommand::List => list_venvs(&manager).await,
50        VenvCommand::Activate { name } => activate_venv(&manager, &name).await,
51        VenvCommand::Deactivate => deactivate_venv().await,
52        VenvCommand::Remove { name, force } => remove_venv(&manager, &name, force).await,
53        VenvCommand::Current => show_current_venv().await,
54    }
55}
56
57async fn create_venv(manager: &VenvManager, name: &str, tools: &[String]) -> Result<()> {
58    UI::info(&format!("Creating virtual environment '{name}'"));
59
60    // Parse tool specifications
61    let mut tool_specs = Vec::new();
62    for tool_spec in tools {
63        if let Some((tool, version)) = tool_spec.split_once('@') {
64            tool_specs.push((tool.to_string(), version.to_string()));
65        } else {
66            tool_specs.push((tool_spec.clone(), "latest".to_string()));
67        }
68    }
69
70    if tool_specs.is_empty() {
71        UI::warning("No tools specified. Creating empty virtual environment.");
72    } else {
73        UI::info("Tools to install:");
74        for (tool, version) in &tool_specs {
75            UI::detail(&format!("  {} @ {}", tool, version));
76        }
77    }
78
79    // Create the virtual environment using VenvManager
80    match manager.create(name, &tool_specs) {
81        Ok(()) => {
82            UI::success(&format!(
83                "Virtual environment '{}' created successfully!",
84                name
85            ));
86            UI::hint(&format!("Activate with: vx venv activate {}", name));
87        }
88        Err(e) => {
89            UI::error(&format!(
90                "Failed to create virtual environment '{}': {}",
91                name, e
92            ));
93            return Err(e);
94        }
95    }
96
97    Ok(())
98}
99
100async fn list_venvs(manager: &VenvManager) -> Result<()> {
101    UI::header("Virtual Environments");
102
103    let venvs = match manager.list() {
104        Ok(venvs) => venvs,
105        Err(e) => {
106            UI::error(&format!("Failed to list virtual environments: {}", e));
107            return Err(e);
108        }
109    };
110
111    if venvs.is_empty() {
112        UI::info("No virtual environments found.");
113        UI::hint("Create one with: vx venv create <name>");
114        return Ok(());
115    }
116
117    let current = VenvManager::current();
118
119    for venv in venvs {
120        if Some(&venv) == current.as_ref() {
121            UI::success(&format!("* {} (active)", venv));
122        } else {
123            UI::info(&format!("  {}", venv));
124        }
125    }
126
127    if let Some(current) = current {
128        UI::detail(&format!("Currently active: {}", current));
129    } else {
130        UI::hint("Activate an environment with: vx venv activate <name>");
131    }
132
133    Ok(())
134}
135
136async fn activate_venv(manager: &VenvManager, name: &str) -> Result<()> {
137    UI::info(&format!("Activating virtual environment '{}'", name));
138
139    // Check if already active
140    if VenvManager::is_active() {
141        if let Some(current) = VenvManager::current() {
142            if current == name {
143                UI::warning(&format!("Virtual environment '{}' is already active", name));
144                return Ok(());
145            } else {
146                UI::warning(&format!("Deactivating current environment '{}'", current));
147            }
148        }
149    }
150
151    // Generate activation script
152    let activation_script = match manager.activate(name) {
153        Ok(script) => script,
154        Err(e) => {
155            UI::error(&format!(
156                "Failed to activate virtual environment '{}': {}",
157                name, e
158            ));
159            return Err(e);
160        }
161    };
162
163    UI::success(&format!("Activating virtual environment '{}'", name));
164    UI::info("Run the following commands in your shell:");
165    println!();
166    println!("{}", activation_script);
167    println!();
168    UI::hint(&format!(
169        "Copy and paste the above commands, or use: eval \"$(vx venv activate {})\"",
170        name
171    ));
172
173    Ok(())
174}
175
176async fn deactivate_venv() -> Result<()> {
177    UI::info("Deactivating virtual environment");
178
179    if !VenvManager::is_active() {
180        UI::warning("No virtual environment is currently active");
181        return Ok(());
182    }
183
184    let current = VenvManager::current().unwrap();
185    let deactivation_script = VenvManager::deactivate();
186
187    UI::success(&format!("Deactivating virtual environment '{}'", current));
188    UI::info("Run the following commands in your shell:");
189    println!();
190    println!("{}", deactivation_script);
191    println!();
192    UI::hint("Copy and paste the above commands, or use: eval \"$(vx venv deactivate)\"");
193
194    Ok(())
195}
196
197async fn remove_venv(manager: &VenvManager, name: &str, force: bool) -> Result<()> {
198    UI::info(&format!("Removing virtual environment '{}'", name));
199
200    // Check if trying to remove active environment
201    if let Some(current) = VenvManager::current() {
202        if current == name {
203            UI::error("Cannot remove active virtual environment. Deactivate first.");
204            UI::hint("Run: vx venv deactivate");
205            return Ok(());
206        }
207    }
208
209    if !force {
210        UI::warning(&format!(
211            "This will permanently delete virtual environment '{}'",
212            name
213        ));
214        UI::info("Use --force to confirm removal");
215        return Ok(());
216    }
217
218    // Remove the virtual environment
219    match manager.remove(name) {
220        Ok(()) => {
221            UI::success(&format!(
222                "Virtual environment '{}' removed successfully",
223                name
224            ));
225        }
226        Err(e) => {
227            UI::error(&format!(
228                "Failed to remove virtual environment '{}': {}",
229                name, e
230            ));
231            return Err(e);
232        }
233    }
234
235    Ok(())
236}
237
238async fn show_current_venv() -> Result<()> {
239    UI::header("Current Virtual Environment");
240
241    if let Some(current) = VenvManager::current() {
242        UI::success(&format!("Current virtual environment: {}", current));
243        UI::detail("Environment is active");
244    } else {
245        UI::info("No virtual environment is currently active");
246        UI::hint("Activate one with: vx venv activate <name>");
247    }
248
249    Ok(())
250}