json_schema_store: Include available LSP adapters in settings schema (#46766)

Bae Seokjae and Ben Kunkle created

Closes #46556

  ## Summary

- Fix "Property `ty` is not allowed" warning in `settings.json` for LSP
adapters registered via `register_available_lsp_adapter()`
- Add `available_lsp_adapter_names()` method to include these adapters
in schema generation
- Support `initialization_options` schema lookup for available adapters

  ## Problem

LSP adapters registered via `register_available_lsp_adapter()` were not
included in the settings JSON schema. This caused validation warnings
like:

  Property ty is not allowed

Even though `ty` is a built-in Python language server that works
correctly.

  **Affected adapters:**
  - `ty`, `py`, `python-lsp-server`
  - `eslint`, `vtsls`, `typescript-language-server`
  - `tailwindcss-language-server`, `tailwindcss-intellisense-css`

  ## Solution

  Schema generation now queries both:
  1. `all_lsp_adapters()` - adapters bound to specific languages
2. `available_lsp_adapter_names()` - adapters enabled via settings (new)

  Related: #43104, #45928 
  
  Release Notes:

- Fixed an issue where not all LSP adapters would be suggested for
completion, or recognized as valid in `settings.json`

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>

Change summary

crates/json_schema_store/src/json_schema_store.rs | 29 ++++++++++++++--
crates/language/src/language_registry.rs          | 11 ++++++
2 files changed, 36 insertions(+), 4 deletions(-)

Detailed changes

crates/json_schema_store/src/json_schema_store.rs 🔗

@@ -3,7 +3,10 @@ use std::sync::{Arc, LazyLock};
 use anyhow::{Context as _, Result};
 use collections::HashMap;
 use gpui::{App, AsyncApp, BorrowAppContext as _, Entity, Task, WeakEntity};
-use language::{LanguageRegistry, LspAdapterDelegate, language_settings::AllLanguageSettings};
+use language::{
+    LanguageRegistry, LanguageServerName, LspAdapterDelegate,
+    language_settings::AllLanguageSettings,
+};
 use parking_lot::RwLock;
 use project::{LspStore, lsp_store::LocalLspAdapterDelegate};
 use settings::{LSP_SETTINGS_SCHEMA_URL_PREFIX, Settings as _, SettingsLocation};
@@ -244,6 +247,9 @@ async fn resolve_dynamic_schema(
                 .all_lsp_adapters()
                 .into_iter()
                 .find(|adapter| adapter.name().as_ref() as &str == lsp_name)
+                .or_else(|| {
+                    languages.load_available_lsp_adapter(&LanguageServerName::from(lsp_name))
+                })
                 .with_context(|| format!("LSP adapter not found: {}", lsp_name))?;
 
             let delegate: Arc<dyn LspAdapterDelegate> = cx
@@ -281,11 +287,26 @@ async fn resolve_dynamic_schema(
             })
         }
         "settings" => {
-            let lsp_adapter_names = languages
+            let mut lsp_adapter_names: Vec<String> = languages
                 .all_lsp_adapters()
                 .into_iter()
-                .map(|adapter| adapter.name().to_string())
-                .collect::<Vec<_>>();
+                .map(|adapter| adapter.name())
+                .chain(languages.available_lsp_adapter_names().into_iter())
+                .map(|name| name.to_string())
+                .collect();
+
+            let mut i = 0;
+            while i < lsp_adapter_names.len() {
+                let mut j = i + 1;
+                while j < lsp_adapter_names.len() {
+                    if lsp_adapter_names[i] == lsp_adapter_names[j] {
+                        lsp_adapter_names.swap_remove(j);
+                    } else {
+                        j += 1;
+                    }
+                }
+                i += 1;
+            }
 
             cx.update(|cx| {
                 let font_names = &cx.text_system().all_font_names();

crates/language/src/language_registry.rs 🔗

@@ -414,6 +414,17 @@ impl LanguageRegistry {
         state.available_lsp_adapters.contains_key(name)
     }
 
+    /// Returns the names of all available LSP adapters (registered via `register_available_lsp_adapter`).
+    /// These are adapters that are not bound to a specific language but can be enabled via settings.
+    pub fn available_lsp_adapter_names(&self) -> Vec<LanguageServerName> {
+        self.state
+            .read()
+            .available_lsp_adapters
+            .keys()
+            .cloned()
+            .collect()
+    }
+
     pub fn register_lsp_adapter(&self, language_name: LanguageName, adapter: Arc<dyn LspAdapter>) {
         let mut state = self.state.write();