editor_settings_controls.rs

  1use std::sync::Arc;
  2
  3use gpui::{AppContext, FontFeatures, FontWeight};
  4use project::project_settings::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, _cx: &mut WindowContext) -> 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: &AppContext) -> 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: &AppContext,
 77    ) {
 78        settings.buffer_font_family = Some(value.to_string());
 79    }
 80}
 81
 82impl RenderOnce for BufferFontFamilyControl {
 83    fn render(self, cx: &mut WindowContext) -> 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(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 |_cx| Label::new(font_name.clone()).into_any_element()
100                            },
101                            {
102                                let font_name = font_name.clone();
103                                move |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: &AppContext) -> 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: &AppContext,
136    ) {
137        settings.buffer_font_size = Some(value.into());
138    }
139}
140
141impl RenderOnce for BufferFontSizeControl {
142    fn render(self, cx: &mut WindowContext) -> 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: &AppContext) -> 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: &AppContext,
181    ) {
182        settings.buffer_font_weight = Some(value.0);
183    }
184}
185
186impl RenderOnce for BufferFontWeightControl {
187    fn render(self, cx: &mut WindowContext) -> 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(cx, |mut menu, _cx| {
197                    for weight in FontWeight::ALL {
198                        menu = menu.custom_entry(
199                            move |_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: &AppContext) -> 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: &AppContext,
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, cx: &mut WindowContext) -> 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                        Selection::Selected => true,
269                        Selection::Unselected | Selection::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: &AppContext) -> 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: &AppContext,
298    ) {
299        settings.git.inline_blame.enabled = value;
300    }
301}
302
303impl RenderOnce for InlineGitBlameControl {
304    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
305        let value = Self::read(cx);
306
307        CheckboxWithLabel::new(
308            "inline-git-blame",
309            Label::new(self.name()),
310            value.into(),
311            |selection, cx| {
312                Self::write(
313                    match selection {
314                        Selection::Selected => true,
315                        Selection::Unselected | Selection::Indeterminate => false,
316                    },
317                    cx,
318                );
319            },
320        )
321    }
322}
323
324#[derive(IntoElement)]
325struct LineNumbersControl;
326
327impl EditableSettingControl for LineNumbersControl {
328    type Value = bool;
329    type Settings = EditorSettings;
330
331    fn name(&self) -> SharedString {
332        "Line Numbers".into()
333    }
334
335    fn read(cx: &AppContext) -> Self::Value {
336        let settings = EditorSettings::get_global(cx);
337        settings.gutter.line_numbers
338    }
339
340    fn apply(
341        settings: &mut <Self::Settings as Settings>::FileContent,
342        value: Self::Value,
343        _cx: &AppContext,
344    ) {
345        settings.gutter.line_numbers = value;
346    }
347}
348
349impl RenderOnce for LineNumbersControl {
350    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
351        let value = Self::read(cx);
352
353        CheckboxWithLabel::new(
354            "line-numbers",
355            Label::new(self.name()),
356            value.into(),
357            |selection, cx| {
358                Self::write(
359                    match selection {
360                        Selection::Selected => true,
361                        Selection::Unselected | Selection::Indeterminate => false,
362                    },
363                    cx,
364                );
365            },
366        )
367    }
368}
369
370#[derive(IntoElement)]
371struct RelativeLineNumbersControl;
372
373impl EditableSettingControl for RelativeLineNumbersControl {
374    type Value = bool;
375    type Settings = EditorSettings;
376
377    fn name(&self) -> SharedString {
378        "Relative Line Numbers".into()
379    }
380
381    fn read(cx: &AppContext) -> Self::Value {
382        let settings = EditorSettings::get_global(cx);
383        settings.relative_line_numbers
384    }
385
386    fn apply(
387        settings: &mut <Self::Settings as Settings>::FileContent,
388        value: Self::Value,
389        _cx: &AppContext,
390    ) {
391        settings.relative_line_numbers = value;
392    }
393}
394
395impl RenderOnce for RelativeLineNumbersControl {
396    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
397        let value = Self::read(cx);
398
399        DropdownMenu::new(
400            "relative-line-numbers",
401            if value { "Relative" } else { "Ascending" },
402            ContextMenu::build(cx, |menu, _cx| {
403                menu.custom_entry(
404                    |_cx| Label::new("Ascending").into_any_element(),
405                    move |cx| Self::write(false, cx),
406                )
407                .custom_entry(
408                    |_cx| Label::new("Relative").into_any_element(),
409                    move |cx| Self::write(true, cx),
410                )
411            }),
412        )
413    }
414}