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, ThemeSettings};
  7use ui::{
  8    prelude::*, CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer,
  9    SettingsGroup,
 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(value.to_string());
 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.clone(),
 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        let settings = ThemeSettings::get_global(cx);
129        settings.buffer_font_size
130    }
131
132    fn apply(
133        settings: &mut <Self::Settings as Settings>::FileContent,
134        value: Self::Value,
135        _cx: &App,
136    ) {
137        settings.buffer_font_size = Some(value.into());
138    }
139}
140
141impl RenderOnce for BufferFontSizeControl {
142    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
143        let value = Self::read(cx);
144
145        h_flex()
146            .gap_2()
147            .child(Icon::new(IconName::FontSize))
148            .child(NumericStepper::new(
149                "buffer-font-size",
150                value.to_string(),
151                move |_, _, cx| {
152                    Self::write(value - px(1.), cx);
153                },
154                move |_, _, cx| {
155                    Self::write(value + px(1.), cx);
156                },
157            ))
158    }
159}
160
161#[derive(IntoElement)]
162struct BufferFontWeightControl;
163
164impl EditableSettingControl for BufferFontWeightControl {
165    type Value = FontWeight;
166    type Settings = ThemeSettings;
167
168    fn name(&self) -> SharedString {
169        "Buffer Font Weight".into()
170    }
171
172    fn read(cx: &App) -> Self::Value {
173        let settings = ThemeSettings::get_global(cx);
174        settings.buffer_font.weight
175    }
176
177    fn apply(
178        settings: &mut <Self::Settings as Settings>::FileContent,
179        value: Self::Value,
180        _cx: &App,
181    ) {
182        settings.buffer_font_weight = Some(value.0);
183    }
184}
185
186impl RenderOnce for BufferFontWeightControl {
187    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
188        let value = Self::read(cx);
189
190        h_flex()
191            .gap_2()
192            .child(Icon::new(IconName::FontWeight))
193            .child(DropdownMenu::new(
194                "buffer-font-weight",
195                value.0.to_string(),
196                ContextMenu::build(window, cx, |mut menu, _window, _cx| {
197                    for weight in FontWeight::ALL {
198                        menu = menu.custom_entry(
199                            move |_window, _cx| Label::new(weight.0.to_string()).into_any_element(),
200                            {
201                                move |_, cx| {
202                                    Self::write(weight, cx);
203                                }
204                            },
205                        )
206                    }
207
208                    menu
209                }),
210            ))
211    }
212}
213
214#[derive(IntoElement)]
215struct BufferFontLigaturesControl;
216
217impl EditableSettingControl for BufferFontLigaturesControl {
218    type Value = bool;
219    type Settings = ThemeSettings;
220
221    fn name(&self) -> SharedString {
222        "Buffer Font Ligatures".into()
223    }
224
225    fn read(cx: &App) -> Self::Value {
226        let settings = ThemeSettings::get_global(cx);
227        settings
228            .buffer_font
229            .features
230            .is_calt_enabled()
231            .unwrap_or(true)
232    }
233
234    fn apply(
235        settings: &mut <Self::Settings as Settings>::FileContent,
236        value: Self::Value,
237        _cx: &App,
238    ) {
239        let value = if value { 1 } else { 0 };
240
241        let mut features = settings
242            .buffer_font_features
243            .as_ref()
244            .map(|features| features.tag_value_list().to_vec())
245            .unwrap_or_default();
246
247        if let Some(calt_index) = features.iter().position(|(tag, _)| tag == "calt") {
248            features[calt_index].1 = value;
249        } else {
250            features.push(("calt".into(), value));
251        }
252
253        settings.buffer_font_features = Some(FontFeatures(Arc::new(features)));
254    }
255}
256
257impl RenderOnce for BufferFontLigaturesControl {
258    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
259        let value = Self::read(cx);
260
261        CheckboxWithLabel::new(
262            "buffer-font-ligatures",
263            Label::new(self.name()),
264            value.into(),
265            |selection, _, cx| {
266                Self::write(
267                    match selection {
268                        ToggleState::Selected => true,
269                        ToggleState::Unselected | ToggleState::Indeterminate => false,
270                    },
271                    cx,
272                );
273            },
274        )
275    }
276}
277
278#[derive(IntoElement)]
279struct InlineGitBlameControl;
280
281impl EditableSettingControl for InlineGitBlameControl {
282    type Value = bool;
283    type Settings = ProjectSettings;
284
285    fn name(&self) -> SharedString {
286        "Inline Git Blame".into()
287    }
288
289    fn read(cx: &App) -> Self::Value {
290        let settings = ProjectSettings::get_global(cx);
291        settings.git.inline_blame_enabled()
292    }
293
294    fn apply(
295        settings: &mut <Self::Settings as Settings>::FileContent,
296        value: Self::Value,
297        _cx: &App,
298    ) {
299        if let Some(inline_blame) = settings.git.inline_blame.as_mut() {
300            inline_blame.enabled = value;
301        } else {
302            settings.git.inline_blame = Some(InlineBlameSettings {
303                enabled: false,
304                ..Default::default()
305            });
306        }
307    }
308}
309
310impl RenderOnce for InlineGitBlameControl {
311    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
312        let value = Self::read(cx);
313
314        CheckboxWithLabel::new(
315            "inline-git-blame",
316            Label::new(self.name()),
317            value.into(),
318            |selection, _, cx| {
319                Self::write(
320                    match selection {
321                        ToggleState::Selected => true,
322                        ToggleState::Unselected | ToggleState::Indeterminate => false,
323                    },
324                    cx,
325                );
326            },
327        )
328    }
329}
330
331#[derive(IntoElement)]
332struct LineNumbersControl;
333
334impl EditableSettingControl for LineNumbersControl {
335    type Value = bool;
336    type Settings = EditorSettings;
337
338    fn name(&self) -> SharedString {
339        "Line Numbers".into()
340    }
341
342    fn read(cx: &App) -> Self::Value {
343        let settings = EditorSettings::get_global(cx);
344        settings.gutter.line_numbers
345    }
346
347    fn apply(
348        settings: &mut <Self::Settings as Settings>::FileContent,
349        value: Self::Value,
350        _cx: &App,
351    ) {
352        if let Some(gutter) = settings.gutter.as_mut() {
353            gutter.line_numbers = Some(value);
354        } else {
355            settings.gutter = Some(crate::editor_settings::GutterContent {
356                line_numbers: Some(value),
357                ..Default::default()
358            });
359        }
360    }
361}
362
363impl RenderOnce for LineNumbersControl {
364    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
365        let value = Self::read(cx);
366
367        CheckboxWithLabel::new(
368            "line-numbers",
369            Label::new(self.name()),
370            value.into(),
371            |selection, _, cx| {
372                Self::write(
373                    match selection {
374                        ToggleState::Selected => true,
375                        ToggleState::Unselected | ToggleState::Indeterminate => false,
376                    },
377                    cx,
378                );
379            },
380        )
381    }
382}
383
384#[derive(IntoElement)]
385struct RelativeLineNumbersControl;
386
387impl EditableSettingControl for RelativeLineNumbersControl {
388    type Value = bool;
389    type Settings = EditorSettings;
390
391    fn name(&self) -> SharedString {
392        "Relative Line Numbers".into()
393    }
394
395    fn read(cx: &App) -> Self::Value {
396        let settings = EditorSettings::get_global(cx);
397        settings.relative_line_numbers
398    }
399
400    fn apply(
401        settings: &mut <Self::Settings as Settings>::FileContent,
402        value: Self::Value,
403        _cx: &App,
404    ) {
405        settings.relative_line_numbers = Some(value);
406    }
407}
408
409impl RenderOnce for RelativeLineNumbersControl {
410    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
411        let value = Self::read(cx);
412
413        DropdownMenu::new(
414            "relative-line-numbers",
415            if value { "Relative" } else { "Ascending" },
416            ContextMenu::build(window, cx, |menu, _window, _cx| {
417                menu.custom_entry(
418                    |_window, _cx| Label::new("Ascending").into_any_element(),
419                    move |_, cx| Self::write(false, cx),
420                )
421                .custom_entry(
422                    |_window, _cx| Label::new("Relative").into_any_element(),
423                    move |_, cx| Self::write(true, cx),
424                )
425            }),
426        )
427    }
428}