python: Stop falling back to conda activate base for nameless toolchains (#56785)
Stops the `conda activate base` fallback in `PythonToolchainProvider::activation_script` for two cases: 1. A Conda toolchain whose `environment.name` is `None`. 2. A Conda toolchain whose name is `Some(...)` but `shell.try_quote` rejects it. Previously both cases pushed `conda activate base` into the activation script. With miniforge installed and no env explicitly selected, every terminal silently activated `(base)`. The user didn't pick `base`; injecting it is contamination. Both branches now log a warning and emit no activation line. Users who want `(base)` can select it as the toolchain explicitly. Two unit tests cover the new behavior: `test_conda_activation_skips_when_name_missing` (name=None) and `test_conda_activation_skips_unquotable_name` (name=Some but unquotable, exercised with an embedded NUL byte that `shlex::try_quote` rejects). The existing `test_conda_activation_script_injection` still verifies that quotable names get activated. Release Notes: - Fixed Zed silently injecting `conda activate base` into terminals when a Conda manager (miniforge/miniconda) was installed but no specific environment was selected.
This commit is contained in:
1 parent
ee591c04ae
commit
7cb01e42ef
1 file changed
+145
-3
@@ -1495,18 +1495,23 @@ impl ToolchainLister for PythonToolchainProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// Only inject `{manager} activate <name>` when we have a
|
||||
// safely-quotable name. Never silently fall back to
|
||||
// `activate base`: a user with miniforge installed but a
|
||||
// local uv/venv project should not have their terminal
|
||||
// hijacked just because we couldn't resolve a name.
|
||||
if let Some(name) = &toolchain.environment.name {
|
||||
if let Some(quoted_name) = shell.try_quote(name) {
|
||||
activation_script.push(format!("{manager} activate {quoted_name}"));
|
||||
} else {
|
||||
log::warn!(
|
||||
"Could not safely quote environment name {:?}, falling back to base",
|
||||
"Conda environment name {:?} could not be safely quoted; \
|
||||
skipping terminal activation",
|
||||
name
|
||||
);
|
||||
activation_script.push(format!("{manager} activate base"));
|
||||
}
|
||||
} else {
|
||||
activation_script.push(format!("{manager} activate base"));
|
||||
log::warn!("Conda toolchain has no name; skipping terminal activation");
|
||||
}
|
||||
}
|
||||
Some(
|
||||
@@ -2793,6 +2798,143 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_conda_activation_skips_when_name_missing(cx: &mut TestAppContext) {
|
||||
use language::{LanguageName, Toolchain, ToolchainLister};
|
||||
use settings::{CondaManager, VenvSettings};
|
||||
use task::ShellKind;
|
||||
|
||||
use crate::python::PythonToolchainProvider;
|
||||
|
||||
cx.executor().allow_parking();
|
||||
|
||||
cx.update(|cx| {
|
||||
let test_settings = SettingsStore::test(cx);
|
||||
cx.set_global(test_settings);
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
store.update_user_settings(cx, |s| {
|
||||
s.terminal
|
||||
.get_or_insert_with(Default::default)
|
||||
.project
|
||||
.detect_venv = Some(VenvSettings::On {
|
||||
activate_script: None,
|
||||
venv_name: None,
|
||||
directories: None,
|
||||
conda_manager: Some(CondaManager::Conda),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let fs = project::FakeFs::new(cx.executor());
|
||||
let provider = PythonToolchainProvider::new(fs);
|
||||
let manager_executable = std::env::current_exe().unwrap();
|
||||
|
||||
let data = serde_json::json!({
|
||||
"name": serde_json::Value::Null,
|
||||
"kind": "Conda",
|
||||
"executable": "/tmp/conda/bin/python",
|
||||
"version": serde_json::Value::Null,
|
||||
"prefix": serde_json::Value::Null,
|
||||
"arch": serde_json::Value::Null,
|
||||
"displayName": serde_json::Value::Null,
|
||||
"project": serde_json::Value::Null,
|
||||
"symlinks": serde_json::Value::Null,
|
||||
"manager": {
|
||||
"executable": manager_executable,
|
||||
"version": serde_json::Value::Null,
|
||||
"tool": "Conda",
|
||||
},
|
||||
});
|
||||
|
||||
let toolchain = Toolchain {
|
||||
name: "test".into(),
|
||||
path: "/tmp/conda".into(),
|
||||
language_name: LanguageName::new_static("Python"),
|
||||
as_json: data,
|
||||
};
|
||||
|
||||
let script = cx
|
||||
.update(|cx| provider.activation_script(&toolchain, ShellKind::Posix, cx))
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
script.is_empty(),
|
||||
"Nameless conda toolchains must not fall back to `conda activate base`, actual: {:?}",
|
||||
script
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_conda_activation_skips_unquotable_name(cx: &mut TestAppContext) {
|
||||
use language::{LanguageName, Toolchain, ToolchainLister};
|
||||
use settings::{CondaManager, VenvSettings};
|
||||
use task::ShellKind;
|
||||
|
||||
use crate::python::PythonToolchainProvider;
|
||||
|
||||
cx.executor().allow_parking();
|
||||
|
||||
cx.update(|cx| {
|
||||
let test_settings = SettingsStore::test(cx);
|
||||
cx.set_global(test_settings);
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
store.update_user_settings(cx, |s| {
|
||||
s.terminal
|
||||
.get_or_insert_with(Default::default)
|
||||
.project
|
||||
.detect_venv = Some(VenvSettings::On {
|
||||
activate_script: None,
|
||||
venv_name: None,
|
||||
directories: None,
|
||||
conda_manager: Some(CondaManager::Conda),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let fs = project::FakeFs::new(cx.executor());
|
||||
let provider = PythonToolchainProvider::new(fs);
|
||||
// shlex::try_quote rejects strings containing a NUL byte, so this name
|
||||
// is guaranteed to fail the Posix quoting path.
|
||||
let unquotable_name = "foo\0bar";
|
||||
let manager_executable = std::env::current_exe().unwrap();
|
||||
|
||||
let data = serde_json::json!({
|
||||
"name": unquotable_name,
|
||||
"kind": "Conda",
|
||||
"executable": "/tmp/conda/bin/python",
|
||||
"version": serde_json::Value::Null,
|
||||
"prefix": serde_json::Value::Null,
|
||||
"arch": serde_json::Value::Null,
|
||||
"displayName": serde_json::Value::Null,
|
||||
"project": serde_json::Value::Null,
|
||||
"symlinks": serde_json::Value::Null,
|
||||
"manager": {
|
||||
"executable": manager_executable,
|
||||
"version": serde_json::Value::Null,
|
||||
"tool": "Conda",
|
||||
},
|
||||
});
|
||||
|
||||
let toolchain = Toolchain {
|
||||
name: "test".into(),
|
||||
path: "/tmp/conda".into(),
|
||||
language_name: LanguageName::new_static("Python"),
|
||||
as_json: data,
|
||||
};
|
||||
|
||||
let script = cx
|
||||
.update(|cx| provider.activation_script(&toolchain, ShellKind::Posix, cx))
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
!script.iter().any(|s| s.contains("conda activate")),
|
||||
"Unquotable conda env names must not emit any `conda activate` line, actual: {:?}",
|
||||
script
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_python_autoindent(cx: &mut TestAppContext) {
|
||||
cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
|
||||
|
||||
Reference in new issue
Block a user