Skip to content

Commit a350438

Browse files
Specify a schema to use when dealing with JSONC files (#44250)
Follow-up of #43854 Closes #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
1 parent bd6ca84 commit a350438

File tree

5 files changed

+65
-15
lines changed

5 files changed

+65
-15
lines changed

crates/json_schema_store/src/json_schema_store.rs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ use std::{str::FromStr, sync::Arc};
33

44
use anyhow::{Context as _, Result};
55
use gpui::{App, AsyncApp, BorrowAppContext as _, Entity, WeakEntity};
6-
use language::LanguageRegistry;
6+
use language::{LanguageRegistry, language_settings::all_language_settings};
77
use project::LspStore;
8+
use util::schemars::{AllowTrailingCommas, DefaultDenyUnknownFields};
89

910
// Origin: https://github.com/SchemaStore/schemastore
1011
const TSCONFIG_SCHEMA: &str = include_str!("schemas/tsconfig.json");
@@ -159,14 +160,35 @@ pub fn resolve_schema_request_inner(
159160
}
160161
}
161162
"snippets" => snippet_provider::format::VsSnippetsFile::generate_json_schema(),
163+
"jsonc" => jsonc_schema(),
162164
_ => {
163-
anyhow::bail!("Unrecognized builtin JSON schema: {}", schema_name);
165+
anyhow::bail!("Unrecognized builtin JSON schema: {schema_name}");
164166
}
165167
};
166168
Ok(schema)
167169
}
168170

169-
pub fn all_schema_file_associations(cx: &mut App) -> serde_json::Value {
171+
const JSONC_LANGUAGE_NAME: &str = "JSONC";
172+
173+
pub fn all_schema_file_associations(
174+
languages: &Arc<LanguageRegistry>,
175+
cx: &mut App,
176+
) -> serde_json::Value {
177+
let extension_globs = languages
178+
.available_language_for_name(JSONC_LANGUAGE_NAME)
179+
.map(|language| language.matcher().path_suffixes.clone())
180+
.into_iter()
181+
.flatten()
182+
// Path suffixes can be entire file names or just their extensions.
183+
.flat_map(|path_suffix| [format!("*.{path_suffix}"), path_suffix]);
184+
let override_globs = all_language_settings(None, cx)
185+
.file_types
186+
.get(JSONC_LANGUAGE_NAME)
187+
.into_iter()
188+
.flat_map(|(_, glob_strings)| glob_strings)
189+
.cloned();
190+
let jsonc_globs = extension_globs.chain(override_globs).collect::<Vec<_>>();
191+
170192
let mut file_associations = serde_json::json!([
171193
{
172194
"fileMatch": [
@@ -211,6 +233,10 @@ pub fn all_schema_file_associations(cx: &mut App) -> serde_json::Value {
211233
"fileMatch": ["package.json"],
212234
"url": "zed://schemas/package_json"
213235
},
236+
{
237+
"fileMatch": &jsonc_globs,
238+
"url": "zed://schemas/jsonc"
239+
},
214240
]);
215241

216242
#[cfg(debug_assertions)]
@@ -233,7 +259,7 @@ pub fn all_schema_file_associations(cx: &mut App) -> serde_json::Value {
233259
let file_name = normalized_action_name_to_file_name(normalized_name.clone());
234260
serde_json::json!({
235261
"fileMatch": [file_name],
236-
"url": format!("zed://schemas/action/{}", normalized_name)
262+
"url": format!("zed://schemas/action/{normalized_name}")
237263
})
238264
}),
239265
);
@@ -249,6 +275,26 @@ fn package_json_schema() -> serde_json::Value {
249275
serde_json::Value::from_str(PACKAGE_JSON_SCHEMA).unwrap()
250276
}
251277

278+
fn jsonc_schema() -> serde_json::Value {
279+
let generator = schemars::generate::SchemaSettings::draft2019_09()
280+
.with_transform(DefaultDenyUnknownFields)
281+
.with_transform(AllowTrailingCommas)
282+
.into_generator();
283+
let meta_schema = generator
284+
.settings()
285+
.meta_schema
286+
.as_ref()
287+
.expect("meta_schema should be present in schemars settings")
288+
.to_string();
289+
let defs = generator.definitions();
290+
let schema = schemars::json_schema!({
291+
"$schema": meta_schema,
292+
"allowTrailingCommas": true,
293+
"$defs": defs,
294+
});
295+
serde_json::to_value(schema).unwrap()
296+
}
297+
252298
fn generate_inspector_style_schema() -> serde_json::Value {
253299
let schema = schemars::generate::SchemaSettings::draft2019_09()
254300
.with_transform(util::schemars::DefaultDenyUnknownFields)

crates/language/src/language_registry.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ impl LanguageRegistry {
745745
self: &Arc<Self>,
746746
path: &Path,
747747
content: Option<&Rope>,
748-
user_file_types: Option<&FxHashMap<Arc<str>, GlobSet>>,
748+
user_file_types: Option<&FxHashMap<Arc<str>, (GlobSet, Vec<String>)>>,
749749
) -> Option<AvailableLanguage> {
750750
let filename = path.file_name().and_then(|filename| filename.to_str());
751751
// `Path.extension()` returns None for files with a leading '.'
@@ -788,7 +788,7 @@ impl LanguageRegistry {
788788
let path_matches_custom_suffix = || {
789789
user_file_types
790790
.and_then(|types| types.get(language_name.as_ref()))
791-
.map_or(None, |custom_suffixes| {
791+
.map_or(None, |(custom_suffixes, _)| {
792792
path_suffixes
793793
.iter()
794794
.find(|(_, candidate)| custom_suffixes.is_match_candidate(candidate))

crates/language/src/language_settings.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub struct AllLanguageSettings {
5151
pub edit_predictions: EditPredictionSettings,
5252
pub defaults: LanguageSettings,
5353
languages: HashMap<LanguageName, LanguageSettings>,
54-
pub(crate) file_types: FxHashMap<Arc<str>, GlobSet>,
54+
pub file_types: FxHashMap<Arc<str>, (GlobSet, Vec<String>)>,
5555
}
5656

5757
#[derive(Debug, Clone, PartialEq)]
@@ -656,7 +656,7 @@ impl settings::Settings for AllLanguageSettings {
656656

657657
let enabled_in_text_threads = edit_predictions.enabled_in_text_threads.unwrap();
658658

659-
let mut file_types: FxHashMap<Arc<str>, GlobSet> = FxHashMap::default();
659+
let mut file_types: FxHashMap<Arc<str>, (GlobSet, Vec<String>)> = FxHashMap::default();
660660

661661
for (language, patterns) in all_languages.file_types.iter().flatten() {
662662
let mut builder = GlobSetBuilder::new();
@@ -665,7 +665,10 @@ impl settings::Settings for AllLanguageSettings {
665665
builder.add(Glob::new(pattern).unwrap());
666666
}
667667

668-
file_types.insert(language.clone(), builder.build().unwrap());
668+
file_types.insert(
669+
language.clone(),
670+
(builder.build().unwrap(), patterns.0.clone()),
671+
);
669672
}
670673

671674
Self {

crates/languages/src/json.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use futures::StreamExt;
77
use gpui::{App, AsyncApp, Task};
88
use http_client::github::{GitHubLspBinaryVersion, latest_github_release};
99
use language::{
10-
ContextProvider, LanguageName, LocalFile as _, LspAdapter, LspAdapterDelegate, LspInstaller,
11-
Toolchain,
10+
ContextProvider, LanguageName, LanguageRegistry, LocalFile as _, LspAdapter,
11+
LspAdapterDelegate, LspInstaller, Toolchain,
1212
};
1313
use lsp::{LanguageServerBinary, LanguageServerName, Uri};
1414
use node_runtime::{NodeRuntime, VersionStrategy};
@@ -129,14 +129,15 @@ fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
129129
}
130130

131131
pub struct JsonLspAdapter {
132+
languages: Arc<LanguageRegistry>,
132133
node: NodeRuntime,
133134
}
134135

135136
impl JsonLspAdapter {
136137
const PACKAGE_NAME: &str = "vscode-langservers-extracted";
137138

138-
pub fn new(node: NodeRuntime) -> Self {
139-
Self { node }
139+
pub fn new(languages: Arc<LanguageRegistry>, node: NodeRuntime) -> Self {
140+
Self { languages, node }
140141
}
141142
}
142143

@@ -255,7 +256,7 @@ impl LspAdapter for JsonLspAdapter {
255256
cx: &mut AsyncApp,
256257
) -> Result<Value> {
257258
let mut config = cx.update(|cx| {
258-
let schemas = json_schema_store::all_schema_file_associations(cx);
259+
let schemas = json_schema_store::all_schema_file_associations(&self.languages, cx);
259260

260261
// This can be viewed via `dev: open language server logs` -> `json-language-server` ->
261262
// `Server Info`

crates/languages/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub fn init(languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, node: NodeRuntime
8989
let go_context_provider = Arc::new(go::GoContextProvider);
9090
let go_lsp_adapter = Arc::new(go::GoLspAdapter);
9191
let json_context_provider = Arc::new(JsonTaskProvider);
92-
let json_lsp_adapter = Arc::new(json::JsonLspAdapter::new(node.clone()));
92+
let json_lsp_adapter = Arc::new(json::JsonLspAdapter::new(languages.clone(), node.clone()));
9393
let node_version_lsp_adapter = Arc::new(json::NodeVersionAdapter);
9494
let py_lsp_adapter = Arc::new(python::PyLspAdapter::new());
9595
let ty_lsp_adapter = Arc::new(python::TyLspAdapter::new(fs.clone()));

0 commit comments

Comments
 (0)