diff --git a/crates/language_tools/src/lsp_button.rs b/crates/language_tools/src/lsp_button.rs index 335381c6f79d950498a0f0c1d330cb21c681f32e..7775586bf19539e13adc6b9df6d92914be6b7f21 100644 --- a/crates/language_tools/src/lsp_button.rs +++ b/crates/language_tools/src/lsp_button.rs @@ -127,6 +127,16 @@ impl LanguageServerState { return menu; }; + let server_versions = self + .lsp_store + .update(cx, |lsp_store, _| { + lsp_store + .language_server_statuses() + .map(|(server_id, status)| (server_id, status.server_version.clone())) + .collect::>() + }) + .unwrap_or_default(); + let mut first_button_encountered = false; for item in &self.items { if let LspMenuItem::ToggleServersButton { restart } = item { @@ -254,6 +264,22 @@ impl LanguageServerState { }; let server_name = server_info.name.clone(); + let server_version = server_versions + .get(&server_info.id) + .and_then(|version| version.clone()); + + let tooltip_text = match (&server_version, &message) { + (None, None) => None, + (Some(version), None) => { + Some(SharedString::from(format!("Version: {}", version.as_ref()))) + } + (None, Some(message)) => Some(message.clone()), + (Some(version), Some(message)) => Some(SharedString::from(format!( + "Version: {}\n\n{}", + version.as_ref(), + message.as_ref() + ))), + }; menu = menu.item(ContextMenuItem::custom_entry( move |_, _| { h_flex() @@ -355,11 +381,11 @@ impl LanguageServerState { } } }, - message.map(|server_message| { + tooltip_text.map(|tooltip_text| { DocumentationAside::new( DocumentationSide::Right, - DocumentationEdge::Bottom, - Rc::new(move |_| Label::new(server_message.clone()).into_any_element()), + DocumentationEdge::Top, + Rc::new(move |_| Label::new(tooltip_text.clone()).into_any_element()), ) }), )); diff --git a/crates/language_tools/src/lsp_log_view.rs b/crates/language_tools/src/lsp_log_view.rs index e34bbb46d35d5a524c08369fcc991dfe81865127..2b2575912ae4543d2bf3cbd0c6b667ace7c82e91 100644 --- a/crates/language_tools/src/lsp_log_view.rs +++ b/crates/language_tools/src/lsp_log_view.rs @@ -330,6 +330,8 @@ impl LspLogView { let server_info = format!( "* Server: {NAME} (id {ID}) +* Version: {VERSION} + * Binary: {BINARY} * Registered workspace folders: @@ -340,6 +342,12 @@ impl LspLogView { * Configuration: {CONFIGURATION}", NAME = info.status.name, ID = info.id, + VERSION = info + .status + .server_version + .as_ref() + .map(|version| version.as_ref()) + .unwrap_or("Unknown"), BINARY = info .status .binary @@ -1334,6 +1342,7 @@ impl ServerInfo { capabilities: server.capabilities(), status: LanguageServerStatus { name: server.name(), + server_version: server.version(), pending_work: Default::default(), has_pending_diagnostic_updates: false, progress_tokens: Default::default(), diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index faa094153d4a26fb1a2b96360f2691989e81aad9..36938f62a3048b87dd890ca6e7ca8fc2499689e4 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -89,6 +89,7 @@ pub struct LanguageServer { outbound_tx: channel::Sender, notification_tx: channel::Sender, name: LanguageServerName, + version: Option, process_name: Arc, binary: LanguageServerBinary, capabilities: RwLock, @@ -501,6 +502,7 @@ impl LanguageServer { response_handlers, io_handlers, name: server_name, + version: None, process_name: binary .path .file_name() @@ -925,6 +927,7 @@ impl LanguageServer { ) })?; if let Some(info) = response.server_info { + self.version = info.version.map(SharedString::from); self.process_name = info.name.into(); } self.capabilities = RwLock::new(response.capabilities); @@ -1155,6 +1158,11 @@ impl LanguageServer { self.name.clone() } + /// Get the version of the running language server. + pub fn version(&self) -> Option { + self.version.clone() + } + pub fn process_name(&self) -> &str { &self.process_name } diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 5093b6977a1bffe82339ede00d2e6e4b4b14b4c1..7e8624daad628fd653326647537eb51dad208a02 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -3864,6 +3864,7 @@ pub enum LspStoreEvent { #[derive(Clone, Debug, Serialize)] pub struct LanguageServerStatus { pub name: LanguageServerName, + pub server_version: Option, pub pending_work: BTreeMap, pub has_pending_diagnostic_updates: bool, pub progress_tokens: HashSet, @@ -8354,6 +8355,7 @@ impl LspStore { server_id, LanguageServerStatus { name, + server_version: None, pending_work: Default::default(), has_pending_diagnostic_updates: false, progress_tokens: Default::default(), @@ -9389,6 +9391,7 @@ impl LspStore { server_id, LanguageServerStatus { name: server_name.clone(), + server_version: None, pending_work: Default::default(), has_pending_diagnostic_updates: false, progress_tokens: Default::default(), @@ -11419,6 +11422,7 @@ impl LspStore { server_id, LanguageServerStatus { name: language_server.name(), + server_version: language_server.version(), pending_work: Default::default(), has_pending_diagnostic_updates: false, progress_tokens: Default::default(), diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index 756a2a9364193d6f1cdace8ed8c92cecf401a864..7e5e9032c9d4b0521f972b47d90d24cd502faf7b 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -893,39 +893,57 @@ impl ContextMenu { entry_render, handler, selectable, + documentation_aside, .. } => { let handler = handler.clone(); let menu = cx.entity().downgrade(); let selectable = *selectable; - ListItem::new(ix) - .inset(true) - .toggle_state(if selectable { - Some(ix) == self.selected_index - } else { - false + + div() + .id(("context-menu-child", ix)) + .when_some(documentation_aside.clone(), |this, documentation_aside| { + this.occlude() + .on_hover(cx.listener(move |menu, hovered, _, cx| { + if *hovered { + menu.documentation_aside = Some((ix, documentation_aside.clone())); + } else if matches!(menu.documentation_aside, Some((id, _)) if id == ix) + { + menu.documentation_aside = None; + } + cx.notify(); + })) }) - .selectable(selectable) - .when(selectable, |item| { - item.on_click({ - let context = self.action_context.clone(); - let keep_open_on_confirm = self.keep_open_on_confirm; - move |_, window, cx| { - handler(context.as_ref(), window, cx); - menu.update(cx, |menu, cx| { - menu.clicked = true; - - if keep_open_on_confirm { - menu.rebuild(window, cx); - } else { - cx.emit(DismissEvent); + .child( + ListItem::new(ix) + .inset(true) + .toggle_state(if selectable { + Some(ix) == self.selected_index + } else { + false + }) + .selectable(selectable) + .when(selectable, |item| { + item.on_click({ + let context = self.action_context.clone(); + let keep_open_on_confirm = self.keep_open_on_confirm; + move |_, window, cx| { + handler(context.as_ref(), window, cx); + menu.update(cx, |menu, cx| { + menu.clicked = true; + + if keep_open_on_confirm { + menu.rebuild(window, cx); + } else { + cx.emit(DismissEvent); + } + }) + .ok(); } }) - .ok(); - } - }) - }) - .child(entry_render(window, cx)) + }) + .child(entry_render(window, cx)), + ) .into_any_element() } }