lsp: Display readable gopls version in UI (#49835)

ᴀᴍᴛᴏᴀᴇʀ created

Closes #49757
|Before|After|
|--|--|
|<img width="2776" height="218" alt="CleanShot 2026-02-22 at 17 43
16@2x"
src="https://github.com/user-attachments/assets/1ea30418-b192-4a6f-ba11-41d5728a1645"
/>|<img width="814" height="232" alt="CleanShot 2026-02-22 at 17 42
21@2x"
src="https://github.com/user-attachments/assets/ac1637fa-1c97-47e2-90e7-00f68b58345f"
/>|

Before you mark this PR as ready for review, make sure that you have:
- [x] Added a solid test coverage and/or screenshots from doing manual
testing
- [x] Done a self-review taking into account security and performance
aspects
- [x] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)

Release Notes:

- Fixed gopls version display showing raw JSON instead of readable
version number

Change summary

crates/language_tools/src/lsp_button.rs   |  2 +-
crates/language_tools/src/lsp_log_view.rs |  1 +
crates/lsp/src/lsp.rs                     | 23 +++++++++++++++++++++++
crates/project/src/lsp_store.rs           |  4 ++++
4 files changed, 29 insertions(+), 1 deletion(-)

Detailed changes

crates/language_tools/src/lsp_button.rs 🔗

@@ -230,7 +230,7 @@ impl LanguageServerState {
                         (
                             server_id,
                             (
-                                status.server_version.clone(),
+                                status.server_readable_version.clone(),
                                 status.binary.as_ref().map(|b| b.path.clone()),
                                 status.process_id,
                             ),

crates/language_tools/src/lsp_log_view.rs 🔗

@@ -1355,6 +1355,7 @@ impl ServerInfo {
             status: LanguageServerStatus {
                 name: server.name(),
                 server_version: server.version(),
+                server_readable_version: server.readable_version(),
                 pending_work: Default::default(),
                 has_pending_diagnostic_updates: false,
                 progress_tokens: Default::default(),

crates/lsp/src/lsp.rs 🔗

@@ -1306,6 +1306,29 @@ impl LanguageServer {
         self.version.clone()
     }
 
+    /// Get the readable version of the running language server.
+    pub fn readable_version(&self) -> Option<SharedString> {
+        match self.name().as_ref() {
+            "gopls" => {
+                // Gopls returns a detailed JSON object as its version string; we must parse it to extract the semantic version.
+                // Example: `{"GoVersion":"go1.26.0","Path":"golang.org/x/tools/gopls","Main":{},"Deps":[],"Settings":[],"Version":"v0.21.1"}`
+                self.version
+                    .as_ref()
+                    .and_then(|obj| {
+                        #[derive(Deserialize)]
+                        struct GoplsVersion<'a> {
+                            #[serde(rename = "Version")]
+                            version: &'a str,
+                        }
+                        let parsed: GoplsVersion = serde_json::from_str(obj.as_str()).ok()?;
+                        Some(parsed.version.trim_start_matches("v").to_owned().into())
+                    })
+                    .or_else(|| self.version.clone())
+            }
+            _ => self.version.clone(),
+        }
+    }
+
     /// Get the process name of the running language server.
     pub fn process_name(&self) -> &str {
         &self.process_name

crates/project/src/lsp_store.rs 🔗

@@ -4028,6 +4028,7 @@ pub enum LspStoreEvent {
 pub struct LanguageServerStatus {
     pub name: LanguageServerName,
     pub server_version: Option<SharedString>,
+    pub server_readable_version: Option<SharedString>,
     pub pending_work: BTreeMap<ProgressToken, LanguageServerProgress>,
     pub has_pending_diagnostic_updates: bool,
     pub progress_tokens: HashSet<ProgressToken>,
@@ -8201,6 +8202,7 @@ impl LspStore {
                     LanguageServerStatus {
                         name,
                         server_version: None,
+                        server_readable_version: None,
                         pending_work: Default::default(),
                         has_pending_diagnostic_updates: false,
                         progress_tokens: Default::default(),
@@ -9391,6 +9393,7 @@ impl LspStore {
                 LanguageServerStatus {
                     name: server_name.clone(),
                     server_version: None,
+                    server_readable_version: None,
                     pending_work: Default::default(),
                     has_pending_diagnostic_updates: false,
                     progress_tokens: Default::default(),
@@ -11354,6 +11357,7 @@ impl LspStore {
             LanguageServerStatus {
                 name: language_server.name(),
                 server_version: language_server.version(),
+                server_readable_version: language_server.readable_version(),
                 pending_work: Default::default(),
                 has_pending_diagnostic_updates: false,
                 progress_tokens: Default::default(),