language servers: Fix wrong language server name (#20428)

Thorsten Ball , Sam Rose , and Bennet created

This fixes the issue of multiple language servers showing up as `node`
in the language server logs dropdown.

It does this by changing `language_server.name()` to return the
adapter's name, not the binary name, and changing types to make sure
that we always use this.

Release Notes:

- Fixed language server names showing up only as `"node"`

---------

Co-authored-by: Sam Rose <hello@samwho.dev>
Co-authored-by: Bennet <bennet@zed.dev>

Change summary

Cargo.lock                                               |  2 
crates/activity_indicator/Cargo.toml                     |  1 
crates/activity_indicator/src/activity_indicator.rs      |  5 
crates/collab/src/tests/integration_tests.rs             |  5 
crates/copilot/src/copilot.rs                            |  4 
crates/editor/src/editor.rs                              |  6 
crates/extension/src/extension_manifest.rs               |  3 
crates/extension_host/src/extension_host.rs              | 10 
crates/extension_host/src/extension_lsp_adapter.rs       |  5 
crates/extension_host/src/wasm_host/wit.rs               |  3 
crates/extension_host/src/wasm_host/wit/since_v0_0_1.rs  |  2 
crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs  |  6 
crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs  |  8 
crates/extensions_ui/Cargo.toml                          |  1 
crates/extensions_ui/src/extension_registration_hooks.rs |  4 
crates/extensions_ui/src/extension_store_test.rs         |  3 
crates/language/src/language.rs                          | 53 -----
crates/language_tools/src/lsp_log.rs                     |  6 
crates/language_tools/src/lsp_log_tests.rs               |  5 
crates/languages/src/c.rs                                |  2 
crates/languages/src/css.rs                              |  4 
crates/languages/src/go.rs                               |  2 
crates/languages/src/json.rs                             |  6 
crates/languages/src/lib.rs                              |  1 
crates/languages/src/python.rs                           |  3 
crates/languages/src/rust.rs                             |  2 
crates/languages/src/tailwind.rs                         |  4 
crates/languages/src/typescript.rs                       |  4 
crates/languages/src/vtsls.rs                            |  4 
crates/languages/src/yaml.rs                             |  5 
crates/lsp/Cargo.toml                                    |  1 
crates/lsp/src/lsp.rs                                    | 90 ++++++++-
crates/prettier/src/prettier.rs                          | 15 +
crates/project/src/lsp_store.rs                          | 17 +
crates/project/src/prettier_store.rs                     |  4 
crates/project/src/project.rs                            |  6 
crates/project/src/project_settings.rs                   |  2 
crates/project/src/project_tests.rs                      | 14 
crates/remote_server/src/remote_editing_tests.rs         |  5 
39 files changed, 172 insertions(+), 151 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -13,6 +13,7 @@ dependencies = [
  "futures 0.3.30",
  "gpui",
  "language",
+ "lsp",
  "project",
  "smallvec",
  "ui",
@@ -6898,6 +6899,7 @@ dependencies = [
  "parking_lot",
  "postage",
  "release_channel",
+ "schemars",
  "serde",
  "serde_json",
  "smol",

crates/activity_indicator/Cargo.toml 🔗

@@ -20,6 +20,7 @@ extension_host.workspace = true
 futures.workspace = true
 gpui.workspace = true
 language.workspace = true
+lsp.workspace = true
 project.workspace = true
 smallvec.workspace = true
 ui.workspace = true

crates/activity_indicator/src/activity_indicator.rs 🔗

@@ -7,9 +7,8 @@ use gpui::{
     InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
     StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
 };
-use language::{
-    LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId, LanguageServerName,
-};
+use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId};
+use lsp::LanguageServerName;
 use project::{EnvironmentErrorMessage, LanguageServerProgress, Project, WorktreeId};
 use smallvec::SmallVec;
 use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};

crates/collab/src/tests/integration_tests.rs 🔗

@@ -5130,11 +5130,10 @@ async fn test_lsp_hover(
         });
         let new_server_name = new_server.server.name();
         assert!(
-            !servers_with_hover_requests.contains_key(new_server_name),
+            !servers_with_hover_requests.contains_key(&new_server_name),
             "Unexpected: initialized server with the same name twice. Name: `{new_server_name}`"
         );
-        let new_server_name = new_server_name.to_string();
-        match new_server_name.as_str() {
+        match new_server_name.as_ref() {
             "CrabLang-ls" => {
                 servers_with_hover_requests.insert(
                     new_server_name.clone(),

crates/copilot/src/copilot.rs 🔗

@@ -21,7 +21,7 @@ use language::{
     point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
     ToPointUtf16,
 };
-use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId};
+use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId, LanguageServerName};
 use node_runtime::NodeRuntime;
 use parking_lot::Mutex;
 use request::StatusNotification;
@@ -446,9 +446,11 @@ impl Copilot {
                 Path::new("/")
             };
 
+            let server_name = LanguageServerName("copilot".into());
             let server = LanguageServer::new(
                 Arc::new(Mutex::new(None)),
                 new_server_id,
+                server_name,
                 binary,
                 root_path,
                 None,

crates/editor/src/editor.rs 🔗

@@ -96,9 +96,7 @@ use language::{
     CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
     Point, Selection, SelectionGoal, TransactionId,
 };
-use language::{
-    point_to_lsp, BufferRow, CharClassifier, LanguageServerName, Runnable, RunnableRange,
-};
+use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
 use linked_editing_ranges::refresh_linked_ranges;
 pub use proposed_changes_editor::{
     ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
@@ -111,7 +109,7 @@ use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
 pub use lsp::CompletionContext;
 use lsp::{
     CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity, InsertTextFormat,
-    LanguageServerId,
+    LanguageServerId, LanguageServerName,
 };
 use mouse_context_menu::MouseContextMenu;
 use movement::TextLayoutDetails;

crates/extension/src/extension_manifest.rs 🔗

@@ -1,7 +1,8 @@
 use anyhow::{anyhow, Context, Result};
 use collections::{BTreeMap, HashMap};
 use fs::Fs;
-use language::{LanguageName, LanguageServerName};
+use language::LanguageName;
+use lsp::LanguageServerName;
 use semantic_version::SemanticVersion;
 use serde::{Deserialize, Serialize};
 use std::{

crates/extension_host/src/extension_host.rs 🔗

@@ -28,6 +28,7 @@ use language::{
     LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage,
     QUERY_FILENAME_PREFIXES,
 };
+use lsp::LanguageServerName;
 use node_runtime::NodeRuntime;
 use project::ContextProviderWithTasks;
 use release_channel::ReleaseChannel;
@@ -121,12 +122,7 @@ pub trait ExtensionRegistrationHooks: Send + Sync + 'static {
 
     fn register_lsp_adapter(&self, _language: LanguageName, _adapter: ExtensionLspAdapter) {}
 
-    fn remove_lsp_adapter(
-        &self,
-        _language: &LanguageName,
-        _server_name: &language::LanguageServerName,
-    ) {
-    }
+    fn remove_lsp_adapter(&self, _language: &LanguageName, _server_name: &LanguageServerName) {}
 
     fn register_wasm_grammars(&self, _grammars: Vec<(Arc<str>, PathBuf)>) {}
 
@@ -167,7 +163,7 @@ pub trait ExtensionRegistrationHooks: Send + Sync + 'static {
 
     fn update_lsp_status(
         &self,
-        _server_name: language::LanguageServerName,
+        _server_name: lsp::LanguageServerName,
         _status: language::LanguageServerBinaryStatus,
     ) {
     }

crates/extension_host/src/extension_lsp_adapter.rs 🔗

@@ -8,10 +8,9 @@ use collections::HashMap;
 use futures::{Future, FutureExt};
 use gpui::AsyncAppContext;
 use language::{
-    CodeLabel, HighlightId, Language, LanguageServerName, LanguageToolchainStore, LspAdapter,
-    LspAdapterDelegate,
+    CodeLabel, HighlightId, Language, LanguageToolchainStore, LspAdapter, LspAdapterDelegate,
 };
-use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions};
+use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName};
 use serde::Serialize;
 use serde_json::Value;
 use std::ops::Range;

crates/extension_host/src/wasm_host/wit.rs 🔗

@@ -3,6 +3,7 @@ mod since_v0_0_4;
 mod since_v0_0_6;
 mod since_v0_1_0;
 mod since_v0_2_0;
+use lsp::LanguageServerName;
 // use indexed_docs::IndexedDocsDatabase;
 use release_channel::ReleaseChannel;
 use since_v0_2_0 as latest;
@@ -11,7 +12,7 @@ use crate::DocsDatabase;
 
 use super::{wasm_engine, WasmState};
 use anyhow::{anyhow, Context, Result};
-use language::{LanguageServerName, LspAdapterDelegate};
+use language::LspAdapterDelegate;
 use semantic_version::SemanticVersion;
 use std::{ops::RangeInclusive, sync::Arc};
 use wasmtime::{

crates/extension_host/src/wasm_host/wit/since_v0_0_1.rs 🔗

@@ -149,7 +149,7 @@ impl ExtensionImports for WasmState {
 
         self.host
             .registration_hooks
-            .update_lsp_status(language::LanguageServerName(server_name.into()), status);
+            .update_lsp_status(lsp::LanguageServerName(server_name.into()), status);
         Ok(())
     }
 

crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs 🔗

@@ -8,10 +8,10 @@ use async_tar::Archive;
 use async_trait::async_trait;
 use futures::{io::BufReader, FutureExt as _};
 use futures::{lock::Mutex, AsyncReadExt};
+use language::LanguageName;
 use language::{
     language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
 };
-use language::{LanguageName, LanguageServerName};
 use project::project_settings::ProjectSettings;
 use semantic_version::SemanticVersion;
 use std::{
@@ -469,7 +469,7 @@ impl ExtensionImports for WasmState {
                             .and_then(|key| {
                                 ProjectSettings::get(location, cx)
                                     .lsp
-                                    .get(&LanguageServerName(key.into()))
+                                    .get(&::lsp::LanguageServerName(key.into()))
                             })
                             .cloned()
                             .unwrap_or_default();
@@ -513,7 +513,7 @@ impl ExtensionImports for WasmState {
 
         self.host
             .registration_hooks
-            .update_lsp_status(language::LanguageServerName(server_name.into()), status);
+            .update_lsp_status(::lsp::LanguageServerName(server_name.into()), status);
         Ok(())
     }
 

crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs 🔗

@@ -9,9 +9,9 @@ use async_trait::async_trait;
 use futures::{io::BufReader, FutureExt as _};
 use futures::{lock::Mutex, AsyncReadExt};
 use language::{
-    language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
+    language_settings::AllLanguageSettings, LanguageName, LanguageServerBinaryStatus,
+    LspAdapterDelegate,
 };
-use language::{LanguageName, LanguageServerName};
 use project::project_settings::ProjectSettings;
 use semantic_version::SemanticVersion;
 use std::{
@@ -416,7 +416,7 @@ impl ExtensionImports for WasmState {
                             .and_then(|key| {
                                 ProjectSettings::get(location, cx)
                                     .lsp
-                                    .get(&LanguageServerName::from_proto(key))
+                                    .get(&::lsp::LanguageServerName::from_proto(key))
                             })
                             .cloned()
                             .unwrap_or_default();
@@ -460,7 +460,7 @@ impl ExtensionImports for WasmState {
 
         self.host
             .registration_hooks
-            .update_lsp_status(language::LanguageServerName(server_name.into()), status);
+            .update_lsp_status(::lsp::LanguageServerName(server_name.into()), status);
         Ok(())
     }
 

crates/extensions_ui/Cargo.toml 🔗

@@ -30,6 +30,7 @@ fuzzy.workspace = true
 gpui.workspace = true
 indexed_docs.workspace = true
 language.workspace = true
+lsp.workspace = true
 num-format.workspace = true
 picker.workspace = true
 project.workspace = true

crates/extensions_ui/src/extension_registration_hooks.rs 🔗

@@ -121,7 +121,7 @@ impl extension_host::ExtensionRegistrationHooks for ConcreteExtensionRegistratio
 
     fn update_lsp_status(
         &self,
-        server_name: language::LanguageServerName,
+        server_name: lsp::LanguageServerName,
         status: LanguageServerBinaryStatus,
     ) {
         self.language_registry
@@ -140,7 +140,7 @@ impl extension_host::ExtensionRegistrationHooks for ConcreteExtensionRegistratio
     fn remove_lsp_adapter(
         &self,
         language_name: &language::LanguageName,
-        server_name: &language::LanguageServerName,
+        server_name: &lsp::LanguageServerName,
     ) {
         self.language_registry
             .remove_lsp_adapter(language_name, server_name);

crates/extensions_ui/src/extension_store_test.rs 🔗

@@ -14,7 +14,8 @@ use futures::{io::BufReader, AsyncReadExt, StreamExt};
 use gpui::{Context, SemanticVersion, TestAppContext};
 use http_client::{FakeHttpClient, Response};
 use indexed_docs::IndexedDocsRegistry;
-use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
+use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus};
+use lsp::LanguageServerName;
 use node_runtime::NodeRuntime;
 use parking_lot::Mutex;
 use project::{Project, DEFAULT_COMPLETION_CONTEXT};

crates/language/src/language.rs 🔗

@@ -30,7 +30,7 @@ use gpui::{AppContext, AsyncAppContext, Model, SharedString, Task};
 pub use highlight_map::HighlightMap;
 use http_client::HttpClient;
 pub use language_registry::{LanguageName, LoadedLanguage};
-use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions};
+use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName};
 use parking_lot::Mutex;
 use regex::Regex;
 use schemars::{
@@ -139,57 +139,6 @@ pub trait ToLspPosition {
     fn to_lsp_position(self) -> lsp::Position;
 }
 
-/// A name of a language server.
-#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
-pub struct LanguageServerName(pub SharedString);
-
-impl std::fmt::Display for LanguageServerName {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        std::fmt::Display::fmt(&self.0, f)
-    }
-}
-
-impl AsRef<str> for LanguageServerName {
-    fn as_ref(&self) -> &str {
-        self.0.as_ref()
-    }
-}
-
-impl AsRef<OsStr> for LanguageServerName {
-    fn as_ref(&self) -> &OsStr {
-        self.0.as_ref().as_ref()
-    }
-}
-
-impl JsonSchema for LanguageServerName {
-    fn schema_name() -> String {
-        "LanguageServerName".into()
-    }
-
-    fn json_schema(_: &mut SchemaGenerator) -> Schema {
-        SchemaObject {
-            instance_type: Some(InstanceType::String.into()),
-            ..Default::default()
-        }
-        .into()
-    }
-}
-impl LanguageServerName {
-    pub const fn new_static(s: &'static str) -> Self {
-        Self(SharedString::new_static(s))
-    }
-
-    pub fn from_proto(s: String) -> Self {
-        Self(s.into())
-    }
-}
-
-impl<'a> From<&'a str> for LanguageServerName {
-    fn from(str: &'a str) -> LanguageServerName {
-        LanguageServerName(str.to_string().into())
-    }
-}
-
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Location {
     pub buffer: Model<Buffer>,

crates/language_tools/src/lsp_log.rs 🔗

@@ -7,10 +7,10 @@ use gpui::{
     IntoElement, Model, ModelContext, ParentElement, Render, Styled, Subscription, View,
     ViewContext, VisualContext, WeakModel, WindowContext,
 };
-use language::{LanguageServerId, LanguageServerName};
+use language::LanguageServerId;
 use lsp::{
-    notification::SetTrace, IoKind, LanguageServer, MessageType, ServerCapabilities,
-    SetTraceParams, TraceValue,
+    notification::SetTrace, IoKind, LanguageServer, LanguageServerName, MessageType,
+    ServerCapabilities, SetTraceParams, TraceValue,
 };
 use project::{search::SearchQuery, Project, WorktreeId};
 use std::{borrow::Cow, sync::Arc};

crates/language_tools/src/lsp_log_tests.rs 🔗

@@ -5,9 +5,8 @@ use crate::lsp_log::LogMenuItem;
 use super::*;
 use futures::StreamExt;
 use gpui::{Context, SemanticVersion, TestAppContext, VisualTestContext};
-use language::{
-    tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageServerName,
-};
+use language::{tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher};
+use lsp::LanguageServerName;
 use lsp_log::LogKind;
 use project::{FakeFs, Project};
 use serde_json::json;

crates/languages/src/c.rs 🔗

@@ -4,7 +4,7 @@ use futures::StreamExt;
 use gpui::AsyncAppContext;
 use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
 pub use language::*;
-use lsp::LanguageServerBinary;
+use lsp::{LanguageServerBinary, LanguageServerName};
 use smol::fs::{self, File};
 use std::{any::Any, env::consts, path::PathBuf, sync::Arc};
 use util::{fs::remove_matching, maybe, ResultExt};

crates/languages/src/css.rs 🔗

@@ -1,8 +1,8 @@
 use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use futures::StreamExt;
-use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
-use lsp::LanguageServerBinary;
+use language::{LspAdapter, LspAdapterDelegate};
+use lsp::{LanguageServerBinary, LanguageServerName};
 use node_runtime::NodeRuntime;
 use serde_json::json;
 use smol::fs;

crates/languages/src/go.rs 🔗

@@ -5,7 +5,7 @@ use futures::StreamExt;
 use gpui::{AppContext, AsyncAppContext, Task};
 use http_client::github::latest_github_release;
 pub use language::*;
-use lsp::LanguageServerBinary;
+use lsp::{LanguageServerBinary, LanguageServerName};
 use regex::Regex;
 use serde_json::json;
 use smol::{fs, process};

crates/languages/src/json.rs 🔗

@@ -6,10 +6,8 @@ use collections::HashMap;
 use futures::StreamExt;
 use gpui::{AppContext, AsyncAppContext};
 use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
-use language::{
-    LanguageRegistry, LanguageServerName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate,
-};
-use lsp::LanguageServerBinary;
+use language::{LanguageRegistry, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
+use lsp::{LanguageServerBinary, LanguageServerName};
 use node_runtime::NodeRuntime;
 use project::ContextProviderWithTasks;
 use serde_json::{json, Value};

crates/languages/src/lib.rs 🔗

@@ -2,6 +2,7 @@ use anyhow::Context;
 use gpui::{AppContext, UpdateGlobal};
 use json::json_task_context;
 pub use language::*;
+use lsp::LanguageServerName;
 use node_runtime::NodeRuntime;
 use python::{PythonContextProvider, PythonToolchainProvider};
 use rust_embed::RustEmbed;

crates/languages/src/python.rs 🔗

@@ -8,8 +8,9 @@ use language::LanguageToolchainStore;
 use language::Toolchain;
 use language::ToolchainList;
 use language::ToolchainLister;
-use language::{ContextProvider, LanguageServerName, LspAdapter, LspAdapterDelegate};
+use language::{ContextProvider, LspAdapter, LspAdapterDelegate};
 use lsp::LanguageServerBinary;
+use lsp::LanguageServerName;
 use node_runtime::NodeRuntime;
 use pet_core::os_environment::Environment;
 use pet_core::python_environment::PythonEnvironmentKind;

crates/languages/src/rust.rs 🔗

@@ -6,7 +6,7 @@ use futures::{io::BufReader, StreamExt};
 use gpui::{AppContext, AsyncAppContext};
 use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
 pub use language::*;
-use lsp::LanguageServerBinary;
+use lsp::{LanguageServerBinary, LanguageServerName};
 use regex::Regex;
 use smol::fs::{self, File};
 use std::{

crates/languages/src/tailwind.rs 🔗

@@ -3,8 +3,8 @@ use async_trait::async_trait;
 use collections::HashMap;
 use futures::StreamExt;
 use gpui::AsyncAppContext;
-use language::{LanguageServerName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
-use lsp::LanguageServerBinary;
+use language::{LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
+use lsp::{LanguageServerBinary, LanguageServerName};
 use node_runtime::NodeRuntime;
 use project::lsp_store::language_server_settings;
 use serde_json::{json, Value};

crates/languages/src/typescript.rs 🔗

@@ -5,8 +5,8 @@ use async_trait::async_trait;
 use collections::HashMap;
 use gpui::AsyncAppContext;
 use http_client::github::{build_asset_url, AssetKind, GitHubLspBinaryVersion};
-use language::{LanguageServerName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
-use lsp::{CodeActionKind, LanguageServerBinary};
+use language::{LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
+use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
 use node_runtime::NodeRuntime;
 use project::lsp_store::language_server_settings;
 use project::ContextProviderWithTasks;

crates/languages/src/vtsls.rs 🔗

@@ -2,8 +2,8 @@ use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use collections::HashMap;
 use gpui::AsyncAppContext;
-use language::{LanguageServerName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
-use lsp::{CodeActionKind, LanguageServerBinary};
+use language::{LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
+use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
 use node_runtime::NodeRuntime;
 use project::lsp_store::language_server_settings;
 use serde_json::Value;

crates/languages/src/yaml.rs 🔗

@@ -3,10 +3,9 @@ use async_trait::async_trait;
 use futures::StreamExt;
 use gpui::AsyncAppContext;
 use language::{
-    language_settings::AllLanguageSettings, LanguageServerName, LanguageToolchainStore, LspAdapter,
-    LspAdapterDelegate,
+    language_settings::AllLanguageSettings, LanguageToolchainStore, LspAdapter, LspAdapterDelegate,
 };
-use lsp::LanguageServerBinary;
+use lsp::{LanguageServerBinary, LanguageServerName};
 use node_runtime::NodeRuntime;
 use project::lsp_store::language_server_settings;
 use serde_json::Value;

crates/lsp/Cargo.toml 🔗

@@ -27,6 +27,7 @@ parking_lot.workspace = true
 postage.workspace = true
 serde.workspace = true
 serde_json.workspace = true
+schemars.workspace = true
 smol.workspace = true
 util.workspace = true
 release_channel.workspace = true

crates/lsp/src/lsp.rs 🔗

@@ -6,9 +6,14 @@ pub use lsp_types::*;
 use anyhow::{anyhow, Context, Result};
 use collections::HashMap;
 use futures::{channel::oneshot, io::BufWriter, select, AsyncRead, AsyncWrite, Future, FutureExt};
-use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
+use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, SharedString, Task};
 use parking_lot::{Mutex, RwLock};
 use postage::{barrier, prelude::Stream};
+use schemars::{
+    gen::SchemaGenerator,
+    schema::{InstanceType, Schema, SchemaObject},
+    JsonSchema,
+};
 use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use serde_json::{json, value::RawValue, Value};
 use smol::{
@@ -21,7 +26,7 @@ use smol::{
 use smol::process::windows::CommandExt;
 
 use std::{
-    ffi::OsString,
+    ffi::{OsStr, OsString},
     fmt,
     io::Write,
     ops::DerefMut,
@@ -78,7 +83,8 @@ pub struct LanguageServer {
     server_id: LanguageServerId,
     next_id: AtomicI32,
     outbound_tx: channel::Sender<String>,
-    name: Arc<str>,
+    name: LanguageServerName,
+    process_name: Arc<str>,
     capabilities: RwLock<ServerCapabilities>,
     code_action_kinds: Option<Vec<CodeActionKind>>,
     notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
@@ -108,6 +114,58 @@ impl LanguageServerId {
     }
 }
 
+/// A name of a language server.
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
+pub struct LanguageServerName(pub SharedString);
+
+impl std::fmt::Display for LanguageServerName {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(&self.0, f)
+    }
+}
+
+impl AsRef<str> for LanguageServerName {
+    fn as_ref(&self) -> &str {
+        self.0.as_ref()
+    }
+}
+
+impl AsRef<OsStr> for LanguageServerName {
+    fn as_ref(&self) -> &OsStr {
+        self.0.as_ref().as_ref()
+    }
+}
+
+impl JsonSchema for LanguageServerName {
+    fn schema_name() -> String {
+        "LanguageServerName".into()
+    }
+
+    fn json_schema(_: &mut SchemaGenerator) -> Schema {
+        SchemaObject {
+            instance_type: Some(InstanceType::String.into()),
+            ..Default::default()
+        }
+        .into()
+    }
+}
+
+impl LanguageServerName {
+    pub const fn new_static(s: &'static str) -> Self {
+        Self(SharedString::new_static(s))
+    }
+
+    pub fn from_proto(s: String) -> Self {
+        Self(s.into())
+    }
+}
+
+impl<'a> From<&'a str> for LanguageServerName {
+    fn from(str: &'a str) -> LanguageServerName {
+        LanguageServerName(str.to_string().into())
+    }
+}
+
 /// Handle to a language server RPC activity subscription.
 pub enum Subscription {
     Notification {
@@ -269,6 +327,7 @@ impl LanguageServer {
     pub fn new(
         stderr_capture: Arc<Mutex<Option<String>>>,
         server_id: LanguageServerId,
+        server_name: LanguageServerName,
         binary: LanguageServerBinary,
         root_path: &Path,
         code_action_kinds: Option<Vec<CodeActionKind>>,
@@ -310,6 +369,7 @@ impl LanguageServer {
         let stderr = server.stderr.take().unwrap();
         let mut server = Self::new_internal(
             server_id,
+            server_name,
             stdin,
             stdout,
             Some(stderr),
@@ -330,7 +390,7 @@ impl LanguageServer {
         );
 
         if let Some(name) = binary.path.file_name() {
-            server.name = name.to_string_lossy().into();
+            server.process_name = name.to_string_lossy().into();
         }
 
         Ok(server)
@@ -339,6 +399,7 @@ impl LanguageServer {
     #[allow(clippy::too_many_arguments)]
     fn new_internal<Stdin, Stdout, Stderr, F>(
         server_id: LanguageServerId,
+        server_name: LanguageServerName,
         stdin: Stdin,
         stdout: Stdout,
         stderr: Option<Stderr>,
@@ -408,7 +469,8 @@ impl LanguageServer {
             notification_handlers,
             response_handlers,
             io_handlers,
-            name: Arc::default(),
+            name: server_name,
+            process_name: Arc::default(),
             capabilities: Default::default(),
             code_action_kinds,
             next_id: Default::default(),
@@ -725,7 +787,7 @@ impl LanguageServer {
         cx.spawn(|_| async move {
             let response = self.request::<request::Initialize>(params).await?;
             if let Some(info) = response.server_info {
-                self.name = info.name.into();
+                self.process_name = info.name.into();
             }
             self.capabilities = RwLock::new(response.capabilities);
 
@@ -937,8 +999,12 @@ impl LanguageServer {
     }
 
     /// Get the name of the running language server.
-    pub fn name(&self) -> &str {
-        &self.name
+    pub fn name(&self) -> LanguageServerName {
+        self.name.clone()
+    }
+
+    pub fn process_name(&self) -> &str {
+        &self.process_name
     }
 
     /// Get the reported capabilities of the running language server.
@@ -1179,8 +1245,11 @@ impl FakeLanguageServer {
 
         let root = Self::root_path();
 
+        let server_name = LanguageServerName(name.clone().into());
+        let process_name = Arc::from(name.as_str());
         let mut server = LanguageServer::new_internal(
             server_id,
+            server_name.clone(),
             stdin_writer,
             stdout_reader,
             None::<async_pipe::PipeReader>,
@@ -1192,12 +1261,13 @@ impl FakeLanguageServer {
             cx.clone(),
             |_| {},
         );
-        server.name = name.as_str().into();
+        server.process_name = process_name;
         let fake = FakeLanguageServer {
             binary,
             server: Arc::new({
                 let mut server = LanguageServer::new_internal(
                     server_id,
+                    server_name,
                     stdout_writer,
                     stdin_reader,
                     None::<async_pipe::PipeReader>,
@@ -1216,7 +1286,7 @@ impl FakeLanguageServer {
                             .ok();
                     },
                 );
-                server.name = name.as_str().into();
+                server.process_name = name.as_str().into();
                 server
             }),
             notifications_rx,

crates/prettier/src/prettier.rs 🔗

@@ -154,7 +154,7 @@ impl Prettier {
         node: NodeRuntime,
         cx: AsyncAppContext,
     ) -> anyhow::Result<Self> {
-        use lsp::LanguageServerBinary;
+        use lsp::{LanguageServerBinary, LanguageServerName};
 
         let executor = cx.background_executor().clone();
         anyhow::ensure!(
@@ -170,14 +170,17 @@ impl Prettier {
         let node_path = executor
             .spawn(async move { node.binary_path().await })
             .await?;
+        let server_name = LanguageServerName("prettier".into());
+        let server_binary = LanguageServerBinary {
+            path: node_path,
+            arguments: vec![prettier_server.into(), prettier_dir.as_path().into()],
+            env: None,
+        };
         let server = LanguageServer::new(
             Arc::new(parking_lot::Mutex::new(None)),
             server_id,
-            LanguageServerBinary {
-                path: node_path,
-                arguments: vec![prettier_server.into(), prettier_dir.as_path().into()],
-                env: None,
-            },
+            server_name,
+            server_binary,
             &prettier_dir,
             None,
             cx.clone(),

crates/project/src/lsp_store.rs 🔗

@@ -38,16 +38,17 @@ use language::{
     proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
     range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic,
     DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageName,
-    LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName, LanguageToolchainStore,
-    LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset,
-    ToPointUtf16, Transaction, Unclipped,
+    LanguageRegistry, LanguageServerBinaryStatus, LanguageToolchainStore, LocalFile, LspAdapter,
+    LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
+    Unclipped,
 };
 use lsp::{
     CodeActionKind, CompletionContext, DiagnosticSeverity, DiagnosticTag,
     DidChangeWatchedFilesRegistrationOptions, Edit, FileSystemWatcher, InsertTextFormat,
     LanguageServer, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId,
-    LspRequestFuture, MessageActionItem, MessageType, OneOf, ServerHealthStatus, ServerStatus,
-    SymbolKind, TextEdit, Url, WorkDoneProgressCancelParams, WorkspaceFolder,
+    LanguageServerName, LspRequestFuture, MessageActionItem, MessageType, OneOf,
+    ServerHealthStatus, ServerStatus, SymbolKind, TextEdit, Url, WorkDoneProgressCancelParams,
+    WorkspaceFolder,
 };
 use node_runtime::read_package_installed_version;
 use parking_lot::{Mutex, RwLock};
@@ -5598,6 +5599,7 @@ impl LspStore {
 
         let pending_server = cx.spawn({
             let adapter = adapter.clone();
+            let server_name = adapter.name.clone();
             let stderr_capture = stderr_capture.clone();
 
             move |_lsp_store, cx| async move {
@@ -5608,7 +5610,7 @@ impl LspStore {
                     .update(&mut cx.clone(), |this, cx| {
                         this.languages.create_fake_language_server(
                             server_id,
-                            &adapter.name,
+                            &server_name,
                             binary.clone(),
                             cx.to_async(),
                         )
@@ -5622,6 +5624,7 @@ impl LspStore {
                 lsp::LanguageServer::new(
                     stderr_capture,
                     server_id,
+                    server_name,
                     binary,
                     &root_path,
                     adapter.code_action_kinds(),
@@ -6617,7 +6620,7 @@ impl LspStore {
 
         cx.emit(LspStoreEvent::LanguageServerAdded(
             server_id,
-            language_server.name().into(),
+            language_server.name(),
             Some(key.0),
         ));
 

crates/project/src/prettier_store.rs 🔗

@@ -15,9 +15,9 @@ use futures::{
 use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, Task, WeakModel};
 use language::{
     language_settings::{Formatter, LanguageSettings, SelectedFormatter},
-    Buffer, LanguageRegistry, LanguageServerName, LocalFile,
+    Buffer, LanguageRegistry, LocalFile,
 };
-use lsp::{LanguageServer, LanguageServerId};
+use lsp::{LanguageServer, LanguageServerId, LanguageServerName};
 use node_runtime::NodeRuntime;
 use paths::default_prettier_dir;
 use prettier::Prettier;

crates/project/src/project.rs 🔗

@@ -48,12 +48,12 @@ use itertools::Itertools;
 use language::{
     language_settings::InlayHintKind, proto::split_operations, Buffer, BufferEvent,
     CachedLspAdapter, Capability, CodeLabel, DiagnosticEntry, Documentation, File as _, Language,
-    LanguageName, LanguageRegistry, LanguageServerName, PointUtf16, ToOffset, ToPointUtf16,
-    Toolchain, ToolchainList, Transaction, Unclipped,
+    LanguageName, LanguageRegistry, PointUtf16, ToOffset, ToPointUtf16, Toolchain, ToolchainList,
+    Transaction, Unclipped,
 };
 use lsp::{
     CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServer, LanguageServerId,
-    MessageActionItem,
+    LanguageServerName, MessageActionItem,
 };
 use lsp_command::*;
 use node_runtime::NodeRuntime;

crates/project/src/project_settings.rs 🔗

@@ -2,7 +2,7 @@ use anyhow::Context;
 use collections::HashMap;
 use fs::Fs;
 use gpui::{AppContext, AsyncAppContext, BorrowAppContext, EventEmitter, Model, ModelContext};
-use language::LanguageServerName;
+use lsp::LanguageServerName;
 use paths::{
     local_settings_file_relative_path, local_tasks_file_relative_path,
     local_vscode_tasks_file_relative_path, EDITORCONFIG_NAME,

crates/project/src/project_tests.rs 🔗

@@ -1243,7 +1243,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
         events.next().await.unwrap(),
         Event::LanguageServerAdded(
             LanguageServerId(0),
-            fake_server.server.name().into(),
+            fake_server.server.name(),
             Some(worktree_id)
         ),
     );
@@ -1378,7 +1378,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
         events.next().await.unwrap(),
         Event::LanguageServerAdded(
             LanguageServerId(1),
-            fake_server.server.name().into(),
+            fake_server.server.name(),
             Some(worktree_id)
         )
     );
@@ -4865,11 +4865,10 @@ async fn test_multiple_language_server_hovers(cx: &mut gpui::TestAppContext) {
         });
         let new_server_name = new_server.server.name();
         assert!(
-            !servers_with_hover_requests.contains_key(new_server_name),
+            !servers_with_hover_requests.contains_key(&new_server_name),
             "Unexpected: initialized server with the same name twice. Name: `{new_server_name}`"
         );
-        let new_server_name = new_server_name.to_string();
-        match new_server_name.as_str() {
+        match new_server_name.as_ref() {
             "TailwindServer" | "TypeScriptServer" => {
                 servers_with_hover_requests.insert(
                     new_server_name.clone(),
@@ -5089,11 +5088,10 @@ async fn test_multiple_language_server_actions(cx: &mut gpui::TestAppContext) {
         let new_server_name = new_server.server.name();
 
         assert!(
-            !servers_with_actions_requests.contains_key(new_server_name),
+            !servers_with_actions_requests.contains_key(&new_server_name),
             "Unexpected: initialized server with the same name twice. Name: `{new_server_name}`"
         );
-        let new_server_name = new_server_name.to_string();
-        match new_server_name.as_str() {
+        match new_server_name.0.as_ref() {
             "TailwindServer" | "TypeScriptServer" => {
                 servers_with_actions_requests.insert(
                     new_server_name.clone(),

crates/remote_server/src/remote_editing_tests.rs 🔗

@@ -6,10 +6,9 @@ use gpui::{Context, Model, SemanticVersion, TestAppContext};
 use http_client::{BlockedHttpClient, FakeHttpClient};
 use language::{
     language_settings::{language_settings, AllLanguageSettings},
-    Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LanguageServerName,
-    LineEnding,
+    Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding,
 };
-use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind};
+use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind, LanguageServerName};
 use node_runtime::NodeRuntime;
 use project::{
     search::{SearchQuery, SearchResult},