terminal: Allow configuring conda manager (#40577)
Closes #40576 This PR makes Conda activation configurable and transparent by adding a `terminal.detect_venv.on.conda_manager` setting (`"auto" | "conda" | "mamba" | "micromamba"`, default `"auto"`), updating Python environment activation to honor this preference (or the detected manager executable) and fall back to `conda` when necessary. The preference is passed via `ZED_CONDA_MANAGER` from the terminal settings, and the activation command is built accordingly (with proper quoting for paths). Changes span `zed/crates/terminal/src/terminal_settings.rs` (new `CondaManager` and setting), `zed/crates/project/src/terminals.rs` (inject env var), `zed/crates/languages/src/python.rs` (activation logic), and `zed/assets/settings/default.json` (document the setting). Default behavior remains unchanged for most users while enabling explicit selection of `mamba` or `micromamba`. Release Notes: - Added: terminal.detect_venv.on.conda_manager setting to choose the Conda manager (auto, conda, mamba, micromamba). Default: auto. - Changed: Python Conda environment activation now respects the configured manager, otherwise uses the detected environment manager executable, and falls back to conda. - Reliability: Activation commands quote manager paths to handle spaces across platforms. - Compatibility: No breaking changes; non-Conda environments are unaffected; remote terminals are supported. --------- Co-authored-by: Lukas Wirth <me@lukaswirth.dev> Co-authored-by: Lukas Wirth <lukas@zed.dev>
This commit is contained in:
co-authored by
Lukas Wirth
Lukas Wirth
parent
082b80ec89
commit
9d52b6c538
Generated
+1
@@ -9032,6 +9032,7 @@ dependencies = [
|
||||
"settings",
|
||||
"smol",
|
||||
"task",
|
||||
"terminal",
|
||||
"text",
|
||||
"theme",
|
||||
"toml 0.8.23",
|
||||
|
||||
@@ -1487,7 +1487,11 @@
|
||||
// in your project's settings, rather than globally.
|
||||
"directories": [".env", "env", ".venv", "venv"],
|
||||
// Can also be `csh`, `fish`, `nushell` and `power_shell`
|
||||
"activate_script": "default"
|
||||
"activate_script": "default",
|
||||
// Preferred Conda manager to use when activating Conda environments.
|
||||
// Values: "auto", "conda", "mamba", "micromamba"
|
||||
// Default: "auto"
|
||||
"conda_manager": "auto"
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::{path::PathBuf, sync::Arc};
|
||||
use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use fs::Fs;
|
||||
use gpui::{AsyncApp, SharedString};
|
||||
use gpui::{App, AsyncApp, SharedString};
|
||||
use settings::WorktreeId;
|
||||
use task::ShellKind;
|
||||
use util::rel_path::RelPath;
|
||||
@@ -110,7 +110,7 @@ pub trait ToolchainLister: Send + Sync + 'static {
|
||||
fs: &dyn Fs,
|
||||
) -> anyhow::Result<Toolchain>;
|
||||
|
||||
fn activation_script(&self, toolchain: &Toolchain, shell: ShellKind) -> Vec<String>;
|
||||
fn activation_script(&self, toolchain: &Toolchain, shell: ShellKind, cx: &App) -> Vec<String>;
|
||||
|
||||
/// Returns various "static" bits of information about this toolchain lister. This function should be pure.
|
||||
fn meta(&self) -> ToolchainMetadata;
|
||||
|
||||
@@ -69,6 +69,7 @@ settings.workspace = true
|
||||
smol.workspace = true
|
||||
url.workspace = true
|
||||
task.workspace = true
|
||||
terminal.workspace = true
|
||||
theme.workspace = true
|
||||
toml.workspace = true
|
||||
tree-sitter = { workspace = true, optional = true }
|
||||
|
||||
@@ -21,9 +21,11 @@ use project::Fs;
|
||||
use project::lsp_store::language_server_settings;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use settings::Settings;
|
||||
use smol::lock::OnceCell;
|
||||
use std::cmp::Ordering;
|
||||
use std::env::consts;
|
||||
use terminal::terminal_settings::TerminalSettings;
|
||||
use util::command::new_smol_command;
|
||||
use util::fs::{make_file_executable, remove_matching};
|
||||
use util::rel_path::RelPath;
|
||||
@@ -1171,7 +1173,7 @@ impl ToolchainLister for PythonToolchainProvider {
|
||||
.context("Could not convert a venv into a toolchain")
|
||||
}
|
||||
|
||||
fn activation_script(&self, toolchain: &Toolchain, shell: ShellKind) -> Vec<String> {
|
||||
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 {
|
||||
@@ -1184,10 +1186,34 @@ impl ToolchainLister for PythonToolchainProvider {
|
||||
|
||||
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 => {
|
||||
// When auto, prefer the detected manager or fall back to conda
|
||||
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")
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(name) = &toolchain.environment.name {
|
||||
activation_script.push(format!("conda activate {name}"));
|
||||
activation_script.push(format!("{manager} activate {name}"));
|
||||
} else {
|
||||
activation_script.push("conda activate".to_string());
|
||||
activation_script.push(format!("{manager} activate base"));
|
||||
}
|
||||
}
|
||||
Some(PythonEnvironmentKind::Venv | PythonEnvironmentKind::VirtualEnv) => {
|
||||
|
||||
@@ -10185,7 +10185,7 @@ fn python_lang(fs: Arc<FakeFs>) -> Arc<Language> {
|
||||
manifest_name: ManifestName::from(SharedString::new_static("pyproject.toml")),
|
||||
}
|
||||
}
|
||||
fn activation_script(&self, _: &Toolchain, _: ShellKind) -> Vec<String> {
|
||||
fn activation_script(&self, _: &Toolchain, _: ShellKind, _: &gpui::App) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,8 +127,10 @@ impl Project {
|
||||
.language_for_name(&toolchain.language_name.0)
|
||||
.await
|
||||
.ok();
|
||||
let lister = language?.toolchain_lister();
|
||||
return Some(lister?.activation_script(&toolchain, shell_kind));
|
||||
let lister = language?.toolchain_lister()?;
|
||||
return cx
|
||||
.update(|cx| lister.activation_script(&toolchain, shell_kind, cx))
|
||||
.ok();
|
||||
}
|
||||
None
|
||||
})
|
||||
@@ -317,7 +319,8 @@ impl Project {
|
||||
.unwrap_or_else(get_default_system_shell),
|
||||
None => settings.shell.program(),
|
||||
};
|
||||
let shell_kind = ShellKind::new(&shell, self.path_style(cx).is_windows());
|
||||
|
||||
let is_windows = self.path_style(cx).is_windows();
|
||||
|
||||
// Prepare a task for resolving the environment
|
||||
let env_task =
|
||||
@@ -325,6 +328,7 @@ impl Project {
|
||||
|
||||
let lang_registry = self.languages.clone();
|
||||
cx.spawn(async move |project, cx| {
|
||||
let shell_kind = ShellKind::new(&shell, is_windows);
|
||||
let mut env = env_task.await.unwrap_or_default();
|
||||
env.extend(settings.env);
|
||||
|
||||
@@ -337,13 +341,16 @@ impl Project {
|
||||
.language_for_name(&toolchain.language_name.0)
|
||||
.await
|
||||
.ok();
|
||||
let lister = language?.toolchain_lister();
|
||||
return Some(lister?.activation_script(&toolchain, shell_kind));
|
||||
let lister = language?.toolchain_lister()?;
|
||||
return cx
|
||||
.update(|cx| lister.activation_script(&toolchain, shell_kind, cx))
|
||||
.ok();
|
||||
}
|
||||
None
|
||||
})
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let builder = project
|
||||
.update(cx, move |_, cx| {
|
||||
let (shell, env) = {
|
||||
|
||||
@@ -348,6 +348,22 @@ pub struct TerminalToolbarContent {
|
||||
pub breadcrumbs: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CondaManager {
|
||||
/// Automatically detect the conda manager
|
||||
#[default]
|
||||
Auto,
|
||||
/// Use conda
|
||||
Conda,
|
||||
/// Use mamba
|
||||
Mamba,
|
||||
/// Use micromamba
|
||||
Micromamba,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum VenvSettings {
|
||||
@@ -360,6 +376,10 @@ pub enum VenvSettings {
|
||||
activate_script: Option<ActivateScript>,
|
||||
venv_name: Option<String>,
|
||||
directories: Option<Vec<PathBuf>>,
|
||||
/// Preferred Conda manager to use when activating Conda environments.
|
||||
///
|
||||
/// Default: auto
|
||||
conda_manager: Option<CondaManager>,
|
||||
},
|
||||
}
|
||||
#[skip_serializing_none]
|
||||
@@ -367,6 +387,7 @@ pub struct VenvSettingsContent<'a> {
|
||||
pub activate_script: ActivateScript,
|
||||
pub venv_name: &'a str,
|
||||
pub directories: &'a [PathBuf],
|
||||
pub conda_manager: CondaManager,
|
||||
}
|
||||
|
||||
impl VenvSettings {
|
||||
@@ -377,10 +398,12 @@ impl VenvSettings {
|
||||
activate_script,
|
||||
venv_name,
|
||||
directories,
|
||||
conda_manager,
|
||||
} => Some(VenvSettingsContent {
|
||||
activate_script: activate_script.unwrap_or(ActivateScript::Default),
|
||||
venv_name: venv_name.as_deref().unwrap_or(""),
|
||||
directories: directories.as_deref().unwrap_or(&[]),
|
||||
conda_manager: conda_manager.unwrap_or(CondaManager::Auto),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user