From 9dd18e5ee175bfd9529fede6f71f3033c26b4384 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 2 Jun 2025 18:29:06 +0200 Subject: [PATCH] python: Re-land usage of source file path in toolchain picker (#31893) This reverts commit 1e55e88c1822402566bdec8a893ec0429d8ee5e6. 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. --- crates/language/src/language.rs | 3 +- crates/language/src/manifest.rs | 10 +- crates/language/src/toolchain.rs | 5 +- crates/languages/src/python.rs | 13 +- crates/project/src/lsp_store.rs | 30 ++--- crates/project/src/manifest_tree.rs | 34 ++++- .../project/src/manifest_tree/server_tree.rs | 8 +- crates/project/src/project.rs | 19 +-- crates/project/src/toolchain_store.rs | 123 +++++++++++++----- crates/proto/proto/toolchain.proto | 1 + crates/remote_server/src/headless_project.rs | 8 +- crates/repl/src/kernels/mod.rs | 2 +- .../src/active_toolchain.rs | 2 +- .../src/toolchain_selector.rs | 29 +++-- 14 files changed, 195 insertions(+), 92 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 744eed0ddc..f4c90c64e7 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -34,7 +34,7 @@ pub use highlight_map::HighlightMap; use http_client::HttpClient; pub use language_registry::{LanguageName, LoadedLanguage}; use lsp::{CodeActionKind, InitializeParams, LanguageServerBinary, LanguageServerBinaryOptions}; -pub use manifest::{ManifestName, ManifestProvider, ManifestQuery}; +pub use manifest::{ManifestDelegate, ManifestName, ManifestProvider, ManifestQuery}; use parking_lot::Mutex; use regex::Regex; use schemars::{ @@ -323,7 +323,6 @@ pub trait LspAdapterDelegate: Send + Sync { fn http_client(&self) -> Arc; fn worktree_id(&self) -> WorktreeId; fn worktree_root_path(&self) -> &Path; - fn exists(&self, path: &Path, is_dir: Option) -> bool; fn update_status(&self, language: LanguageServerName, status: BinaryStatus); fn registered_lsp_adapters(&self) -> Vec>; async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option>; diff --git a/crates/language/src/manifest.rs b/crates/language/src/manifest.rs index f55c875070..37505fec3b 100644 --- a/crates/language/src/manifest.rs +++ b/crates/language/src/manifest.rs @@ -1,8 +1,7 @@ use std::{borrow::Borrow, path::Path, sync::Arc}; use gpui::SharedString; - -use crate::LspAdapterDelegate; +use settings::WorktreeId; #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct ManifestName(SharedString); @@ -39,10 +38,15 @@ pub struct ManifestQuery { /// Path to the file, relative to worktree root. pub path: Arc, pub depth: usize, - pub delegate: Arc, + pub delegate: Arc, } pub trait ManifestProvider { fn name(&self) -> ManifestName; fn search(&self, query: ManifestQuery) -> Option>; } + +pub trait ManifestDelegate: Send + Sync { + fn worktree_id(&self) -> WorktreeId; + fn exists(&self, path: &Path, is_dir: Option) -> bool; +} diff --git a/crates/language/src/toolchain.rs b/crates/language/src/toolchain.rs index fb738edb88..1f4b038f68 100644 --- a/crates/language/src/toolchain.rs +++ b/crates/language/src/toolchain.rs @@ -14,7 +14,7 @@ use collections::HashMap; use gpui::{AsyncApp, SharedString}; use settings::WorktreeId; -use crate::LanguageName; +use crate::{LanguageName, ManifestName}; /// Represents a single toolchain. #[derive(Clone, Debug)] @@ -44,10 +44,13 @@ pub trait ToolchainLister: Send + Sync { async fn list( &self, worktree_root: PathBuf, + subroot_relative_path: Option>, project_env: Option>, ) -> ToolchainList; // Returns a term which we should use in UI to refer to a toolchain. fn term(&self) -> SharedString; + /// Returns the name of the manifest file for this toolchain. + fn manifest_name(&self) -> ManifestName; } #[async_trait(?Send)] diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 0a5c9dfc9e..a35608b473 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -693,9 +693,13 @@ fn get_worktree_venv_declaration(worktree_root: &Path) -> Option { #[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, + subroot_relative_path: Option>, project_env: Option>, ) -> ToolchainList { let env = project_env.unwrap_or_default(); @@ -706,7 +710,14 @@ impl ToolchainLister for PythonToolchainProvider { &environment, ); 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() { locator.configure(&config); } diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index fdf12e8f04..2d5da1c5b3 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -10,7 +10,8 @@ use crate::{ lsp_command::{self, *}, lsp_store, manifest_tree::{ - AdapterQuery, LanguageServerTree, LanguageServerTreeNode, LaunchDisposition, ManifestTree, + AdapterQuery, LanguageServerTree, LanguageServerTreeNode, LaunchDisposition, + ManifestQueryDelegate, ManifestTree, }, prettier_store::{self, PrettierStore, PrettierStoreEvent}, project_settings::{LspSettings, ProjectSettings}, @@ -1036,7 +1037,7 @@ impl LocalLspStore { else { 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| { this.get( project_path, @@ -2290,7 +2291,8 @@ impl LocalLspStore { }) .map(|(delegate, servers)| (true, delegate, servers)) .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 .lsp_tree .clone() @@ -2304,7 +2306,7 @@ impl LocalLspStore { ) .collect::>() }); - (false, delegate, servers) + (false, lsp_delegate, servers) }); let servers = servers .into_iter() @@ -3585,6 +3587,7 @@ impl LspStore { prettier_store: Entity, toolchain_store: Entity, environment: Entity, + manifest_tree: Entity, languages: Arc, http_client: Arc, fs: Arc, @@ -3618,7 +3621,7 @@ impl LspStore { sender, ) }; - let manifest_tree = ManifestTree::new(worktree_store.clone(), cx); + Self { mode: LspStoreMode::Local(LocalLspStore { weak: cx.weak_entity(), @@ -4465,10 +4468,13 @@ impl LspStore { ) .map(|(delegate, servers)| (true, delegate, servers)) .or_else(|| { - let delegate = adapters + let lsp_delegate = adapters .entry(worktree_id) .or_insert_with(|| get_adapter(worktree_id, cx)) .clone()?; + let delegate = Arc::new(ManifestQueryDelegate::new( + worktree.read(cx).snapshot(), + )); let path = file .path() .parent() @@ -4483,7 +4489,7 @@ impl LspStore { cx, ); - Some((false, delegate, nodes.collect())) + Some((false, lsp_delegate, nodes.collect())) }) else { continue; @@ -6476,7 +6482,7 @@ impl LspStore { worktree_id, 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| { for node in language_server_tree.get( path, @@ -10204,14 +10210,6 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate { self.worktree.id() } - fn exists(&self, path: &Path, is_dir: Option) -> 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 { self.worktree.abs_path().as_ref() } diff --git a/crates/project/src/manifest_tree.rs b/crates/project/src/manifest_tree.rs index 3fc37f37e4..7266acb5b4 100644 --- a/crates/project/src/manifest_tree.rs +++ b/crates/project/src/manifest_tree.rs @@ -11,16 +11,17 @@ use std::{ borrow::Borrow, collections::{BTreeMap, hash_map::Entry}, ops::ControlFlow, + path::Path, sync::Arc, }; use collections::HashMap; use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription}; -use language::{LspAdapterDelegate, ManifestName, ManifestQuery}; +use language::{ManifestDelegate, ManifestName, ManifestQuery}; pub use manifest_store::ManifestProviders; use path_trie::{LabelPresence, RootPathTrie, TriePath}; use settings::{SettingsStore, WorktreeId}; -use worktree::{Event as WorktreeEvent, Worktree}; +use worktree::{Event as WorktreeEvent, Snapshot, Worktree}; use crate::{ ProjectPath, @@ -89,7 +90,7 @@ pub(crate) enum ManifestTreeEvent { impl EventEmitter for ManifestTree {} impl ManifestTree { - pub(crate) fn new(worktree_store: Entity, cx: &mut App) -> Entity { + pub fn new(worktree_store: Entity, cx: &mut App) -> Entity { cx.new(|cx| Self { root_points: Default::default(), _subscriptions: [ @@ -106,11 +107,11 @@ impl ManifestTree { worktree_store, }) } - fn root_for_path( + pub(crate) fn root_for_path( &mut self, ProjectPath { worktree_id, path }: ProjectPath, manifests: &mut dyn Iterator, - delegate: Arc, + delegate: Arc, cx: &mut App, ) -> BTreeMap { 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 { + 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() + } +} diff --git a/crates/project/src/manifest_tree/server_tree.rs b/crates/project/src/manifest_tree/server_tree.rs index cc41f3dff2..1ac990a508 100644 --- a/crates/project/src/manifest_tree/server_tree.rs +++ b/crates/project/src/manifest_tree/server_tree.rs @@ -16,7 +16,7 @@ use std::{ use collections::{HashMap, IndexMap}; use gpui::{App, AppContext as _, Entity, Subscription}; use language::{ - Attach, CachedLspAdapter, LanguageName, LanguageRegistry, LspAdapterDelegate, + Attach, CachedLspAdapter, LanguageName, LanguageRegistry, ManifestDelegate, language_settings::AllLanguageSettings, }; use lsp::LanguageServerName; @@ -151,7 +151,7 @@ impl LanguageServerTree { &'a mut self, path: ProjectPath, query: AdapterQuery<'_>, - delegate: Arc, + delegate: Arc, cx: &mut App, ) -> impl Iterator + 'a { let settings_location = SettingsLocation { @@ -181,7 +181,7 @@ impl LanguageServerTree { LanguageServerName, (LspSettings, BTreeSet, Arc), >, - delegate: Arc, + delegate: Arc, cx: &mut App, ) -> impl Iterator + 'a { let worktree_id = path.worktree_id; @@ -401,7 +401,7 @@ impl<'tree> ServerTreeRebase<'tree> { &'a mut self, path: ProjectPath, query: AdapterQuery<'_>, - delegate: Arc, + delegate: Arc, cx: &mut App, ) -> impl Iterator + 'a { let settings_location = SettingsLocation { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 22a53878a8..99ffb2055b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -35,6 +35,7 @@ pub use git_store::{ ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate, git_traversal::{ChildEntriesGitIter, GitEntry, GitEntryRef, GitTraversal}, }; +pub use manifest_tree::ManifestTree; use anyhow::{Context as _, Result, anyhow}; use buffer_store::{BufferStore, BufferStoreEvent}; @@ -874,11 +875,13 @@ impl Project { cx.new(|cx| ContextServerStore::new(worktree_store.clone(), cx)); let environment = cx.new(|_| ProjectEnvironment::new(env)); + let manifest_tree = ManifestTree::new(worktree_store.clone(), cx); let toolchain_store = cx.new(|cx| { ToolchainStore::local( languages.clone(), worktree_store.clone(), environment.clone(), + manifest_tree.clone(), cx, ) }); @@ -946,6 +949,7 @@ impl Project { prettier_store.clone(), toolchain_store.clone(), environment.clone(), + manifest_tree, languages.clone(), client.http_client(), fs.clone(), @@ -3084,16 +3088,13 @@ impl Project { path: ProjectPath, language_name: LanguageName, cx: &App, - ) -> Task> { - if let Some(toolchain_store) = self.toolchain_store.clone() { + ) -> Task)>> { + if let Some(toolchain_store) = self.toolchain_store.as_ref().map(Entity::downgrade) { cx.spawn(async move |cx| { - cx.update(|cx| { - toolchain_store - .read(cx) - .list_toolchains(path, language_name, cx) - }) - .ok()? - .await + toolchain_store + .update(cx, |this, cx| this.list_toolchains(path, language_name, cx)) + .ok()? + .await }) } else { Task::ready(None) diff --git a/crates/project/src/toolchain_store.rs b/crates/project/src/toolchain_store.rs index f758bd0d88..7be0aa4262 100644 --- a/crates/project/src/toolchain_store.rs +++ b/crates/project/src/toolchain_store.rs @@ -19,7 +19,11 @@ use rpc::{ use settings::WorktreeId; 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); enum ToolchainStoreInner { @@ -42,6 +46,7 @@ impl ToolchainStore { languages: Arc, worktree_store: Entity, project_environment: Entity, + manifest_tree: Entity, cx: &mut Context, ) -> Self { let entity = cx.new(|_| LocalToolchainStore { @@ -49,6 +54,7 @@ impl ToolchainStore { worktree_store, project_environment, active_toolchains: Default::default(), + manifest_tree, }); let subscription = cx.subscribe(&entity, |_, _, e: &ToolchainStoreEvent, cx| { cx.emit(e.clone()) @@ -80,11 +86,11 @@ impl ToolchainStore { &self, path: ProjectPath, language_name: LanguageName, - cx: &App, - ) -> Task> { + cx: &mut Context, + ) -> Task)>> { match &self.0 { 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) => { remote.read(cx).list_toolchains(path, language_name, cx) @@ -181,7 +187,7 @@ impl ToolchainStore { })? .await; let has_values = toolchains.is_some(); - let groups = if let Some(toolchains) = &toolchains { + let groups = if let Some((toolchains, _)) = &toolchains { toolchains .groups .iter() @@ -195,8 +201,8 @@ impl ToolchainStore { } else { vec![] }; - let toolchains = if let Some(toolchains) = toolchains { - toolchains + let (toolchains, relative_path) = if let Some((toolchains, relative_path)) = toolchains { + let toolchains = toolchains .toolchains .into_iter() .map(|toolchain| { @@ -207,15 +213,17 @@ impl ToolchainStore { raw_json: toolchain.as_json.to_string(), } }) - .collect::>() + .collect::>(); + (toolchains, relative_path) } else { - vec![] + (vec![], Arc::from(Path::new(""))) }; Ok(proto::ListToolchainsResponse { has_values, toolchains, groups, + relative_worktree_path: Some(relative_path.to_string_lossy().into_owned()), }) } pub fn as_language_toolchain_store(&self) -> Arc { @@ -231,6 +239,7 @@ struct LocalToolchainStore { worktree_store: Entity, project_environment: Entity, active_toolchains: BTreeMap<(WorktreeId, LanguageName), BTreeMap, Toolchain>>, + manifest_tree: Entity, } #[async_trait(?Send)] @@ -312,36 +321,73 @@ impl LocalToolchainStore { }) } pub(crate) fn list_toolchains( - &self, + &mut self, path: ProjectPath, language_name: LanguageName, - cx: &App, - ) -> Task> { + cx: &mut Context, + ) -> Task)>> { let registry = self.languages.clone(); - let Some(abs_path) = self - .worktree_store - .read(cx) - .worktree_for_id(path.worktree_id, cx) - .map(|worktree| worktree.read(cx).abs_path()) - else { - return Task::ready(None); - }; + + let manifest_tree = self.manifest_tree.downgrade(); + 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 .update(cx, |environment, cx| { - environment.get_directory_environment(abs_path.clone(), cx) + environment.get_directory_environment(abs_path.as_path().into(), cx) }) .ok()? .await; cx.background_spawn(async move { - let language = registry - .language_for_name(language_name.as_ref()) - .await - .ok()?; - let toolchains = language.toolchain_lister()?; - Some(toolchains.list(abs_path.to_path_buf(), project_env).await) + Some(( + toolchains + .list( + worktree_root, + Some(relative_path.path.clone()) + .filter(|_| *relative_path.path != *Path::new("")), + project_env, + ) + .await, + relative_path.path, + )) }) .await }) @@ -404,7 +450,7 @@ impl RemoteToolchainStore { path: ProjectPath, language_name: LanguageName, cx: &App, - ) -> Task> { + ) -> Task)>> { let project_id = self.project_id; let client = self.client.clone(); cx.background_spawn(async move { @@ -444,11 +490,20 @@ impl RemoteToolchainStore { Some((usize::try_from(group.start_index).ok()?, group.name.into())) }) .collect(); - Some(ToolchainList { - toolchains, - default: None, - groups, - }) + let relative_path = Arc::from(Path::new( + response + .relative_worktree_path + .as_deref() + .unwrap_or_default(), + )); + Some(( + ToolchainList { + toolchains, + default: None, + groups, + }, + relative_path, + )) }) } pub(crate) fn active_toolchain( diff --git a/crates/proto/proto/toolchain.proto b/crates/proto/proto/toolchain.proto index 9c24fb40f0..08844a307a 100644 --- a/crates/proto/proto/toolchain.proto +++ b/crates/proto/proto/toolchain.proto @@ -23,6 +23,7 @@ message ListToolchainsResponse { repeated Toolchain toolchains = 1; bool has_values = 2; repeated ToolchainGroup groups = 3; + optional string relative_worktree_path = 4; } message ActivateToolchain { diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 44ae3a003d..ab37050525 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -9,8 +9,8 @@ use http_client::HttpClient; use language::{Buffer, BufferEvent, LanguageRegistry, proto::serialize_operation}; use node_runtime::NodeRuntime; use project::{ - LspStore, LspStoreEvent, PrettierStore, ProjectEnvironment, ProjectPath, ToolchainStore, - WorktreeId, + LspStore, LspStoreEvent, ManifestTree, PrettierStore, ProjectEnvironment, ProjectPath, + ToolchainStore, WorktreeId, buffer_store::{BufferStore, BufferStoreEvent}, debugger::{breakpoint_store::BreakpointStore, dap_store::DapStore}, git_store::GitStore, @@ -87,12 +87,13 @@ impl HeadlessProject { }); let environment = cx.new(|_| ProjectEnvironment::new(None)); - + let manifest_tree = ManifestTree::new(worktree_store.clone(), cx); let toolchain_store = cx.new(|cx| { ToolchainStore::local( languages.clone(), worktree_store.clone(), environment.clone(), + manifest_tree.clone(), cx, ) }); @@ -172,6 +173,7 @@ impl HeadlessProject { prettier_store.clone(), toolchain_store.clone(), environment, + manifest_tree, languages.clone(), http_client.clone(), fs.clone(), diff --git a/crates/repl/src/kernels/mod.rs b/crates/repl/src/kernels/mod.rs index 25156b30f0..3c3b766612 100644 --- a/crates/repl/src/kernels/mod.rs +++ b/crates/repl/src/kernels/mod.rs @@ -92,7 +92,7 @@ pub fn python_env_kernel_specifications( let background_executor = cx.background_executor().clone(); async move { - let toolchains = if let Some(toolchains) = toolchains.await { + let toolchains = if let Some((toolchains, _)) = toolchains.await { toolchains } else { return Ok(Vec::new()); diff --git a/crates/toolchain_selector/src/active_toolchain.rs b/crates/toolchain_selector/src/active_toolchain.rs index 05370f64a2..631f66a83c 100644 --- a/crates/toolchain_selector/src/active_toolchain.rs +++ b/crates/toolchain_selector/src/active_toolchain.rs @@ -158,7 +158,7 @@ impl ActiveToolchain { let project = workspace .read_with(cx, |this, _| this.project().clone()) .ok()?; - let toolchains = cx + let (toolchains, relative_path) = cx .update(|_, cx| { project.read(cx).available_toolchains( ProjectPath { diff --git a/crates/toolchain_selector/src/toolchain_selector.rs b/crates/toolchain_selector/src/toolchain_selector.rs index 5a19b6a0b3..88b5b82b45 100644 --- a/crates/toolchain_selector/src/toolchain_selector.rs +++ b/crates/toolchain_selector/src/toolchain_selector.rs @@ -10,7 +10,7 @@ use gpui::{ use language::{LanguageName, Toolchain, ToolchainList}; use picker::{Picker, PickerDelegate}; 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 util::ResultExt; use workspace::{ModalView, Workspace}; @@ -172,18 +172,8 @@ impl ToolchainSelectorDelegate { let relative_path = this .read_with(cx, |this, _| this.delegate.relative_path.clone()) .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| { this.available_toolchains( ProjectPath { @@ -196,6 +186,21 @@ impl ToolchainSelectorDelegate { }) .ok()? .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| { this.delegate.candidates = available_toolchains;