Settings refactor (#38367)
Co-Authored-By: Ben K <ben@zed.dev> Co-Authored-By: Anthony <anthony@zed.dev> Co-Authored-By: Mikayla <mikayla@zed.dev> Release Notes: - settings: Major internal changes to settings. The primary user-facing effect is that some settings which did not make sense in project settings files are no-longer read from there. (For example the inline blame settings) --------- Co-authored-by: Ben Kunkle <ben@zed.dev> Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com> Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
co-authored by
Ben Kunkle
Mikayla Maki
Anthony
parent
0a9023bce0
commit
fcdab160f9
+719
-1500
File diff suppressed because it is too large
Load Diff
+349
-491
@@ -1,22 +1,23 @@
|
||||
use crate::fallback_themes::zed_default_dark;
|
||||
use crate::{
|
||||
Appearance, DEFAULT_ICON_THEME_NAME, IconTheme, IconThemeNotFoundError, SyntaxTheme, Theme,
|
||||
ThemeNotFoundError, ThemeRegistry, ThemeStyleContent,
|
||||
ThemeNotFoundError, ThemeRegistry, status_colors_refinement, syntax_overrides,
|
||||
theme_colors_refinement,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use gpui::{
|
||||
App, Context, Font, FontFallbacks, FontFeatures, FontStyle, FontWeight, Global, Pixels,
|
||||
SharedString, Subscription, Window, px,
|
||||
App, Context, Font, FontFallbacks, FontStyle, FontWeight, Global, Pixels, SharedString,
|
||||
Subscription, Window, px,
|
||||
};
|
||||
use refineable::Refineable;
|
||||
use schemars::{JsonSchema, json_schema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{ParameterizedJsonSchema, Settings, SettingsKey, SettingsSources, SettingsUi};
|
||||
pub use settings::{FontFamilyName, IconThemeName, ThemeMode, ThemeName};
|
||||
use settings::{ParameterizedJsonSchema, Settings, SettingsContent};
|
||||
use std::sync::Arc;
|
||||
use util::ResultExt as _;
|
||||
use util::schemars::replace_subschema;
|
||||
use util::{MergeFrom, ResultExt as _};
|
||||
|
||||
const MIN_FONT_SIZE: Pixels = px(6.0);
|
||||
const MAX_FONT_SIZE: Pixels = px(100.0);
|
||||
@@ -86,6 +87,16 @@ impl From<UiDensity> for String {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<settings::UiDensity> for UiDensity {
|
||||
fn from(val: settings::UiDensity) -> Self {
|
||||
match val {
|
||||
settings::UiDensity::Compact => Self::Compact,
|
||||
settings::UiDensity::Default => Self::Default,
|
||||
settings::UiDensity::Comfortable => Self::Comfortable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Customizable settings for the UI and theme system.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct ThemeSettings {
|
||||
@@ -119,9 +130,9 @@ pub struct ThemeSettings {
|
||||
/// Manual overrides for the active theme.
|
||||
///
|
||||
/// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
|
||||
pub experimental_theme_overrides: Option<ThemeStyleContent>,
|
||||
pub experimental_theme_overrides: Option<settings::ThemeStyleContent>,
|
||||
/// Manual overrides per theme
|
||||
pub theme_overrides: HashMap<String, ThemeStyleContent>,
|
||||
pub theme_overrides: HashMap<String, settings::ThemeStyleContent>,
|
||||
/// The current icon theme selection.
|
||||
pub icon_theme_selection: Option<IconThemeSelection>,
|
||||
/// The active icon theme.
|
||||
@@ -259,6 +270,45 @@ pub struct AgentFontSize(Pixels);
|
||||
|
||||
impl Global for AgentFontSize {}
|
||||
|
||||
inventory::submit! {
|
||||
ParameterizedJsonSchema {
|
||||
add_and_get_ref: |generator, _params, cx| {
|
||||
replace_subschema::<settings::ThemeName>(generator, || json_schema!({
|
||||
"type": "string",
|
||||
"enum": ThemeRegistry::global(cx).list_names(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inventory::submit! {
|
||||
ParameterizedJsonSchema {
|
||||
add_and_get_ref: |generator, _params, cx| {
|
||||
replace_subschema::<settings::IconThemeName>(generator, || json_schema!({
|
||||
"type": "string",
|
||||
"enum": ThemeRegistry::global(cx)
|
||||
.list_icon_themes()
|
||||
.into_iter()
|
||||
.map(|icon_theme| icon_theme.name)
|
||||
.collect::<Vec<SharedString>>(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inventory::submit! {
|
||||
ParameterizedJsonSchema {
|
||||
add_and_get_ref: |generator, params, _cx| {
|
||||
replace_subschema::<settings::FontFamilyName>(generator, || {
|
||||
json_schema!({
|
||||
"type": "string",
|
||||
"enum": params.font_names,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the selection of a theme, which can be either static or dynamic.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(untagged)]
|
||||
@@ -277,24 +327,15 @@ pub enum ThemeSelection {
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: Rename ThemeMode -> ThemeAppearanceMode
|
||||
/// The mode use to select a theme.
|
||||
///
|
||||
/// `Light` and `Dark` will select their respective themes.
|
||||
///
|
||||
/// `System` will select the theme based on the system's appearance.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ThemeMode {
|
||||
/// Use the specified `light` theme.
|
||||
Light,
|
||||
|
||||
/// Use the specified `dark` theme.
|
||||
Dark,
|
||||
|
||||
/// Use the theme based on the system's appearance.
|
||||
#[default]
|
||||
System,
|
||||
impl From<settings::ThemeSelection> for ThemeSelection {
|
||||
fn from(selection: settings::ThemeSelection) -> Self {
|
||||
match selection {
|
||||
settings::ThemeSelection::Static(theme) => ThemeSelection::Static(theme),
|
||||
settings::ThemeSelection::Dynamic { mode, light, dark } => {
|
||||
ThemeSelection::Dynamic { mode, light, dark }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ThemeSelection {
|
||||
@@ -323,15 +364,13 @@ impl ThemeSelection {
|
||||
}
|
||||
|
||||
/// Represents the selection of an icon theme, which can be either static or dynamic.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(untagged)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum IconThemeSelection {
|
||||
/// A static icon theme selection, represented by a single icon theme name.
|
||||
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.
|
||||
light: IconThemeName,
|
||||
@@ -340,6 +379,17 @@ pub enum IconThemeSelection {
|
||||
},
|
||||
}
|
||||
|
||||
impl From<settings::IconThemeSelection> for IconThemeSelection {
|
||||
fn from(selection: settings::IconThemeSelection) -> Self {
|
||||
match selection {
|
||||
settings::IconThemeSelection::Static(theme) => IconThemeSelection::Static(theme),
|
||||
settings::IconThemeSelection::Dynamic { mode, light, dark } => {
|
||||
IconThemeSelection::Dynamic { mode, light, dark }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IconThemeSelection {
|
||||
/// Returns the icon theme name based on the given [`Appearance`].
|
||||
pub fn icon_theme(&self, system_appearance: Appearance) -> &str {
|
||||
@@ -365,189 +415,117 @@ impl IconThemeSelection {
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings for rendering text in UI and text buffers.
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
|
||||
#[settings_key(None)]
|
||||
pub struct ThemeSettingsContent {
|
||||
/// The default font size for text in the UI.
|
||||
#[serde(default)]
|
||||
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<FontFamilyName>,
|
||||
/// The font fallbacks to use for rendering in the UI.
|
||||
#[serde(default)]
|
||||
#[schemars(default = "default_font_fallbacks")]
|
||||
#[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")]
|
||||
pub ui_font_features: Option<FontFeatures>,
|
||||
/// The weight of the UI font in CSS units from 100 to 900.
|
||||
#[serde(default)]
|
||||
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<FontFamilyName>,
|
||||
/// The font fallbacks to use for rendering in text buffers.
|
||||
#[serde(default)]
|
||||
#[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>,
|
||||
/// The weight of the editor font in CSS units from 100 to 900.
|
||||
#[serde(default)]
|
||||
pub buffer_font_weight: Option<f32>,
|
||||
/// The buffer's line height.
|
||||
#[serde(default)]
|
||||
pub buffer_line_height: Option<BufferLineHeight>,
|
||||
/// The OpenType features to enable for rendering in text buffers.
|
||||
#[serde(default)]
|
||||
#[schemars(default = "default_font_features")]
|
||||
pub buffer_font_features: Option<FontFeatures>,
|
||||
/// The font size for the agent panel. Falls back to the UI font size if unset.
|
||||
#[serde(default)]
|
||||
pub agent_font_size: Option<Option<f32>>,
|
||||
/// The name of the Zed theme to use.
|
||||
#[serde(default)]
|
||||
pub theme: Option<ThemeSelection>,
|
||||
/// The name of the icon theme to use.
|
||||
#[serde(default)]
|
||||
pub icon_theme: Option<IconThemeSelection>,
|
||||
|
||||
/// UNSTABLE: Expect many elements to be broken.
|
||||
///
|
||||
// Controls the density of the UI.
|
||||
#[serde(rename = "unstable.ui_density", default)]
|
||||
pub ui_density: Option<UiDensity>,
|
||||
|
||||
/// How much to fade out unused code.
|
||||
#[serde(default)]
|
||||
pub unnecessary_code_fade: Option<f32>,
|
||||
|
||||
/// EXPERIMENTAL: Overrides for the current theme.
|
||||
///
|
||||
/// These values will override the ones on the current theme specified in `theme`.
|
||||
#[serde(rename = "experimental.theme_overrides", default)]
|
||||
pub experimental_theme_overrides: Option<ThemeStyleContent>,
|
||||
|
||||
/// Overrides per theme
|
||||
///
|
||||
/// These values will override the ones on the specified theme
|
||||
#[serde(default)]
|
||||
pub theme_overrides: HashMap<String, ThemeStyleContent>,
|
||||
}
|
||||
|
||||
fn default_font_features() -> Option<FontFeatures> {
|
||||
Some(FontFeatures::default())
|
||||
}
|
||||
|
||||
fn default_font_fallbacks() -> Option<FontFallbacks> {
|
||||
Some(FontFallbacks::default())
|
||||
}
|
||||
|
||||
impl ThemeSettingsContent {
|
||||
/// Sets the theme for the given appearance to the theme with the specified name.
|
||||
pub fn set_theme(&mut self, theme_name: impl Into<Arc<str>>, appearance: Appearance) {
|
||||
if let Some(selection) = self.theme.as_mut() {
|
||||
let theme_to_update = match selection {
|
||||
ThemeSelection::Static(theme) => theme,
|
||||
ThemeSelection::Dynamic { mode, light, dark } => match mode {
|
||||
ThemeMode::Light => light,
|
||||
ThemeMode::Dark => dark,
|
||||
ThemeMode::System => match appearance {
|
||||
Appearance::Light => light,
|
||||
Appearance::Dark => dark,
|
||||
},
|
||||
// impl ThemeSettingsContent {
|
||||
/// Sets the theme for the given appearance to the theme with the specified name.
|
||||
pub fn set_theme(
|
||||
current: &mut SettingsContent,
|
||||
theme_name: impl Into<Arc<str>>,
|
||||
appearance: Appearance,
|
||||
) {
|
||||
if let Some(selection) = current.theme.theme.as_mut() {
|
||||
let theme_to_update = match selection {
|
||||
settings::ThemeSelection::Static(theme) => theme,
|
||||
settings::ThemeSelection::Dynamic { mode, light, dark } => match mode {
|
||||
ThemeMode::Light => light,
|
||||
ThemeMode::Dark => dark,
|
||||
ThemeMode::System => match appearance {
|
||||
Appearance::Light => light,
|
||||
Appearance::Dark => dark,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
*theme_to_update = ThemeName(theme_name.into());
|
||||
} else {
|
||||
self.theme = Some(ThemeSelection::Static(ThemeName(theme_name.into())));
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the icon theme for the given appearance to the icon theme with the specified name.
|
||||
pub fn set_icon_theme(&mut self, icon_theme_name: String, appearance: Appearance) {
|
||||
if let Some(selection) = self.icon_theme.as_mut() {
|
||||
let icon_theme_to_update = match selection {
|
||||
IconThemeSelection::Static(theme) => theme,
|
||||
IconThemeSelection::Dynamic { mode, light, dark } => match mode {
|
||||
ThemeMode::Light => light,
|
||||
ThemeMode::Dark => dark,
|
||||
ThemeMode::System => match appearance {
|
||||
Appearance::Light => light,
|
||||
Appearance::Dark => dark,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
*icon_theme_to_update = IconThemeName(icon_theme_name.into());
|
||||
} else {
|
||||
self.icon_theme = Some(IconThemeSelection::Static(IconThemeName(
|
||||
icon_theme_name.into(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the mode for the theme.
|
||||
pub fn set_mode(&mut self, mode: ThemeMode) {
|
||||
if let Some(selection) = self.theme.as_mut() {
|
||||
match selection {
|
||||
ThemeSelection::Static(theme) => {
|
||||
// If the theme was previously set to a single static theme,
|
||||
// we don't know whether it was a light or dark theme, so we
|
||||
// just use it for both.
|
||||
self.theme = Some(ThemeSelection::Dynamic {
|
||||
mode,
|
||||
light: theme.clone(),
|
||||
dark: theme.clone(),
|
||||
});
|
||||
}
|
||||
ThemeSelection::Dynamic {
|
||||
mode: mode_to_update,
|
||||
..
|
||||
} => *mode_to_update = mode,
|
||||
}
|
||||
} else {
|
||||
self.theme = Some(ThemeSelection::Dynamic {
|
||||
mode,
|
||||
light: ThemeName(ThemeSettings::DEFAULT_LIGHT_THEME.into()),
|
||||
dark: ThemeName(ThemeSettings::DEFAULT_DARK_THEME.into()),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(selection) = self.icon_theme.as_mut() {
|
||||
match selection {
|
||||
IconThemeSelection::Static(icon_theme) => {
|
||||
// If the icon theme was previously set to a single static
|
||||
// theme, we don't know whether it was a light or dark
|
||||
// theme, so we just use it for both.
|
||||
self.icon_theme = Some(IconThemeSelection::Dynamic {
|
||||
mode,
|
||||
light: icon_theme.clone(),
|
||||
dark: icon_theme.clone(),
|
||||
});
|
||||
}
|
||||
IconThemeSelection::Dynamic {
|
||||
mode: mode_to_update,
|
||||
..
|
||||
} => *mode_to_update = mode,
|
||||
}
|
||||
} else {
|
||||
self.icon_theme = Some(IconThemeSelection::Static(IconThemeName(
|
||||
DEFAULT_ICON_THEME_NAME.into(),
|
||||
)));
|
||||
}
|
||||
*theme_to_update = ThemeName(theme_name.into());
|
||||
} else {
|
||||
current.theme.theme = Some(settings::ThemeSelection::Static(ThemeName(
|
||||
theme_name.into(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the icon theme for the given appearance to the icon theme with the specified name.
|
||||
pub fn set_icon_theme(
|
||||
current: &mut SettingsContent,
|
||||
icon_theme_name: String,
|
||||
appearance: Appearance,
|
||||
) {
|
||||
if let Some(selection) = current.theme.icon_theme.as_mut() {
|
||||
let icon_theme_to_update = match selection {
|
||||
settings::IconThemeSelection::Static(theme) => theme,
|
||||
settings::IconThemeSelection::Dynamic { mode, light, dark } => match mode {
|
||||
ThemeMode::Light => light,
|
||||
ThemeMode::Dark => dark,
|
||||
ThemeMode::System => match appearance {
|
||||
Appearance::Light => light,
|
||||
Appearance::Dark => dark,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
*icon_theme_to_update = IconThemeName(icon_theme_name.into());
|
||||
} else {
|
||||
current.theme.icon_theme = Some(settings::IconThemeSelection::Static(IconThemeName(
|
||||
icon_theme_name.into(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the mode for the theme.
|
||||
pub fn set_mode(content: &mut SettingsContent, mode: ThemeMode) {
|
||||
let theme = content.theme.as_mut();
|
||||
|
||||
if let Some(selection) = theme.theme.as_mut() {
|
||||
match selection {
|
||||
settings::ThemeSelection::Static(theme) => {
|
||||
// If the theme was previously set to a single static theme,
|
||||
// we don't know whether it was a light or dark theme, so we
|
||||
// just use it for both.
|
||||
*selection = settings::ThemeSelection::Dynamic {
|
||||
mode,
|
||||
light: theme.clone(),
|
||||
dark: theme.clone(),
|
||||
};
|
||||
}
|
||||
settings::ThemeSelection::Dynamic {
|
||||
mode: mode_to_update,
|
||||
..
|
||||
} => *mode_to_update = mode,
|
||||
}
|
||||
} else {
|
||||
theme.theme = Some(settings::ThemeSelection::Dynamic {
|
||||
mode,
|
||||
light: ThemeName(ThemeSettings::DEFAULT_LIGHT_THEME.into()),
|
||||
dark: ThemeName(ThemeSettings::DEFAULT_DARK_THEME.into()),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(selection) = theme.icon_theme.as_mut() {
|
||||
match selection {
|
||||
settings::IconThemeSelection::Static(icon_theme) => {
|
||||
// If the icon theme was previously set to a single static
|
||||
// theme, we don't know whether it was a light or dark
|
||||
// theme, so we just use it for both.
|
||||
*selection = settings::IconThemeSelection::Dynamic {
|
||||
mode,
|
||||
light: icon_theme.clone(),
|
||||
dark: icon_theme.clone(),
|
||||
};
|
||||
}
|
||||
settings::IconThemeSelection::Dynamic {
|
||||
mode: mode_to_update,
|
||||
..
|
||||
} => *mode_to_update = mode,
|
||||
}
|
||||
} else {
|
||||
theme.icon_theme = Some(settings::IconThemeSelection::Static(IconThemeName(
|
||||
DEFAULT_ICON_THEME_NAME.into(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
// }
|
||||
|
||||
/// The buffer's line height.
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
||||
pub enum BufferLineHeight {
|
||||
/// A less dense line height.
|
||||
#[default]
|
||||
@@ -555,21 +533,19 @@ pub enum BufferLineHeight {
|
||||
/// The default line height.
|
||||
Standard,
|
||||
/// A custom line height, where 1.0 is the font's height. Must be at least 1.0.
|
||||
Custom(#[serde(deserialize_with = "deserialize_line_height")] f32),
|
||||
Custom(f32),
|
||||
}
|
||||
|
||||
fn deserialize_line_height<'de, D>(deserializer: D) -> Result<f32, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value = f32::deserialize(deserializer)?;
|
||||
if value < 1.0 {
|
||||
return Err(serde::de::Error::custom(
|
||||
"buffer_line_height.custom must be at least 1.0",
|
||||
));
|
||||
impl From<settings::BufferLineHeight> for BufferLineHeight {
|
||||
fn from(value: settings::BufferLineHeight) -> Self {
|
||||
match value {
|
||||
settings::BufferLineHeight::Comfortable => BufferLineHeight::Comfortable,
|
||||
settings::BufferLineHeight::Standard => BufferLineHeight::Standard,
|
||||
settings::BufferLineHeight::Custom(line_height) => {
|
||||
BufferLineHeight::Custom(line_height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
impl BufferLineHeight {
|
||||
@@ -681,24 +657,22 @@ impl ThemeSettings {
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_theme(base_theme: &mut Theme, theme_overrides: &ThemeStyleContent) {
|
||||
fn modify_theme(base_theme: &mut Theme, theme_overrides: &settings::ThemeStyleContent) {
|
||||
if let Some(window_background_appearance) = theme_overrides.window_background_appearance {
|
||||
base_theme.styles.window_background_appearance = window_background_appearance.into();
|
||||
}
|
||||
let status_color_refinement = status_colors_refinement(&theme_overrides.status);
|
||||
|
||||
base_theme
|
||||
.styles
|
||||
.colors
|
||||
.refine(&theme_overrides.theme_colors_refinement());
|
||||
base_theme
|
||||
.styles
|
||||
.status
|
||||
.refine(&theme_overrides.status_colors_refinement());
|
||||
base_theme.styles.colors.refine(&theme_colors_refinement(
|
||||
&theme_overrides.colors,
|
||||
&status_color_refinement,
|
||||
));
|
||||
base_theme.styles.status.refine(&status_color_refinement);
|
||||
base_theme.styles.player.merge(&theme_overrides.players);
|
||||
base_theme.styles.accents.merge(&theme_overrides.accents);
|
||||
base_theme.styles.syntax = SyntaxTheme::merge(
|
||||
base_theme.styles.syntax.clone(),
|
||||
theme_overrides.syntax_overrides(),
|
||||
syntax_overrides(&theme_overrides),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -818,294 +792,178 @@ fn clamp_font_weight(weight: f32) -> FontWeight {
|
||||
FontWeight(weight.clamp(100., 950.))
|
||||
}
|
||||
|
||||
impl settings::Settings for ThemeSettings {
|
||||
type FileContent = ThemeSettingsContent;
|
||||
/// font fallback from settings
|
||||
pub fn font_fallbacks_from_settings(
|
||||
fallbacks: Option<Vec<settings::FontFamilyName>>,
|
||||
) -> Option<FontFallbacks> {
|
||||
fallbacks.map(|fallbacks| {
|
||||
FontFallbacks::from_fonts(
|
||||
fallbacks
|
||||
.into_iter()
|
||||
.map(|font_family| font_family.0.to_string())
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, cx: &mut App) -> Result<Self> {
|
||||
impl settings::Settings for ThemeSettings {
|
||||
fn from_defaults(content: &settings::SettingsContent, cx: &mut App) -> Self {
|
||||
let content = &content.theme;
|
||||
// todo(settings_refactor). This should *not* require cx...
|
||||
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(),
|
||||
let theme_selection: ThemeSelection = content.theme.clone().unwrap().into();
|
||||
let icon_theme_selection: IconThemeSelection = content.icon_theme.clone().unwrap().into();
|
||||
Self {
|
||||
ui_font_size: content.ui_font_size.unwrap().into(),
|
||||
ui_font: Font {
|
||||
family: defaults.ui_font_family.as_ref().unwrap().0.clone().into(),
|
||||
features: defaults.ui_font_features.clone().unwrap(),
|
||||
fallbacks: font_fallbacks_from_settings(defaults.ui_font_fallbacks.clone()),
|
||||
weight: defaults.ui_font_weight.map(FontWeight).unwrap(),
|
||||
family: content.ui_font_family.as_ref().unwrap().0.clone().into(),
|
||||
features: content.ui_font_features.clone().unwrap(),
|
||||
fallbacks: font_fallbacks_from_settings(content.ui_font_fallbacks.clone()),
|
||||
weight: content.ui_font_weight.map(FontWeight).unwrap(),
|
||||
style: Default::default(),
|
||||
},
|
||||
buffer_font: Font {
|
||||
family: defaults
|
||||
family: content
|
||||
.buffer_font_family
|
||||
.as_ref()
|
||||
.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(),
|
||||
features: content.buffer_font_features.clone().unwrap(),
|
||||
fallbacks: font_fallbacks_from_settings(content.buffer_font_fallbacks.clone()),
|
||||
weight: content.buffer_font_weight.map(FontWeight).unwrap(),
|
||||
style: FontStyle::default(),
|
||||
},
|
||||
buffer_font_size: defaults.buffer_font_size.unwrap().into(),
|
||||
buffer_line_height: defaults.buffer_line_height.unwrap(),
|
||||
agent_font_size: defaults.agent_font_size.flatten().map(Into::into),
|
||||
theme_selection: defaults.theme.clone(),
|
||||
buffer_font_size: content.buffer_font_size.unwrap().into(),
|
||||
buffer_line_height: content.buffer_line_height.unwrap().into(),
|
||||
agent_font_size: content.agent_font_size.flatten().map(Into::into),
|
||||
active_theme: themes
|
||||
.get(defaults.theme.as_ref().unwrap().theme(*system_appearance))
|
||||
.get(theme_selection.theme(*system_appearance))
|
||||
.or(themes.get(&zed_default_dark().name))
|
||||
.unwrap(),
|
||||
theme_selection: Some(theme_selection),
|
||||
experimental_theme_overrides: None,
|
||||
theme_overrides: HashMap::default(),
|
||||
icon_theme_selection: defaults.icon_theme.clone(),
|
||||
active_icon_theme: defaults
|
||||
.icon_theme
|
||||
.as_ref()
|
||||
.and_then(|selection| {
|
||||
themes
|
||||
.get_icon_theme(selection.icon_theme(*system_appearance))
|
||||
.ok()
|
||||
})
|
||||
.unwrap_or_else(|| themes.get_icon_theme(DEFAULT_ICON_THEME_NAME).unwrap()),
|
||||
ui_density: defaults.ui_density.unwrap_or(UiDensity::Default),
|
||||
unnecessary_code_fade: defaults.unnecessary_code_fade.unwrap_or(0.0),
|
||||
};
|
||||
|
||||
for value in sources
|
||||
.user
|
||||
.into_iter()
|
||||
.chain(sources.release_channel)
|
||||
.chain(sources.operating_system)
|
||||
.chain(sources.profile)
|
||||
.chain(sources.server)
|
||||
{
|
||||
if let Some(value) = value.ui_density {
|
||||
this.ui_density = value;
|
||||
}
|
||||
|
||||
if let Some(value) = value.buffer_font_family.clone() {
|
||||
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 = 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.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 = font_fallbacks_from_settings(Some(value));
|
||||
}
|
||||
if let Some(value) = value.ui_font_weight {
|
||||
this.ui_font.weight = clamp_font_weight(value);
|
||||
}
|
||||
|
||||
if let Some(value) = &value.theme {
|
||||
this.theme_selection = Some(value.clone());
|
||||
|
||||
let theme_name = value.theme(*system_appearance);
|
||||
|
||||
match themes.get(theme_name) {
|
||||
Ok(theme) => {
|
||||
this.active_theme = theme;
|
||||
}
|
||||
Err(err @ ThemeNotFoundError(_)) => {
|
||||
if themes.extensions_loaded() {
|
||||
log::error!("{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.experimental_theme_overrides
|
||||
.clone_from(&value.experimental_theme_overrides);
|
||||
this.theme_overrides.clone_from(&value.theme_overrides);
|
||||
this.apply_theme_overrides();
|
||||
|
||||
if let Some(value) = &value.icon_theme {
|
||||
this.icon_theme_selection = Some(value.clone());
|
||||
|
||||
let icon_theme_name = value.icon_theme(*system_appearance);
|
||||
|
||||
match themes.get_icon_theme(icon_theme_name) {
|
||||
Ok(icon_theme) => {
|
||||
this.active_icon_theme = icon_theme;
|
||||
}
|
||||
Err(err @ IconThemeNotFoundError(_)) => {
|
||||
if themes.extensions_loaded() {
|
||||
log::error!("{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
merge(
|
||||
&mut this.ui_font_size,
|
||||
value.ui_font_size.map(Into::into).map(clamp_font_size),
|
||||
);
|
||||
merge(
|
||||
&mut this.buffer_font_size,
|
||||
value.buffer_font_size.map(Into::into).map(clamp_font_size),
|
||||
);
|
||||
merge(
|
||||
&mut this.agent_font_size,
|
||||
value
|
||||
.agent_font_size
|
||||
.map(|value| value.map(Into::into).map(clamp_font_size)),
|
||||
);
|
||||
|
||||
merge(&mut this.buffer_line_height, value.buffer_line_height);
|
||||
|
||||
// Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely.
|
||||
merge(&mut this.unnecessary_code_fade, value.unnecessary_code_fade);
|
||||
this.unnecessary_code_fade = this.unnecessary_code_fade.clamp(0.0, 0.9);
|
||||
active_icon_theme: themes
|
||||
.get_icon_theme(icon_theme_selection.icon_theme(*system_appearance))
|
||||
.ok()
|
||||
.unwrap(),
|
||||
icon_theme_selection: Some(icon_theme_selection),
|
||||
ui_density: content.ui_density.unwrap_or_default().into(),
|
||||
unnecessary_code_fade: content.unnecessary_code_fade.unwrap(),
|
||||
}
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
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);
|
||||
fn refine(&mut self, content: &SettingsContent, cx: &mut App) {
|
||||
let value = &content.theme;
|
||||
|
||||
let themes = ThemeRegistry::default_global(cx);
|
||||
let system_appearance = SystemAppearance::default_global(cx);
|
||||
|
||||
self.ui_density
|
||||
.merge_from(&value.ui_density.map(Into::into));
|
||||
|
||||
if let Some(value) = value.buffer_font_family.clone() {
|
||||
self.buffer_font.family = value.0.into();
|
||||
}
|
||||
if let Some(value) = value.buffer_font_features.clone() {
|
||||
self.buffer_font.features = value;
|
||||
}
|
||||
if let Some(value) = value.buffer_font_fallbacks.clone() {
|
||||
self.buffer_font.fallbacks = font_fallbacks_from_settings(Some(value));
|
||||
}
|
||||
if let Some(value) = value.buffer_font_weight {
|
||||
self.buffer_font.weight = clamp_font_weight(value);
|
||||
}
|
||||
|
||||
if let Some(value) = value.ui_font_family.clone() {
|
||||
self.ui_font.family = value.0.into();
|
||||
}
|
||||
if let Some(value) = value.ui_font_features.clone() {
|
||||
self.ui_font.features = value;
|
||||
}
|
||||
if let Some(value) = value.ui_font_fallbacks.clone() {
|
||||
self.ui_font.fallbacks = font_fallbacks_from_settings(Some(value));
|
||||
}
|
||||
if let Some(value) = value.ui_font_weight {
|
||||
self.ui_font.weight = clamp_font_weight(value);
|
||||
}
|
||||
|
||||
if let Some(value) = &value.theme {
|
||||
self.theme_selection = Some(value.clone().into());
|
||||
|
||||
let theme_name = self
|
||||
.theme_selection
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.theme(*system_appearance);
|
||||
|
||||
match themes.get(theme_name) {
|
||||
Ok(theme) => {
|
||||
self.active_theme = theme;
|
||||
}
|
||||
Err(err @ ThemeNotFoundError(_)) => {
|
||||
if themes.extensions_loaded() {
|
||||
log::error!("{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.experimental_theme_overrides
|
||||
.clone_from(&value.experimental_theme_overrides);
|
||||
self.theme_overrides.clone_from(&value.theme_overrides);
|
||||
|
||||
self.apply_theme_overrides();
|
||||
|
||||
if let Some(value) = &value.icon_theme {
|
||||
self.icon_theme_selection = Some(value.clone().into());
|
||||
|
||||
let icon_theme_name = self
|
||||
.icon_theme_selection
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.icon_theme(*system_appearance);
|
||||
|
||||
match themes.get_icon_theme(icon_theme_name) {
|
||||
Ok(icon_theme) => {
|
||||
self.active_icon_theme = icon_theme;
|
||||
}
|
||||
Err(err @ IconThemeNotFoundError(_)) => {
|
||||
if themes.extensions_loaded() {
|
||||
log::error!("{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.ui_font_size
|
||||
.merge_from(&value.ui_font_size.map(Into::into).map(clamp_font_size));
|
||||
self.buffer_font_size
|
||||
.merge_from(&value.buffer_font_size.map(Into::into).map(clamp_font_size));
|
||||
self.agent_font_size.merge_from(
|
||||
&value
|
||||
.agent_font_size
|
||||
.map(|value| value.map(Into::into).map(clamp_font_size)),
|
||||
);
|
||||
|
||||
self.buffer_line_height
|
||||
.merge_from(&value.buffer_line_height.map(Into::into));
|
||||
|
||||
// Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely.
|
||||
self.unnecessary_code_fade
|
||||
.merge_from(&value.unnecessary_code_fade);
|
||||
self.unnecessary_code_fade = self.unnecessary_code_fade.clamp(0.0, 0.9);
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
|
||||
vscode.f32_setting("editor.fontWeight", &mut current.theme.buffer_font_weight);
|
||||
vscode.f32_setting("editor.fontSize", &mut current.theme.buffer_font_size);
|
||||
if let Some(font) = vscode.read_string("editor.font") {
|
||||
current.buffer_font_family = Some(FontFamilyName(font.into()));
|
||||
current.theme.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| {
|
||||
replace_subschema::<ThemeName>(generator, || json_schema!({
|
||||
"type": "string",
|
||||
"enum": ThemeRegistry::global(cx).list_names(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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| {
|
||||
replace_subschema::<IconThemeName>(generator, || json_schema!({
|
||||
"type": "string",
|
||||
"enum": ThemeRegistry::global(cx)
|
||||
.list_icon_themes()
|
||||
.into_iter()
|
||||
.map(|icon_theme| icon_theme.name)
|
||||
.collect::<Vec<SharedString>>(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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| {
|
||||
replace_subschema::<FontFamilyName>(generator, || {
|
||||
json_schema!({
|
||||
"type": "string",
|
||||
"enum": params.font_names,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn merge<T: Copy>(target: &mut T, value: Option<T>) {
|
||||
if let Some(value) = value {
|
||||
*target = value;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_buffer_line_height_deserialize_valid() {
|
||||
assert_eq!(
|
||||
serde_json::from_value::<BufferLineHeight>(json!("comfortable")).unwrap(),
|
||||
BufferLineHeight::Comfortable
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_value::<BufferLineHeight>(json!("standard")).unwrap(),
|
||||
BufferLineHeight::Standard
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_value::<BufferLineHeight>(json!({"custom": 1.0})).unwrap(),
|
||||
BufferLineHeight::Custom(1.0)
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_value::<BufferLineHeight>(json!({"custom": 1.5})).unwrap(),
|
||||
BufferLineHeight::Custom(1.5)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_line_height_deserialize_invalid() {
|
||||
assert!(
|
||||
serde_json::from_value::<BufferLineHeight>(json!({"custom": 0.99}))
|
||||
.err()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.contains("buffer_line_height.custom must be at least 1.0")
|
||||
);
|
||||
assert!(
|
||||
serde_json::from_value::<BufferLineHeight>(json!({"custom": 0.0}))
|
||||
.err()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.contains("buffer_line_height.custom must be at least 1.0")
|
||||
);
|
||||
assert!(
|
||||
serde_json::from_value::<BufferLineHeight>(json!({"custom": -1.0}))
|
||||
.err()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.contains("buffer_line_height.custom must be at least 1.0")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ use gpui::Hsla;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
AccentContent, amber, blue, cyan, gold, grass, indigo, iris, jade, lime, orange, pink, purple,
|
||||
tomato, try_parse_color,
|
||||
amber, blue, cyan, gold, grass, indigo, iris, jade, lime, orange, pink, purple, tomato,
|
||||
try_parse_color,
|
||||
};
|
||||
|
||||
/// A collection of colors that are used to color indent aware lines in the editor.
|
||||
@@ -66,7 +66,7 @@ impl AccentColors {
|
||||
}
|
||||
|
||||
/// Merges the given accent colors into this [`AccentColors`] instance.
|
||||
pub fn merge(&mut self, accent_colors: &[AccentContent]) {
|
||||
pub fn merge(&mut self, accent_colors: &[settings::AccentContent]) {
|
||||
if accent_colors.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
use gpui::Hsla;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
PlayerColorContent, amber, blue, jade, lime, orange, pink, purple, red, try_parse_color,
|
||||
};
|
||||
use crate::{amber, blue, jade, lime, orange, pink, purple, red, try_parse_color};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Default, PartialEq)]
|
||||
pub struct PlayerColor {
|
||||
@@ -152,7 +150,7 @@ impl PlayerColors {
|
||||
}
|
||||
|
||||
/// Merges the given player colors into this [`PlayerColors`] instance.
|
||||
pub fn merge(&mut self, user_player_colors: &[PlayerColorContent]) {
|
||||
pub fn merge(&mut self, user_player_colors: &[settings::PlayerColorContent]) {
|
||||
if user_player_colors.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@ pub use crate::scale::*;
|
||||
pub use crate::schema::*;
|
||||
pub use crate::settings::*;
|
||||
pub use crate::styles::*;
|
||||
pub use ::settings::{
|
||||
FontStyleContent, HighlightStyleContent, StatusColorsContent, ThemeColorsContent,
|
||||
ThemeStyleContent,
|
||||
};
|
||||
|
||||
/// Defines window border radius for platforms that use client side decorations.
|
||||
pub const CLIENT_SIDE_DECORATION_ROUNDING: Pixels = px(10.0);
|
||||
@@ -178,7 +182,7 @@ impl ThemeFamily {
|
||||
AppearanceContent::Light => StatusColors::light(),
|
||||
AppearanceContent::Dark => StatusColors::dark(),
|
||||
};
|
||||
let mut status_colors_refinement = theme.style.status_colors_refinement();
|
||||
let mut status_colors_refinement = status_colors_refinement(&theme.style.status);
|
||||
apply_status_color_defaults(&mut status_colors_refinement);
|
||||
refined_status_colors.refine(&status_colors_refinement);
|
||||
|
||||
@@ -192,7 +196,8 @@ impl ThemeFamily {
|
||||
AppearanceContent::Light => ThemeColors::light(),
|
||||
AppearanceContent::Dark => ThemeColors::dark(),
|
||||
};
|
||||
let mut theme_colors_refinement = theme.style.theme_colors_refinement();
|
||||
let mut theme_colors_refinement =
|
||||
theme_colors_refinement(&theme.style.colors, &status_colors_refinement);
|
||||
apply_theme_color_defaults(&mut theme_colors_refinement, &refined_player_colors);
|
||||
refined_theme_colors.refine(&theme_colors_refinement);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user