Don't try to spawn conda if it's not there (#47261)

Conrad Irwin created

Closes #ISSUE

Release Notes:

- Fixed conda showing up in terminal windows

Change summary

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(-)

Detailed changes

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<Toolchain>;
 
-    fn activation_script(&self, toolchain: &Toolchain, shell: ShellKind, cx: &App) -> Vec<String>;
+    fn activation_script(
+        &self,
+        toolchain: &Toolchain,
+        shell: ShellKind,
+        cx: &App,
+    ) -> BoxFuture<'static, Vec<String>>;
 
     /// Returns various "static" bits of information about this toolchain lister. This function should be pure.
     fn meta(&self) -> ToolchainMetadata;

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<String> {
-        let Ok(toolchain) =
-            serde_json::from_value::<PythonToolchainData>(toolchain.as_json.clone())
-        else {
-            return vec![];
-        };
+    fn activation_script(
+        &self,
+        toolchain: &Toolchain,
+        shell: ShellKind,
+        cx: &App,
+    ) -> BoxFuture<'static, Vec<String>> {
+        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::<PythonToolchainData>(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
+        })
     }
 }
 

crates/project/src/project_tests.rs 🔗

@@ -11110,8 +11110,13 @@ fn python_lang(fs: Arc<FakeFs>) -> Arc<Language> {
                 manifest_name: ManifestName::from(SharedString::new_static("pyproject.toml")),
             }
         }
-        fn activation_script(&self, _: &Toolchain, _: ShellKind, _: &gpui::App) -> Vec<String> {
-            vec![]
+        fn activation_script(
+            &self,
+            _: &Toolchain,
+            _: ShellKind,
+            _: &gpui::App,
+        ) -> futures::future::BoxFuture<'static, Vec<String>> {
+            Box::pin(async { vec![] })
         }
     }
     Arc::new(

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
             })