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