Add language toolchains (#19576)
This PR adds support for selecting toolchains for a given language (e.g. Rust toolchains or Python virtual environments) with support for SSH projects provided out of the box. For Python we piggy-back off of [PET](https://github.com/microsoft/python-environment-tools), a library maintained by Microsoft. Closes #16421 Closes #7646 Release Notes: - Added toolchain selector to the status bar (with initial support for Python virtual environments)
This commit is contained in:
parent
03bd95405b
commit
cdddb4d360
33 changed files with 2221 additions and 133 deletions
|
@ -3,9 +3,16 @@ use async_trait::async_trait;
|
|||
use collections::HashMap;
|
||||
use gpui::AppContext;
|
||||
use gpui::AsyncAppContext;
|
||||
use language::LanguageName;
|
||||
use language::LanguageToolchainStore;
|
||||
use language::Toolchain;
|
||||
use language::ToolchainList;
|
||||
use language::ToolchainLister;
|
||||
use language::{ContextProvider, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
use pet_core::python_environment::PythonEnvironmentKind;
|
||||
use pet_core::Configuration;
|
||||
use project::lsp_store::language_server_settings;
|
||||
use serde_json::Value;
|
||||
|
||||
|
@ -200,12 +207,35 @@ impl LspAdapter for PythonLspAdapter {
|
|||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
adapter: &Arc<dyn LspAdapterDelegate>,
|
||||
toolchains: Arc<dyn LanguageToolchainStore>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Value> {
|
||||
cx.update(|cx| {
|
||||
language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
|
||||
.and_then(|s| s.settings.clone())
|
||||
.unwrap_or_default()
|
||||
let toolchain = toolchains
|
||||
.active_toolchain(adapter.worktree_id(), LanguageName::new("Python"), cx)
|
||||
.await;
|
||||
cx.update(move |cx| {
|
||||
let mut user_settings =
|
||||
language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
|
||||
.and_then(|s| s.settings.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
// If python.pythonPath is not set in user config, do so using our toolchain picker.
|
||||
if let Some(toolchain) = toolchain {
|
||||
if user_settings.is_null() {
|
||||
user_settings = Value::Object(serde_json::Map::default());
|
||||
}
|
||||
let object = user_settings.as_object_mut().unwrap();
|
||||
if let Some(python) = object
|
||||
.entry("python")
|
||||
.or_insert(Value::Object(serde_json::Map::default()))
|
||||
.as_object_mut()
|
||||
{
|
||||
python
|
||||
.entry("pythonPath")
|
||||
.or_insert(Value::String(toolchain.path.into()));
|
||||
}
|
||||
}
|
||||
user_settings
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -320,6 +350,83 @@ fn python_module_name_from_relative_path(relative_path: &str) -> String {
|
|||
.to_string()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct PythonToolchainProvider {}
|
||||
|
||||
static ENV_PRIORITY_LIST: &'static [PythonEnvironmentKind] = &[
|
||||
// Prioritize non-Conda environments.
|
||||
PythonEnvironmentKind::Poetry,
|
||||
PythonEnvironmentKind::Pipenv,
|
||||
PythonEnvironmentKind::VirtualEnvWrapper,
|
||||
PythonEnvironmentKind::Venv,
|
||||
PythonEnvironmentKind::VirtualEnv,
|
||||
PythonEnvironmentKind::Conda,
|
||||
PythonEnvironmentKind::Pyenv,
|
||||
PythonEnvironmentKind::GlobalPaths,
|
||||
PythonEnvironmentKind::Homebrew,
|
||||
];
|
||||
|
||||
fn env_priority(kind: Option<PythonEnvironmentKind>) -> usize {
|
||||
if let Some(kind) = kind {
|
||||
ENV_PRIORITY_LIST
|
||||
.iter()
|
||||
.position(|blessed_env| blessed_env == &kind)
|
||||
.unwrap_or(ENV_PRIORITY_LIST.len())
|
||||
} else {
|
||||
// Unknown toolchains are less useful than non-blessed ones.
|
||||
ENV_PRIORITY_LIST.len() + 1
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl ToolchainLister for PythonToolchainProvider {
|
||||
async fn list(&self, worktree_root: PathBuf) -> ToolchainList {
|
||||
let environment = pet_core::os_environment::EnvironmentApi::new();
|
||||
let locators = pet::locators::create_locators(
|
||||
Arc::new(pet_conda::Conda::from(&environment)),
|
||||
Arc::new(pet_poetry::Poetry::from(&environment)),
|
||||
&environment,
|
||||
);
|
||||
let mut config = Configuration::default();
|
||||
config.workspace_directories = Some(vec![worktree_root]);
|
||||
let reporter = pet_reporter::collect::create_reporter();
|
||||
pet::find::find_and_report_envs(&reporter, config, &locators, &environment, None);
|
||||
|
||||
let mut toolchains = reporter
|
||||
.environments
|
||||
.lock()
|
||||
.ok()
|
||||
.map_or(Vec::new(), |mut guard| std::mem::take(&mut guard));
|
||||
toolchains.sort_by(|lhs, rhs| {
|
||||
env_priority(lhs.kind)
|
||||
.cmp(&env_priority(rhs.kind))
|
||||
.then_with(|| lhs.executable.cmp(&rhs.executable))
|
||||
});
|
||||
let mut toolchains: Vec<_> = toolchains
|
||||
.into_iter()
|
||||
.filter_map(|toolchain| {
|
||||
let name = if let Some(version) = &toolchain.version {
|
||||
format!("Python {version} ({:?})", toolchain.kind?)
|
||||
} else {
|
||||
format!("{:?}", toolchain.kind?)
|
||||
}
|
||||
.into();
|
||||
Some(Toolchain {
|
||||
name,
|
||||
path: toolchain.executable?.to_str()?.to_owned().into(),
|
||||
language_name: LanguageName::new("Python"),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
toolchains.dedup();
|
||||
ToolchainList {
|
||||
toolchains,
|
||||
default: None,
|
||||
groups: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::{BorrowAppContext, Context, ModelContext, TestAppContext};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue