python: Re-land usage of source file path in toolchain picker (#31893)

This reverts commit 1e55e88c18.

Closes #ISSUE

Release Notes:

- Python toolchain selector now uses path to the closest pyproject.toml
as a basis for picking a toolchain. All files under the same
pyproject.toml (in filesystem hierarchy) will share a single virtual
environment. It is possible to have multiple Python virtual environments
selected for disjoint parts of the same project.
This commit is contained in:
Piotr Osiewicz 2025-06-02 18:29:06 +02:00 committed by GitHub
parent 2ebe16a52f
commit 9dd18e5ee1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 195 additions and 92 deletions

View file

@ -34,7 +34,7 @@ pub use highlight_map::HighlightMap;
use http_client::HttpClient; use http_client::HttpClient;
pub use language_registry::{LanguageName, LoadedLanguage}; pub use language_registry::{LanguageName, LoadedLanguage};
use lsp::{CodeActionKind, InitializeParams, LanguageServerBinary, LanguageServerBinaryOptions}; use lsp::{CodeActionKind, InitializeParams, LanguageServerBinary, LanguageServerBinaryOptions};
pub use manifest::{ManifestName, ManifestProvider, ManifestQuery}; pub use manifest::{ManifestDelegate, ManifestName, ManifestProvider, ManifestQuery};
use parking_lot::Mutex; use parking_lot::Mutex;
use regex::Regex; use regex::Regex;
use schemars::{ use schemars::{
@ -323,7 +323,6 @@ pub trait LspAdapterDelegate: Send + Sync {
fn http_client(&self) -> Arc<dyn HttpClient>; fn http_client(&self) -> Arc<dyn HttpClient>;
fn worktree_id(&self) -> WorktreeId; fn worktree_id(&self) -> WorktreeId;
fn worktree_root_path(&self) -> &Path; fn worktree_root_path(&self) -> &Path;
fn exists(&self, path: &Path, is_dir: Option<bool>) -> bool;
fn update_status(&self, language: LanguageServerName, status: BinaryStatus); fn update_status(&self, language: LanguageServerName, status: BinaryStatus);
fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>>; fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>>;
async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>>; async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>>;

View file

@ -1,8 +1,7 @@
use std::{borrow::Borrow, path::Path, sync::Arc}; use std::{borrow::Borrow, path::Path, sync::Arc};
use gpui::SharedString; use gpui::SharedString;
use settings::WorktreeId;
use crate::LspAdapterDelegate;
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ManifestName(SharedString); pub struct ManifestName(SharedString);
@ -39,10 +38,15 @@ pub struct ManifestQuery {
/// Path to the file, relative to worktree root. /// Path to the file, relative to worktree root.
pub path: Arc<Path>, pub path: Arc<Path>,
pub depth: usize, pub depth: usize,
pub delegate: Arc<dyn LspAdapterDelegate>, pub delegate: Arc<dyn ManifestDelegate>,
} }
pub trait ManifestProvider { pub trait ManifestProvider {
fn name(&self) -> ManifestName; fn name(&self) -> ManifestName;
fn search(&self, query: ManifestQuery) -> Option<Arc<Path>>; fn search(&self, query: ManifestQuery) -> Option<Arc<Path>>;
} }
pub trait ManifestDelegate: Send + Sync {
fn worktree_id(&self) -> WorktreeId;
fn exists(&self, path: &Path, is_dir: Option<bool>) -> bool;
}

View file

@ -14,7 +14,7 @@ use collections::HashMap;
use gpui::{AsyncApp, SharedString}; use gpui::{AsyncApp, SharedString};
use settings::WorktreeId; use settings::WorktreeId;
use crate::LanguageName; use crate::{LanguageName, ManifestName};
/// Represents a single toolchain. /// Represents a single toolchain.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -44,10 +44,13 @@ pub trait ToolchainLister: Send + Sync {
async fn list( async fn list(
&self, &self,
worktree_root: PathBuf, worktree_root: PathBuf,
subroot_relative_path: Option<Arc<Path>>,
project_env: Option<HashMap<String, String>>, project_env: Option<HashMap<String, String>>,
) -> ToolchainList; ) -> ToolchainList;
// Returns a term which we should use in UI to refer to a toolchain. // Returns a term which we should use in UI to refer to a toolchain.
fn term(&self) -> SharedString; fn term(&self) -> SharedString;
/// Returns the name of the manifest file for this toolchain.
fn manifest_name(&self) -> ManifestName;
} }
#[async_trait(?Send)] #[async_trait(?Send)]

View file

@ -693,9 +693,13 @@ fn get_worktree_venv_declaration(worktree_root: &Path) -> Option<String> {
#[async_trait] #[async_trait]
impl ToolchainLister for PythonToolchainProvider { impl ToolchainLister for PythonToolchainProvider {
fn manifest_name(&self) -> language::ManifestName {
ManifestName::from(SharedString::new_static("pyproject.toml"))
}
async fn list( async fn list(
&self, &self,
worktree_root: PathBuf, worktree_root: PathBuf,
subroot_relative_path: Option<Arc<Path>>,
project_env: Option<HashMap<String, String>>, project_env: Option<HashMap<String, String>>,
) -> ToolchainList { ) -> ToolchainList {
let env = project_env.unwrap_or_default(); let env = project_env.unwrap_or_default();
@ -706,7 +710,14 @@ impl ToolchainLister for PythonToolchainProvider {
&environment, &environment,
); );
let mut config = Configuration::default(); let mut config = Configuration::default();
config.workspace_directories = Some(vec![worktree_root.clone()]);
let mut directories = vec![worktree_root.clone()];
if let Some(subroot_relative_path) = subroot_relative_path {
debug_assert!(subroot_relative_path.is_relative());
directories.push(worktree_root.join(subroot_relative_path));
}
config.workspace_directories = Some(directories);
for locator in locators.iter() { for locator in locators.iter() {
locator.configure(&config); locator.configure(&config);
} }

View file

@ -10,7 +10,8 @@ use crate::{
lsp_command::{self, *}, lsp_command::{self, *},
lsp_store, lsp_store,
manifest_tree::{ manifest_tree::{
AdapterQuery, LanguageServerTree, LanguageServerTreeNode, LaunchDisposition, ManifestTree, AdapterQuery, LanguageServerTree, LanguageServerTreeNode, LaunchDisposition,
ManifestQueryDelegate, ManifestTree,
}, },
prettier_store::{self, PrettierStore, PrettierStoreEvent}, prettier_store::{self, PrettierStore, PrettierStoreEvent},
project_settings::{LspSettings, ProjectSettings}, project_settings::{LspSettings, ProjectSettings},
@ -1036,7 +1037,7 @@ impl LocalLspStore {
else { else {
return Vec::new(); return Vec::new();
}; };
let delegate = LocalLspAdapterDelegate::from_local_lsp(self, &worktree, cx); let delegate = Arc::new(ManifestQueryDelegate::new(worktree.read(cx).snapshot()));
let root = self.lsp_tree.update(cx, |this, cx| { let root = self.lsp_tree.update(cx, |this, cx| {
this.get( this.get(
project_path, project_path,
@ -2290,7 +2291,8 @@ impl LocalLspStore {
}) })
.map(|(delegate, servers)| (true, delegate, servers)) .map(|(delegate, servers)| (true, delegate, servers))
.unwrap_or_else(|| { .unwrap_or_else(|| {
let delegate = LocalLspAdapterDelegate::from_local_lsp(self, &worktree, cx); let lsp_delegate = LocalLspAdapterDelegate::from_local_lsp(self, &worktree, cx);
let delegate = Arc::new(ManifestQueryDelegate::new(worktree.read(cx).snapshot()));
let servers = self let servers = self
.lsp_tree .lsp_tree
.clone() .clone()
@ -2304,7 +2306,7 @@ impl LocalLspStore {
) )
.collect::<Vec<_>>() .collect::<Vec<_>>()
}); });
(false, delegate, servers) (false, lsp_delegate, servers)
}); });
let servers = servers let servers = servers
.into_iter() .into_iter()
@ -3585,6 +3587,7 @@ impl LspStore {
prettier_store: Entity<PrettierStore>, prettier_store: Entity<PrettierStore>,
toolchain_store: Entity<ToolchainStore>, toolchain_store: Entity<ToolchainStore>,
environment: Entity<ProjectEnvironment>, environment: Entity<ProjectEnvironment>,
manifest_tree: Entity<ManifestTree>,
languages: Arc<LanguageRegistry>, languages: Arc<LanguageRegistry>,
http_client: Arc<dyn HttpClient>, http_client: Arc<dyn HttpClient>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
@ -3618,7 +3621,7 @@ impl LspStore {
sender, sender,
) )
}; };
let manifest_tree = ManifestTree::new(worktree_store.clone(), cx);
Self { Self {
mode: LspStoreMode::Local(LocalLspStore { mode: LspStoreMode::Local(LocalLspStore {
weak: cx.weak_entity(), weak: cx.weak_entity(),
@ -4465,10 +4468,13 @@ impl LspStore {
) )
.map(|(delegate, servers)| (true, delegate, servers)) .map(|(delegate, servers)| (true, delegate, servers))
.or_else(|| { .or_else(|| {
let delegate = adapters let lsp_delegate = adapters
.entry(worktree_id) .entry(worktree_id)
.or_insert_with(|| get_adapter(worktree_id, cx)) .or_insert_with(|| get_adapter(worktree_id, cx))
.clone()?; .clone()?;
let delegate = Arc::new(ManifestQueryDelegate::new(
worktree.read(cx).snapshot(),
));
let path = file let path = file
.path() .path()
.parent() .parent()
@ -4483,7 +4489,7 @@ impl LspStore {
cx, cx,
); );
Some((false, delegate, nodes.collect())) Some((false, lsp_delegate, nodes.collect()))
}) })
else { else {
continue; continue;
@ -6476,7 +6482,7 @@ impl LspStore {
worktree_id, worktree_id,
path: Arc::from("".as_ref()), path: Arc::from("".as_ref()),
}; };
let delegate = LocalLspAdapterDelegate::from_local_lsp(local, &worktree, cx); let delegate = Arc::new(ManifestQueryDelegate::new(worktree.read(cx).snapshot()));
local.lsp_tree.update(cx, |language_server_tree, cx| { local.lsp_tree.update(cx, |language_server_tree, cx| {
for node in language_server_tree.get( for node in language_server_tree.get(
path, path,
@ -10204,14 +10210,6 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
self.worktree.id() self.worktree.id()
} }
fn exists(&self, path: &Path, is_dir: Option<bool>) -> bool {
self.worktree.entry_for_path(path).map_or(false, |entry| {
is_dir.map_or(true, |is_required_to_be_dir| {
is_required_to_be_dir == entry.is_dir()
})
})
}
fn worktree_root_path(&self) -> &Path { fn worktree_root_path(&self) -> &Path {
self.worktree.abs_path().as_ref() self.worktree.abs_path().as_ref()
} }

View file

@ -11,16 +11,17 @@ use std::{
borrow::Borrow, borrow::Borrow,
collections::{BTreeMap, hash_map::Entry}, collections::{BTreeMap, hash_map::Entry},
ops::ControlFlow, ops::ControlFlow,
path::Path,
sync::Arc, sync::Arc,
}; };
use collections::HashMap; use collections::HashMap;
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription}; use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription};
use language::{LspAdapterDelegate, ManifestName, ManifestQuery}; use language::{ManifestDelegate, ManifestName, ManifestQuery};
pub use manifest_store::ManifestProviders; pub use manifest_store::ManifestProviders;
use path_trie::{LabelPresence, RootPathTrie, TriePath}; use path_trie::{LabelPresence, RootPathTrie, TriePath};
use settings::{SettingsStore, WorktreeId}; use settings::{SettingsStore, WorktreeId};
use worktree::{Event as WorktreeEvent, Worktree}; use worktree::{Event as WorktreeEvent, Snapshot, Worktree};
use crate::{ use crate::{
ProjectPath, ProjectPath,
@ -89,7 +90,7 @@ pub(crate) enum ManifestTreeEvent {
impl EventEmitter<ManifestTreeEvent> for ManifestTree {} impl EventEmitter<ManifestTreeEvent> for ManifestTree {}
impl ManifestTree { impl ManifestTree {
pub(crate) fn new(worktree_store: Entity<WorktreeStore>, cx: &mut App) -> Entity<Self> { pub fn new(worktree_store: Entity<WorktreeStore>, cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self { cx.new(|cx| Self {
root_points: Default::default(), root_points: Default::default(),
_subscriptions: [ _subscriptions: [
@ -106,11 +107,11 @@ impl ManifestTree {
worktree_store, worktree_store,
}) })
} }
fn root_for_path( pub(crate) fn root_for_path(
&mut self, &mut self,
ProjectPath { worktree_id, path }: ProjectPath, ProjectPath { worktree_id, path }: ProjectPath,
manifests: &mut dyn Iterator<Item = ManifestName>, manifests: &mut dyn Iterator<Item = ManifestName>,
delegate: Arc<dyn LspAdapterDelegate>, delegate: Arc<dyn ManifestDelegate>,
cx: &mut App, cx: &mut App,
) -> BTreeMap<ManifestName, ProjectPath> { ) -> BTreeMap<ManifestName, ProjectPath> {
debug_assert_eq!(delegate.worktree_id(), worktree_id); debug_assert_eq!(delegate.worktree_id(), worktree_id);
@ -218,3 +219,26 @@ impl ManifestTree {
} }
} }
} }
pub(crate) struct ManifestQueryDelegate {
worktree: Snapshot,
}
impl ManifestQueryDelegate {
pub fn new(worktree: Snapshot) -> Self {
Self { worktree }
}
}
impl ManifestDelegate for ManifestQueryDelegate {
fn exists(&self, path: &Path, is_dir: Option<bool>) -> bool {
self.worktree.entry_for_path(path).map_or(false, |entry| {
is_dir.map_or(true, |is_required_to_be_dir| {
is_required_to_be_dir == entry.is_dir()
})
})
}
fn worktree_id(&self) -> WorktreeId {
self.worktree.id()
}
}

View file

@ -16,7 +16,7 @@ use std::{
use collections::{HashMap, IndexMap}; use collections::{HashMap, IndexMap};
use gpui::{App, AppContext as _, Entity, Subscription}; use gpui::{App, AppContext as _, Entity, Subscription};
use language::{ use language::{
Attach, CachedLspAdapter, LanguageName, LanguageRegistry, LspAdapterDelegate, Attach, CachedLspAdapter, LanguageName, LanguageRegistry, ManifestDelegate,
language_settings::AllLanguageSettings, language_settings::AllLanguageSettings,
}; };
use lsp::LanguageServerName; use lsp::LanguageServerName;
@ -151,7 +151,7 @@ impl LanguageServerTree {
&'a mut self, &'a mut self,
path: ProjectPath, path: ProjectPath,
query: AdapterQuery<'_>, query: AdapterQuery<'_>,
delegate: Arc<dyn LspAdapterDelegate>, delegate: Arc<dyn ManifestDelegate>,
cx: &mut App, cx: &mut App,
) -> impl Iterator<Item = LanguageServerTreeNode> + 'a { ) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
let settings_location = SettingsLocation { let settings_location = SettingsLocation {
@ -181,7 +181,7 @@ impl LanguageServerTree {
LanguageServerName, LanguageServerName,
(LspSettings, BTreeSet<LanguageName>, Arc<CachedLspAdapter>), (LspSettings, BTreeSet<LanguageName>, Arc<CachedLspAdapter>),
>, >,
delegate: Arc<dyn LspAdapterDelegate>, delegate: Arc<dyn ManifestDelegate>,
cx: &mut App, cx: &mut App,
) -> impl Iterator<Item = LanguageServerTreeNode> + 'a { ) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
let worktree_id = path.worktree_id; let worktree_id = path.worktree_id;
@ -401,7 +401,7 @@ impl<'tree> ServerTreeRebase<'tree> {
&'a mut self, &'a mut self,
path: ProjectPath, path: ProjectPath,
query: AdapterQuery<'_>, query: AdapterQuery<'_>,
delegate: Arc<dyn LspAdapterDelegate>, delegate: Arc<dyn ManifestDelegate>,
cx: &mut App, cx: &mut App,
) -> impl Iterator<Item = LanguageServerTreeNode> + 'a { ) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
let settings_location = SettingsLocation { let settings_location = SettingsLocation {

View file

@ -35,6 +35,7 @@ pub use git_store::{
ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate, ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate,
git_traversal::{ChildEntriesGitIter, GitEntry, GitEntryRef, GitTraversal}, git_traversal::{ChildEntriesGitIter, GitEntry, GitEntryRef, GitTraversal},
}; };
pub use manifest_tree::ManifestTree;
use anyhow::{Context as _, Result, anyhow}; use anyhow::{Context as _, Result, anyhow};
use buffer_store::{BufferStore, BufferStoreEvent}; use buffer_store::{BufferStore, BufferStoreEvent};
@ -874,11 +875,13 @@ impl Project {
cx.new(|cx| ContextServerStore::new(worktree_store.clone(), cx)); cx.new(|cx| ContextServerStore::new(worktree_store.clone(), cx));
let environment = cx.new(|_| ProjectEnvironment::new(env)); let environment = cx.new(|_| ProjectEnvironment::new(env));
let manifest_tree = ManifestTree::new(worktree_store.clone(), cx);
let toolchain_store = cx.new(|cx| { let toolchain_store = cx.new(|cx| {
ToolchainStore::local( ToolchainStore::local(
languages.clone(), languages.clone(),
worktree_store.clone(), worktree_store.clone(),
environment.clone(), environment.clone(),
manifest_tree.clone(),
cx, cx,
) )
}); });
@ -946,6 +949,7 @@ impl Project {
prettier_store.clone(), prettier_store.clone(),
toolchain_store.clone(), toolchain_store.clone(),
environment.clone(), environment.clone(),
manifest_tree,
languages.clone(), languages.clone(),
client.http_client(), client.http_client(),
fs.clone(), fs.clone(),
@ -3084,16 +3088,13 @@ impl Project {
path: ProjectPath, path: ProjectPath,
language_name: LanguageName, language_name: LanguageName,
cx: &App, cx: &App,
) -> Task<Option<ToolchainList>> { ) -> Task<Option<(ToolchainList, Arc<Path>)>> {
if let Some(toolchain_store) = self.toolchain_store.clone() { if let Some(toolchain_store) = self.toolchain_store.as_ref().map(Entity::downgrade) {
cx.spawn(async move |cx| { cx.spawn(async move |cx| {
cx.update(|cx| { toolchain_store
toolchain_store .update(cx, |this, cx| this.list_toolchains(path, language_name, cx))
.read(cx) .ok()?
.list_toolchains(path, language_name, cx) .await
})
.ok()?
.await
}) })
} else { } else {
Task::ready(None) Task::ready(None)

View file

@ -19,7 +19,11 @@ use rpc::{
use settings::WorktreeId; use settings::WorktreeId;
use util::ResultExt as _; use util::ResultExt as _;
use crate::{ProjectEnvironment, ProjectPath, worktree_store::WorktreeStore}; use crate::{
ProjectEnvironment, ProjectPath,
manifest_tree::{ManifestQueryDelegate, ManifestTree},
worktree_store::WorktreeStore,
};
pub struct ToolchainStore(ToolchainStoreInner); pub struct ToolchainStore(ToolchainStoreInner);
enum ToolchainStoreInner { enum ToolchainStoreInner {
@ -42,6 +46,7 @@ impl ToolchainStore {
languages: Arc<LanguageRegistry>, languages: Arc<LanguageRegistry>,
worktree_store: Entity<WorktreeStore>, worktree_store: Entity<WorktreeStore>,
project_environment: Entity<ProjectEnvironment>, project_environment: Entity<ProjectEnvironment>,
manifest_tree: Entity<ManifestTree>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
let entity = cx.new(|_| LocalToolchainStore { let entity = cx.new(|_| LocalToolchainStore {
@ -49,6 +54,7 @@ impl ToolchainStore {
worktree_store, worktree_store,
project_environment, project_environment,
active_toolchains: Default::default(), active_toolchains: Default::default(),
manifest_tree,
}); });
let subscription = cx.subscribe(&entity, |_, _, e: &ToolchainStoreEvent, cx| { let subscription = cx.subscribe(&entity, |_, _, e: &ToolchainStoreEvent, cx| {
cx.emit(e.clone()) cx.emit(e.clone())
@ -80,11 +86,11 @@ impl ToolchainStore {
&self, &self,
path: ProjectPath, path: ProjectPath,
language_name: LanguageName, language_name: LanguageName,
cx: &App, cx: &mut Context<Self>,
) -> Task<Option<ToolchainList>> { ) -> Task<Option<(ToolchainList, Arc<Path>)>> {
match &self.0 { match &self.0 {
ToolchainStoreInner::Local(local, _) => { ToolchainStoreInner::Local(local, _) => {
local.read(cx).list_toolchains(path, language_name, cx) local.update(cx, |this, cx| this.list_toolchains(path, language_name, cx))
} }
ToolchainStoreInner::Remote(remote) => { ToolchainStoreInner::Remote(remote) => {
remote.read(cx).list_toolchains(path, language_name, cx) remote.read(cx).list_toolchains(path, language_name, cx)
@ -181,7 +187,7 @@ impl ToolchainStore {
})? })?
.await; .await;
let has_values = toolchains.is_some(); let has_values = toolchains.is_some();
let groups = if let Some(toolchains) = &toolchains { let groups = if let Some((toolchains, _)) = &toolchains {
toolchains toolchains
.groups .groups
.iter() .iter()
@ -195,8 +201,8 @@ impl ToolchainStore {
} else { } else {
vec![] vec![]
}; };
let toolchains = if let Some(toolchains) = toolchains { let (toolchains, relative_path) = if let Some((toolchains, relative_path)) = toolchains {
toolchains let toolchains = toolchains
.toolchains .toolchains
.into_iter() .into_iter()
.map(|toolchain| { .map(|toolchain| {
@ -207,15 +213,17 @@ impl ToolchainStore {
raw_json: toolchain.as_json.to_string(), raw_json: toolchain.as_json.to_string(),
} }
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>();
(toolchains, relative_path)
} else { } else {
vec![] (vec![], Arc::from(Path::new("")))
}; };
Ok(proto::ListToolchainsResponse { Ok(proto::ListToolchainsResponse {
has_values, has_values,
toolchains, toolchains,
groups, groups,
relative_worktree_path: Some(relative_path.to_string_lossy().into_owned()),
}) })
} }
pub fn as_language_toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> { pub fn as_language_toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
@ -231,6 +239,7 @@ struct LocalToolchainStore {
worktree_store: Entity<WorktreeStore>, worktree_store: Entity<WorktreeStore>,
project_environment: Entity<ProjectEnvironment>, project_environment: Entity<ProjectEnvironment>,
active_toolchains: BTreeMap<(WorktreeId, LanguageName), BTreeMap<Arc<Path>, Toolchain>>, active_toolchains: BTreeMap<(WorktreeId, LanguageName), BTreeMap<Arc<Path>, Toolchain>>,
manifest_tree: Entity<ManifestTree>,
} }
#[async_trait(?Send)] #[async_trait(?Send)]
@ -312,36 +321,73 @@ impl LocalToolchainStore {
}) })
} }
pub(crate) fn list_toolchains( pub(crate) fn list_toolchains(
&self, &mut self,
path: ProjectPath, path: ProjectPath,
language_name: LanguageName, language_name: LanguageName,
cx: &App, cx: &mut Context<Self>,
) -> Task<Option<ToolchainList>> { ) -> Task<Option<(ToolchainList, Arc<Path>)>> {
let registry = self.languages.clone(); let registry = self.languages.clone();
let Some(abs_path) = self
.worktree_store let manifest_tree = self.manifest_tree.downgrade();
.read(cx)
.worktree_for_id(path.worktree_id, cx)
.map(|worktree| worktree.read(cx).abs_path())
else {
return Task::ready(None);
};
let environment = self.project_environment.clone(); let environment = self.project_environment.clone();
cx.spawn(async move |cx| { cx.spawn(async move |this, cx| {
let language = cx
.background_spawn(registry.language_for_name(language_name.as_ref()))
.await
.ok()?;
let toolchains = language.toolchain_lister()?;
let manifest_name = toolchains.manifest_name();
let (snapshot, worktree) = this
.update(cx, |this, cx| {
this.worktree_store
.read(cx)
.worktree_for_id(path.worktree_id, cx)
.map(|worktree| (worktree.read(cx).snapshot(), worktree))
})
.ok()
.flatten()?;
let worktree_id = snapshot.id();
let worktree_root = snapshot.abs_path().to_path_buf();
let relative_path = manifest_tree
.update(cx, |this, cx| {
this.root_for_path(
path,
&mut std::iter::once(manifest_name.clone()),
Arc::new(ManifestQueryDelegate::new(snapshot)),
cx,
)
})
.ok()?
.remove(&manifest_name)
.unwrap_or_else(|| ProjectPath {
path: Arc::from(Path::new("")),
worktree_id,
});
let abs_path = worktree
.update(cx, |this, _| this.absolutize(&relative_path.path).ok())
.ok()
.flatten()?;
let project_env = environment let project_env = environment
.update(cx, |environment, cx| { .update(cx, |environment, cx| {
environment.get_directory_environment(abs_path.clone(), cx) environment.get_directory_environment(abs_path.as_path().into(), cx)
}) })
.ok()? .ok()?
.await; .await;
cx.background_spawn(async move { cx.background_spawn(async move {
let language = registry Some((
.language_for_name(language_name.as_ref()) toolchains
.await .list(
.ok()?; worktree_root,
let toolchains = language.toolchain_lister()?; Some(relative_path.path.clone())
Some(toolchains.list(abs_path.to_path_buf(), project_env).await) .filter(|_| *relative_path.path != *Path::new("")),
project_env,
)
.await,
relative_path.path,
))
}) })
.await .await
}) })
@ -404,7 +450,7 @@ impl RemoteToolchainStore {
path: ProjectPath, path: ProjectPath,
language_name: LanguageName, language_name: LanguageName,
cx: &App, cx: &App,
) -> Task<Option<ToolchainList>> { ) -> Task<Option<(ToolchainList, Arc<Path>)>> {
let project_id = self.project_id; let project_id = self.project_id;
let client = self.client.clone(); let client = self.client.clone();
cx.background_spawn(async move { cx.background_spawn(async move {
@ -444,11 +490,20 @@ impl RemoteToolchainStore {
Some((usize::try_from(group.start_index).ok()?, group.name.into())) Some((usize::try_from(group.start_index).ok()?, group.name.into()))
}) })
.collect(); .collect();
Some(ToolchainList { let relative_path = Arc::from(Path::new(
toolchains, response
default: None, .relative_worktree_path
groups, .as_deref()
}) .unwrap_or_default(),
));
Some((
ToolchainList {
toolchains,
default: None,
groups,
},
relative_path,
))
}) })
} }
pub(crate) fn active_toolchain( pub(crate) fn active_toolchain(

View file

@ -23,6 +23,7 @@ message ListToolchainsResponse {
repeated Toolchain toolchains = 1; repeated Toolchain toolchains = 1;
bool has_values = 2; bool has_values = 2;
repeated ToolchainGroup groups = 3; repeated ToolchainGroup groups = 3;
optional string relative_worktree_path = 4;
} }
message ActivateToolchain { message ActivateToolchain {

View file

@ -9,8 +9,8 @@ use http_client::HttpClient;
use language::{Buffer, BufferEvent, LanguageRegistry, proto::serialize_operation}; use language::{Buffer, BufferEvent, LanguageRegistry, proto::serialize_operation};
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use project::{ use project::{
LspStore, LspStoreEvent, PrettierStore, ProjectEnvironment, ProjectPath, ToolchainStore, LspStore, LspStoreEvent, ManifestTree, PrettierStore, ProjectEnvironment, ProjectPath,
WorktreeId, ToolchainStore, WorktreeId,
buffer_store::{BufferStore, BufferStoreEvent}, buffer_store::{BufferStore, BufferStoreEvent},
debugger::{breakpoint_store::BreakpointStore, dap_store::DapStore}, debugger::{breakpoint_store::BreakpointStore, dap_store::DapStore},
git_store::GitStore, git_store::GitStore,
@ -87,12 +87,13 @@ impl HeadlessProject {
}); });
let environment = cx.new(|_| ProjectEnvironment::new(None)); let environment = cx.new(|_| ProjectEnvironment::new(None));
let manifest_tree = ManifestTree::new(worktree_store.clone(), cx);
let toolchain_store = cx.new(|cx| { let toolchain_store = cx.new(|cx| {
ToolchainStore::local( ToolchainStore::local(
languages.clone(), languages.clone(),
worktree_store.clone(), worktree_store.clone(),
environment.clone(), environment.clone(),
manifest_tree.clone(),
cx, cx,
) )
}); });
@ -172,6 +173,7 @@ impl HeadlessProject {
prettier_store.clone(), prettier_store.clone(),
toolchain_store.clone(), toolchain_store.clone(),
environment, environment,
manifest_tree,
languages.clone(), languages.clone(),
http_client.clone(), http_client.clone(),
fs.clone(), fs.clone(),

View file

@ -92,7 +92,7 @@ pub fn python_env_kernel_specifications(
let background_executor = cx.background_executor().clone(); let background_executor = cx.background_executor().clone();
async move { async move {
let toolchains = if let Some(toolchains) = toolchains.await { let toolchains = if let Some((toolchains, _)) = toolchains.await {
toolchains toolchains
} else { } else {
return Ok(Vec::new()); return Ok(Vec::new());

View file

@ -158,7 +158,7 @@ impl ActiveToolchain {
let project = workspace let project = workspace
.read_with(cx, |this, _| this.project().clone()) .read_with(cx, |this, _| this.project().clone())
.ok()?; .ok()?;
let toolchains = cx let (toolchains, relative_path) = cx
.update(|_, cx| { .update(|_, cx| {
project.read(cx).available_toolchains( project.read(cx).available_toolchains(
ProjectPath { ProjectPath {

View file

@ -10,7 +10,7 @@ use gpui::{
use language::{LanguageName, Toolchain, ToolchainList}; use language::{LanguageName, Toolchain, ToolchainList};
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::{Project, ProjectPath, WorktreeId}; use project::{Project, ProjectPath, WorktreeId};
use std::{path::Path, sync::Arc}; use std::{borrow::Cow, path::Path, sync::Arc};
use ui::{HighlightedLabel, ListItem, ListItemSpacing, prelude::*}; use ui::{HighlightedLabel, ListItem, ListItemSpacing, prelude::*};
use util::ResultExt; use util::ResultExt;
use workspace::{ModalView, Workspace}; use workspace::{ModalView, Workspace};
@ -172,18 +172,8 @@ impl ToolchainSelectorDelegate {
let relative_path = this let relative_path = this
.read_with(cx, |this, _| this.delegate.relative_path.clone()) .read_with(cx, |this, _| this.delegate.relative_path.clone())
.ok()?; .ok()?;
let placeholder_text = format!(
"Select a {} for `{}`…",
term.to_lowercase(),
relative_path.to_string_lossy()
)
.into();
let _ = this.update_in(cx, move |this, window, cx| {
this.delegate.placeholder_text = placeholder_text;
this.refresh_placeholder(window, cx);
});
let available_toolchains = project let (available_toolchains, relative_path) = project
.update(cx, |this, cx| { .update(cx, |this, cx| {
this.available_toolchains( this.available_toolchains(
ProjectPath { ProjectPath {
@ -196,6 +186,21 @@ impl ToolchainSelectorDelegate {
}) })
.ok()? .ok()?
.await?; .await?;
let pretty_path = {
let path = relative_path.to_string_lossy();
if path.is_empty() {
Cow::Borrowed("worktree root")
} else {
Cow::Owned(format!("`{}`", path))
}
};
let placeholder_text =
format!("Select a {} for {pretty_path}", term.to_lowercase(),).into();
let _ = this.update_in(cx, move |this, window, cx| {
this.delegate.relative_path = relative_path;
this.delegate.placeholder_text = placeholder_text;
this.refresh_placeholder(window, cx);
});
let _ = this.update_in(cx, move |this, window, cx| { let _ = this.update_in(cx, move |this, window, cx| {
this.delegate.candidates = available_toolchains; this.delegate.candidates = available_toolchains;