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