Allow builtin language servers to be disabled by default (#65049)

## Summary

This addresses the concern mentioned in
https://github.com/zed-industries/zed/pull/65030#pullrequestreview-5380076060

Release Notes:

- N/A
This commit is contained in:
Finn Evers 2026-10-02 07:47:07 +00:00 • committed by GitHub
parent 2a97fbf22b
commit 35bba8eef7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 40 additions and 21 deletions

View file

@ -34,7 +34,7 @@ pub fn register_fake_definition_server(
arguments: Vec::new(),
env: None,
},
enabled_by_default: true,
opt_in_languages: Default::default(),
capabilities: lsp::ServerCapabilities {
definition_provider: Some(lsp::OneOf::Left(true)),
type_definition_provider: Some(lsp::TypeDefinitionProviderCapability::Simple(true)),

View file

@ -334,7 +334,6 @@ pub struct CachedLspAdapter {
pub disk_based_diagnostic_sources: Vec<String>,
pub disk_based_diagnostics_progress_token: Option<String>,
language_ids: HashMap<LanguageName, String>,
pub enabled_by_default: bool,
pub adapter: Arc<dyn LspAdapter>,
cached_binary: Arc<ServerBinaryCache>,
}
@ -352,7 +351,6 @@ impl Debug for CachedLspAdapter {
&self.disk_based_diagnostics_progress_token,
)
.field("language_ids", &self.language_ids)
.field("enabled_by_default", &self.enabled_by_default)
.finish_non_exhaustive()
}
}
@ -363,14 +361,12 @@ impl CachedLspAdapter {
let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources();
let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token();
let language_ids = adapter.language_ids();
let enabled_by_default = adapter.enabled_by_default();
Arc::new(CachedLspAdapter {
name,
disk_based_diagnostic_sources,
disk_based_diagnostics_progress_token,
language_ids,
enabled_by_default,
adapter,
cached_binary: Default::default(),
})
@ -380,6 +376,12 @@ impl CachedLspAdapter {
self.adapter.name()
}
/// Returns whether the language server only starts for the given language
/// when it is explicitly listed in the `language_servers` setting.
pub fn is_opt_in_for(&self, language: &LanguageName) -> bool {
self.adapter.is_opt_in_for(language)
}
pub async fn get_language_server_command(
self: Arc<Self>,
delegate: Arc<dyn LspAdapterDelegate>,
@ -660,10 +662,11 @@ pub trait LspAdapter: 'static + Send + Sync + DynLspInstaller {
HashMap::default()
}
/// Whether the `...` wildcard in the `language_servers` setting includes this
/// language server. If `false`, it only starts when listed explicitly.
fn enabled_by_default(&self) -> bool {
true
/// Whether the `...` wildcard in the `language_servers` setting excludes this
/// language server for the given language, so that it only starts when
/// listed explicitly.
fn is_opt_in_for(&self, _language: &LanguageName) -> bool {
false
}
/// Support custom initialize params.
@ -933,7 +936,7 @@ pub struct FakeLspAdapter {
pub disk_based_diagnostics_progress_token: Option<String>,
pub disk_based_diagnostics_sources: Vec<String>,
pub language_server_binary: LanguageServerBinary,
pub enabled_by_default: bool,
pub opt_in_languages: HashSet<LanguageName>,
pub capabilities: lsp::ServerCapabilities,
pub initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp::FakeLanguageServer)>>,
@ -1561,7 +1564,7 @@ impl Default for FakeLspAdapter {
arguments: vec![],
env: Default::default(),
},
enabled_by_default: true,
opt_in_languages: HashSet::default(),
label_for_completion: None,
}
}
@ -1664,8 +1667,8 @@ impl LspAdapter for FakeLspAdapter {
label_for_completion(item, language)
}
fn enabled_by_default(&self) -> bool {
self.enabled_by_default
fn is_opt_in_for(&self, language: &LanguageName) -> bool {
self.opt_in_languages.contains(language)
}
fn is_extension(&self) -> bool {

View file

@ -305,12 +305,12 @@ impl LspAdapter for ExtensionLspAdapter {
.unwrap_or_default()
}
fn enabled_by_default(&self) -> bool {
fn is_opt_in_for(&self, language: &LanguageName) -> bool {
self.extension
.manifest()
.language_servers
.get(&self.language_server_id)
.is_none_or(|server| !server.is_opt_in_for(&self.language_name))
.is_some_and(|server| server.is_opt_in_for(language))
}
async fn initialization_options(

View file

@ -372,6 +372,10 @@ impl LspAdapter for TyLspAdapter {
Self::SERVER_NAME
}
fn is_opt_in_for(&self, language: &LanguageName) -> bool {
language == "Python"
}
async fn label_for_completion(
&self,
item: &lsp::CompletionItem,
@ -665,6 +669,10 @@ impl LspAdapter for PyrightLspAdapter {
Self::SERVER_NAME
}
fn is_opt_in_for(&self, language: &LanguageName) -> bool {
language == "Python"
}
async fn initialization_options(
self: Arc<Self>,
_: &Arc<dyn LspAdapterDelegate>,
@ -1816,6 +1824,10 @@ impl LspAdapter for PyLspAdapter {
Self::SERVER_NAME
}
fn is_opt_in_for(&self, language: &LanguageName) -> bool {
language == "Python"
}
async fn process_completions(&self, items: &mut [lsp::CompletionItem]) {
for item in items {
let is_named_argument = item.label.ends_with('=');

View file

@ -875,6 +875,10 @@ impl LspAdapter for TypeScriptLspAdapter {
(LanguageName::new_static("TSX"), "typescriptreact".into()),
])
}
fn is_opt_in_for(&self, language: &LanguageName) -> bool {
matches!(language.as_ref(), "TypeScript" | "JavaScript" | "TSX")
}
}
async fn get_cached_ts_server_binary(

View file

@ -253,7 +253,7 @@ impl LanguageServerTree {
let available_lsp_adapters = self.languages.lsp_adapters(language_name);
let available_language_servers = available_lsp_adapters
.iter()
.filter(|lsp_adapter| lsp_adapter.enabled_by_default)
.filter(|lsp_adapter| !lsp_adapter.is_opt_in_for(language_name))
.map(|lsp_adapter| lsp_adapter.name.clone())
.collect::<Vec<_>>();

View file

@ -2089,7 +2089,7 @@ async fn test_language_servers_disabled_by_default(cx: &mut gpui::TestAppContext
"Rust",
FakeLspAdapter {
name: "opt-in-server",
enabled_by_default: false,
opt_in_languages: HashSet::from_iter([LanguageName::new_static("Rust")]),
..Default::default()
},
);

View file

@ -128,7 +128,7 @@ In this example:
The `"..."` entry acts as a wildcard that includes any registered language server you haven't explicitly mentioned. Servers you list by name keep their position, and `"..."` fills in the remaining ones at that point in the list. Servers prefixed with `!` are excluded entirely. This means that if a new language server extension is installed or a new server is registered for a language, `"..."` will automatically include it. If you want full control over which servers are enabled, omit `"..."` — only the servers you list by name will be used.
Extensions can also mark a language server as [disabled by default](./extensions/languages.md#opt-in-language-servers) for some of its languages. Such servers are never included by `"..."`, regardless of your configuration, and only start when you list them by name, e.g. `["my-alternative-server", "..."]`.
Some language servers are disabled by default for some of their languages, e.g. `typescript-language-server` for TypeScript, or `ty` for Python. Extensions can also mark their language servers as [disabled by default](./extensions/languages.md#opt-in-language-servers). Such servers are never included by `"..."`, regardless of your configuration, and only start when you list them by name, e.g. `["my-alternative-server", "..."]`.
#### Examples

View file

@ -80,7 +80,7 @@ Other built-in language servers are:
- [Pyright](https://github.com/microsoft/pyright)&mdash;The basis for basedpyright.
- [PyLSP](https://github.com/python-lsp/python-lsp-server)&mdash;A plugin-based language server that integrates with tools like `pycodestyle`, `autopep8`, and `yapf`.
These are disabled by default, but can be enabled in your settings.
These are disabled by default and are not included by `"..."`. Enable them by listing them by name in your settings.
Configure language servers in Settings ({#kb zed::OpenSettings}) under Languages > Python, or add to your settings file:
@ -89,8 +89,8 @@ Configure language servers in Settings ({#kb zed::OpenSettings}) under Languages
"languages": {
"Python": {
"language_servers": [
// Enable ty, disable basedpyright, and enable all
// other registered language servers (ruff, pylsp, pyright).
// Enable ty, disable basedpyright, and enable all other
// language servers that are enabled by default (e.g. ruff).
"ty",
"!basedpyright",
"..."