editor_settings_controls.rs

  1use std::sync::Arc;
  2
  3use gpui::{App, FontFeatures, FontWeight};
  4use project::project_settings::ProjectSettings;
  5use settings::{EditableSettingControl, Settings, SettingsContent};
  6use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
  7use ui::{
  8    CheckboxWithLabel, ContextMenu, DropdownMenu, SettingsContainer, SettingsGroup, prelude::*,
  9};
 10
 11use crate::EditorSettings;
 12
 13#[derive(IntoElement)]
 14pub struct EditorSettingsControls {}
 15
 16impl Default for EditorSettingsControls {
 17    fn default() -> Self {
 18        Self::new()
 19    }
 20}
 21
 22impl EditorSettingsControls {
 23    pub fn new() -> Self {
 24        Self {}
 25    }
 26}
 27
 28impl RenderOnce for EditorSettingsControls {
 29    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
 30        SettingsContainer::new()
 31            .child(
 32                SettingsGroup::new("Font")
 33                    .child(
 34                        h_flex()
 35                            .gap_2()
 36                            .justify_between()
 37                            .child(BufferFontFamilyControl)
 38                            .child(BufferFontWeightControl),
 39                    )
 40                    .child(BufferFontSizeControl)
 41                    .child(BufferFontLigaturesControl),
 42            )
 43            .child(SettingsGroup::new("Editor").child(InlineGitBlameControl))
 44            .child(
 45                SettingsGroup::new("Gutter").child(
 46                    h_flex()
 47                        .gap_2()
 48                        .justify_between()
 49                        .child(LineNumbersControl)
 50                        .child(RelativeLineNumbersControl),
 51                ),
 52            )
 53    }
 54}
 55
 56#[derive(IntoElement)]
 57struct BufferFontFamilyControl;
 58
 59impl EditableSettingControl for BufferFontFamilyControl {
 60    type Value = SharedString;
 61
 62    fn name(&self) -> SharedString {
 63        "Buffer Font Family".into()
 64    }
 65
 66    fn read(cx: &App) -> Self::Value {
 67        let settings = ThemeSettings::get_global(cx);
 68        settings.buffer_font.family.clone()
 69    }
 70
 71    fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
 72        settings.theme.buffer_font_family = Some(FontFamilyName(value.into()));
 73    }
 74}
 75
 76impl RenderOnce for BufferFontFamilyControl {
 77    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
 78        let value = Self::read(cx);
 79
 80        h_flex()
 81            .gap_2()
 82            .child(Icon::new(IconName::Font))
 83            .child(DropdownMenu::new(
 84                "buffer-font-family",
 85                value,
 86                ContextMenu::build(window, cx, |mut menu, _, cx| {
 87                    let font_family_cache = FontFamilyCache::global(cx);
 88
 89                    for font_name in font_family_cache.list_font_families(cx) {
 90                        menu = menu.custom_entry(
 91                            {
 92                                let font_name = font_name.clone();
 93                                move |_window, _cx| Label::new(font_name.clone()).into_any_element()
 94                            },
 95                            {
 96                                let font_name = font_name.clone();
 97                                move |_window, cx| {
 98                                    Self::write(font_name.clone(), cx);
 99                                }
100                            },
101                        )
102                    }
103
104                    menu
105                }),
106            ))
107    }
108}
109
110#[derive(IntoElement)]
111struct BufferFontSizeControl;
112
113impl EditableSettingControl for BufferFontSizeControl {
114    type Value = Pixels;
115
116    fn name(&self) -> SharedString {
117        "Buffer Font Size".into()
118    }
119
120    fn read(cx: &App) -> Self::Value {
121        ThemeSettings::get_global(cx).buffer_font_size(cx)
122    }
123
124    fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
125        settings.theme.buffer_font_size = Some(value.into());
126    }
127}
128
129impl RenderOnce for BufferFontSizeControl {
130    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
131        let _value = Self::read(cx);
132
133        h_flex()
134            .gap_2()
135            .child(Icon::new(IconName::FontSize))
136            .child(div()) // TODO: Re-evaluate this whole crate once settings UI is complete
137    }
138}
139
140#[derive(IntoElement)]
141struct BufferFontWeightControl;
142
143impl EditableSettingControl for BufferFontWeightControl {
144    type Value = FontWeight;
145
146    fn name(&self) -> SharedString {
147        "Buffer Font Weight".into()
148    }
149
150    fn read(cx: &App) -> Self::Value {
151        let settings = ThemeSettings::get_global(cx);
152        settings.buffer_font.weight
153    }
154
155    fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
156        settings.theme.buffer_font_weight = Some(value.0);
157    }
158}
159
160impl RenderOnce for BufferFontWeightControl {
161    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
162        let value = Self::read(cx);
163
164        h_flex()
165            .gap_2()
166            .child(Icon::new(IconName::FontWeight))
167            .child(DropdownMenu::new(
168                "buffer-font-weight",
169                value.0.to_string(),
170                ContextMenu::build(window, cx, |mut menu, _window, _cx| {
171                    for weight in FontWeight::ALL {
172                        menu = menu.custom_entry(
173                            move |_window, _cx| Label::new(weight.0.to_string()).into_any_element(),
174                            {
175                                move |_, cx| {
176                                    Self::write(weight, cx);
177                                }
178                            },
179                        )
180                    }
181
182                    menu
183                }),
184            ))
185    }
186}
187
188#[derive(IntoElement)]
189struct BufferFontLigaturesControl;
190
191impl EditableSettingControl for BufferFontLigaturesControl {
192    type Value = bool;
193
194    fn name(&self) -> SharedString {
195        "Buffer Font Ligatures".into()
196    }
197
198    fn read(cx: &App) -> Self::Value {
199        let settings = ThemeSettings::get_global(cx);
200        settings
201            .buffer_font
202            .features
203            .is_calt_enabled()
204            .unwrap_or(true)
205    }
206
207    fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
208        let value = if value { 1 } else { 0 };
209
210        let mut features = settings
211            .theme
212            .buffer_font_features
213            .as_ref()
214            .map(|features| features.tag_value_list().to_vec())
215            .unwrap_or_default();
216
217        if let Some(calt_index) = features.iter().position(|(tag, _)| tag == "calt") {
218            features[calt_index].1 = value;
219        } else {
220            features.push(("calt".into(), value));
221        }
222
223        settings.theme.buffer_font_features = Some(FontFeatures(Arc::new(features)));
224    }
225}
226
227impl RenderOnce for BufferFontLigaturesControl {
228    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
229        let value = Self::read(cx);
230
231        CheckboxWithLabel::new(
232            "buffer-font-ligatures",
233            Label::new(self.name()),
234            value.into(),
235            |selection, _, cx| {
236                Self::write(
237                    match selection {
238                        ToggleState::Selected => true,
239                        ToggleState::Unselected | ToggleState::Indeterminate => false,
240                    },
241                    cx,
242                );
243            },
244        )
245    }
246}
247
248#[derive(IntoElement)]
249struct InlineGitBlameControl;
250
251impl EditableSettingControl for InlineGitBlameControl {
252    type Value = bool;
253
254    fn name(&self) -> SharedString {
255        "Inline Git Blame".into()
256    }
257
258    fn read(cx: &App) -> Self::Value {
259        let settings = ProjectSettings::get_global(cx);
260        settings.git.inline_blame.enabled
261    }
262
263    fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
264        settings
265            .git
266            .get_or_insert_default()
267            .inline_blame
268            .get_or_insert_default()
269            .enabled = Some(value)
270    }
271}
272
273impl RenderOnce for InlineGitBlameControl {
274    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
275        let value = Self::read(cx);
276
277        CheckboxWithLabel::new(
278            "inline-git-blame",
279            Label::new(self.name()),
280            value.into(),
281            |selection, _, cx| {
282                Self::write(
283                    match selection {
284                        ToggleState::Selected => true,
285                        ToggleState::Unselected | ToggleState::Indeterminate => false,
286                    },
287                    cx,
288                );
289            },
290        )
291    }
292}
293
294#[derive(IntoElement)]
295struct LineNumbersControl;
296
297impl EditableSettingControl for LineNumbersControl {
298    type Value = bool;
299
300    fn name(&self) -> SharedString {
301        "Line Numbers".into()
302    }
303
304    fn read(cx: &App) -> Self::Value {
305        let settings = EditorSettings::get_global(cx);
306        settings.gutter.line_numbers
307    }
308
309    fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
310        settings.editor.gutter.get_or_insert_default().line_numbers = Some(value);
311    }
312}
313
314impl RenderOnce for LineNumbersControl {
315    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
316        let value = Self::read(cx);
317
318        CheckboxWithLabel::new(
319            "line-numbers",
320            Label::new(self.name()),
321            value.into(),
322            |selection, _, cx| {
323                Self::write(
324                    match selection {
325                        ToggleState::Selected => true,
326                        ToggleState::Unselected | ToggleState::Indeterminate => false,
327                    },
328                    cx,
329                );
330            },
331        )
332    }
333}
334
335#[derive(IntoElement)]
336struct RelativeLineNumbersControl;
337
338impl EditableSettingControl for RelativeLineNumbersControl {
339    type Value = bool;
340
341    fn name(&self) -> SharedString {
342        "Relative Line Numbers".into()
343    }
344
345    fn read(cx: &App) -> Self::Value {
346        let settings = EditorSettings::get_global(cx);
347        settings.relative_line_numbers
348    }
349
350    fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
351        settings.editor.relative_line_numbers = Some(value);
352    }
353}
354
355impl RenderOnce for RelativeLineNumbersControl {
356    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
357        let value = Self::read(cx);
358
359        DropdownMenu::new(
360            "relative-line-numbers",
361            if value { "Relative" } else { "Ascending" },
362            ContextMenu::build(window, cx, |menu, _window, _cx| {
363                menu.custom_entry(
364                    |_window, _cx| Label::new("Ascending").into_any_element(),
365                    move |_, cx| Self::write(false, cx),
366                )
367                .custom_entry(
368                    |_window, _cx| Label::new("Relative").into_any_element(),
369                    move |_, cx| Self::write(true, cx),
370                )
371            }),
372        )
373    }
374}