editing_page.rs

  1use std::sync::Arc;
  2
  3use editor::{EditorSettings, ShowMinimap};
  4use fs::Fs;
  5use gpui::{Action, App, FontFeatures, IntoElement, Pixels, Window};
  6use language::language_settings::{AllLanguageSettings, FormatOnSave};
  7use project::project_settings::ProjectSettings;
  8use settings::{Settings as _, update_settings_file};
  9use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
 10use ui::{
 11    ButtonLike, ContextMenu, DropdownMenu, NumericStepper, SwitchField, ToggleButtonGroup,
 12    ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, prelude::*,
 13};
 14
 15use crate::{ImportCursorSettings, ImportVsCodeSettings};
 16
 17fn read_show_mini_map(cx: &App) -> ShowMinimap {
 18    editor::EditorSettings::get_global(cx).minimap.show
 19}
 20
 21fn write_show_mini_map(show: ShowMinimap, cx: &mut App) {
 22    let fs = <dyn Fs>::global(cx);
 23
 24    // This is used to speed up the UI
 25    // the UI reads the current values to get what toggle state to show on buttons
 26    // there's a slight delay if we just call update_settings_file so we manually set
 27    // the value here then call update_settings file to get around the delay
 28    let mut curr_settings = EditorSettings::get_global(cx).clone();
 29    curr_settings.minimap.show = show;
 30    EditorSettings::override_global(curr_settings, cx);
 31
 32    update_settings_file::<EditorSettings>(fs, cx, move |editor_settings, _| {
 33        editor_settings.minimap.get_or_insert_default().show = Some(show);
 34    });
 35}
 36
 37fn read_inlay_hints(cx: &App) -> bool {
 38    AllLanguageSettings::get_global(cx)
 39        .defaults
 40        .inlay_hints
 41        .enabled
 42}
 43
 44fn write_inlay_hints(enabled: bool, cx: &mut App) {
 45    let fs = <dyn Fs>::global(cx);
 46
 47    let mut curr_settings = AllLanguageSettings::get_global(cx).clone();
 48    curr_settings.defaults.inlay_hints.enabled = enabled;
 49    AllLanguageSettings::override_global(curr_settings, cx);
 50
 51    update_settings_file::<AllLanguageSettings>(fs, cx, move |all_language_settings, cx| {
 52        all_language_settings
 53            .defaults
 54            .inlay_hints
 55            .get_or_insert_with(|| {
 56                AllLanguageSettings::get_global(cx)
 57                    .clone()
 58                    .defaults
 59                    .inlay_hints
 60            })
 61            .enabled = enabled;
 62    });
 63}
 64
 65fn read_git_blame(cx: &App) -> bool {
 66    ProjectSettings::get_global(cx).git.inline_blame_enabled()
 67}
 68
 69fn set_git_blame(enabled: bool, cx: &mut App) {
 70    let fs = <dyn Fs>::global(cx);
 71
 72    let mut curr_settings = ProjectSettings::get_global(cx).clone();
 73    curr_settings
 74        .git
 75        .inline_blame
 76        .get_or_insert_default()
 77        .enabled = enabled;
 78    ProjectSettings::override_global(curr_settings, cx);
 79
 80    update_settings_file::<ProjectSettings>(fs, cx, move |project_settings, _| {
 81        project_settings
 82            .git
 83            .inline_blame
 84            .get_or_insert_default()
 85            .enabled = enabled;
 86    });
 87}
 88
 89fn write_ui_font_family(font: SharedString, cx: &mut App) {
 90    let fs = <dyn Fs>::global(cx);
 91
 92    update_settings_file::<ThemeSettings>(fs, cx, move |theme_settings, _| {
 93        theme_settings.ui_font_family = Some(FontFamilyName(font.into()));
 94    });
 95}
 96
 97fn write_ui_font_size(size: Pixels, cx: &mut App) {
 98    let fs = <dyn Fs>::global(cx);
 99
100    update_settings_file::<ThemeSettings>(fs, cx, move |theme_settings, _| {
101        theme_settings.ui_font_size = Some(size.into());
102    });
103}
104
105fn write_buffer_font_size(size: Pixels, cx: &mut App) {
106    let fs = <dyn Fs>::global(cx);
107
108    update_settings_file::<ThemeSettings>(fs, cx, move |theme_settings, _| {
109        theme_settings.buffer_font_size = Some(size.into());
110    });
111}
112
113fn write_buffer_font_family(font_family: SharedString, cx: &mut App) {
114    let fs = <dyn Fs>::global(cx);
115
116    update_settings_file::<ThemeSettings>(fs, cx, move |theme_settings, _| {
117        theme_settings.buffer_font_family = Some(FontFamilyName(font_family.into()));
118    });
119}
120
121fn read_font_ligatures(cx: &App) -> bool {
122    ThemeSettings::get_global(cx)
123        .buffer_font
124        .features
125        .is_calt_enabled()
126        .unwrap_or(true)
127}
128
129fn write_font_ligatures(enabled: bool, cx: &mut App) {
130    let fs = <dyn Fs>::global(cx);
131    let bit = if enabled { 1 } else { 0 };
132
133    update_settings_file::<ThemeSettings>(fs, cx, move |theme_settings, _| {
134        let mut features = theme_settings
135            .buffer_font_features
136            .as_mut()
137            .map(|features| features.tag_value_list().to_vec())
138            .unwrap_or_default();
139
140        if let Some(calt_index) = features.iter().position(|(tag, _)| tag == "calt") {
141            features[calt_index].1 = bit;
142        } else {
143            features.push(("calt".into(), bit));
144        }
145
146        theme_settings.buffer_font_features = Some(FontFeatures(Arc::new(features)));
147    });
148}
149
150fn read_format_on_save(cx: &App) -> bool {
151    match AllLanguageSettings::get_global(cx).defaults.format_on_save {
152        FormatOnSave::On | FormatOnSave::List(_) => true,
153        FormatOnSave::Off => false,
154    }
155}
156
157fn write_format_on_save(format_on_save: bool, cx: &mut App) {
158    let fs = <dyn Fs>::global(cx);
159
160    update_settings_file::<AllLanguageSettings>(fs, cx, move |language_settings, _| {
161        language_settings.defaults.format_on_save = Some(match format_on_save {
162            true => FormatOnSave::On,
163            false => FormatOnSave::Off,
164        });
165    });
166}
167
168fn render_import_settings_section() -> impl IntoElement {
169    v_flex()
170        .gap_4()
171        .child(
172            v_flex()
173                .child(Label::new("Import Settings").size(LabelSize::Large))
174                .child(
175                    Label::new("Automatically pull your settings from other editors.")
176                        .color(Color::Muted),
177                ),
178        )
179        .child(
180            h_flex()
181                .w_full()
182                .gap_4()
183                .child(
184                    h_flex().w_full().child(
185                        ButtonLike::new("import_vs_code")
186                            .full_width()
187                            .style(ButtonStyle::Outlined)
188                            .size(ButtonSize::Large)
189                            .child(
190                                h_flex()
191                                    .w_full()
192                                    .gap_1p5()
193                                    .px_1()
194                                    .child(
195                                        Icon::new(IconName::EditorVsCode)
196                                            .color(Color::Muted)
197                                            .size(IconSize::XSmall),
198                                    )
199                                    .child(Label::new("VS Code")),
200                            )
201                            .on_click(|_, window, cx| {
202                                window.dispatch_action(
203                                    ImportVsCodeSettings::default().boxed_clone(),
204                                    cx,
205                                )
206                            }),
207                    ),
208                )
209                .child(
210                    h_flex().w_full().child(
211                        ButtonLike::new("import_cursor")
212                            .full_width()
213                            .style(ButtonStyle::Outlined)
214                            .size(ButtonSize::Large)
215                            .child(
216                                h_flex()
217                                    .w_full()
218                                    .gap_1p5()
219                                    .px_1()
220                                    .child(
221                                        Icon::new(IconName::EditorCursor)
222                                            .color(Color::Muted)
223                                            .size(IconSize::XSmall),
224                                    )
225                                    .child(Label::new("Cursor")),
226                            )
227                            .on_click(|_, window, cx| {
228                                window.dispatch_action(
229                                    ImportCursorSettings::default().boxed_clone(),
230                                    cx,
231                                )
232                            }),
233                    ),
234                ),
235        )
236}
237
238fn render_font_customization_section(window: &mut Window, cx: &mut App) -> impl IntoElement {
239    let theme_settings = ThemeSettings::get_global(cx);
240    let ui_font_size = theme_settings.ui_font_size(cx);
241    let font_family = theme_settings.buffer_font.family.clone();
242    let buffer_font_size = theme_settings.buffer_font_size(cx);
243
244    h_flex()
245        .w_full()
246        .gap_4()
247        .child(
248            v_flex()
249                .w_full()
250                .gap_1()
251                .child(Label::new("UI Font"))
252                .child(
253                    h_flex()
254                        .w_full()
255                        .justify_between()
256                        .gap_2()
257                        .child(
258                            DropdownMenu::new(
259                                "ui-font-family",
260                                theme_settings.ui_font.family.clone(),
261                                ContextMenu::build(window, cx, |mut menu, _, cx| {
262                                    let font_family_cache = FontFamilyCache::global(cx);
263
264                                    for font_name in font_family_cache.list_font_families(cx) {
265                                        menu = menu.custom_entry(
266                                            {
267                                                let font_name = font_name.clone();
268                                                move |_window, _cx| {
269                                                    Label::new(font_name.clone()).into_any_element()
270                                                }
271                                            },
272                                            {
273                                                let font_name = font_name.clone();
274                                                move |_window, cx| {
275                                                    write_ui_font_family(font_name.clone(), cx);
276                                                }
277                                            },
278                                        )
279                                    }
280
281                                    menu
282                                }),
283                            )
284                            .style(ui::DropdownStyle::Outlined)
285                            .full_width(true),
286                        )
287                        .child(
288                            NumericStepper::new(
289                                "ui-font-size",
290                                ui_font_size.to_string(),
291                                move |_, _, cx| {
292                                    write_ui_font_size(ui_font_size - px(1.), cx);
293                                },
294                                move |_, _, cx| {
295                                    write_ui_font_size(ui_font_size + px(1.), cx);
296                                },
297                            )
298                            .style(ui::NumericStepperStyle::Outlined),
299                        ),
300                ),
301        )
302        .child(
303            v_flex()
304                .w_full()
305                .gap_1()
306                .child(Label::new("Editor Font"))
307                .child(
308                    h_flex()
309                        .w_full()
310                        .justify_between()
311                        .gap_2()
312                        .child(
313                            DropdownMenu::new(
314                                "buffer-font-family",
315                                font_family,
316                                ContextMenu::build(window, cx, |mut menu, _, cx| {
317                                    let font_family_cache = FontFamilyCache::global(cx);
318
319                                    for font_name in font_family_cache.list_font_families(cx) {
320                                        menu = menu.custom_entry(
321                                            {
322                                                let font_name = font_name.clone();
323                                                move |_window, _cx| {
324                                                    Label::new(font_name.clone()).into_any_element()
325                                                }
326                                            },
327                                            {
328                                                let font_name = font_name.clone();
329                                                move |_window, cx| {
330                                                    write_buffer_font_family(font_name.clone(), cx);
331                                                }
332                                            },
333                                        )
334                                    }
335
336                                    menu
337                                }),
338                            )
339                            .style(ui::DropdownStyle::Outlined)
340                            .full_width(true),
341                        )
342                        .child(
343                            NumericStepper::new(
344                                "buffer-font-size",
345                                buffer_font_size.to_string(),
346                                move |_, _, cx| {
347                                    write_buffer_font_size(buffer_font_size - px(1.), cx);
348                                },
349                                move |_, _, cx| {
350                                    write_buffer_font_size(buffer_font_size + px(1.), cx);
351                                },
352                            )
353                            .style(ui::NumericStepperStyle::Outlined),
354                        ),
355                ),
356        )
357}
358
359fn render_popular_settings_section(window: &mut Window, cx: &mut App) -> impl IntoElement {
360    v_flex()
361        .gap_5()
362        .child(Label::new("Popular Settings").size(LabelSize::Large).mt_8())
363        .child(render_font_customization_section(window, cx))
364        .child(SwitchField::new(
365            "onboarding-font-ligatures",
366            "Font Ligatures",
367            Some("Combine text characters into their associated symbols.".into()),
368            if read_font_ligatures(cx) {
369                ui::ToggleState::Selected
370            } else {
371                ui::ToggleState::Unselected
372            },
373            |toggle_state, _, cx| {
374                write_font_ligatures(toggle_state == &ToggleState::Selected, cx);
375            },
376        ))
377        .child(SwitchField::new(
378            "onboarding-format-on-save",
379            "Format on Save",
380            Some("Format code automatically when saving.".into()),
381            if read_format_on_save(cx) {
382                ui::ToggleState::Selected
383            } else {
384                ui::ToggleState::Unselected
385            },
386            |toggle_state, _, cx| {
387                write_format_on_save(toggle_state == &ToggleState::Selected, cx);
388            },
389        ))
390        .child(
391            h_flex()
392                .items_start()
393                .justify_between()
394                .child(
395                    v_flex().child(Label::new("Mini Map")).child(
396                        Label::new("See a high-level overview of your source code.")
397                            .color(Color::Muted),
398                    ),
399                )
400                .child(
401                    ToggleButtonGroup::single_row(
402                        "onboarding-show-mini-map",
403                        [
404                            ToggleButtonSimple::new("Auto", |_, _, cx| {
405                                write_show_mini_map(ShowMinimap::Auto, cx);
406                            }),
407                            ToggleButtonSimple::new("Always", |_, _, cx| {
408                                write_show_mini_map(ShowMinimap::Always, cx);
409                            }),
410                            ToggleButtonSimple::new("Never", |_, _, cx| {
411                                write_show_mini_map(ShowMinimap::Never, cx);
412                            }),
413                        ],
414                    )
415                    .selected_index(match read_show_mini_map(cx) {
416                        ShowMinimap::Auto => 0,
417                        ShowMinimap::Always => 1,
418                        ShowMinimap::Never => 2,
419                    })
420                    .style(ToggleButtonGroupStyle::Outlined)
421                    .button_width(ui::rems_from_px(64.)),
422                ),
423        )
424        .child(SwitchField::new(
425            "onboarding-enable-inlay-hints",
426            "Inlay Hints",
427            Some("See parameter names for function and method calls inline.".into()),
428            if read_inlay_hints(cx) {
429                ui::ToggleState::Selected
430            } else {
431                ui::ToggleState::Unselected
432            },
433            |toggle_state, _, cx| {
434                write_inlay_hints(toggle_state == &ToggleState::Selected, cx);
435            },
436        ))
437        .child(SwitchField::new(
438            "onboarding-git-blame-switch",
439            "Git Blame",
440            Some("See who committed each line on a given file.".into()),
441            if read_git_blame(cx) {
442                ui::ToggleState::Selected
443            } else {
444                ui::ToggleState::Unselected
445            },
446            |toggle_state, _, cx| {
447                set_git_blame(toggle_state == &ToggleState::Selected, cx);
448            },
449        ))
450}
451
452pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
453    v_flex()
454        .gap_4()
455        .child(render_import_settings_section())
456        .child(render_popular_settings_section(window, cx))
457}