Specify a schema to use when dealing with JSONC files (#44250)
Follow-up of https://github.com/zed-industries/zed/pull/43854 Closes https://github.com/zed-industries/zed/issues/40970 Seems that json language server does not distinguish between JSONC and JSON files in runtime, but there is a static schema, which accepts globs in its `fileMatch` fields. Use all glob overrides and file suffixes for JSONC inside those match fields, and provide a grammar for such matches, which accepts trailing commas. Release Notes: - Improved JSONC trailing comma handling
This commit is contained in:
@@ -3,8 +3,9 @@ use std::{str::FromStr, sync::Arc};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use gpui::{App, AsyncApp, BorrowAppContext as _, Entity, WeakEntity};
|
||||
use language::LanguageRegistry;
|
||||
use language::{LanguageRegistry, language_settings::all_language_settings};
|
||||
use project::LspStore;
|
||||
use util::schemars::{AllowTrailingCommas, DefaultDenyUnknownFields};
|
||||
|
||||
// Origin: https://github.com/SchemaStore/schemastore
|
||||
const TSCONFIG_SCHEMA: &str = include_str!("schemas/tsconfig.json");
|
||||
@@ -159,14 +160,35 @@ pub fn resolve_schema_request_inner(
|
||||
}
|
||||
}
|
||||
"snippets" => snippet_provider::format::VsSnippetsFile::generate_json_schema(),
|
||||
"jsonc" => jsonc_schema(),
|
||||
_ => {
|
||||
anyhow::bail!("Unrecognized builtin JSON schema: {}", schema_name);
|
||||
anyhow::bail!("Unrecognized builtin JSON schema: {schema_name}");
|
||||
}
|
||||
};
|
||||
Ok(schema)
|
||||
}
|
||||
|
||||
pub fn all_schema_file_associations(cx: &mut App) -> serde_json::Value {
|
||||
const JSONC_LANGUAGE_NAME: &str = "JSONC";
|
||||
|
||||
pub fn all_schema_file_associations(
|
||||
languages: &Arc<LanguageRegistry>,
|
||||
cx: &mut App,
|
||||
) -> serde_json::Value {
|
||||
let extension_globs = languages
|
||||
.available_language_for_name(JSONC_LANGUAGE_NAME)
|
||||
.map(|language| language.matcher().path_suffixes.clone())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
// Path suffixes can be entire file names or just their extensions.
|
||||
.flat_map(|path_suffix| [format!("*.{path_suffix}"), path_suffix]);
|
||||
let override_globs = all_language_settings(None, cx)
|
||||
.file_types
|
||||
.get(JSONC_LANGUAGE_NAME)
|
||||
.into_iter()
|
||||
.flat_map(|(_, glob_strings)| glob_strings)
|
||||
.cloned();
|
||||
let jsonc_globs = extension_globs.chain(override_globs).collect::<Vec<_>>();
|
||||
|
||||
let mut file_associations = serde_json::json!([
|
||||
{
|
||||
"fileMatch": [
|
||||
@@ -211,6 +233,10 @@ pub fn all_schema_file_associations(cx: &mut App) -> serde_json::Value {
|
||||
"fileMatch": ["package.json"],
|
||||
"url": "zed://schemas/package_json"
|
||||
},
|
||||
{
|
||||
"fileMatch": &jsonc_globs,
|
||||
"url": "zed://schemas/jsonc"
|
||||
},
|
||||
]);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -233,7 +259,7 @@ pub fn all_schema_file_associations(cx: &mut App) -> serde_json::Value {
|
||||
let file_name = normalized_action_name_to_file_name(normalized_name.clone());
|
||||
serde_json::json!({
|
||||
"fileMatch": [file_name],
|
||||
"url": format!("zed://schemas/action/{}", normalized_name)
|
||||
"url": format!("zed://schemas/action/{normalized_name}")
|
||||
})
|
||||
}),
|
||||
);
|
||||
@@ -249,6 +275,26 @@ fn package_json_schema() -> serde_json::Value {
|
||||
serde_json::Value::from_str(PACKAGE_JSON_SCHEMA).unwrap()
|
||||
}
|
||||
|
||||
fn jsonc_schema() -> serde_json::Value {
|
||||
let generator = schemars::generate::SchemaSettings::draft2019_09()
|
||||
.with_transform(DefaultDenyUnknownFields)
|
||||
.with_transform(AllowTrailingCommas)
|
||||
.into_generator();
|
||||
let meta_schema = generator
|
||||
.settings()
|
||||
.meta_schema
|
||||
.as_ref()
|
||||
.expect("meta_schema should be present in schemars settings")
|
||||
.to_string();
|
||||
let defs = generator.definitions();
|
||||
let schema = schemars::json_schema!({
|
||||
"$schema": meta_schema,
|
||||
"allowTrailingCommas": true,
|
||||
"$defs": defs,
|
||||
});
|
||||
serde_json::to_value(schema).unwrap()
|
||||
}
|
||||
|
||||
fn generate_inspector_style_schema() -> serde_json::Value {
|
||||
let schema = schemars::generate::SchemaSettings::draft2019_09()
|
||||
.with_transform(util::schemars::DefaultDenyUnknownFields)
|
||||
|
||||
@@ -745,7 +745,7 @@ impl LanguageRegistry {
|
||||
self: &Arc<Self>,
|
||||
path: &Path,
|
||||
content: Option<&Rope>,
|
||||
user_file_types: Option<&FxHashMap<Arc<str>, GlobSet>>,
|
||||
user_file_types: Option<&FxHashMap<Arc<str>, (GlobSet, Vec<String>)>>,
|
||||
) -> Option<AvailableLanguage> {
|
||||
let filename = path.file_name().and_then(|filename| filename.to_str());
|
||||
// `Path.extension()` returns None for files with a leading '.'
|
||||
@@ -788,7 +788,7 @@ impl LanguageRegistry {
|
||||
let path_matches_custom_suffix = || {
|
||||
user_file_types
|
||||
.and_then(|types| types.get(language_name.as_ref()))
|
||||
.map_or(None, |custom_suffixes| {
|
||||
.map_or(None, |(custom_suffixes, _)| {
|
||||
path_suffixes
|
||||
.iter()
|
||||
.find(|(_, candidate)| custom_suffixes.is_match_candidate(candidate))
|
||||
|
||||
@@ -51,7 +51,7 @@ pub struct AllLanguageSettings {
|
||||
pub edit_predictions: EditPredictionSettings,
|
||||
pub defaults: LanguageSettings,
|
||||
languages: HashMap<LanguageName, LanguageSettings>,
|
||||
pub(crate) file_types: FxHashMap<Arc<str>, GlobSet>,
|
||||
pub file_types: FxHashMap<Arc<str>, (GlobSet, Vec<String>)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -656,7 +656,7 @@ impl settings::Settings for AllLanguageSettings {
|
||||
|
||||
let enabled_in_text_threads = edit_predictions.enabled_in_text_threads.unwrap();
|
||||
|
||||
let mut file_types: FxHashMap<Arc<str>, GlobSet> = FxHashMap::default();
|
||||
let mut file_types: FxHashMap<Arc<str>, (GlobSet, Vec<String>)> = FxHashMap::default();
|
||||
|
||||
for (language, patterns) in all_languages.file_types.iter().flatten() {
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
@@ -665,7 +665,10 @@ impl settings::Settings for AllLanguageSettings {
|
||||
builder.add(Glob::new(pattern).unwrap());
|
||||
}
|
||||
|
||||
file_types.insert(language.clone(), builder.build().unwrap());
|
||||
file_types.insert(
|
||||
language.clone(),
|
||||
(builder.build().unwrap(), patterns.0.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
Self {
|
||||
|
||||
@@ -7,8 +7,8 @@ use futures::StreamExt;
|
||||
use gpui::{App, AsyncApp, Task};
|
||||
use http_client::github::{GitHubLspBinaryVersion, latest_github_release};
|
||||
use language::{
|
||||
ContextProvider, LanguageName, LocalFile as _, LspAdapter, LspAdapterDelegate, LspInstaller,
|
||||
Toolchain,
|
||||
ContextProvider, LanguageName, LanguageRegistry, LocalFile as _, LspAdapter,
|
||||
LspAdapterDelegate, LspInstaller, Toolchain,
|
||||
};
|
||||
use lsp::{LanguageServerBinary, LanguageServerName, Uri};
|
||||
use node_runtime::{NodeRuntime, VersionStrategy};
|
||||
@@ -129,14 +129,15 @@ fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
}
|
||||
|
||||
pub struct JsonLspAdapter {
|
||||
languages: Arc<LanguageRegistry>,
|
||||
node: NodeRuntime,
|
||||
}
|
||||
|
||||
impl JsonLspAdapter {
|
||||
const PACKAGE_NAME: &str = "vscode-langservers-extracted";
|
||||
|
||||
pub fn new(node: NodeRuntime) -> Self {
|
||||
Self { node }
|
||||
pub fn new(languages: Arc<LanguageRegistry>, node: NodeRuntime) -> Self {
|
||||
Self { languages, node }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +256,7 @@ impl LspAdapter for JsonLspAdapter {
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Value> {
|
||||
let mut config = cx.update(|cx| {
|
||||
let schemas = json_schema_store::all_schema_file_associations(cx);
|
||||
let schemas = json_schema_store::all_schema_file_associations(&self.languages, cx);
|
||||
|
||||
// This can be viewed via `dev: open language server logs` -> `json-language-server` ->
|
||||
// `Server Info`
|
||||
|
||||
@@ -89,7 +89,7 @@ pub fn init(languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, node: NodeRuntime
|
||||
let go_context_provider = Arc::new(go::GoContextProvider);
|
||||
let go_lsp_adapter = Arc::new(go::GoLspAdapter);
|
||||
let json_context_provider = Arc::new(JsonTaskProvider);
|
||||
let json_lsp_adapter = Arc::new(json::JsonLspAdapter::new(node.clone()));
|
||||
let json_lsp_adapter = Arc::new(json::JsonLspAdapter::new(languages.clone(), node.clone()));
|
||||
let node_version_lsp_adapter = Arc::new(json::NodeVersionAdapter);
|
||||
let py_lsp_adapter = Arc::new(python::PyLspAdapter::new());
|
||||
let ty_lsp_adapter = Arc::new(python::TyLspAdapter::new(fs.clone()));
|
||||
|
||||
Reference in New Issue
Block a user