Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

8 changes: 4 additions & 4 deletions crates/editor/src/editor_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18345,7 +18345,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppCon
);

update_test_project_settings(cx, |project_settings| {
project_settings.lsp.insert(
project_settings.lsp.0.insert(
"Some other server name".into(),
LspSettings {
binary: None,
Expand All @@ -18366,7 +18366,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppCon
);

update_test_project_settings(cx, |project_settings| {
project_settings.lsp.insert(
project_settings.lsp.0.insert(
language_server_name.into(),
LspSettings {
binary: None,
Expand All @@ -18387,7 +18387,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppCon
);

update_test_project_settings(cx, |project_settings| {
project_settings.lsp.insert(
project_settings.lsp.0.insert(
language_server_name.into(),
LspSettings {
binary: None,
Expand All @@ -18408,7 +18408,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppCon
);

update_test_project_settings(cx, |project_settings| {
project_settings.lsp.insert(
project_settings.lsp.0.insert(
language_server_name.into(),
LspSettings {
binary: None,
Expand Down
1 change: 1 addition & 0 deletions crates/json_schema_store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dap.workspace = true
extension.workspace = true
gpui.workspace = true
language.workspace = true
lsp.workspace = true
paths.workspace = true
project.workspace = true
schemars.workspace = true
Expand Down
150 changes: 113 additions & 37 deletions crates/json_schema_store/src/json_schema_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
use std::{str::FromStr, sync::Arc};

use anyhow::{Context as _, Result};
use gpui::{App, AsyncApp, BorrowAppContext as _, Entity, WeakEntity};
use gpui::{App, AsyncApp, BorrowAppContext as _, Entity, Task, WeakEntity};
use language::{LanguageRegistry, language_settings::all_language_settings};
use project::LspStore;
use lsp::LanguageServerBinaryOptions;
use project::{LspStore, lsp_store::LocalLspAdapterDelegate};
use settings::LSP_SETTINGS_SCHEMA_URL_PREFIX;
use util::schemars::{AllowTrailingCommas, DefaultDenyUnknownFields};

// Origin: https://github.com/SchemaStore/schemastore
Expand Down Expand Up @@ -75,61 +77,135 @@ fn handle_schema_request(
lsp_store: Entity<LspStore>,
uri: String,
cx: &mut AsyncApp,
) -> Result<String> {
let languages = lsp_store.read_with(cx, |lsp_store, _| lsp_store.languages.clone())?;
let schema = resolve_schema_request(&languages, uri, cx)?;
serde_json::to_string(&schema).context("Failed to serialize schema")
) -> Task<Result<String>> {
let languages = lsp_store.read_with(cx, |lsp_store, _| lsp_store.languages.clone());
cx.spawn(async move |cx| {
let languages = languages?;
let schema = resolve_schema_request(&languages, lsp_store, uri, cx).await?;
serde_json::to_string(&schema).context("Failed to serialize schema")
})
}

pub fn resolve_schema_request(
pub async fn resolve_schema_request(
languages: &Arc<LanguageRegistry>,
lsp_store: Entity<LspStore>,
uri: String,
cx: &mut AsyncApp,
) -> Result<serde_json::Value> {
let path = uri.strip_prefix("zed://schemas/").context("Invalid URI")?;
resolve_schema_request_inner(languages, path, cx)
resolve_schema_request_inner(languages, lsp_store, path, cx).await
}

pub fn resolve_schema_request_inner(
pub async fn resolve_schema_request_inner(
languages: &Arc<LanguageRegistry>,
lsp_store: Entity<LspStore>,
path: &str,
cx: &mut AsyncApp,
) -> Result<serde_json::Value> {
let (schema_name, rest) = path.split_once('/').unzip();
let schema_name = schema_name.unwrap_or(path);

let schema = match schema_name {
"settings" => cx.update(|cx| {
let font_names = &cx.text_system().all_font_names();
let language_names = &languages
.language_names()
"settings" if rest.is_some_and(|r| r.starts_with("lsp/")) => {
let lsp_name = rest
.and_then(|r| {
r.strip_prefix(
LSP_SETTINGS_SCHEMA_URL_PREFIX
.strip_prefix("zed://schemas/settings/")
.unwrap(),
)
})
.context("Invalid LSP schema path")?;

let adapter = languages
.all_lsp_adapters()
.into_iter()
.map(|name| name.to_string())
.find(|adapter| adapter.name().as_ref() as &str == lsp_name)
.with_context(|| format!("LSP adapter not found: {}", lsp_name))?;

let delegate = cx.update(|inner_cx| {
lsp_store.update(inner_cx, |lsp_store, inner_cx| {
let Some(local) = lsp_store.as_local() else {
return None;
};
let Some(worktree) = local.worktree_store.read(inner_cx).worktrees().next() else {
return None;
};
Some(LocalLspAdapterDelegate::from_local_lsp(
local, &worktree, inner_cx,
))
})
})?.context("Failed to create adapter delegate - either LSP store is not in local mode or no worktree is available")?;

let adapter_for_schema = adapter.clone();

let binary = adapter
.get_language_server_command(
delegate,
None,
LanguageServerBinaryOptions {
allow_path_lookup: true,
allow_binary_download: false,
pre_release: false,
},
cx,
)
.await
.await
.0.with_context(|| format!("Failed to find language server {lsp_name} to generate initialization params schema"))?;

adapter_for_schema
.adapter
.clone()
.initialization_options_schema(&binary)
.await
.unwrap_or_else(|| {
serde_json::json!({
"type": "object",
"additionalProperties": true
})
})
}
"settings" => {
let lsp_adapter_names = languages
.all_lsp_adapters()
.into_iter()
.map(|adapter| adapter.name().to_string())
.collect::<Vec<_>>();

let mut icon_theme_names = vec![];
let mut theme_names = vec![];
if let Some(registry) = theme::ThemeRegistry::try_global(cx) {
icon_theme_names.extend(
registry
.list_icon_themes()
.into_iter()
.map(|icon_theme| icon_theme.name),
);
theme_names.extend(registry.list_names());
}
let icon_theme_names = icon_theme_names.as_slice();
let theme_names = theme_names.as_slice();

cx.global::<settings::SettingsStore>().json_schema(
&settings::SettingsJsonSchemaParams {
language_names,
font_names,
theme_names,
icon_theme_names,
},
)
})?,
cx.update(|cx| {
let font_names = &cx.text_system().all_font_names();
let language_names = &languages
.language_names()
.into_iter()
.map(|name| name.to_string())
.collect::<Vec<_>>();

let mut icon_theme_names = vec![];
let mut theme_names = vec![];
if let Some(registry) = theme::ThemeRegistry::try_global(cx) {
icon_theme_names.extend(
registry
.list_icon_themes()
.into_iter()
.map(|icon_theme| icon_theme.name),
);
theme_names.extend(registry.list_names());
}
let icon_theme_names = icon_theme_names.as_slice();
let theme_names = theme_names.as_slice();

cx.global::<settings::SettingsStore>().json_schema(
&settings::SettingsJsonSchemaParams {
language_names,
font_names,
theme_names,
icon_theme_names,
lsp_adapter_names: &lsp_adapter_names,
},
)
})?
}
"keymap" => cx.update(settings::KeymapFile::generate_json_schema_for_registered_actions)?,
"action" => {
let normalized_action_name = rest.context("No Action name provided")?;
Expand Down
8 changes: 8 additions & 0 deletions crates/language/src/language.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,14 @@ pub trait LspAdapter: 'static + Send + Sync + DynLspInstaller {
Ok(None)
}

/// Returns the JSON schema of the initialization_options for the language server.
async fn initialization_options_schema(
self: Arc<Self>,
_language_server_binary: &LanguageServerBinary,
) -> Option<serde_json::Value> {
None
}

async fn workspace_configuration(
self: Arc<Self>,
_: &Arc<dyn LspAdapterDelegate>,
Expand Down
Loading