Migrate to schemars version 1.0 (#33635)

The major change in schemars 1.0 is that now schemas are represented as
plain json values instead of specialized datatypes. This allows for more
concise construction and manipulation.

This change also improves how settings schemas are generated. Each top
level settings type was being generated as a full root schema including
the definitions it references, and then these were merged. This meant
generating all shared definitions multiple times, and might have bugs in
cases where there are two types with the same names.

Now instead the schemar generator's `definitions` are built up as they
normally are and the `Settings` trait no longer has a special
`json_schema` method. To handle types that have schema that vary at
runtime (`FontFamilyName`, `ThemeName`, etc), values of
`ParameterizedJsonSchema` are collected by `inventory`, and the schema
definitions for these types are replaced.

To help check that this doesn't break anything, I tried to minimize the
overall [schema
diff](https://gist.github.com/mgsloan/1de549def20399d6f37943a3c1583ee7)
with some patches to make the order more consistent + schemas also
sorted with `jq -S .`. A skim of the diff shows that the diffs come
from:

* `enum: ["value"]` turning into `const: "value"`
* Differences in handling of newlines for "description"
* Schemas for generic types no longer including the parameter name, now
all disambiguation is with numeric suffixes
* Enums now using `oneOf` instead of `anyOf`.

Release Notes:

- N/A
This commit is contained in:
Michael Sloan 2025-06-30 15:07:28 -06:00 committed by GitHub
parent a2e786e0f9
commit 5fafab6e52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 714 additions and 963 deletions

13
Cargo.lock generated
View file

@ -4133,7 +4133,7 @@ dependencies = [
[[package]] [[package]]
name = "dap-types" name = "dap-types"
version = "0.0.1" 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 = [ dependencies = [
"schemars", "schemars",
"serde", "serde",
@ -8847,6 +8847,7 @@ dependencies = [
"http_client", "http_client",
"imara-diff", "imara-diff",
"indoc", "indoc",
"inventory",
"itertools 0.14.0", "itertools 0.14.0",
"log", "log",
"lsp", "lsp",
@ -14053,12 +14054,13 @@ dependencies = [
[[package]] [[package]]
name = "schemars" name = "schemars"
version = "0.8.22" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" checksum = "fe8c9d1c68d67dd9f97ecbc6f932b60eb289c5dbddd8aa1405484a8fd2fcd984"
dependencies = [ dependencies = [
"dyn-clone", "dyn-clone",
"indexmap", "indexmap",
"ref-cast",
"schemars_derive", "schemars_derive",
"serde", "serde",
"serde_json", "serde_json",
@ -14066,9 +14068,9 @@ dependencies = [
[[package]] [[package]]
name = "schemars_derive" name = "schemars_derive"
version = "0.8.22" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" checksum = "6ca9fcb757952f8e8629b9ab066fc62da523c46c2b247b1708a3be06dd82530b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -16010,6 +16012,7 @@ dependencies = [
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
"indexmap", "indexmap",
"inventory",
"log", "log",
"palette", "palette",
"parking_lot", "parking_lot",

View file

@ -444,7 +444,7 @@ core-video = { version = "0.4.3", features = ["metal"] }
cpal = "0.16" cpal = "0.16"
criterion = { version = "0.5", features = ["html_reports"] } criterion = { version = "0.5", features = ["html_reports"] }
ctor = "0.4.0" 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" dashmap = "6.0"
derive_more = "0.99.17" derive_more = "0.99.17"
dirs = "4.0" dirs = "4.0"
@ -540,7 +540,7 @@ rustc-hash = "2.1.0"
rustls = { version = "0.23.26" } rustls = { version = "0.23.26" }
rustls-platform-verifier = "0.5.0" rustls-platform-verifier = "0.5.0"
scap = { git = "https://github.com/zed-industries/scap", rev = "08f0a01417505cc0990b9931a37e5120db92e0d0", default-features = false } 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" semver = "1.0"
serde = { version = "1.0", features = ["derive", "rc"] } serde = { version = "1.0", features = ["derive", "rc"] }
serde_derive = { version = "1.0", features = ["deserialize_in_place"] } serde_derive = { version = "1.0", features = ["deserialize_in_place"] }

View file

@ -6,9 +6,10 @@ use anyhow::{Result, bail};
use collections::IndexMap; use collections::IndexMap;
use gpui::{App, Pixels, SharedString}; use gpui::{App, Pixels, SharedString};
use language_model::LanguageModel; use language_model::LanguageModel;
use schemars::{JsonSchema, schema::Schema}; use schemars::{JsonSchema, json_schema};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{Settings, SettingsSources};
use std::borrow::Cow;
pub use crate::agent_profile::*; pub use crate::agent_profile::*;
@ -321,29 +322,27 @@ pub struct LanguageModelSelection {
pub struct LanguageModelProviderSetting(pub String); pub struct LanguageModelProviderSetting(pub String);
impl JsonSchema for LanguageModelProviderSetting { impl JsonSchema for LanguageModelProviderSetting {
fn schema_name() -> String { fn schema_name() -> Cow<'static, str> {
"LanguageModelProviderSetting".into() "LanguageModelProviderSetting".into()
} }
fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> Schema { fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::schema::SchemaObject { json_schema!({
enum_values: Some(vec![ "enum": [
"anthropic".into(), "anthropic",
"amazon-bedrock".into(), "amazon-bedrock",
"google".into(), "google",
"lmstudio".into(), "lmstudio",
"ollama".into(), "ollama",
"openai".into(), "openai",
"zed.dev".into(), "zed.dev",
"copilot_chat".into(), "copilot_chat",
"deepseek".into(), "deepseek",
"openrouter".into(), "openrouter",
"mistral".into(), "mistral",
"vercel".into(), "vercel"
]), ]
..Default::default() })
}
.into()
} }
} }

View file

@ -1,8 +1,9 @@
use anyhow::Result; use anyhow::Result;
use language_model::LanguageModelToolSchemaFormat; use language_model::LanguageModelToolSchemaFormat;
use schemars::{ use schemars::{
JsonSchema, JsonSchema, Schema,
schema::{RootSchema, Schema, SchemaObject}, generate::SchemaSettings,
transform::{Transform, transform_subschemas},
}; };
pub fn json_schema_for<T: JsonSchema>( pub fn json_schema_for<T: JsonSchema>(
@ -13,7 +14,7 @@ pub fn json_schema_for<T: JsonSchema>(
} }
fn schema_to_json( fn schema_to_json(
schema: &RootSchema, schema: &Schema,
format: LanguageModelToolSchemaFormat, format: LanguageModelToolSchemaFormat,
) -> Result<serde_json::Value> { ) -> Result<serde_json::Value> {
let mut value = serde_json::to_value(schema)?; let mut value = serde_json::to_value(schema)?;
@ -21,58 +22,42 @@ fn schema_to_json(
Ok(value) Ok(value)
} }
fn root_schema_for<T: JsonSchema>(format: LanguageModelToolSchemaFormat) -> RootSchema { fn root_schema_for<T: JsonSchema>(format: LanguageModelToolSchemaFormat) -> Schema {
let mut generator = match format { let mut generator = match format {
LanguageModelToolSchemaFormat::JsonSchema => schemars::SchemaGenerator::default(), LanguageModelToolSchemaFormat::JsonSchema => SchemaSettings::draft07().into_generator(),
LanguageModelToolSchemaFormat::JsonSchemaSubset => { // TODO: Gemini docs mention using a subset of OpenAPI 3, so this may benefit from using
schemars::r#gen::SchemaSettings::default() // `SchemaSettings::openapi3()`.
.with(|settings| { LanguageModelToolSchemaFormat::JsonSchemaSubset => SchemaSettings::draft07()
settings.meta_schema = None; .with(|settings| {
settings.inline_subschemas = true; settings.meta_schema = None;
settings settings.inline_subschemas = true;
.visitors })
.push(Box::new(TransformToJsonSchemaSubsetVisitor)); .with_transform(ToJsonSchemaSubsetTransform)
}) .into_generator(),
.into_generator()
}
}; };
generator.root_schema_for::<T>() generator.root_schema_for::<T>()
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct TransformToJsonSchemaSubsetVisitor; struct ToJsonSchemaSubsetTransform;
impl schemars::visit::Visitor for TransformToJsonSchemaSubsetVisitor { impl Transform for ToJsonSchemaSubsetTransform {
fn visit_root_schema(&mut self, root: &mut RootSchema) { fn transform(&mut self, schema: &mut Schema) {
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) {
// Ensure that the type field is not an array, this happens when we use // Ensure that the type field is not an array, this happens when we use
// Option<T>, the type will be [T, "null"]. // Option<T>, the type will be [T, "null"].
if let Some(instance_type) = schema.instance_type.take() { if let Some(type_field) = schema.get_mut("type") {
schema.instance_type = match instance_type { if let Some(types) = type_field.as_array() {
schemars::schema::SingleOrVec::Single(t) => { if let Some(first_type) = types.first() {
Some(schemars::schema::SingleOrVec::Single(t)) *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);
} }
} }

View file

@ -22,9 +22,7 @@ use gpui::{
use language::{ use language::{
Diagnostic, DiagnosticEntry, DiagnosticSourceKind, FakeLspAdapter, Language, LanguageConfig, Diagnostic, DiagnosticEntry, DiagnosticSourceKind, FakeLspAdapter, Language, LanguageConfig,
LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope, LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope,
language_settings::{ language_settings::{AllLanguageSettings, Formatter, PrettierSettings, SelectedFormatter},
AllLanguageSettings, Formatter, FormatterList, PrettierSettings, SelectedFormatter,
},
tree_sitter_rust, tree_sitter_typescript, tree_sitter_rust, tree_sitter_typescript,
}; };
use lsp::{LanguageServerId, OneOf}; use lsp::{LanguageServerId, OneOf};
@ -4591,15 +4589,13 @@ async fn test_formatting_buffer(
cx_a.update(|cx| { cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| { SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |file| { store.update_user_settings::<AllLanguageSettings>(cx, |file| {
file.defaults.formatter = Some(SelectedFormatter::List(FormatterList( file.defaults.formatter =
vec![Formatter::External { Some(SelectedFormatter::List(vec![Formatter::External {
command: "awk".into(), command: "awk".into(),
arguments: Some( arguments: Some(
vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(), vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
), ),
}] }]));
.into(),
)));
}); });
}); });
}); });
@ -4699,9 +4695,10 @@ async fn test_prettier_formatting_buffer(
cx_b.update(|cx| { cx_b.update(|cx| {
SettingsStore::update_global(cx, |store, cx| { SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |file| { store.update_user_settings::<AllLanguageSettings>(cx, |file| {
file.defaults.formatter = Some(SelectedFormatter::List(FormatterList( file.defaults.formatter =
vec![Formatter::LanguageServer { name: None }].into(), Some(SelectedFormatter::List(vec![Formatter::LanguageServer {
))); name: None,
}]));
file.defaults.prettier = Some(PrettierSettings { file.defaults.prettier = Some(PrettierSettings {
allowed: true, allowed: true,
..PrettierSettings::default() ..PrettierSettings::default()

View file

@ -14,8 +14,7 @@ use http_client::BlockedHttpClient;
use language::{ use language::{
FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageRegistry, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageRegistry,
language_settings::{ language_settings::{
AllLanguageSettings, Formatter, FormatterList, PrettierSettings, SelectedFormatter, AllLanguageSettings, Formatter, PrettierSettings, SelectedFormatter, language_settings,
language_settings,
}, },
tree_sitter_typescript, tree_sitter_typescript,
}; };
@ -505,9 +504,10 @@ async fn test_ssh_collaboration_formatting_with_prettier(
cx_b.update(|cx| { cx_b.update(|cx| {
SettingsStore::update_global(cx, |store, cx| { SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |file| { store.update_user_settings::<AllLanguageSettings>(cx, |file| {
file.defaults.formatter = Some(SelectedFormatter::List(FormatterList( file.defaults.formatter =
vec![Formatter::LanguageServer { name: None }].into(), Some(SelectedFormatter::List(vec![Formatter::LanguageServer {
))); name: None,
}]));
file.defaults.prettier = Some(PrettierSettings { file.defaults.prettier = Some(PrettierSettings {
allowed: true, allowed: true,
..PrettierSettings::default() ..PrettierSettings::default()

View file

@ -10,6 +10,7 @@ use gpui::{AsyncApp, SharedString};
pub use http_client::{HttpClient, github::latest_github_release}; pub use http_client::{HttpClient, github::latest_github_release};
use language::{LanguageName, LanguageToolchainStore}; use language::{LanguageName, LanguageToolchainStore};
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::WorktreeId; use settings::WorktreeId;
use smol::fs::File; use smol::fs::File;
@ -47,7 +48,10 @@ pub trait DapDelegate: Send + Sync + 'static {
async fn shell_env(&self) -> collections::HashMap<String, String>; async fn shell_env(&self) -> collections::HashMap<String, String>;
} }
#[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); pub struct DebugAdapterName(pub SharedString);
impl Deref for DebugAdapterName { impl Deref for DebugAdapterName {

View file

@ -3,7 +3,7 @@ use std::sync::Arc;
use gpui::{App, FontFeatures, FontWeight}; use gpui::{App, FontFeatures, FontWeight};
use project::project_settings::{InlineBlameSettings, ProjectSettings}; use project::project_settings::{InlineBlameSettings, ProjectSettings};
use settings::{EditableSettingControl, Settings}; use settings::{EditableSettingControl, Settings};
use theme::{FontFamilyCache, ThemeSettings}; use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
use ui::{ use ui::{
CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup, CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup,
prelude::*, prelude::*,
@ -75,7 +75,7 @@ impl EditableSettingControl for BufferFontFamilyControl {
value: Self::Value, value: Self::Value,
_cx: &App, _cx: &App,
) { ) {
settings.buffer_font_family = Some(value.to_string()); settings.buffer_font_family = Some(FontFamilyName(value.into()));
} }
} }

View file

@ -30,7 +30,7 @@ use language::{
}, },
tree_sitter_python, tree_sitter_python,
}; };
use language_settings::{Formatter, FormatterList, IndentGuideSettings}; use language_settings::{Formatter, IndentGuideSettings};
use lsp::CompletionParams; use lsp::CompletionParams;
use multi_buffer::{IndentGuide, PathKey}; use multi_buffer::{IndentGuide, PathKey};
use parking_lot::Mutex; use parking_lot::Mutex;
@ -3567,7 +3567,7 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
#[gpui::test] #[gpui::test]
fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) { fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
init_test(cx, |settings| { init_test(cx, |settings| {
settings.languages.extend([ settings.languages.0.extend([
( (
"TOML".into(), "TOML".into(),
LanguageSettingsContent { LanguageSettingsContent {
@ -5145,7 +5145,7 @@ fn test_transpose(cx: &mut TestAppContext) {
#[gpui::test] #[gpui::test]
async fn test_rewrap(cx: &mut TestAppContext) { async fn test_rewrap(cx: &mut TestAppContext) {
init_test(cx, |settings| { init_test(cx, |settings| {
settings.languages.extend([ settings.languages.0.extend([
( (
"Markdown".into(), "Markdown".into(),
LanguageSettingsContent { 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 // Set rust language override and assert overridden tabsize is sent to language server
update_test_language_settings(cx, |settings| { update_test_language_settings(cx, |settings| {
settings.languages.insert( settings.languages.0.insert(
"Rust".into(), "Rust".into(),
LanguageSettingsContent { LanguageSettingsContent {
tab_size: NonZeroU32::new(8), 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 // Set Rust language override and assert overridden tabsize is sent to language server
update_test_language_settings(cx, |settings| { update_test_language_settings(cx, |settings| {
settings.languages.insert( settings.languages.0.insert(
"Rust".into(), "Rust".into(),
LanguageSettingsContent { LanguageSettingsContent {
tab_size: NonZeroU32::new(8), tab_size: NonZeroU32::new(8),
@ -9933,9 +9933,9 @@ async fn test_range_format_during_save(cx: &mut TestAppContext) {
#[gpui::test] #[gpui::test]
async fn test_document_format_manual_trigger(cx: &mut TestAppContext) { async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
init_test(cx, |settings| { init_test(cx, |settings| {
settings.defaults.formatter = Some(language_settings::SelectedFormatter::List( settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![
FormatterList(vec![Formatter::LanguageServer { name: None }].into()), Formatter::LanguageServer { name: None },
)) ]))
}); });
let fs = FakeFs::new(cx.executor()); 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) { async fn test_multiple_formatters(cx: &mut TestAppContext) {
init_test(cx, |settings| { init_test(cx, |settings| {
settings.defaults.remove_trailing_whitespace_on_save = Some(true); settings.defaults.remove_trailing_whitespace_on_save = Some(true);
settings.defaults.formatter = settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![
Some(language_settings::SelectedFormatter::List(FormatterList( Formatter::LanguageServer { name: None },
vec![ Formatter::CodeActions(
Formatter::LanguageServer { name: None }, [
Formatter::CodeActions( ("code-action-1".into(), true),
[ ("code-action-2".into(), true),
("code-action-1".into(), true),
("code-action-2".into(), true),
]
.into_iter()
.collect(),
),
] ]
.into(), .into_iter()
))) .collect(),
),
]))
}); });
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
@ -10328,9 +10324,9 @@ async fn test_multiple_formatters(cx: &mut TestAppContext) {
#[gpui::test] #[gpui::test]
async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) { async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
init_test(cx, |settings| { init_test(cx, |settings| {
settings.defaults.formatter = Some(language_settings::SelectedFormatter::List( settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![
FormatterList(vec![Formatter::LanguageServer { name: None }].into()), Formatter::LanguageServer { name: None },
)) ]))
}); });
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
@ -14905,7 +14901,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppCon
.unwrap(); .unwrap();
let _fake_server = fake_servers.next().await.unwrap(); let _fake_server = fake_servers.next().await.unwrap();
update_test_language_settings(cx, |language_settings| { update_test_language_settings(cx, |language_settings| {
language_settings.languages.insert( language_settings.languages.0.insert(
language_name.clone(), language_name.clone(),
LanguageSettingsContent { LanguageSettingsContent {
tab_size: NonZeroU32::new(8), tab_size: NonZeroU32::new(8),
@ -15803,9 +15799,9 @@ fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
#[gpui::test] #[gpui::test]
async fn test_document_format_with_prettier(cx: &mut TestAppContext) { async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
init_test(cx, |settings| { init_test(cx, |settings| {
settings.defaults.formatter = Some(language_settings::SelectedFormatter::List( settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![
FormatterList(vec![Formatter::Prettier].into()), Formatter::Prettier,
)) ]))
}); });
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());

View file

@ -125,9 +125,7 @@ pub trait Action: Any + Send {
Self: Sized; Self: Sized;
/// Optional JSON schema for the action's input data. /// Optional JSON schema for the action's input data.
fn action_json_schema( fn action_json_schema(_: &mut schemars::SchemaGenerator) -> Option<schemars::Schema>
_: &mut schemars::r#gen::SchemaGenerator,
) -> Option<schemars::schema::Schema>
where where
Self: Sized, Self: Sized,
{ {
@ -238,7 +236,7 @@ impl Default for ActionRegistry {
struct ActionData { struct ActionData {
pub build: ActionBuilder, pub build: ActionBuilder,
pub json_schema: fn(&mut schemars::r#gen::SchemaGenerator) -> Option<schemars::schema::Schema>, pub json_schema: fn(&mut schemars::SchemaGenerator) -> Option<schemars::Schema>,
} }
/// This type must be public so that our macros can build it in other crates. /// 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 name: &'static str,
pub type_id: TypeId, pub type_id: TypeId,
pub build: ActionBuilder, pub build: ActionBuilder,
pub json_schema: fn(&mut schemars::r#gen::SchemaGenerator) -> Option<schemars::schema::Schema>, pub json_schema: fn(&mut schemars::SchemaGenerator) -> Option<schemars::Schema>,
pub deprecated_aliases: &'static [&'static str], pub deprecated_aliases: &'static [&'static str],
pub deprecation_message: Option<&'static str>, pub deprecation_message: Option<&'static str>,
} }
@ -357,8 +355,8 @@ impl ActionRegistry {
pub fn action_schemas( pub fn action_schemas(
&self, &self,
generator: &mut schemars::r#gen::SchemaGenerator, generator: &mut schemars::SchemaGenerator,
) -> Vec<(&'static str, Option<schemars::schema::Schema>)> { ) -> Vec<(&'static str, Option<schemars::Schema>)> {
// Use the order from all_names so that the resulting schema has sensible order. // Use the order from all_names so that the resulting schema has sensible order.
self.all_names self.all_names
.iter() .iter()

View file

@ -1388,8 +1388,8 @@ impl App {
/// Get all non-internal actions that have been registered, along with their schemas. /// Get all non-internal actions that have been registered, along with their schemas.
pub fn action_schemas( pub fn action_schemas(
&self, &self,
generator: &mut schemars::r#gen::SchemaGenerator, generator: &mut schemars::SchemaGenerator,
) -> Vec<(&'static str, Option<schemars::schema::Schema>)> { ) -> Vec<(&'static str, Option<schemars::Schema>)> {
self.actions.action_schemas(generator) self.actions.action_schemas(generator)
} }

View file

@ -1,9 +1,10 @@
use anyhow::{Context as _, bail}; use anyhow::{Context as _, bail};
use schemars::{JsonSchema, SchemaGenerator, schema::Schema}; use schemars::{JsonSchema, json_schema};
use serde::{ use serde::{
Deserialize, Deserializer, Serialize, Serializer, Deserialize, Deserializer, Serialize, Serializer,
de::{self, Visitor}, de::{self, Visitor},
}; };
use std::borrow::Cow;
use std::{ use std::{
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
hash::{Hash, Hasher}, hash::{Hash, Hasher},
@ -99,22 +100,14 @@ impl Visitor<'_> for RgbaVisitor {
} }
impl JsonSchema for Rgba { impl JsonSchema for Rgba {
fn schema_name() -> String { fn schema_name() -> Cow<'static, str> {
"Rgba".to_string() "Rgba".into()
} }
fn json_schema(_generator: &mut SchemaGenerator) -> Schema { fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
use schemars::schema::{InstanceType, SchemaObject, StringValidation}; json_schema!({
"type": "string",
Schema::Object(SchemaObject { "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
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()
}) })
} }
} }
@ -629,11 +622,11 @@ impl From<Rgba> for Hsla {
} }
impl JsonSchema for Hsla { impl JsonSchema for Hsla {
fn schema_name() -> String { fn schema_name() -> Cow<'static, str> {
Rgba::schema_name() Rgba::schema_name()
} }
fn json_schema(generator: &mut SchemaGenerator) -> Schema { fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
Rgba::json_schema(generator) Rgba::json_schema(generator)
} }
} }

View file

@ -6,8 +6,9 @@ use anyhow::{Context as _, anyhow};
use core::fmt::Debug; use core::fmt::Debug;
use derive_more::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub, SubAssign}; use derive_more::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub, SubAssign};
use refineable::Refineable; use refineable::Refineable;
use schemars::{JsonSchema, SchemaGenerator, schema::Schema}; use schemars::{JsonSchema, json_schema};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use std::borrow::Cow;
use std::{ use std::{
cmp::{self, PartialOrd}, cmp::{self, PartialOrd},
fmt::{self, Display}, fmt::{self, Display},
@ -3229,20 +3230,14 @@ impl TryFrom<&'_ str> for AbsoluteLength {
} }
impl JsonSchema for AbsoluteLength { impl JsonSchema for AbsoluteLength {
fn schema_name() -> String { fn schema_name() -> Cow<'static, str> {
"AbsoluteLength".to_string() "AbsoluteLength".into()
} }
fn json_schema(_generator: &mut SchemaGenerator) -> Schema { fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
use schemars::schema::{InstanceType, SchemaObject, StringValidation}; json_schema!({
"type": "string",
Schema::Object(SchemaObject { "pattern": r"^-?\d+(\.\d+)?(px|rem)$"
instance_type: Some(InstanceType::String.into()),
string: Some(Box::new(StringValidation {
pattern: Some(r"^-?\d+(\.\d+)?(px|rem)$".to_string()),
..Default::default()
})),
..Default::default()
}) })
} }
} }
@ -3366,20 +3361,14 @@ impl TryFrom<&'_ str> for DefiniteLength {
} }
impl JsonSchema for DefiniteLength { impl JsonSchema for DefiniteLength {
fn schema_name() -> String { fn schema_name() -> Cow<'static, str> {
"DefiniteLength".to_string() "DefiniteLength".into()
} }
fn json_schema(_generator: &mut SchemaGenerator) -> Schema { fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
use schemars::schema::{InstanceType, SchemaObject, StringValidation}; json_schema!({
"type": "string",
Schema::Object(SchemaObject { "pattern": r"^-?\d+(\.\d+)?(px|rem|%)$"
instance_type: Some(InstanceType::String.into()),
string: Some(Box::new(StringValidation {
pattern: Some(r"^-?\d+(\.\d+)?(px|rem|%)$".to_string()),
..Default::default()
})),
..Default::default()
}) })
} }
} }
@ -3480,20 +3469,14 @@ impl TryFrom<&'_ str> for Length {
} }
impl JsonSchema for Length { impl JsonSchema for Length {
fn schema_name() -> String { fn schema_name() -> Cow<'static, str> {
"Length".to_string() "Length".into()
} }
fn json_schema(_generator: &mut SchemaGenerator) -> Schema { fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
use schemars::schema::{InstanceType, SchemaObject, StringValidation}; json_schema!({
"type": "string",
Schema::Object(SchemaObject { "pattern": r"^(auto|-?\d+(\.\d+)?(px|rem|%))$"
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()
}) })
} }
} }

View file

@ -2,7 +2,10 @@ use derive_more::{Deref, DerefMut};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{borrow::Borrow, sync::Arc}; use std::{
borrow::{Borrow, Cow},
sync::Arc,
};
use util::arc_cow::ArcCow; use util::arc_cow::ArcCow;
/// A shared string is an immutable string that can be cheaply cloned in GPUI /// A shared string is an immutable string that can be cheaply cloned in GPUI
@ -23,12 +26,16 @@ impl SharedString {
} }
impl JsonSchema for 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() String::schema_name()
} }
fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
String::json_schema(r#gen) String::json_schema(generator)
} }
} }

View file

@ -1,6 +1,7 @@
use std::borrow::Cow;
use std::sync::Arc; 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. /// The OpenType features that can be configured for a given font.
#[derive(Default, Clone, Eq, PartialEq, Hash)] #[derive(Default, Clone, Eq, PartialEq, Hash)]
@ -128,36 +129,22 @@ impl serde::Serialize for FontFeatures {
} }
} }
impl schemars::JsonSchema for FontFeatures { impl JsonSchema for FontFeatures {
fn schema_name() -> String { fn schema_name() -> Cow<'static, str> {
"FontFeatures".into() "FontFeatures".into()
} }
fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
let mut schema = SchemaObject::default(); json_schema!({
schema.instance_type = Some(schemars::schema::SingleOrVec::Single(Box::new( "type": "object",
InstanceType::Object, "patternProperties": {
))); "[0-9a-zA-Z]{4}$": {
{ "type": ["boolean", "integer"],
let mut property = SchemaObject { "minimum": 0,
instance_type: Some(schemars::schema::SingleOrVec::Vec(vec![ "multipleOf": 1
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);
} }
schema })
.object()
.pattern_properties
.insert("[0-9a-zA-Z]{4}$".into(), property.into());
}
schema.into()
} }
} }

View file

@ -159,8 +159,8 @@ pub(crate) fn derive_action(input: TokenStream) -> TokenStream {
} }
fn action_json_schema( fn action_json_schema(
_generator: &mut gpui::private::schemars::r#gen::SchemaGenerator, _generator: &mut gpui::private::schemars::SchemaGenerator,
) -> Option<gpui::private::schemars::schema::Schema> { ) -> Option<gpui::private::schemars::Schema> {
#json_schema_fn_body #json_schema_fn_body
} }

View file

@ -967,6 +967,7 @@ fn toggle_show_inline_completions_for_language(
all_language_settings(None, cx).show_edit_predictions(Some(&language), cx); all_language_settings(None, cx).show_edit_predictions(Some(&language), cx);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| { update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.languages file.languages
.0
.entry(language.name()) .entry(language.name())
.or_default() .or_default()
.show_edit_predictions = Some(!show_edit_predictions); .show_edit_predictions = Some(!show_edit_predictions);

View file

@ -39,6 +39,7 @@ globset.workspace = true
gpui.workspace = true gpui.workspace = true
http_client.workspace = true http_client.workspace = true
imara-diff.workspace = true imara-diff.workspace = true
inventory.workspace = true
itertools.workspace = true itertools.workspace = true
log.workspace = true log.workspace = true
lsp.workspace = true lsp.workspace = true

View file

@ -2006,7 +2006,7 @@ fn test_autoindent_language_without_indents_query(cx: &mut App) {
#[gpui::test] #[gpui::test]
fn test_autoindent_with_injected_languages(cx: &mut App) { fn test_autoindent_with_injected_languages(cx: &mut App) {
init_settings(cx, |settings| { init_settings(cx, |settings| {
settings.languages.extend([ settings.languages.0.extend([
( (
"HTML".into(), "HTML".into(),
LanguageSettingsContent { LanguageSettingsContent {

View file

@ -39,11 +39,7 @@ use lsp::{CodeActionKind, InitializeParams, LanguageServerBinary, LanguageServer
pub use manifest::{ManifestDelegate, ManifestName, ManifestProvider, ManifestQuery}; pub use manifest::{ManifestDelegate, ManifestName, ManifestProvider, ManifestQuery};
use parking_lot::Mutex; use parking_lot::Mutex;
use regex::Regex; use regex::Regex;
use schemars::{ use schemars::{JsonSchema, json_schema};
JsonSchema,
r#gen::SchemaGenerator,
schema::{InstanceType, Schema, SchemaObject},
};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use serde_json::Value; use serde_json::Value;
use settings::WorktreeId; use settings::WorktreeId;
@ -694,7 +690,6 @@ pub struct LanguageConfig {
pub matcher: LanguageMatcher, pub matcher: LanguageMatcher,
/// List of bracket types in a language. /// List of bracket types in a language.
#[serde(default)] #[serde(default)]
#[schemars(schema_with = "bracket_pair_config_json_schema")]
pub brackets: BracketPairConfig, pub brackets: BracketPairConfig,
/// If set to true, auto indentation uses last non empty line to determine /// If set to true, auto indentation uses last non empty line to determine
/// the indentation level for a new line. /// the indentation level for a new line.
@ -944,10 +939,9 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Regex>, D
} }
} }
fn regex_json_schema(_: &mut SchemaGenerator) -> Schema { fn regex_json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
Schema::Object(SchemaObject { json_schema!({
instance_type: Some(InstanceType::String.into()), "type": "string"
..Default::default()
}) })
} }
@ -988,12 +982,12 @@ pub struct FakeLspAdapter {
/// This struct includes settings for defining which pairs of characters are considered brackets and /// 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. /// also specifies any language-specific scopes where these pairs should be ignored for bracket matching purposes.
#[derive(Clone, Debug, Default, JsonSchema)] #[derive(Clone, Debug, Default, JsonSchema)]
#[schemars(with = "Vec::<BracketPairContent>")]
pub struct BracketPairConfig { pub struct BracketPairConfig {
/// A list of character pairs that should be treated as brackets in the context of a given language. /// A list of character pairs that should be treated as brackets in the context of a given language.
pub pairs: Vec<BracketPair>, pub pairs: Vec<BracketPair>,
/// A list of tree-sitter scopes for which a given bracket should not be active. /// 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]` /// 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<Vec<String>>, pub disabled_scopes_by_bracket_ix: Vec<Vec<String>>,
} }
@ -1003,10 +997,6 @@ impl BracketPairConfig {
} }
} }
fn bracket_pair_config_json_schema(r#gen: &mut SchemaGenerator) -> Schema {
Option::<Vec<BracketPairContent>>::json_schema(r#gen)
}
#[derive(Deserialize, JsonSchema)] #[derive(Deserialize, JsonSchema)]
pub struct BracketPairContent { pub struct BracketPairContent {
#[serde(flatten)] #[serde(flatten)]

View file

@ -1170,7 +1170,7 @@ impl LanguageRegistryState {
if let Some(theme) = self.theme.as_ref() { if let Some(theme) = self.theme.as_ref() {
language.set_theme(theme.syntax()); language.set_theme(theme.syntax());
} }
self.language_settings.languages.insert( self.language_settings.languages.0.insert(
language.name(), language.name(),
LanguageSettingsContent { LanguageSettingsContent {
tab_size: language.config.tab_size, tab_size: language.config.tab_size,

View file

@ -3,7 +3,6 @@
use crate::{File, Language, LanguageName, LanguageServerName}; use crate::{File, Language, LanguageName, LanguageServerName};
use anyhow::Result; use anyhow::Result;
use collections::{FxHashMap, HashMap, HashSet}; use collections::{FxHashMap, HashMap, HashSet};
use core::slice;
use ec4rs::{ use ec4rs::{
Properties as EditorconfigProperties, Properties as EditorconfigProperties,
property::{FinalNewline, IndentSize, IndentStyle, TabWidth, TrimTrailingWs}, property::{FinalNewline, IndentSize, IndentStyle, TabWidth, TrimTrailingWs},
@ -11,17 +10,15 @@ use ec4rs::{
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder}; use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
use gpui::{App, Modifiers}; use gpui::{App, Modifiers};
use itertools::{Either, Itertools}; use itertools::{Either, Itertools};
use schemars::{ use schemars::{JsonSchema, json_schema};
JsonSchema,
schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec},
};
use serde::{ use serde::{
Deserialize, Deserializer, Serialize, Deserialize, Deserializer, Serialize,
de::{self, IntoDeserializer, MapAccess, SeqAccess, Visitor}, de::{self, IntoDeserializer, MapAccess, SeqAccess, Visitor},
}; };
use serde_json::Value;
use settings::{ use settings::{
Settings, SettingsLocation, SettingsSources, SettingsStore, add_references_to_properties, ParameterizedJsonSchema, Settings, SettingsLocation, SettingsSources, SettingsStore,
replace_subschema,
}; };
use shellexpand; use shellexpand;
use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc}; use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc};
@ -306,13 +303,42 @@ pub struct AllLanguageSettingsContent {
pub defaults: LanguageSettingsContent, pub defaults: LanguageSettingsContent,
/// The settings for individual languages. /// The settings for individual languages.
#[serde(default)] #[serde(default)]
pub languages: HashMap<LanguageName, LanguageSettingsContent>, pub languages: LanguageToSettingsMap,
/// Settings for associating file extensions and filenames /// Settings for associating file extensions and filenames
/// with languages. /// with languages.
#[serde(default)] #[serde(default)]
pub file_types: HashMap<Arc<str>, Vec<String>>, pub file_types: HashMap<Arc<str>, Vec<String>>,
} }
/// 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<LanguageName, LanguageSettingsContent>);
inventory::submit! {
ParameterizedJsonSchema {
add_and_get_ref: |generator, params, _cx| {
let language_settings_content_ref = generator
.subschema_for::<LanguageSettingsContent>()
.to_value();
let schema = json_schema!({
"type": "object",
"properties": params
.language_names
.iter()
.map(|name| {
(
name.clone(),
language_settings_content_ref.clone(),
)
})
.collect::<serde_json::Map<_, _>>()
});
replace_subschema::<LanguageToSettingsMap>(generator, schema)
}
}
}
/// Controls how completions are processed for this language. /// Controls how completions are processed for this language.
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -648,45 +674,30 @@ pub enum FormatOnSave {
On, On,
/// Files should not be formatted on save. /// Files should not be formatted on save.
Off, Off,
List(FormatterList), List(Vec<Formatter>),
} }
impl JsonSchema for FormatOnSave { impl JsonSchema for FormatOnSave {
fn schema_name() -> String { fn schema_name() -> Cow<'static, str> {
"OnSaveFormatter".into() "OnSaveFormatter".into()
} }
fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> Schema { fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
let mut schema = SchemaObject::default();
let formatter_schema = Formatter::json_schema(generator); let formatter_schema = Formatter::json_schema(generator);
schema.instance_type = Some(
vec![ json_schema!({
InstanceType::Object, "oneOf": [
InstanceType::String, {
InstanceType::Array, "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" { } else if v == "off" {
Ok(Self::Value::Off) Ok(Self::Value::Off)
} else if v == "language_server" { } else if v == "language_server" {
Ok(Self::Value::List(FormatterList( Ok(Self::Value::List(vec![Formatter::LanguageServer {
Formatter::LanguageServer { name: None }.into(), name: None,
))) }]))
} else { } else {
let ret: Result<FormatterList, _> = let ret: Result<Vec<Formatter>, _> =
Deserialize::deserialize(v.into_deserializer()); Deserialize::deserialize(v.into_deserializer());
ret.map(Self::Value::List) ret.map(Self::Value::List)
} }
@ -738,7 +749,7 @@ impl<'de> Deserialize<'de> for FormatOnSave {
where where
A: MapAccess<'d>, A: MapAccess<'d>,
{ {
let ret: Result<FormatterList, _> = let ret: Result<Vec<Formatter>, _> =
Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)); Deserialize::deserialize(de::value::MapAccessDeserializer::new(map));
ret.map(Self::Value::List) ret.map(Self::Value::List)
} }
@ -746,7 +757,7 @@ impl<'de> Deserialize<'de> for FormatOnSave {
where where
A: SeqAccess<'d>, A: SeqAccess<'d>,
{ {
let ret: Result<FormatterList, _> = let ret: Result<Vec<Formatter>, _> =
Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map)); Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map));
ret.map(Self::Value::List) ret.map(Self::Value::List)
} }
@ -783,45 +794,30 @@ pub enum SelectedFormatter {
/// or falling back to formatting via language server. /// or falling back to formatting via language server.
#[default] #[default]
Auto, Auto,
List(FormatterList), List(Vec<Formatter>),
} }
impl JsonSchema for SelectedFormatter { impl JsonSchema for SelectedFormatter {
fn schema_name() -> String { fn schema_name() -> Cow<'static, str> {
"Formatter".into() "Formatter".into()
} }
fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> Schema { fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
let mut schema = SchemaObject::default();
let formatter_schema = Formatter::json_schema(generator); let formatter_schema = Formatter::json_schema(generator);
schema.instance_type = Some(
vec![ json_schema!({
InstanceType::Object, "oneOf": [
InstanceType::String, {
InstanceType::Array, "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 { impl<'de> Deserialize<'de> for SelectedFormatter {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where where
@ -856,11 +853,11 @@ impl<'de> Deserialize<'de> for SelectedFormatter {
if v == "auto" { if v == "auto" {
Ok(Self::Value::Auto) Ok(Self::Value::Auto)
} else if v == "language_server" { } else if v == "language_server" {
Ok(Self::Value::List(FormatterList( Ok(Self::Value::List(vec![Formatter::LanguageServer {
Formatter::LanguageServer { name: None }.into(), name: None,
))) }]))
} else { } else {
let ret: Result<FormatterList, _> = let ret: Result<Vec<Formatter>, _> =
Deserialize::deserialize(v.into_deserializer()); Deserialize::deserialize(v.into_deserializer());
ret.map(SelectedFormatter::List) ret.map(SelectedFormatter::List)
} }
@ -869,7 +866,7 @@ impl<'de> Deserialize<'de> for SelectedFormatter {
where where
A: MapAccess<'d>, A: MapAccess<'d>,
{ {
let ret: Result<FormatterList, _> = let ret: Result<Vec<Formatter>, _> =
Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)); Deserialize::deserialize(de::value::MapAccessDeserializer::new(map));
ret.map(SelectedFormatter::List) ret.map(SelectedFormatter::List)
} }
@ -877,7 +874,7 @@ impl<'de> Deserialize<'de> for SelectedFormatter {
where where
A: SeqAccess<'d>, A: SeqAccess<'d>,
{ {
let ret: Result<FormatterList, _> = let ret: Result<Vec<Formatter>, _> =
Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map)); Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map));
ret.map(SelectedFormatter::List) ret.map(SelectedFormatter::List)
} }
@ -885,19 +882,6 @@ impl<'de> Deserialize<'de> for SelectedFormatter {
deserializer.deserialize_any(FormatDeserializer) 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<Formatter>);
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. /// 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)] #[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)?)?; serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
let mut languages = HashMap::default(); 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(); let mut language_settings = defaults.clone();
merge_settings(&mut language_settings, settings); merge_settings(&mut language_settings, settings);
languages.insert(language_name.clone(), language_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. // 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( merge_settings(
languages languages
.entry(language_name.clone()) .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::<Self::FileContent>();
// 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) { fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
let d = &mut current.defaults; let d = &mut current.defaults;
if let Some(size) = vscode if let Some(size) = vscode
@ -1674,29 +1613,26 @@ mod tests {
let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
assert_eq!( assert_eq!(
settings.formatter, settings.formatter,
Some(SelectedFormatter::List(FormatterList( Some(SelectedFormatter::List(vec![Formatter::LanguageServer {
Formatter::LanguageServer { name: None }.into() name: None
))) }]))
); );
let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}]}"; let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}]}";
let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
assert_eq!( assert_eq!(
settings.formatter, settings.formatter,
Some(SelectedFormatter::List(FormatterList( Some(SelectedFormatter::List(vec![Formatter::LanguageServer {
vec![Formatter::LanguageServer { name: None }].into() name: None
))) }]))
); );
let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}, \"prettier\"]}"; let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}, \"prettier\"]}";
let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
assert_eq!( assert_eq!(
settings.formatter, settings.formatter,
Some(SelectedFormatter::List(FormatterList( Some(SelectedFormatter::List(vec![
vec![ Formatter::LanguageServer { name: None },
Formatter::LanguageServer { name: None }, Formatter::Prettier
Formatter::Prettier ]))
]
.into()
)))
); );
} }

View file

@ -269,10 +269,9 @@ impl JsonLspAdapter {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
fn generate_inspector_style_schema() -> serde_json_lenient::Value { fn generate_inspector_style_schema() -> serde_json_lenient::Value {
let schema = schemars::r#gen::SchemaSettings::draft07() let schema = schemars::generate::SchemaSettings::draft07()
.with(|settings| settings.option_add_null_type = false)
.into_generator() .into_generator()
.into_root_schema_for::<gpui::StyleRefinement>(); .root_schema_for::<gpui::StyleRefinement>();
serde_json_lenient::to_value(schema).unwrap() serde_json_lenient::to_value(schema).unwrap()
} }

View file

@ -15,11 +15,7 @@ use gpui::{App, AppContext as _, AsyncApp, BackgroundExecutor, SharedString, Tas
use notification::DidChangeWorkspaceFolders; use notification::DidChangeWorkspaceFolders;
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use postage::{barrier, prelude::Stream}; use postage::{barrier, prelude::Stream};
use schemars::{ use schemars::JsonSchema;
JsonSchema,
r#gen::SchemaGenerator,
schema::{InstanceType, Schema, SchemaObject},
};
use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde_json::{Value, json, value::RawValue}; use serde_json::{Value, json, value::RawValue};
use smol::{ use smol::{
@ -130,7 +126,10 @@ impl LanguageServerId {
} }
/// A name of a language server. /// 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); pub struct LanguageServerName(pub SharedString);
impl std::fmt::Display for LanguageServerName { impl std::fmt::Display for LanguageServerName {
@ -151,20 +150,6 @@ impl AsRef<OsStr> 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 { impl LanguageServerName {
pub const fn new_static(s: &'static str) -> Self { pub const fn new_static(s: &'static str) -> Self {
Self(SharedString::new_static(s)) Self(SharedString::new_static(s))

View file

@ -1405,7 +1405,7 @@ impl LocalLspStore {
let formatters = match (trigger, &settings.format_on_save) { let formatters = match (trigger, &settings.format_on_save) {
(FormatTrigger::Save, FormatOnSave::Off) => &[], (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) => { (FormatTrigger::Manual, _) | (FormatTrigger::Save, FormatOnSave::On) => {
match &settings.formatter { match &settings.formatter {
SelectedFormatter::Auto => { SelectedFormatter::Auto => {
@ -1417,7 +1417,7 @@ impl LocalLspStore {
std::slice::from_ref(&Formatter::LanguageServer { name: None }) std::slice::from_ref(&Formatter::LanguageServer { name: None })
} }
} }
SelectedFormatter::List(formatter_list) => formatter_list.as_ref(), SelectedFormatter::List(formatter_list) => formatter_list.as_slice(),
} }
} }
}; };

View file

@ -705,7 +705,6 @@ pub fn prettier_plugins_for_language(
SelectedFormatter::Auto => Some(&language_settings.prettier.plugins), SelectedFormatter::Auto => Some(&language_settings.prettier.plugins),
SelectedFormatter::List(list) => list SelectedFormatter::List(list) => list
.as_ref()
.contains(&Formatter::Prettier) .contains(&Formatter::Prettier)
.then_some(&language_settings.prettier.plugins), .then_some(&language_settings.prettier.plugins),
} }

View file

@ -2023,7 +2023,7 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
cx.update(|cx| { cx.update(|cx| {
SettingsStore::update_global(cx, |settings, cx| { SettingsStore::update_global(cx, |settings, cx| {
settings.update_user_settings::<AllLanguageSettings>(cx, |settings| { settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
settings.languages.insert( settings.languages.0.insert(
"Rust".into(), "Rust".into(),
LanguageSettingsContent { LanguageSettingsContent {
enable_language_server: Some(false), enable_language_server: Some(false),
@ -2042,14 +2042,14 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
cx.update(|cx| { cx.update(|cx| {
SettingsStore::update_global(cx, |settings, cx| { SettingsStore::update_global(cx, |settings, cx| {
settings.update_user_settings::<AllLanguageSettings>(cx, |settings| { settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
settings.languages.insert( settings.languages.0.insert(
LanguageName::new("Rust"), LanguageName::new("Rust"),
LanguageSettingsContent { LanguageSettingsContent {
enable_language_server: Some(true), enable_language_server: Some(true),
..Default::default() ..Default::default()
}, },
); );
settings.languages.insert( settings.languages.0.insert(
LanguageName::new("JavaScript"), LanguageName::new("JavaScript"),
LanguageSettingsContent { LanguageSettingsContent {
enable_language_server: Some(false), enable_language_server: Some(false),

View file

@ -5,13 +5,10 @@ use gpui::{
Action, ActionBuildError, App, InvalidKeystrokeError, KEYSTROKE_PARSE_EXPECTED_MESSAGE, Action, ActionBuildError, App, InvalidKeystrokeError, KEYSTROKE_PARSE_EXPECTED_MESSAGE,
KeyBinding, KeyBindingContextPredicate, KeyBindingMetaIndex, NoAction, KeyBinding, KeyBindingContextPredicate, KeyBindingMetaIndex, NoAction,
}; };
use schemars::{ use schemars::{JsonSchema, json_schema};
JsonSchema,
r#gen::{SchemaGenerator, SchemaSettings},
schema::{ArrayValidation, InstanceType, Schema, SchemaObject, SubschemaValidation},
};
use serde::Deserialize; 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 std::{any::TypeId, fmt::Write, rc::Rc, sync::Arc, sync::LazyLock};
use util::{ use util::{
asset_str, asset_str,
@ -123,14 +120,14 @@ impl std::fmt::Display for KeymapAction {
impl JsonSchema for KeymapAction { impl JsonSchema for KeymapAction {
/// This is used when generating the JSON schema for the `KeymapAction` type, so that it can /// This is used when generating the JSON schema for the `KeymapAction` type, so that it can
/// reference the keymap action schema. /// reference the keymap action schema.
fn schema_name() -> String { fn schema_name() -> Cow<'static, str> {
"KeymapAction".into() "KeymapAction".into()
} }
/// This schema will be replaced with the full action schema in /// This schema will be replaced with the full action schema in
/// `KeymapFile::generate_json_schema`. /// `KeymapFile::generate_json_schema`.
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
Schema::Bool(true) json_schema!(true)
} }
} }
@ -424,9 +421,7 @@ impl KeymapFile {
} }
pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value { pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value {
let mut generator = SchemaSettings::draft07() let mut generator = schemars::generate::SchemaSettings::draft07().into_generator();
.with(|settings| settings.option_add_null_type = false)
.into_generator();
let action_schemas = cx.action_schemas(&mut generator); let action_schemas = cx.action_schemas(&mut generator);
let deprecations = cx.deprecated_actions_to_preferred_actions(); let deprecations = cx.deprecated_actions_to_preferred_actions();
@ -440,92 +435,70 @@ impl KeymapFile {
} }
fn generate_json_schema( fn generate_json_schema(
generator: SchemaGenerator, mut generator: schemars::SchemaGenerator,
action_schemas: Vec<(&'static str, Option<Schema>)>, action_schemas: Vec<(&'static str, Option<schemars::Schema>)>,
deprecations: &HashMap<&'static str, &'static str>, deprecations: &HashMap<&'static str, &'static str>,
deprecation_messages: &HashMap<&'static str, &'static str>, deprecation_messages: &HashMap<&'static str, &'static str>,
) -> serde_json::Value { ) -> serde_json::Value {
fn set<I, O>(input: I) -> Option<O> fn add_deprecation(schema: &mut schemars::Schema, message: String) {
where schema.insert(
I: Into<O>, // deprecationMessage is not part of the JSON Schema spec, but
{ // json-language-server recognizes it.
Some(input.into()) "deprecationMessage".to_string(),
}
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(),
Value::String(message), Value::String(message),
); );
} }
fn add_deprecation_preferred_name(schema_object: &mut SchemaObject, new_name: &str) { fn add_deprecation_preferred_name(schema: &mut schemars::Schema, new_name: &str) {
add_deprecation(schema_object, format!("Deprecated, use {new_name}")); add_deprecation(schema, format!("Deprecated, use {new_name}"));
} }
fn add_description(schema_object: &mut SchemaObject, description: String) { fn add_description(schema: &mut schemars::Schema, description: String) {
schema_object schema.insert("description".to_string(), Value::String(description));
.metadata
.get_or_insert(Default::default())
.description = Some(description);
} }
let empty_object: SchemaObject = SchemaObject { let empty_object = json_schema!({
instance_type: set(InstanceType::Object), "type": "object"
..Default::default() });
};
// This is a workaround for a json-language-server issue where it matches the first // 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. // 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 // 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. // must match the name of the first alternative.
let mut plain_action = SchemaObject { let mut plain_action = json_schema!({
instance_type: set(InstanceType::String), "type": "string",
const_value: Some(Value::String("".to_owned())), "const": ""
..Default::default() });
};
let no_action_message = "No action named this."; let no_action_message = "No action named this.";
add_description(&mut plain_action, no_action_message.to_owned()); add_description(&mut plain_action, no_action_message.to_owned());
add_deprecation(&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())), let mut matches_action_name = json_schema!({
..Default::default() "const": ""
}; });
let no_action_message = "No action named this that takes input."; let no_action_message_input = "No action named this that takes input.";
add_description(&mut matches_action_name, no_action_message.to_owned()); add_description(&mut matches_action_name, no_action_message_input.to_owned());
add_deprecation(&mut matches_action_name, no_action_message.to_owned()); add_deprecation(&mut matches_action_name, no_action_message_input.to_owned());
let action_with_input = SchemaObject {
instance_type: set(InstanceType::Array), let action_with_input = json_schema!({
array: set(ArrayValidation { "type": "array",
items: set(vec![ "items": [
matches_action_name.into(), matches_action_name,
// Accept any value, as we want this to be the preferred match when there is a true
// typo in the name. ],
Schema::Bool(true), "minItems": 2,
]), "maxItems": 2
min_items: Some(2), });
max_items: Some(2), let mut keymap_action_alternatives = vec![plain_action, action_with_input];
..Default::default()
}),
..Default::default()
};
let mut keymap_action_alternatives = vec![plain_action.into(), action_with_input.into()];
for (name, action_schema) in action_schemas.into_iter() { for (name, action_schema) in action_schemas.into_iter() {
let schema = if let Some(Schema::Object(schema)) = action_schema { let description = action_schema.as_ref().and_then(|schema| {
Some(schema)
} else {
None
};
let description = schema.as_ref().and_then(|schema| {
schema schema
.metadata .as_object()
.as_ref() .and_then(|obj| obj.get("description"))
.and_then(|metadata| metadata.description.clone()) .and_then(|v| v.as_str())
.map(|s| s.to_string())
}); });
let deprecation = if name == NoAction.name() { let deprecation = if name == NoAction.name() {
@ -535,84 +508,64 @@ impl KeymapFile {
}; };
// Add an alternative for plain action names. // Add an alternative for plain action names.
let mut plain_action = SchemaObject { let mut plain_action = json_schema!({
instance_type: set(InstanceType::String), "type": "string",
const_value: Some(Value::String(name.to_string())), "const": name
..Default::default() });
};
if let Some(message) = deprecation_messages.get(name) { if let Some(message) = deprecation_messages.get(name) {
add_deprecation(&mut plain_action, message.to_string()); add_deprecation(&mut plain_action, message.to_string());
} else if let Some(new_name) = deprecation { } else if let Some(new_name) = deprecation {
add_deprecation_preferred_name(&mut plain_action, new_name); add_deprecation_preferred_name(&mut plain_action, new_name);
} }
if let Some(description) = description.clone() { if let Some(desc) = description.clone() {
add_description(&mut plain_action, description); 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. // 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! / // When a struct with no deserializable fields is added by deriving `Action`, an empty
// impl_actions_as! an empty object schema is produced. The action should be invoked // object schema is produced. The action should be invoked without data in this case.
// without data in this case. if let Some(schema) = action_schema {
if let Some(schema) = schema {
if schema != empty_object { if schema != empty_object {
let mut matches_action_name = SchemaObject { let mut matches_action_name = json_schema!({
const_value: Some(Value::String(name.to_string())), "const": name
..Default::default() });
}; if let Some(desc) = description.clone() {
if let Some(description) = description.clone() { add_description(&mut matches_action_name, desc);
add_description(&mut matches_action_name, description);
} }
if let Some(message) = deprecation_messages.get(name) { if let Some(message) = deprecation_messages.get(name) {
add_deprecation(&mut matches_action_name, message.to_string()); add_deprecation(&mut matches_action_name, message.to_string());
} else if let Some(new_name) = deprecation { } else if let Some(new_name) = deprecation {
add_deprecation_preferred_name(&mut matches_action_name, new_name); add_deprecation_preferred_name(&mut matches_action_name, new_name);
} }
let action_with_input = SchemaObject { let action_with_input = json_schema!({
instance_type: set(InstanceType::Array), "type": "array",
array: set(ArrayValidation { "items": [matches_action_name, schema],
items: set(vec![matches_action_name.into(), schema.into()]), "minItems": 2,
min_items: Some(2), "maxItems": 2
max_items: Some(2), });
..Default::default() keymap_action_alternatives.push(action_with_input);
}),
..Default::default()
};
keymap_action_alternatives.push(action_with_input.into());
} }
} }
} }
// Placing null first causes json-language-server to default assuming actions should be // Placing null first causes json-language-server to default assuming actions should be
// null, so place it last. // null, so place it last.
keymap_action_alternatives.push( keymap_action_alternatives.push(json_schema!({
SchemaObject { "type": "null"
instance_type: set(InstanceType::Null), }));
..Default::default()
} // The `KeymapSection` schema will reference the `KeymapAction` schema by name, so setting
.into(), // 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 { generator.root_schema_for::<KeymapFile>().to_value()
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::<KeymapFile>();
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()
} }
pub fn sections(&self) -> impl DoubleEndedIterator<Item = &KeymapSection> { pub fn sections(&self) -> impl DoubleEndedIterator<Item = &KeymapSection> {

View file

@ -1,11 +1,9 @@
use std::{ops::Range, sync::LazyLock};
use anyhow::Result; use anyhow::Result;
use schemars::schema::{ use gpui::App;
ArrayValidation, InstanceType, RootSchema, Schema, SchemaObject, SingleOrVec, use schemars::{JsonSchema, Schema};
};
use serde::{Serialize, de::DeserializeOwned}; use serde::{Serialize, de::DeserializeOwned};
use serde_json::Value; use serde_json::Value;
use std::{ops::Range, sync::LazyLock};
use tree_sitter::{Query, StreamingIterator as _}; use tree_sitter::{Query, StreamingIterator as _};
use util::RangeExt; use util::RangeExt;
@ -14,70 +12,43 @@ pub struct SettingsJsonSchemaParams<'a> {
pub font_names: &'a [String], pub font_names: &'a [String],
} }
impl SettingsJsonSchemaParams<'_> { pub struct ParameterizedJsonSchema {
pub fn font_family_schema(&self) -> Schema { pub add_and_get_ref:
let available_fonts: Vec<_> = self.font_names.iter().cloned().map(Value::String).collect(); fn(&mut schemars::SchemaGenerator, &SettingsJsonSchemaParams, &App) -> schemars::Schema,
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()
}
} }
type PropertyName<'a> = &'a str; inventory::collect!(ParameterizedJsonSchema);
type ReferencePath<'a> = &'a str;
/// Modifies the provided [`RootSchema`] by adding references to all of the specified properties. pub fn replace_subschema<T: JsonSchema>(
/// generator: &mut schemars::SchemaGenerator,
/// # Examples schema: schemars::Schema,
/// ) -> schemars::Schema {
/// ``` const DEFINITIONS_PATH: &str = "#/definitions/";
/// # let root_schema = RootSchema::default(); // The key in definitions may not match T::schema_name() if multiple types have the same name.
/// add_references_to_properties(&mut root_schema, &[ // This is a workaround for there being no straightforward way to get the key used for a type -
/// ("property_a", "#/definitions/DefinitionA"), // see https://github.com/GREsau/schemars/issues/449
/// ("property_b", "#/definitions/DefinitionB"), let ref_schema = generator.subschema_for::<T>();
/// ]) if let Some(serde_json::Value::String(definition_pointer)) = ref_schema.get("$ref") {
/// ``` if let Some(definition_name) = definition_pointer.strip_prefix(DEFINITIONS_PATH) {
pub fn add_references_to_properties( generator
root_schema: &mut RootSchema, .definitions_mut()
properties_with_references: &[(PropertyName, ReferencePath)], .insert(definition_name.to_string(), schema.to_value());
) { return ref_schema;
for (property, definition) in properties_with_references { } else {
let Some(schema) = root_schema.schema.object().properties.get_mut(*property) else { log::error!(
log::warn!("property '{property}' not found in JSON schema"); "bug: expected `$ref` field to start with {DEFINITIONS_PATH}, \
continue; got {definition_pointer}"
}; );
match schema {
Schema::Object(schema) => {
schema.reference = Some(definition.to_string());
}
Schema::Bool(_) => {
// Boolean schemas can't have references.
}
} }
} 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>( pub fn update_value_in_json_text<'a>(

View file

@ -6,7 +6,7 @@ use futures::{FutureExt, StreamExt, channel::mpsc, future::LocalBoxFuture};
use gpui::{App, AsyncApp, BorrowAppContext, Global, Task, UpdateGlobal}; use gpui::{App, AsyncApp, BorrowAppContext, Global, Task, UpdateGlobal};
use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name}; 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::{Deserialize, Serialize, de::DeserializeOwned};
use serde_json::{Value, json}; use serde_json::{Value, json};
use smallvec::SmallVec; use smallvec::SmallVec;
@ -24,8 +24,8 @@ use util::{ResultExt as _, merge_non_null_json_value_into};
pub type EditorconfigProperties = ec4rs::Properties; pub type EditorconfigProperties = ec4rs::Properties;
use crate::{ use crate::{
SettingsJsonSchemaParams, VsCodeSettings, WorktreeId, parse_json_with_comments, ParameterizedJsonSchema, SettingsJsonSchemaParams, VsCodeSettings, WorktreeId,
update_value_in_json_text, parse_json_with_comments, update_value_in_json_text,
}; };
/// A value that can be defined as a user setting. /// A value that can be defined as a user setting.
@ -57,14 +57,6 @@ pub trait Settings: 'static + Send + Sync {
where where
Self: Sized; Self: Sized;
fn json_schema(
generator: &mut SchemaGenerator,
_: &SettingsJsonSchemaParams,
_: &App,
) -> RootSchema {
generator.root_schema_for::<Self::FileContent>()
}
fn missing_default() -> anyhow::Error { fn missing_default() -> anyhow::Error {
anyhow::anyhow!("missing default") anyhow::anyhow!("missing default")
} }
@ -253,12 +245,7 @@ trait AnySettingValue: 'static + Send + Sync {
fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)>; fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)>;
fn set_global_value(&mut self, value: Box<dyn Any>); fn set_global_value(&mut self, value: Box<dyn Any>);
fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>); fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>);
fn json_schema( fn json_schema(&self, generator: &mut schemars::SchemaGenerator) -> schemars::Schema;
&self,
generator: &mut SchemaGenerator,
_: &SettingsJsonSchemaParams,
cx: &App,
) -> RootSchema;
fn edits_for_update( fn edits_for_update(
&self, &self,
raw_settings: &serde_json::Value, raw_settings: &serde_json::Value,
@ -276,11 +263,11 @@ impl SettingsStore {
let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded(); let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded();
Self { Self {
setting_values: Default::default(), setting_values: Default::default(),
raw_default_settings: serde_json::json!({}), raw_default_settings: json!({}),
raw_global_settings: None, raw_global_settings: None,
raw_user_settings: serde_json::json!({}), raw_user_settings: json!({}),
raw_server_settings: None, raw_server_settings: None,
raw_extension_settings: serde_json::json!({}), raw_extension_settings: json!({}),
raw_local_settings: Default::default(), raw_local_settings: Default::default(),
raw_editorconfig_settings: BTreeMap::default(), raw_editorconfig_settings: BTreeMap::default(),
tab_size_callback: Default::default(), tab_size_callback: Default::default(),
@ -877,108 +864,151 @@ impl SettingsStore {
} }
pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams, cx: &App) -> Value { pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams, cx: &App) -> Value {
use schemars::{ let mut generator = schemars::generate::SchemaSettings::draft07().into_generator();
r#gen::SchemaSettings, let mut combined_schema = json!({
schema::{Schema, SchemaObject}, "type": "object",
}; "properties": {}
let settings = SchemaSettings::draft07().with(|settings| {
settings.option_add_null_type = true;
}); });
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() { for setting_value in self.setting_values.values() {
let setting_schema = setting_value.json_schema(&mut generator, schema_params, cx); let mut setting_schema = setting_value.json_schema(&mut generator);
combined_schema
.definitions
.extend(setting_schema.definitions);
let target_schema = if let Some(key) = setting_value.key() { if let Some(key) = setting_value.key() {
let key_schema = combined_schema if let Some(properties) = combined_schema.get_mut("properties") {
.schema if let Some(properties_obj) = properties.as_object_mut() {
.object() if let Some(target) = properties_obj.get_mut(key) {
.properties merge_schema(target, setting_schema.to_value());
.entry(key.to_string()) } else {
.or_insert_with(|| Schema::Object(SchemaObject::default())); properties_obj.insert(key.to_string(), setting_schema.to_value());
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);
}
} }
} }
} }
} 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); fn merge_schema(target: &mut serde_json::Value, source: serde_json::Value) {
overwrite(&mut target.string, source.string); let (Some(target_obj), serde_json::Value::Object(source_obj)) =
overwrite(&mut target.number, source.number); (target.as_object_mut(), source)
overwrite(&mut target.reference, source.reference); else {
overwrite(&mut target.array, source.array); return;
overwrite(&mut target.enum_values, source.enum_values); };
fn overwrite<T>(target: &mut Option<T>, source: Option<T>) { for (source_key, source_value) in source_obj {
if let Some(source) = source { match source_key.as_str() {
*target = Some(source); "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::<ParameterizedJsonSchema>() {
(parameterized_json_schema.add_and_get_ref)(&mut generator, schema_params, cx);
}
const ZED_SETTINGS: &str = "ZedSettings"; const ZED_SETTINGS: &str = "ZedSettings";
let RootSchema { let old_zed_settings_definition = generator
meta_schema, .definitions_mut()
schema: zed_settings_schema, .insert(ZED_SETTINGS.to_string(), combined_schema);
mut definitions, assert_eq!(old_zed_settings_definition, None);
} = combined_schema; let zed_settings_ref = schemars::Schema::new_ref(format!("#/definitions/{ZED_SETTINGS}"));
definitions.insert(ZED_SETTINGS.to_string(), zed_settings_schema.into());
let zed_settings_ref = 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 // settings file contents matches ZedSettings + overrides for each release stage
let mut root_schema = json!({ root_schema.insert(
"allOf": [ "allOf".to_string(),
json!([
zed_settings_ref, zed_settings_ref,
{ {
"properties": { "properties": {
@ -988,17 +1018,14 @@ impl SettingsStore {
"preview": zed_settings_ref, "preview": zed_settings_ref,
} }
} }
], ]),
"definitions": definitions, );
}); root_schema.insert(
"definitions".to_string(),
generator.take_definitions(true).into(),
);
if let Some(meta_schema) = meta_schema { root_schema.to_value()
if let Some(root_schema_object) = root_schema.as_object_mut() {
root_schema_object.insert("$schema".to_string(), meta_schema.into());
}
}
root_schema
} }
fn recompute_values( fn recompute_values(
@ -1311,13 +1338,8 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
} }
} }
fn json_schema( fn json_schema(&self, generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
&self, T::FileContent::json_schema(generator)
generator: &mut SchemaGenerator,
params: &SettingsJsonSchemaParams,
cx: &App,
) -> RootSchema {
T::json_schema(generator, params, cx)
} }
fn edits_for_update( fn edits_for_update(

View file

@ -2,7 +2,9 @@ use std::sync::Arc;
use gpui::{App, FontFeatures, FontWeight}; use gpui::{App, FontFeatures, FontWeight};
use settings::{EditableSettingControl, Settings}; use settings::{EditableSettingControl, Settings};
use theme::{FontFamilyCache, SystemAppearance, ThemeMode, ThemeRegistry, ThemeSettings}; use theme::{
FontFamilyCache, FontFamilyName, SystemAppearance, ThemeMode, ThemeRegistry, ThemeSettings,
};
use ui::{ use ui::{
CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup, CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup,
ToggleButton, prelude::*, ToggleButton, prelude::*,
@ -189,7 +191,7 @@ impl EditableSettingControl for UiFontFamilyControl {
value: Self::Value, value: Self::Value,
_cx: &App, _cx: &App,
) { ) {
settings.ui_font_family = Some(value.to_string()); settings.ui_font_family = Some(FontFamilyName(value.into()));
} }
} }

View file

@ -1,11 +1,8 @@
use collections::HashMap; use collections::HashMap;
use schemars::{ use schemars::{JsonSchema, json_schema};
JsonSchema,
r#gen::SchemaSettings,
schema::{ObjectValidation, Schema, SchemaObject},
};
use serde::Deserialize; use serde::Deserialize;
use serde_json_lenient::Value; use serde_json_lenient::Value;
use std::borrow::Cow;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct VsSnippetsFile { pub struct VsSnippetsFile {
@ -15,29 +12,25 @@ pub struct VsSnippetsFile {
impl VsSnippetsFile { impl VsSnippetsFile {
pub fn generate_json_schema() -> Value { pub fn generate_json_schema() -> Value {
let schema = SchemaSettings::draft07() let schema = schemars::generate::SchemaSettings::draft07()
.with(|settings| settings.option_add_null_type = false)
.into_generator() .into_generator()
.into_root_schema_for::<Self>(); .root_schema_for::<Self>();
serde_json_lenient::to_value(schema).unwrap() serde_json_lenient::to_value(schema).unwrap()
} }
} }
impl JsonSchema for VsSnippetsFile { impl JsonSchema for VsSnippetsFile {
fn schema_name() -> String { fn schema_name() -> Cow<'static, str> {
"VsSnippetsFile".into() "VsSnippetsFile".into()
} }
fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> Schema { fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
SchemaObject { let snippet_schema = generator.subschema_for::<VsCodeSnippet>();
object: Some(Box::new(ObjectValidation { json_schema!({
additional_properties: Some(Box::new(r#gen.subschema_for::<VsCodeSnippet>())), "type": "object",
..Default::default() "additionalProperties": snippet_schema
})), })
..Default::default()
}
.into()
} }
} }

View file

@ -287,7 +287,8 @@ pub struct DebugTaskFile(pub Vec<DebugScenario>);
impl DebugTaskFile { impl DebugTaskFile {
pub fn generate_json_schema(schemas: &AdapterSchemas) -> serde_json_lenient::Value { 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::<BuildTaskDefinition>();
let mut build_task_value = let mut build_task_value =
serde_json_lenient::to_value(&build_task_schema).unwrap_or_default(); serde_json_lenient::to_value(&build_task_schema).unwrap_or_default();

View file

@ -1,33 +1,6 @@
use schemars::{
SchemaGenerator,
schema::{ArrayValidation, InstanceType, Schema, SchemaObject, SingleOrVec, StringValidation},
};
use serde::de::{self, Deserializer, Visitor}; use serde::de::{self, Deserializer, Visitor};
use std::fmt; 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. /// Deserializes a non-empty string array.
pub fn non_empty_string_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error> pub fn non_empty_string_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where where

View file

@ -1,6 +1,6 @@
use anyhow::{Context as _, bail}; use anyhow::{Context as _, bail};
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use schemars::{JsonSchema, r#gen::SchemaSettings}; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::path::PathBuf; use std::path::PathBuf;
@ -9,8 +9,7 @@ use util::{ResultExt, truncate_and_remove_front};
use crate::{ use crate::{
AttachRequest, ResolvedTask, RevealTarget, Shell, SpawnInTerminal, TaskContext, TaskId, AttachRequest, ResolvedTask, RevealTarget, Shell, SpawnInTerminal, TaskContext, TaskId,
VariableName, ZED_VARIABLE_NAME_PREFIX, VariableName, ZED_VARIABLE_NAME_PREFIX, serde_helpers::non_empty_string_vec,
serde_helpers::{non_empty_string_vec, non_empty_string_vec_json_schema},
}; };
/// A template definition of a Zed task to run. /// A template definition of a Zed task to run.
@ -61,7 +60,7 @@ pub struct TaskTemplate {
/// Represents the tags which this template attaches to. /// 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. /// 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")] #[serde(default, deserialize_with = "non_empty_string_vec")]
#[schemars(schema_with = "non_empty_string_vec_json_schema")] #[schemars(length(min = 1))]
pub tags: Vec<String>, pub tags: Vec<String>,
/// Which shell to use when spawning the task. /// Which shell to use when spawning the task.
#[serde(default)] #[serde(default)]
@ -116,10 +115,9 @@ pub struct TaskTemplates(pub Vec<TaskTemplate>);
impl TaskTemplates { impl TaskTemplates {
/// Generates JSON schema of Tasks JSON template format. /// Generates JSON schema of Tasks JSON template format.
pub fn generate_json_schema() -> serde_json_lenient::Value { pub fn generate_json_schema() -> serde_json_lenient::Value {
let schema = SchemaSettings::draft07() let schema = schemars::generate::SchemaSettings::draft07()
.with(|settings| settings.option_add_null_type = false)
.into_generator() .into_generator()
.into_root_schema_for::<Self>(); .root_schema_for::<Self>();
serde_json_lenient::to_value(schema).unwrap() serde_json_lenient::to_value(schema).unwrap()
} }

View file

@ -2,14 +2,14 @@ use alacritty_terminal::vte::ansi::{
CursorShape as AlacCursorShape, CursorStyle as AlacCursorStyle, CursorShape as AlacCursorShape, CursorStyle as AlacCursorStyle,
}; };
use collections::HashMap; use collections::HashMap;
use gpui::{ use gpui::{AbsoluteLength, App, FontFallbacks, FontFeatures, FontWeight, Pixels, px};
AbsoluteLength, App, FontFallbacks, FontFeatures, FontWeight, Pixels, SharedString, px, use schemars::JsonSchema;
};
use schemars::{JsonSchema, r#gen::SchemaGenerator, schema::RootSchema};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use settings::{SettingsJsonSchemaParams, SettingsSources, add_references_to_properties};
use settings::SettingsSources;
use std::path::PathBuf; use std::path::PathBuf;
use task::Shell; use task::Shell;
use theme::FontFamilyName;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -29,7 +29,7 @@ pub struct TerminalSettings {
pub shell: Shell, pub shell: Shell,
pub working_directory: WorkingDirectory, pub working_directory: WorkingDirectory,
pub font_size: Option<Pixels>, pub font_size: Option<Pixels>,
pub font_family: Option<SharedString>, pub font_family: Option<FontFamilyName>,
pub font_fallbacks: Option<FontFallbacks>, pub font_fallbacks: Option<FontFallbacks>,
pub font_features: Option<FontFeatures>, pub font_features: Option<FontFeatures>,
pub font_weight: Option<FontWeight>, pub font_weight: Option<FontWeight>,
@ -147,13 +147,14 @@ pub struct TerminalSettingsContent {
/// ///
/// If this option is not included, /// If this option is not included,
/// the terminal will default to matching the buffer's font family. /// the terminal will default to matching the buffer's font family.
pub font_family: Option<String>, pub font_family: Option<FontFamilyName>,
/// Sets the terminal's font fallbacks. /// Sets the terminal's font fallbacks.
/// ///
/// If this option is not included, /// If this option is not included,
/// the terminal will default to matching the buffer's font fallbacks. /// the terminal will default to matching the buffer's font fallbacks.
pub font_fallbacks: Option<Vec<String>>, #[schemars(extend("uniqueItems" = true))]
pub font_fallbacks: Option<Vec<FontFamilyName>>,
/// Sets the terminal's line height. /// Sets the terminal's line height.
/// ///
@ -234,33 +235,13 @@ impl settings::Settings for TerminalSettings {
sources.json_merge() sources.json_merge()
} }
fn json_schema(
generator: &mut SchemaGenerator,
params: &SettingsJsonSchemaParams,
_: &App,
) -> RootSchema {
let mut root_schema = generator.root_schema_for::<Self::FileContent>();
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) { fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
let name = |s| format!("terminal.integrated.{s}"); let name = |s| format!("terminal.integrated.{s}");
vscode.f32_setting(&name("fontSize"), &mut current.font_size); 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(&name("copyOnSelection"), &mut current.copy_on_select);
vscode.bool_setting("macOptionIsMeta", &mut current.option_as_meta); vscode.bool_setting("macOptionIsMeta", &mut current.option_as_meta);
vscode.usize_setting("scrollback", &mut current.max_scroll_history_lines); vscode.usize_setting("scrollback", &mut current.max_scroll_history_lines);

View file

@ -682,11 +682,10 @@ impl Element for TerminalElement {
let terminal_settings = TerminalSettings::get_global(cx); let terminal_settings = TerminalSettings::get_global(cx);
let font_family = terminal_settings let font_family = terminal_settings.font_family.as_ref().map_or_else(
.font_family || settings.buffer_font.family.clone(),
.as_ref() |font_family| font_family.0.clone().into(),
.unwrap_or(&settings.buffer_font.family) );
.clone();
let font_fallbacks = terminal_settings let font_fallbacks = terminal_settings
.font_fallbacks .font_fallbacks

View file

@ -24,6 +24,7 @@ fs.workspace = true
futures.workspace = true futures.workspace = true
gpui.workspace = true gpui.workspace = true
indexmap.workspace = true indexmap.workspace = true
inventory.workspace = true
log.workspace = true log.workspace = true
palette = { workspace = true, default-features = false, features = ["std"] } palette = { workspace = true, default-features = false, features = ["std"] }
parking_lot.workspace = true parking_lot.workspace = true

View file

@ -4,12 +4,11 @@ use anyhow::Result;
use gpui::{FontStyle, FontWeight, HighlightStyle, Hsla, WindowBackgroundAppearance}; use gpui::{FontStyle, FontWeight, HighlightStyle, Hsla, WindowBackgroundAppearance};
use indexmap::IndexMap; use indexmap::IndexMap;
use palette::FromColor; use palette::FromColor;
use schemars::JsonSchema; use schemars::{JsonSchema, json_schema};
use schemars::r#gen::SchemaGenerator;
use schemars::schema::{Schema, SchemaObject};
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value; use serde_json::Value;
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use std::borrow::Cow;
use crate::{StatusColorsRefinement, ThemeColorsRefinement}; use crate::{StatusColorsRefinement, ThemeColorsRefinement};
@ -1502,30 +1501,15 @@ pub enum FontWeightContent {
} }
impl JsonSchema for FontWeightContent { impl JsonSchema for FontWeightContent {
fn schema_name() -> String { fn schema_name() -> Cow<'static, str> {
"FontWeightContent".to_owned() "FontWeightContent".into()
} }
fn is_referenceable() -> bool { fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
false json_schema!({
} "type": "integer",
"enum": [100, 200, 300, 400, 500, 600, 700, 800, 900]
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()
} }
} }

View file

@ -7,17 +7,12 @@ use anyhow::Result;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use gpui::{ use gpui::{
App, Context, Font, FontFallbacks, FontFeatures, FontStyle, FontWeight, Global, Pixels, App, Context, Font, FontFallbacks, FontFeatures, FontStyle, FontWeight, Global, Pixels,
Subscription, Window, px, SharedString, Subscription, Window, px,
}; };
use refineable::Refineable; use refineable::Refineable;
use schemars::{ use schemars::{JsonSchema, json_schema};
JsonSchema,
r#gen::SchemaGenerator,
schema::{InstanceType, Schema, SchemaObject},
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use settings::{ParameterizedJsonSchema, Settings, SettingsSources, replace_subschema};
use settings::{Settings, SettingsJsonSchemaParams, SettingsSources, add_references_to_properties};
use std::sync::Arc; use std::sync::Arc;
use util::ResultExt as _; use util::ResultExt as _;
@ -263,25 +258,19 @@ impl Global for AgentFontSize {}
#[serde(untagged)] #[serde(untagged)]
pub enum ThemeSelection { pub enum ThemeSelection {
/// A static theme selection, represented by a single theme name. /// 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]. /// A dynamic theme selection, which can change based the [ThemeMode].
Dynamic { Dynamic {
/// The mode used to determine which theme to use. /// The mode used to determine which theme to use.
#[serde(default)] #[serde(default)]
mode: ThemeMode, mode: ThemeMode,
/// The theme to use for light mode. /// The theme to use for light mode.
#[schemars(schema_with = "theme_name_ref")] light: ThemeName,
light: String,
/// The theme to use for dark mode. /// The theme to use for dark mode.
#[schemars(schema_with = "theme_name_ref")] dark: ThemeName,
dark: String,
}, },
} }
fn theme_name_ref(_: &mut SchemaGenerator) -> Schema {
Schema::new_ref("#/definitions/ThemeName".into())
}
// TODO: Rename ThemeMode -> ThemeAppearanceMode // TODO: Rename ThemeMode -> ThemeAppearanceMode
/// The mode use to select a theme. /// The mode use to select a theme.
/// ///
@ -306,13 +295,13 @@ impl ThemeSelection {
/// Returns the theme name for the selected [ThemeMode]. /// Returns the theme name for the selected [ThemeMode].
pub fn theme(&self, system_appearance: Appearance) -> &str { pub fn theme(&self, system_appearance: Appearance) -> &str {
match self { match self {
Self::Static(theme) => theme, Self::Static(theme) => &theme.0,
Self::Dynamic { mode, light, dark } => match mode { Self::Dynamic { mode, light, dark } => match mode {
ThemeMode::Light => light, ThemeMode::Light => &light.0,
ThemeMode::Dark => dark, ThemeMode::Dark => &dark.0,
ThemeMode::System => match system_appearance { ThemeMode::System => match system_appearance {
Appearance::Light => light, Appearance::Light => &light.0,
Appearance::Dark => dark, 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. /// Represents the selection of an icon theme, which can be either static or dynamic.
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(untagged)] #[serde(untagged)]
pub enum IconThemeSelection { pub enum IconThemeSelection {
/// A static icon theme selection, represented by a single icon theme name. /// 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`]. /// A dynamic icon theme selection, which can change based on the [`ThemeMode`].
Dynamic { Dynamic {
/// The mode used to determine which theme to use. /// The mode used to determine which theme to use.
#[serde(default)] #[serde(default)]
mode: ThemeMode, mode: ThemeMode,
/// The icon theme to use for light mode. /// The icon theme to use for light mode.
#[schemars(schema_with = "icon_theme_name_ref")] light: IconThemeName,
light: String,
/// The icon theme to use for dark mode. /// The icon theme to use for dark mode.
#[schemars(schema_with = "icon_theme_name_ref")] dark: IconThemeName,
dark: String,
}, },
} }
@ -355,13 +338,13 @@ impl IconThemeSelection {
/// Returns the icon theme name based on the given [`Appearance`]. /// Returns the icon theme name based on the given [`Appearance`].
pub fn icon_theme(&self, system_appearance: Appearance) -> &str { pub fn icon_theme(&self, system_appearance: Appearance) -> &str {
match self { match self {
Self::Static(theme) => theme, Self::Static(theme) => &theme.0,
Self::Dynamic { mode, light, dark } => match mode { Self::Dynamic { mode, light, dark } => match mode {
ThemeMode::Light => light, ThemeMode::Light => &light.0,
ThemeMode::Dark => dark, ThemeMode::Dark => &dark.0,
ThemeMode::System => match system_appearance { ThemeMode::System => match system_appearance {
Appearance::Light => light, Appearance::Light => &light.0,
Appearance::Dark => dark, Appearance::Dark => &dark.0,
}, },
}, },
} }
@ -384,11 +367,12 @@ pub struct ThemeSettingsContent {
pub ui_font_size: Option<f32>, pub ui_font_size: Option<f32>,
/// The name of a font to use for rendering in the UI. /// The name of a font to use for rendering in the UI.
#[serde(default)] #[serde(default)]
pub ui_font_family: Option<String>, pub ui_font_family: Option<FontFamilyName>,
/// The font fallbacks to use for rendering in the UI. /// The font fallbacks to use for rendering in the UI.
#[serde(default)] #[serde(default)]
#[schemars(default = "default_font_fallbacks")] #[schemars(default = "default_font_fallbacks")]
pub ui_font_fallbacks: Option<Vec<String>>, #[schemars(extend("uniqueItems" = true))]
pub ui_font_fallbacks: Option<Vec<FontFamilyName>>,
/// The OpenType features to enable for text in the UI. /// The OpenType features to enable for text in the UI.
#[serde(default)] #[serde(default)]
#[schemars(default = "default_font_features")] #[schemars(default = "default_font_features")]
@ -398,11 +382,11 @@ pub struct ThemeSettingsContent {
pub ui_font_weight: Option<f32>, pub ui_font_weight: Option<f32>,
/// The name of a font to use for rendering in text buffers. /// The name of a font to use for rendering in text buffers.
#[serde(default)] #[serde(default)]
pub buffer_font_family: Option<String>, pub buffer_font_family: Option<FontFamilyName>,
/// The font fallbacks to use for rendering in text buffers. /// The font fallbacks to use for rendering in text buffers.
#[serde(default)] #[serde(default)]
#[schemars(default = "default_font_fallbacks")] #[schemars(extend("uniqueItems" = true))]
pub buffer_font_fallbacks: Option<Vec<String>>, pub buffer_font_fallbacks: Option<Vec<FontFamilyName>>,
/// The default font size for rendering in text buffers. /// The default font size for rendering in text buffers.
#[serde(default)] #[serde(default)]
pub buffer_font_size: Option<f32>, pub buffer_font_size: Option<f32>,
@ -467,9 +451,9 @@ impl ThemeSettingsContent {
}, },
}; };
*theme_to_update = theme_name.to_string(); *theme_to_update = ThemeName(theme_name.into());
} else { } 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 { } 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 { } else {
self.theme = Some(ThemeSelection::Dynamic { self.theme = Some(ThemeSelection::Dynamic {
mode, mode,
light: ThemeSettings::DEFAULT_LIGHT_THEME.into(), light: ThemeName(ThemeSettings::DEFAULT_LIGHT_THEME.into()),
dark: ThemeSettings::DEFAULT_DARK_THEME.into(), dark: ThemeName(ThemeSettings::DEFAULT_DARK_THEME.into()),
}); });
} }
@ -539,7 +525,9 @@ impl ThemeSettingsContent {
} => *mode_to_update = mode, } => *mode_to_update = mode,
} }
} else { } 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 themes = ThemeRegistry::default_global(cx);
let system_appearance = SystemAppearance::default_global(cx); let system_appearance = SystemAppearance::default_global(cx);
fn font_fallbacks_from_settings(
fallbacks: Option<Vec<FontFamilyName>>,
) -> Option<FontFallbacks> {
fallbacks.map(|fallbacks| {
FontFallbacks::from_fonts(
fallbacks
.into_iter()
.map(|font_family| font_family.0.to_string())
.collect(),
)
})
}
let defaults = sources.default; let defaults = sources.default;
let mut this = Self { let mut this = Self {
ui_font_size: defaults.ui_font_size.unwrap().into(), ui_font_size: defaults.ui_font_size.unwrap().into(),
ui_font: Font { 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(), features: defaults.ui_font_features.clone().unwrap(),
fallbacks: defaults fallbacks: font_fallbacks_from_settings(defaults.ui_font_fallbacks.clone()),
.ui_font_fallbacks
.as_ref()
.map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
weight: defaults.ui_font_weight.map(FontWeight).unwrap(), weight: defaults.ui_font_weight.map(FontWeight).unwrap(),
style: Default::default(), style: Default::default(),
}, },
buffer_font: Font { buffer_font: Font {
family: defaults.buffer_font_family.as_ref().unwrap().clone().into(), family: defaults
features: defaults.buffer_font_features.clone().unwrap(), .buffer_font_family
fallbacks: defaults
.buffer_font_fallbacks
.as_ref() .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(), weight: defaults.buffer_font_weight.map(FontWeight).unwrap(),
style: FontStyle::default(), style: FontStyle::default(),
}, },
@ -872,26 +873,26 @@ impl settings::Settings for ThemeSettings {
} }
if let Some(value) = value.buffer_font_family.clone() { 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() { if let Some(value) = value.buffer_font_features.clone() {
this.buffer_font.features = value; this.buffer_font.features = value;
} }
if let Some(value) = value.buffer_font_fallbacks.clone() { 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 { if let Some(value) = value.buffer_font_weight {
this.buffer_font.weight = clamp_font_weight(value); this.buffer_font.weight = clamp_font_weight(value);
} }
if let Some(value) = value.ui_font_family.clone() { 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() { if let Some(value) = value.ui_font_features.clone() {
this.ui_font.features = value; this.ui_font.features = value;
} }
if let Some(value) = value.ui_font_fallbacks.clone() { 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 { if let Some(value) = value.ui_font_weight {
this.ui_font.weight = clamp_font_weight(value); this.ui_font.weight = clamp_font_weight(value);
@ -959,64 +960,73 @@ impl settings::Settings for ThemeSettings {
Ok(this) Ok(this)
} }
fn json_schema(
generator: &mut SchemaGenerator,
params: &SettingsJsonSchemaParams,
cx: &App,
) -> schemars::schema::RootSchema {
let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
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) { 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.fontWeight", &mut current.buffer_font_weight);
vscode.f32_setting("editor.fontSize", &mut current.buffer_font_size); 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? // 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<str>);
inventory::submit! {
ParameterizedJsonSchema {
add_and_get_ref: |generator, _params, cx| {
let schema = json_schema!({
"type": "string",
"enum": ThemeRegistry::global(cx).list_names(),
});
replace_subschema::<ThemeName>(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<str>);
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::<Vec<SharedString>>(),
});
replace_subschema::<IconThemeName>(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<str>);
inventory::submit! {
ParameterizedJsonSchema {
add_and_get_ref: |generator, params, _cx| {
let schema = json_schema!({
"type": "string",
"enum": params.font_names,
});
replace_subschema::<FontFamilyName>(generator, schema)
}
}
}
fn merge<T: Copy>(target: &mut T, value: Option<T>) { fn merge<T: Copy>(target: &mut T, value: Option<T>) {
if let Some(value) = value { if let Some(value) = value {
*target = value; *target = value;

View file

@ -711,7 +711,7 @@ mod test {
); );
cx.update_global(|store: &mut SettingsStore, cx| { cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |settings| { store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
settings.languages.insert( settings.languages.0.insert(
LanguageName::new("Rust"), LanguageName::new("Rust"),
LanguageSettingsContent { LanguageSettingsContent {
auto_indent_on_paste: Some(false), auto_indent_on_paste: Some(false),

View file

@ -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"] } 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"] } semver = { version = "1", features = ["serde"] }
serde = { version = "1", features = ["alloc", "derive", "rc"] } 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"] } sha1 = { version = "0.10", features = ["compress"] }
simd-adler32 = { version = "0.3" } simd-adler32 = { version = "0.3" }
smallvec = { version = "1", default-features = false, features = ["const_new", "serde", "union", "write"] } 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"] } semver = { version = "1", features = ["serde"] }
serde = { version = "1", features = ["alloc", "derive", "rc"] } serde = { version = "1", features = ["alloc", "derive", "rc"] }
serde_derive = { version = "1", features = ["deserialize_in_place"] } 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"] } sha1 = { version = "0.10", features = ["compress"] }
simd-adler32 = { version = "0.3" } simd-adler32 = { version = "0.3" }
smallvec = { version = "1", default-features = false, features = ["const_new", "serde", "union", "write"] } smallvec = { version = "1", default-features = false, features = ["const_new", "serde", "union", "write"] }