From f881cacd8a2e014b179cb4eec110b55560947871 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 30 May 2025 22:28:56 +0300 Subject: [PATCH] Use both language and LSP icons for LSP tasks (#31773) Make more explicit which language LSP tasks are used. Before: ![image](https://github.com/user-attachments/assets/27f93c5f-942e-47a0-9b74-2c6d4d6248de) After: ![image (1)](https://github.com/user-attachments/assets/5a29fb0a-2e16-4c35-9dda-ae7925eaa034) ![image](https://github.com/user-attachments/assets/d1bf518e-63d1-4ebf-af3d-3c9d464c6532) Release Notes: - N/A --- crates/debugger_ui/src/new_session_modal.rs | 25 +++++++++++++++------ crates/editor/src/lsp_ext.rs | 23 ++++++++++++++----- crates/project/src/task_inventory.rs | 12 +++++++--- crates/tasks_ui/src/modal.rs | 25 ++++++++++++++++----- 4 files changed, 65 insertions(+), 20 deletions(-) diff --git a/crates/debugger_ui/src/new_session_modal.rs b/crates/debugger_ui/src/new_session_modal.rs index aeac24d3d1..115d03ba48 100644 --- a/crates/debugger_ui/src/new_session_modal.rs +++ b/crates/debugger_ui/src/new_session_modal.rs @@ -27,9 +27,9 @@ use theme::ThemeSettings; use ui::{ ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context, ContextMenu, Disableable, DropdownMenu, FluentBuilder, Icon, IconButton, IconName, IconSize, - InteractiveElement, IntoElement, Label, LabelCommon as _, ListItem, ListItemSpacing, - ParentElement, RenderOnce, SharedString, Styled, StyledExt, ToggleButton, ToggleState, - Toggleable, Window, div, h_flex, relative, rems, v_flex, + IconWithIndicator, Indicator, InteractiveElement, IntoElement, Label, LabelCommon as _, + ListItem, ListItemSpacing, ParentElement, RenderOnce, SharedString, Styled, StyledExt, + ToggleButton, ToggleState, Toggleable, Window, div, h_flex, relative, rems, v_flex, }; use util::ResultExt; use workspace::{ModalView, Workspace, pane}; @@ -1222,21 +1222,32 @@ impl PickerDelegate for DebugScenarioDelegate { let task_kind = &self.candidates[hit.candidate_id].0; let icon = match task_kind { - Some(TaskSourceKind::Lsp(..)) => Some(Icon::new(IconName::BoltFilled)), Some(TaskSourceKind::UserInput) => Some(Icon::new(IconName::Terminal)), Some(TaskSourceKind::AbsPath { .. }) => Some(Icon::new(IconName::Settings)), Some(TaskSourceKind::Worktree { .. }) => Some(Icon::new(IconName::FileTree)), - Some(TaskSourceKind::Language { name }) => file_icons::FileIcons::get(cx) + Some(TaskSourceKind::Lsp { + language_name: name, + .. + }) + | Some(TaskSourceKind::Language { name }) => file_icons::FileIcons::get(cx) .get_icon_for_type(&name.to_lowercase(), cx) .map(Icon::from_path), None => Some(Icon::new(IconName::HistoryRerun)), } - .map(|icon| icon.color(Color::Muted).size(ui::IconSize::Small)); + .map(|icon| icon.color(Color::Muted).size(IconSize::Small)); + let indicator = if matches!(task_kind, Some(TaskSourceKind::Lsp { .. })) { + Some(Indicator::icon( + Icon::new(IconName::BoltFilled).color(Color::Muted), + )) + } else { + None + }; + let icon = icon.map(|icon| IconWithIndicator::new(icon, indicator)); Some( ListItem::new(SharedString::from(format!("debug-scenario-selection-{ix}"))) .inset(true) - .start_slot::(icon) + .start_slot::(icon) .spacing(ListItemSpacing::Sparse) .toggle_state(selected) .child(highlighted_location.render(window, cx)), diff --git a/crates/editor/src/lsp_ext.rs b/crates/editor/src/lsp_ext.rs index dd91b59b48..810cf17190 100644 --- a/crates/editor/src/lsp_ext.rs +++ b/crates/editor/src/lsp_ext.rs @@ -22,6 +22,7 @@ use smol::stream::StreamExt; use task::ResolvedTask; use task::TaskContext; use text::BufferId; +use ui::SharedString; use util::ResultExt as _; pub(crate) fn find_specific_language_server_in_selection( @@ -133,13 +134,22 @@ pub fn lsp_tasks( cx.spawn(async move |cx| { cx.spawn(async move |cx| { - let mut lsp_tasks = Vec::new(); + let mut lsp_tasks = HashMap::default(); while let Some(server_to_query) = lsp_task_sources.next().await { if let Some((server_id, buffers)) = server_to_query { - let source_kind = TaskSourceKind::Lsp(server_id); - let id_base = source_kind.to_id_base(); let mut new_lsp_tasks = Vec::new(); for buffer in buffers { + let source_kind = match buffer.update(cx, |buffer, _| { + buffer.language().map(|language| language.name()) + }) { + Ok(Some(language_name)) => TaskSourceKind::Lsp { + server: server_id, + language_name: SharedString::from(language_name), + }, + Ok(None) => continue, + Err(_) => return Vec::new(), + }; + let id_base = source_kind.to_id_base(); let lsp_buffer_context = lsp_task_context(&project, &buffer, cx) .await .unwrap_or_default(); @@ -168,11 +178,14 @@ pub fn lsp_tasks( ); } } + lsp_tasks + .entry(source_kind) + .or_insert_with(Vec::new) + .append(&mut new_lsp_tasks); } - lsp_tasks.push((source_kind, new_lsp_tasks)); } } - lsp_tasks + lsp_tasks.into_iter().collect() }) .race({ // `lsp::LSP_REQUEST_TIMEOUT` is larger than we want for the modal to open fast diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index 371449d4b6..0e4ca55c99 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -132,7 +132,10 @@ pub enum TaskSourceKind { /// Languages-specific tasks coming from extensions. Language { name: SharedString }, /// Language-specific tasks coming from LSP servers. - Lsp(LanguageServerId), + Lsp { + language_name: SharedString, + server: LanguageServerId, + }, } /// A collection of task contexts, derived from the current state of the workspace. @@ -211,7 +214,10 @@ impl TaskSourceKind { format!("{id_base}_{id}_{}", directory_in_worktree.display()) } Self::Language { name } => format!("language_{name}"), - Self::Lsp(server_id) => format!("lsp_{server_id}"), + Self::Lsp { + server, + language_name, + } => format!("lsp_{language_name}_{server}"), } } } @@ -712,7 +718,7 @@ fn task_lru_comparator( fn task_source_kind_preference(kind: &TaskSourceKind) -> u32 { match kind { - TaskSourceKind::Lsp(..) => 0, + TaskSourceKind::Lsp { .. } => 0, TaskSourceKind::Language { .. } => 1, TaskSourceKind::UserInput => 2, TaskSourceKind::Worktree { .. } => 3, diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index ecdab689dc..0a003c324f 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -14,8 +14,9 @@ use project::{TaskSourceKind, task_store::TaskStore}; use task::{DebugScenario, ResolvedTask, RevealTarget, TaskContext, TaskTemplate}; use ui::{ ActiveTheme, Button, ButtonCommon, ButtonSize, Clickable, Color, FluentBuilder as _, Icon, - IconButton, IconButtonShape, IconName, IconSize, IntoElement, KeyBinding, Label, LabelSize, - ListItem, ListItemSpacing, RenderOnce, Toggleable, Tooltip, div, h_flex, v_flex, + IconButton, IconButtonShape, IconName, IconSize, IconWithIndicator, Indicator, IntoElement, + KeyBinding, Label, LabelSize, ListItem, ListItemSpacing, RenderOnce, Toggleable, Tooltip, div, + h_flex, v_flex, }; use util::{ResultExt, truncate_and_trailoff}; @@ -448,15 +449,29 @@ impl PickerDelegate for TasksModalDelegate { color: Color::Default, }; let icon = match source_kind { - TaskSourceKind::Lsp(..) => Some(Icon::new(IconName::BoltFilled)), TaskSourceKind::UserInput => Some(Icon::new(IconName::Terminal)), TaskSourceKind::AbsPath { .. } => Some(Icon::new(IconName::Settings)), TaskSourceKind::Worktree { .. } => Some(Icon::new(IconName::FileTree)), - TaskSourceKind::Language { name } => file_icons::FileIcons::get(cx) + TaskSourceKind::Lsp { + language_name: name, + .. + } + | TaskSourceKind::Language { name } => file_icons::FileIcons::get(cx) .get_icon_for_type(&name.to_lowercase(), cx) .map(Icon::from_path), } .map(|icon| icon.color(Color::Muted).size(IconSize::Small)); + let indicator = if matches!(source_kind, TaskSourceKind::Lsp { .. }) { + Some(Indicator::icon( + Icon::new(IconName::Bolt).size(IconSize::Small), + )) + } else { + None + }; + let icon = icon.map(|icon| { + IconWithIndicator::new(icon, indicator) + .indicator_border_color(Some(cx.theme().colors().border_transparent)) + }); let history_run_icon = if Some(ix) <= self.divider_index { Some( Icon::new(IconName::HistoryRerun) @@ -476,7 +491,7 @@ impl PickerDelegate for TasksModalDelegate { Some( ListItem::new(SharedString::from(format!("tasks-modal-{ix}"))) .inset(true) - .start_slot::(icon) + .start_slot::(icon) .end_slot::( h_flex() .gap_1()