diff --git a/Cargo.lock b/Cargo.lock index 1abe5bc059..3ba97e9382 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4133,7 +4133,7 @@ dependencies = [ [[package]] name = "dap-types" version = "0.0.1" -source = "git+https://github.com/zed-industries/dap-types?rev=b40956a7f4d1939da67429d941389ee306a3a308#b40956a7f4d1939da67429d941389ee306a3a308" +source = "git+https://github.com/zed-industries/dap-types?rev=7f39295b441614ca9dbf44293e53c32f666897f9#7f39295b441614ca9dbf44293e53c32f666897f9" dependencies = [ "schemars", "serde", @@ -8847,6 +8847,7 @@ dependencies = [ "http_client", "imara-diff", "indoc", + "inventory", "itertools 0.14.0", "log", "lsp", @@ -14053,12 +14054,13 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.22" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +checksum = "fe8c9d1c68d67dd9f97ecbc6f932b60eb289c5dbddd8aa1405484a8fd2fcd984" dependencies = [ "dyn-clone", "indexmap", + "ref-cast", "schemars_derive", "serde", "serde_json", @@ -14066,9 +14068,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.22" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +checksum = "6ca9fcb757952f8e8629b9ab066fc62da523c46c2b247b1708a3be06dd82530b" dependencies = [ "proc-macro2", "quote", @@ -16010,6 +16012,7 @@ dependencies = [ "futures 0.3.31", "gpui", "indexmap", + "inventory", "log", "palette", "parking_lot", diff --git a/Cargo.toml b/Cargo.toml index 4239fcf1e9..2fee4059c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -444,7 +444,7 @@ core-video = { version = "0.4.3", features = ["metal"] } cpal = "0.16" criterion = { version = "0.5", features = ["html_reports"] } ctor = "0.4.0" -dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "b40956a7f4d1939da67429d941389ee306a3a308" } +dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "7f39295b441614ca9dbf44293e53c32f666897f9" } dashmap = "6.0" derive_more = "0.99.17" dirs = "4.0" @@ -540,7 +540,7 @@ rustc-hash = "2.1.0" rustls = { version = "0.23.26" } rustls-platform-verifier = "0.5.0" scap = { git = "https://github.com/zed-industries/scap", rev = "08f0a01417505cc0990b9931a37e5120db92e0d0", default-features = false } -schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] } +schemars = { version = "1.0", features = ["indexmap2"] } semver = "1.0" serde = { version = "1.0", features = ["derive", "rc"] } serde_derive = { version = "1.0", features = ["deserialize_in_place"] } diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index 294d793e79..019ab18c20 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -6,9 +6,10 @@ use anyhow::{Result, bail}; use collections::IndexMap; use gpui::{App, Pixels, SharedString}; use language_model::LanguageModel; -use schemars::{JsonSchema, schema::Schema}; +use schemars::{JsonSchema, json_schema}; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; +use std::borrow::Cow; pub use crate::agent_profile::*; @@ -321,29 +322,27 @@ pub struct LanguageModelSelection { pub struct LanguageModelProviderSetting(pub String); impl JsonSchema for LanguageModelProviderSetting { - fn schema_name() -> String { + fn schema_name() -> Cow<'static, str> { "LanguageModelProviderSetting".into() } - fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> Schema { - schemars::schema::SchemaObject { - enum_values: Some(vec![ - "anthropic".into(), - "amazon-bedrock".into(), - "google".into(), - "lmstudio".into(), - "ollama".into(), - "openai".into(), - "zed.dev".into(), - "copilot_chat".into(), - "deepseek".into(), - "openrouter".into(), - "mistral".into(), - "vercel".into(), - ]), - ..Default::default() - } - .into() + fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { + json_schema!({ + "enum": [ + "anthropic", + "amazon-bedrock", + "google", + "lmstudio", + "ollama", + "openai", + "zed.dev", + "copilot_chat", + "deepseek", + "openrouter", + "mistral", + "vercel" + ] + }) } } diff --git a/crates/assistant_tools/src/schema.rs b/crates/assistant_tools/src/schema.rs index 4a71d47d2c..888e11de4e 100644 --- a/crates/assistant_tools/src/schema.rs +++ b/crates/assistant_tools/src/schema.rs @@ -1,8 +1,9 @@ use anyhow::Result; use language_model::LanguageModelToolSchemaFormat; use schemars::{ - JsonSchema, - schema::{RootSchema, Schema, SchemaObject}, + JsonSchema, Schema, + generate::SchemaSettings, + transform::{Transform, transform_subschemas}, }; pub fn json_schema_for( @@ -13,7 +14,7 @@ pub fn json_schema_for( } fn schema_to_json( - schema: &RootSchema, + schema: &Schema, format: LanguageModelToolSchemaFormat, ) -> Result { let mut value = serde_json::to_value(schema)?; @@ -21,58 +22,42 @@ fn schema_to_json( Ok(value) } -fn root_schema_for(format: LanguageModelToolSchemaFormat) -> RootSchema { +fn root_schema_for(format: LanguageModelToolSchemaFormat) -> Schema { let mut generator = match format { - LanguageModelToolSchemaFormat::JsonSchema => schemars::SchemaGenerator::default(), - LanguageModelToolSchemaFormat::JsonSchemaSubset => { - schemars::r#gen::SchemaSettings::default() - .with(|settings| { - settings.meta_schema = None; - settings.inline_subschemas = true; - settings - .visitors - .push(Box::new(TransformToJsonSchemaSubsetVisitor)); - }) - .into_generator() - } + LanguageModelToolSchemaFormat::JsonSchema => SchemaSettings::draft07().into_generator(), + // TODO: Gemini docs mention using a subset of OpenAPI 3, so this may benefit from using + // `SchemaSettings::openapi3()`. + LanguageModelToolSchemaFormat::JsonSchemaSubset => SchemaSettings::draft07() + .with(|settings| { + settings.meta_schema = None; + settings.inline_subschemas = true; + }) + .with_transform(ToJsonSchemaSubsetTransform) + .into_generator(), }; generator.root_schema_for::() } #[derive(Debug, Clone)] -struct TransformToJsonSchemaSubsetVisitor; +struct ToJsonSchemaSubsetTransform; -impl schemars::visit::Visitor for TransformToJsonSchemaSubsetVisitor { - fn visit_root_schema(&mut self, root: &mut RootSchema) { - schemars::visit::visit_root_schema(self, root) - } - - fn visit_schema(&mut self, schema: &mut Schema) { - schemars::visit::visit_schema(self, schema) - } - - fn visit_schema_object(&mut self, schema: &mut SchemaObject) { +impl Transform for ToJsonSchemaSubsetTransform { + fn transform(&mut self, schema: &mut Schema) { // Ensure that the type field is not an array, this happens when we use // Option, the type will be [T, "null"]. - if let Some(instance_type) = schema.instance_type.take() { - schema.instance_type = match instance_type { - schemars::schema::SingleOrVec::Single(t) => { - Some(schemars::schema::SingleOrVec::Single(t)) + if let Some(type_field) = schema.get_mut("type") { + if let Some(types) = type_field.as_array() { + if let Some(first_type) = types.first() { + *type_field = first_type.clone(); } - schemars::schema::SingleOrVec::Vec(items) => items - .into_iter() - .next() - .map(schemars::schema::SingleOrVec::from), - }; - } - - // One of is not supported, use anyOf instead. - if let Some(subschema) = schema.subschemas.as_mut() { - if let Some(one_of) = subschema.one_of.take() { - subschema.any_of = Some(one_of); } } - schemars::visit::visit_schema_object(self, schema) + // oneOf is not supported, use anyOf instead + if let Some(one_of) = schema.remove("oneOf") { + schema.insert("anyOf".to_string(), one_of); + } + + transform_subschemas(self, schema); } } diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 145a31a179..55427b1aa7 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -22,9 +22,7 @@ use gpui::{ use language::{ Diagnostic, DiagnosticEntry, DiagnosticSourceKind, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope, - language_settings::{ - AllLanguageSettings, Formatter, FormatterList, PrettierSettings, SelectedFormatter, - }, + language_settings::{AllLanguageSettings, Formatter, PrettierSettings, SelectedFormatter}, tree_sitter_rust, tree_sitter_typescript, }; use lsp::{LanguageServerId, OneOf}; @@ -4591,15 +4589,13 @@ async fn test_formatting_buffer( cx_a.update(|cx| { SettingsStore::update_global(cx, |store, cx| { store.update_user_settings::(cx, |file| { - file.defaults.formatter = Some(SelectedFormatter::List(FormatterList( - vec![Formatter::External { + file.defaults.formatter = + Some(SelectedFormatter::List(vec![Formatter::External { command: "awk".into(), arguments: Some( vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(), ), - }] - .into(), - ))); + }])); }); }); }); @@ -4699,9 +4695,10 @@ async fn test_prettier_formatting_buffer( cx_b.update(|cx| { SettingsStore::update_global(cx, |store, cx| { store.update_user_settings::(cx, |file| { - file.defaults.formatter = Some(SelectedFormatter::List(FormatterList( - vec![Formatter::LanguageServer { name: None }].into(), - ))); + file.defaults.formatter = + Some(SelectedFormatter::List(vec![Formatter::LanguageServer { + name: None, + }])); file.defaults.prettier = Some(PrettierSettings { allowed: true, ..PrettierSettings::default() diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs index 217273a387..0e9b25dc38 100644 --- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs +++ b/crates/collab/src/tests/remote_editing_collaboration_tests.rs @@ -14,8 +14,7 @@ use http_client::BlockedHttpClient; use language::{ FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageRegistry, language_settings::{ - AllLanguageSettings, Formatter, FormatterList, PrettierSettings, SelectedFormatter, - language_settings, + AllLanguageSettings, Formatter, PrettierSettings, SelectedFormatter, language_settings, }, tree_sitter_typescript, }; @@ -505,9 +504,10 @@ async fn test_ssh_collaboration_formatting_with_prettier( cx_b.update(|cx| { SettingsStore::update_global(cx, |store, cx| { store.update_user_settings::(cx, |file| { - file.defaults.formatter = Some(SelectedFormatter::List(FormatterList( - vec![Formatter::LanguageServer { name: None }].into(), - ))); + file.defaults.formatter = + Some(SelectedFormatter::List(vec![Formatter::LanguageServer { + name: None, + }])); file.defaults.prettier = Some(PrettierSettings { allowed: true, ..PrettierSettings::default() diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 8e1c84083f..d9f26b3b34 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -10,6 +10,7 @@ use gpui::{AsyncApp, SharedString}; pub use http_client::{HttpClient, github::latest_github_release}; use language::{LanguageName, LanguageToolchainStore}; use node_runtime::NodeRuntime; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::WorktreeId; use smol::fs::File; @@ -47,7 +48,10 @@ pub trait DapDelegate: Send + Sync + 'static { async fn shell_env(&self) -> collections::HashMap; } -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] +#[derive( + Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, JsonSchema, +)] +#[serde(transparent)] pub struct DebugAdapterName(pub SharedString); impl Deref for DebugAdapterName { diff --git a/crates/editor/src/editor_settings_controls.rs b/crates/editor/src/editor_settings_controls.rs index 54bb865520..dc5557b052 100644 --- a/crates/editor/src/editor_settings_controls.rs +++ b/crates/editor/src/editor_settings_controls.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use gpui::{App, FontFeatures, FontWeight}; use project::project_settings::{InlineBlameSettings, ProjectSettings}; use settings::{EditableSettingControl, Settings}; -use theme::{FontFamilyCache, ThemeSettings}; +use theme::{FontFamilyCache, FontFamilyName, ThemeSettings}; use ui::{ CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup, prelude::*, @@ -75,7 +75,7 @@ impl EditableSettingControl for BufferFontFamilyControl { value: Self::Value, _cx: &App, ) { - settings.buffer_font_family = Some(value.to_string()); + settings.buffer_font_family = Some(FontFamilyName(value.into())); } } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index a6bbe6d621..404284c4b0 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -30,7 +30,7 @@ use language::{ }, tree_sitter_python, }; -use language_settings::{Formatter, FormatterList, IndentGuideSettings}; +use language_settings::{Formatter, IndentGuideSettings}; use lsp::CompletionParams; use multi_buffer::{IndentGuide, PathKey}; use parking_lot::Mutex; @@ -3567,7 +3567,7 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) { #[gpui::test] fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) { init_test(cx, |settings| { - settings.languages.extend([ + settings.languages.0.extend([ ( "TOML".into(), LanguageSettingsContent { @@ -5145,7 +5145,7 @@ fn test_transpose(cx: &mut TestAppContext) { #[gpui::test] async fn test_rewrap(cx: &mut TestAppContext) { init_test(cx, |settings| { - settings.languages.extend([ + settings.languages.0.extend([ ( "Markdown".into(), LanguageSettingsContent { @@ -9326,7 +9326,7 @@ async fn test_document_format_during_save(cx: &mut TestAppContext) { // Set rust language override and assert overridden tabsize is sent to language server update_test_language_settings(cx, |settings| { - settings.languages.insert( + settings.languages.0.insert( "Rust".into(), LanguageSettingsContent { tab_size: NonZeroU32::new(8), @@ -9890,7 +9890,7 @@ async fn test_range_format_during_save(cx: &mut TestAppContext) { // Set Rust language override and assert overridden tabsize is sent to language server update_test_language_settings(cx, |settings| { - settings.languages.insert( + settings.languages.0.insert( "Rust".into(), LanguageSettingsContent { tab_size: NonZeroU32::new(8), @@ -9933,9 +9933,9 @@ async fn test_range_format_during_save(cx: &mut TestAppContext) { #[gpui::test] async fn test_document_format_manual_trigger(cx: &mut TestAppContext) { init_test(cx, |settings| { - settings.defaults.formatter = Some(language_settings::SelectedFormatter::List( - FormatterList(vec![Formatter::LanguageServer { name: None }].into()), - )) + settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![ + Formatter::LanguageServer { name: None }, + ])) }); let fs = FakeFs::new(cx.executor()); @@ -10062,21 +10062,17 @@ async fn test_document_format_manual_trigger(cx: &mut TestAppContext) { async fn test_multiple_formatters(cx: &mut TestAppContext) { init_test(cx, |settings| { settings.defaults.remove_trailing_whitespace_on_save = Some(true); - settings.defaults.formatter = - Some(language_settings::SelectedFormatter::List(FormatterList( - vec![ - Formatter::LanguageServer { name: None }, - Formatter::CodeActions( - [ - ("code-action-1".into(), true), - ("code-action-2".into(), true), - ] - .into_iter() - .collect(), - ), + settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![ + Formatter::LanguageServer { name: None }, + Formatter::CodeActions( + [ + ("code-action-1".into(), true), + ("code-action-2".into(), true), ] - .into(), - ))) + .into_iter() + .collect(), + ), + ])) }); let fs = FakeFs::new(cx.executor()); @@ -10328,9 +10324,9 @@ async fn test_multiple_formatters(cx: &mut TestAppContext) { #[gpui::test] async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) { init_test(cx, |settings| { - settings.defaults.formatter = Some(language_settings::SelectedFormatter::List( - FormatterList(vec![Formatter::LanguageServer { name: None }].into()), - )) + settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![ + Formatter::LanguageServer { name: None }, + ])) }); let fs = FakeFs::new(cx.executor()); @@ -14905,7 +14901,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppCon .unwrap(); let _fake_server = fake_servers.next().await.unwrap(); update_test_language_settings(cx, |language_settings| { - language_settings.languages.insert( + language_settings.languages.0.insert( language_name.clone(), LanguageSettingsContent { tab_size: NonZeroU32::new(8), @@ -15803,9 +15799,9 @@ fn completion_menu_entries(menu: &CompletionsMenu) -> Vec { #[gpui::test] async fn test_document_format_with_prettier(cx: &mut TestAppContext) { init_test(cx, |settings| { - settings.defaults.formatter = Some(language_settings::SelectedFormatter::List( - FormatterList(vec![Formatter::Prettier].into()), - )) + settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![ + Formatter::Prettier, + ])) }); let fs = FakeFs::new(cx.executor()); diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index 24fbd70b63..7885497034 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -125,9 +125,7 @@ pub trait Action: Any + Send { Self: Sized; /// Optional JSON schema for the action's input data. - fn action_json_schema( - _: &mut schemars::r#gen::SchemaGenerator, - ) -> Option + fn action_json_schema(_: &mut schemars::SchemaGenerator) -> Option where Self: Sized, { @@ -238,7 +236,7 @@ impl Default for ActionRegistry { struct ActionData { pub build: ActionBuilder, - pub json_schema: fn(&mut schemars::r#gen::SchemaGenerator) -> Option, + pub json_schema: fn(&mut schemars::SchemaGenerator) -> Option, } /// This type must be public so that our macros can build it in other crates. @@ -253,7 +251,7 @@ pub struct MacroActionData { pub name: &'static str, pub type_id: TypeId, pub build: ActionBuilder, - pub json_schema: fn(&mut schemars::r#gen::SchemaGenerator) -> Option, + pub json_schema: fn(&mut schemars::SchemaGenerator) -> Option, pub deprecated_aliases: &'static [&'static str], pub deprecation_message: Option<&'static str>, } @@ -357,8 +355,8 @@ impl ActionRegistry { pub fn action_schemas( &self, - generator: &mut schemars::r#gen::SchemaGenerator, - ) -> Vec<(&'static str, Option)> { + generator: &mut schemars::SchemaGenerator, + ) -> Vec<(&'static str, Option)> { // Use the order from all_names so that the resulting schema has sensible order. self.all_names .iter() diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 1853e6e934..5fee4c9047 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1388,8 +1388,8 @@ impl App { /// Get all non-internal actions that have been registered, along with their schemas. pub fn action_schemas( &self, - generator: &mut schemars::r#gen::SchemaGenerator, - ) -> Vec<(&'static str, Option)> { + generator: &mut schemars::SchemaGenerator, + ) -> Vec<(&'static str, Option)> { self.actions.action_schemas(generator) } diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index 1115d1c99c..7fc9c24393 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -1,9 +1,10 @@ use anyhow::{Context as _, bail}; -use schemars::{JsonSchema, SchemaGenerator, schema::Schema}; +use schemars::{JsonSchema, json_schema}; use serde::{ Deserialize, Deserializer, Serialize, Serializer, de::{self, Visitor}, }; +use std::borrow::Cow; use std::{ fmt::{self, Display, Formatter}, hash::{Hash, Hasher}, @@ -99,22 +100,14 @@ impl Visitor<'_> for RgbaVisitor { } impl JsonSchema for Rgba { - fn schema_name() -> String { - "Rgba".to_string() + fn schema_name() -> Cow<'static, str> { + "Rgba".into() } - fn json_schema(_generator: &mut SchemaGenerator) -> Schema { - use schemars::schema::{InstanceType, SchemaObject, StringValidation}; - - Schema::Object(SchemaObject { - instance_type: Some(InstanceType::String.into()), - string: Some(Box::new(StringValidation { - pattern: Some( - r"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$".to_string(), - ), - ..Default::default() - })), - ..Default::default() + fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema { + json_schema!({ + "type": "string", + "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$" }) } } @@ -629,11 +622,11 @@ impl From for Hsla { } impl JsonSchema for Hsla { - fn schema_name() -> String { + fn schema_name() -> Cow<'static, str> { Rgba::schema_name() } - fn json_schema(generator: &mut SchemaGenerator) -> Schema { + fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema { Rgba::json_schema(generator) } } diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index 30283c8dde..74be6344f9 100644 --- a/crates/gpui/src/geometry.rs +++ b/crates/gpui/src/geometry.rs @@ -6,8 +6,9 @@ use anyhow::{Context as _, anyhow}; use core::fmt::Debug; use derive_more::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub, SubAssign}; use refineable::Refineable; -use schemars::{JsonSchema, SchemaGenerator, schema::Schema}; +use schemars::{JsonSchema, json_schema}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; +use std::borrow::Cow; use std::{ cmp::{self, PartialOrd}, fmt::{self, Display}, @@ -3229,20 +3230,14 @@ impl TryFrom<&'_ str> for AbsoluteLength { } impl JsonSchema for AbsoluteLength { - fn schema_name() -> String { - "AbsoluteLength".to_string() + fn schema_name() -> Cow<'static, str> { + "AbsoluteLength".into() } - fn json_schema(_generator: &mut SchemaGenerator) -> Schema { - use schemars::schema::{InstanceType, SchemaObject, StringValidation}; - - Schema::Object(SchemaObject { - instance_type: Some(InstanceType::String.into()), - string: Some(Box::new(StringValidation { - pattern: Some(r"^-?\d+(\.\d+)?(px|rem)$".to_string()), - ..Default::default() - })), - ..Default::default() + fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema { + json_schema!({ + "type": "string", + "pattern": r"^-?\d+(\.\d+)?(px|rem)$" }) } } @@ -3366,20 +3361,14 @@ impl TryFrom<&'_ str> for DefiniteLength { } impl JsonSchema for DefiniteLength { - fn schema_name() -> String { - "DefiniteLength".to_string() + fn schema_name() -> Cow<'static, str> { + "DefiniteLength".into() } - fn json_schema(_generator: &mut SchemaGenerator) -> Schema { - use schemars::schema::{InstanceType, SchemaObject, StringValidation}; - - Schema::Object(SchemaObject { - instance_type: Some(InstanceType::String.into()), - string: Some(Box::new(StringValidation { - pattern: Some(r"^-?\d+(\.\d+)?(px|rem|%)$".to_string()), - ..Default::default() - })), - ..Default::default() + fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema { + json_schema!({ + "type": "string", + "pattern": r"^-?\d+(\.\d+)?(px|rem|%)$" }) } } @@ -3480,20 +3469,14 @@ impl TryFrom<&'_ str> for Length { } impl JsonSchema for Length { - fn schema_name() -> String { - "Length".to_string() + fn schema_name() -> Cow<'static, str> { + "Length".into() } - fn json_schema(_generator: &mut SchemaGenerator) -> Schema { - use schemars::schema::{InstanceType, SchemaObject, StringValidation}; - - Schema::Object(SchemaObject { - instance_type: Some(InstanceType::String.into()), - string: Some(Box::new(StringValidation { - pattern: Some(r"^(auto|-?\d+(\.\d+)?(px|rem|%))$".to_string()), - ..Default::default() - })), - ..Default::default() + fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema { + json_schema!({ + "type": "string", + "pattern": r"^(auto|-?\d+(\.\d+)?(px|rem|%))$" }) } } diff --git a/crates/gpui/src/shared_string.rs b/crates/gpui/src/shared_string.rs index 591bada48d..c325f98cd2 100644 --- a/crates/gpui/src/shared_string.rs +++ b/crates/gpui/src/shared_string.rs @@ -2,7 +2,10 @@ use derive_more::{Deref, DerefMut}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::{borrow::Borrow, sync::Arc}; +use std::{ + borrow::{Borrow, Cow}, + sync::Arc, +}; use util::arc_cow::ArcCow; /// A shared string is an immutable string that can be cheaply cloned in GPUI @@ -23,12 +26,16 @@ impl SharedString { } impl JsonSchema for SharedString { - fn schema_name() -> String { + fn inline_schema() -> bool { + String::inline_schema() + } + + fn schema_name() -> Cow<'static, str> { String::schema_name() } - fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - String::json_schema(r#gen) + fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema { + String::json_schema(generator) } } diff --git a/crates/gpui/src/text_system/font_features.rs b/crates/gpui/src/text_system/font_features.rs index 9fca903807..f95a0581f1 100644 --- a/crates/gpui/src/text_system/font_features.rs +++ b/crates/gpui/src/text_system/font_features.rs @@ -1,6 +1,7 @@ +use std::borrow::Cow; use std::sync::Arc; -use schemars::schema::{InstanceType, SchemaObject}; +use schemars::{JsonSchema, json_schema}; /// The OpenType features that can be configured for a given font. #[derive(Default, Clone, Eq, PartialEq, Hash)] @@ -128,36 +129,22 @@ impl serde::Serialize for FontFeatures { } } -impl schemars::JsonSchema for FontFeatures { - fn schema_name() -> String { +impl JsonSchema for FontFeatures { + fn schema_name() -> Cow<'static, str> { "FontFeatures".into() } - fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { - let mut schema = SchemaObject::default(); - schema.instance_type = Some(schemars::schema::SingleOrVec::Single(Box::new( - InstanceType::Object, - ))); - { - let mut property = SchemaObject { - instance_type: Some(schemars::schema::SingleOrVec::Vec(vec![ - InstanceType::Boolean, - InstanceType::Integer, - ])), - ..Default::default() - }; - - { - let mut number_constraints = property.number(); - number_constraints.multiple_of = Some(1.0); - number_constraints.minimum = Some(0.0); + fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { + json_schema!({ + "type": "object", + "patternProperties": { + "[0-9a-zA-Z]{4}$": { + "type": ["boolean", "integer"], + "minimum": 0, + "multipleOf": 1 + } } - schema - .object() - .pattern_properties - .insert("[0-9a-zA-Z]{4}$".into(), property.into()); - } - schema.into() + }) } } diff --git a/crates/gpui_macros/src/derive_action.rs b/crates/gpui_macros/src/derive_action.rs index c382ddd9c6..c32baba6cb 100644 --- a/crates/gpui_macros/src/derive_action.rs +++ b/crates/gpui_macros/src/derive_action.rs @@ -159,8 +159,8 @@ pub(crate) fn derive_action(input: TokenStream) -> TokenStream { } fn action_json_schema( - _generator: &mut gpui::private::schemars::r#gen::SchemaGenerator, - ) -> Option { + _generator: &mut gpui::private::schemars::SchemaGenerator, + ) -> Option { #json_schema_fn_body } diff --git a/crates/inline_completion_button/src/inline_completion_button.rs b/crates/inline_completion_button/src/inline_completion_button.rs index 4e9c887124..cf1e808f60 100644 --- a/crates/inline_completion_button/src/inline_completion_button.rs +++ b/crates/inline_completion_button/src/inline_completion_button.rs @@ -967,6 +967,7 @@ fn toggle_show_inline_completions_for_language( all_language_settings(None, cx).show_edit_predictions(Some(&language), cx); update_settings_file::(fs, cx, move |file, _| { file.languages + .0 .entry(language.name()) .or_default() .show_edit_predictions = Some(!show_edit_predictions); diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index b0e06c3d65..477b978517 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -39,6 +39,7 @@ globset.workspace = true gpui.workspace = true http_client.workspace = true imara-diff.workspace = true +inventory.workspace = true itertools.workspace = true log.workspace = true lsp.workspace = true diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index ebf7558abb..6955cd0549 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -2006,7 +2006,7 @@ fn test_autoindent_language_without_indents_query(cx: &mut App) { #[gpui::test] fn test_autoindent_with_injected_languages(cx: &mut App) { init_settings(cx, |settings| { - settings.languages.extend([ + settings.languages.0.extend([ ( "HTML".into(), LanguageSettingsContent { diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index f77afc76d2..951a0dbddc 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -39,11 +39,7 @@ use lsp::{CodeActionKind, InitializeParams, LanguageServerBinary, LanguageServer pub use manifest::{ManifestDelegate, ManifestName, ManifestProvider, ManifestQuery}; use parking_lot::Mutex; use regex::Regex; -use schemars::{ - JsonSchema, - r#gen::SchemaGenerator, - schema::{InstanceType, Schema, SchemaObject}, -}; +use schemars::{JsonSchema, json_schema}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; use serde_json::Value; use settings::WorktreeId; @@ -694,7 +690,6 @@ pub struct LanguageConfig { pub matcher: LanguageMatcher, /// List of bracket types in a language. #[serde(default)] - #[schemars(schema_with = "bracket_pair_config_json_schema")] pub brackets: BracketPairConfig, /// If set to true, auto indentation uses last non empty line to determine /// the indentation level for a new line. @@ -944,10 +939,9 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result, D } } -fn regex_json_schema(_: &mut SchemaGenerator) -> Schema { - Schema::Object(SchemaObject { - instance_type: Some(InstanceType::String.into()), - ..Default::default() +fn regex_json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { + json_schema!({ + "type": "string" }) } @@ -988,12 +982,12 @@ pub struct FakeLspAdapter { /// This struct includes settings for defining which pairs of characters are considered brackets and /// also specifies any language-specific scopes where these pairs should be ignored for bracket matching purposes. #[derive(Clone, Debug, Default, JsonSchema)] +#[schemars(with = "Vec::")] pub struct BracketPairConfig { /// A list of character pairs that should be treated as brackets in the context of a given language. pub pairs: Vec, /// A list of tree-sitter scopes for which a given bracket should not be active. /// N-th entry in `[Self::disabled_scopes_by_bracket_ix]` contains a list of disabled scopes for an n-th entry in `[Self::pairs]` - #[serde(skip)] pub disabled_scopes_by_bracket_ix: Vec>, } @@ -1003,10 +997,6 @@ impl BracketPairConfig { } } -fn bracket_pair_config_json_schema(r#gen: &mut SchemaGenerator) -> Schema { - Option::>::json_schema(r#gen) -} - #[derive(Deserialize, JsonSchema)] pub struct BracketPairContent { #[serde(flatten)] diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index b2bb684e1b..ff17d6dd9a 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -1170,7 +1170,7 @@ impl LanguageRegistryState { if let Some(theme) = self.theme.as_ref() { language.set_theme(theme.syntax()); } - self.language_settings.languages.insert( + self.language_settings.languages.0.insert( language.name(), LanguageSettingsContent { tab_size: language.config.tab_size, diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 9dda60b6a6..d2b9005f97 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -3,7 +3,6 @@ use crate::{File, Language, LanguageName, LanguageServerName}; use anyhow::Result; use collections::{FxHashMap, HashMap, HashSet}; -use core::slice; use ec4rs::{ Properties as EditorconfigProperties, property::{FinalNewline, IndentSize, IndentStyle, TabWidth, TrimTrailingWs}, @@ -11,17 +10,15 @@ use ec4rs::{ use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder}; use gpui::{App, Modifiers}; use itertools::{Either, Itertools}; -use schemars::{ - JsonSchema, - schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec}, -}; +use schemars::{JsonSchema, json_schema}; use serde::{ Deserialize, Deserializer, Serialize, de::{self, IntoDeserializer, MapAccess, SeqAccess, Visitor}, }; -use serde_json::Value; + use settings::{ - Settings, SettingsLocation, SettingsSources, SettingsStore, add_references_to_properties, + ParameterizedJsonSchema, Settings, SettingsLocation, SettingsSources, SettingsStore, + replace_subschema, }; use shellexpand; use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc}; @@ -306,13 +303,42 @@ pub struct AllLanguageSettingsContent { pub defaults: LanguageSettingsContent, /// The settings for individual languages. #[serde(default)] - pub languages: HashMap, + pub languages: LanguageToSettingsMap, /// Settings for associating file extensions and filenames /// with languages. #[serde(default)] pub file_types: HashMap, Vec>, } +/// Map from language name to settings. Its `ParameterizedJsonSchema` allows only known language +/// names in the keys. +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct LanguageToSettingsMap(pub HashMap); + +inventory::submit! { + ParameterizedJsonSchema { + add_and_get_ref: |generator, params, _cx| { + let language_settings_content_ref = generator + .subschema_for::() + .to_value(); + let schema = json_schema!({ + "type": "object", + "properties": params + .language_names + .iter() + .map(|name| { + ( + name.clone(), + language_settings_content_ref.clone(), + ) + }) + .collect::>() + }); + replace_subschema::(generator, schema) + } + } +} + /// Controls how completions are processed for this language. #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -648,45 +674,30 @@ pub enum FormatOnSave { On, /// Files should not be formatted on save. Off, - List(FormatterList), + List(Vec), } impl JsonSchema for FormatOnSave { - fn schema_name() -> String { + fn schema_name() -> Cow<'static, str> { "OnSaveFormatter".into() } - fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> Schema { - let mut schema = SchemaObject::default(); + fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema { let formatter_schema = Formatter::json_schema(generator); - schema.instance_type = Some( - vec![ - InstanceType::Object, - InstanceType::String, - InstanceType::Array, + + json_schema!({ + "oneOf": [ + { + "type": "array", + "items": formatter_schema + }, + { + "type": "string", + "enum": ["on", "off", "prettier", "language_server"] + }, + formatter_schema ] - .into(), - ); - - let valid_raw_values = SchemaObject { - enum_values: Some(vec![ - Value::String("on".into()), - Value::String("off".into()), - Value::String("prettier".into()), - Value::String("language_server".into()), - ]), - ..Default::default() - }; - let mut nested_values = SchemaObject::default(); - - nested_values.array().items = Some(formatter_schema.clone().into()); - - schema.subschemas().any_of = Some(vec![ - nested_values.into(), - valid_raw_values.into(), - formatter_schema, - ]); - schema.into() + }) } } @@ -725,11 +736,11 @@ impl<'de> Deserialize<'de> for FormatOnSave { } else if v == "off" { Ok(Self::Value::Off) } else if v == "language_server" { - Ok(Self::Value::List(FormatterList( - Formatter::LanguageServer { name: None }.into(), - ))) + Ok(Self::Value::List(vec![Formatter::LanguageServer { + name: None, + }])) } else { - let ret: Result = + let ret: Result, _> = Deserialize::deserialize(v.into_deserializer()); ret.map(Self::Value::List) } @@ -738,7 +749,7 @@ impl<'de> Deserialize<'de> for FormatOnSave { where A: MapAccess<'d>, { - let ret: Result = + let ret: Result, _> = Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)); ret.map(Self::Value::List) } @@ -746,7 +757,7 @@ impl<'de> Deserialize<'de> for FormatOnSave { where A: SeqAccess<'d>, { - let ret: Result = + let ret: Result, _> = Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map)); ret.map(Self::Value::List) } @@ -783,45 +794,30 @@ pub enum SelectedFormatter { /// or falling back to formatting via language server. #[default] Auto, - List(FormatterList), + List(Vec), } impl JsonSchema for SelectedFormatter { - fn schema_name() -> String { + fn schema_name() -> Cow<'static, str> { "Formatter".into() } - fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> Schema { - let mut schema = SchemaObject::default(); + fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema { let formatter_schema = Formatter::json_schema(generator); - schema.instance_type = Some( - vec![ - InstanceType::Object, - InstanceType::String, - InstanceType::Array, + + json_schema!({ + "oneOf": [ + { + "type": "array", + "items": formatter_schema + }, + { + "type": "string", + "enum": ["auto", "prettier", "language_server"] + }, + formatter_schema ] - .into(), - ); - - let valid_raw_values = SchemaObject { - enum_values: Some(vec![ - Value::String("auto".into()), - Value::String("prettier".into()), - Value::String("language_server".into()), - ]), - ..Default::default() - }; - - let mut nested_values = SchemaObject::default(); - - nested_values.array().items = Some(formatter_schema.clone().into()); - - schema.subschemas().any_of = Some(vec![ - nested_values.into(), - valid_raw_values.into(), - formatter_schema, - ]); - schema.into() + }) } } @@ -836,6 +832,7 @@ impl Serialize for SelectedFormatter { } } } + impl<'de> Deserialize<'de> for SelectedFormatter { fn deserialize(deserializer: D) -> std::result::Result where @@ -856,11 +853,11 @@ impl<'de> Deserialize<'de> for SelectedFormatter { if v == "auto" { Ok(Self::Value::Auto) } else if v == "language_server" { - Ok(Self::Value::List(FormatterList( - Formatter::LanguageServer { name: None }.into(), - ))) + Ok(Self::Value::List(vec![Formatter::LanguageServer { + name: None, + }])) } else { - let ret: Result = + let ret: Result, _> = Deserialize::deserialize(v.into_deserializer()); ret.map(SelectedFormatter::List) } @@ -869,7 +866,7 @@ impl<'de> Deserialize<'de> for SelectedFormatter { where A: MapAccess<'d>, { - let ret: Result = + let ret: Result, _> = Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)); ret.map(SelectedFormatter::List) } @@ -877,7 +874,7 @@ impl<'de> Deserialize<'de> for SelectedFormatter { where A: SeqAccess<'d>, { - let ret: Result = + let ret: Result, _> = Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map)); ret.map(SelectedFormatter::List) } @@ -885,19 +882,6 @@ impl<'de> Deserialize<'de> for SelectedFormatter { deserializer.deserialize_any(FormatDeserializer) } } -/// Controls which formatter should be used when formatting code. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case", transparent)] -pub struct FormatterList(pub SingleOrVec); - -impl AsRef<[Formatter]> for FormatterList { - fn as_ref(&self) -> &[Formatter] { - match &self.0 { - SingleOrVec::Single(single) => slice::from_ref(single), - SingleOrVec::Vec(v) => v, - } - } -} /// Controls which formatter should be used when formatting code. If there are multiple formatters, they are executed in the order of declaration. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] @@ -1209,7 +1193,7 @@ impl settings::Settings for AllLanguageSettings { serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?; let mut languages = HashMap::default(); - for (language_name, settings) in &default_value.languages { + for (language_name, settings) in &default_value.languages.0 { let mut language_settings = defaults.clone(); merge_settings(&mut language_settings, settings); languages.insert(language_name.clone(), language_settings); @@ -1310,7 +1294,7 @@ impl settings::Settings for AllLanguageSettings { } // A user's language-specific settings override default language-specific settings. - for (language_name, user_language_settings) in &user_settings.languages { + for (language_name, user_language_settings) in &user_settings.languages.0 { merge_settings( languages .entry(language_name.clone()) @@ -1366,51 +1350,6 @@ impl settings::Settings for AllLanguageSettings { }) } - fn json_schema( - generator: &mut schemars::r#gen::SchemaGenerator, - params: &settings::SettingsJsonSchemaParams, - _: &App, - ) -> schemars::schema::RootSchema { - let mut root_schema = generator.root_schema_for::(); - - // Create a schema for a 'languages overrides' object, associating editor - // settings with specific languages. - assert!( - root_schema - .definitions - .contains_key("LanguageSettingsContent") - ); - - let languages_object_schema = SchemaObject { - instance_type: Some(InstanceType::Object.into()), - object: Some(Box::new(ObjectValidation { - properties: params - .language_names - .iter() - .map(|name| { - ( - name.clone(), - Schema::new_ref("#/definitions/LanguageSettingsContent".into()), - ) - }) - .collect(), - ..Default::default() - })), - ..Default::default() - }; - - root_schema - .definitions - .extend([("Languages".into(), languages_object_schema.into())]); - - add_references_to_properties( - &mut root_schema, - &[("languages", "#/definitions/Languages")], - ); - - root_schema - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { let d = &mut current.defaults; if let Some(size) = vscode @@ -1674,29 +1613,26 @@ mod tests { let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); assert_eq!( settings.formatter, - Some(SelectedFormatter::List(FormatterList( - Formatter::LanguageServer { name: None }.into() - ))) + Some(SelectedFormatter::List(vec![Formatter::LanguageServer { + name: None + }])) ); let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}]}"; let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); assert_eq!( settings.formatter, - Some(SelectedFormatter::List(FormatterList( - vec![Formatter::LanguageServer { name: None }].into() - ))) + Some(SelectedFormatter::List(vec![Formatter::LanguageServer { + name: None + }])) ); let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}, \"prettier\"]}"; let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); assert_eq!( settings.formatter, - Some(SelectedFormatter::List(FormatterList( - vec![ - Formatter::LanguageServer { name: None }, - Formatter::Prettier - ] - .into() - ))) + Some(SelectedFormatter::List(vec![ + Formatter::LanguageServer { name: None }, + Formatter::Prettier + ])) ); } diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index 7a52a82f6b..acc24bb29c 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -269,10 +269,9 @@ impl JsonLspAdapter { #[cfg(debug_assertions)] fn generate_inspector_style_schema() -> serde_json_lenient::Value { - let schema = schemars::r#gen::SchemaSettings::draft07() - .with(|settings| settings.option_add_null_type = false) + let schema = schemars::generate::SchemaSettings::draft07() .into_generator() - .into_root_schema_for::(); + .root_schema_for::(); serde_json_lenient::to_value(schema).unwrap() } diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 28ad606132..53dc24a21a 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -15,11 +15,7 @@ use gpui::{App, AppContext as _, AsyncApp, BackgroundExecutor, SharedString, Tas use notification::DidChangeWorkspaceFolders; use parking_lot::{Mutex, RwLock}; use postage::{barrier, prelude::Stream}; -use schemars::{ - JsonSchema, - r#gen::SchemaGenerator, - schema::{InstanceType, Schema, SchemaObject}, -}; +use schemars::JsonSchema; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde_json::{Value, json, value::RawValue}; use smol::{ @@ -130,7 +126,10 @@ impl LanguageServerId { } /// A name of a language server. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] +#[derive( + Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, JsonSchema, +)] +#[serde(transparent)] pub struct LanguageServerName(pub SharedString); impl std::fmt::Display for LanguageServerName { @@ -151,20 +150,6 @@ impl AsRef for LanguageServerName { } } -impl JsonSchema for LanguageServerName { - fn schema_name() -> String { - "LanguageServerName".into() - } - - fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - ..Default::default() - } - .into() - } -} - impl LanguageServerName { pub const fn new_static(s: &'static str) -> Self { Self(SharedString::new_static(s)) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index dc402be2b6..9e1a38a6d1 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -1405,7 +1405,7 @@ impl LocalLspStore { let formatters = match (trigger, &settings.format_on_save) { (FormatTrigger::Save, FormatOnSave::Off) => &[], - (FormatTrigger::Save, FormatOnSave::List(formatters)) => formatters.as_ref(), + (FormatTrigger::Save, FormatOnSave::List(formatters)) => formatters.as_slice(), (FormatTrigger::Manual, _) | (FormatTrigger::Save, FormatOnSave::On) => { match &settings.formatter { SelectedFormatter::Auto => { @@ -1417,7 +1417,7 @@ impl LocalLspStore { std::slice::from_ref(&Formatter::LanguageServer { name: None }) } } - SelectedFormatter::List(formatter_list) => formatter_list.as_ref(), + SelectedFormatter::List(formatter_list) => formatter_list.as_slice(), } } }; diff --git a/crates/project/src/prettier_store.rs b/crates/project/src/prettier_store.rs index 29997545cd..68a3ae8778 100644 --- a/crates/project/src/prettier_store.rs +++ b/crates/project/src/prettier_store.rs @@ -705,7 +705,6 @@ pub fn prettier_plugins_for_language( SelectedFormatter::Auto => Some(&language_settings.prettier.plugins), SelectedFormatter::List(list) => list - .as_ref() .contains(&Formatter::Prettier) .then_some(&language_settings.prettier.plugins), } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 54a013bc41..3e9a2c3273 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -2023,7 +2023,7 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) { cx.update(|cx| { SettingsStore::update_global(cx, |settings, cx| { settings.update_user_settings::(cx, |settings| { - settings.languages.insert( + settings.languages.0.insert( "Rust".into(), LanguageSettingsContent { enable_language_server: Some(false), @@ -2042,14 +2042,14 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) { cx.update(|cx| { SettingsStore::update_global(cx, |settings, cx| { settings.update_user_settings::(cx, |settings| { - settings.languages.insert( + settings.languages.0.insert( LanguageName::new("Rust"), LanguageSettingsContent { enable_language_server: Some(true), ..Default::default() }, ); - settings.languages.insert( + settings.languages.0.insert( LanguageName::new("JavaScript"), LanguageSettingsContent { enable_language_server: Some(false), diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index 833882dd60..7ec34fde22 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -5,13 +5,10 @@ use gpui::{ Action, ActionBuildError, App, InvalidKeystrokeError, KEYSTROKE_PARSE_EXPECTED_MESSAGE, KeyBinding, KeyBindingContextPredicate, KeyBindingMetaIndex, NoAction, }; -use schemars::{ - JsonSchema, - r#gen::{SchemaGenerator, SchemaSettings}, - schema::{ArrayValidation, InstanceType, Schema, SchemaObject, SubschemaValidation}, -}; +use schemars::{JsonSchema, json_schema}; use serde::Deserialize; -use serde_json::Value; +use serde_json::{Value, json}; +use std::borrow::Cow; use std::{any::TypeId, fmt::Write, rc::Rc, sync::Arc, sync::LazyLock}; use util::{ asset_str, @@ -123,14 +120,14 @@ impl std::fmt::Display for KeymapAction { impl JsonSchema for KeymapAction { /// This is used when generating the JSON schema for the `KeymapAction` type, so that it can /// reference the keymap action schema. - fn schema_name() -> String { + fn schema_name() -> Cow<'static, str> { "KeymapAction".into() } /// This schema will be replaced with the full action schema in /// `KeymapFile::generate_json_schema`. - fn json_schema(_: &mut SchemaGenerator) -> Schema { - Schema::Bool(true) + fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { + json_schema!(true) } } @@ -424,9 +421,7 @@ impl KeymapFile { } pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value { - let mut generator = SchemaSettings::draft07() - .with(|settings| settings.option_add_null_type = false) - .into_generator(); + let mut generator = schemars::generate::SchemaSettings::draft07().into_generator(); let action_schemas = cx.action_schemas(&mut generator); let deprecations = cx.deprecated_actions_to_preferred_actions(); @@ -440,92 +435,70 @@ impl KeymapFile { } fn generate_json_schema( - generator: SchemaGenerator, - action_schemas: Vec<(&'static str, Option)>, + mut generator: schemars::SchemaGenerator, + action_schemas: Vec<(&'static str, Option)>, deprecations: &HashMap<&'static str, &'static str>, deprecation_messages: &HashMap<&'static str, &'static str>, ) -> serde_json::Value { - fn set(input: I) -> Option - where - I: Into, - { - Some(input.into()) - } - - fn add_deprecation(schema_object: &mut SchemaObject, message: String) { - schema_object.extensions.insert( - // deprecationMessage is not part of the JSON Schema spec, - // but json-language-server recognizes it. - "deprecationMessage".to_owned(), + fn add_deprecation(schema: &mut schemars::Schema, message: String) { + schema.insert( + // deprecationMessage is not part of the JSON Schema spec, but + // json-language-server recognizes it. + "deprecationMessage".to_string(), Value::String(message), ); } - fn add_deprecation_preferred_name(schema_object: &mut SchemaObject, new_name: &str) { - add_deprecation(schema_object, format!("Deprecated, use {new_name}")); + fn add_deprecation_preferred_name(schema: &mut schemars::Schema, new_name: &str) { + add_deprecation(schema, format!("Deprecated, use {new_name}")); } - fn add_description(schema_object: &mut SchemaObject, description: String) { - schema_object - .metadata - .get_or_insert(Default::default()) - .description = Some(description); + fn add_description(schema: &mut schemars::Schema, description: String) { + schema.insert("description".to_string(), Value::String(description)); } - let empty_object: SchemaObject = SchemaObject { - instance_type: set(InstanceType::Object), - ..Default::default() - }; + let empty_object = json_schema!({ + "type": "object" + }); // This is a workaround for a json-language-server issue where it matches the first // alternative that matches the value's shape and uses that for documentation. // // In the case of the array validations, it would even provide an error saying that the name // must match the name of the first alternative. - let mut plain_action = SchemaObject { - instance_type: set(InstanceType::String), - const_value: Some(Value::String("".to_owned())), - ..Default::default() - }; + let mut plain_action = json_schema!({ + "type": "string", + "const": "" + }); let no_action_message = "No action named this."; add_description(&mut plain_action, no_action_message.to_owned()); add_deprecation(&mut plain_action, no_action_message.to_owned()); - let mut matches_action_name = SchemaObject { - const_value: Some(Value::String("".to_owned())), - ..Default::default() - }; - let no_action_message = "No action named this that takes input."; - add_description(&mut matches_action_name, no_action_message.to_owned()); - add_deprecation(&mut matches_action_name, no_action_message.to_owned()); - let action_with_input = SchemaObject { - instance_type: set(InstanceType::Array), - array: set(ArrayValidation { - items: set(vec![ - matches_action_name.into(), - // Accept any value, as we want this to be the preferred match when there is a - // typo in the name. - Schema::Bool(true), - ]), - min_items: Some(2), - max_items: Some(2), - ..Default::default() - }), - ..Default::default() - }; - let mut keymap_action_alternatives = vec![plain_action.into(), action_with_input.into()]; + + let mut matches_action_name = json_schema!({ + "const": "" + }); + let no_action_message_input = "No action named this that takes input."; + add_description(&mut matches_action_name, no_action_message_input.to_owned()); + add_deprecation(&mut matches_action_name, no_action_message_input.to_owned()); + + let action_with_input = json_schema!({ + "type": "array", + "items": [ + matches_action_name, + true + ], + "minItems": 2, + "maxItems": 2 + }); + let mut keymap_action_alternatives = vec![plain_action, action_with_input]; for (name, action_schema) in action_schemas.into_iter() { - let schema = if let Some(Schema::Object(schema)) = action_schema { - Some(schema) - } else { - None - }; - - let description = schema.as_ref().and_then(|schema| { + let description = action_schema.as_ref().and_then(|schema| { schema - .metadata - .as_ref() - .and_then(|metadata| metadata.description.clone()) + .as_object() + .and_then(|obj| obj.get("description")) + .and_then(|v| v.as_str()) + .map(|s| s.to_string()) }); let deprecation = if name == NoAction.name() { @@ -535,84 +508,64 @@ impl KeymapFile { }; // Add an alternative for plain action names. - let mut plain_action = SchemaObject { - instance_type: set(InstanceType::String), - const_value: Some(Value::String(name.to_string())), - ..Default::default() - }; + let mut plain_action = json_schema!({ + "type": "string", + "const": name + }); if let Some(message) = deprecation_messages.get(name) { add_deprecation(&mut plain_action, message.to_string()); } else if let Some(new_name) = deprecation { add_deprecation_preferred_name(&mut plain_action, new_name); } - if let Some(description) = description.clone() { - add_description(&mut plain_action, description); + if let Some(desc) = description.clone() { + add_description(&mut plain_action, desc); } - keymap_action_alternatives.push(plain_action.into()); + keymap_action_alternatives.push(plain_action); // Add an alternative for actions with data specified as a [name, data] array. // - // When a struct with no deserializable fields is added with impl_actions! / - // impl_actions_as! an empty object schema is produced. The action should be invoked - // without data in this case. - if let Some(schema) = schema { + // When a struct with no deserializable fields is added by deriving `Action`, an empty + // object schema is produced. The action should be invoked without data in this case. + if let Some(schema) = action_schema { if schema != empty_object { - let mut matches_action_name = SchemaObject { - const_value: Some(Value::String(name.to_string())), - ..Default::default() - }; - if let Some(description) = description.clone() { - add_description(&mut matches_action_name, description); + let mut matches_action_name = json_schema!({ + "const": name + }); + if let Some(desc) = description.clone() { + add_description(&mut matches_action_name, desc); } if let Some(message) = deprecation_messages.get(name) { add_deprecation(&mut matches_action_name, message.to_string()); } else if let Some(new_name) = deprecation { add_deprecation_preferred_name(&mut matches_action_name, new_name); } - let action_with_input = SchemaObject { - instance_type: set(InstanceType::Array), - array: set(ArrayValidation { - items: set(vec![matches_action_name.into(), schema.into()]), - min_items: Some(2), - max_items: Some(2), - ..Default::default() - }), - ..Default::default() - }; - keymap_action_alternatives.push(action_with_input.into()); + let action_with_input = json_schema!({ + "type": "array", + "items": [matches_action_name, schema], + "minItems": 2, + "maxItems": 2 + }); + keymap_action_alternatives.push(action_with_input); } } } // Placing null first causes json-language-server to default assuming actions should be // null, so place it last. - keymap_action_alternatives.push( - SchemaObject { - instance_type: set(InstanceType::Null), - ..Default::default() - } - .into(), + keymap_action_alternatives.push(json_schema!({ + "type": "null" + })); + + // The `KeymapSection` schema will reference the `KeymapAction` schema by name, so setting + // the definition of `KeymapAction` results in the full action schema being used. + generator.definitions_mut().insert( + KeymapAction::schema_name().to_string(), + json!({ + "oneOf": keymap_action_alternatives + }), ); - let action_schema = SchemaObject { - subschemas: set(SubschemaValidation { - one_of: Some(keymap_action_alternatives), - ..Default::default() - }), - ..Default::default() - } - .into(); - - // The `KeymapSection` schema will reference the `KeymapAction` schema by name, so replacing - // the definition of `KeymapAction` results in the full action schema being used. - let mut root_schema = generator.into_root_schema_for::(); - root_schema - .definitions - .insert(KeymapAction::schema_name(), action_schema); - - // This and other json schemas can be viewed via `dev: open language server logs` -> - // `json-language-server` -> `Server Info`. - serde_json::to_value(root_schema).unwrap() + generator.root_schema_for::().to_value() } pub fn sections(&self) -> impl DoubleEndedIterator { diff --git a/crates/settings/src/settings_json.rs b/crates/settings/src/settings_json.rs index 1a045607e6..e64d8efee0 100644 --- a/crates/settings/src/settings_json.rs +++ b/crates/settings/src/settings_json.rs @@ -1,11 +1,9 @@ -use std::{ops::Range, sync::LazyLock}; - use anyhow::Result; -use schemars::schema::{ - ArrayValidation, InstanceType, RootSchema, Schema, SchemaObject, SingleOrVec, -}; +use gpui::App; +use schemars::{JsonSchema, Schema}; use serde::{Serialize, de::DeserializeOwned}; use serde_json::Value; +use std::{ops::Range, sync::LazyLock}; use tree_sitter::{Query, StreamingIterator as _}; use util::RangeExt; @@ -14,70 +12,43 @@ pub struct SettingsJsonSchemaParams<'a> { pub font_names: &'a [String], } -impl SettingsJsonSchemaParams<'_> { - pub fn font_family_schema(&self) -> Schema { - let available_fonts: Vec<_> = self.font_names.iter().cloned().map(Value::String).collect(); - - SchemaObject { - instance_type: Some(InstanceType::String.into()), - enum_values: Some(available_fonts), - ..Default::default() - } - .into() - } - - pub fn font_fallback_schema(&self) -> Schema { - SchemaObject { - instance_type: Some(SingleOrVec::Vec(vec![ - InstanceType::Array, - InstanceType::Null, - ])), - array: Some(Box::new(ArrayValidation { - items: Some(schemars::schema::SingleOrVec::Single(Box::new( - self.font_family_schema(), - ))), - unique_items: Some(true), - ..Default::default() - })), - ..Default::default() - } - .into() - } +pub struct ParameterizedJsonSchema { + pub add_and_get_ref: + fn(&mut schemars::SchemaGenerator, &SettingsJsonSchemaParams, &App) -> schemars::Schema, } -type PropertyName<'a> = &'a str; -type ReferencePath<'a> = &'a str; +inventory::collect!(ParameterizedJsonSchema); -/// Modifies the provided [`RootSchema`] by adding references to all of the specified properties. -/// -/// # Examples -/// -/// ``` -/// # let root_schema = RootSchema::default(); -/// add_references_to_properties(&mut root_schema, &[ -/// ("property_a", "#/definitions/DefinitionA"), -/// ("property_b", "#/definitions/DefinitionB"), -/// ]) -/// ``` -pub fn add_references_to_properties( - root_schema: &mut RootSchema, - properties_with_references: &[(PropertyName, ReferencePath)], -) { - for (property, definition) in properties_with_references { - let Some(schema) = root_schema.schema.object().properties.get_mut(*property) else { - log::warn!("property '{property}' not found in JSON schema"); - continue; - }; - - match schema { - Schema::Object(schema) => { - schema.reference = Some(definition.to_string()); - } - Schema::Bool(_) => { - // Boolean schemas can't have references. - } +pub fn replace_subschema( + generator: &mut schemars::SchemaGenerator, + schema: schemars::Schema, +) -> schemars::Schema { + const DEFINITIONS_PATH: &str = "#/definitions/"; + // The key in definitions may not match T::schema_name() if multiple types have the same name. + // This is a workaround for there being no straightforward way to get the key used for a type - + // see https://github.com/GREsau/schemars/issues/449 + let ref_schema = generator.subschema_for::(); + if let Some(serde_json::Value::String(definition_pointer)) = ref_schema.get("$ref") { + if let Some(definition_name) = definition_pointer.strip_prefix(DEFINITIONS_PATH) { + generator + .definitions_mut() + .insert(definition_name.to_string(), schema.to_value()); + return ref_schema; + } else { + log::error!( + "bug: expected `$ref` field to start with {DEFINITIONS_PATH}, \ + got {definition_pointer}" + ); } + } else { + log::error!("bug: expected `$ref` field in result of `subschema_for`"); } + // fallback on just using the schema name, which could collide. + let schema_name = T::schema_name(); + generator + .definitions_mut() + .insert(schema_name.to_string(), schema.to_value()); + Schema::new_ref(format!("{DEFINITIONS_PATH}{schema_name}")) } pub fn update_value_in_json_text<'a>( diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index c4cf97bd6c..bdc2320508 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -6,7 +6,7 @@ use futures::{FutureExt, StreamExt, channel::mpsc, future::LocalBoxFuture}; use gpui::{App, AsyncApp, BorrowAppContext, Global, Task, UpdateGlobal}; use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name}; -use schemars::{JsonSchema, r#gen::SchemaGenerator, schema::RootSchema}; +use schemars::{JsonSchema, json_schema}; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde_json::{Value, json}; use smallvec::SmallVec; @@ -24,8 +24,8 @@ use util::{ResultExt as _, merge_non_null_json_value_into}; pub type EditorconfigProperties = ec4rs::Properties; use crate::{ - SettingsJsonSchemaParams, VsCodeSettings, WorktreeId, parse_json_with_comments, - update_value_in_json_text, + ParameterizedJsonSchema, SettingsJsonSchemaParams, VsCodeSettings, WorktreeId, + parse_json_with_comments, update_value_in_json_text, }; /// A value that can be defined as a user setting. @@ -57,14 +57,6 @@ pub trait Settings: 'static + Send + Sync { where Self: Sized; - fn json_schema( - generator: &mut SchemaGenerator, - _: &SettingsJsonSchemaParams, - _: &App, - ) -> RootSchema { - generator.root_schema_for::() - } - fn missing_default() -> anyhow::Error { anyhow::anyhow!("missing default") } @@ -253,12 +245,7 @@ trait AnySettingValue: 'static + Send + Sync { fn all_local_values(&self) -> Vec<(WorktreeId, Arc, &dyn Any)>; fn set_global_value(&mut self, value: Box); fn set_local_value(&mut self, root_id: WorktreeId, path: Arc, value: Box); - fn json_schema( - &self, - generator: &mut SchemaGenerator, - _: &SettingsJsonSchemaParams, - cx: &App, - ) -> RootSchema; + fn json_schema(&self, generator: &mut schemars::SchemaGenerator) -> schemars::Schema; fn edits_for_update( &self, raw_settings: &serde_json::Value, @@ -276,11 +263,11 @@ impl SettingsStore { let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded(); Self { setting_values: Default::default(), - raw_default_settings: serde_json::json!({}), + raw_default_settings: json!({}), raw_global_settings: None, - raw_user_settings: serde_json::json!({}), + raw_user_settings: json!({}), raw_server_settings: None, - raw_extension_settings: serde_json::json!({}), + raw_extension_settings: json!({}), raw_local_settings: Default::default(), raw_editorconfig_settings: BTreeMap::default(), tab_size_callback: Default::default(), @@ -877,108 +864,151 @@ impl SettingsStore { } pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams, cx: &App) -> Value { - use schemars::{ - r#gen::SchemaSettings, - schema::{Schema, SchemaObject}, - }; - - let settings = SchemaSettings::draft07().with(|settings| { - settings.option_add_null_type = true; + let mut generator = schemars::generate::SchemaSettings::draft07().into_generator(); + let mut combined_schema = json!({ + "type": "object", + "properties": {} }); - let mut generator = SchemaGenerator::new(settings); - let mut combined_schema = RootSchema::default(); + // Merge together settings schemas, similarly to json schema's "allOf". This merging is + // recursive, though at time of writing this recursive nature isn't used very much. An + // example of it is the schema for `jupyter` having contribution from both `EditorSettings` + // and `JupyterSettings`. + // + // This logic could be removed in favor of "allOf", but then there isn't the opportunity to + // validate and fully control the merge. for setting_value in self.setting_values.values() { - let setting_schema = setting_value.json_schema(&mut generator, schema_params, cx); - combined_schema - .definitions - .extend(setting_schema.definitions); + let mut setting_schema = setting_value.json_schema(&mut generator); - 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, mut source: SchemaObject) { - let source_subschemas = source.subschemas(); - let target_subschemas = target.subschemas(); - if let Some(all_of) = source_subschemas.all_of.take() { - target_subschemas - .all_of - .get_or_insert(Vec::new()) - .extend(all_of); - } - if let Some(any_of) = source_subschemas.any_of.take() { - target_subschemas - .any_of - .get_or_insert(Vec::new()) - .extend(any_of); - } - if let Some(one_of) = source_subschemas.one_of.take() { - target_subschemas - .one_of - .get_or_insert(Vec::new()) - .extend(one_of); - } - - 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); - } + if let Some(key) = setting_value.key() { + if let Some(properties) = combined_schema.get_mut("properties") { + if let Some(properties_obj) = properties.as_object_mut() { + if let Some(target) = properties_obj.get_mut(key) { + merge_schema(target, setting_schema.to_value()); + } else { + properties_obj.insert(key.to_string(), setting_schema.to_value()); } } } + } else { + setting_schema.remove("description"); + setting_schema.remove("additionalProperties"); + merge_schema(&mut combined_schema, setting_schema.to_value()); } + } - 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 merge_schema(target: &mut serde_json::Value, source: serde_json::Value) { + let (Some(target_obj), serde_json::Value::Object(source_obj)) = + (target.as_object_mut(), source) + else { + return; + }; - fn overwrite(target: &mut Option, source: Option) { - if let Some(source) = source { - *target = Some(source); + for (source_key, source_value) in source_obj { + match source_key.as_str() { + "properties" => { + let serde_json::Value::Object(source_properties) = source_value else { + log::error!( + "bug: expected object for `{}` json schema field, but got: {}", + source_key, + source_value + ); + continue; + }; + let target_properties = + target_obj.entry(source_key.clone()).or_insert(json!({})); + let Some(target_properties) = target_properties.as_object_mut() else { + log::error!( + "bug: expected object for `{}` json schema field, but got: {}", + source_key, + target_properties + ); + continue; + }; + for (key, value) in source_properties { + if let Some(existing) = target_properties.get_mut(&key) { + merge_schema(existing, value); + } else { + target_properties.insert(key, value); + } + } + } + "allOf" | "anyOf" | "oneOf" => { + let serde_json::Value::Array(source_array) = source_value else { + log::error!( + "bug: expected array for `{}` json schema field, but got: {}", + source_key, + source_value, + ); + continue; + }; + let target_array = + target_obj.entry(source_key.clone()).or_insert(json!([])); + let Some(target_array) = target_array.as_array_mut() else { + log::error!( + "bug: expected array for `{}` json schema field, but got: {}", + source_key, + target_array, + ); + continue; + }; + target_array.extend(source_array); + } + "type" + | "$ref" + | "enum" + | "minimum" + | "maximum" + | "pattern" + | "description" + | "additionalProperties" => { + if let Some(old_value) = + target_obj.insert(source_key.clone(), source_value.clone()) + { + if old_value != source_value { + log::error!( + "bug: while merging JSON schemas, \ + mismatch `\"{}\": {}` (before was `{}`)", + source_key, + old_value, + source_value + ); + } + } + } + _ => { + log::error!( + "bug: while merging settings JSON schemas, \ + encountered unexpected `\"{}\": {}`", + source_key, + source_value + ); + } } } } + for parameterized_json_schema in inventory::iter::() { + (parameterized_json_schema.add_and_get_ref)(&mut generator, schema_params, cx); + } + const ZED_SETTINGS: &str = "ZedSettings"; - let RootSchema { - meta_schema, - schema: zed_settings_schema, - mut definitions, - } = combined_schema; - definitions.insert(ZED_SETTINGS.to_string(), zed_settings_schema.into()); - let zed_settings_ref = Schema::new_ref(format!("#/definitions/{ZED_SETTINGS}")); + let old_zed_settings_definition = generator + .definitions_mut() + .insert(ZED_SETTINGS.to_string(), combined_schema); + assert_eq!(old_zed_settings_definition, None); + let zed_settings_ref = schemars::Schema::new_ref(format!("#/definitions/{ZED_SETTINGS}")); + + let mut root_schema = if let Some(meta_schema) = generator.settings().meta_schema.as_ref() { + json_schema!({ "$schema": meta_schema.to_string() }) + } else { + json_schema!({}) + }; // settings file contents matches ZedSettings + overrides for each release stage - let mut root_schema = json!({ - "allOf": [ + root_schema.insert( + "allOf".to_string(), + json!([ zed_settings_ref, { "properties": { @@ -988,17 +1018,14 @@ impl SettingsStore { "preview": zed_settings_ref, } } - ], - "definitions": definitions, - }); + ]), + ); + root_schema.insert( + "definitions".to_string(), + generator.take_definitions(true).into(), + ); - if let Some(meta_schema) = meta_schema { - if let Some(root_schema_object) = root_schema.as_object_mut() { - root_schema_object.insert("$schema".to_string(), meta_schema.into()); - } - } - - root_schema + root_schema.to_value() } fn recompute_values( @@ -1311,13 +1338,8 @@ impl AnySettingValue for SettingValue { } } - fn json_schema( - &self, - generator: &mut SchemaGenerator, - params: &SettingsJsonSchemaParams, - cx: &App, - ) -> RootSchema { - T::json_schema(generator, params, cx) + fn json_schema(&self, generator: &mut schemars::SchemaGenerator) -> schemars::Schema { + T::FileContent::json_schema(generator) } fn edits_for_update( diff --git a/crates/settings_ui/src/appearance_settings_controls.rs b/crates/settings_ui/src/appearance_settings_controls.rs index fa7e31c5cd..141ae13182 100644 --- a/crates/settings_ui/src/appearance_settings_controls.rs +++ b/crates/settings_ui/src/appearance_settings_controls.rs @@ -2,7 +2,9 @@ use std::sync::Arc; use gpui::{App, FontFeatures, FontWeight}; use settings::{EditableSettingControl, Settings}; -use theme::{FontFamilyCache, SystemAppearance, ThemeMode, ThemeRegistry, ThemeSettings}; +use theme::{ + FontFamilyCache, FontFamilyName, SystemAppearance, ThemeMode, ThemeRegistry, ThemeSettings, +}; use ui::{ CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup, ToggleButton, prelude::*, @@ -189,7 +191,7 @@ impl EditableSettingControl for UiFontFamilyControl { value: Self::Value, _cx: &App, ) { - settings.ui_font_family = Some(value.to_string()); + settings.ui_font_family = Some(FontFamilyName(value.into())); } } diff --git a/crates/snippet_provider/src/format.rs b/crates/snippet_provider/src/format.rs index 84bae238b4..7aa02c7db5 100644 --- a/crates/snippet_provider/src/format.rs +++ b/crates/snippet_provider/src/format.rs @@ -1,11 +1,8 @@ use collections::HashMap; -use schemars::{ - JsonSchema, - r#gen::SchemaSettings, - schema::{ObjectValidation, Schema, SchemaObject}, -}; +use schemars::{JsonSchema, json_schema}; use serde::Deserialize; use serde_json_lenient::Value; +use std::borrow::Cow; #[derive(Deserialize)] pub struct VsSnippetsFile { @@ -15,29 +12,25 @@ pub struct VsSnippetsFile { impl VsSnippetsFile { pub fn generate_json_schema() -> Value { - let schema = SchemaSettings::draft07() - .with(|settings| settings.option_add_null_type = false) + let schema = schemars::generate::SchemaSettings::draft07() .into_generator() - .into_root_schema_for::(); + .root_schema_for::(); serde_json_lenient::to_value(schema).unwrap() } } impl JsonSchema for VsSnippetsFile { - fn schema_name() -> String { + fn schema_name() -> Cow<'static, str> { "VsSnippetsFile".into() } - fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> Schema { - SchemaObject { - object: Some(Box::new(ObjectValidation { - additional_properties: Some(Box::new(r#gen.subschema_for::())), - ..Default::default() - })), - ..Default::default() - } - .into() + fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema { + let snippet_schema = generator.subschema_for::(); + json_schema!({ + "type": "object", + "additionalProperties": snippet_schema + }) } } diff --git a/crates/task/src/debug_format.rs b/crates/task/src/debug_format.rs index 0d9733ebff..81941e0074 100644 --- a/crates/task/src/debug_format.rs +++ b/crates/task/src/debug_format.rs @@ -287,7 +287,8 @@ pub struct DebugTaskFile(pub Vec); impl DebugTaskFile { pub fn generate_json_schema(schemas: &AdapterSchemas) -> serde_json_lenient::Value { - let build_task_schema = schemars::schema_for!(BuildTaskDefinition); + let mut generator = schemars::generate::SchemaSettings::draft07().into_generator(); + let build_task_schema = generator.root_schema_for::(); let mut build_task_value = serde_json_lenient::to_value(&build_task_schema).unwrap_or_default(); diff --git a/crates/task/src/serde_helpers.rs b/crates/task/src/serde_helpers.rs index d7af919fbf..a95214d8b0 100644 --- a/crates/task/src/serde_helpers.rs +++ b/crates/task/src/serde_helpers.rs @@ -1,33 +1,6 @@ -use schemars::{ - SchemaGenerator, - schema::{ArrayValidation, InstanceType, Schema, SchemaObject, SingleOrVec, StringValidation}, -}; use serde::de::{self, Deserializer, Visitor}; use std::fmt; -/// Generates a JSON schema for a non-empty string array. -pub fn non_empty_string_vec_json_schema(_: &mut SchemaGenerator) -> Schema { - Schema::Object(SchemaObject { - instance_type: Some(InstanceType::Array.into()), - array: Some(Box::new(ArrayValidation { - unique_items: Some(true), - items: Some(SingleOrVec::Single(Box::new(Schema::Object( - SchemaObject { - instance_type: Some(InstanceType::String.into()), - string: Some(Box::new(StringValidation { - min_length: Some(1), // Ensures string in the array is non-empty - ..Default::default() - })), - ..Default::default() - }, - )))), - ..Default::default() - })), - format: Some("vec-of-non-empty-strings".to_string()), // Use a custom format keyword - ..Default::default() - }) -} - /// Deserializes a non-empty string array. pub fn non_empty_string_vec<'de, D>(deserializer: D) -> Result, D::Error> where diff --git a/crates/task/src/task_template.rs b/crates/task/src/task_template.rs index 02310bb1b0..4ff45cad9e 100644 --- a/crates/task/src/task_template.rs +++ b/crates/task/src/task_template.rs @@ -1,6 +1,6 @@ use anyhow::{Context as _, bail}; use collections::{HashMap, HashSet}; -use schemars::{JsonSchema, r#gen::SchemaSettings}; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::path::PathBuf; @@ -9,8 +9,7 @@ use util::{ResultExt, truncate_and_remove_front}; use crate::{ AttachRequest, ResolvedTask, RevealTarget, Shell, SpawnInTerminal, TaskContext, TaskId, - VariableName, ZED_VARIABLE_NAME_PREFIX, - serde_helpers::{non_empty_string_vec, non_empty_string_vec_json_schema}, + VariableName, ZED_VARIABLE_NAME_PREFIX, serde_helpers::non_empty_string_vec, }; /// A template definition of a Zed task to run. @@ -61,7 +60,7 @@ pub struct TaskTemplate { /// Represents the tags which this template attaches to. /// Adding this removes this task from other UI and gives you ability to run it by tag. #[serde(default, deserialize_with = "non_empty_string_vec")] - #[schemars(schema_with = "non_empty_string_vec_json_schema")] + #[schemars(length(min = 1))] pub tags: Vec, /// Which shell to use when spawning the task. #[serde(default)] @@ -116,10 +115,9 @@ pub struct TaskTemplates(pub Vec); impl TaskTemplates { /// Generates JSON schema of Tasks JSON template format. pub fn generate_json_schema() -> serde_json_lenient::Value { - let schema = SchemaSettings::draft07() - .with(|settings| settings.option_add_null_type = false) + let schema = schemars::generate::SchemaSettings::draft07() .into_generator() - .into_root_schema_for::(); + .root_schema_for::(); serde_json_lenient::to_value(schema).unwrap() } diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index bd93b7e0a6..d588d3680b 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -2,14 +2,14 @@ use alacritty_terminal::vte::ansi::{ CursorShape as AlacCursorShape, CursorStyle as AlacCursorStyle, }; use collections::HashMap; -use gpui::{ - AbsoluteLength, App, FontFallbacks, FontFeatures, FontWeight, Pixels, SharedString, px, -}; -use schemars::{JsonSchema, r#gen::SchemaGenerator, schema::RootSchema}; +use gpui::{AbsoluteLength, App, FontFallbacks, FontFeatures, FontWeight, Pixels, px}; +use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; -use settings::{SettingsJsonSchemaParams, SettingsSources, add_references_to_properties}; + +use settings::SettingsSources; use std::path::PathBuf; use task::Shell; +use theme::FontFamilyName; #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] @@ -29,7 +29,7 @@ pub struct TerminalSettings { pub shell: Shell, pub working_directory: WorkingDirectory, pub font_size: Option, - pub font_family: Option, + pub font_family: Option, pub font_fallbacks: Option, pub font_features: Option, pub font_weight: Option, @@ -147,13 +147,14 @@ pub struct TerminalSettingsContent { /// /// If this option is not included, /// the terminal will default to matching the buffer's font family. - pub font_family: Option, + pub font_family: Option, /// Sets the terminal's font fallbacks. /// /// If this option is not included, /// the terminal will default to matching the buffer's font fallbacks. - pub font_fallbacks: Option>, + #[schemars(extend("uniqueItems" = true))] + pub font_fallbacks: Option>, /// Sets the terminal's line height. /// @@ -234,33 +235,13 @@ impl settings::Settings for TerminalSettings { sources.json_merge() } - fn json_schema( - generator: &mut SchemaGenerator, - params: &SettingsJsonSchemaParams, - _: &App, - ) -> RootSchema { - let mut root_schema = generator.root_schema_for::(); - root_schema.definitions.extend([ - ("FontFamilies".into(), params.font_family_schema()), - ("FontFallbacks".into(), params.font_fallback_schema()), - ]); - - add_references_to_properties( - &mut root_schema, - &[ - ("font_family", "#/definitions/FontFamilies"), - ("font_fallbacks", "#/definitions/FontFallbacks"), - ], - ); - - root_schema - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { let name = |s| format!("terminal.integrated.{s}"); vscode.f32_setting(&name("fontSize"), &mut current.font_size); - vscode.string_setting(&name("fontFamily"), &mut current.font_family); + if let Some(font_family) = vscode.read_string(&name("fontFamily")) { + current.font_family = Some(FontFamilyName(font_family.into())); + } vscode.bool_setting(&name("copyOnSelection"), &mut current.copy_on_select); vscode.bool_setting("macOptionIsMeta", &mut current.option_as_meta); vscode.usize_setting("scrollback", &mut current.max_scroll_history_lines); diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index c0671048f6..e85032b460 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -682,11 +682,10 @@ impl Element for TerminalElement { let terminal_settings = TerminalSettings::get_global(cx); - let font_family = terminal_settings - .font_family - .as_ref() - .unwrap_or(&settings.buffer_font.family) - .clone(); + let font_family = terminal_settings.font_family.as_ref().map_or_else( + || settings.buffer_font.family.clone(), + |font_family| font_family.0.clone().into(), + ); let font_fallbacks = terminal_settings .font_fallbacks diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index 43d720b556..998d31bb3c 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -24,6 +24,7 @@ fs.workspace = true futures.workspace = true gpui.workspace = true indexmap.workspace = true +inventory.workspace = true log.workspace = true palette = { workspace = true, default-features = false, features = ["std"] } parking_lot.workspace = true diff --git a/crates/theme/src/schema.rs b/crates/theme/src/schema.rs index 01fdafd94d..b2a13b54b6 100644 --- a/crates/theme/src/schema.rs +++ b/crates/theme/src/schema.rs @@ -4,12 +4,11 @@ use anyhow::Result; use gpui::{FontStyle, FontWeight, HighlightStyle, Hsla, WindowBackgroundAppearance}; use indexmap::IndexMap; use palette::FromColor; -use schemars::JsonSchema; -use schemars::r#gen::SchemaGenerator; -use schemars::schema::{Schema, SchemaObject}; +use schemars::{JsonSchema, json_schema}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use serde_repr::{Deserialize_repr, Serialize_repr}; +use std::borrow::Cow; use crate::{StatusColorsRefinement, ThemeColorsRefinement}; @@ -1502,30 +1501,15 @@ pub enum FontWeightContent { } impl JsonSchema for FontWeightContent { - fn schema_name() -> String { - "FontWeightContent".to_owned() + fn schema_name() -> Cow<'static, str> { + "FontWeightContent".into() } - fn is_referenceable() -> bool { - false - } - - fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - enum_values: Some(vec![ - 100.into(), - 200.into(), - 300.into(), - 400.into(), - 500.into(), - 600.into(), - 700.into(), - 800.into(), - 900.into(), - ]), - ..Default::default() - } - .into() + fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { + json_schema!({ + "type": "integer", + "enum": [100, 200, 300, 400, 500, 600, 700, 800, 900] + }) } } diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index eedee05592..42012e080c 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -7,17 +7,12 @@ use anyhow::Result; use derive_more::{Deref, DerefMut}; use gpui::{ App, Context, Font, FontFallbacks, FontFeatures, FontStyle, FontWeight, Global, Pixels, - Subscription, Window, px, + SharedString, Subscription, Window, px, }; use refineable::Refineable; -use schemars::{ - JsonSchema, - r#gen::SchemaGenerator, - schema::{InstanceType, Schema, SchemaObject}, -}; +use schemars::{JsonSchema, json_schema}; use serde::{Deserialize, Serialize}; -use serde_json::Value; -use settings::{Settings, SettingsJsonSchemaParams, SettingsSources, add_references_to_properties}; +use settings::{ParameterizedJsonSchema, Settings, SettingsSources, replace_subschema}; use std::sync::Arc; use util::ResultExt as _; @@ -263,25 +258,19 @@ impl Global for AgentFontSize {} #[serde(untagged)] pub enum ThemeSelection { /// A static theme selection, represented by a single theme name. - Static(#[schemars(schema_with = "theme_name_ref")] String), + Static(ThemeName), /// A dynamic theme selection, which can change based the [ThemeMode]. Dynamic { /// The mode used to determine which theme to use. #[serde(default)] mode: ThemeMode, /// The theme to use for light mode. - #[schemars(schema_with = "theme_name_ref")] - light: String, + light: ThemeName, /// The theme to use for dark mode. - #[schemars(schema_with = "theme_name_ref")] - dark: String, + dark: ThemeName, }, } -fn theme_name_ref(_: &mut SchemaGenerator) -> Schema { - Schema::new_ref("#/definitions/ThemeName".into()) -} - // TODO: Rename ThemeMode -> ThemeAppearanceMode /// The mode use to select a theme. /// @@ -306,13 +295,13 @@ impl ThemeSelection { /// Returns the theme name for the selected [ThemeMode]. pub fn theme(&self, system_appearance: Appearance) -> &str { match self { - Self::Static(theme) => theme, + Self::Static(theme) => &theme.0, Self::Dynamic { mode, light, dark } => match mode { - ThemeMode::Light => light, - ThemeMode::Dark => dark, + ThemeMode::Light => &light.0, + ThemeMode::Dark => &dark.0, ThemeMode::System => match system_appearance { - Appearance::Light => light, - Appearance::Dark => dark, + Appearance::Light => &light.0, + Appearance::Dark => &dark.0, }, }, } @@ -327,27 +316,21 @@ impl ThemeSelection { } } -fn icon_theme_name_ref(_: &mut SchemaGenerator) -> Schema { - Schema::new_ref("#/definitions/IconThemeName".into()) -} - /// Represents the selection of an icon theme, which can be either static or dynamic. #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(untagged)] pub enum IconThemeSelection { /// A static icon theme selection, represented by a single icon theme name. - Static(#[schemars(schema_with = "icon_theme_name_ref")] String), + Static(IconThemeName), /// A dynamic icon theme selection, which can change based on the [`ThemeMode`]. Dynamic { /// The mode used to determine which theme to use. #[serde(default)] mode: ThemeMode, /// The icon theme to use for light mode. - #[schemars(schema_with = "icon_theme_name_ref")] - light: String, + light: IconThemeName, /// The icon theme to use for dark mode. - #[schemars(schema_with = "icon_theme_name_ref")] - dark: String, + dark: IconThemeName, }, } @@ -355,13 +338,13 @@ impl IconThemeSelection { /// Returns the icon theme name based on the given [`Appearance`]. pub fn icon_theme(&self, system_appearance: Appearance) -> &str { match self { - Self::Static(theme) => theme, + Self::Static(theme) => &theme.0, Self::Dynamic { mode, light, dark } => match mode { - ThemeMode::Light => light, - ThemeMode::Dark => dark, + ThemeMode::Light => &light.0, + ThemeMode::Dark => &dark.0, ThemeMode::System => match system_appearance { - Appearance::Light => light, - Appearance::Dark => dark, + Appearance::Light => &light.0, + Appearance::Dark => &dark.0, }, }, } @@ -384,11 +367,12 @@ pub struct ThemeSettingsContent { pub ui_font_size: Option, /// The name of a font to use for rendering in the UI. #[serde(default)] - pub ui_font_family: Option, + pub ui_font_family: Option, /// The font fallbacks to use for rendering in the UI. #[serde(default)] #[schemars(default = "default_font_fallbacks")] - pub ui_font_fallbacks: Option>, + #[schemars(extend("uniqueItems" = true))] + pub ui_font_fallbacks: Option>, /// The OpenType features to enable for text in the UI. #[serde(default)] #[schemars(default = "default_font_features")] @@ -398,11 +382,11 @@ pub struct ThemeSettingsContent { pub ui_font_weight: Option, /// The name of a font to use for rendering in text buffers. #[serde(default)] - pub buffer_font_family: Option, + pub buffer_font_family: Option, /// The font fallbacks to use for rendering in text buffers. #[serde(default)] - #[schemars(default = "default_font_fallbacks")] - pub buffer_font_fallbacks: Option>, + #[schemars(extend("uniqueItems" = true))] + pub buffer_font_fallbacks: Option>, /// The default font size for rendering in text buffers. #[serde(default)] pub buffer_font_size: Option, @@ -467,9 +451,9 @@ impl ThemeSettingsContent { }, }; - *theme_to_update = theme_name.to_string(); + *theme_to_update = ThemeName(theme_name.into()); } else { - self.theme = Some(ThemeSelection::Static(theme_name.to_string())); + self.theme = Some(ThemeSelection::Static(ThemeName(theme_name.into()))); } } @@ -488,9 +472,11 @@ impl ThemeSettingsContent { }, }; - *icon_theme_to_update = icon_theme_name.to_string(); + *icon_theme_to_update = IconThemeName(icon_theme_name.into()); } else { - self.icon_theme = Some(IconThemeSelection::Static(icon_theme_name.to_string())); + self.icon_theme = Some(IconThemeSelection::Static(IconThemeName( + icon_theme_name.into(), + ))); } } @@ -516,8 +502,8 @@ impl ThemeSettingsContent { } else { self.theme = Some(ThemeSelection::Dynamic { mode, - light: ThemeSettings::DEFAULT_LIGHT_THEME.into(), - dark: ThemeSettings::DEFAULT_DARK_THEME.into(), + light: ThemeName(ThemeSettings::DEFAULT_LIGHT_THEME.into()), + dark: ThemeName(ThemeSettings::DEFAULT_DARK_THEME.into()), }); } @@ -539,7 +525,9 @@ impl ThemeSettingsContent { } => *mode_to_update = mode, } } else { - self.icon_theme = Some(IconThemeSelection::Static(DEFAULT_ICON_THEME_NAME.into())); + self.icon_theme = Some(IconThemeSelection::Static(IconThemeName( + DEFAULT_ICON_THEME_NAME.into(), + ))); } } } @@ -815,26 +803,39 @@ impl settings::Settings for ThemeSettings { let themes = ThemeRegistry::default_global(cx); let system_appearance = SystemAppearance::default_global(cx); + fn font_fallbacks_from_settings( + fallbacks: Option>, + ) -> Option { + fallbacks.map(|fallbacks| { + FontFallbacks::from_fonts( + fallbacks + .into_iter() + .map(|font_family| font_family.0.to_string()) + .collect(), + ) + }) + } + let defaults = sources.default; let mut this = Self { ui_font_size: defaults.ui_font_size.unwrap().into(), ui_font: Font { - family: defaults.ui_font_family.as_ref().unwrap().clone().into(), + family: defaults.ui_font_family.as_ref().unwrap().0.clone().into(), features: defaults.ui_font_features.clone().unwrap(), - fallbacks: defaults - .ui_font_fallbacks - .as_ref() - .map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())), + fallbacks: font_fallbacks_from_settings(defaults.ui_font_fallbacks.clone()), weight: defaults.ui_font_weight.map(FontWeight).unwrap(), style: Default::default(), }, buffer_font: Font { - family: defaults.buffer_font_family.as_ref().unwrap().clone().into(), - features: defaults.buffer_font_features.clone().unwrap(), - fallbacks: defaults - .buffer_font_fallbacks + family: defaults + .buffer_font_family .as_ref() - .map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())), + .unwrap() + .0 + .clone() + .into(), + features: defaults.buffer_font_features.clone().unwrap(), + fallbacks: font_fallbacks_from_settings(defaults.buffer_font_fallbacks.clone()), weight: defaults.buffer_font_weight.map(FontWeight).unwrap(), style: FontStyle::default(), }, @@ -872,26 +873,26 @@ impl settings::Settings for ThemeSettings { } if let Some(value) = value.buffer_font_family.clone() { - this.buffer_font.family = value.into(); + this.buffer_font.family = value.0.into(); } if let Some(value) = value.buffer_font_features.clone() { this.buffer_font.features = value; } if let Some(value) = value.buffer_font_fallbacks.clone() { - this.buffer_font.fallbacks = Some(FontFallbacks::from_fonts(value)); + this.buffer_font.fallbacks = font_fallbacks_from_settings(Some(value)); } if let Some(value) = value.buffer_font_weight { this.buffer_font.weight = clamp_font_weight(value); } if let Some(value) = value.ui_font_family.clone() { - this.ui_font.family = value.into(); + this.ui_font.family = value.0.into(); } if let Some(value) = value.ui_font_features.clone() { this.ui_font.features = value; } if let Some(value) = value.ui_font_fallbacks.clone() { - this.ui_font.fallbacks = Some(FontFallbacks::from_fonts(value)); + this.ui_font.fallbacks = font_fallbacks_from_settings(Some(value)); } if let Some(value) = value.ui_font_weight { this.ui_font.weight = clamp_font_weight(value); @@ -959,64 +960,73 @@ impl settings::Settings for ThemeSettings { Ok(this) } - fn json_schema( - generator: &mut SchemaGenerator, - params: &SettingsJsonSchemaParams, - cx: &App, - ) -> schemars::schema::RootSchema { - let mut root_schema = generator.root_schema_for::(); - let theme_names = ThemeRegistry::global(cx) - .list_names() - .into_iter() - .map(|theme_name| Value::String(theme_name.to_string())) - .collect(); - - let theme_name_schema = SchemaObject { - instance_type: Some(InstanceType::String.into()), - enum_values: Some(theme_names), - ..Default::default() - }; - - let icon_theme_names = ThemeRegistry::global(cx) - .list_icon_themes() - .into_iter() - .map(|icon_theme| Value::String(icon_theme.name.to_string())) - .collect(); - - let icon_theme_name_schema = SchemaObject { - instance_type: Some(InstanceType::String.into()), - enum_values: Some(icon_theme_names), - ..Default::default() - }; - - root_schema.definitions.extend([ - ("ThemeName".into(), theme_name_schema.into()), - ("IconThemeName".into(), icon_theme_name_schema.into()), - ("FontFamilies".into(), params.font_family_schema()), - ("FontFallbacks".into(), params.font_fallback_schema()), - ]); - - add_references_to_properties( - &mut root_schema, - &[ - ("buffer_font_family", "#/definitions/FontFamilies"), - ("buffer_font_fallbacks", "#/definitions/FontFallbacks"), - ("ui_font_family", "#/definitions/FontFamilies"), - ("ui_font_fallbacks", "#/definitions/FontFallbacks"), - ], - ); - - root_schema - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { vscode.f32_setting("editor.fontWeight", &mut current.buffer_font_weight); vscode.f32_setting("editor.fontSize", &mut current.buffer_font_size); - vscode.string_setting("editor.font", &mut current.buffer_font_family); + if let Some(font) = vscode.read_string("editor.font") { + current.buffer_font_family = Some(FontFamilyName(font.into())); + } // TODO: possibly map editor.fontLigatures to buffer_font_features? } } +/// Newtype for a theme name. Its `ParameterizedJsonSchema` lists the theme names known at runtime. +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(transparent)] +pub struct ThemeName(pub Arc); + +inventory::submit! { + ParameterizedJsonSchema { + add_and_get_ref: |generator, _params, cx| { + let schema = json_schema!({ + "type": "string", + "enum": ThemeRegistry::global(cx).list_names(), + }); + replace_subschema::(generator, schema) + } + } +} + +/// Newtype for a icon theme name. Its `ParameterizedJsonSchema` lists the icon theme names known at +/// runtime. +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(transparent)] +pub struct IconThemeName(pub Arc); + +inventory::submit! { + ParameterizedJsonSchema { + add_and_get_ref: |generator, _params, cx| { + let schema = json_schema!({ + "type": "string", + "enum": ThemeRegistry::global(cx) + .list_icon_themes() + .into_iter() + .map(|icon_theme| icon_theme.name) + .collect::>(), + }); + replace_subschema::(generator, schema) + } + } +} + +/// Newtype for font family name. Its `ParameterizedJsonSchema` lists the font families known at +/// runtime. +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(transparent)] +pub struct FontFamilyName(pub Arc); + +inventory::submit! { + ParameterizedJsonSchema { + add_and_get_ref: |generator, params, _cx| { + let schema = json_schema!({ + "type": "string", + "enum": params.font_names, + }); + replace_subschema::(generator, schema) + } + } +} + fn merge(target: &mut T, value: Option) { if let Some(value) = value { *target = value; diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index 67ca6314af..86a5392b87 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -711,7 +711,7 @@ mod test { ); cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::(cx, |settings| { - settings.languages.insert( + settings.languages.0.insert( LanguageName::new("Rust"), LanguageSettingsContent { auto_indent_on_paste: Some(false), diff --git a/tooling/workspace-hack/Cargo.toml b/tooling/workspace-hack/Cargo.toml index 347d59e293..84ed001536 100644 --- a/tooling/workspace-hack/Cargo.toml +++ b/tooling/workspace-hack/Cargo.toml @@ -111,7 +111,7 @@ sea-orm = { version = "1", features = ["runtime-tokio-rustls", "sqlx-postgres", sea-query-binder = { version = "0.7", default-features = false, features = ["postgres-array", "sqlx-postgres", "sqlx-sqlite", "with-bigdecimal", "with-chrono", "with-json", "with-rust_decimal", "with-time", "with-uuid"] } semver = { version = "1", features = ["serde"] } serde = { version = "1", features = ["alloc", "derive", "rc"] } -serde_json = { version = "1", features = ["preserve_order", "raw_value", "unbounded_depth"] } +serde_json = { version = "1", features = ["alloc", "preserve_order", "raw_value", "unbounded_depth"] } sha1 = { version = "0.10", features = ["compress"] } simd-adler32 = { version = "0.3" } smallvec = { version = "1", default-features = false, features = ["const_new", "serde", "union", "write"] } @@ -244,7 +244,7 @@ sea-query-binder = { version = "0.7", default-features = false, features = ["pos semver = { version = "1", features = ["serde"] } serde = { version = "1", features = ["alloc", "derive", "rc"] } serde_derive = { version = "1", features = ["deserialize_in_place"] } -serde_json = { version = "1", features = ["preserve_order", "raw_value", "unbounded_depth"] } +serde_json = { version = "1", features = ["alloc", "preserve_order", "raw_value", "unbounded_depth"] } sha1 = { version = "0.10", features = ["compress"] } simd-adler32 = { version = "0.3" } smallvec = { version = "1", default-features = false, features = ["const_new", "serde", "union", "write"] }