settings.rs

  1use crate::one_themes::one_dark;
  2use crate::{Theme, ThemeRegistry};
  3use anyhow::Result;
  4use gpui::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels};
  5use schemars::{
  6    gen::SchemaGenerator,
  7    schema::{InstanceType, Schema, SchemaObject},
  8    JsonSchema,
  9};
 10use serde::{Deserialize, Serialize};
 11use serde_json::Value;
 12use settings::{Settings, SettingsJsonSchemaParams};
 13use std::sync::Arc;
 14use util::ResultExt as _;
 15
 16const MIN_FONT_SIZE: Pixels = px(6.0);
 17const MIN_LINE_HEIGHT: f32 = 1.0;
 18
 19#[derive(Clone)]
 20pub struct ThemeSettings {
 21    pub ui_font_size: Pixels,
 22    pub buffer_font: Font,
 23    pub buffer_font_size: Pixels,
 24    pub buffer_line_height: BufferLineHeight,
 25    pub active_theme: Arc<Theme>,
 26}
 27
 28#[derive(Default)]
 29pub struct AdjustedBufferFontSize(Option<Pixels>);
 30
 31#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 32pub struct ThemeSettingsContent {
 33    #[serde(default)]
 34    pub ui_font_size: Option<f32>,
 35    #[serde(default)]
 36    pub buffer_font_family: Option<String>,
 37    #[serde(default)]
 38    pub buffer_font_size: Option<f32>,
 39    #[serde(default)]
 40    pub buffer_line_height: Option<BufferLineHeight>,
 41    #[serde(default)]
 42    pub buffer_font_features: Option<FontFeatures>,
 43    #[serde(default)]
 44    pub theme: Option<String>,
 45}
 46
 47#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
 48#[serde(rename_all = "snake_case")]
 49pub enum BufferLineHeight {
 50    #[default]
 51    Comfortable,
 52    Standard,
 53    Custom(f32),
 54}
 55
 56impl BufferLineHeight {
 57    pub fn value(&self) -> f32 {
 58        match self {
 59            BufferLineHeight::Comfortable => 1.618,
 60            BufferLineHeight::Standard => 1.3,
 61            BufferLineHeight::Custom(line_height) => *line_height,
 62        }
 63    }
 64}
 65
 66impl ThemeSettings {
 67    pub fn buffer_font_size(&self, cx: &mut AppContext) -> Pixels {
 68        let font_size = *cx
 69            .default_global::<AdjustedBufferFontSize>()
 70            .0
 71            .get_or_insert(self.buffer_font_size.into());
 72        font_size.max(MIN_FONT_SIZE)
 73    }
 74
 75    pub fn line_height(&self) -> f32 {
 76        f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
 77    }
 78}
 79
 80pub fn adjusted_font_size(size: Pixels, cx: &mut AppContext) -> Pixels {
 81    if let Some(adjusted_size) = cx.default_global::<AdjustedBufferFontSize>().0 {
 82        let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
 83        let delta = adjusted_size - buffer_font_size;
 84        size + delta
 85    } else {
 86        size
 87    }
 88    .max(MIN_FONT_SIZE)
 89}
 90
 91pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) {
 92    let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
 93    let adjusted_size = cx
 94        .default_global::<AdjustedBufferFontSize>()
 95        .0
 96        .get_or_insert(buffer_font_size);
 97    f(adjusted_size);
 98    *adjusted_size = (*adjusted_size).max(MIN_FONT_SIZE - buffer_font_size);
 99    cx.refresh();
100}
101
102pub fn reset_font_size(cx: &mut AppContext) {
103    if cx.has_global::<AdjustedBufferFontSize>() {
104        cx.global_mut::<AdjustedBufferFontSize>().0 = None;
105        cx.refresh();
106    }
107}
108
109impl settings::Settings for ThemeSettings {
110    const KEY: Option<&'static str> = None;
111
112    type FileContent = ThemeSettingsContent;
113
114    fn load(
115        defaults: &Self::FileContent,
116        user_values: &[&Self::FileContent],
117        cx: &mut AppContext,
118    ) -> Result<Self> {
119        let themes = cx.default_global::<Arc<ThemeRegistry>>();
120
121        let mut this = Self {
122            ui_font_size: defaults.ui_font_size.unwrap_or(16.).into(),
123            buffer_font: Font {
124                family: defaults.buffer_font_family.clone().unwrap().into(),
125                features: defaults.buffer_font_features.clone().unwrap(),
126                weight: FontWeight::default(),
127                style: FontStyle::default(),
128            },
129            buffer_font_size: defaults.buffer_font_size.unwrap().into(),
130            buffer_line_height: defaults.buffer_line_height.unwrap(),
131            active_theme: themes
132                .get(defaults.theme.as_ref().unwrap())
133                .or(themes.get(&one_dark().name))
134                .unwrap(),
135        };
136
137        for value in user_values.into_iter().copied().cloned() {
138            if let Some(value) = value.buffer_font_family {
139                this.buffer_font.family = value.into();
140            }
141            if let Some(value) = value.buffer_font_features {
142                this.buffer_font.features = value;
143            }
144
145            if let Some(value) = &value.theme {
146                if let Some(theme) = themes.get(value).log_err() {
147                    this.active_theme = theme;
148                }
149            }
150
151            merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into));
152            merge(
153                &mut this.buffer_font_size,
154                value.buffer_font_size.map(Into::into),
155            );
156            merge(&mut this.buffer_line_height, value.buffer_line_height);
157        }
158
159        Ok(this)
160    }
161
162    fn json_schema(
163        generator: &mut SchemaGenerator,
164        params: &SettingsJsonSchemaParams,
165        cx: &AppContext,
166    ) -> schemars::schema::RootSchema {
167        let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
168        let theme_names = cx
169            .global::<Arc<ThemeRegistry>>()
170            .list_names(params.staff_mode)
171            .map(|theme_name| Value::String(theme_name.to_string()))
172            .collect();
173
174        let theme_name_schema = SchemaObject {
175            instance_type: Some(InstanceType::String.into()),
176            enum_values: Some(theme_names),
177            ..Default::default()
178        };
179
180        root_schema
181            .definitions
182            .extend([("ThemeName".into(), theme_name_schema.into())]);
183
184        root_schema
185            .schema
186            .object
187            .as_mut()
188            .unwrap()
189            .properties
190            .extend([(
191                "theme".to_owned(),
192                Schema::new_ref("#/definitions/ThemeName".into()),
193            )]);
194
195        root_schema
196    }
197}
198
199fn merge<T: Copy>(target: &mut T, value: Option<T>) {
200    if let Some(value) = value {
201        *target = value;
202    }
203}