toolchains: Use language-specific terms in UI (#20985)

Piotr Osiewicz created

Closes #ISSUE

Release Notes:

- N/A

Change summary

crates/language/src/toolchain.rs                    |  2 +
crates/languages/src/python.rs                      | 18 ++++++++++--
crates/picker/src/picker.rs                         | 13 +++++++++
crates/project/src/project.rs                       | 13 +++++++++
crates/toolchain_selector/src/active_toolchain.rs   | 21 ++++++++++++--
crates/toolchain_selector/src/toolchain_selector.rs | 18 +++++++++++-
6 files changed, 76 insertions(+), 9 deletions(-)

Detailed changes

crates/language/src/toolchain.rs πŸ”—

@@ -31,6 +31,8 @@ pub trait ToolchainLister: Send + Sync {
         worktree_root: PathBuf,
         project_env: Option<HashMap<String, String>>,
     ) -> ToolchainList;
+    // Returns a term which we should use in UI to refer to a toolchain.
+    fn term(&self) -> SharedString;
 }
 
 #[async_trait(?Send)]

crates/languages/src/python.rs πŸ”—

@@ -2,8 +2,8 @@ use anyhow::ensure;
 use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use collections::HashMap;
-use gpui::AsyncAppContext;
 use gpui::{AppContext, Task};
+use gpui::{AsyncAppContext, SharedString};
 use language::language_settings::language_settings;
 use language::LanguageName;
 use language::LanguageToolchainStore;
@@ -498,8 +498,17 @@ fn python_module_name_from_relative_path(relative_path: &str) -> String {
         .to_string()
 }
 
-#[derive(Default)]
-pub(crate) struct PythonToolchainProvider {}
+pub(crate) struct PythonToolchainProvider {
+    term: SharedString,
+}
+
+impl Default for PythonToolchainProvider {
+    fn default() -> Self {
+        Self {
+            term: SharedString::new_static("Virtual Environment"),
+        }
+    }
+}
 
 static ENV_PRIORITY_LIST: &'static [PythonEnvironmentKind] = &[
     // Prioritize non-Conda environments.
@@ -604,6 +613,9 @@ impl ToolchainLister for PythonToolchainProvider {
             groups: Default::default(),
         }
     }
+    fn term(&self) -> SharedString {
+        self.term.clone()
+    }
 }
 
 pub struct EnvironmentApi<'a> {

crates/picker/src/picker.rs πŸ”—

@@ -425,6 +425,19 @@ impl<D: PickerDelegate> Picker<D> {
         self.cancel(&menu::Cancel, cx);
     }
 
+    pub fn refresh_placeholder(&mut self, cx: &mut WindowContext<'_>) {
+        match &self.head {
+            Head::Editor(view) => {
+                let placeholder = self.delegate.placeholder_text(cx);
+                view.update(cx, |this, cx| {
+                    this.set_placeholder_text(placeholder, cx);
+                    cx.notify();
+                });
+            }
+            Head::Empty(_) => {}
+        }
+    }
+
     pub fn refresh(&mut self, cx: &mut ViewContext<Self>) {
         let query = self.query(cx);
         self.update_matches(query, cx);

crates/project/src/project.rs πŸ”—

@@ -2464,6 +2464,19 @@ impl Project {
             Task::ready(None)
         }
     }
+
+    pub async fn toolchain_term(
+        languages: Arc<LanguageRegistry>,
+        language_name: LanguageName,
+    ) -> Option<SharedString> {
+        languages
+            .language_for_name(&language_name.0)
+            .await
+            .ok()?
+            .toolchain_lister()
+            .map(|lister| lister.term())
+    }
+
     pub fn activate_toolchain(
         &self,
         worktree_id: WorktreeId,

crates/toolchain_selector/src/active_toolchain.rs πŸ”—

@@ -4,14 +4,15 @@ use gpui::{
     ViewContext, WeakModel, WeakView,
 };
 use language::{Buffer, BufferEvent, LanguageName, Toolchain};
-use project::WorktreeId;
-use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, Tooltip};
+use project::{Project, WorktreeId};
+use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, SharedString, Tooltip};
 use workspace::{item::ItemHandle, StatusItemView, Workspace};
 
 use crate::ToolchainSelector;
 
 pub struct ActiveToolchain {
     active_toolchain: Option<Toolchain>,
+    term: SharedString,
     workspace: WeakView<Workspace>,
     active_buffer: Option<(WorktreeId, WeakModel<Buffer>, Subscription)>,
     _update_toolchain_task: Task<Option<()>>,
@@ -22,6 +23,7 @@ impl ActiveToolchain {
         Self {
             active_toolchain: None,
             active_buffer: None,
+            term: SharedString::new_static("Toolchain"),
             workspace: workspace.weak_handle(),
 
             _update_toolchain_task: Self::spawn_tracker_task(cx),
@@ -44,7 +46,17 @@ impl ActiveToolchain {
                 .update(&mut cx, |this, _| Some(this.language()?.name()))
                 .ok()
                 .flatten()?;
-
+            let term = workspace
+                .update(&mut cx, |workspace, cx| {
+                    let languages = workspace.project().read(cx).languages();
+                    Project::toolchain_term(languages.clone(), language_name.clone())
+                })
+                .ok()?
+                .await?;
+            let _ = this.update(&mut cx, |this, cx| {
+                this.term = term;
+                cx.notify();
+            });
             let worktree_id = active_file
                 .update(&mut cx, |this, cx| Some(this.file()?.worktree_id(cx)))
                 .ok()
@@ -133,6 +145,7 @@ impl ActiveToolchain {
 impl Render for ActiveToolchain {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
         div().when_some(self.active_toolchain.as_ref(), |el, active_toolchain| {
+            let term = self.term.clone();
             el.child(
                 Button::new("change-toolchain", active_toolchain.name.clone())
                     .label_size(LabelSize::Small)
@@ -143,7 +156,7 @@ impl Render for ActiveToolchain {
                             });
                         }
                     }))
-                    .tooltip(|cx| Tooltip::text("Select Toolchain", cx)),
+                    .tooltip(move |cx| Tooltip::text(format!("Select {}", &term), cx)),
             )
         })
     }

crates/toolchain_selector/src/toolchain_selector.rs πŸ”—

@@ -126,6 +126,7 @@ pub struct ToolchainSelectorDelegate {
     workspace: WeakView<Workspace>,
     worktree_id: WorktreeId,
     worktree_abs_path_root: Arc<Path>,
+    placeholder_text: Arc<str>,
     _fetch_candidates_task: Task<Option<()>>,
 }
 
@@ -144,6 +145,17 @@ impl ToolchainSelectorDelegate {
         let _fetch_candidates_task = cx.spawn({
             let project = project.clone();
             move |this, mut cx| async move {
+                let term = project
+                    .update(&mut cx, |this, _| {
+                        Project::toolchain_term(this.languages().clone(), language_name.clone())
+                    })
+                    .ok()?
+                    .await?;
+                let placeholder_text = format!("Select a {}…", term.to_lowercase()).into();
+                let _ = this.update(&mut cx, move |this, cx| {
+                    this.delegate.placeholder_text = placeholder_text;
+                    this.refresh_placeholder(cx);
+                });
                 let available_toolchains = project
                     .update(&mut cx, |this, cx| {
                         this.available_toolchains(worktree_id, language_name, cx)
@@ -153,6 +165,7 @@ impl ToolchainSelectorDelegate {
 
                 let _ = this.update(&mut cx, move |this, cx| {
                     this.delegate.candidates = available_toolchains;
+
                     if let Some(active_toolchain) = active_toolchain {
                         if let Some(position) = this
                             .delegate
@@ -170,7 +183,7 @@ impl ToolchainSelectorDelegate {
                 Some(())
             }
         });
-
+        let placeholder_text = "Select a toolchain…".to_string().into();
         Self {
             toolchain_selector: language_selector,
             candidates: Default::default(),
@@ -179,6 +192,7 @@ impl ToolchainSelectorDelegate {
             workspace,
             worktree_id,
             worktree_abs_path_root,
+            placeholder_text,
             _fetch_candidates_task,
         }
     }
@@ -196,7 +210,7 @@ impl PickerDelegate for ToolchainSelectorDelegate {
     type ListItem = ListItem;
 
     fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
-        "Select a toolchain...".into()
+        self.placeholder_text.clone()
     }
 
     fn match_count(&self) -> usize {