Introduce a LanguageServerId wrapper type

Max Brunsfeld created

Clarify the meaning of all the usizes in use in all of these
struct fields an method signatures.

Change summary

Cargo.lock                                   |  1 
crates/collab/src/tests/integration_tests.rs |  9 +-
crates/copilot/src/copilot.rs                |  4 
crates/diagnostics/Cargo.toml                |  2 
crates/diagnostics/src/diagnostics.rs        | 26 ++++--
crates/diagnostics/src/items.rs              |  3 
crates/editor/src/hover_popover.rs           |  3 
crates/language/src/buffer.rs                | 13 +-
crates/language/src/buffer_tests.rs          |  2 
crates/language/src/language.rs              | 11 ++
crates/language/src/proto.rs                 | 10 +-
crates/lsp/src/lsp.rs                        | 23 ++++-
crates/project/src/lsp_command.rs            | 26 +++---
crates/project/src/project.rs                | 84 ++++++++++++---------
crates/project/src/project_tests.rs          | 40 +++++----
crates/project/src/worktree.rs               | 26 ++++--
16 files changed, 166 insertions(+), 117 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1834,6 +1834,7 @@ dependencies = [
  "editor",
  "gpui",
  "language",
+ "lsp",
  "postage",
  "project",
  "serde_json",

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

@@ -22,6 +22,7 @@ use language::{
     LanguageConfig, OffsetRangeExt, Point, Rope,
 };
 use live_kit_client::MacOSDisplay;
+use lsp::LanguageServerId;
 use project::{search::SearchQuery, DiagnosticSummary, Project, ProjectPath};
 use rand::prelude::*;
 use serde_json::json;
@@ -3477,7 +3478,7 @@ async fn test_collaborating_with_diagnostics(
                     worktree_id,
                     path: Arc::from(Path::new("a.rs")),
                 },
-                0,
+                LanguageServerId(0),
                 DiagnosticSummary {
                     error_count: 1,
                     warning_count: 0,
@@ -3513,7 +3514,7 @@ async fn test_collaborating_with_diagnostics(
                 worktree_id,
                 path: Arc::from(Path::new("a.rs")),
             },
-            0,
+            LanguageServerId(0),
             DiagnosticSummary {
                 error_count: 1,
                 warning_count: 0,
@@ -3554,7 +3555,7 @@ async fn test_collaborating_with_diagnostics(
                     worktree_id,
                     path: Arc::from(Path::new("a.rs")),
                 },
-                0,
+                LanguageServerId(0),
                 DiagnosticSummary {
                     error_count: 1,
                     warning_count: 1,
@@ -3570,7 +3571,7 @@ async fn test_collaborating_with_diagnostics(
                     worktree_id,
                     path: Arc::from(Path::new("a.rs")),
                 },
-                0,
+                LanguageServerId(0),
                 DiagnosticSummary {
                     error_count: 1,
                     warning_count: 1,

crates/copilot/src/copilot.rs 🔗

@@ -14,7 +14,7 @@ use language::{
     ToPointUtf16,
 };
 use log::{debug, error};
-use lsp::LanguageServer;
+use lsp::{LanguageServer, LanguageServerId};
 use node_runtime::NodeRuntime;
 use request::{LogMessage, StatusNotification};
 use settings::Settings;
@@ -380,7 +380,7 @@ impl Copilot {
                 let node_path = node_runtime.binary_path().await?;
                 let arguments: &[OsString] = &[server_path.into(), "--stdio".into()];
                 let server = LanguageServer::new(
-                    0,
+                    LanguageServerId(0),
                     &node_path,
                     arguments,
                     Path::new("/"),

crates/diagnostics/Cargo.toml 🔗

@@ -14,6 +14,7 @@ smallvec = { version = "1.6", features = ["union"] }
 collections = { path = "../collections" }
 editor = { path = "../editor" }
 language = { path = "../language" }
+lsp = { path = "../lsp" }
 gpui = { path = "../gpui" }
 project = { path = "../project" }
 settings = { path = "../settings" }
@@ -27,6 +28,7 @@ unindent = "0.1"
 client = { path = "../client", features = ["test-support"] }
 editor = { path = "../editor", features = ["test-support"] }
 language = { path = "../language", features = ["test-support"] }
+lsp = { path = "../lsp", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }
 serde_json = { workspace = true }

crates/diagnostics/src/diagnostics.rs 🔗

@@ -18,6 +18,7 @@ use language::{
     Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
     SelectionGoal,
 };
+use lsp::LanguageServerId;
 use project::{DiagnosticSummary, Project, ProjectPath};
 use serde_json::json;
 use settings::Settings;
@@ -56,7 +57,7 @@ struct ProjectDiagnosticsEditor {
     summary: DiagnosticSummary,
     excerpts: ModelHandle<MultiBuffer>,
     path_states: Vec<PathState>,
-    paths_to_update: BTreeMap<ProjectPath, usize>,
+    paths_to_update: BTreeMap<ProjectPath, LanguageServerId>,
 }
 
 struct PathState {
@@ -116,7 +117,7 @@ impl View for ProjectDiagnosticsEditor {
             }),
             "summary": self.summary,
             "paths_to_update": self.paths_to_update.iter().map(|(path, server_id)|
-                (path.path.to_string_lossy(), server_id)
+                (path.path.to_string_lossy(), server_id.0)
             ).collect::<Vec<_>>(),
             "paths_states": self.path_states.iter().map(|state|
                 json!({
@@ -196,7 +197,11 @@ impl ProjectDiagnosticsEditor {
         }
     }
 
-    fn update_excerpts(&mut self, language_server_id: Option<usize>, cx: &mut ViewContext<Self>) {
+    fn update_excerpts(
+        &mut self,
+        language_server_id: Option<LanguageServerId>,
+        cx: &mut ViewContext<Self>,
+    ) {
         let mut paths = Vec::new();
         self.paths_to_update.retain(|path, server_id| {
             if language_server_id
@@ -809,6 +814,7 @@ mod tests {
             )
             .await;
 
+        let language_server_id = LanguageServerId(0);
         let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
         let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 
@@ -816,7 +822,7 @@ mod tests {
         project.update(cx, |project, cx| {
             project
                 .update_diagnostic_entries(
-                    0,
+                    language_server_id,
                     PathBuf::from("/test/main.rs"),
                     None,
                     vec![
@@ -965,10 +971,10 @@ mod tests {
 
         // Diagnostics are added for another earlier path.
         project.update(cx, |project, cx| {
-            project.disk_based_diagnostics_started(0, cx);
+            project.disk_based_diagnostics_started(language_server_id, cx);
             project
                 .update_diagnostic_entries(
-                    0,
+                    language_server_id,
                     PathBuf::from("/test/consts.rs"),
                     None,
                     vec![DiagnosticEntry {
@@ -985,7 +991,7 @@ mod tests {
                     cx,
                 )
                 .unwrap();
-            project.disk_based_diagnostics_finished(0, cx);
+            project.disk_based_diagnostics_finished(language_server_id, cx);
         });
 
         view.next_notification(cx).await;
@@ -1065,10 +1071,10 @@ mod tests {
 
         // Diagnostics are added to the first path
         project.update(cx, |project, cx| {
-            project.disk_based_diagnostics_started(0, cx);
+            project.disk_based_diagnostics_started(language_server_id, cx);
             project
                 .update_diagnostic_entries(
-                    0,
+                    language_server_id,
                     PathBuf::from("/test/consts.rs"),
                     None,
                     vec![
@@ -1101,7 +1107,7 @@ mod tests {
                     cx,
                 )
                 .unwrap();
-            project.disk_based_diagnostics_finished(0, cx);
+            project.disk_based_diagnostics_finished(language_server_id, cx);
         });
 
         view.next_notification(cx).await;

crates/diagnostics/src/items.rs 🔗

@@ -7,6 +7,7 @@ use gpui::{
     ViewHandle, WeakViewHandle,
 };
 use language::Diagnostic;
+use lsp::LanguageServerId;
 use project::Project;
 use settings::Settings;
 use workspace::{item::ItemHandle, StatusItemView};
@@ -15,7 +16,7 @@ pub struct DiagnosticIndicator {
     summary: project::DiagnosticSummary,
     active_editor: Option<WeakViewHandle<Editor>>,
     current_diagnostic: Option<Diagnostic>,
-    in_progress_checks: HashSet<usize>,
+    in_progress_checks: HashSet<LanguageServerId>,
     _observe_active_editor: Option<Subscription>,
 }
 

crates/editor/src/hover_popover.rs 🔗

@@ -436,6 +436,7 @@ mod tests {
     use indoc::indoc;
 
     use language::{Diagnostic, DiagnosticSet};
+    use lsp::LanguageServerId;
     use project::HoverBlock;
     use smol::stream::StreamExt;
 
@@ -620,7 +621,7 @@ mod tests {
                 }],
                 &snapshot,
             );
-            buffer.update_diagnostics(0, set, cx);
+            buffer.update_diagnostics(LanguageServerId(0), set, cx);
         });
 
         // Hover pops diagnostic immediately

crates/language/src/buffer.rs 🔗

@@ -17,6 +17,7 @@ use collections::HashMap;
 use fs::LineEnding;
 use futures::FutureExt as _;
 use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, Task};
+use lsp::LanguageServerId;
 use parking_lot::Mutex;
 use settings::Settings;
 use similar::{ChangeTag, TextDiff};
@@ -72,7 +73,7 @@ pub struct Buffer {
     syntax_map: Mutex<SyntaxMap>,
     parsing_in_background: bool,
     parse_count: usize,
-    diagnostics: HashMap<usize, DiagnosticSet>, // server_id -> diagnostic set
+    diagnostics: HashMap<LanguageServerId, DiagnosticSet>,
     remote_selections: TreeMap<ReplicaId, SelectionSet>,
     selections_update_count: usize,
     diagnostics_update_count: usize,
@@ -89,7 +90,7 @@ pub struct BufferSnapshot {
     pub git_diff: git::diff::BufferDiff,
     pub(crate) syntax: SyntaxSnapshot,
     file: Option<Arc<dyn File>>,
-    diagnostics: HashMap<usize, DiagnosticSet>, // server_id -> diagnostic set
+    diagnostics: HashMap<LanguageServerId, DiagnosticSet>,
     diagnostics_update_count: usize,
     file_update_count: usize,
     git_diff_update_count: usize,
@@ -157,7 +158,7 @@ pub struct Completion {
 
 #[derive(Clone, Debug)]
 pub struct CodeAction {
-    pub server_id: usize,
+    pub server_id: LanguageServerId,
     pub range: Range<Anchor>,
     pub lsp_action: lsp::CodeAction,
 }
@@ -167,7 +168,7 @@ pub enum Operation {
     Buffer(text::Operation),
 
     UpdateDiagnostics {
-        server_id: usize,
+        server_id: LanguageServerId,
         diagnostics: Arc<[DiagnosticEntry<Anchor>]>,
         lamport_timestamp: clock::Lamport,
     },
@@ -879,7 +880,7 @@ impl Buffer {
 
     pub fn update_diagnostics(
         &mut self,
-        server_id: usize,
+        server_id: LanguageServerId,
         diagnostics: DiagnosticSet,
         cx: &mut ModelContext<Self>,
     ) {
@@ -1645,7 +1646,7 @@ impl Buffer {
 
     fn apply_diagnostic_update(
         &mut self,
-        server_id: usize,
+        server_id: LanguageServerId,
         diagnostics: DiagnosticSet,
         lamport_timestamp: clock::Lamport,
         cx: &mut ModelContext<Self>,

crates/language/src/buffer_tests.rs 🔗

@@ -1866,7 +1866,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
                         buffer,
                     );
                     log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
-                    buffer.update_diagnostics(0, diagnostics, cx);
+                    buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
                 });
                 mutation_count -= 1;
             }

crates/language/src/language.rs 🔗

@@ -54,6 +54,7 @@ use futures::channel::mpsc;
 pub use buffer::Operation;
 pub use buffer::*;
 pub use diagnostic_set::DiagnosticEntry;
+pub use lsp::LanguageServerId;
 pub use outline::{Outline, OutlineItem};
 pub use tree_sitter::{Parser, Tree};
 
@@ -524,7 +525,7 @@ struct LanguageRegistryState {
 }
 
 pub struct PendingLanguageServer {
-    pub server_id: usize,
+    pub server_id: LanguageServerId,
     pub task: Task<Result<lsp::LanguageServer>>,
 }
 
@@ -819,7 +820,7 @@ impl LanguageRegistry {
                 Ok(server)
             });
 
-            let server_id = post_inc(&mut self.state.write().next_language_server_id);
+            let server_id = self.state.write().next_language_server_id();
             return Some(PendingLanguageServer { server_id, task });
         }
 
@@ -837,7 +838,7 @@ impl LanguageRegistry {
         let adapter = adapter.clone();
         let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
         let login_shell_env_loaded = self.login_shell_env_loaded.clone();
-        let server_id = post_inc(&mut self.state.write().next_language_server_id);
+        let server_id = self.state.write().next_language_server_id();
 
         let task = cx.spawn(|cx| async move {
             login_shell_env_loaded.await;
@@ -884,6 +885,10 @@ impl LanguageRegistry {
 }
 
 impl LanguageRegistryState {
+    fn next_language_server_id(&mut self) -> LanguageServerId {
+        LanguageServerId(post_inc(&mut self.next_language_server_id))
+    }
+
     fn add(&mut self, language: Arc<Language>) {
         if let Some(theme) = self.theme.as_ref() {
             language.set_theme(&theme.editor.syntax);

crates/language/src/proto.rs 🔗

@@ -4,7 +4,7 @@ use crate::{
 };
 use anyhow::{anyhow, Result};
 use clock::ReplicaId;
-use lsp::DiagnosticSeverity;
+use lsp::{DiagnosticSeverity, LanguageServerId};
 use rpc::proto;
 use std::{ops::Range, sync::Arc};
 use text::*;
@@ -80,7 +80,7 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
             } => proto::operation::Variant::UpdateDiagnostics(proto::UpdateDiagnostics {
                 replica_id: lamport_timestamp.replica_id as u32,
                 lamport_timestamp: lamport_timestamp.value,
-                server_id: *server_id as u64,
+                server_id: server_id.0 as u64,
                 diagnostics: serialize_diagnostics(diagnostics.iter()),
             }),
 
@@ -277,7 +277,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operati
                         replica_id: message.replica_id as ReplicaId,
                         value: message.lamport_timestamp,
                     },
-                    server_id: message.server_id as usize,
+                    server_id: LanguageServerId(message.server_id as usize),
                     diagnostics: deserialize_diagnostics(message.diagnostics),
                 }
             }
@@ -469,7 +469,7 @@ pub async fn deserialize_completion(
 
 pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
     proto::CodeAction {
-        server_id: action.server_id as u64,
+        server_id: action.server_id.0 as u64,
         start: Some(serialize_anchor(&action.range.start)),
         end: Some(serialize_anchor(&action.range.end)),
         lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(),
@@ -487,7 +487,7 @@ pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction>
         .ok_or_else(|| anyhow!("invalid end"))?;
     let lsp_action = serde_json::from_slice(&action.lsp_action)?;
     Ok(CodeAction {
-        server_id: action.server_id as usize,
+        server_id: LanguageServerId(action.server_id as usize),
         range: start..end,
         lsp_action,
     })

crates/lsp/src/lsp.rs 🔗

@@ -16,6 +16,7 @@ use smol::{
     process::{self, Child},
 };
 use std::{
+    fmt,
     future::Future,
     io::Write,
     path::PathBuf,
@@ -35,7 +36,7 @@ type NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppCon
 type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
 
 pub struct LanguageServer {
-    server_id: usize,
+    server_id: LanguageServerId,
     next_id: AtomicUsize,
     outbound_tx: channel::Sender<Vec<u8>>,
     name: String,
@@ -51,6 +52,10 @@ pub struct LanguageServer {
     _server: Option<Child>,
 }
 
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[repr(transparent)]
+pub struct LanguageServerId(pub usize);
+
 pub struct Subscription {
     method: &'static str,
     notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
@@ -107,7 +112,7 @@ struct Error {
 
 impl LanguageServer {
     pub fn new<T: AsRef<std::ffi::OsStr>>(
-        server_id: usize,
+        server_id: LanguageServerId,
         binary_path: &Path,
         arguments: &[T],
         root_path: &Path,
@@ -158,7 +163,7 @@ impl LanguageServer {
     }
 
     fn new_internal<Stdin, Stdout, F>(
-        server_id: usize,
+        server_id: LanguageServerId,
         stdin: Stdin,
         stdout: Stdout,
         server: Option<Child>,
@@ -581,7 +586,7 @@ impl LanguageServer {
         &self.capabilities
     }
 
-    pub fn server_id(&self) -> usize {
+    pub fn server_id(&self) -> LanguageServerId {
         self.server_id
     }
 
@@ -685,6 +690,12 @@ impl Subscription {
     }
 }
 
+impl fmt::Display for LanguageServerId {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
 impl Drop for Subscription {
     fn drop(&mut self) {
         self.notification_handlers.lock().remove(self.method);
@@ -720,7 +731,7 @@ impl LanguageServer {
         let (notifications_tx, notifications_rx) = channel::unbounded();
 
         let server = Self::new_internal(
-            0,
+            LanguageServerId(0),
             stdin_writer,
             stdout_reader,
             None,
@@ -731,7 +742,7 @@ impl LanguageServer {
         );
         let fake = FakeLanguageServer {
             server: Arc::new(Self::new_internal(
-                0,
+                LanguageServerId(0),
                 stdout_writer,
                 stdin_reader,
                 None,

crates/project/src/lsp_command.rs 🔗

@@ -12,7 +12,7 @@ use language::{
     range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction,
     Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Unclipped,
 };
-use lsp::{DocumentHighlightKind, LanguageServer, ServerCapabilities};
+use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, ServerCapabilities};
 use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
 use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
 
@@ -39,7 +39,7 @@ pub(crate) trait LspCommand: 'static + Sized {
         message: <Self::LspRequest as lsp::request::Request>::Result,
         project: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
-        server_id: usize,
+        server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Self::Response>;
 
@@ -143,7 +143,7 @@ impl LspCommand for PrepareRename {
         message: Option<lsp::PrepareRenameResponse>,
         _: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
-        _: usize,
+        _: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Option<Range<Anchor>>> {
         buffer.read_with(&cx, |buffer, _| {
@@ -270,7 +270,7 @@ impl LspCommand for PerformRename {
         message: Option<lsp::WorkspaceEdit>,
         project: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
-        server_id: usize,
+        server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<ProjectTransaction> {
         if let Some(edit) = message {
@@ -389,7 +389,7 @@ impl LspCommand for GetDefinition {
         message: Option<lsp::GotoDefinitionResponse>,
         project: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
-        server_id: usize,
+        server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
         location_links_from_lsp(message, project, buffer, server_id, cx).await
@@ -482,7 +482,7 @@ impl LspCommand for GetTypeDefinition {
         message: Option<lsp::GotoTypeDefinitionResponse>,
         project: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
-        server_id: usize,
+        server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
         location_links_from_lsp(message, project, buffer, server_id, cx).await
@@ -548,7 +548,7 @@ impl LspCommand for GetTypeDefinition {
 fn language_server_for_buffer(
     project: &ModelHandle<Project>,
     buffer: &ModelHandle<Buffer>,
-    server_id: usize,
+    server_id: LanguageServerId,
     cx: &mut AsyncAppContext,
 ) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
     project
@@ -626,7 +626,7 @@ async fn location_links_from_lsp(
     message: Option<lsp::GotoDefinitionResponse>,
     project: ModelHandle<Project>,
     buffer: ModelHandle<Buffer>,
-    server_id: usize,
+    server_id: LanguageServerId,
     mut cx: AsyncAppContext,
 ) -> Result<Vec<LocationLink>> {
     let message = match message {
@@ -770,7 +770,7 @@ impl LspCommand for GetReferences {
         locations: Option<Vec<lsp::Location>>,
         project: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
-        server_id: usize,
+        server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Location>> {
         let mut references = Vec::new();
@@ -932,7 +932,7 @@ impl LspCommand for GetDocumentHighlights {
         lsp_highlights: Option<Vec<lsp::DocumentHighlight>>,
         _: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
-        _: usize,
+        _: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Vec<DocumentHighlight>> {
         buffer.read_with(&cx, |buffer, _| {
@@ -1078,7 +1078,7 @@ impl LspCommand for GetHover {
         message: Option<lsp::Hover>,
         _: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
-        _: usize,
+        _: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Self::Response> {
         Ok(message.and_then(|hover| {
@@ -1300,7 +1300,7 @@ impl LspCommand for GetCompletions {
         completions: Option<lsp::CompletionResponse>,
         _: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
-        _: usize,
+        _: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Vec<Completion>> {
         let completions = if let Some(completions) = completions {
@@ -1520,7 +1520,7 @@ impl LspCommand for GetCodeActions {
         actions: Option<lsp::CodeActionResponse>,
         _: ModelHandle<Project>,
         _: ModelHandle<Buffer>,
-        server_id: usize,
+        server_id: LanguageServerId,
         _: AsyncAppContext,
     ) -> Result<Vec<CodeAction>> {
         Ok(actions

crates/project/src/project.rs 🔗

@@ -36,7 +36,7 @@ use language::{
 };
 use lsp::{
     DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
-    DocumentHighlightKind, LanguageServer, LanguageString, MarkedString,
+    DocumentHighlightKind, LanguageServer, LanguageServerId, LanguageString, MarkedString,
 };
 use lsp_command::*;
 use lsp_glob_set::LspGlobSet;
@@ -95,10 +95,10 @@ pub struct Project {
     active_entry: Option<ProjectEntryId>,
     buffer_changes_tx: mpsc::UnboundedSender<BufferMessage>,
     languages: Arc<LanguageRegistry>,
-    language_servers: HashMap<usize, LanguageServerState>,
-    language_server_ids: HashMap<(WorktreeId, LanguageServerName), usize>,
-    language_server_statuses: BTreeMap<usize, LanguageServerStatus>,
-    last_workspace_edits_by_language_server: HashMap<usize, ProjectTransaction>,
+    language_servers: HashMap<LanguageServerId, LanguageServerState>,
+    language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>,
+    language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
+    last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,
     client: Arc<client::Client>,
     next_entry_id: Arc<AtomicUsize>,
     join_project_response_message_id: u32,
@@ -123,7 +123,7 @@ pub struct Project {
     /// A mapping from a buffer ID to None means that we've started waiting for an ID but haven't finished loading it.
     /// Used for re-issuing buffer requests when peers temporarily disconnect
     incomplete_remote_buffers: HashMap<u64, Option<ModelHandle<Buffer>>>,
-    buffer_snapshots: HashMap<u64, HashMap<usize, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
+    buffer_snapshots: HashMap<u64, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
     buffers_being_formatted: HashSet<usize>,
     nonce: u128,
     _maintain_buffer_languages: Task<()>,
@@ -189,14 +189,14 @@ pub enum Event {
     WorktreeAdded,
     WorktreeRemoved(WorktreeId),
     DiskBasedDiagnosticsStarted {
-        language_server_id: usize,
+        language_server_id: LanguageServerId,
     },
     DiskBasedDiagnosticsFinished {
-        language_server_id: usize,
+        language_server_id: LanguageServerId,
     },
     DiagnosticsUpdated {
         path: ProjectPath,
-        language_server_id: usize,
+        language_server_id: LanguageServerId,
     },
     RemoteIdChanged(Option<u64>),
     DisconnectedFromHost,
@@ -336,10 +336,14 @@ impl DiagnosticSummary {
         self.error_count == 0 && self.warning_count == 0
     }
 
-    pub fn to_proto(&self, language_server_id: usize, path: &Path) -> proto::DiagnosticSummary {
+    pub fn to_proto(
+        &self,
+        language_server_id: LanguageServerId,
+        path: &Path,
+    ) -> proto::DiagnosticSummary {
         proto::DiagnosticSummary {
             path: path.to_string_lossy().to_string(),
-            language_server_id: language_server_id as u64,
+            language_server_id: language_server_id.0 as u64,
             error_count: self.error_count as u32,
             warning_count: self.warning_count as u32,
         }
@@ -541,7 +545,7 @@ impl Project {
                     .into_iter()
                     .map(|server| {
                         (
-                            server.id as usize,
+                            LanguageServerId(server.id as usize),
                             LanguageServerStatus {
                                 name: server.name,
                                 pending_work: Default::default(),
@@ -1025,7 +1029,7 @@ impl Project {
                 .send(proto::StartLanguageServer {
                     project_id,
                     server: Some(proto::LanguageServer {
-                        id: *server_id as u64,
+                        id: server_id.0 as u64,
                         name: status.name.clone(),
                     }),
                 })
@@ -1152,7 +1156,7 @@ impl Project {
             .into_iter()
             .map(|server| {
                 (
-                    server.id as usize,
+                    LanguageServerId(server.id as usize),
                     LanguageServerStatus {
                         name: server.name,
                         pending_work: Default::default(),
@@ -1444,7 +1448,7 @@ impl Project {
     fn open_local_buffer_via_lsp(
         &mut self,
         abs_path: lsp::Url,
-        language_server_id: usize,
+        language_server_id: LanguageServerId,
         language_server_name: LanguageServerName,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<ModelHandle<Buffer>>> {
@@ -2381,7 +2385,7 @@ impl Project {
                         .send(proto::StartLanguageServer {
                             project_id,
                             server: Some(proto::LanguageServer {
-                                id: server_id as u64,
+                                id: server_id.0 as u64,
                                 name: language_server.name().to_string(),
                             }),
                         })
@@ -2603,7 +2607,7 @@ impl Project {
     fn on_lsp_progress(
         &mut self,
         progress: lsp::ProgressParams,
-        server_id: usize,
+        server_id: LanguageServerId,
         disk_based_diagnostics_progress_token: Option<String>,
         cx: &mut ModelContext<Self>,
     ) {
@@ -2715,7 +2719,7 @@ impl Project {
 
     fn on_lsp_work_start(
         &mut self,
-        language_server_id: usize,
+        language_server_id: LanguageServerId,
         token: String,
         progress: LanguageServerProgress,
         cx: &mut ModelContext<Self>,
@@ -2728,7 +2732,7 @@ impl Project {
 
     fn on_lsp_work_progress(
         &mut self,
-        language_server_id: usize,
+        language_server_id: LanguageServerId,
         token: String,
         progress: LanguageServerProgress,
         cx: &mut ModelContext<Self>,
@@ -2755,7 +2759,7 @@ impl Project {
 
     fn on_lsp_work_end(
         &mut self,
-        language_server_id: usize,
+        language_server_id: LanguageServerId,
         token: String,
         cx: &mut ModelContext<Self>,
     ) {
@@ -2767,7 +2771,7 @@ impl Project {
 
     fn on_lsp_did_change_watched_files(
         &mut self,
-        language_server_id: usize,
+        language_server_id: LanguageServerId,
         params: DidChangeWatchedFilesRegistrationOptions,
         cx: &mut ModelContext<Self>,
     ) {
@@ -2785,7 +2789,7 @@ impl Project {
     async fn on_lsp_workspace_edit(
         this: WeakModelHandle<Self>,
         params: lsp::ApplyWorkspaceEditParams,
-        server_id: usize,
+        server_id: LanguageServerId,
         adapter: Arc<CachedLspAdapter>,
         language_server: Arc<LanguageServer>,
         mut cx: AsyncAppContext,
@@ -2818,14 +2822,14 @@ impl Project {
 
     fn broadcast_language_server_update(
         &self,
-        language_server_id: usize,
+        language_server_id: LanguageServerId,
         event: proto::update_language_server::Variant,
     ) {
         if let Some(project_id) = self.remote_id() {
             self.client
                 .send(proto::UpdateLanguageServer {
                     project_id,
-                    language_server_id: language_server_id as u64,
+                    language_server_id: language_server_id.0 as u64,
                     variant: Some(event),
                 })
                 .log_err();
@@ -2840,7 +2844,7 @@ impl Project {
 
     pub fn update_diagnostics(
         &mut self,
-        language_server_id: usize,
+        language_server_id: LanguageServerId,
         mut params: lsp::PublishDiagnosticsParams,
         disk_based_sources: &[String],
         cx: &mut ModelContext<Self>,
@@ -2960,7 +2964,7 @@ impl Project {
 
     pub fn update_diagnostic_entries(
         &mut self,
-        server_id: usize,
+        server_id: LanguageServerId,
         abs_path: PathBuf,
         version: Option<i32>,
         diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
@@ -2997,7 +3001,7 @@ impl Project {
     fn update_buffer_diagnostics(
         &mut self,
         buffer: &ModelHandle<Buffer>,
-        server_id: usize,
+        server_id: LanguageServerId,
         version: Option<i32>,
         mut diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
         cx: &mut ModelContext<Self>,
@@ -4712,7 +4716,7 @@ impl Project {
 
     pub fn language_servers_running_disk_based_diagnostics(
         &self,
-    ) -> impl Iterator<Item = usize> + '_ {
+    ) -> impl Iterator<Item = LanguageServerId> + '_ {
         self.language_server_statuses
             .iter()
             .filter_map(|(id, status)| {
@@ -4736,7 +4740,7 @@ impl Project {
     pub fn diagnostic_summaries<'a>(
         &'a self,
         cx: &'a AppContext,
-    ) -> impl Iterator<Item = (ProjectPath, usize, DiagnosticSummary)> + 'a {
+    ) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
         self.visible_worktrees(cx).flat_map(move |worktree| {
             let worktree = worktree.read(cx);
             let worktree_id = worktree.id();
@@ -4750,7 +4754,7 @@ impl Project {
 
     pub fn disk_based_diagnostics_started(
         &mut self,
-        language_server_id: usize,
+        language_server_id: LanguageServerId,
         cx: &mut ModelContext<Self>,
     ) {
         cx.emit(Event::DiskBasedDiagnosticsStarted { language_server_id });
@@ -4758,7 +4762,7 @@ impl Project {
 
     pub fn disk_based_diagnostics_finished(
         &mut self,
-        language_server_id: usize,
+        language_server_id: LanguageServerId,
         cx: &mut ModelContext<Self>,
     ) {
         cx.emit(Event::DiskBasedDiagnosticsFinished { language_server_id });
@@ -5065,7 +5069,7 @@ impl Project {
                             .update_diagnostic_summary(project_path.path.clone(), &summary);
                     });
                     cx.emit(Event::DiagnosticsUpdated {
-                        language_server_id: summary.language_server_id as usize,
+                        language_server_id: LanguageServerId(summary.language_server_id as usize),
                         path: project_path,
                     });
                 }
@@ -5086,7 +5090,7 @@ impl Project {
             .ok_or_else(|| anyhow!("invalid server"))?;
         this.update(&mut cx, |this, cx| {
             this.language_server_statuses.insert(
-                server.id as usize,
+                LanguageServerId(server.id as usize),
                 LanguageServerStatus {
                     name: server.name,
                     pending_work: Default::default(),
@@ -5106,7 +5110,7 @@ impl Project {
         mut cx: AsyncAppContext,
     ) -> Result<()> {
         this.update(&mut cx, |this, cx| {
-            let language_server_id = envelope.payload.language_server_id as usize;
+            let language_server_id = LanguageServerId(envelope.payload.language_server_id as usize);
 
             match envelope
                 .payload
@@ -6142,7 +6146,7 @@ impl Project {
         &mut self,
         buffer: &ModelHandle<Buffer>,
         lsp_edits: impl 'static + Send + IntoIterator<Item = lsp::TextEdit>,
-        server_id: usize,
+        server_id: LanguageServerId,
         version: Option<i32>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<(Range<Anchor>, String)>>> {
@@ -6245,7 +6249,7 @@ impl Project {
     fn buffer_snapshot_for_lsp_version(
         &mut self,
         buffer: &ModelHandle<Buffer>,
-        server_id: usize,
+        server_id: LanguageServerId,
         version: Option<i32>,
         cx: &AppContext,
     ) -> Result<TextBufferSnapshot> {
@@ -6314,14 +6318,18 @@ impl Project {
     fn language_server_for_buffer(
         &self,
         buffer: &Buffer,
-        server_id: usize,
+        server_id: LanguageServerId,
         cx: &AppContext,
     ) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
         self.language_servers_iter_for_buffer(buffer, cx)
             .find(|(_, s)| s.server_id() == server_id)
     }
 
-    fn language_server_ids_for_buffer(&self, buffer: &Buffer, cx: &AppContext) -> Vec<usize> {
+    fn language_server_ids_for_buffer(
+        &self,
+        buffer: &Buffer,
+        cx: &AppContext,
+    ) -> Vec<LanguageServerId> {
         if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) {
             let worktree_id = file.worktree_id(cx);
             language

crates/project/src/project_tests.rs 🔗

@@ -303,7 +303,7 @@ async fn test_managing_language_servers(
 
     rust_buffer2.update(cx, |buffer, cx| {
         buffer.update_diagnostics(
-            0,
+            LanguageServerId(0),
             DiagnosticSet::from_sorted_entries(
                 vec![DiagnosticEntry {
                     diagnostic: Default::default(),
@@ -582,7 +582,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
     project.update(cx, |project, cx| {
         project
             .update_diagnostics(
-                0,
+                LanguageServerId(0),
                 lsp::PublishDiagnosticsParams {
                     uri: Url::from_file_path("/dir/a.rs").unwrap(),
                     version: None,
@@ -599,7 +599,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
             .unwrap();
         project
             .update_diagnostics(
-                0,
+                LanguageServerId(0),
                 lsp::PublishDiagnosticsParams {
                     uri: Url::from_file_path("/dir/b.rs").unwrap(),
                     version: None,
@@ -675,7 +675,7 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
     project.update(cx, |project, cx| {
         project
             .update_diagnostics(
-                0,
+                LanguageServerId(0),
                 lsp::PublishDiagnosticsParams {
                     uri: Url::from_file_path("/root/other.rs").unwrap(),
                     version: None,
@@ -767,7 +767,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
     assert_eq!(
         events.next().await.unwrap(),
         Event::DiskBasedDiagnosticsStarted {
-            language_server_id: 0,
+            language_server_id: LanguageServerId(0),
         }
     );
 
@@ -784,7 +784,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
     assert_eq!(
         events.next().await.unwrap(),
         Event::DiagnosticsUpdated {
-            language_server_id: 0,
+            language_server_id: LanguageServerId(0),
             path: (worktree_id, Path::new("a.rs")).into()
         }
     );
@@ -793,7 +793,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
     assert_eq!(
         events.next().await.unwrap(),
         Event::DiskBasedDiagnosticsFinished {
-            language_server_id: 0
+            language_server_id: LanguageServerId(0)
         }
     );
 
@@ -831,7 +831,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
     assert_eq!(
         events.next().await.unwrap(),
         Event::DiagnosticsUpdated {
-            language_server_id: 0,
+            language_server_id: LanguageServerId(0),
             path: (worktree_id, Path::new("a.rs")).into()
         }
     );
@@ -892,7 +892,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
     assert_eq!(
         events.next().await.unwrap(),
         Event::DiskBasedDiagnosticsStarted {
-            language_server_id: 1
+            language_server_id: LanguageServerId(1)
         }
     );
     project.read_with(cx, |project, _| {
@@ -900,7 +900,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
             project
                 .language_servers_running_disk_based_diagnostics()
                 .collect::<Vec<_>>(),
-            [1]
+            [LanguageServerId(1)]
         );
     });
 
@@ -910,7 +910,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
     assert_eq!(
         events.next().await.unwrap(),
         Event::DiskBasedDiagnosticsFinished {
-            language_server_id: 1
+            language_server_id: LanguageServerId(1)
         }
     );
     project.read_with(cx, |project, _| {
@@ -918,7 +918,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
             project
                 .language_servers_running_disk_based_diagnostics()
                 .collect::<Vec<_>>(),
-            [0; 0]
+            [LanguageServerId(0); 0]
         );
     });
 }
@@ -1403,7 +1403,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
         project
             .update_buffer_diagnostics(
                 &buffer,
-                0,
+                LanguageServerId(0),
                 None,
                 vec![
                     DiagnosticEntry {
@@ -1464,7 +1464,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC
     project.update(cx, |project, cx| {
         project
             .update_diagnostic_entries(
-                0,
+                LanguageServerId(0),
                 Path::new("/dir/a.rs").to_owned(),
                 None,
                 vec![DiagnosticEntry {
@@ -1481,7 +1481,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC
             .unwrap();
         project
             .update_diagnostic_entries(
-                1,
+                LanguageServerId(1),
                 Path::new("/dir/a.rs").to_owned(),
                 None,
                 vec![DiagnosticEntry {
@@ -1633,7 +1633,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
                         new_text: "".into(),
                     },
                 ],
-                0,
+                LanguageServerId(0),
                 Some(lsp_document_version),
                 cx,
             )
@@ -1728,7 +1728,7 @@ async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestApp
                         new_text: "".into(),
                     },
                 ],
-                0,
+                LanguageServerId(0),
                 None,
                 cx,
             )
@@ -1832,7 +1832,7 @@ async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
                         .unindent(),
                     },
                 ],
-                0,
+                LanguageServerId(0),
                 None,
                 cx,
             )
@@ -3011,7 +3011,9 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
     };
 
     project
-        .update(cx, |p, cx| p.update_diagnostics(0, message, &[], cx))
+        .update(cx, |p, cx| {
+            p.update_diagnostics(LanguageServerId(0), message, &[], cx)
+        })
         .unwrap();
     let buffer = buffer.read_with(cx, |buffer, _| buffer.snapshot());
 

crates/project/src/worktree.rs 🔗

@@ -26,6 +26,7 @@ use language::{
     },
     Buffer, DiagnosticEntry, File as _, PointUtf16, Rope, RopeFingerprint, Unclipped,
 };
+use lsp::LanguageServerId;
 use parking_lot::Mutex;
 use postage::{
     barrier,
@@ -67,8 +68,14 @@ pub struct LocalWorktree {
     is_scanning: (watch::Sender<bool>, watch::Receiver<bool>),
     _background_scanner_task: Task<()>,
     share: Option<ShareState>,
-    diagnostics: HashMap<Arc<Path>, Vec<(usize, Vec<DiagnosticEntry<Unclipped<PointUtf16>>>)>>,
-    diagnostic_summaries: HashMap<Arc<Path>, HashMap<usize, DiagnosticSummary>>,
+    diagnostics: HashMap<
+        Arc<Path>,
+        Vec<(
+            LanguageServerId,
+            Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
+        )>,
+    >,
+    diagnostic_summaries: HashMap<Arc<Path>, HashMap<LanguageServerId, DiagnosticSummary>>,
     client: Arc<Client>,
     fs: Arc<dyn Fs>,
     visible: bool,
@@ -82,7 +89,7 @@ pub struct RemoteWorktree {
     updates_tx: Option<UnboundedSender<proto::UpdateWorktree>>,
     snapshot_subscriptions: VecDeque<(usize, oneshot::Sender<()>)>,
     replica_id: ReplicaId,
-    diagnostic_summaries: HashMap<Arc<Path>, HashMap<usize, DiagnosticSummary>>,
+    diagnostic_summaries: HashMap<Arc<Path>, HashMap<LanguageServerId, DiagnosticSummary>>,
     visible: bool,
     disconnected: bool,
 }
@@ -463,7 +470,7 @@ impl Worktree {
 
     pub fn diagnostic_summaries(
         &self,
-    ) -> impl Iterator<Item = (Arc<Path>, usize, DiagnosticSummary)> + '_ {
+    ) -> impl Iterator<Item = (Arc<Path>, LanguageServerId, DiagnosticSummary)> + '_ {
         match self {
             Worktree::Local(worktree) => &worktree.diagnostic_summaries,
             Worktree::Remote(worktree) => &worktree.diagnostic_summaries,
@@ -518,13 +525,16 @@ impl LocalWorktree {
     pub fn diagnostics_for_path(
         &self,
         path: &Path,
-    ) -> Vec<(usize, Vec<DiagnosticEntry<Unclipped<PointUtf16>>>)> {
+    ) -> Vec<(
+        LanguageServerId,
+        Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
+    )> {
         self.diagnostics.get(path).cloned().unwrap_or_default()
     }
 
     pub fn update_diagnostics(
         &mut self,
-        server_id: usize,
+        server_id: LanguageServerId,
         worktree_path: Arc<Path>,
         diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
         _: &mut ModelContext<Worktree>,
@@ -570,7 +580,7 @@ impl LocalWorktree {
                         worktree_id: self.id().to_proto(),
                         summary: Some(proto::DiagnosticSummary {
                             path: worktree_path.to_string_lossy().to_string(),
-                            language_server_id: server_id as u64,
+                            language_server_id: server_id.0 as u64,
                             error_count: new_summary.error_count as u32,
                             warning_count: new_summary.warning_count as u32,
                         }),
@@ -1135,7 +1145,7 @@ impl RemoteWorktree {
         path: Arc<Path>,
         summary: &proto::DiagnosticSummary,
     ) {
-        let server_id = summary.language_server_id as usize;
+        let server_id = LanguageServerId(summary.language_server_id as usize);
         let summary = DiagnosticSummary {
             error_count: summary.error_count as usize,
             warning_count: summary.warning_count as usize,