Skip to content

Commit

Permalink
Respect the language_servers setting's order when determining the p…
Browse files Browse the repository at this point in the history
…rimary language server (#15624)

This PR updates how we determine the "primary" language server for a
buffer to make it respect the order specified by the `language_servers`
setting.

Previously we were relying on the language servers to be registered in
the right order in order to select the primary one effectively.

However, in my testing I observed some cases where a native language
server (e.g., `tailwindcss-language-server`) could end up first in the
list of language servers despite not being first in the
`language_servers` setting.

While this wasn't a problem for the Tailwind or ESLint language servers
on account of them being defined natively with the designation of
"secondary" language servers, this could cause problems with
extension-based language servers.

To remedy this, every time we start up language servers we reorder the
list of language servers for a given language to reflect the order in
the `language_servers` setting. This ordering then allows us to treat
the first language server in the list as the "primary" one.

Related issues:

- #15023
- #15279

Release Notes:

- The ordering of language servers will now respect the order in the
`language_servers` setting.
- The first language server in this list will be used as the primary
language server.
  • Loading branch information
maxdeviant authored Aug 1, 2024
1 parent 0b175ac commit 3bd9a3f
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 5 deletions.
48 changes: 47 additions & 1 deletion crates/language/src/language_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
LanguageServerName, LspAdapter, LspAdapterDelegate, PLAIN_TEXT,
};
use anyhow::{anyhow, Context as _, Result};
use collections::{hash_map, HashMap};
use collections::{hash_map, HashMap, HashSet};
use futures::TryFutureExt;
use futures::{
channel::{mpsc, oneshot},
Expand Down Expand Up @@ -188,6 +188,22 @@ impl LanguageRegistry {
self.state.write().reload();
}

/// Reorders the list of language servers for the given language.
///
/// Uses the provided list of ordered [`CachedLspAdapters`] as the desired order.
///
/// Any existing language servers not present in `ordered_lsp_adapters` will be
/// appended to the end.
pub fn reorder_language_servers(
&self,
language: &Arc<Language>,
ordered_lsp_adapters: Vec<Arc<CachedLspAdapter>>,
) {
self.state
.write()
.reorder_language_servers(language, ordered_lsp_adapters);
}

/// Removes the specified languages and grammars from the registry.
pub fn remove_languages(
&self,
Expand Down Expand Up @@ -920,6 +936,36 @@ impl LanguageRegistryState {
*self.subscription.0.borrow_mut() = ();
}

/// Reorders the list of language servers for the given language.
///
/// Uses the provided list of ordered [`CachedLspAdapters`] as the desired order.
///
/// Any existing language servers not present in `ordered_lsp_adapters` will be
/// appended to the end.
fn reorder_language_servers(
&mut self,
language: &Arc<Language>,
ordered_lsp_adapters: Vec<Arc<CachedLspAdapter>>,
) {
let Some(lsp_adapters) = self.lsp_adapters.get_mut(&language.config.name) else {
return;
};

let ordered_lsp_adapter_ids = ordered_lsp_adapters
.iter()
.map(|lsp_adapter| lsp_adapter.name.clone())
.collect::<HashSet<_>>();

let mut new_lsp_adapters = ordered_lsp_adapters;
for adapter in lsp_adapters.iter() {
if !ordered_lsp_adapter_ids.contains(&adapter.name) {
new_lsp_adapters.push(adapter.clone());
}
}

*lsp_adapters = new_lsp_adapters;
}

fn remove_languages(
&mut self,
languages_to_remove: &[Arc<str>],
Expand Down
19 changes: 15 additions & 4 deletions crates/project/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2999,9 +2999,18 @@ impl Project {
.join(", ")
);

for adapter in enabled_lsp_adapters {
self.start_language_server(worktree, adapter, language.clone(), cx);
for adapter in &enabled_lsp_adapters {
self.start_language_server(worktree, adapter.clone(), language.clone(), cx);
}

// After starting all the language servers, reorder them to reflect the desired order
// based on the settings.
//
// This is done, in part, to ensure that language servers loaded at different points
// (e.g., native vs extension) still end up in the right order at the end, rather than
// it being based on which language server happened to be loaded in first.
self.languages()
.reorder_language_servers(&language, enabled_lsp_adapters);
}

fn start_language_server(
Expand Down Expand Up @@ -10247,8 +10256,10 @@ impl Project {
buffer: &Buffer,
cx: &AppContext,
) -> Option<(&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> {
self.language_servers_for_buffer(buffer, cx)
.find(|s| s.0.is_primary)
// The list of language servers is ordered based on the `language_servers` setting
// for each language, thus we can consider the first one in the list to be the
// primary one.
self.language_servers_for_buffer(buffer, cx).next()
}

pub fn language_server_for_buffer(
Expand Down

0 comments on commit 3bd9a3f

Please sign in to comment.