Skip to content

Commit

Permalink
Add language_servers setting for customizing which language servers…
Browse files Browse the repository at this point in the history
… run (#10911)

This PR adds a new `language_servers` setting underneath the language
settings.


This setting controls which of the available language servers for a
given language will run.

The `language_servers` setting is an array of strings. Each item in the
array must be either:

- A language server ID (e.g., `"rust-analyzer"`,
`"typescript-language-server"`, `"eslint"`, etc.) denoting a language
server that should be enabled.
- A language server ID prefixed with a `!` (e.g., `"!rust-analyzer"`,
`"!typescript-language-server"`, `"!eslint"`, etc.) denoting a language
server that should be disabled.
- A `"..."` placeholder, which will be replaced by the remaining
available language servers that haven't already been mentioned in the
array.

For example, to enable the Biome language server in place of the default
TypeScript language server, you would add the following to your
settings:

```json
{
  "languages": {
    "TypeScript": {
      "language_servers": ["biome", "!typescript-language-server", "..."]
    }
  }
}
```

More details can be found in #10906.

Release Notes:

- Added `language_servers` setting to language settings for customizing
which language server(s) run for a given language.
  • Loading branch information
maxdeviant committed Apr 23, 2024
1 parent 68a1ad8 commit cf67fc9
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 10 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions assets/settings/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,10 @@
"show_call_status_icon": true,
// Whether to use language servers to provide code intelligence.
"enable_language_server": true,
// The list of language servers to use (or disable) for all languages.
//
// This is typically customized on a per-language basis.
"language_servers": ["..."],
// When to automatically save edited buffers. This setting can
// take four values.
//
Expand Down
5 changes: 3 additions & 2 deletions crates/language/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ fuzzy.workspace = true
git.workspace = true
globset.workspace = true
gpui.workspace = true
itertools.workspace = true
lazy_static.workspace = true
log.workspace = true
lsp.workspace = true
parking_lot.workspace = true
postage.workspace = true
pulldown-cmark.workspace = true
rand = { workspace = true, optional = true }
regex.workspace = true
rpc.workspace = true
Expand All @@ -50,15 +52,14 @@ similar = "1.3"
smallvec.workspace = true
smol.workspace = true
sum_tree.workspace = true
task.workspace = true
text.workspace = true
theme.workspace = true
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-typescript = { workspace = true, optional = true }
pulldown-cmark.workspace = true
tree-sitter.workspace = true
unicase = "2.6"
util.workspace = true
task.workspace = true

[dev-dependencies]
collections = { workspace = true, features = ["test-support"] }
Expand Down
147 changes: 140 additions & 7 deletions crates/language/src/language_settings.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
//! Provides `language`-related settings.

use crate::{File, Language};
use crate::{File, Language, LanguageServerName};
use anyhow::Result;
use collections::{HashMap, HashSet};
use globset::GlobMatcher;
use gpui::AppContext;
use itertools::{Either, Itertools};
use schemars::{
schema::{InstanceType, ObjectValidation, Schema, SchemaObject},
JsonSchema,
Expand Down Expand Up @@ -92,6 +93,13 @@ pub struct LanguageSettings {
pub prettier: HashMap<String, serde_json::Value>,
/// Whether to use language servers to provide code intelligence.
pub enable_language_server: bool,
/// The list of language servers to use (or disable) for this language.
///
/// This array should consist of language server IDs, as well as the following
/// special tokens:
/// - `"!<language_server_id>"` - A language server ID prefixed with a `!` will be disabled.
/// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
pub language_servers: Vec<Arc<str>>,
/// Controls whether Copilot provides suggestion immediately (true)
/// or waits for a `copilot::Toggle` (false).
pub show_copilot_suggestions: bool,
Expand All @@ -109,6 +117,53 @@ pub struct LanguageSettings {
pub code_actions_on_format: HashMap<String, bool>,
}

impl LanguageSettings {
/// A token representing the rest of the available language servers.
const REST_OF_LANGUAGE_SERVERS: &'static str = "...";

/// Returns the customized list of language servers from the list of
/// available language servers.
pub fn customized_language_servers(
&self,
available_language_servers: &[LanguageServerName],
) -> Vec<LanguageServerName> {
Self::resolve_language_servers(&self.language_servers, available_language_servers)
}

pub(crate) fn resolve_language_servers(
configured_language_servers: &[Arc<str>],
available_language_servers: &[LanguageServerName],
) -> Vec<LanguageServerName> {
let (disabled_language_servers, enabled_language_servers): (Vec<Arc<str>>, Vec<Arc<str>>) =
configured_language_servers.iter().partition_map(
|language_server| match language_server.strip_prefix('!') {
Some(disabled) => Either::Left(disabled.into()),
None => Either::Right(language_server.clone()),
},
);

let rest = available_language_servers
.into_iter()
.filter(|&available_language_server| {
!disabled_language_servers.contains(&&available_language_server.0)
&& !enabled_language_servers.contains(&&available_language_server.0)
})
.cloned()
.collect::<Vec<_>>();

enabled_language_servers
.into_iter()
.flat_map(|language_server| {
if language_server.as_ref() == Self::REST_OF_LANGUAGE_SERVERS {
rest.clone()
} else {
vec![LanguageServerName(language_server.clone())]
}
})
.collect::<Vec<_>>()
}
}

/// The settings for [GitHub Copilot](https://github.com/features/copilot).
#[derive(Clone, Debug, Default)]
pub struct CopilotSettings {
Expand All @@ -119,7 +174,7 @@ pub struct CopilotSettings {
}

/// The settings for all languages.
#[derive(Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct AllLanguageSettingsContent {
/// The settings for enabling/disabling features.
#[serde(default)]
Expand All @@ -140,7 +195,7 @@ pub struct AllLanguageSettingsContent {
}

/// The settings for a particular language.
#[derive(Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct LanguageSettingsContent {
/// How many columns a tab should occupy.
///
Expand Down Expand Up @@ -211,6 +266,16 @@ pub struct LanguageSettingsContent {
/// Default: true
#[serde(default)]
pub enable_language_server: Option<bool>,
/// The list of language servers to use (or disable) for this language.
///
/// This array should consist of language server IDs, as well as the following
/// special tokens:
/// - `"!<language_server_id>"` - A language server ID prefixed with a `!` will be disabled.
/// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
///
/// Default: ["..."]
#[serde(default)]
pub language_servers: Option<Vec<Arc<str>>>,
/// Controls whether Copilot provides suggestion immediately (true)
/// or waits for a `copilot::Toggle` (false).
///
Expand Down Expand Up @@ -257,7 +322,7 @@ pub struct CopilotSettingsContent {
}

/// The settings for enabling/disabling features.
#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct FeaturesContent {
/// Whether the GitHub Copilot feature is enabled.
Expand Down Expand Up @@ -608,6 +673,12 @@ impl settings::Settings for AllLanguageSettings {
}

fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
fn merge<T>(target: &mut T, value: Option<T>) {
if let Some(value) = value {
*target = value;
}
}

merge(&mut settings.tab_size, src.tab_size);
merge(&mut settings.hard_tabs, src.hard_tabs);
merge(&mut settings.soft_wrap, src.soft_wrap);
Expand Down Expand Up @@ -642,6 +713,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
&mut settings.enable_language_server,
src.enable_language_server,
);
merge(&mut settings.language_servers, src.language_servers.clone());
merge(
&mut settings.show_copilot_suggestions,
src.show_copilot_suggestions,
Expand All @@ -652,9 +724,70 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
src.extend_comment_on_newline,
);
merge(&mut settings.inlay_hints, src.inlay_hints);
fn merge<T>(target: &mut T, value: Option<T>) {
if let Some(value) = value {
*target = value;
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
pub fn test_resolve_language_servers() {
fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {
names
.into_iter()
.copied()
.map(|name| LanguageServerName(name.into()))
.collect::<Vec<_>>()
}

let available_language_servers = language_server_names(&[
"typescript-language-server",
"biome",
"deno",
"eslint",
"tailwind",
]);

// A value of just `["..."]` is the same as taking all of the available language servers.
assert_eq!(
LanguageSettings::resolve_language_servers(
&[LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()],
&available_language_servers,
),
available_language_servers
);

// Referencing one of the available language servers will change its order.
assert_eq!(
LanguageSettings::resolve_language_servers(
&[
"biome".into(),
LanguageSettings::REST_OF_LANGUAGE_SERVERS.into(),
"deno".into()
],
&available_language_servers
),
language_server_names(&[
"biome",
"typescript-language-server",
"eslint",
"tailwind",
"deno",
])
);

// Negating an available language server removes it from the list.
assert_eq!(
LanguageSettings::resolve_language_servers(
&[
"deno".into(),
"!typescript-language-server".into(),
"!biome".into(),
LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
],
&available_language_servers
),
language_server_names(&["deno", "eslint", "tailwind"])
);
}
}
15 changes: 14 additions & 1 deletion crates/project/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3060,7 +3060,20 @@ impl Project {
return;
}

for adapter in self.languages.clone().lsp_adapters(&language) {
let available_lsp_adapters = self.languages.clone().lsp_adapters(&language);
let available_language_servers = available_lsp_adapters
.iter()
.map(|lsp_adapter| lsp_adapter.name.clone())
.collect::<Vec<_>>();

let enabled_language_servers =
settings.customized_language_servers(&available_language_servers);

for adapter in available_lsp_adapters {
if !enabled_language_servers.contains(&adapter.name) {
continue;
}

self.start_language_server(worktree, adapter.clone(), language.clone(), cx);
}
}
Expand Down

0 comments on commit cf67fc9

Please sign in to comment.