Add wrapper type for language servers in settings (#61398)
This is in preparation for making configuration of language servers more comfortable for users/extensions. Release Notes: - N/A
This commit is contained in:
1 parent
cb68afbd2b
commit
7dc8071ac8
5 files changed
+145
-50
No files matched your search
@@ -14,7 +14,7 @@ use gpui::{
|
||||
use http_client::BlockedHttpClient;
|
||||
use language::{
|
||||
FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageRegistry,
|
||||
language_settings::{Formatter, FormatterList, LanguageSettings},
|
||||
language_settings::{ConfiguredLanguageServer, Formatter, FormatterList, LanguageSettings},
|
||||
rust_lang, tree_sitter_typescript,
|
||||
};
|
||||
use node_runtime::NodeRuntime;
|
||||
@@ -180,7 +180,7 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
cx_b.read(|cx| {
|
||||
assert_eq!(
|
||||
LanguageSettings::for_buffer(buffer_b.read(cx), cx).language_servers,
|
||||
["override-rust-analyzer".to_string()]
|
||||
[ConfiguredLanguageServer::new("override-rust-analyzer")]
|
||||
)
|
||||
});
|
||||
|
||||
@@ -1441,7 +1441,7 @@ async fn test_ssh_remote_worktree_trust(cx_a: &mut TestAppContext, server_cx: &m
|
||||
cx_a.read(|cx| {
|
||||
assert_eq!(
|
||||
LanguageSettings::for_buffer(buffer_before_approval.read(cx), cx).language_servers,
|
||||
["...".to_string()],
|
||||
[ConfiguredLanguageServer::new("...")],
|
||||
"remote .zed/settings.json must not sync before trust approval"
|
||||
)
|
||||
});
|
||||
@@ -1469,7 +1469,7 @@ async fn test_ssh_remote_worktree_trust(cx_a: &mut TestAppContext, server_cx: &m
|
||||
cx_a.read(|cx| {
|
||||
assert_eq!(
|
||||
LanguageSettings::for_buffer(buffer_before_approval.read(cx), cx).language_servers,
|
||||
["override-rust-analyzer".to_string()],
|
||||
[ConfiguredLanguageServer::new("override-rust-analyzer")],
|
||||
"remote .zed/settings.json should sync after trust approval"
|
||||
)
|
||||
});
|
||||
|
||||
@@ -39242,7 +39242,9 @@ async fn test_local_worktree_trust(cx: &mut TestAppContext) {
|
||||
cx
|
||||
)
|
||||
.language_servers,
|
||||
["...".to_string()],
|
||||
[language::language_settings::ConfiguredLanguageServer::new(
|
||||
"..."
|
||||
)],
|
||||
"local .zed/settings.json must not apply before trust approval"
|
||||
)
|
||||
});
|
||||
@@ -39275,7 +39277,9 @@ async fn test_local_worktree_trust(cx: &mut TestAppContext) {
|
||||
cx
|
||||
)
|
||||
.language_servers,
|
||||
["override-rust-analyzer".to_string()],
|
||||
[language::language_settings::ConfiguredLanguageServer::new(
|
||||
"override-rust-analyzer"
|
||||
)],
|
||||
"local .zed/settings.json should apply after trust approval"
|
||||
)
|
||||
});
|
||||
|
||||
@@ -12,15 +12,15 @@ use ec4rs::{
|
||||
};
|
||||
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
|
||||
use gpui::{App, Modifiers, SharedString};
|
||||
use itertools::{Either, Itertools};
|
||||
use itertools::Itertools;
|
||||
use settings::{DocumentFoldingRanges, DocumentSymbols, IntoGpui, SemanticTokens};
|
||||
|
||||
pub use settings::{
|
||||
AutoIndentMode, CompletionSettingsContent, EditPredictionDataCollectionChoice,
|
||||
EditPredictionPromptFormatContent, EditPredictionProvider, EditPredictionsMode, FormatOnSave,
|
||||
Formatter, FormatterList, InlayHintKind, LanguageSettingsContent, LineEndingSetting,
|
||||
LspInsertMode, REST_OF_LANGUAGE_SERVERS, RewrapBehavior, ShowWhitespaceSetting, SoftWrap,
|
||||
WordsCompletionMode,
|
||||
AutoIndentMode, CompletionSettingsContent, ConfiguredLanguageServer,
|
||||
EditPredictionDataCollectionChoice, EditPredictionPromptFormatContent, EditPredictionProvider,
|
||||
EditPredictionsMode, FormatOnSave, Formatter, FormatterList, InlayHintKind,
|
||||
LanguageSettingsContent, LineEndingSetting, LspInsertMode, REST_OF_LANGUAGE_SERVERS,
|
||||
RewrapBehavior, ShowWhitespaceSetting, SoftWrap, WordsCompletionMode,
|
||||
};
|
||||
use settings::{RegisterSetting, Settings, SettingsLocation, SettingsStore, merge_from::MergeFrom};
|
||||
use shellexpand;
|
||||
@@ -102,7 +102,7 @@ pub struct LanguageSettings {
|
||||
/// 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<String>,
|
||||
pub language_servers: Vec<ConfiguredLanguageServer>,
|
||||
/// Controls how semantic tokens from language servers are used for syntax highlighting.
|
||||
pub semantic_tokens: SemanticTokens,
|
||||
/// Controls whether folding ranges from language servers are used instead of
|
||||
@@ -355,18 +355,22 @@ impl LanguageSettings {
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_language_servers(
|
||||
configured_language_servers: &[String],
|
||||
configured_language_servers: &[ConfiguredLanguageServer],
|
||||
available_language_servers: &[LanguageServerName],
|
||||
) -> Vec<LanguageServerName> {
|
||||
let (disabled_language_servers, enabled_language_servers): (
|
||||
Vec<LanguageServerName>,
|
||||
Vec<LanguageServerName>,
|
||||
) = configured_language_servers.iter().partition_map(
|
||||
|language_server| match language_server.strip_prefix('!') {
|
||||
Some(disabled) => Either::Left(LanguageServerName(disabled.to_string().into())),
|
||||
None => Either::Right(LanguageServerName(language_server.clone().into())),
|
||||
},
|
||||
);
|
||||
) = configured_language_servers
|
||||
.iter()
|
||||
.partition_map(|language_server| {
|
||||
let name = LanguageServerName(language_server.name.clone().into());
|
||||
if language_server.disabled {
|
||||
itertools::Either::Left(name)
|
||||
} else {
|
||||
itertools::Either::Right(name)
|
||||
}
|
||||
});
|
||||
|
||||
let rest = available_language_servers
|
||||
.iter()
|
||||
|
||||
@@ -28,7 +28,7 @@ use gpui::{AppContext as _, Entity, SharedString, TestAppContext, UpdateGlobal,
|
||||
use http_client::{BlockedHttpClient, FakeHttpClient};
|
||||
use language::{
|
||||
Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding, Point,
|
||||
language_settings::{AllLanguageSettings, LanguageSettings},
|
||||
language_settings::{AllLanguageSettings, ConfiguredLanguageServer, LanguageSettings},
|
||||
};
|
||||
use lsp::{
|
||||
CompletionContext, CompletionResponse, CompletionTriggerKind, DEFAULT_LSP_REQUEST_TIMEOUT,
|
||||
@@ -557,7 +557,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
|
||||
AllLanguageSettings::get_global(cx)
|
||||
.language(None, Some(&"Rust".into()), cx)
|
||||
.language_servers,
|
||||
["from-local-settings"],
|
||||
[ConfiguredLanguageServer::new("from-local-settings")],
|
||||
"User language settings should be synchronized with the server settings"
|
||||
)
|
||||
});
|
||||
@@ -578,7 +578,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
|
||||
AllLanguageSettings::get_global(cx)
|
||||
.language(None, Some(&"Rust".into()), cx)
|
||||
.language_servers,
|
||||
["from-server-settings".to_string()],
|
||||
[ConfiguredLanguageServer::new("from-server-settings")],
|
||||
"Server language settings should take precedence over the user settings"
|
||||
)
|
||||
});
|
||||
@@ -639,14 +639,14 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
|
||||
)
|
||||
.language(None, Some(&"Rust".into()), cx)
|
||||
.language_servers,
|
||||
["override-rust-analyzer".to_string()]
|
||||
[ConfiguredLanguageServer::new("override-rust-analyzer")]
|
||||
)
|
||||
});
|
||||
|
||||
cx.read(|cx| {
|
||||
assert_eq!(
|
||||
LanguageSettings::for_buffer(buffer.read(cx), cx).language_servers,
|
||||
["override-rust-analyzer".to_string()]
|
||||
[ConfiguredLanguageServer::new("override-rust-analyzer")]
|
||||
)
|
||||
});
|
||||
}
|
||||
@@ -793,7 +793,10 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
|
||||
cx.read(|cx| {
|
||||
assert_eq!(
|
||||
LanguageSettings::for_buffer(buffer.read(cx), cx).language_servers,
|
||||
["rust-analyzer".to_string(), "fake-analyzer".to_string()]
|
||||
[
|
||||
ConfiguredLanguageServer::new("rust-analyzer"),
|
||||
ConfiguredLanguageServer::new("fake-analyzer"),
|
||||
]
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::num::NonZeroU32;
|
||||
use std::{borrow::Cow, num::NonZeroU32};
|
||||
|
||||
use collections::{HashMap, HashSet};
|
||||
use schemars::JsonSchema;
|
||||
@@ -58,7 +58,7 @@ impl merge_from::MergeFrom for AllLanguageSettingsContent {
|
||||
let globally_disabled_servers = other.defaults.language_servers.as_ref().map(|servers| {
|
||||
servers
|
||||
.iter()
|
||||
.filter(|entry| entry.starts_with('!'))
|
||||
.filter(|entry| entry.disabled)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
@@ -68,14 +68,13 @@ impl merge_from::MergeFrom for AllLanguageSettingsContent {
|
||||
if let Some(mut language_server_overrides) = language_server_overrides {
|
||||
if let Some(disabled) = &globally_disabled_servers {
|
||||
for disabled_server in disabled {
|
||||
if let Some(enabled_server) = disabled_server.strip_prefix('!') {
|
||||
language_server_overrides.retain(|entry| entry != enabled_server);
|
||||
}
|
||||
language_server_overrides
|
||||
.retain(|entry| entry.disabled || entry.name != disabled_server.name);
|
||||
}
|
||||
|
||||
let insert_before = language_server_overrides
|
||||
.iter()
|
||||
.position(|entry| entry == REST_OF_LANGUAGE_SERVERS)
|
||||
.position(|entry| entry.name.as_ref() == REST_OF_LANGUAGE_SERVERS)
|
||||
.unwrap_or(language_server_overrides.len());
|
||||
for disabled_server in disabled {
|
||||
if !language_server_overrides.contains(disabled_server) {
|
||||
@@ -420,6 +419,63 @@ pub enum SoftWrap {
|
||||
|
||||
pub const REST_OF_LANGUAGE_SERVERS: &str = "...";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[schemars(with = "String")]
|
||||
pub struct ConfiguredLanguageServer {
|
||||
pub name: Arc<str>,
|
||||
pub disabled: bool,
|
||||
}
|
||||
|
||||
impl ConfiguredLanguageServer {
|
||||
const DISABLED_CHAR: char = '!';
|
||||
|
||||
pub fn new(name: impl Into<Arc<str>>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_disabled(name: impl Into<Arc<str>>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
disabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ConfiguredLanguageServer {
|
||||
fn from(value: &str) -> Self {
|
||||
match value.strip_prefix(Self::DISABLED_CHAR) {
|
||||
Some(name) => Self::new_disabled(name),
|
||||
None => Self::new(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ConfiguredLanguageServer {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
if self.disabled {
|
||||
serializer.collect_str(&format_args!("{}{}", Self::DISABLED_CHAR, self.name))
|
||||
} else {
|
||||
serializer.serialize_str(&self.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ConfiguredLanguageServer {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value = Cow::<'de, str>::deserialize(deserializer)?;
|
||||
Ok(Self::from(value.as_ref()))
|
||||
}
|
||||
}
|
||||
|
||||
/// The settings for a particular language.
|
||||
#[with_fallible_options]
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
|
||||
@@ -511,7 +567,7 @@ pub struct LanguageSettingsContent {
|
||||
/// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
|
||||
///
|
||||
/// Default: ["..."]
|
||||
pub language_servers: Option<Vec<String>>,
|
||||
pub language_servers: Option<Vec<ConfiguredLanguageServer>>,
|
||||
/// Controls how semantic tokens from language servers are used for syntax highlighting.
|
||||
///
|
||||
/// Options:
|
||||
@@ -1282,6 +1338,31 @@ mod test {
|
||||
assert!(matches!(result, ParseStatus::Failed { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_configured_language_server_serialization() {
|
||||
let enabled = ConfiguredLanguageServer::new("rust-analyzer");
|
||||
let disabled = ConfiguredLanguageServer::new_disabled("rust-analyzer");
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_string(&enabled).expect("enabled server should serialize"),
|
||||
"\"rust-analyzer\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&disabled).expect("disabled server should serialize"),
|
||||
"\"!rust-analyzer\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ConfiguredLanguageServer>("\"rust-analyzer\"")
|
||||
.expect("enabled server should deserialize"),
|
||||
enabled
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ConfiguredLanguageServer>("\"!rust-analyzer\"")
|
||||
.expect("disabled server should deserialize"),
|
||||
disabled
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_language_servers_merge_preserves_per_language_config() {
|
||||
let mut base = AllLanguageSettingsContent {
|
||||
@@ -1328,11 +1409,11 @@ mod test {
|
||||
assert_eq!(
|
||||
ts_servers,
|
||||
&vec![
|
||||
"!typescript-language-server".to_string(),
|
||||
"vtsls".to_string(),
|
||||
"!eslint".to_string(),
|
||||
"!tailwindcss-language-server".to_string(),
|
||||
REST_OF_LANGUAGE_SERVERS.to_string(),
|
||||
ConfiguredLanguageServer::new_disabled("typescript-language-server"),
|
||||
ConfiguredLanguageServer::new("vtsls"),
|
||||
ConfiguredLanguageServer::new_disabled("eslint"),
|
||||
ConfiguredLanguageServer::new_disabled("tailwindcss-language-server"),
|
||||
ConfiguredLanguageServer::new(REST_OF_LANGUAGE_SERVERS),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -1340,9 +1421,9 @@ mod test {
|
||||
assert_eq!(
|
||||
default_servers,
|
||||
&vec![
|
||||
"!tailwindcss-language-server".to_string(),
|
||||
"!eslint".to_string(),
|
||||
REST_OF_LANGUAGE_SERVERS.to_string(),
|
||||
ConfiguredLanguageServer::new_disabled("tailwindcss-language-server"),
|
||||
ConfiguredLanguageServer::new_disabled("eslint"),
|
||||
ConfiguredLanguageServer::new(REST_OF_LANGUAGE_SERVERS),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -1383,9 +1464,9 @@ mod test {
|
||||
base.merge_from(&user);
|
||||
|
||||
let expected = vec![
|
||||
"!typescript-language-server".to_string(),
|
||||
"!vtsls".to_string(),
|
||||
REST_OF_LANGUAGE_SERVERS.to_string(),
|
||||
ConfiguredLanguageServer::new_disabled("typescript-language-server"),
|
||||
ConfiguredLanguageServer::new_disabled("vtsls"),
|
||||
ConfiguredLanguageServer::new(REST_OF_LANGUAGE_SERVERS),
|
||||
];
|
||||
assert_eq!(
|
||||
base.languages
|
||||
@@ -1430,7 +1511,10 @@ mod test {
|
||||
let rust_servers = base.languages.0["Rust"].language_servers.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
rust_servers,
|
||||
&vec!["!eslint".to_string(), REST_OF_LANGUAGE_SERVERS.to_string(),]
|
||||
&vec![
|
||||
ConfiguredLanguageServer::new_disabled("eslint"),
|
||||
ConfiguredLanguageServer::new(REST_OF_LANGUAGE_SERVERS),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1491,9 +1575,9 @@ mod test {
|
||||
assert_eq!(
|
||||
ts_servers,
|
||||
&vec![
|
||||
"deno".to_string(),
|
||||
"!vtsls".to_string(),
|
||||
REST_OF_LANGUAGE_SERVERS.to_string(),
|
||||
ConfiguredLanguageServer::new("deno"),
|
||||
ConfiguredLanguageServer::new_disabled("vtsls"),
|
||||
ConfiguredLanguageServer::new(REST_OF_LANGUAGE_SERVERS),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -1550,8 +1634,8 @@ mod test {
|
||||
assert_eq!(
|
||||
ts_servers,
|
||||
&vec![
|
||||
"typescript-language-server".to_string(),
|
||||
REST_OF_LANGUAGE_SERVERS.to_string(),
|
||||
ConfiguredLanguageServer::new("typescript-language-server"),
|
||||
ConfiguredLanguageServer::new(REST_OF_LANGUAGE_SERVERS),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user