From fa534aeabee082360cf0b2ceb3d01d8e2c37e9ac Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 23 Jan 2026 14:03:13 -0700 Subject: [PATCH] Don't try to spawn conda if it's not there (#47261) Closes #ISSUE Release Notes: - Fixed conda showing up in terminal windows --- crates/language/src/toolchain.rs | 8 +- crates/languages/src/python.rs | 181 +++++++++++++++------------- crates/project/src/project_tests.rs | 9 +- crates/project/src/terminals.rs | 12 +- 4 files changed, 120 insertions(+), 90 deletions(-) diff --git a/crates/language/src/toolchain.rs b/crates/language/src/toolchain.rs index 815ece30a1ed46ae65ec4af2ba64501ff3489718..0d80f84e7ec1dc330db823a0938421a1f5ad85c9 100644 --- a/crates/language/src/toolchain.rs +++ b/crates/language/src/toolchain.rs @@ -12,6 +12,7 @@ use std::{ use async_trait::async_trait; use collections::HashMap; use fs::Fs; +use futures::future::BoxFuture; use gpui::{App, AsyncApp, SharedString}; use settings::WorktreeId; use task::ShellKind; @@ -113,7 +114,12 @@ pub trait ToolchainLister: Send + Sync + 'static { fs: &dyn Fs, ) -> anyhow::Result; - fn activation_script(&self, toolchain: &Toolchain, shell: ShellKind, cx: &App) -> Vec; + fn activation_script( + &self, + toolchain: &Toolchain, + shell: ShellKind, + cx: &App, + ) -> BoxFuture<'static, Vec>; /// Returns various "static" bits of information about this toolchain lister. This function should be pure. fn meta(&self) -> ToolchainMetadata; diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index d3b2dab028f627d7797feb6f710aebc83a1996d4..0efaa3f0964a28711ccb3e55a3c75825cd3a0b83 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -2,6 +2,7 @@ use anyhow::{Context as _, ensure}; use anyhow::{Result, anyhow}; use async_trait::async_trait; use collections::HashMap; +use futures::future::BoxFuture; use futures::lock::OwnedMutexGuard; use futures::{AsyncBufReadExt, StreamExt as _}; use gpui::{App, AsyncApp, SharedString, Task}; @@ -24,11 +25,13 @@ use semver::Version; use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; use settings::Settings; +use terminal::terminal_settings::TerminalSettings; + use smol::lock::OnceCell; use std::cmp::{Ordering, Reverse}; use std::env::consts; use std::process::Stdio; -use terminal::terminal_settings::TerminalSettings; + use util::command::new_smol_command; use util::fs::{make_file_executable, remove_matching}; use util::paths::PathStyle; @@ -1333,94 +1336,110 @@ impl ToolchainLister for PythonToolchainProvider { .context("Could not convert a venv into a toolchain") } - fn activation_script(&self, toolchain: &Toolchain, shell: ShellKind, cx: &App) -> Vec { - let Ok(toolchain) = - serde_json::from_value::(toolchain.as_json.clone()) - else { - return vec![]; - }; + fn activation_script( + &self, + toolchain: &Toolchain, + shell: ShellKind, + cx: &App, + ) -> BoxFuture<'static, Vec> { + let settings = TerminalSettings::get_global(cx); + let conda_manager = settings + .detect_venv + .as_option() + .map(|venv| venv.conda_manager) + .unwrap_or(settings::CondaManager::Auto); + + let toolchain_clone = toolchain.clone(); + Box::pin(async move { + let Ok(toolchain) = + serde_json::from_value::(toolchain_clone.as_json.clone()) + else { + return vec![]; + }; - log::debug!("(Python) Composing activation script for toolchain {toolchain:?}"); - - let mut activation_script = vec![]; - - match toolchain.environment.kind { - Some(PythonEnvironmentKind::Conda) => { - let settings = TerminalSettings::get_global(cx); - let conda_manager = settings - .detect_venv - .as_option() - .map(|venv| venv.conda_manager) - .unwrap_or(settings::CondaManager::Auto); - let manager = match conda_manager { - settings::CondaManager::Conda => "conda", - settings::CondaManager::Mamba => "mamba", - settings::CondaManager::Micromamba => "micromamba", - settings::CondaManager::Auto => toolchain - .environment - .manager - .as_ref() - .and_then(|m| m.executable.file_name()) - .and_then(|name| name.to_str()) - .filter(|name| matches!(*name, "conda" | "mamba" | "micromamba")) - .unwrap_or("conda"), - }; + log::debug!("(Python) Composing activation script for toolchain {toolchain:?}"); - // Activate micromamba shell in the child shell - // [required for micromamba] - if manager == "micromamba" { - let shell = micromamba_shell_name(shell); - activation_script - .push(format!(r#"eval "$({manager} shell hook --shell {shell})""#)); - } + let mut activation_script = vec![]; - if let Some(name) = &toolchain.environment.name { - activation_script.push(format!("{manager} activate {name}")); - } else { - activation_script.push(format!("{manager} activate base")); + match toolchain.environment.kind { + Some(PythonEnvironmentKind::Conda) => { + let Some(manager_info) = &toolchain.environment.manager else { + return vec![]; + }; + if smol::fs::metadata(&manager_info.executable).await.is_err() { + return vec![]; + } + + let manager = match conda_manager { + settings::CondaManager::Conda => "conda", + settings::CondaManager::Mamba => "mamba", + settings::CondaManager::Micromamba => "micromamba", + settings::CondaManager::Auto => toolchain + .environment + .manager + .as_ref() + .and_then(|m| m.executable.file_name()) + .and_then(|name| name.to_str()) + .filter(|name| matches!(*name, "conda" | "mamba" | "micromamba")) + .unwrap_or("conda"), + }; + + // Activate micromamba shell in the child shell + // [required for micromamba] + if manager == "micromamba" { + let shell = micromamba_shell_name(shell); + activation_script + .push(format!(r#"eval "$({manager} shell hook --shell {shell})""#)); + } + + if let Some(name) = &toolchain.environment.name { + activation_script.push(format!("{manager} activate {name}")); + } else { + activation_script.push(format!("{manager} activate base")); + } } - } - Some( - PythonEnvironmentKind::Venv - | PythonEnvironmentKind::VirtualEnv - | PythonEnvironmentKind::Uv - | PythonEnvironmentKind::UvWorkspace - | PythonEnvironmentKind::Poetry, - ) => { - if let Some(activation_scripts) = &toolchain.activation_scripts { - if let Some(activate_script_path) = activation_scripts.get(&shell) { - let activate_keyword = shell.activate_keyword(); - if let Some(quoted) = - shell.try_quote(&activate_script_path.to_string_lossy()) - { - activation_script.push(format!("{activate_keyword} {quoted}")); + Some( + PythonEnvironmentKind::Venv + | PythonEnvironmentKind::VirtualEnv + | PythonEnvironmentKind::Uv + | PythonEnvironmentKind::UvWorkspace + | PythonEnvironmentKind::Poetry, + ) => { + if let Some(activation_scripts) = &toolchain.activation_scripts { + if let Some(activate_script_path) = activation_scripts.get(&shell) { + let activate_keyword = shell.activate_keyword(); + if let Some(quoted) = + shell.try_quote(&activate_script_path.to_string_lossy()) + { + activation_script.push(format!("{activate_keyword} {quoted}")); + } } } } + Some(PythonEnvironmentKind::Pyenv) => { + let Some(manager) = &toolchain.environment.manager else { + return vec![]; + }; + let version = toolchain.environment.version.as_deref().unwrap_or("system"); + let pyenv = &manager.executable; + let pyenv = pyenv.display(); + activation_script.extend(match shell { + ShellKind::Fish => Some(format!("\"{pyenv}\" shell - fish {version}")), + ShellKind::Posix => Some(format!("\"{pyenv}\" shell - sh {version}")), + ShellKind::Nushell => Some(format!("^\"{pyenv}\" shell - nu {version}")), + ShellKind::PowerShell | ShellKind::Pwsh => None, + ShellKind::Csh => None, + ShellKind::Tcsh => None, + ShellKind::Cmd => None, + ShellKind::Rc => None, + ShellKind::Xonsh => None, + ShellKind::Elvish => None, + }) + } + _ => {} } - Some(PythonEnvironmentKind::Pyenv) => { - let Some(manager) = &toolchain.environment.manager else { - return vec![]; - }; - let version = toolchain.environment.version.as_deref().unwrap_or("system"); - let pyenv = &manager.executable; - let pyenv = pyenv.display(); - activation_script.extend(match shell { - ShellKind::Fish => Some(format!("\"{pyenv}\" shell - fish {version}")), - ShellKind::Posix => Some(format!("\"{pyenv}\" shell - sh {version}")), - ShellKind::Nushell => Some(format!("^\"{pyenv}\" shell - nu {version}")), - ShellKind::PowerShell | ShellKind::Pwsh => None, - ShellKind::Csh => None, - ShellKind::Tcsh => None, - ShellKind::Cmd => None, - ShellKind::Rc => None, - ShellKind::Xonsh => None, - ShellKind::Elvish => None, - }) - } - _ => {} - } - activation_script + activation_script + }) } } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 53e936d476685d96915c417f74716f814bfd061f..c0f558365604cb11c8038b0303d8bef59ce078ca 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -11110,8 +11110,13 @@ fn python_lang(fs: Arc) -> Arc { manifest_name: ManifestName::from(SharedString::new_static("pyproject.toml")), } } - fn activation_script(&self, _: &Toolchain, _: ShellKind, _: &gpui::App) -> Vec { - vec![] + fn activation_script( + &self, + _: &Toolchain, + _: ShellKind, + _: &gpui::App, + ) -> futures::future::BoxFuture<'static, Vec> { + Box::pin(async { vec![] }) } } Arc::new( diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 7927a8ab2c3bcd9280d4dc7f0af5580bee8a3ef7..74e09bc964ff8c10423a073fb80700d205e5bb31 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -129,9 +129,9 @@ impl Project { .await .ok(); let lister = language?.toolchain_lister()?; - return Some( - cx.update(|cx| lister.activation_script(&toolchain, shell_kind, cx)), - ); + let future = + cx.update(|cx| lister.activation_script(&toolchain, shell_kind, cx)); + return Some(future.await); } None }) @@ -379,9 +379,9 @@ impl Project { .await .ok(); let lister = language?.toolchain_lister()?; - return Some( - cx.update(|cx| lister.activation_script(&toolchain, shell_kind, cx)), - ); + let future = + cx.update(|cx| lister.activation_script(&toolchain, shell_kind, cx)); + return Some(future.await); } None })