editor_settings_controls.rs

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