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, Tooltip, 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    const LIGATURE_TOOLTIP: &'static str = "Ligatures are when a font creates a special character out of combining two characters into one. For example, with ligatures turned on, =/= would become ≠.";
361
362    v_flex()
363        .gap_5()
364        .child(Label::new("Popular Settings").size(LabelSize::Large).mt_8())
365        .child(render_font_customization_section(window, cx))
366        .child(
367            SwitchField::new(
368                "onboarding-font-ligatures",
369                "Font Ligatures",
370                Some("Combine text characters into their associated symbols.".into()),
371                if read_font_ligatures(cx) {
372                    ui::ToggleState::Selected
373                } else {
374                    ui::ToggleState::Unselected
375                },
376                |toggle_state, _, cx| {
377                    write_font_ligatures(toggle_state == &ToggleState::Selected, cx);
378                },
379            )
380            .tooltip(Tooltip::text(LIGATURE_TOOLTIP)),
381        )
382        .child(SwitchField::new(
383            "onboarding-format-on-save",
384            "Format on Save",
385            Some("Format code automatically when saving.".into()),
386            if read_format_on_save(cx) {
387                ui::ToggleState::Selected
388            } else {
389                ui::ToggleState::Unselected
390            },
391            |toggle_state, _, cx| {
392                write_format_on_save(toggle_state == &ToggleState::Selected, cx);
393            },
394        ))
395        .child(SwitchField::new(
396            "onboarding-enable-inlay-hints",
397            "Inlay Hints",
398            Some("See parameter names for function and method calls inline.".into()),
399            if read_inlay_hints(cx) {
400                ui::ToggleState::Selected
401            } else {
402                ui::ToggleState::Unselected
403            },
404            |toggle_state, _, cx| {
405                write_inlay_hints(toggle_state == &ToggleState::Selected, cx);
406            },
407        ))
408        .child(SwitchField::new(
409            "onboarding-git-blame-switch",
410            "Git Blame",
411            Some("See who committed each line on a given file.".into()),
412            if read_git_blame(cx) {
413                ui::ToggleState::Selected
414            } else {
415                ui::ToggleState::Unselected
416            },
417            |toggle_state, _, cx| {
418                set_git_blame(toggle_state == &ToggleState::Selected, cx);
419            },
420        ))
421        .child(
422            h_flex()
423                .items_start()
424                .justify_between()
425                .child(
426                    v_flex().child(Label::new("Mini Map")).child(
427                        Label::new("See a high-level overview of your source code.")
428                            .color(Color::Muted),
429                    ),
430                )
431                .child(
432                    ToggleButtonGroup::single_row(
433                        "onboarding-show-mini-map",
434                        [
435                            ToggleButtonSimple::new("Auto", |_, _, cx| {
436                                write_show_mini_map(ShowMinimap::Auto, cx);
437                            }),
438                            ToggleButtonSimple::new("Always", |_, _, cx| {
439                                write_show_mini_map(ShowMinimap::Always, cx);
440                            }),
441                            ToggleButtonSimple::new("Never", |_, _, cx| {
442                                write_show_mini_map(ShowMinimap::Never, cx);
443                            }),
444                        ],
445                    )
446                    .selected_index(match read_show_mini_map(cx) {
447                        ShowMinimap::Auto => 0,
448                        ShowMinimap::Always => 1,
449                        ShowMinimap::Never => 2,
450                    })
451                    .style(ToggleButtonGroupStyle::Outlined)
452                    .button_width(ui::rems_from_px(64.)),
453                ),
454        )
455}
456
457pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
458    v_flex()
459        .gap_4()
460        .child(render_import_settings_section())
461        .child(render_popular_settings_section(window, cx))
462}