diff --git a/Cargo.lock b/Cargo.lock index a281af4f21..1d6afcab5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6403,6 +6403,7 @@ dependencies = [ "pet", "pet-conda", "pet-core", + "pet-fs", "pet-poetry", "pet-reporter", "project", diff --git a/Cargo.toml b/Cargo.toml index a9511a59e0..e0a5b7d491 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -381,6 +381,7 @@ palette = { version = "0.7.5", default-features = false, features = ["std"] } parking_lot = "0.12.1" pathdiff = "0.2" pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } +pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } diff --git a/crates/language/src/toolchain.rs b/crates/language/src/toolchain.rs index efb27008d0..cd9a3bc403 100644 --- a/crates/language/src/toolchain.rs +++ b/crates/language/src/toolchain.rs @@ -7,6 +7,7 @@ use std::{path::PathBuf, sync::Arc}; use async_trait::async_trait; +use collections::HashMap; use gpui::{AsyncAppContext, SharedString}; use settings::WorktreeId; @@ -23,7 +24,11 @@ pub struct Toolchain { #[async_trait(?Send)] pub trait ToolchainLister: Send + Sync { - async fn list(&self, _: PathBuf) -> ToolchainList; + async fn list( + &self, + worktree_root: PathBuf, + project_env: Option>, + ) -> ToolchainList; } #[async_trait(?Send)] diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index d76dd5a327..96a44403bc 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -47,6 +47,7 @@ lsp.workspace = true node_runtime.workspace = true paths.workspace = true pet.workspace = true +pet-fs.workspace = true pet-core.workspace = true pet-conda.workspace = true pet-poetry.workspace = true diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index e73e3c8682..cad2e3483a 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -11,11 +11,13 @@ use language::ToolchainLister; use language::{ContextProvider, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; +use pet_core::os_environment::Environment; use pet_core::python_environment::PythonEnvironmentKind; use pet_core::Configuration; use project::lsp_store::language_server_settings; use serde_json::Value; +use std::sync::Mutex; use std::{ any::Any, borrow::Cow, @@ -380,8 +382,13 @@ fn env_priority(kind: Option) -> usize { #[async_trait(?Send)] impl ToolchainLister for PythonToolchainProvider { - async fn list(&self, worktree_root: PathBuf) -> ToolchainList { - let environment = pet_core::os_environment::EnvironmentApi::new(); + async fn list( + &self, + worktree_root: PathBuf, + project_env: Option>, + ) -> ToolchainList { + let env = project_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)), @@ -427,6 +434,78 @@ impl ToolchainLister for PythonToolchainProvider { } } +pub struct EnvironmentApi<'a> { + global_search_locations: Arc>>, + project_env: &'a HashMap, + pet_env: pet_core::os_environment::EnvironmentApi, +} + +impl<'a> EnvironmentApi<'a> { + pub fn from_env(project_env: &'a HashMap) -> Self { + let paths = project_env + .get("PATH") + .map(|p| std::env::split_paths(p).collect()) + .unwrap_or_default(); + + EnvironmentApi { + global_search_locations: Arc::new(Mutex::new(paths)), + project_env, + pet_env: pet_core::os_environment::EnvironmentApi::new(), + } + } + + fn user_home(&self) -> Option { + self.project_env + .get("HOME") + .or_else(|| self.project_env.get("USERPROFILE")) + .map(|home| pet_fs::path::norm_case(PathBuf::from(home))) + .or_else(|| self.pet_env.get_user_home()) + } +} + +impl<'a> pet_core::os_environment::Environment for EnvironmentApi<'a> { + fn get_user_home(&self) -> Option { + self.user_home() + } + + fn get_root(&self) -> Option { + None + } + + fn get_env_var(&self, key: String) -> Option { + self.project_env + .get(&key) + .cloned() + .or_else(|| self.pet_env.get_env_var(key)) + } + + fn get_know_global_search_locations(&self) -> Vec { + if self.global_search_locations.lock().unwrap().is_empty() { + let mut paths = + std::env::split_paths(&self.get_env_var("PATH".to_string()).unwrap_or_default()) + .collect::>(); + + log::trace!("Env PATH: {:?}", paths); + for p in self.pet_env.get_know_global_search_locations() { + if !paths.contains(&p) { + paths.push(p); + } + } + + let mut paths = paths + .into_iter() + .filter(|p| p.exists()) + .collect::>(); + + self.global_search_locations + .lock() + .unwrap() + .append(&mut paths); + } + self.global_search_locations.lock().unwrap().clone() + } +} + #[cfg(test)] mod tests { use gpui::{BorrowAppContext, Context, ModelContext, TestAppContext}; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d72d6171c9..938072eba6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -639,7 +639,12 @@ impl Project { cx.subscribe(&settings_observer, Self::on_settings_observer_event) .detach(); let toolchain_store = cx.new_model(|cx| { - ToolchainStore::local(languages.clone(), worktree_store.clone(), cx) + ToolchainStore::local( + languages.clone(), + worktree_store.clone(), + environment.clone(), + cx, + ) }); let lsp_store = cx.new_model(|cx| { LspStore::new_local( @@ -2369,10 +2374,16 @@ impl Project { language_name: LanguageName, cx: &AppContext, ) -> Task> { - if let Some(toolchain_store) = self.toolchain_store.as_ref() { - toolchain_store - .read(cx) - .list_toolchains(worktree_id, language_name, cx) + if let Some(toolchain_store) = self.toolchain_store.clone() { + cx.spawn(|cx| async move { + cx.update(|cx| { + toolchain_store + .read(cx) + .list_toolchains(worktree_id, language_name, cx) + }) + .unwrap_or(Task::Ready(None)) + .await + }) } else { Task::ready(None) } diff --git a/crates/project/src/toolchain_store.rs b/crates/project/src/toolchain_store.rs index a3f27d731b..ba9149a3c2 100644 --- a/crates/project/src/toolchain_store.rs +++ b/crates/project/src/toolchain_store.rs @@ -13,7 +13,7 @@ use rpc::{proto, AnyProtoClient, TypedEnvelope}; use settings::WorktreeId; use util::ResultExt as _; -use crate::worktree_store::WorktreeStore; +use crate::{worktree_store::WorktreeStore, ProjectEnvironment}; pub struct ToolchainStore(ToolchainStoreInner); enum ToolchainStoreInner { @@ -32,11 +32,13 @@ impl ToolchainStore { pub fn local( languages: Arc, worktree_store: Model, + project_environment: Model, cx: &mut ModelContext, ) -> Self { let model = cx.new_model(|_| LocalToolchainStore { languages, worktree_store, + project_environment, active_toolchains: Default::default(), }); let subscription = cx.subscribe(&model, |_, _, e: &ToolchainStoreEvent, cx| { @@ -203,6 +205,7 @@ impl ToolchainStore { struct LocalToolchainStore { languages: Arc, worktree_store: Model, + project_environment: Model, active_toolchains: BTreeMap<(WorktreeId, LanguageName), Toolchain>, } @@ -296,9 +299,20 @@ impl LocalToolchainStore { else { return Task::ready(None); }; - cx.spawn(|_| async move { + + let environment = self.project_environment.clone(); + cx.spawn(|mut cx| async move { + let project_env = environment + .update(&mut cx, |environment, cx| { + environment.get_environment(Some(worktree_id), Some(root.clone()), cx) + }) + .ok()? + .await; let language = registry.language_for_name(&language_name.0).await.ok()?; - let toolchains = language.toolchain_lister()?.list(root.to_path_buf()).await; + let toolchains = language + .toolchain_lister()? + .list(root.to_path_buf(), project_env) + .await; Some(toolchains) }) } @@ -345,6 +359,7 @@ impl RemoteToolchainStore { Some(()) }) } + pub(crate) fn list_toolchains( &self, worktree_id: WorktreeId, diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 155b141af6..773f6b381f 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -108,8 +108,14 @@ impl HeadlessProject { observer.shared(SSH_PROJECT_ID, session.clone().into(), cx); observer }); - let toolchain_store = - cx.new_model(|cx| ToolchainStore::local(languages.clone(), worktree_store.clone(), cx)); + let toolchain_store = cx.new_model(|cx| { + ToolchainStore::local( + languages.clone(), + worktree_store.clone(), + environment.clone(), + cx, + ) + }); let lsp_store = cx.new_model(|cx| { let mut lsp_store = LspStore::new_local( buffer_store.clone(),