editing_page.rs

  1use std::sync::Arc;
  2
  3use editor::{EditorSettings, ShowMinimap};
  4use fs::Fs;
  5use fuzzy::{StringMatch, StringMatchCandidate};
  6use gpui::{
  7    Action, AnyElement, App, Context, FontFeatures, IntoElement, Pixels, SharedString, Task, Window,
  8};
  9use language::language_settings::{AllLanguageSettings, FormatOnSave};
 10use picker::{Picker, PickerDelegate};
 11use project::project_settings::ProjectSettings;
 12use settings::{Settings as _, update_settings_file};
 13use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
 14use ui::{
 15    ButtonLike, ListItem, ListItemSpacing, PopoverMenu, SwitchField, ToggleButtonGroup,
 16    ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, Tooltip, prelude::*,
 17};
 18use ui_input::NumericStepper;
 19
 20use crate::{ImportCursorSettings, ImportVsCodeSettings, SettingsImportState};
 21
 22fn read_show_mini_map(cx: &App) -> ShowMinimap {
 23    editor::EditorSettings::get_global(cx).minimap.show
 24}
 25
 26fn write_show_mini_map(show: ShowMinimap, cx: &mut App) {
 27    let fs = <dyn Fs>::global(cx);
 28
 29    // This is used to speed up the UI
 30    // the UI reads the current values to get what toggle state to show on buttons
 31    // there's a slight delay if we just call update_settings_file so we manually set
 32    // the value here then call update_settings file to get around the delay
 33    let mut curr_settings = EditorSettings::get_global(cx).clone();
 34    curr_settings.minimap.show = show;
 35    EditorSettings::override_global(curr_settings, cx);
 36
 37    update_settings_file(fs, cx, move |settings, _| {
 38        telemetry::event!(
 39            "Welcome Minimap Clicked",
 40            from = settings.editor.minimap.clone().unwrap_or_default(),
 41            to = show
 42        );
 43        settings.editor.minimap.get_or_insert_default().show = Some(show);
 44    });
 45}
 46
 47fn read_inlay_hints(cx: &App) -> bool {
 48    AllLanguageSettings::get_global(cx)
 49        .defaults
 50        .inlay_hints
 51        .enabled
 52}
 53
 54fn write_inlay_hints(enabled: bool, cx: &mut App) {
 55    let fs = <dyn Fs>::global(cx);
 56
 57    let mut curr_settings = AllLanguageSettings::get_global(cx).clone();
 58    curr_settings.defaults.inlay_hints.enabled = enabled;
 59    AllLanguageSettings::override_global(curr_settings, cx);
 60
 61    update_settings_file(fs, cx, move |settings, _cx| {
 62        settings
 63            .project
 64            .all_languages
 65            .defaults
 66            .inlay_hints
 67            .get_or_insert_default()
 68            .enabled = Some(enabled);
 69    });
 70}
 71
 72fn read_git_blame(cx: &App) -> bool {
 73    ProjectSettings::get_global(cx).git.inline_blame.enabled
 74}
 75
 76fn write_git_blame(enabled: bool, cx: &mut App) {
 77    let fs = <dyn Fs>::global(cx);
 78
 79    let mut curr_settings = ProjectSettings::get_global(cx).clone();
 80    curr_settings.git.inline_blame.enabled = enabled;
 81    ProjectSettings::override_global(curr_settings, cx);
 82
 83    update_settings_file(fs, cx, move |settings, _| {
 84        settings
 85            .git
 86            .get_or_insert_default()
 87            .inline_blame
 88            .get_or_insert_default()
 89            .enabled = Some(enabled);
 90    });
 91}
 92
 93fn write_ui_font_family(font: SharedString, cx: &mut App) {
 94    let fs = <dyn Fs>::global(cx);
 95
 96    update_settings_file(fs, cx, move |settings, _| {
 97        telemetry::event!(
 98            "Welcome Font Changed",
 99            type = "ui font",
100            old = settings.theme.ui_font_family,
101            new = font
102        );
103        settings.theme.ui_font_family = Some(FontFamilyName(font.into()));
104    });
105}
106
107fn write_ui_font_size(size: Pixels, cx: &mut App) {
108    let fs = <dyn Fs>::global(cx);
109
110    update_settings_file(fs, cx, move |settings, _| {
111        settings.theme.ui_font_size = Some(size.into());
112    });
113}
114
115fn write_buffer_font_size(size: Pixels, cx: &mut App) {
116    let fs = <dyn Fs>::global(cx);
117
118    update_settings_file(fs, cx, move |settings, _| {
119        settings.theme.buffer_font_size = Some(size.into());
120    });
121}
122
123fn write_buffer_font_family(font_family: SharedString, cx: &mut App) {
124    let fs = <dyn Fs>::global(cx);
125
126    update_settings_file(fs, cx, move |settings, _| {
127        telemetry::event!(
128            "Welcome Font Changed",
129            type = "editor font",
130            old = settings.theme.buffer_font_family,
131            new = font_family
132        );
133
134        settings.theme.buffer_font_family = Some(FontFamilyName(font_family.into()));
135    });
136}
137
138fn read_font_ligatures(cx: &App) -> bool {
139    ThemeSettings::get_global(cx)
140        .buffer_font
141        .features
142        .is_calt_enabled()
143        .unwrap_or(true)
144}
145
146fn write_font_ligatures(enabled: bool, cx: &mut App) {
147    let fs = <dyn Fs>::global(cx);
148    let bit = if enabled { 1 } else { 0 };
149
150    update_settings_file(fs, cx, move |settings, _| {
151        let mut features = settings
152            .theme
153            .buffer_font_features
154            .as_mut()
155            .map(|features| features.tag_value_list().to_vec())
156            .unwrap_or_default();
157
158        if let Some(calt_index) = features.iter().position(|(tag, _)| tag == "calt") {
159            features[calt_index].1 = bit;
160        } else {
161            features.push(("calt".into(), bit));
162        }
163
164        settings.theme.buffer_font_features = Some(FontFeatures(Arc::new(features)));
165    });
166}
167
168fn read_format_on_save(cx: &App) -> bool {
169    match AllLanguageSettings::get_global(cx).defaults.format_on_save {
170        FormatOnSave::On => true,
171        FormatOnSave::Off => false,
172    }
173}
174
175fn write_format_on_save(format_on_save: bool, cx: &mut App) {
176    let fs = <dyn Fs>::global(cx);
177
178    update_settings_file(fs, cx, move |settings, _| {
179        settings.project.all_languages.defaults.format_on_save = Some(match format_on_save {
180            true => FormatOnSave::On,
181            false => FormatOnSave::Off,
182        });
183    });
184}
185
186fn render_setting_import_button(
187    tab_index: isize,
188    label: SharedString,
189    icon_name: IconName,
190    action: &dyn Action,
191    imported: bool,
192) -> impl IntoElement {
193    let action = action.boxed_clone();
194    h_flex().w_full().child(
195        ButtonLike::new(label.clone())
196            .full_width()
197            .style(ButtonStyle::Outlined)
198            .size(ButtonSize::Large)
199            .tab_index(tab_index)
200            .child(
201                h_flex()
202                    .w_full()
203                    .justify_between()
204                    .child(
205                        h_flex()
206                            .gap_1p5()
207                            .px_1()
208                            .child(
209                                Icon::new(icon_name)
210                                    .color(Color::Muted)
211                                    .size(IconSize::XSmall),
212                            )
213                            .child(Label::new(label.clone())),
214                    )
215                    .when(imported, |this| {
216                        this.child(
217                            h_flex()
218                                .gap_1p5()
219                                .child(
220                                    Icon::new(IconName::Check)
221                                        .color(Color::Success)
222                                        .size(IconSize::XSmall),
223                                )
224                                .child(Label::new("Imported").size(LabelSize::Small)),
225                        )
226                    }),
227            )
228            .on_click(move |_, window, cx| {
229                telemetry::event!("Welcome Import Settings", import_source = label,);
230                window.dispatch_action(action.boxed_clone(), cx);
231            }),
232    )
233}
234
235fn render_import_settings_section(tab_index: &mut isize, cx: &App) -> impl IntoElement {
236    let import_state = SettingsImportState::global(cx);
237    let imports: [(SharedString, IconName, &dyn Action, bool); 2] = [
238        (
239            "VS Code".into(),
240            IconName::EditorVsCode,
241            &ImportVsCodeSettings { skip_prompt: false },
242            import_state.vscode,
243        ),
244        (
245            "Cursor".into(),
246            IconName::EditorCursor,
247            &ImportCursorSettings { skip_prompt: false },
248            import_state.cursor,
249        ),
250    ];
251
252    let [vscode, cursor] = imports.map(|(label, icon_name, action, imported)| {
253        *tab_index += 1;
254        render_setting_import_button(*tab_index - 1, label, icon_name, action, imported)
255    });
256
257    v_flex()
258        .gap_4()
259        .child(
260            v_flex()
261                .child(Label::new("Import Settings").size(LabelSize::Large))
262                .child(
263                    Label::new("Automatically pull your settings from other editors.")
264                        .color(Color::Muted),
265                ),
266        )
267        .child(h_flex().w_full().gap_4().child(vscode).child(cursor))
268}
269
270fn render_font_customization_section(
271    tab_index: &mut isize,
272    window: &mut Window,
273    cx: &mut App,
274) -> impl IntoElement {
275    let theme_settings = ThemeSettings::get_global(cx);
276    let ui_font_size = theme_settings.ui_font_size(cx);
277    let ui_font_family = theme_settings.ui_font.family.clone();
278    let buffer_font_family = theme_settings.buffer_font.family.clone();
279    let buffer_font_size = theme_settings.buffer_font_size(cx);
280
281    let ui_font_picker =
282        cx.new(|cx| font_picker(ui_font_family.clone(), write_ui_font_family, window, cx));
283
284    let buffer_font_picker = cx.new(|cx| {
285        font_picker(
286            buffer_font_family.clone(),
287            write_buffer_font_family,
288            window,
289            cx,
290        )
291    });
292
293    let ui_font_handle = ui::PopoverMenuHandle::default();
294    let buffer_font_handle = ui::PopoverMenuHandle::default();
295
296    h_flex()
297        .w_full()
298        .gap_4()
299        .child(
300            v_flex()
301                .w_full()
302                .gap_1()
303                .child(Label::new("UI Font"))
304                .child(
305                    h_flex()
306                        .w_full()
307                        .justify_between()
308                        .gap_2()
309                        .child(
310                            PopoverMenu::new("ui-font-picker")
311                                .menu({
312                                    let ui_font_picker = ui_font_picker;
313                                    move |_window, _cx| Some(ui_font_picker.clone())
314                                })
315                                .trigger(
316                                    ButtonLike::new("ui-font-family-button")
317                                        .style(ButtonStyle::Outlined)
318                                        .size(ButtonSize::Medium)
319                                        .full_width()
320                                        .tab_index({
321                                            *tab_index += 1;
322                                            *tab_index - 1
323                                        })
324                                        .child(
325                                            h_flex()
326                                                .w_full()
327                                                .justify_between()
328                                                .child(Label::new(ui_font_family))
329                                                .child(
330                                                    Icon::new(IconName::ChevronUpDown)
331                                                        .color(Color::Muted)
332                                                        .size(IconSize::XSmall),
333                                                ),
334                                        ),
335                                )
336                                .full_width(true)
337                                .anchor(gpui::Corner::TopLeft)
338                                .offset(gpui::Point {
339                                    x: px(0.0),
340                                    y: px(4.0),
341                                })
342                                .with_handle(ui_font_handle),
343                        )
344                        .child(font_picker_stepper(
345                            "ui-font-size",
346                            &ui_font_size,
347                            tab_index,
348                            write_ui_font_size,
349                            window,
350                            cx,
351                        )),
352                ),
353        )
354        .child(
355            v_flex()
356                .w_full()
357                .gap_1()
358                .child(Label::new("Editor Font"))
359                .child(
360                    h_flex()
361                        .w_full()
362                        .justify_between()
363                        .gap_2()
364                        .child(
365                            PopoverMenu::new("buffer-font-picker")
366                                .menu({
367                                    let buffer_font_picker = buffer_font_picker;
368                                    move |_window, _cx| Some(buffer_font_picker.clone())
369                                })
370                                .trigger(
371                                    ButtonLike::new("buffer-font-family-button")
372                                        .style(ButtonStyle::Outlined)
373                                        .size(ButtonSize::Medium)
374                                        .full_width()
375                                        .tab_index({
376                                            *tab_index += 1;
377                                            *tab_index - 1
378                                        })
379                                        .child(
380                                            h_flex()
381                                                .w_full()
382                                                .justify_between()
383                                                .child(Label::new(buffer_font_family))
384                                                .child(
385                                                    Icon::new(IconName::ChevronUpDown)
386                                                        .color(Color::Muted)
387                                                        .size(IconSize::XSmall),
388                                                ),
389                                        ),
390                                )
391                                .full_width(true)
392                                .anchor(gpui::Corner::TopLeft)
393                                .offset(gpui::Point {
394                                    x: px(0.0),
395                                    y: px(4.0),
396                                })
397                                .with_handle(buffer_font_handle),
398                        )
399                        .child(font_picker_stepper(
400                            "buffer-font-size",
401                            &buffer_font_size,
402                            tab_index,
403                            write_buffer_font_size,
404                            window,
405                            cx,
406                        )),
407                ),
408        )
409}
410
411fn font_picker_stepper(
412    id: &'static str,
413    font_size: &Pixels,
414    tab_index: &mut isize,
415    write_font_size: fn(Pixels, &mut App),
416    window: &mut Window,
417    cx: &mut App,
418) -> NumericStepper<u32> {
419    window.with_id(id, |window| {
420        let optimistic_font_size: gpui::Entity<Option<u32>> = window.use_state(cx, |_, _| None);
421        optimistic_font_size.update(cx, |optimistic_font_size, _| {
422            if let Some(optimistic_font_size_val) = optimistic_font_size {
423                if *optimistic_font_size_val == u32::from(font_size) {
424                    *optimistic_font_size = None;
425                }
426            }
427        });
428
429        let stepper_font_size = optimistic_font_size
430            .read(cx)
431            .unwrap_or_else(|| font_size.into());
432
433        NumericStepper::new(
434            SharedString::new(format!("{}-stepper", id)),
435            stepper_font_size,
436            window,
437            cx,
438        )
439        .on_change(move |new_value, _, cx| {
440            optimistic_font_size.write(cx, Some(*new_value));
441            write_font_size(Pixels::from(*new_value), cx);
442        })
443        .format(|value| format!("{value}px"))
444        .style(ui_input::NumericStepperStyle::Outlined)
445        .tab_index({
446            *tab_index += 2;
447            *tab_index - 2
448        })
449        .min(6)
450        .max(32)
451    })
452}
453
454type FontPicker = Picker<FontPickerDelegate>;
455
456pub struct FontPickerDelegate {
457    fonts: Vec<SharedString>,
458    filtered_fonts: Vec<StringMatch>,
459    selected_index: usize,
460    current_font: SharedString,
461    on_font_changed: Arc<dyn Fn(SharedString, &mut App) + 'static>,
462}
463
464impl FontPickerDelegate {
465    fn new(
466        current_font: SharedString,
467        on_font_changed: impl Fn(SharedString, &mut App) + 'static,
468        cx: &mut Context<FontPicker>,
469    ) -> Self {
470        let font_family_cache = FontFamilyCache::global(cx);
471
472        let fonts = font_family_cache
473            .try_list_font_families()
474            .unwrap_or_else(|| vec![current_font.clone()]);
475        let selected_index = fonts
476            .iter()
477            .position(|font| *font == current_font)
478            .unwrap_or(0);
479
480        let filtered_fonts = fonts
481            .iter()
482            .enumerate()
483            .map(|(index, font)| StringMatch {
484                candidate_id: index,
485                string: font.to_string(),
486                positions: Vec::new(),
487                score: 0.0,
488            })
489            .collect();
490
491        Self {
492            fonts,
493            filtered_fonts,
494            selected_index,
495            current_font,
496            on_font_changed: Arc::new(on_font_changed),
497        }
498    }
499}
500
501impl PickerDelegate for FontPickerDelegate {
502    type ListItem = AnyElement;
503
504    fn match_count(&self) -> usize {
505        self.filtered_fonts.len()
506    }
507
508    fn selected_index(&self) -> usize {
509        self.selected_index
510    }
511
512    fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<FontPicker>) {
513        self.selected_index = ix.min(self.filtered_fonts.len().saturating_sub(1));
514        cx.notify();
515    }
516
517    fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
518        "Search fonts…".into()
519    }
520
521    fn update_matches(
522        &mut self,
523        query: String,
524        _window: &mut Window,
525        cx: &mut Context<FontPicker>,
526    ) -> Task<()> {
527        let fonts = self.fonts.clone();
528        let current_font = self.current_font.clone();
529
530        let matches: Vec<StringMatch> = if query.is_empty() {
531            fonts
532                .iter()
533                .enumerate()
534                .map(|(index, font)| StringMatch {
535                    candidate_id: index,
536                    string: font.to_string(),
537                    positions: Vec::new(),
538                    score: 0.0,
539                })
540                .collect()
541        } else {
542            let _candidates: Vec<StringMatchCandidate> = fonts
543                .iter()
544                .enumerate()
545                .map(|(id, font)| StringMatchCandidate::new(id, font.as_ref()))
546                .collect();
547
548            fonts
549                .iter()
550                .enumerate()
551                .filter(|(_, font)| font.to_lowercase().contains(&query.to_lowercase()))
552                .map(|(index, font)| StringMatch {
553                    candidate_id: index,
554                    string: font.to_string(),
555                    positions: Vec::new(),
556                    score: 0.0,
557                })
558                .collect()
559        };
560
561        let selected_index = if query.is_empty() {
562            fonts
563                .iter()
564                .position(|font| *font == current_font)
565                .unwrap_or(0)
566        } else {
567            matches
568                .iter()
569                .position(|m| fonts[m.candidate_id] == current_font)
570                .unwrap_or(0)
571        };
572
573        self.filtered_fonts = matches;
574        self.selected_index = selected_index;
575        cx.notify();
576
577        Task::ready(())
578    }
579
580    fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<FontPicker>) {
581        if let Some(font_match) = self.filtered_fonts.get(self.selected_index) {
582            let font = font_match.string.clone();
583            (self.on_font_changed)(font.into(), cx);
584        }
585    }
586
587    fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context<FontPicker>) {}
588
589    fn render_match(
590        &self,
591        ix: usize,
592        selected: bool,
593        _window: &mut Window,
594        _cx: &mut Context<FontPicker>,
595    ) -> Option<Self::ListItem> {
596        let font_match = self.filtered_fonts.get(ix)?;
597
598        Some(
599            ListItem::new(ix)
600                .inset(true)
601                .spacing(ListItemSpacing::Sparse)
602                .toggle_state(selected)
603                .child(Label::new(font_match.string.clone()))
604                .into_any_element(),
605        )
606    }
607}
608
609fn font_picker(
610    current_font: SharedString,
611    on_font_changed: impl Fn(SharedString, &mut App) + 'static,
612    window: &mut Window,
613    cx: &mut Context<FontPicker>,
614) -> FontPicker {
615    let delegate = FontPickerDelegate::new(current_font, on_font_changed, cx);
616
617    Picker::uniform_list(delegate, window, cx)
618        .show_scrollbar(true)
619        .width(rems_from_px(210.))
620        .max_height(Some(rems(20.).into()))
621}
622
623fn render_popular_settings_section(
624    tab_index: &mut isize,
625    window: &mut Window,
626    cx: &mut App,
627) -> impl IntoElement {
628    const LIGATURE_TOOLTIP: &str =
629        "Font ligatures combine two characters into one. For example, turning != into ≠.";
630
631    v_flex()
632        .pt_6()
633        .gap_4()
634        .border_t_1()
635        .border_color(cx.theme().colors().border_variant.opacity(0.5))
636        .child(Label::new("Popular Settings").size(LabelSize::Large))
637        .child(render_font_customization_section(tab_index, window, cx))
638        .child(
639            SwitchField::new(
640                "onboarding-font-ligatures",
641                "Font Ligatures",
642                Some("Combine text characters into their associated symbols.".into()),
643                if read_font_ligatures(cx) {
644                    ui::ToggleState::Selected
645                } else {
646                    ui::ToggleState::Unselected
647                },
648                |toggle_state, _, cx| {
649                    let enabled = toggle_state == &ToggleState::Selected;
650                    telemetry::event!(
651                        "Welcome Font Ligature",
652                        options = if enabled { "on" } else { "off" },
653                    );
654
655                    write_font_ligatures(enabled, cx);
656                },
657            )
658            .tab_index({
659                *tab_index += 1;
660                *tab_index - 1
661            })
662            .tooltip(Tooltip::text(LIGATURE_TOOLTIP)),
663        )
664        .child(
665            SwitchField::new(
666                "onboarding-format-on-save",
667                "Format on Save",
668                Some("Format code automatically when saving.".into()),
669                if read_format_on_save(cx) {
670                    ui::ToggleState::Selected
671                } else {
672                    ui::ToggleState::Unselected
673                },
674                |toggle_state, _, cx| {
675                    let enabled = toggle_state == &ToggleState::Selected;
676                    telemetry::event!(
677                        "Welcome Format On Save Changed",
678                        options = if enabled { "on" } else { "off" },
679                    );
680
681                    write_format_on_save(enabled, cx);
682                },
683            )
684            .tab_index({
685                *tab_index += 1;
686                *tab_index - 1
687            }),
688        )
689        .child(
690            SwitchField::new(
691                "onboarding-enable-inlay-hints",
692                "Inlay Hints",
693                Some("See parameter names for function and method calls inline.".into()),
694                if read_inlay_hints(cx) {
695                    ui::ToggleState::Selected
696                } else {
697                    ui::ToggleState::Unselected
698                },
699                |toggle_state, _, cx| {
700                    let enabled = toggle_state == &ToggleState::Selected;
701                    telemetry::event!(
702                        "Welcome Inlay Hints Changed",
703                        options = if enabled { "on" } else { "off" },
704                    );
705
706                    write_inlay_hints(enabled, cx);
707                },
708            )
709            .tab_index({
710                *tab_index += 1;
711                *tab_index - 1
712            }),
713        )
714        .child(
715            SwitchField::new(
716                "onboarding-git-blame-switch",
717                "Inline Git Blame",
718                Some("See who committed each line on a given file.".into()),
719                if read_git_blame(cx) {
720                    ui::ToggleState::Selected
721                } else {
722                    ui::ToggleState::Unselected
723                },
724                |toggle_state, _, cx| {
725                    let enabled = toggle_state == &ToggleState::Selected;
726                    telemetry::event!(
727                        "Welcome Git Blame Changed",
728                        options = if enabled { "on" } else { "off" },
729                    );
730
731                    write_git_blame(enabled, cx);
732                },
733            )
734            .tab_index({
735                *tab_index += 1;
736                *tab_index - 1
737            }),
738        )
739        .child(
740            h_flex()
741                .items_start()
742                .justify_between()
743                .child(
744                    v_flex().child(Label::new("Minimap")).child(
745                        Label::new("See a high-level overview of your source code.")
746                            .color(Color::Muted),
747                    ),
748                )
749                .child(
750                    ToggleButtonGroup::single_row(
751                        "onboarding-show-mini-map",
752                        [
753                            ToggleButtonSimple::new("Auto", |_, _, cx| {
754                                write_show_mini_map(ShowMinimap::Auto, cx);
755                            })
756                            .tooltip(Tooltip::text(
757                                "Show the minimap if the editor's scrollbar is visible.",
758                            )),
759                            ToggleButtonSimple::new("Always", |_, _, cx| {
760                                write_show_mini_map(ShowMinimap::Always, cx);
761                            }),
762                            ToggleButtonSimple::new("Never", |_, _, cx| {
763                                write_show_mini_map(ShowMinimap::Never, cx);
764                            }),
765                        ],
766                    )
767                    .selected_index(match read_show_mini_map(cx) {
768                        ShowMinimap::Auto => 0,
769                        ShowMinimap::Always => 1,
770                        ShowMinimap::Never => 2,
771                    })
772                    .tab_index(tab_index)
773                    .style(ToggleButtonGroupStyle::Outlined)
774                    .width(ui::rems_from_px(3. * 64.)),
775                ),
776        )
777}
778
779pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
780    let mut tab_index = 0;
781    v_flex()
782        .gap_6()
783        .child(render_import_settings_section(&mut tab_index, cx))
784        .child(render_popular_settings_section(&mut tab_index, window, cx))
785}