toolchains: Allow users to provide custom paths to toolchains (#37009)

- **toolchains: Add new state to toolchain selector**
- **Use toolchain term for Add Toolchain button**
- **Hoist out a meta function for toolchain listers**

Closes #27332

Release Notes:

- python: Users can now specify a custom path to their virtual
environment from within the picker.

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This commit is contained in:
Piotr Osiewicz
2025-09-06 00:47:39 +02:00
committed by GitHub
co-authored by Danilo Leal
parent 59bdbf5a5d
commit 6a7b84eb87
22 changed files with 1506 additions and 233 deletions
+56 -44
View File
@@ -5,19 +5,19 @@ use collections::HashMap;
use futures::AsyncBufReadExt;
use gpui::{App, Task};
use gpui::{AsyncApp, SharedString};
use language::Toolchain;
use language::ToolchainList;
use language::ToolchainLister;
use language::language_settings::language_settings;
use language::{ContextLocation, LanguageToolchainStore};
use language::{ContextProvider, LspAdapter, LspAdapterDelegate};
use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery};
use language::{Toolchain, ToolchainMetadata};
use lsp::LanguageServerBinary;
use lsp::LanguageServerName;
use node_runtime::{NodeRuntime, VersionStrategy};
use pet_core::Configuration;
use pet_core::os_environment::Environment;
use pet_core::python_environment::PythonEnvironmentKind;
use pet_core::python_environment::{PythonEnvironment, PythonEnvironmentKind};
use project::Fs;
use project::lsp_store::language_server_settings;
use serde_json::{Value, json};
@@ -688,17 +688,7 @@ fn python_env_kind_display(k: &PythonEnvironmentKind) -> &'static str {
}
}
pub(crate) struct PythonToolchainProvider {
term: SharedString,
}
impl Default for PythonToolchainProvider {
fn default() -> Self {
Self {
term: SharedString::new_static("Virtual Environment"),
}
}
}
pub(crate) struct PythonToolchainProvider;
static ENV_PRIORITY_LIST: &[PythonEnvironmentKind] = &[
// Prioritize non-Conda environments.
@@ -744,9 +734,6 @@ async fn get_worktree_venv_declaration(worktree_root: &Path) -> Option<String> {
#[async_trait]
impl ToolchainLister for PythonToolchainProvider {
fn manifest_name(&self) -> language::ManifestName {
ManifestName::from(SharedString::new_static("pyproject.toml"))
}
async fn list(
&self,
worktree_root: PathBuf,
@@ -847,32 +834,7 @@ impl ToolchainLister for PythonToolchainProvider {
let mut toolchains: Vec<_> = toolchains
.into_iter()
.filter_map(|toolchain| {
let mut name = String::from("Python");
if let Some(version) = &toolchain.version {
_ = write!(name, " {version}");
}
let name_and_kind = match (&toolchain.name, &toolchain.kind) {
(Some(name), Some(kind)) => {
Some(format!("({name}; {})", python_env_kind_display(kind)))
}
(Some(name), None) => Some(format!("({name})")),
(None, Some(kind)) => Some(format!("({})", python_env_kind_display(kind))),
(None, None) => None,
};
if let Some(nk) = name_and_kind {
_ = write!(name, " {nk}");
}
Some(Toolchain {
name: name.into(),
path: toolchain.executable.as_ref()?.to_str()?.to_owned().into(),
language_name: LanguageName::new("Python"),
as_json: serde_json::to_value(toolchain.clone()).ok()?,
})
})
.filter_map(venv_to_toolchain)
.collect();
toolchains.dedup();
ToolchainList {
@@ -881,9 +843,34 @@ impl ToolchainLister for PythonToolchainProvider {
groups: Default::default(),
}
}
fn term(&self) -> SharedString {
self.term.clone()
fn meta(&self) -> ToolchainMetadata {
ToolchainMetadata {
term: SharedString::new_static("Virtual Environment"),
new_toolchain_placeholder: SharedString::new_static(
"A path to the python3 executable within a virtual environment, or path to virtual environment itself",
),
manifest_name: ManifestName::from(SharedString::new_static("pyproject.toml")),
}
}
async fn resolve(
&self,
path: PathBuf,
env: Option<HashMap<String, String>>,
) -> anyhow::Result<Toolchain> {
let env = env.unwrap_or_default();
let environment = EnvironmentApi::from_env(&env);
let locators = pet::locators::create_locators(
Arc::new(pet_conda::Conda::from(&environment)),
Arc::new(pet_poetry::Poetry::from(&environment)),
&environment,
);
let toolchain = pet::resolve::resolve_environment(&path, &locators, &environment)
.context("Could not find a virtual environment in provided path")?;
let venv = toolchain.resolved.unwrap_or(toolchain.discovered);
venv_to_toolchain(venv).context("Could not convert a venv into a toolchain")
}
async fn activation_script(
&self,
toolchain: &Toolchain,
@@ -956,6 +943,31 @@ impl ToolchainLister for PythonToolchainProvider {
}
}
fn venv_to_toolchain(venv: PythonEnvironment) -> Option<Toolchain> {
let mut name = String::from("Python");
if let Some(ref version) = venv.version {
_ = write!(name, " {version}");
}
let name_and_kind = match (&venv.name, &venv.kind) {
(Some(name), Some(kind)) => Some(format!("({name}; {})", python_env_kind_display(kind))),
(Some(name), None) => Some(format!("({name})")),
(None, Some(kind)) => Some(format!("({})", python_env_kind_display(kind))),
(None, None) => None,
};
if let Some(nk) = name_and_kind {
_ = write!(name, " {nk}");
}
Some(Toolchain {
name: name.into(),
path: venv.executable.as_ref()?.to_str()?.to_owned().into(),
language_name: LanguageName::new("Python"),
as_json: serde_json::to_value(venv).ok()?,
})
}
pub struct EnvironmentApi<'a> {
global_search_locations: Arc<Mutex<Vec<PathBuf>>>,
project_env: &'a HashMap<String, String>,