diff --git a/Cargo.lock b/Cargo.lock index b1d770142e5..08f030e9661 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9949,6 +9949,7 @@ dependencies = [ "language", "lsp", "menu", + "path", "project", "proto", "release_channel", diff --git a/crates/language_tools/Cargo.toml b/crates/language_tools/Cargo.toml index 4e18b4f3ef7..409dfa0594f 100644 --- a/crates/language_tools/Cargo.toml +++ b/crates/language_tools/Cargo.toml @@ -25,6 +25,7 @@ itertools.workspace = true language.workspace = true lsp.workspace = true menu.workspace = true +path.workspace = true project.workspace = true proto.workspace = true serde_json.workspace = true diff --git a/crates/language_tools/src/lsp_button.rs b/crates/language_tools/src/lsp_button.rs index 6cc52556fa0..0cd9d6b60f6 100644 --- a/crates/language_tools/src/lsp_button.rs +++ b/crates/language_tools/src/lsp_button.rs @@ -16,6 +16,7 @@ use editor::{Editor, EditorEvent}; use gpui::{Action as _, Anchor, App, Entity, Subscription, Task, TaskExt, WeakEntity, actions}; use language::{BinaryStatus, BufferId, ServerHealth}; use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector}; +use path::PathStyle; use project::{ LspStore, LspStoreEvent, Worktree, lsp_store::log_store::GlobalLogStore, project_settings::ProjectSettings, trusted_worktrees::TrustedWorktrees, @@ -188,6 +189,13 @@ struct ServerInfo { message: Option, } +#[derive(Default, Clone)] +struct ServerMetadata { + server_version: Option, + binary_display_path: Option, + process_id: Option, +} + impl ServerInfo { fn server_selector(&self) -> LanguageServerSelector { LanguageServerSelector::Id(self.id) @@ -260,24 +268,32 @@ impl LanguageServerState { ); } - let server_metadata = self - .lsp_store - .update(cx, |lsp_store, _| { - lsp_store - .language_server_statuses() - .map(|(server_id, status)| { - ( - server_id, + let path_style = self + .workspace + .upgrade() + .map(|workspace| workspace.read(cx).path_style(cx)) + .unwrap_or(PathStyle::local()); + + let server_metadata = + self.lsp_store + .update(cx, |lsp_store, _| { + lsp_store + .language_server_statuses() + .map(|(server_id, status)| { ( - status.server_readable_version.clone(), - status.binary.as_ref().map(|b| b.path.clone()), - status.process_id, - ), - ) - }) - .collect::>() - }) - .unwrap_or_default(); + server_id, + ServerMetadata { + server_version: status.server_readable_version.clone(), + binary_display_path: status.binary.as_ref().map(|binary| { + tooltip_for_server_binary(binary, path_style) + }), + process_id: status.process_id, + }, + ) + }) + .collect::>() + }) + .unwrap_or_default(); let process_memory_cache = self.process_memory_cache.clone(); @@ -360,17 +376,14 @@ impl LanguageServerState { .or_else(|| server_info.binary_status.as_ref()?.message.as_ref()) .cloned(); - let (server_version, binary_path, process_id) = server_metadata + let ServerMetadata { + server_version, + binary_display_path, + process_id, + } = server_metadata .get(&server_info.id) - .map(|(version, path, process_id)| { - ( - version.clone(), - path.as_ref() - .map(|p| SharedString::from(p.compact().to_string_lossy().to_string())), - *process_id, - ) - }) - .unwrap_or((None, None, None)); + .cloned() + .unwrap_or_default(); let server_message = message.clone(); @@ -581,7 +594,7 @@ impl LanguageServerState { } submenu = submenu.separator().custom_row({ - let binary_path = binary_path.clone(); + let binary_display_path = binary_display_path.clone(); let server_version = server_version.clone(); let server_message = server_message.clone(); let process_memory_cache = process_memory_cache.clone(); @@ -659,7 +672,7 @@ impl LanguageServerState { .size(LabelSize::Small), ) }) - .when_some(binary_path.clone(), |el, path| { + .when_some(binary_display_path.clone(), |el, path| { el.tooltip(Tooltip::text(path)) }) .into_any_element() @@ -675,6 +688,34 @@ impl LanguageServerState { } } +fn tooltip_for_server_binary( + server_binary: &lsp::LanguageServerBinary, + path_style: PathStyle, +) -> SharedString { + let runtime = path_style.file_name(&server_binary.path).and_then(|name| { + ["node", "python"] + .into_iter() + .find(|runtime| name.starts_with(runtime)) + }); + + let target_path = runtime + .and_then(|_runtime| { + server_binary + .arguments + .iter() + .find(|arg| !arg.to_string_lossy().starts_with('-')) + }) + .map(Path::new) + .unwrap_or(&server_binary.path); + + let display_path = path_style.normalize(&target_path.compact().to_string_lossy()); + + match runtime { + Some(runtime) => format!("{display_path} ({runtime})").into(), + None => display_path.into(), + } +} + impl LanguageServers { fn update_binary_status( &mut self, @@ -1585,4 +1626,67 @@ mod tests { "the new server's health entry is present", ); } + + #[test] + fn tooltip_for_server_binary_handles_runtime_and_standalone_servers() { + let node_server = lsp::LanguageServerBinary { + path: "/usr/bin/node".into(), + arguments: vec![ + "/zed/languages/basedpyright/langserver.index.js".into(), + "--stdio".into(), + ], + env: None, + }; + assert_eq!( + tooltip_for_server_binary(&node_server, PathStyle::Unix), + "/zed/languages/basedpyright/langserver.index.js (node)" + ); + + let node_server_windows = lsp::LanguageServerBinary { + path: "C:\\Program Files\\nodejs\\node.exe".into(), + arguments: vec![ + "C:\\Users\\Zed\\languages\\basedpyright\\node_modules/basedpyright/langserver.index.js".into(), + "--stdio".into(), + ], + env: None + }; + assert_eq!( + tooltip_for_server_binary(&node_server_windows, PathStyle::Windows), + "C:\\Users\\Zed\\languages\\basedpyright\\node_modules\\basedpyright\\langserver.index.js (node)" + ); + + let python_server = lsp::LanguageServerBinary { + path: "/usr/bin/python3".into(), + arguments: vec!["/zed/languages/pylsp/pylsp".into(), "--stdio".into()], + env: None, + }; + assert_eq!( + tooltip_for_server_binary(&python_server, PathStyle::Unix), + "/zed/languages/pylsp/pylsp (python)" + ); + + let standalone_server = lsp::LanguageServerBinary { + path: "/usr/bin/ty".into(), + arguments: vec!["server".into()], + env: None, + }; + assert_eq!( + tooltip_for_server_binary(&standalone_server, PathStyle::Unix), + "/usr/bin/ty" + ); + + let flagged_node_server = lsp::LanguageServerBinary { + path: "/usr/bin/node".into(), + arguments: vec![ + "--max-old-space-size=8192".into(), + "/zed/languages/eslint/server.js".into(), + "--stdio".into(), + ], + env: None, + }; + assert_eq!( + tooltip_for_server_binary(&flagged_node_server, PathStyle::Unix), + "/zed/languages/eslint/server.js (node)" + ); + } }