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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -159,8 +159,8 @@ pub(crate) fn derive_action(input: TokenStream) -> TokenStream {
}
fn action_json_schema(
_generator: &mut gpui::private::schemars::r#gen::SchemaGenerator,
) -> Option<gpui::private::schemars::schema::Schema> {
_generator: &mut gpui::private::schemars::SchemaGenerator,
) -> Option<gpui::private::schemars::Schema> {
#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);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.languages
.0
.entry(language.name())
.or_default()
.show_edit_predictions = Some(!show_edit_predictions);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -269,10 +269,9 @@ impl JsonLspAdapter {
#[cfg(debug_assertions)]
fn generate_inspector_style_schema() -> serde_json_lenient::Value {
let schema = schemars::r#gen::SchemaSettings::draft07()
.with(|settings| settings.option_add_null_type = false)
let schema = schemars::generate::SchemaSettings::draft07()
.into_generator()
.into_root_schema_for::<gpui::StyleRefinement>();
.root_schema_for::<gpui::StyleRefinement>();
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 parking_lot::{Mutex, RwLock};
use postage::{barrier, prelude::Stream};
use schemars::{
JsonSchema,
r#gen::SchemaGenerator,
schema::{InstanceType, Schema, SchemaObject},
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde_json::{Value, json, value::RawValue};
use smol::{
@ -130,7 +126,10 @@ impl LanguageServerId {
}
/// A name of a language server.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, JsonSchema,
)]
#[serde(transparent)]
pub struct LanguageServerName(pub SharedString);
impl std::fmt::Display for LanguageServerName {
@ -151,20 +150,6 @@ impl AsRef<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 {
pub const fn new_static(s: &'static str) -> Self {
Self(SharedString::new_static(s))

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,7 @@ use futures::{FutureExt, StreamExt, channel::mpsc, future::LocalBoxFuture};
use gpui::{App, AsyncApp, BorrowAppContext, Global, Task, UpdateGlobal};
use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name};
use schemars::{JsonSchema, r#gen::SchemaGenerator, schema::RootSchema};
use schemars::{JsonSchema, json_schema};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde_json::{Value, json};
use smallvec::SmallVec;
@ -24,8 +24,8 @@ use util::{ResultExt as _, merge_non_null_json_value_into};
pub type EditorconfigProperties = ec4rs::Properties;
use crate::{
SettingsJsonSchemaParams, VsCodeSettings, WorktreeId, parse_json_with_comments,
update_value_in_json_text,
ParameterizedJsonSchema, SettingsJsonSchemaParams, VsCodeSettings, WorktreeId,
parse_json_with_comments, update_value_in_json_text,
};
/// A value that can be defined as a user setting.
@ -57,14 +57,6 @@ pub trait Settings: 'static + Send + Sync {
where
Self: Sized;
fn json_schema(
generator: &mut SchemaGenerator,
_: &SettingsJsonSchemaParams,
_: &App,
) -> RootSchema {
generator.root_schema_for::<Self::FileContent>()
}
fn missing_default() -> anyhow::Error {
anyhow::anyhow!("missing default")
}
@ -253,12 +245,7 @@ trait AnySettingValue: 'static + Send + Sync {
fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &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 json_schema(
&self,
generator: &mut SchemaGenerator,
_: &SettingsJsonSchemaParams,
cx: &App,
) -> RootSchema;
fn json_schema(&self, generator: &mut schemars::SchemaGenerator) -> schemars::Schema;
fn edits_for_update(
&self,
raw_settings: &serde_json::Value,
@ -276,11 +263,11 @@ impl SettingsStore {
let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded();
Self {
setting_values: Default::default(),
raw_default_settings: serde_json::json!({}),
raw_default_settings: json!({}),
raw_global_settings: None,
raw_user_settings: serde_json::json!({}),
raw_user_settings: json!({}),
raw_server_settings: None,
raw_extension_settings: serde_json::json!({}),
raw_extension_settings: json!({}),
raw_local_settings: Default::default(),
raw_editorconfig_settings: BTreeMap::default(),
tab_size_callback: Default::default(),
@ -877,108 +864,151 @@ impl SettingsStore {
}
pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams, cx: &App) -> Value {
use schemars::{
r#gen::SchemaSettings,
schema::{Schema, SchemaObject},
};
let settings = SchemaSettings::draft07().with(|settings| {
settings.option_add_null_type = true;
let mut generator = schemars::generate::SchemaSettings::draft07().into_generator();
let mut combined_schema = json!({
"type": "object",
"properties": {}
});
let mut generator = SchemaGenerator::new(settings);
let mut combined_schema = RootSchema::default();
// Merge together settings schemas, similarly to json schema's "allOf". This merging is
// recursive, though at time of writing this recursive nature isn't used very much. An
// example of it is the schema for `jupyter` having contribution from both `EditorSettings`
// and `JupyterSettings`.
//
// This logic could be removed in favor of "allOf", but then there isn't the opportunity to
// validate and fully control the merge.
for setting_value in self.setting_values.values() {
let setting_schema = setting_value.json_schema(&mut generator, schema_params, cx);
combined_schema
.definitions
.extend(setting_schema.definitions);
let mut setting_schema = setting_value.json_schema(&mut generator);
let target_schema = if let Some(key) = setting_value.key() {
let key_schema = combined_schema
.schema
.object()
.properties
.entry(key.to_string())
.or_insert_with(|| Schema::Object(SchemaObject::default()));
if let Schema::Object(key_schema) = key_schema {
key_schema
} else {
continue;
}
} else {
&mut combined_schema.schema
};
merge_schema(target_schema, setting_schema.schema);
}
fn merge_schema(target: &mut SchemaObject, mut source: SchemaObject) {
let source_subschemas = source.subschemas();
let target_subschemas = target.subschemas();
if let Some(all_of) = source_subschemas.all_of.take() {
target_subschemas
.all_of
.get_or_insert(Vec::new())
.extend(all_of);
}
if let Some(any_of) = source_subschemas.any_of.take() {
target_subschemas
.any_of
.get_or_insert(Vec::new())
.extend(any_of);
}
if let Some(one_of) = source_subschemas.one_of.take() {
target_subschemas
.one_of
.get_or_insert(Vec::new())
.extend(one_of);
}
if let Some(source) = source.object {
let target_properties = &mut target.object().properties;
for (key, value) in source.properties {
match target_properties.entry(key) {
btree_map::Entry::Vacant(e) => {
e.insert(value);
}
btree_map::Entry::Occupied(e) => {
if let (Schema::Object(target), Schema::Object(src)) =
(e.into_mut(), value)
{
merge_schema(target, src);
}
if let Some(key) = setting_value.key() {
if let Some(properties) = combined_schema.get_mut("properties") {
if let Some(properties_obj) = properties.as_object_mut() {
if let Some(target) = properties_obj.get_mut(key) {
merge_schema(target, setting_schema.to_value());
} else {
properties_obj.insert(key.to_string(), setting_schema.to_value());
}
}
}
} else {
setting_schema.remove("description");
setting_schema.remove("additionalProperties");
merge_schema(&mut combined_schema, setting_schema.to_value());
}
}
overwrite(&mut target.instance_type, source.instance_type);
overwrite(&mut target.string, source.string);
overwrite(&mut target.number, source.number);
overwrite(&mut target.reference, source.reference);
overwrite(&mut target.array, source.array);
overwrite(&mut target.enum_values, source.enum_values);
fn merge_schema(target: &mut serde_json::Value, source: serde_json::Value) {
let (Some(target_obj), serde_json::Value::Object(source_obj)) =
(target.as_object_mut(), source)
else {
return;
};
fn overwrite<T>(target: &mut Option<T>, source: Option<T>) {
if let Some(source) = source {
*target = Some(source);
for (source_key, source_value) in source_obj {
match source_key.as_str() {
"properties" => {
let serde_json::Value::Object(source_properties) = source_value else {
log::error!(
"bug: expected object for `{}` json schema field, but got: {}",
source_key,
source_value
);
continue;
};
let target_properties =
target_obj.entry(source_key.clone()).or_insert(json!({}));
let Some(target_properties) = target_properties.as_object_mut() else {
log::error!(
"bug: expected object for `{}` json schema field, but got: {}",
source_key,
target_properties
);
continue;
};
for (key, value) in source_properties {
if let Some(existing) = target_properties.get_mut(&key) {
merge_schema(existing, value);
} else {
target_properties.insert(key, value);
}
}
}
"allOf" | "anyOf" | "oneOf" => {
let serde_json::Value::Array(source_array) = source_value else {
log::error!(
"bug: expected array for `{}` json schema field, but got: {}",
source_key,
source_value,
);
continue;
};
let target_array =
target_obj.entry(source_key.clone()).or_insert(json!([]));
let Some(target_array) = target_array.as_array_mut() else {
log::error!(
"bug: expected array for `{}` json schema field, but got: {}",
source_key,
target_array,
);
continue;
};
target_array.extend(source_array);
}
"type"
| "$ref"
| "enum"
| "minimum"
| "maximum"
| "pattern"
| "description"
| "additionalProperties" => {
if let Some(old_value) =
target_obj.insert(source_key.clone(), source_value.clone())
{
if old_value != source_value {
log::error!(
"bug: while merging JSON schemas, \
mismatch `\"{}\": {}` (before was `{}`)",
source_key,
old_value,
source_value
);
}
}
}
_ => {
log::error!(
"bug: while merging settings JSON schemas, \
encountered unexpected `\"{}\": {}`",
source_key,
source_value
);
}
}
}
}
for parameterized_json_schema in inventory::iter::<ParameterizedJsonSchema>() {
(parameterized_json_schema.add_and_get_ref)(&mut generator, schema_params, cx);
}
const ZED_SETTINGS: &str = "ZedSettings";
let RootSchema {
meta_schema,
schema: zed_settings_schema,
mut definitions,
} = combined_schema;
definitions.insert(ZED_SETTINGS.to_string(), zed_settings_schema.into());
let zed_settings_ref = Schema::new_ref(format!("#/definitions/{ZED_SETTINGS}"));
let old_zed_settings_definition = generator
.definitions_mut()
.insert(ZED_SETTINGS.to_string(), combined_schema);
assert_eq!(old_zed_settings_definition, None);
let zed_settings_ref = schemars::Schema::new_ref(format!("#/definitions/{ZED_SETTINGS}"));
let mut root_schema = if let Some(meta_schema) = generator.settings().meta_schema.as_ref() {
json_schema!({ "$schema": meta_schema.to_string() })
} else {
json_schema!({})
};
// settings file contents matches ZedSettings + overrides for each release stage
let mut root_schema = json!({
"allOf": [
root_schema.insert(
"allOf".to_string(),
json!([
zed_settings_ref,
{
"properties": {
@ -988,17 +1018,14 @@ impl SettingsStore {
"preview": zed_settings_ref,
}
}
],
"definitions": definitions,
});
]),
);
root_schema.insert(
"definitions".to_string(),
generator.take_definitions(true).into(),
);
if let Some(meta_schema) = meta_schema {
if let Some(root_schema_object) = root_schema.as_object_mut() {
root_schema_object.insert("$schema".to_string(), meta_schema.into());
}
}
root_schema
root_schema.to_value()
}
fn recompute_values(
@ -1311,13 +1338,8 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
}
}
fn json_schema(
&self,
generator: &mut SchemaGenerator,
params: &SettingsJsonSchemaParams,
cx: &App,
) -> RootSchema {
T::json_schema(generator, params, cx)
fn json_schema(&self, generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
T::FileContent::json_schema(generator)
}
fn edits_for_update(

View file

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

View file

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

View file

@ -287,7 +287,8 @@ pub struct DebugTaskFile(pub Vec<DebugScenario>);
impl DebugTaskFile {
pub fn generate_json_schema(schemas: &AdapterSchemas) -> serde_json_lenient::Value {
let build_task_schema = schemars::schema_for!(BuildTaskDefinition);
let mut generator = schemars::generate::SchemaSettings::draft07().into_generator();
let build_task_schema = generator.root_schema_for::<BuildTaskDefinition>();
let mut build_task_value =
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 std::fmt;
/// Generates a JSON schema for a non-empty string array.
pub fn non_empty_string_vec_json_schema(_: &mut SchemaGenerator) -> Schema {
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::Array.into()),
array: Some(Box::new(ArrayValidation {
unique_items: Some(true),
items: Some(SingleOrVec::Single(Box::new(Schema::Object(
SchemaObject {
instance_type: Some(InstanceType::String.into()),
string: Some(Box::new(StringValidation {
min_length: Some(1), // Ensures string in the array is non-empty
..Default::default()
})),
..Default::default()
},
)))),
..Default::default()
})),
format: Some("vec-of-non-empty-strings".to_string()), // Use a custom format keyword
..Default::default()
})
}
/// Deserializes a non-empty string array.
pub fn non_empty_string_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -711,7 +711,7 @@ mod test {
);
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
settings.languages.insert(
settings.languages.0.insert(
LanguageName::new("Rust"),
LanguageSettingsContent {
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"] }
semver = { version = "1", features = ["serde"] }
serde = { version = "1", features = ["alloc", "derive", "rc"] }
serde_json = { version = "1", features = ["preserve_order", "raw_value", "unbounded_depth"] }
serde_json = { version = "1", features = ["alloc", "preserve_order", "raw_value", "unbounded_depth"] }
sha1 = { version = "0.10", features = ["compress"] }
simd-adler32 = { version = "0.3" }
smallvec = { version = "1", default-features = false, features = ["const_new", "serde", "union", "write"] }
@ -244,7 +244,7 @@ sea-query-binder = { version = "0.7", default-features = false, features = ["pos
semver = { version = "1", features = ["serde"] }
serde = { version = "1", features = ["alloc", "derive", "rc"] }
serde_derive = { version = "1", features = ["deserialize_in_place"] }
serde_json = { version = "1", features = ["preserve_order", "raw_value", "unbounded_depth"] }
serde_json = { version = "1", features = ["alloc", "preserve_order", "raw_value", "unbounded_depth"] }
sha1 = { version = "0.10", features = ["compress"] }
simd-adler32 = { version = "0.3" }
smallvec = { version = "1", default-features = false, features = ["const_new", "serde", "union", "write"] }