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