@@ -73,6 +73,8 @@ pub fn init(cx: &mut App) {
}
cx.update_global::<SchemaStore, _>(|schema_store, cx| {
schema_store.notify_schema_changed(&format!("{SCHEMA_URI_PREFIX}settings"), cx);
+ schema_store
+ .notify_schema_changed(&format!("{SCHEMA_URI_PREFIX}project_settings"), cx);
});
})
.detach();
@@ -294,6 +296,34 @@ async fn resolve_dynamic_schema(
)
})
}
+ "project_settings" => {
+ let lsp_adapter_names = languages
+ .all_lsp_adapters()
+ .into_iter()
+ .map(|adapter| adapter.name().to_string())
+ .collect::<Vec<_>>();
+
+ cx.update(|cx| {
+ let language_names = &languages
+ .language_names()
+ .into_iter()
+ .map(|name| name.to_string())
+ .collect::<Vec<_>>();
+
+ cx.global::<settings::SettingsStore>().project_json_schema(
+ &settings::SettingsJsonSchemaParams {
+ language_names,
+ lsp_adapter_names: &lsp_adapter_names,
+ // These are not allowed in project-specific settings but
+ // they're still fields required by the
+ // `SettingsJsonSchemaParams` struct.
+ font_names: &[],
+ theme_names: &[],
+ icon_theme_names: &[],
+ },
+ )
+ })
+ }
"debug_tasks" => {
let adapter_schemas = cx.read_global::<dap::DapRegistry, _>(|dap_registry, _| {
dap_registry.adapters_schema()
@@ -344,10 +374,14 @@ pub fn all_schema_file_associations(
{
"fileMatch": [
schema_file_match(paths::settings_file()),
- paths::local_settings_file_relative_path()
],
"url": format!("{SCHEMA_URI_PREFIX}settings"),
},
+ {
+ "fileMatch": [
+ paths::local_settings_file_relative_path()],
+ "url": format!("{SCHEMA_URI_PREFIX}project_settings"),
+ },
{
"fileMatch": [schema_file_match(paths::keymap_file())],
"url": format!("{SCHEMA_URI_PREFIX}keymap"),
@@ -989,42 +989,76 @@ impl SettingsStore {
.map(|((_, path), content)| (path.clone(), &content.project))
}
- pub fn json_schema(&self, params: &SettingsJsonSchemaParams) -> Value {
- let mut generator = schemars::generate::SchemaSettings::draft2019_09()
- .with_transform(DefaultDenyUnknownFields)
- .with_transform(AllowTrailingCommas)
- .into_generator();
-
- UserSettingsContent::json_schema(&mut generator);
-
+ /// Configures common schema replacements shared between user and project
+ /// settings schemas.
+ ///
+ /// This sets up language-specific settings and LSP adapter settings that
+ /// are valid in both user and project settings.
+ fn configure_schema_generator(
+ generator: &mut schemars::SchemaGenerator,
+ params: &SettingsJsonSchemaParams,
+ ) {
let language_settings_content_ref = generator
.subschema_for::<LanguageSettingsContent>()
.to_value();
+ replace_subschema::<LanguageToSettingsMap>(generator, || {
+ json_schema!({
+ "type": "object",
+ "errorMessage": "No language with this name is installed.",
+ "properties": params.language_names.iter().map(|name| (name.clone(), language_settings_content_ref.clone())).collect::<serde_json::Map<_, _>>()
+ })
+ });
+
generator.subschema_for::<LspSettings>();
- let lsp_settings_def = generator
+ let lsp_settings_definition = generator
.definitions()
.get("LspSettings")
.expect("LspSettings should be defined")
.clone();
- replace_subschema::<LanguageToSettingsMap>(&mut generator, || {
+ replace_subschema::<LspSettingsMap>(generator, || {
+ let mut lsp_properties = serde_json::Map::new();
+
+ for adapter_name in params.lsp_adapter_names {
+ let mut base_lsp_settings = lsp_settings_definition
+ .as_object()
+ .expect("LspSettings should be an object")
+ .clone();
+
+ if let Some(properties) = base_lsp_settings.get_mut("properties") {
+ if let Some(properties_object) = properties.as_object_mut() {
+ properties_object.insert(
+ "initialization_options".to_string(),
+ serde_json::json!({
+ "$ref": format!("{LSP_SETTINGS_SCHEMA_URL_PREFIX}{adapter_name}")
+ }),
+ );
+ }
+ }
+
+ lsp_properties.insert(
+ adapter_name.clone(),
+ serde_json::Value::Object(base_lsp_settings),
+ );
+ }
+
json_schema!({
"type": "object",
- "properties": params
- .language_names
- .iter()
- .map(|name| {
- (
- name.clone(),
- language_settings_content_ref.clone(),
- )
- })
- .collect::<serde_json::Map<_, _>>(),
- "errorMessage": "No language with this name is installed."
+ "properties": lsp_properties
})
});
+ }
+
+ pub fn json_schema(&self, params: &SettingsJsonSchemaParams) -> Value {
+ let mut generator = schemars::generate::SchemaSettings::draft2019_09()
+ .with_transform(DefaultDenyUnknownFields)
+ .with_transform(AllowTrailingCommas)
+ .into_generator();
+
+ UserSettingsContent::json_schema(&mut generator);
+ Self::configure_schema_generator(&mut generator, params);
replace_subschema::<FontFamilyName>(&mut generator, || {
json_schema!({
@@ -1047,40 +1081,24 @@ impl SettingsStore {
})
});
- replace_subschema::<LspSettingsMap>(&mut generator, || {
- let mut lsp_properties = serde_json::Map::new();
-
- for adapter_name in params.lsp_adapter_names {
- let mut base_lsp_settings = lsp_settings_def
- .as_object()
- .expect("LspSettings should be an object")
- .clone();
-
- if let Some(properties) = base_lsp_settings.get_mut("properties") {
- if let Some(props_obj) = properties.as_object_mut() {
- props_obj.insert(
- "initialization_options".to_string(),
- serde_json::json!({
- "$ref": format!("{LSP_SETTINGS_SCHEMA_URL_PREFIX}{adapter_name}")
- }),
- );
- }
- }
+ generator
+ .root_schema_for::<UserSettingsContent>()
+ .to_value()
+ }
- lsp_properties.insert(
- adapter_name.clone(),
- serde_json::Value::Object(base_lsp_settings),
- );
- }
+ /// Generate JSON schema for project settings, including only settings valid
+ /// for project-level configurations.
+ pub fn project_json_schema(&self, params: &SettingsJsonSchemaParams) -> Value {
+ let mut generator = schemars::generate::SchemaSettings::draft2019_09()
+ .with_transform(DefaultDenyUnknownFields)
+ .with_transform(AllowTrailingCommas)
+ .into_generator();
- json_schema!({
- "type": "object",
- "properties": lsp_properties,
- })
- });
+ ProjectSettingsContent::json_schema(&mut generator);
+ Self::configure_schema_generator(&mut generator, params);
generator
- .root_schema_for::<UserSettingsContent>()
+ .root_schema_for::<ProjectSettingsContent>()
.to_value()
}
@@ -2336,4 +2354,63 @@ mod tests {
assert_eq!(init_options_ref, "zed://schemas/settings/lsp/rust-analyzer");
}
+
+ #[gpui::test]
+ fn test_lsp_project_settings_schema_generation(cx: &mut App) {
+ let store = SettingsStore::test(cx);
+
+ let schema = store.project_json_schema(&SettingsJsonSchemaParams {
+ language_names: &["Rust".to_string(), "TypeScript".to_string()],
+ font_names: &["Zed Mono".to_string()],
+ theme_names: &["One Dark".into()],
+ icon_theme_names: &["Zed Icons".into()],
+ lsp_adapter_names: &[
+ "rust-analyzer".to_string(),
+ "typescript-language-server".to_string(),
+ ],
+ });
+
+ let properties = schema
+ .pointer("/$defs/LspSettingsMap/properties")
+ .expect("LspSettingsMap should have properties")
+ .as_object()
+ .unwrap();
+
+ assert!(properties.contains_key("rust-analyzer"));
+ assert!(properties.contains_key("typescript-language-server"));
+
+ let init_options_ref = properties
+ .get("rust-analyzer")
+ .unwrap()
+ .pointer("/properties/initialization_options/$ref")
+ .expect("initialization_options should have a $ref")
+ .as_str()
+ .unwrap();
+
+ assert_eq!(init_options_ref, "zed://schemas/settings/lsp/rust-analyzer");
+ }
+
+ #[gpui::test]
+ fn test_project_json_schema_differs_from_user_schema(cx: &mut App) {
+ let store = SettingsStore::test(cx);
+
+ let params = SettingsJsonSchemaParams {
+ language_names: &["Rust".to_string()],
+ font_names: &["Zed Mono".to_string()],
+ theme_names: &["One Dark".into()],
+ icon_theme_names: &["Zed Icons".into()],
+ lsp_adapter_names: &["rust-analyzer".to_string()],
+ };
+
+ let user_schema = store.json_schema(¶ms);
+ let project_schema = store.project_json_schema(¶ms);
+
+ assert_ne!(user_schema, project_schema);
+
+ let user_schema_str = serde_json::to_string(&user_schema).unwrap();
+ let project_schema_str = serde_json::to_string(&project_schema).unwrap();
+
+ assert!(user_schema_str.contains("\"auto_update\""));
+ assert!(!project_schema_str.contains("\"auto_update\""));
+ }
}