Detailed changes
@@ -8,7 +8,7 @@ use gpui::{
fonts, AppContext, AssetSource,
};
use schemars::{
- gen::{SchemaGenerator, SchemaSettings},
+ gen::SchemaGenerator,
schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec},
JsonSchema,
};
@@ -25,7 +25,7 @@ use util::ResultExt as _;
pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
pub use settings_file::*;
-pub use settings_store::SettingsStore;
+pub use settings_store::{SettingsJsonSchemaParams, SettingsStore};
pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json";
pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json";
@@ -150,6 +150,75 @@ impl Setting for Settings {
this
}
+
+ fn json_schema(
+ generator: &mut SchemaGenerator,
+ params: &SettingsJsonSchemaParams,
+ ) -> schemars::schema::RootSchema {
+ let mut root_schema = generator.root_schema_for::<SettingsFileContent>();
+
+ // Create a schema for a theme name.
+ let theme_name_schema = SchemaObject {
+ instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
+ enum_values: Some(
+ params
+ .theme_names
+ .iter()
+ .cloned()
+ .map(Value::String)
+ .collect(),
+ ),
+ ..Default::default()
+ };
+
+ // Create a schema for a 'languages overrides' object, associating editor
+ // settings with specific langauges.
+ assert!(root_schema.definitions.contains_key("EditorSettings"));
+
+ let languages_object_schema = SchemaObject {
+ instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
+ object: Some(Box::new(ObjectValidation {
+ properties: params
+ .language_names
+ .iter()
+ .map(|name| {
+ (
+ name.clone(),
+ Schema::new_ref("#/definitions/EditorSettings".into()),
+ )
+ })
+ .collect(),
+ ..Default::default()
+ })),
+ ..Default::default()
+ };
+
+ // Add these new schemas as definitions, and modify properties of the root
+ // schema to reference them.
+ root_schema.definitions.extend([
+ ("ThemeName".into(), theme_name_schema.into()),
+ ("Languages".into(), languages_object_schema.into()),
+ ]);
+ let root_schema_object = &mut root_schema.schema.object.as_mut().unwrap();
+
+ root_schema_object.properties.extend([
+ (
+ "theme".to_owned(),
+ Schema::new_ref("#/definitions/ThemeName".into()),
+ ),
+ (
+ "languages".to_owned(),
+ Schema::new_ref("#/definitions/Languages".into()),
+ ),
+ // For backward compatibility
+ (
+ "language_overrides".to_owned(),
+ Schema::new_ref("#/definitions/Languages".into()),
+ ),
+ ]);
+
+ root_schema
+ }
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
@@ -926,72 +995,6 @@ impl Settings {
}
}
-pub fn settings_file_json_schema(
- theme_names: Vec<String>,
- language_names: &[String],
-) -> serde_json::Value {
- let settings = SchemaSettings::draft07().with(|settings| {
- settings.option_add_null_type = false;
- });
- let generator = SchemaGenerator::new(settings);
-
- let mut root_schema = generator.into_root_schema_for::<SettingsFileContent>();
-
- // Create a schema for a theme name.
- let theme_name_schema = SchemaObject {
- instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
- enum_values: Some(theme_names.into_iter().map(Value::String).collect()),
- ..Default::default()
- };
-
- // Create a schema for a 'languages overrides' object, associating editor
- // settings with specific langauges.
- assert!(root_schema.definitions.contains_key("EditorSettings"));
-
- let languages_object_schema = SchemaObject {
- instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
- object: Some(Box::new(ObjectValidation {
- properties: language_names
- .iter()
- .map(|name| {
- (
- name.clone(),
- Schema::new_ref("#/definitions/EditorSettings".into()),
- )
- })
- .collect(),
- ..Default::default()
- })),
- ..Default::default()
- };
-
- // Add these new schemas as definitions, and modify properties of the root
- // schema to reference them.
- root_schema.definitions.extend([
- ("ThemeName".into(), theme_name_schema.into()),
- ("Languages".into(), languages_object_schema.into()),
- ]);
- let root_schema_object = &mut root_schema.schema.object.as_mut().unwrap();
-
- root_schema_object.properties.extend([
- (
- "theme".to_owned(),
- Schema::new_ref("#/definitions/ThemeName".into()),
- ),
- (
- "languages".to_owned(),
- Schema::new_ref("#/definitions/Languages".into()),
- ),
- // For backward compatibility
- (
- "language_overrides".to_owned(),
- Schema::new_ref("#/definitions/Languages".into()),
- ),
- ]);
-
- serde_json::to_value(root_schema).unwrap()
-}
-
fn merge<T: Copy>(target: &mut T, value: Option<T>) {
if let Some(value) = value {
*target = value;
@@ -1,8 +1,8 @@
use anyhow::{anyhow, Result};
-use collections::{hash_map, BTreeMap, HashMap, HashSet};
+use collections::{btree_map, hash_map, BTreeMap, HashMap, HashSet};
use gpui::AppContext;
use lazy_static::lazy_static;
-use schemars::JsonSchema;
+use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};
use serde::{de::DeserializeOwned, Deserialize as _, Serialize};
use smallvec::SmallVec;
use std::{
@@ -39,6 +39,10 @@ pub trait Setting: 'static {
cx: &AppContext,
) -> Self;
+ fn json_schema(generator: &mut SchemaGenerator, _: &SettingsJsonSchemaParams) -> RootSchema {
+ generator.root_schema_for::<Self::FileContent>()
+ }
+
fn load_via_json_merge(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
@@ -54,6 +58,11 @@ pub trait Setting: 'static {
}
}
+pub struct SettingsJsonSchemaParams<'a> {
+ pub theme_names: &'a [String],
+ pub language_names: &'a [String],
+}
+
/// A set of strongly-typed setting values defined via multiple JSON files.
#[derive(Default)]
pub struct SettingsStore {
@@ -84,6 +93,11 @@ trait AnySettingValue {
fn value_for_path(&self, path: Option<&Path>) -> &dyn Any;
fn set_global_value(&mut self, value: Box<dyn Any>);
fn set_local_value(&mut self, path: Arc<Path>, value: Box<dyn Any>);
+ fn json_schema(
+ &self,
+ generator: &mut SchemaGenerator,
+ _: &SettingsJsonSchemaParams,
+ ) -> RootSchema;
}
struct DeserializedSetting(Box<dyn Any>);
@@ -270,6 +284,79 @@ impl SettingsStore {
Ok(())
}
+ pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams) -> serde_json::Value {
+ use schemars::{
+ gen::SchemaSettings,
+ schema::{Schema, SchemaObject},
+ };
+
+ let settings = SchemaSettings::draft07().with(|settings| {
+ settings.option_add_null_type = false;
+ });
+ let mut generator = SchemaGenerator::new(settings);
+ let mut combined_schema = RootSchema::default();
+
+ for setting_value in self.setting_values.values() {
+ let setting_schema = setting_value.json_schema(&mut generator, schema_params);
+ combined_schema
+ .definitions
+ .extend(setting_schema.definitions);
+
+ let target_schema = if let Some(key) = setting_value.key() {
+ let key_schema = combined_schema
+ .schema
+ .object()
+ .properties
+ .entry(key.to_string())
+ .or_insert_with(|| Schema::Object(SchemaObject::default()));
+ if let Schema::Object(key_schema) = key_schema {
+ key_schema
+ } else {
+ continue;
+ }
+ } else {
+ &mut combined_schema.schema
+ };
+
+ merge_schema(target_schema, setting_schema.schema);
+ }
+
+ fn merge_schema(target: &mut SchemaObject, source: SchemaObject) {
+ if let Some(source) = source.object {
+ let target_properties = &mut target.object().properties;
+ for (key, value) in source.properties {
+ match target_properties.entry(key) {
+ btree_map::Entry::Vacant(e) => {
+ e.insert(value);
+ }
+ btree_map::Entry::Occupied(e) => {
+ if let (Schema::Object(target), Schema::Object(src)) =
+ (e.into_mut(), value)
+ {
+ merge_schema(target, src);
+ }
+ }
+ }
+ }
+ }
+
+ overwrite(&mut target.instance_type, source.instance_type);
+ overwrite(&mut target.string, source.string);
+ overwrite(&mut target.number, source.number);
+ overwrite(&mut target.reference, source.reference);
+ overwrite(&mut target.array, source.array);
+ overwrite(&mut target.enum_values, source.enum_values);
+
+ fn overwrite<T>(target: &mut Option<T>, source: Option<T>) {
+ if let Some(source) = source {
+ *target = Some(source);
+ }
+ }
+ }
+
+ serde_json::to_value(&combined_schema).unwrap()
+ }
+
fn recompute_values(
&mut self,
user_settings_changed: bool,
@@ -457,6 +544,14 @@ impl<T: Setting> AnySettingValue for SettingValue<T> {
Err(ix) => self.local_values.insert(ix, (path, value)),
}
}
+
+ fn json_schema(
+ &self,
+ generator: &mut SchemaGenerator,
+ params: &SettingsJsonSchemaParams,
+ ) -> RootSchema {
+ T::json_schema(generator, params)
+ }
}
// impl Debug for SettingsStore {
@@ -6,7 +6,7 @@ use gpui::AppContext;
use language::{LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter};
use node_runtime::NodeRuntime;
use serde_json::json;
-use settings::{keymap_file_json_schema, settings_file_json_schema};
+use settings::{keymap_file_json_schema, SettingsJsonSchemaParams, SettingsStore};
use smol::fs;
use staff_mode::StaffMode;
use std::{
@@ -128,12 +128,18 @@ impl LspAdapter for JsonLspAdapter {
cx: &mut AppContext,
) -> Option<BoxFuture<'static, serde_json::Value>> {
let action_names = cx.all_action_names().collect::<Vec<_>>();
- let theme_names = self
+ let theme_names = &self
.themes
.list(**cx.default_global::<StaffMode>())
.map(|meta| meta.name)
- .collect();
- let language_names = self.languages.language_names();
+ .collect::<Vec<_>>();
+ let language_names = &self.languages.language_names();
+ let settings_schema = cx
+ .global::<SettingsStore>()
+ .json_schema(&SettingsJsonSchemaParams {
+ theme_names,
+ language_names,
+ });
Some(
future::ready(serde_json::json!({
"json": {
@@ -143,7 +149,7 @@ impl LspAdapter for JsonLspAdapter {
"schemas": [
{
"fileMatch": [schema_file_match(&paths::SETTINGS)],
- "schema": settings_file_json_schema(theme_names, &language_names),
+ "schema": settings_schema,
},
{
"fileMatch": [schema_file_match(&paths::KEYMAP)],