Incomplete refactor to allow for multiple adapters per language

Julia created

Change summary

crates/editor/src/multi_buffer.rs      |   9 
crates/language/src/buffer.rs          |   1 
crates/language/src/language.rs        | 169 +++--
crates/language/src/proto.rs           |   2 
crates/project/src/lsp_command.rs      |  32 
crates/project/src/project.rs          | 794 ++++++++++++++-------------
crates/project/src/project_tests.rs    |   5 
crates/rpc/proto/zed.proto             |   7 
crates/zed/src/languages.rs            |  84 +-
crates/zed/src/languages/typescript.rs | 104 +++
10 files changed, 691 insertions(+), 516 deletions(-)

Detailed changes

crates/editor/src/multi_buffer.rs 🔗

@@ -2764,6 +2764,15 @@ impl MultiBufferSnapshot {
             .and_then(|(buffer, offset)| buffer.language_scope_at(offset))
     }
 
+    pub fn language_indent_size_at<T: ToOffset>(
+        &self,
+        position: T,
+        cx: &AppContext,
+    ) -> Option<IndentSize> {
+        let (buffer_snapshot, offset) = self.point_to_buffer_offset(position)?;
+        Some(buffer_snapshot.language_indent_size_at(offset, cx))
+    }
+
     pub fn is_dirty(&self) -> bool {
         self.is_dirty
     }

crates/language/src/buffer.rs 🔗

@@ -156,6 +156,7 @@ pub struct Completion {
 
 #[derive(Clone, Debug)]
 pub struct CodeAction {
+    pub server_id: usize,
     pub range: Range<Anchor>,
     pub lsp_action: lsp::CodeAction,
 }

crates/language/src/language.rs 🔗

@@ -414,7 +414,7 @@ pub struct BracketPair {
 pub struct Language {
     pub(crate) config: LanguageConfig,
     pub(crate) grammar: Option<Arc<Grammar>>,
-    pub(crate) adapter: Option<Arc<CachedLspAdapter>>,
+    pub(crate) adapters: Vec<Arc<CachedLspAdapter>>,
 
     #[cfg(any(test, feature = "test-support"))]
     fake_adapter: Option<(
@@ -492,7 +492,7 @@ struct AvailableLanguage {
     path: &'static str,
     config: LanguageConfig,
     grammar: tree_sitter::Language,
-    lsp_adapter: Option<Arc<dyn LspAdapter>>,
+    lsp_adapters: Vec<Arc<dyn LspAdapter>>,
     get_queries: fn(&str) -> LanguageQueries,
 }
 
@@ -513,6 +513,7 @@ pub struct LanguageRegistry {
 }
 
 struct LanguageRegistryState {
+    next_language_server_id: usize,
     languages: Vec<Arc<Language>>,
     available_languages: Vec<AvailableLanguage>,
     next_available_language_id: AvailableLanguageId,
@@ -522,11 +523,17 @@ struct LanguageRegistryState {
     version: usize,
 }
 
+pub struct PendingLanguageServer {
+    pub server_id: usize,
+    pub task: Task<Result<lsp::LanguageServer>>,
+}
+
 impl LanguageRegistry {
     pub fn new(login_shell_env_loaded: Task<()>) -> Self {
         let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16);
         Self {
             state: RwLock::new(LanguageRegistryState {
+                next_language_server_id: 0,
                 languages: vec![PLAIN_TEXT.clone()],
                 available_languages: Default::default(),
                 next_available_language_id: 0,
@@ -558,7 +565,7 @@ impl LanguageRegistry {
         path: &'static str,
         config: LanguageConfig,
         grammar: tree_sitter::Language,
-        lsp_adapter: Option<Arc<dyn LspAdapter>>,
+        lsp_adapters: Vec<Arc<dyn LspAdapter>>,
         get_queries: fn(&str) -> LanguageQueries,
     ) {
         let state = &mut *self.state.write();
@@ -567,7 +574,7 @@ impl LanguageRegistry {
             path,
             config,
             grammar,
-            lsp_adapter,
+            lsp_adapters,
             get_queries,
         });
     }
@@ -590,12 +597,13 @@ impl LanguageRegistry {
             state
                 .available_languages
                 .iter()
-                .filter_map(|l| l.lsp_adapter.clone())
+                .flat_map(|l| l.lsp_adapters.clone())
                 .chain(
                     state
                         .languages
                         .iter()
-                        .filter_map(|l| l.adapter.as_ref().map(|a| a.adapter.clone())),
+                        .flat_map(|language| &language.adapters)
+                        .map(|adapter| adapter.adapter.clone()),
                 )
                 .collect::<Vec<_>>()
         };
@@ -721,7 +729,7 @@ impl LanguageRegistry {
                                 let queries = (language.get_queries)(&language.path);
                                 let language =
                                     Language::new(language.config, Some(language.grammar))
-                                        .with_lsp_adapter(language.lsp_adapter)
+                                        .with_lsp_adapters(language.lsp_adapters)
                                         .await;
                                 let name = language.name();
                                 match language.with_queries(queries) {
@@ -774,18 +782,16 @@ impl LanguageRegistry {
         self.state.read().languages.iter().cloned().collect()
     }
 
-    pub fn start_language_server(
+    pub fn start_language_servers(
         self: &Arc<Self>,
-        server_id: usize,
         language: Arc<Language>,
         root_path: Arc<Path>,
         http_client: Arc<dyn HttpClient>,
         cx: &mut AppContext,
-    ) -> Option<Task<Result<lsp::LanguageServer>>> {
+    ) -> Vec<PendingLanguageServer> {
         #[cfg(any(test, feature = "test-support"))]
         if language.fake_adapter.is_some() {
-            let language = language;
-            return Some(cx.spawn(|cx| async move {
+            let task = cx.spawn(|cx| async move {
                 let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
                 let (server, mut fake_server) = lsp::LanguageServer::fake(
                     fake_adapter.name.to_string(),
@@ -810,53 +816,71 @@ impl LanguageRegistry {
                     })
                     .detach();
                 Ok(server)
-            }));
+            });
+            return vec![PendingLanguageServer { server_id: 0, task }];
         }
 
         let download_dir = self
             .language_server_download_dir
             .clone()
             .ok_or_else(|| anyhow!("language server download directory has not been assigned"))
-            .log_err()?;
-
-        let this = self.clone();
-        let adapter = language.adapter.clone()?;
-        let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
-        let login_shell_env_loaded = self.login_shell_env_loaded.clone();
-
-        Some(cx.spawn(|cx| async move {
-            login_shell_env_loaded.await;
-
-            let mut lock = this.lsp_binary_paths.lock();
-            let entry = lock
-                .entry(adapter.name.clone())
-                .or_insert_with(|| {
-                    get_binary(
-                        adapter.clone(),
-                        language.clone(),
-                        http_client,
-                        download_dir,
-                        lsp_binary_statuses,
-                    )
-                    .map_err(Arc::new)
-                    .boxed()
-                    .shared()
-                })
-                .clone();
-            drop(lock);
-            let binary = entry.clone().map_err(|e| anyhow!(e)).await?;
+            .log_err();
+        let download_dir = match download_dir {
+            Some(download_dir) => download_dir,
+            None => return Vec::new(),
+        };
+
+        let mut results = Vec::new();
+
+        for adapter in &language.adapters {
+            let this = self.clone();
+            let language = language.clone();
+            let http_client = http_client.clone();
+            let download_dir = download_dir.clone();
+            let root_path = root_path.clone();
+            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 task = cx.spawn(|cx| async move {
+                login_shell_env_loaded.await;
+
+                let mut lock = this.lsp_binary_paths.lock();
+                let entry = lock
+                    .entry(adapter.name.clone())
+                    .or_insert_with(|| {
+                        get_binary(
+                            adapter.clone(),
+                            language.clone(),
+                            http_client,
+                            download_dir,
+                            lsp_binary_statuses,
+                        )
+                        .map_err(Arc::new)
+                        .boxed()
+                        .shared()
+                    })
+                    .clone();
+                drop(lock);
+                let binary = entry.clone().map_err(|e| anyhow!(e)).await?;
+
+                let server = lsp::LanguageServer::new(
+                    server_id,
+                    &binary.path,
+                    &binary.arguments,
+                    &root_path,
+                    adapter.code_action_kinds(),
+                    cx,
+                )?;
+
+                Ok(server)
+            });
 
-            let server = lsp::LanguageServer::new(
-                server_id,
-                &binary.path,
-                &binary.arguments,
-                &root_path,
-                adapter.code_action_kinds(),
-                cx,
-            )?;
+            results.push(PendingLanguageServer { server_id, task });
+        }
 
-            Ok(server)
-        }))
+        results
     }
 
     pub fn language_server_binary_statuses(
@@ -974,15 +998,15 @@ impl Language {
                     highlight_map: Default::default(),
                 })
             }),
-            adapter: None,
+            adapters: Vec::new(),
 
             #[cfg(any(test, feature = "test-support"))]
             fake_adapter: None,
         }
     }
 
-    pub fn lsp_adapter(&self) -> Option<Arc<CachedLspAdapter>> {
-        self.adapter.clone()
+    pub fn lsp_adapters(&self) -> &[Arc<CachedLspAdapter>] {
+        &self.adapters
     }
 
     pub fn id(&self) -> Option<usize> {
@@ -1209,9 +1233,9 @@ impl Language {
         Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap()
     }
 
-    pub async fn with_lsp_adapter(mut self, lsp_adapter: Option<Arc<dyn LspAdapter>>) -> Self {
-        if let Some(adapter) = lsp_adapter {
-            self.adapter = Some(CachedLspAdapter::new(adapter).await);
+    pub async fn with_lsp_adapters(mut self, lsp_adapters: Vec<Arc<dyn LspAdapter>>) -> Self {
+        for adapter in lsp_adapters {
+            self.adapters.push(CachedLspAdapter::new(adapter).await);
         }
         self
     }
@@ -1224,7 +1248,7 @@ impl Language {
         let (servers_tx, servers_rx) = mpsc::unbounded();
         self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone()));
         let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await;
-        self.adapter = Some(adapter);
+        self.adapters = vec![adapter];
         servers_rx
     }
 
@@ -1233,28 +1257,31 @@ impl Language {
     }
 
     pub async fn disk_based_diagnostic_sources(&self) -> &[String] {
-        match self.adapter.as_ref() {
+        match self.adapters.first().as_ref() {
             Some(adapter) => &adapter.disk_based_diagnostic_sources,
             None => &[],
         }
     }
 
     pub async fn disk_based_diagnostics_progress_token(&self) -> Option<&str> {
-        if let Some(adapter) = self.adapter.as_ref() {
-            adapter.disk_based_diagnostics_progress_token.as_deref()
-        } else {
-            None
+        for adapter in &self.adapters {
+            let token = adapter.disk_based_diagnostics_progress_token.as_deref();
+            if token.is_some() {
+                return token;
+            }
         }
+
+        None
     }
 
     pub async fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) {
-        if let Some(processor) = self.adapter.as_ref() {
-            processor.process_diagnostics(diagnostics).await;
+        for adapter in &self.adapters {
+            adapter.process_diagnostics(diagnostics).await;
         }
     }
 
     pub async fn process_completion(self: &Arc<Self>, completion: &mut lsp::CompletionItem) {
-        if let Some(adapter) = self.adapter.as_ref() {
+        for adapter in &self.adapters {
             adapter.process_completion(completion).await;
         }
     }
@@ -1263,7 +1290,8 @@ impl Language {
         self: &Arc<Self>,
         completion: &lsp::CompletionItem,
     ) -> Option<CodeLabel> {
-        self.adapter
+        self.adapters
+            .first()
             .as_ref()?
             .label_for_completion(completion, self)
             .await
@@ -1274,7 +1302,8 @@ impl Language {
         name: &str,
         kind: lsp::SymbolKind,
     ) -> Option<CodeLabel> {
-        self.adapter
+        self.adapters
+            .first()
             .as_ref()?
             .label_for_symbol(name, kind, self)
             .await
@@ -1595,7 +1624,7 @@ mod tests {
                 ..Default::default()
             },
             tree_sitter_json::language(),
-            None,
+            vec![],
             |_| Default::default(),
         );
         languages.register(
@@ -1606,7 +1635,7 @@ mod tests {
                 ..Default::default()
             },
             tree_sitter_rust::language(),
-            None,
+            vec![],
             |_| Default::default(),
         );
         assert_eq!(

crates/language/src/proto.rs 🔗

@@ -462,6 +462,7 @@ pub async fn deserialize_completion(
 
 pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
     proto::CodeAction {
+        server_id: action.server_id 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(),
@@ -479,6 +480,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,
         range: start..end,
         lsp_action,
     })

crates/project/src/lsp_command.rs 🔗

@@ -33,21 +33,25 @@ pub(crate) trait LspCommand: 'static + Sized {
         language_server: &Arc<LanguageServer>,
         cx: &AppContext,
     ) -> <Self::LspRequest as lsp::request::Request>::Params;
+
     async fn response_from_lsp(
         self,
         message: <Self::LspRequest as lsp::request::Request>::Result,
         project: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
+        server_id: usize,
         cx: AsyncAppContext,
     ) -> Result<Self::Response>;
 
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest;
+
     async fn from_proto(
         message: Self::ProtoRequest,
         project: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Self>;
+
     fn response_to_proto(
         response: Self::Response,
         project: &mut Project,
@@ -55,6 +59,7 @@ pub(crate) trait LspCommand: 'static + Sized {
         buffer_version: &clock::Global,
         cx: &mut AppContext,
     ) -> <Self::ProtoRequest as proto::RequestMessage>::Response;
+
     async fn response_from_proto(
         self,
         message: <Self::ProtoRequest as proto::RequestMessage>::Response,
@@ -62,6 +67,7 @@ pub(crate) trait LspCommand: 'static + Sized {
         buffer: ModelHandle<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Self::Response>;
+
     fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64;
 }
 
@@ -137,6 +143,7 @@ impl LspCommand for PrepareRename {
         message: Option<lsp::PrepareRenameResponse>,
         _: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
+        _: usize,
         cx: AsyncAppContext,
     ) -> Result<Option<Range<Anchor>>> {
         buffer.read_with(&cx, |buffer, _| {
@@ -263,10 +270,12 @@ impl LspCommand for PerformRename {
         message: Option<lsp::WorkspaceEdit>,
         project: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
+        server_id: usize,
         mut cx: AsyncAppContext,
     ) -> Result<ProjectTransaction> {
         if let Some(edit) = message {
-            let (lsp_adapter, lsp_server) = language_server_for_buffer(&project, &buffer, &mut cx)?;
+            let (lsp_adapter, lsp_server) =
+                language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
             Project::deserialize_workspace_edit(
                 project,
                 edit,
@@ -380,9 +389,10 @@ impl LspCommand for GetDefinition {
         message: Option<lsp::GotoDefinitionResponse>,
         project: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
+        server_id: usize,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
-        location_links_from_lsp(message, project, buffer, cx).await
+        location_links_from_lsp(message, project, buffer, server_id, cx).await
     }
 
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition {
@@ -472,9 +482,10 @@ impl LspCommand for GetTypeDefinition {
         message: Option<lsp::GotoTypeDefinitionResponse>,
         project: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
+        server_id: usize,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
-        location_links_from_lsp(message, project, buffer, cx).await
+        location_links_from_lsp(message, project, buffer, server_id, cx).await
     }
 
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetTypeDefinition {
@@ -537,12 +548,13 @@ impl LspCommand for GetTypeDefinition {
 fn language_server_for_buffer(
     project: &ModelHandle<Project>,
     buffer: &ModelHandle<Buffer>,
+    server_id: usize,
     cx: &mut AsyncAppContext,
 ) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
     project
         .read_with(cx, |project, cx| {
             project
-                .language_server_for_buffer(buffer.read(cx), cx)
+                .language_server_for_buffer(buffer.read(cx), server_id, cx)
                 .map(|(adapter, server)| (adapter.clone(), server.clone()))
         })
         .ok_or_else(|| anyhow!("no language server found for buffer"))
@@ -614,6 +626,7 @@ async fn location_links_from_lsp(
     message: Option<lsp::GotoDefinitionResponse>,
     project: ModelHandle<Project>,
     buffer: ModelHandle<Buffer>,
+    server_id: usize,
     mut cx: AsyncAppContext,
 ) -> Result<Vec<LocationLink>> {
     let message = match message {
@@ -642,7 +655,8 @@ async fn location_links_from_lsp(
         }
     }
 
-    let (lsp_adapter, language_server) = language_server_for_buffer(&project, &buffer, &mut cx)?;
+    let (lsp_adapter, language_server) =
+        language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
     let mut definitions = Vec::new();
     for (origin_range, target_uri, target_range) in unresolved_links {
         let target_buffer_handle = project
@@ -756,11 +770,12 @@ impl LspCommand for GetReferences {
         locations: Option<Vec<lsp::Location>>,
         project: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
+        server_id: usize,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Location>> {
         let mut references = Vec::new();
         let (lsp_adapter, language_server) =
-            language_server_for_buffer(&project, &buffer, &mut cx)?;
+            language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
 
         if let Some(locations) = locations {
             for lsp_location in locations {
@@ -917,6 +932,7 @@ impl LspCommand for GetDocumentHighlights {
         lsp_highlights: Option<Vec<lsp::DocumentHighlight>>,
         _: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
+        _: usize,
         cx: AsyncAppContext,
     ) -> Result<Vec<DocumentHighlight>> {
         buffer.read_with(&cx, |buffer, _| {
@@ -1062,6 +1078,7 @@ impl LspCommand for GetHover {
         message: Option<lsp::Hover>,
         _: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
+        _: usize,
         cx: AsyncAppContext,
     ) -> Result<Self::Response> {
         Ok(message.and_then(|hover| {
@@ -1283,6 +1300,7 @@ impl LspCommand for GetCompletions {
         completions: Option<lsp::CompletionResponse>,
         _: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
+        _: usize,
         cx: AsyncAppContext,
     ) -> Result<Vec<Completion>> {
         let completions = if let Some(completions) = completions {
@@ -1502,6 +1520,7 @@ impl LspCommand for GetCodeActions {
         actions: Option<lsp::CodeActionResponse>,
         _: ModelHandle<Project>,
         _: ModelHandle<Buffer>,
+        server_id: usize,
         _: AsyncAppContext,
     ) -> Result<Vec<CodeAction>> {
         Ok(actions
@@ -1510,6 +1529,7 @@ impl LspCommand for GetCodeActions {
             .filter_map(|entry| {
                 if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry {
                     Some(CodeAction {
+                        server_id,
                         range: self.range.clone(),
                         lsp_action,
                     })

crates/project/src/project.rs 🔗

@@ -31,8 +31,8 @@ use language::{
     range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CodeAction, CodeLabel,
     Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent, File as _,
     Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt, Operation, Patch,
-    PointUtf16, RopeFingerprint, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
-    Unclipped,
+    PendingLanguageServer, PointUtf16, RopeFingerprint, TextBufferSnapshot, ToOffset, ToPointUtf16,
+    Transaction, Unclipped,
 };
 use lsp::{
     DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
@@ -99,7 +99,6 @@ pub struct Project {
     language_server_ids: HashMap<(WorktreeId, LanguageServerName), usize>,
     language_server_statuses: BTreeMap<usize, LanguageServerStatus>,
     last_workspace_edits_by_language_server: HashMap<usize, ProjectTransaction>,
-    next_language_server_id: usize,
     client: Arc<client::Client>,
     next_entry_id: Arc<AtomicUsize>,
     join_project_response_message_id: u32,
@@ -124,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, Vec<(i32, TextBufferSnapshot)>>,
+    buffer_snapshots: HashMap<u64, HashMap<usize, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
     buffers_being_formatted: HashSet<usize>,
     nonce: u128,
     _maintain_buffer_languages: Task<()>,
@@ -133,6 +132,11 @@ pub struct Project {
     copilot_enabled: bool,
 }
 
+struct LspBufferSnapshot {
+    version: i32,
+    snapshot: TextBufferSnapshot,
+}
+
 enum BufferMessage {
     Operation {
         buffer_id: u64,
@@ -469,7 +473,6 @@ impl Project {
                 language_server_statuses: Default::default(),
                 last_workspace_edits_by_language_server: Default::default(),
                 buffers_being_formatted: Default::default(),
-                next_language_server_id: 0,
                 nonce: StdRng::from_entropy().gen(),
                 terminals: Terminals {
                     local_handles: Vec::new(),
@@ -554,7 +557,6 @@ impl Project {
                     })
                     .collect(),
                 last_workspace_edits_by_language_server: Default::default(),
-                next_language_server_id: 0,
                 opened_buffers: Default::default(),
                 buffers_being_formatted: Default::default(),
                 buffer_snapshots: Default::default(),
@@ -645,7 +647,7 @@ impl Project {
 
         let mut language_servers_to_stop = Vec::new();
         for language in self.languages.to_vec() {
-            if let Some(lsp_adapter) = language.lsp_adapter() {
+            for lsp_adapter in language.lsp_adapters() {
                 if !settings.enable_language_server(Some(&language.name())) {
                     let lsp_name = &lsp_adapter.name;
                     for (worktree_id, started_lsp_name) in self.language_server_ids.keys() {
@@ -665,7 +667,7 @@ impl Project {
 
         // Start all the newly-enabled language servers.
         for (worktree_id, worktree_path, language) in language_servers_to_start {
-            self.start_language_server(worktree_id, worktree_path, language, cx);
+            self.start_language_servers(worktree_id, worktree_path, language, cx);
         }
 
         if !self.copilot_enabled && Copilot::global(cx).is_some() {
@@ -1550,7 +1552,7 @@ impl Project {
         cx.spawn(|this, mut cx| async move {
             if let Some(old_path) = old_path {
                 this.update(&mut cx, |this, cx| {
-                    this.unregister_buffer_from_language_server(&buffer, old_path, cx);
+                    this.unregister_buffer_from_language_servers(&buffer, old_path, cx);
                 });
             }
             let (worktree, path) = worktree_task.await?;
@@ -1564,7 +1566,7 @@ impl Project {
                 .await?;
             this.update(&mut cx, |this, cx| {
                 this.detect_language_for_buffer(&buffer, cx);
-                this.register_buffer_with_language_server(&buffer, cx);
+                this.register_buffer_with_language_servers(&buffer, cx);
             });
             Ok(())
         })
@@ -1628,14 +1630,15 @@ impl Project {
         .detach();
 
         self.detect_language_for_buffer(buffer, cx);
-        self.register_buffer_with_language_server(buffer, cx);
+        self.register_buffer_with_language_servers(buffer, cx);
         self.register_buffer_with_copilot(buffer, cx);
         cx.observe_release(buffer, |this, buffer, cx| {
             if let Some(file) = File::from_dyn(buffer.file()) {
                 if file.is_local() {
                     let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
-                    if let Some((_, server)) = this.language_server_for_buffer(buffer, cx) {
+                    for server in this.language_servers_for_buffer(buffer, cx) {
                         server
+                            .1
                             .notify::<lsp::notification::DidCloseTextDocument>(
                                 lsp::DidCloseTextDocumentParams {
                                     text_document: lsp::TextDocumentIdentifier::new(uri),
@@ -1652,46 +1655,50 @@ impl Project {
         Ok(())
     }
 
-    fn register_buffer_with_language_server(
+    fn register_buffer_with_language_servers(
         &mut self,
         buffer_handle: &ModelHandle<Buffer>,
         cx: &mut ModelContext<Self>,
     ) {
         let buffer = buffer_handle.read(cx);
         let buffer_id = buffer.remote_id();
+
         if let Some(file) = File::from_dyn(buffer.file()) {
-            if file.is_local() {
-                let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
-                let initial_snapshot = buffer.text_snapshot();
-
-                let mut language_server = None;
-                let mut language_id = None;
-                if let Some(language) = buffer.language() {
-                    let worktree_id = file.worktree_id(cx);
-                    if let Some(adapter) = language.lsp_adapter() {
-                        language_id = adapter.language_ids.get(language.name().as_ref()).cloned();
-                        language_server = self
-                            .language_server_ids
-                            .get(&(worktree_id, adapter.name.clone()))
-                            .and_then(|id| self.language_servers.get(id))
-                            .and_then(|server_state| {
-                                if let LanguageServerState::Running { server, .. } = server_state {
-                                    Some(server.clone())
-                                } else {
-                                    None
-                                }
-                            });
-                    }
-                }
+            if !file.is_local() {
+                return;
+            }
 
-                if let Some(local_worktree) = file.worktree.read(cx).as_local() {
-                    if let Some(diagnostics) = local_worktree.diagnostics_for_path(file.path()) {
-                        self.update_buffer_diagnostics(buffer_handle, diagnostics, None, cx)
-                            .log_err();
-                    }
+            let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
+            let initial_snapshot = buffer.text_snapshot();
+
+            if let Some(local_worktree) = file.worktree.read(cx).as_local() {
+                if let Some(diagnostics) = local_worktree.diagnostics_for_path(file.path()) {
+                    self.update_buffer_diagnostics(buffer_handle, diagnostics, None, cx)
+                        .log_err();
                 }
+            }
+
+            if let Some(language) = buffer.language() {
+                let worktree_id = file.worktree_id(cx);
+
+                for adapter in language.lsp_adapters() {
+                    let language_id = adapter.language_ids.get(language.name().as_ref()).cloned();
+                    let server = self
+                        .language_server_ids
+                        .get(&(worktree_id, adapter.name.clone()))
+                        .and_then(|id| self.language_servers.get(id))
+                        .and_then(|server_state| {
+                            if let LanguageServerState::Running { server, .. } = server_state {
+                                Some(server.clone())
+                            } else {
+                                None
+                            }
+                        });
+                    let server = match server {
+                        Some(server) => server,
+                        None => continue,
+                    };
 
-                if let Some(server) = language_server {
                     server
                         .notify::<lsp::notification::DidOpenTextDocument>(
                             lsp::DidOpenTextDocumentParams {
@@ -1704,6 +1711,7 @@ impl Project {
                             },
                         )
                         .log_err();
+
                     buffer_handle.update(cx, |buffer, cx| {
                         buffer.set_completion_triggers(
                             server
@@ -1713,16 +1721,23 @@ impl Project {
                                 .and_then(|provider| provider.trigger_characters.clone())
                                 .unwrap_or_default(),
                             cx,
-                        )
+                        );
                     });
+
+                    let snapshot = LspBufferSnapshot {
+                        version: 0,
+                        snapshot: initial_snapshot,
+                    };
                     self.buffer_snapshots
-                        .insert(buffer_id, vec![(0, initial_snapshot)]);
+                        .entry(buffer_id)
+                        .or_default()
+                        .insert(server.server_id(), vec![snapshot]);
                 }
             }
         }
     }
 
-    fn unregister_buffer_from_language_server(
+    fn unregister_buffer_from_language_servers(
         &mut self,
         buffer: &ModelHandle<Buffer>,
         old_path: PathBuf,
@@ -1731,7 +1746,7 @@ impl Project {
         buffer.update(cx, |buffer, cx| {
             buffer.update_diagnostics(Default::default(), cx);
             self.buffer_snapshots.remove(&buffer.remote_id());
-            if let Some((_, language_server)) = self.language_server_for_buffer(buffer, cx) {
+            for (_, language_server) in self.language_servers_for_buffer(buffer, cx) {
                 language_server
                     .notify::<lsp::notification::DidCloseTextDocument>(
                         lsp::DidCloseTextDocumentParams {
@@ -1833,52 +1848,62 @@ impl Project {
                     })
                     .ok();
             }
+
             BufferEvent::Edited { .. } => {
-                let language_server = self
-                    .language_server_for_buffer(buffer.read(cx), cx)
-                    .map(|(_, server)| server.clone())?;
                 let buffer = buffer.read(cx);
                 let file = File::from_dyn(buffer.file())?;
                 let abs_path = file.as_local()?.abs_path(cx);
                 let uri = lsp::Url::from_file_path(abs_path).unwrap();
-                let buffer_snapshots = self.buffer_snapshots.get_mut(&buffer.remote_id())?;
-                let (version, prev_snapshot) = buffer_snapshots.last()?;
                 let next_snapshot = buffer.text_snapshot();
-                let next_version = version + 1;
-
-                let content_changes = buffer
-                    .edits_since::<(PointUtf16, usize)>(prev_snapshot.version())
-                    .map(|edit| {
-                        let edit_start = edit.new.start.0;
-                        let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
-                        let new_text = next_snapshot
-                            .text_for_range(edit.new.start.1..edit.new.end.1)
-                            .collect();
-                        lsp::TextDocumentContentChangeEvent {
-                            range: Some(lsp::Range::new(
-                                point_to_lsp(edit_start),
-                                point_to_lsp(edit_end),
-                            )),
-                            range_length: None,
-                            text: new_text,
-                        }
-                    })
-                    .collect();
 
-                buffer_snapshots.push((next_version, next_snapshot));
+                for (_, language_server) in self.language_servers_for_buffer(buffer, cx) {
+                    let language_server = language_server.clone();
+
+                    let buffer_snapshots = self
+                        .buffer_snapshots
+                        .get_mut(&buffer.remote_id())
+                        .and_then(|m| m.get_mut(&language_server.server_id()))?;
+                    let previous_snapshot = buffer_snapshots.last()?;
+                    let next_version = previous_snapshot.version + 1;
+
+                    let content_changes = buffer
+                        .edits_since::<(PointUtf16, usize)>(previous_snapshot.snapshot.version())
+                        .map(|edit| {
+                            let edit_start = edit.new.start.0;
+                            let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
+                            let new_text = next_snapshot
+                                .text_for_range(edit.new.start.1..edit.new.end.1)
+                                .collect();
+                            lsp::TextDocumentContentChangeEvent {
+                                range: Some(lsp::Range::new(
+                                    point_to_lsp(edit_start),
+                                    point_to_lsp(edit_end),
+                                )),
+                                range_length: None,
+                                text: new_text,
+                            }
+                        })
+                        .collect();
 
-                language_server
-                    .notify::<lsp::notification::DidChangeTextDocument>(
-                        lsp::DidChangeTextDocumentParams {
-                            text_document: lsp::VersionedTextDocumentIdentifier::new(
-                                uri,
-                                next_version,
-                            ),
-                            content_changes,
-                        },
-                    )
-                    .log_err();
+                    buffer_snapshots.push(LspBufferSnapshot {
+                        version: next_version,
+                        snapshot: next_snapshot,
+                    });
+
+                    language_server
+                        .notify::<lsp::notification::DidChangeTextDocument>(
+                            lsp::DidChangeTextDocumentParams {
+                                text_document: lsp::VersionedTextDocumentIdentifier::new(
+                                    uri,
+                                    next_version,
+                                ),
+                                content_changes,
+                            },
+                        )
+                        .log_err();
+                }
             }
+
             BufferEvent::Saved => {
                 let file = File::from_dyn(buffer.read(cx).file())?;
                 let worktree_id = file.worktree_id(cx);
@@ -1898,13 +1923,17 @@ impl Project {
                         .log_err();
                 }
 
-                let language_server_id = self.language_server_id_for_buffer(buffer.read(cx), cx)?;
-                if let Some(LanguageServerState::Running {
-                    adapter,
-                    simulate_disk_based_diagnostics_completion,
-                    ..
-                }) = self.language_servers.get_mut(&language_server_id)
-                {
+                let language_server_ids = self.language_server_ids_for_buffer(buffer.read(cx), cx);
+                for language_server_id in language_server_ids {
+                    let LanguageServerState::Running {
+                        adapter,
+                        simulate_disk_based_diagnostics_completion,
+                        ..
+                    } = match self.language_servers.get_mut(&language_server_id) {
+                        Some(state) => state,
+                        None => continue,
+                    };
+
                     // After saving a buffer using a language server that doesn't provide
                     // a disk-based progress token, kick off a timer that will reset every
                     // time the buffer is saved. If the timer eventually fires, simulate
@@ -1933,6 +1962,7 @@ impl Project {
                     }
                 }
             }
+
             _ => {}
         }
 
@@ -1987,7 +2017,7 @@ impl Project {
 
                         for buffer in plain_text_buffers {
                             project.detect_language_for_buffer(&buffer, cx);
-                            project.register_buffer_with_language_server(&buffer, cx);
+                            project.register_buffer_with_language_servers(&buffer, cx);
                         }
 
                         for buffer in buffers_with_unknown_injections {
@@ -2071,12 +2101,12 @@ impl Project {
             if let Some(worktree) = file.worktree.read(cx).as_local() {
                 let worktree_id = worktree.id();
                 let worktree_abs_path = worktree.abs_path().clone();
-                self.start_language_server(worktree_id, worktree_abs_path, new_language, cx);
+                self.start_language_servers(worktree_id, worktree_abs_path, new_language, cx);
             }
         }
     }
 
-    fn start_language_server(
+    fn start_language_servers(
         &mut self,
         worktree_id: WorktreeId,
         worktree_path: Arc<Path>,
@@ -2090,313 +2120,333 @@ impl Project {
             return;
         }
 
-        let adapter = if let Some(adapter) = language.lsp_adapter() {
-            adapter
-        } else {
-            return;
-        };
-        let key = (worktree_id, adapter.name.clone());
+        let adapters = language.lsp_adapters();
+        let language_servers = self.languages.start_language_servers(
+            language.clone(),
+            worktree_path,
+            self.client.http_client(),
+            cx,
+        );
+        debug_assert_eq!(adapters.len(), language_servers.len());
 
-        let mut initialization_options = adapter.initialization_options.clone();
+        for (adapter, pending_server) in adapters.into_iter().zip(language_servers.into_iter()) {
+            let key = (worktree_id, adapter.name.clone());
+            let lsp = &cx.global::<Settings>().lsp.get(&adapter.name.0);
+            let override_options = lsp.map(|s| s.initialization_options.clone()).flatten();
 
-        let lsp = &cx.global::<Settings>().lsp.get(&adapter.name.0);
-        let override_options = lsp.map(|s| s.initialization_options.clone()).flatten();
-        match (&mut initialization_options, override_options) {
-            (Some(initialization_options), Some(override_options)) => {
-                merge_json_value_into(override_options, initialization_options);
+            let mut initialization_options = adapter.initialization_options.clone();
+            match (&mut initialization_options, override_options) {
+                (Some(initialization_options), Some(override_options)) => {
+                    merge_json_value_into(override_options, initialization_options);
+                }
+                (None, override_options) => initialization_options = override_options,
+                _ => {}
             }
-            (None, override_options) => initialization_options = override_options,
-            _ => {}
-        }
 
-        self.language_server_ids
-            .entry(key.clone())
-            .or_insert_with(|| {
-                let languages = self.languages.clone();
-                let server_id = post_inc(&mut self.next_language_server_id);
-                let language_server = self.languages.start_language_server(
-                    server_id,
-                    language.clone(),
-                    worktree_path,
-                    self.client.http_client(),
-                    cx,
-                );
-                self.language_servers.insert(
-                    server_id,
-                    LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move {
-                        let workspace_config =
-                            cx.update(|cx| languages.workspace_configuration(cx)).await;
-                        let language_server = language_server?.await.log_err()?;
-                        let language_server = language_server
-                            .initialize(initialization_options)
-                            .await
-                            .log_err()?;
-                        let this = this.upgrade(&cx)?;
-
-                        language_server
-                            .on_notification::<lsp::notification::PublishDiagnostics, _>({
-                                let this = this.downgrade();
-                                let adapter = adapter.clone();
-                                move |mut params, cx| {
-                                    let this = this;
-                                    let adapter = adapter.clone();
-                                    cx.spawn(|mut cx| async move {
-                                        adapter.process_diagnostics(&mut params).await;
-                                        if let Some(this) = this.upgrade(&cx) {
-                                            this.update(&mut cx, |this, cx| {
-                                                this.update_diagnostics(
-                                                    server_id,
-                                                    params,
-                                                    &adapter.disk_based_diagnostic_sources,
-                                                    cx,
-                                                )
-                                                .log_err();
-                                            });
-                                        }
-                                    })
-                                    .detach();
-                                }
-                            })
-                            .detach();
+            self.language_server_ids
+                .entry(key.clone())
+                .or_insert_with(|| {
+                    self.setup_language_adapter(
+                        worktree_path,
+                        initialization_options,
+                        pending_server,
+                        adapter,
+                        &language,
+                        key,
+                        cx,
+                    )
+                });
+        }
+    }
 
-                        language_server
-                            .on_request::<lsp::request::WorkspaceConfiguration, _, _>({
-                                let languages = languages.clone();
-                                move |params, mut cx| {
-                                    let languages = languages.clone();
-                                    async move {
-                                        let workspace_config = cx
-                                            .update(|cx| languages.workspace_configuration(cx))
-                                            .await;
-                                        Ok(params
-                                            .items
-                                            .into_iter()
-                                            .map(|item| {
-                                                if let Some(section) = &item.section {
-                                                    workspace_config
-                                                        .get(section)
-                                                        .cloned()
-                                                        .unwrap_or(serde_json::Value::Null)
-                                                } else {
-                                                    workspace_config.clone()
-                                                }
-                                            })
-                                            .collect())
-                                    }
-                                }
-                            })
-                            .detach();
+    fn setup_language_adapter(
+        &mut self,
+        worktree_path: Arc<Path>,
+        initialization_options: Option<serde_json::Value>,
+        pending_server: PendingLanguageServer,
+        adapter: &Arc<CachedLspAdapter>,
+        language: &Arc<Language>,
+        key: (WorktreeId, LanguageServerName),
+        cx: &mut ModelContext<Project>,
+    ) -> usize {
+        let server_id = pending_server.server_id;
+        let languages = self.languages.clone();
 
-                        // Even though we don't have handling for these requests, respond to them to
-                        // avoid stalling any language server like `gopls` which waits for a response
-                        // to these requests when initializing.
-                        language_server
-                            .on_request::<lsp::request::WorkDoneProgressCreate, _, _>({
-                                let this = this.downgrade();
-                                move |params, mut cx| async move {
-                                    if let Some(this) = this.upgrade(&cx) {
-                                        this.update(&mut cx, |this, _| {
-                                            if let Some(status) =
-                                                this.language_server_statuses.get_mut(&server_id)
-                                            {
-                                                if let lsp::NumberOrString::String(token) =
-                                                    params.token
-                                                {
-                                                    status.progress_tokens.insert(token);
-                                                }
-                                            }
-                                        });
-                                    }
-                                    Ok(())
-                                }
-                            })
-                            .detach();
-                        language_server
-                            .on_request::<lsp::request::RegisterCapability, _, _>({
-                                let this = this.downgrade();
-                                move |params, mut cx| async move {
-                                    let this = this
-                                        .upgrade(&cx)
-                                        .ok_or_else(|| anyhow!("project dropped"))?;
-                                    for reg in params.registrations {
-                                        if reg.method == "workspace/didChangeWatchedFiles" {
-                                            if let Some(options) = reg.register_options {
-                                                let options = serde_json::from_value(options)?;
-                                                this.update(&mut cx, |this, cx| {
-                                                    this.on_lsp_did_change_watched_files(
-                                                        server_id, options, cx,
-                                                    );
-                                                });
-                                            }
-                                        }
-                                    }
-                                    Ok(())
-                                }
-                            })
-                            .detach();
+        self.language_servers.insert(
+            server_id,
+            LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move {
+                let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await;
+                let language_server = pending_server.task.await.log_err()?;
+                let language_server = language_server
+                    .initialize(initialization_options)
+                    .await
+                    .log_err()?;
+                let this = this.upgrade(&cx)?;
 
-                        language_server
-                            .on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
-                                let this = this.downgrade();
-                                let adapter = adapter.clone();
-                                let language_server = language_server.clone();
-                                move |params, cx| {
-                                    Self::on_lsp_workspace_edit(
-                                        this,
-                                        params,
-                                        server_id,
-                                        adapter.clone(),
-                                        language_server.clone(),
-                                        cx,
-                                    )
+                language_server
+                    .on_notification::<lsp::notification::PublishDiagnostics, _>({
+                        let this = this.downgrade();
+                        let adapter = adapter.clone();
+                        move |mut params, cx| {
+                            let this = this;
+                            let adapter = adapter.clone();
+                            cx.spawn(|mut cx| async move {
+                                adapter.process_diagnostics(&mut params).await;
+                                if let Some(this) = this.upgrade(&cx) {
+                                    this.update(&mut cx, |this, cx| {
+                                        this.update_diagnostics(
+                                            server_id,
+                                            params,
+                                            &adapter.disk_based_diagnostic_sources,
+                                            cx,
+                                        )
+                                        .log_err();
+                                    });
                                 }
                             })
                             .detach();
+                        }
+                    })
+                    .detach();
 
-                        let disk_based_diagnostics_progress_token =
-                            adapter.disk_based_diagnostics_progress_token.clone();
+                language_server
+                    .on_request::<lsp::request::WorkspaceConfiguration, _, _>({
+                        let languages = languages.clone();
+                        move |params, mut cx| {
+                            let languages = languages.clone();
+                            async move {
+                                let workspace_config =
+                                    cx.update(|cx| languages.workspace_configuration(cx)).await;
+                                Ok(params
+                                    .items
+                                    .into_iter()
+                                    .map(|item| {
+                                        if let Some(section) = &item.section {
+                                            workspace_config
+                                                .get(section)
+                                                .cloned()
+                                                .unwrap_or(serde_json::Value::Null)
+                                        } else {
+                                            workspace_config.clone()
+                                        }
+                                    })
+                                    .collect())
+                            }
+                        }
+                    })
+                    .detach();
 
-                        language_server
-                            .on_notification::<lsp::notification::Progress, _>({
-                                let this = this.downgrade();
-                                move |params, mut cx| {
-                                    if let Some(this) = this.upgrade(&cx) {
+                // Even though we don't have handling for these requests, respond to them to
+                // avoid stalling any language server like `gopls` which waits for a response
+                // to these requests when initializing.
+                language_server
+                    .on_request::<lsp::request::WorkDoneProgressCreate, _, _>({
+                        let this = this.downgrade();
+                        move |params, mut cx| async move {
+                            if let Some(this) = this.upgrade(&cx) {
+                                this.update(&mut cx, |this, _| {
+                                    if let Some(status) =
+                                        this.language_server_statuses.get_mut(&server_id)
+                                    {
+                                        if let lsp::NumberOrString::String(token) = params.token {
+                                            status.progress_tokens.insert(token);
+                                        }
+                                    }
+                                });
+                            }
+                            Ok(())
+                        }
+                    })
+                    .detach();
+                language_server
+                    .on_request::<lsp::request::RegisterCapability, _, _>({
+                        let this = this.downgrade();
+                        move |params, mut cx| async move {
+                            let this = this
+                                .upgrade(&cx)
+                                .ok_or_else(|| anyhow!("project dropped"))?;
+                            for reg in params.registrations {
+                                if reg.method == "workspace/didChangeWatchedFiles" {
+                                    if let Some(options) = reg.register_options {
+                                        let options = serde_json::from_value(options)?;
                                         this.update(&mut cx, |this, cx| {
-                                            this.on_lsp_progress(
-                                                params,
-                                                server_id,
-                                                disk_based_diagnostics_progress_token.clone(),
-                                                cx,
+                                            this.on_lsp_did_change_watched_files(
+                                                server_id, options, cx,
                                             );
                                         });
                                     }
                                 }
-                            })
-                            .detach();
-
-                        language_server
-                            .notify::<lsp::notification::DidChangeConfiguration>(
-                                lsp::DidChangeConfigurationParams {
-                                    settings: workspace_config,
-                                },
-                            )
-                            .ok();
-
-                        this.update(&mut cx, |this, cx| {
-                            // If the language server for this key doesn't match the server id, don't store the
-                            // server. Which will cause it to be dropped, killing the process
-                            if this
-                                .language_server_ids
-                                .get(&key)
-                                .map(|id| id != &server_id)
-                                .unwrap_or(false)
-                            {
-                                return None;
                             }
+                            Ok(())
+                        }
+                    })
+                    .detach();
 
-                            // Update language_servers collection with Running variant of LanguageServerState
-                            // indicating that the server is up and running and ready
-                            this.language_servers.insert(
-                                server_id,
-                                LanguageServerState::Running {
-                                    adapter: adapter.clone(),
-                                    language,
-                                    watched_paths: Default::default(),
-                                    server: language_server.clone(),
-                                    simulate_disk_based_diagnostics_completion: None,
-                                },
-                            );
-                            this.language_server_statuses.insert(
+                language_server
+                    .on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
+                        let this = this.downgrade();
+                        let adapter = adapter.clone();
+                        let language_server = language_server.clone();
+                        move |params, cx| {
+                            Self::on_lsp_workspace_edit(
+                                this,
+                                params,
                                 server_id,
-                                LanguageServerStatus {
-                                    name: language_server.name().to_string(),
-                                    pending_work: Default::default(),
-                                    has_pending_diagnostic_updates: false,
-                                    progress_tokens: Default::default(),
-                                },
-                            );
+                                adapter.clone(),
+                                language_server.clone(),
+                                cx,
+                            )
+                        }
+                    })
+                    .detach();
 
-                            if let Some(project_id) = this.remote_id() {
-                                this.client
-                                    .send(proto::StartLanguageServer {
-                                        project_id,
-                                        server: Some(proto::LanguageServer {
-                                            id: server_id as u64,
-                                            name: language_server.name().to_string(),
-                                        }),
-                                    })
-                                    .log_err();
+                let disk_based_diagnostics_progress_token =
+                    adapter.disk_based_diagnostics_progress_token.clone();
+
+                language_server
+                    .on_notification::<lsp::notification::Progress, _>({
+                        let this = this.downgrade();
+                        move |params, mut cx| {
+                            if let Some(this) = this.upgrade(&cx) {
+                                this.update(&mut cx, |this, cx| {
+                                    this.on_lsp_progress(
+                                        params,
+                                        server_id,
+                                        disk_based_diagnostics_progress_token.clone(),
+                                        cx,
+                                    );
+                                });
                             }
+                        }
+                    })
+                    .detach();
 
-                            // Tell the language server about every open buffer in the worktree that matches the language.
-                            for buffer in this.opened_buffers.values() {
-                                if let Some(buffer_handle) = buffer.upgrade(cx) {
-                                    let buffer = buffer_handle.read(cx);
-                                    let file = if let Some(file) = File::from_dyn(buffer.file()) {
-                                        file
-                                    } else {
-                                        continue;
-                                    };
-                                    let language = if let Some(language) = buffer.language() {
-                                        language
-                                    } else {
-                                        continue;
-                                    };
-                                    if file.worktree.read(cx).id() != key.0
-                                        || language.lsp_adapter().map(|a| a.name.clone())
-                                            != Some(key.1.clone())
-                                    {
-                                        continue;
-                                    }
+                language_server
+                    .notify::<lsp::notification::DidChangeConfiguration>(
+                        lsp::DidChangeConfigurationParams {
+                            settings: workspace_config,
+                        },
+                    )
+                    .ok();
 
-                                    let file = file.as_local()?;
-                                    let versions = this
-                                        .buffer_snapshots
-                                        .entry(buffer.remote_id())
-                                        .or_insert_with(|| vec![(0, buffer.text_snapshot())]);
+                this.update(&mut cx, |this, cx| {
+                    // If the language server for this key doesn't match the server id, don't store the
+                    // server. Which will cause it to be dropped, killing the process
+                    if this
+                        .language_server_ids
+                        .get(&key)
+                        .map(|id| id != &server_id)
+                        .unwrap_or(false)
+                    {
+                        return None;
+                    }
 
-                                    let (version, initial_snapshot) = versions.last().unwrap();
-                                    let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
-                                    language_server
-                                        .notify::<lsp::notification::DidOpenTextDocument>(
-                                            lsp::DidOpenTextDocumentParams {
-                                                text_document: lsp::TextDocumentItem::new(
-                                                    uri,
-                                                    adapter
-                                                        .language_ids
-                                                        .get(language.name().as_ref())
-                                                        .cloned()
-                                                        .unwrap_or_default(),
-                                                    *version,
-                                                    initial_snapshot.text(),
-                                                ),
-                                            },
-                                        )
-                                        .log_err()?;
-                                    buffer_handle.update(cx, |buffer, cx| {
-                                        buffer.set_completion_triggers(
-                                            language_server
-                                                .capabilities()
-                                                .completion_provider
-                                                .as_ref()
-                                                .and_then(|provider| {
-                                                    provider.trigger_characters.clone()
-                                                })
-                                                .unwrap_or_default(),
-                                            cx,
-                                        )
-                                    });
-                                }
+                    // Update language_servers collection with Running variant of LanguageServerState
+                    // indicating that the server is up and running and ready
+                    this.language_servers.insert(
+                        server_id,
+                        LanguageServerState::Running {
+                            adapter: adapter.clone(),
+                            language: language.clone(),
+                            watched_paths: Default::default(),
+                            server: language_server.clone(),
+                            simulate_disk_based_diagnostics_completion: None,
+                        },
+                    );
+                    this.language_server_statuses.insert(
+                        server_id,
+                        LanguageServerStatus {
+                            name: language_server.name().to_string(),
+                            pending_work: Default::default(),
+                            has_pending_diagnostic_updates: false,
+                            progress_tokens: Default::default(),
+                        },
+                    );
+
+                    if let Some(project_id) = this.remote_id() {
+                        this.client
+                            .send(proto::StartLanguageServer {
+                                project_id,
+                                server: Some(proto::LanguageServer {
+                                    id: server_id as u64,
+                                    name: language_server.name().to_string(),
+                                }),
+                            })
+                            .log_err();
+                    }
+
+                    // Tell the language server about every open buffer in the worktree that matches the language.
+                    for buffer in this.opened_buffers.values() {
+                        if let Some(buffer_handle) = buffer.upgrade(cx) {
+                            let buffer = buffer_handle.read(cx);
+                            let file = match File::from_dyn(buffer.file()) {
+                                Some(file) => file,
+                                None => continue,
+                            };
+                            let language = match buffer.language() {
+                                Some(language) => language,
+                                None => continue,
+                            };
+
+                            if file.worktree.read(cx).id() != key.0
+                                || !language.lsp_adapters().iter().any(|a| a.name == key.1)
+                            {
+                                continue;
                             }
 
-                            cx.notify();
-                            Some(language_server)
-                        })
-                    })),
-                );
+                            let file = file.as_local()?;
+                            let versions = this
+                                .buffer_snapshots
+                                .entry(buffer.remote_id())
+                                .or_default()
+                                .entry(server_id)
+                                .or_insert_with(|| {
+                                    vec![LspBufferSnapshot {
+                                        version: 0,
+                                        snapshot: buffer.text_snapshot(),
+                                    }]
+                                });
 
-                server_id
-            });
+                            let snapshot = versions.last().unwrap();
+                            let version = snapshot.version;
+                            let initial_snapshot = snapshot.snapshot;
+                            let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap();
+                            language_server
+                                .notify::<lsp::notification::DidOpenTextDocument>(
+                                    lsp::DidOpenTextDocumentParams {
+                                        text_document: lsp::TextDocumentItem::new(
+                                            uri,
+                                            adapter
+                                                .language_ids
+                                                .get(language.name().as_ref())
+                                                .cloned()
+                                                .unwrap_or_default(),
+                                            version,
+                                            initial_snapshot.text(),
+                                        ),
+                                    },
+                                )
+                                .log_err()?;
+                            buffer_handle.update(cx, |buffer, cx| {
+                                buffer.set_completion_triggers(
+                                    language_server
+                                        .capabilities()
+                                        .completion_provider
+                                        .as_ref()
+                                        .and_then(|provider| provider.trigger_characters.clone())
+                                        .unwrap_or_default(),
+                                    cx,
+                                )
+                            });
+                        }
+                    }
+
+                    cx.notify();
+                    Some(language_server)
+                })
+            })),
+        );
+        server_id
     }
 
     // Returns a list of all of the worktrees which no longer have a language server and the root path

crates/project/src/project_tests.rs 🔗

@@ -1573,6 +1573,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
                         new_text: "".into(),
                     },
                 ],
+                0,
                 Some(lsp_document_version),
                 cx,
             )
@@ -1667,6 +1668,7 @@ async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestApp
                         new_text: "".into(),
                     },
                 ],
+                0,
                 None,
                 cx,
             )
@@ -1770,6 +1772,7 @@ async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
                         .unindent(),
                     },
                 ],
+                0,
                 None,
                 cx,
             )
@@ -2258,7 +2261,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
             ..Default::default()
         },
         tree_sitter_rust::language(),
-        None,
+        vec![],
         |_| Default::default(),
     );
 

crates/rpc/proto/zed.proto 🔗

@@ -684,9 +684,10 @@ message SearchProjectResponse {
 }
 
 message CodeAction {
-    Anchor start = 1;
-    Anchor end = 2;
-    bytes lsp_action = 3;
+    uint64 server_id = 1;
+    Anchor start = 2;
+    Anchor end = 3;
+    bytes lsp_action = 4;
 }
 
 message ProjectTransaction {

crates/zed/src/languages.rs 🔗

@@ -37,121 +37,107 @@ pub fn init(
     themes: Arc<ThemeRegistry>,
     node_runtime: Arc<NodeRuntime>,
 ) {
-    for (name, grammar, lsp_adapter) in [
+    fn adapter_arc(adapter: impl LspAdapter) -> Arc<dyn LspAdapter> {
+        Arc::new(adapter)
+    }
+
+    let languages_list = [
         (
             "c",
             tree_sitter_c::language(),
-            Some(Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>),
+            vec![adapter_arc(c::CLspAdapter)],
         ),
         (
             "cpp",
             tree_sitter_cpp::language(),
-            Some(Arc::new(c::CLspAdapter)),
-        ),
-        (
-            "css",
-            tree_sitter_css::language(),
-            None, //
+            vec![adapter_arc(c::CLspAdapter)],
         ),
+        ("css", tree_sitter_css::language(), vec![]),
         (
             "elixir",
             tree_sitter_elixir::language(),
-            Some(Arc::new(elixir::ElixirLspAdapter)),
+            vec![adapter_arc(elixir::ElixirLspAdapter)],
         ),
         (
             "go",
             tree_sitter_go::language(),
-            Some(Arc::new(go::GoLspAdapter)),
+            vec![adapter_arc(go::GoLspAdapter)],
         ),
         (
             "json",
             tree_sitter_json::language(),
-            Some(Arc::new(json::JsonLspAdapter::new(
+            vec![adapter_arc(json::JsonLspAdapter::new(
                 node_runtime.clone(),
                 languages.clone(),
                 themes.clone(),
-            ))),
-        ),
-        (
-            "markdown",
-            tree_sitter_markdown::language(),
-            None, //
+            ))],
         ),
+        ("markdown", tree_sitter_markdown::language(), vec![]),
         (
             "python",
             tree_sitter_python::language(),
-            Some(Arc::new(python::PythonLspAdapter::new(
+            vec![adapter_arc(python::PythonLspAdapter::new(
                 node_runtime.clone(),
-            ))),
+            ))],
         ),
         (
             "rust",
             tree_sitter_rust::language(),
-            Some(Arc::new(rust::RustLspAdapter)),
-        ),
-        (
-            "toml",
-            tree_sitter_toml::language(),
-            None, //
+            vec![adapter_arc(rust::RustLspAdapter)],
         ),
+        ("toml", tree_sitter_toml::language(), vec![]),
         (
             "tsx",
             tree_sitter_typescript::language_tsx(),
-            Some(Arc::new(typescript::TypeScriptLspAdapter::new(
+            vec![adapter_arc(typescript::TypeScriptLspAdapter::new(
                 node_runtime.clone(),
-            ))),
+            ))],
         ),
         (
             "typescript",
             tree_sitter_typescript::language_typescript(),
-            Some(Arc::new(typescript::TypeScriptLspAdapter::new(
+            vec![adapter_arc(typescript::TypeScriptLspAdapter::new(
                 node_runtime.clone(),
-            ))),
+            ))],
         ),
         (
             "javascript",
             tree_sitter_typescript::language_tsx(),
-            Some(Arc::new(typescript::TypeScriptLspAdapter::new(
+            vec![adapter_arc(typescript::TypeScriptLspAdapter::new(
                 node_runtime.clone(),
-            ))),
+            ))],
         ),
         (
             "html",
             tree_sitter_html::language(),
-            Some(Arc::new(html::HtmlLspAdapter::new(node_runtime.clone()))),
+            vec![adapter_arc(html::HtmlLspAdapter::new(node_runtime.clone()))],
         ),
         (
             "ruby",
             tree_sitter_ruby::language(),
-            Some(Arc::new(ruby::RubyLanguageServer)),
+            vec![adapter_arc(ruby::RubyLanguageServer)],
         ),
         (
             "erb",
             tree_sitter_embedded_template::language(),
-            Some(Arc::new(ruby::RubyLanguageServer)),
-        ),
-        (
-            "scheme",
-            tree_sitter_scheme::language(),
-            None, //
-        ),
-        (
-            "racket",
-            tree_sitter_racket::language(),
-            None, //
+            vec![adapter_arc(ruby::RubyLanguageServer)],
         ),
+        ("scheme", tree_sitter_scheme::language(), vec![]),
+        ("racket", tree_sitter_racket::language(), vec![]),
         (
             "lua",
             tree_sitter_lua::language(),
-            Some(Arc::new(lua::LuaLspAdapter)),
+            vec![adapter_arc(lua::LuaLspAdapter)],
         ),
         (
             "yaml",
             tree_sitter_yaml::language(),
-            Some(Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))),
+            vec![adapter_arc(yaml::YamlLspAdapter::new(node_runtime.clone()))],
         ),
-    ] {
-        languages.register(name, load_config(name), grammar, lsp_adapter, load_queries);
+    ];
+
+    for (name, grammar, lsp_adapters) in languages_list {
+        languages.register(name, load_config(name), grammar, lsp_adapters, load_queries);
     }
 }
 
@@ -163,7 +149,7 @@ pub async fn language(
 ) -> Arc<Language> {
     Arc::new(
         Language::new(load_config(name), Some(grammar))
-            .with_lsp_adapter(lsp_adapter)
+            .with_lsp_adapters(lsp_adapter)
             .await
             .with_queries(load_queries(name))
             .unwrap(),

crates/zed/src/languages/typescript.rs 🔗

@@ -37,7 +37,7 @@ impl TypeScriptLspAdapter {
     }
 }
 
-struct Versions {
+struct TypeScriptVersions {
     typescript_version: String,
     server_version: String,
 }
@@ -52,7 +52,8 @@ impl LspAdapter for TypeScriptLspAdapter {
         &self,
         _: Arc<dyn HttpClient>,
     ) -> Result<Box<dyn 'static + Send + Any>> {
-        Ok(Box::new(Versions {
+        dbg!();
+        Ok(Box::new(TypeScriptVersions {
             typescript_version: self.node.npm_package_latest_version("typescript").await?,
             server_version: self
                 .node
@@ -67,7 +68,8 @@ impl LspAdapter for TypeScriptLspAdapter {
         _: Arc<dyn HttpClient>,
         container_dir: PathBuf,
     ) -> Result<LanguageServerBinary> {
-        let versions = versions.downcast::<Versions>().unwrap();
+        dbg!();
+        let versions = versions.downcast::<TypeScriptVersions>().unwrap();
         let server_path = container_dir.join(Self::NEW_SERVER_PATH);
 
         if fs::metadata(&server_path).await.is_err() {
@@ -92,18 +94,10 @@ impl LspAdapter for TypeScriptLspAdapter {
     }
 
     async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
+        dbg!();
         (|| async move {
-            let mut last_version_dir = None;
-            let mut entries = fs::read_dir(&container_dir).await?;
-            while let Some(entry) = entries.next().await {
-                let entry = entry?;
-                if entry.file_type().await?.is_dir() {
-                    last_version_dir = Some(entry.path());
-                }
-            }
-            let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
-            let old_server_path = last_version_dir.join(Self::OLD_SERVER_PATH);
-            let new_server_path = last_version_dir.join(Self::NEW_SERVER_PATH);
+            let old_server_path = container_dir.join(Self::OLD_SERVER_PATH);
+            let new_server_path = container_dir.join(Self::NEW_SERVER_PATH);
             if new_server_path.exists() {
                 Ok(LanguageServerBinary {
                     path: self.node.binary_path().await?,
@@ -117,7 +111,7 @@ impl LspAdapter for TypeScriptLspAdapter {
             } else {
                 Err(anyhow!(
                     "missing executable in directory {:?}",
-                    last_version_dir
+                    container_dir
                 ))
             }
         })()
@@ -170,6 +164,86 @@ impl LspAdapter for TypeScriptLspAdapter {
     }
 }
 
+pub struct EsLintLspAdapter {
+    node: Arc<NodeRuntime>,
+}
+
+impl EsLintLspAdapter {
+    const SERVER_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.mjs";
+
+    pub fn new(node: Arc<NodeRuntime>) -> Self {
+        EsLintLspAdapter { node }
+    }
+}
+
+#[async_trait]
+impl LspAdapter for EsLintLspAdapter {
+    async fn name(&self) -> LanguageServerName {
+        LanguageServerName("eslint".into())
+    }
+
+    async fn fetch_latest_server_version(
+        &self,
+        _: Arc<dyn HttpClient>,
+    ) -> Result<Box<dyn 'static + Send + Any>> {
+        Ok(Box::new(
+            self.node.npm_package_latest_version("eslint").await?,
+        ))
+    }
+
+    async fn fetch_server_binary(
+        &self,
+        versions: Box<dyn 'static + Send + Any>,
+        _: Arc<dyn HttpClient>,
+        container_dir: PathBuf,
+    ) -> Result<LanguageServerBinary> {
+        let version = versions.downcast::<String>().unwrap();
+        let server_path = container_dir.join(Self::SERVER_PATH);
+
+        if fs::metadata(&server_path).await.is_err() {
+            self.node
+                .npm_install_packages([("eslint", version.as_str())], &container_dir)
+                .await?;
+        }
+
+        Ok(LanguageServerBinary {
+            path: self.node.binary_path().await?,
+            arguments: server_binary_arguments(&server_path),
+        })
+    }
+
+    async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
+        (|| async move {
+            let server_path = container_dir.join(Self::SERVER_PATH);
+            if server_path.exists() {
+                Ok(LanguageServerBinary {
+                    path: self.node.binary_path().await?,
+                    arguments: server_binary_arguments(&server_path),
+                })
+            } else {
+                Err(anyhow!(
+                    "missing executable in directory {:?}",
+                    container_dir
+                ))
+            }
+        })()
+        .await
+        .log_err()
+    }
+
+    async fn label_for_completion(
+        &self,
+        item: &lsp::CompletionItem,
+        language: &Arc<language::Language>,
+    ) -> Option<language::CodeLabel> {
+        None
+    }
+
+    async fn initialization_options(&self) -> Option<serde_json::Value> {
+        None
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use gpui::TestAppContext;