editor_settings_controls.rs

  1use std::sync::Arc;
  2
  3use gpui::{AppContext, 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
 12#[derive(IntoElement)]
 13pub struct EditorSettingsControls {}
 14
 15impl EditorSettingsControls {
 16    pub fn new() -> Self {
 17        Self {}
 18    }
 19}
 20
 21impl RenderOnce for EditorSettingsControls {
 22    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
 23        SettingsContainer::new()
 24            .child(
 25                SettingsGroup::new("Font")
 26                    .child(
 27                        h_flex()
 28                            .gap_2()
 29                            .justify_between()
 30                            .child(BufferFontFamilyControl)
 31                            .child(BufferFontWeightControl),
 32                    )
 33                    .child(BufferFontSizeControl)
 34                    .child(BufferFontLigaturesControl),
 35            )
 36            .child(SettingsGroup::new("Editor").child(InlineGitBlameControl))
 37    }
 38}
 39
 40#[derive(IntoElement)]
 41struct BufferFontFamilyControl;
 42
 43impl EditableSettingControl for BufferFontFamilyControl {
 44    type Value = SharedString;
 45    type Settings = ThemeSettings;
 46
 47    fn name(&self) -> SharedString {
 48        "Buffer Font Family".into()
 49    }
 50
 51    fn read(cx: &AppContext) -> Self::Value {
 52        let settings = ThemeSettings::get_global(cx);
 53        settings.buffer_font.family.clone()
 54    }
 55
 56    fn apply(
 57        settings: &mut <Self::Settings as Settings>::FileContent,
 58        value: Self::Value,
 59        _cx: &AppContext,
 60    ) {
 61        settings.buffer_font_family = Some(value.to_string());
 62    }
 63}
 64
 65impl RenderOnce for BufferFontFamilyControl {
 66    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
 67        let value = Self::read(cx);
 68
 69        h_flex()
 70            .gap_2()
 71            .child(Icon::new(IconName::Font))
 72            .child(DropdownMenu::new(
 73                "buffer-font-family",
 74                value.clone(),
 75                ContextMenu::build(cx, |mut menu, cx| {
 76                    let font_family_cache = FontFamilyCache::global(cx);
 77
 78                    for font_name in font_family_cache.list_font_families(cx) {
 79                        menu = menu.custom_entry(
 80                            {
 81                                let font_name = font_name.clone();
 82                                move |_cx| Label::new(font_name.clone()).into_any_element()
 83                            },
 84                            {
 85                                let font_name = font_name.clone();
 86                                move |cx| {
 87                                    Self::write(font_name.clone(), cx);
 88                                }
 89                            },
 90                        )
 91                    }
 92
 93                    menu
 94                }),
 95            ))
 96    }
 97}
 98
 99#[derive(IntoElement)]
100struct BufferFontSizeControl;
101
102impl EditableSettingControl for BufferFontSizeControl {
103    type Value = Pixels;
104    type Settings = ThemeSettings;
105
106    fn name(&self) -> SharedString {
107        "Buffer Font Size".into()
108    }
109
110    fn read(cx: &AppContext) -> Self::Value {
111        let settings = ThemeSettings::get_global(cx);
112        settings.buffer_font_size
113    }
114
115    fn apply(
116        settings: &mut <Self::Settings as Settings>::FileContent,
117        value: Self::Value,
118        _cx: &AppContext,
119    ) {
120        settings.buffer_font_size = Some(value.into());
121    }
122}
123
124impl RenderOnce for BufferFontSizeControl {
125    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
126        let value = Self::read(cx);
127
128        h_flex()
129            .gap_2()
130            .child(Icon::new(IconName::FontSize))
131            .child(NumericStepper::new(
132                value.to_string(),
133                move |_, cx| {
134                    Self::write(value - px(1.), cx);
135                },
136                move |_, cx| {
137                    Self::write(value + px(1.), cx);
138                },
139            ))
140    }
141}
142
143#[derive(IntoElement)]
144struct BufferFontWeightControl;
145
146impl EditableSettingControl for BufferFontWeightControl {
147    type Value = FontWeight;
148    type Settings = ThemeSettings;
149
150    fn name(&self) -> SharedString {
151        "Buffer Font Weight".into()
152    }
153
154    fn read(cx: &AppContext) -> Self::Value {
155        let settings = ThemeSettings::get_global(cx);
156        settings.buffer_font.weight
157    }
158
159    fn apply(
160        settings: &mut <Self::Settings as Settings>::FileContent,
161        value: Self::Value,
162        _cx: &AppContext,
163    ) {
164        settings.buffer_font_weight = Some(value.0);
165    }
166}
167
168impl RenderOnce for BufferFontWeightControl {
169    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
170        let value = Self::read(cx);
171
172        h_flex()
173            .gap_2()
174            .child(Icon::new(IconName::FontWeight))
175            .child(DropdownMenu::new(
176                "buffer-font-weight",
177                value.0.to_string(),
178                ContextMenu::build(cx, |mut menu, _cx| {
179                    for weight in FontWeight::ALL {
180                        menu = menu.custom_entry(
181                            move |_cx| Label::new(weight.0.to_string()).into_any_element(),
182                            {
183                                move |cx| {
184                                    Self::write(weight, cx);
185                                }
186                            },
187                        )
188                    }
189
190                    menu
191                }),
192            ))
193    }
194}
195
196#[derive(IntoElement)]
197struct BufferFontLigaturesControl;
198
199impl EditableSettingControl for BufferFontLigaturesControl {
200    type Value = bool;
201    type Settings = ThemeSettings;
202
203    fn name(&self) -> SharedString {
204        "Buffer Font Ligatures".into()
205    }
206
207    fn read(cx: &AppContext) -> Self::Value {
208        let settings = ThemeSettings::get_global(cx);
209        settings
210            .buffer_font
211            .features
212            .is_calt_enabled()
213            .unwrap_or(true)
214    }
215
216    fn apply(
217        settings: &mut <Self::Settings as Settings>::FileContent,
218        value: Self::Value,
219        _cx: &AppContext,
220    ) {
221        let value = if value { 1 } else { 0 };
222
223        let mut features = settings
224            .buffer_font_features
225            .as_ref()
226            .map(|features| {
227                features
228                    .tag_value_list()
229                    .into_iter()
230                    .cloned()
231                    .collect::<Vec<_>>()
232            })
233            .unwrap_or_default();
234
235        if let Some(calt_index) = features.iter().position(|(tag, _)| tag == "calt") {
236            features[calt_index].1 = value;
237        } else {
238            features.push(("calt".into(), value));
239        }
240
241        settings.buffer_font_features = Some(FontFeatures(Arc::new(features)));
242    }
243}
244
245impl RenderOnce for BufferFontLigaturesControl {
246    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
247        let value = Self::read(cx);
248
249        CheckboxWithLabel::new(
250            "buffer-font-ligatures",
251            Label::new(self.name()),
252            value.into(),
253            |selection, cx| {
254                Self::write(
255                    match selection {
256                        Selection::Selected => true,
257                        Selection::Unselected | Selection::Indeterminate => false,
258                    },
259                    cx,
260                );
261            },
262        )
263    }
264}
265
266#[derive(IntoElement)]
267struct InlineGitBlameControl;
268
269impl EditableSettingControl for InlineGitBlameControl {
270    type Value = bool;
271    type Settings = ProjectSettings;
272
273    fn name(&self) -> SharedString {
274        "Inline Git Blame".into()
275    }
276
277    fn read(cx: &AppContext) -> Self::Value {
278        let settings = ProjectSettings::get_global(cx);
279        settings.git.inline_blame_enabled()
280    }
281
282    fn apply(
283        settings: &mut <Self::Settings as Settings>::FileContent,
284        value: Self::Value,
285        _cx: &AppContext,
286    ) {
287        if let Some(inline_blame) = settings.git.inline_blame.as_mut() {
288            inline_blame.enabled = value;
289        } else {
290            settings.git.inline_blame = Some(InlineBlameSettings {
291                enabled: false,
292                ..Default::default()
293            });
294        }
295    }
296}
297
298impl RenderOnce for InlineGitBlameControl {
299    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
300        let value = Self::read(cx);
301
302        CheckboxWithLabel::new(
303            "inline-git-blame",
304            Label::new(self.name()),
305            value.into(),
306            |selection, cx| {
307                Self::write(
308                    match selection {
309                        Selection::Selected => true,
310                        Selection::Unselected | Selection::Indeterminate => false,
311                    },
312                    cx,
313                );
314            },
315        )
316    }
317}