1use crate::config::{
7 ClipboardConfig, CursorStyle, FileBrowserConfig, FileExplorerConfig, FormatterConfig,
8 Keybinding, KeybindingMapName, KeymapConfig, LanguageConfig, LineEndingOption, OnSaveAction,
9 PluginConfig, TerminalConfig, ThemeName, WarningsConfig,
10};
11use crate::types::LspLanguageConfig;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15pub trait Merge {
18 fn merge_from(&mut self, other: &Self);
21}
22
23impl<T: Clone> Merge for Option<T> {
24 fn merge_from(&mut self, other: &Self) {
25 if self.is_none() {
26 *self = other.clone();
27 }
28 }
29}
30
31fn merge_hashmap<K: Clone + Eq + std::hash::Hash, V: Clone>(
34 target: &mut Option<HashMap<K, V>>,
35 other: &Option<HashMap<K, V>>,
36) {
37 match (target, other) {
38 (Some(t), Some(o)) => {
39 for (key, value) in o {
40 t.entry(key.clone()).or_insert_with(|| value.clone());
41 }
42 }
43 (t @ None, Some(o)) => {
44 *t = Some(o.clone());
45 }
46 _ => {}
47 }
48}
49
50fn merge_hashmap_recursive<K, V>(target: &mut Option<HashMap<K, V>>, other: &Option<HashMap<K, V>>)
52where
53 K: Clone + Eq + std::hash::Hash,
54 V: Clone + Merge + Default,
55{
56 match (target, other) {
57 (Some(t), Some(o)) => {
58 for (key, value) in o {
59 t.entry(key.clone())
60 .and_modify(|existing| existing.merge_from(value))
61 .or_insert_with(|| value.clone());
62 }
63 }
64 (t @ None, Some(o)) => {
65 *t = Some(o.clone());
66 }
67 _ => {}
68 }
69}
70
71#[derive(Debug, Clone, Default, Deserialize, Serialize)]
74#[serde(default)]
75pub struct PartialConfig {
76 pub version: Option<u32>,
77 pub theme: Option<ThemeName>,
78 pub locale: Option<String>,
79 pub check_for_updates: Option<bool>,
80 pub editor: Option<PartialEditorConfig>,
81 pub file_explorer: Option<PartialFileExplorerConfig>,
82 pub file_browser: Option<PartialFileBrowserConfig>,
83 pub clipboard: Option<PartialClipboardConfig>,
84 pub terminal: Option<PartialTerminalConfig>,
85 pub keybindings: Option<Vec<Keybinding>>,
86 pub keybinding_maps: Option<HashMap<String, KeymapConfig>>,
87 pub active_keybinding_map: Option<KeybindingMapName>,
88 pub languages: Option<HashMap<String, PartialLanguageConfig>>,
89 pub default_language: Option<String>,
90 pub lsp: Option<HashMap<String, LspLanguageConfig>>,
91 pub universal_lsp: Option<HashMap<String, LspLanguageConfig>>,
92 pub warnings: Option<PartialWarningsConfig>,
93 pub plugins: Option<HashMap<String, PartialPluginConfig>>,
94 pub packages: Option<PartialPackagesConfig>,
95}
96
97impl Merge for PartialConfig {
98 fn merge_from(&mut self, other: &Self) {
99 self.version.merge_from(&other.version);
100 self.theme.merge_from(&other.theme);
101 self.locale.merge_from(&other.locale);
102 self.check_for_updates.merge_from(&other.check_for_updates);
103
104 merge_partial(&mut self.editor, &other.editor);
106 merge_partial(&mut self.file_explorer, &other.file_explorer);
107 merge_partial(&mut self.file_browser, &other.file_browser);
108 merge_partial(&mut self.clipboard, &other.clipboard);
109 merge_partial(&mut self.terminal, &other.terminal);
110 merge_partial(&mut self.warnings, &other.warnings);
111 merge_partial(&mut self.packages, &other.packages);
112
113 self.keybindings.merge_from(&other.keybindings);
115
116 merge_hashmap(&mut self.keybinding_maps, &other.keybinding_maps);
118 merge_hashmap_recursive(&mut self.languages, &other.languages);
119 self.default_language.merge_from(&other.default_language);
120 merge_hashmap(&mut self.lsp, &other.lsp);
121 merge_hashmap(&mut self.universal_lsp, &other.universal_lsp);
122 merge_hashmap_recursive(&mut self.plugins, &other.plugins);
123
124 self.active_keybinding_map
125 .merge_from(&other.active_keybinding_map);
126 }
127}
128
129fn merge_partial<T: Merge + Clone>(target: &mut Option<T>, other: &Option<T>) {
131 match (target, other) {
132 (Some(t), Some(o)) => t.merge_from(o),
133 (t @ None, Some(o)) => *t = Some(o.clone()),
134 _ => {}
135 }
136}
137
138#[derive(Debug, Clone, Default, Deserialize, Serialize)]
140#[serde(default)]
141pub struct PartialEditorConfig {
142 pub use_tabs: Option<bool>,
143 pub tab_size: Option<usize>,
144 pub auto_indent: Option<bool>,
145 pub auto_close: Option<bool>,
146 pub auto_surround: Option<bool>,
147 pub animations: Option<bool>,
148 pub cursor_jump_animation: Option<bool>,
149 pub line_numbers: Option<bool>,
150 pub relative_line_numbers: Option<bool>,
151 pub scroll_offset: Option<usize>,
152 pub syntax_highlighting: Option<bool>,
153 pub highlight_current_line: Option<bool>,
154 pub highlight_current_column: Option<bool>,
155 pub line_wrap: Option<bool>,
156 pub wrap_indent: Option<bool>,
157 pub wrap_column: Option<Option<usize>>,
158 pub page_width: Option<Option<usize>>,
159 pub highlight_timeout_ms: Option<u64>,
160 pub snapshot_interval: Option<usize>,
161 pub large_file_threshold_bytes: Option<u64>,
162 pub estimated_line_length: Option<usize>,
163 pub enable_inlay_hints: Option<bool>,
164 pub enable_semantic_tokens_full: Option<bool>,
165 pub diagnostics_inline_text: Option<bool>,
166 pub recovery_enabled: Option<bool>,
167 pub auto_recovery_save_interval_secs: Option<u32>,
168 pub auto_save_enabled: Option<bool>,
169 pub auto_save_interval_secs: Option<u32>,
170 pub hot_exit: Option<bool>,
171 pub restore_previous_session: Option<bool>,
172 pub skip_session_restore_when_files_passed: Option<bool>,
173 pub auto_create_empty_buffer_on_last_buffer_close: Option<bool>,
174 pub highlight_context_bytes: Option<usize>,
175 pub mouse_hover_enabled: Option<bool>,
176 pub mouse_hover_delay_ms: Option<u64>,
177 pub double_click_time_ms: Option<u64>,
178 pub auto_revert_poll_interval_ms: Option<u64>,
179 pub read_concurrency: Option<usize>,
180 pub file_tree_poll_interval_ms: Option<u64>,
181 pub default_line_ending: Option<LineEndingOption>,
182 pub trim_trailing_whitespace_on_save: Option<bool>,
183 pub ensure_final_newline_on_save: Option<bool>,
184 pub highlight_matching_brackets: Option<bool>,
185 pub rainbow_brackets: Option<bool>,
186 pub cursor_style: Option<CursorStyle>,
187 pub keyboard_disambiguate_escape_codes: Option<bool>,
188 pub keyboard_report_event_types: Option<bool>,
189 pub keyboard_report_alternate_keys: Option<bool>,
190 pub keyboard_report_all_keys_as_escape_codes: Option<bool>,
191 pub completion_popup_auto_show: Option<bool>,
192 pub quick_suggestions: Option<bool>,
193 pub quick_suggestions_delay_ms: Option<u64>,
194 pub suggest_on_trigger_characters: Option<bool>,
195 pub show_menu_bar: Option<bool>,
196 pub menu_bar_mnemonics: Option<bool>,
197 pub show_tab_bar: Option<bool>,
198 pub show_status_bar: Option<bool>,
199 pub status_bar: Option<crate::config::StatusBarConfig>,
200 pub show_prompt_line: Option<bool>,
201 pub show_vertical_scrollbar: Option<bool>,
202 pub show_horizontal_scrollbar: Option<bool>,
203 pub show_tilde: Option<bool>,
204 pub use_terminal_bg: Option<bool>,
205 pub set_window_title: Option<bool>,
206 pub rulers: Option<Vec<usize>>,
207 pub whitespace_show: Option<bool>,
208 pub whitespace_spaces_leading: Option<bool>,
209 pub whitespace_spaces_inner: Option<bool>,
210 pub whitespace_spaces_trailing: Option<bool>,
211 pub whitespace_tabs_leading: Option<bool>,
212 pub whitespace_tabs_inner: Option<bool>,
213 pub whitespace_tabs_trailing: Option<bool>,
214}
215
216impl Merge for PartialEditorConfig {
217 fn merge_from(&mut self, other: &Self) {
218 self.use_tabs.merge_from(&other.use_tabs);
219 self.tab_size.merge_from(&other.tab_size);
220 self.auto_indent.merge_from(&other.auto_indent);
221 self.auto_close.merge_from(&other.auto_close);
222 self.auto_surround.merge_from(&other.auto_surround);
223 self.animations.merge_from(&other.animations);
224 self.cursor_jump_animation
225 .merge_from(&other.cursor_jump_animation);
226 self.line_numbers.merge_from(&other.line_numbers);
227 self.relative_line_numbers
228 .merge_from(&other.relative_line_numbers);
229 self.scroll_offset.merge_from(&other.scroll_offset);
230 self.syntax_highlighting
231 .merge_from(&other.syntax_highlighting);
232 self.line_wrap.merge_from(&other.line_wrap);
233 self.wrap_indent.merge_from(&other.wrap_indent);
234 self.wrap_column.merge_from(&other.wrap_column);
235 self.page_width.merge_from(&other.page_width);
236 self.highlight_timeout_ms
237 .merge_from(&other.highlight_timeout_ms);
238 self.snapshot_interval.merge_from(&other.snapshot_interval);
239 self.large_file_threshold_bytes
240 .merge_from(&other.large_file_threshold_bytes);
241 self.estimated_line_length
242 .merge_from(&other.estimated_line_length);
243 self.enable_inlay_hints
244 .merge_from(&other.enable_inlay_hints);
245 self.enable_semantic_tokens_full
246 .merge_from(&other.enable_semantic_tokens_full);
247 self.diagnostics_inline_text
248 .merge_from(&other.diagnostics_inline_text);
249 self.recovery_enabled.merge_from(&other.recovery_enabled);
250 self.auto_recovery_save_interval_secs
251 .merge_from(&other.auto_recovery_save_interval_secs);
252 self.auto_save_enabled.merge_from(&other.auto_save_enabled);
253 self.auto_save_interval_secs
254 .merge_from(&other.auto_save_interval_secs);
255 self.hot_exit.merge_from(&other.hot_exit);
256 self.restore_previous_session
257 .merge_from(&other.restore_previous_session);
258 self.skip_session_restore_when_files_passed
259 .merge_from(&other.skip_session_restore_when_files_passed);
260 self.auto_create_empty_buffer_on_last_buffer_close
261 .merge_from(&other.auto_create_empty_buffer_on_last_buffer_close);
262 self.highlight_context_bytes
263 .merge_from(&other.highlight_context_bytes);
264 self.mouse_hover_enabled
265 .merge_from(&other.mouse_hover_enabled);
266 self.mouse_hover_delay_ms
267 .merge_from(&other.mouse_hover_delay_ms);
268 self.double_click_time_ms
269 .merge_from(&other.double_click_time_ms);
270 self.auto_revert_poll_interval_ms
271 .merge_from(&other.auto_revert_poll_interval_ms);
272 self.read_concurrency.merge_from(&other.read_concurrency);
273 self.file_tree_poll_interval_ms
274 .merge_from(&other.file_tree_poll_interval_ms);
275 self.default_line_ending
276 .merge_from(&other.default_line_ending);
277 self.trim_trailing_whitespace_on_save
278 .merge_from(&other.trim_trailing_whitespace_on_save);
279 self.ensure_final_newline_on_save
280 .merge_from(&other.ensure_final_newline_on_save);
281 self.highlight_matching_brackets
282 .merge_from(&other.highlight_matching_brackets);
283 self.rainbow_brackets.merge_from(&other.rainbow_brackets);
284 self.cursor_style.merge_from(&other.cursor_style);
285 self.keyboard_disambiguate_escape_codes
286 .merge_from(&other.keyboard_disambiguate_escape_codes);
287 self.keyboard_report_event_types
288 .merge_from(&other.keyboard_report_event_types);
289 self.keyboard_report_alternate_keys
290 .merge_from(&other.keyboard_report_alternate_keys);
291 self.keyboard_report_all_keys_as_escape_codes
292 .merge_from(&other.keyboard_report_all_keys_as_escape_codes);
293 self.completion_popup_auto_show
294 .merge_from(&other.completion_popup_auto_show);
295 self.quick_suggestions.merge_from(&other.quick_suggestions);
296 self.quick_suggestions_delay_ms
297 .merge_from(&other.quick_suggestions_delay_ms);
298 self.suggest_on_trigger_characters
299 .merge_from(&other.suggest_on_trigger_characters);
300 self.show_menu_bar.merge_from(&other.show_menu_bar);
301 self.menu_bar_mnemonics
302 .merge_from(&other.menu_bar_mnemonics);
303 self.show_tab_bar.merge_from(&other.show_tab_bar);
304 self.show_status_bar.merge_from(&other.show_status_bar);
305 if other.status_bar.is_some() {
306 self.status_bar = other.status_bar.clone();
307 }
308 self.show_prompt_line.merge_from(&other.show_prompt_line);
309 self.show_vertical_scrollbar
310 .merge_from(&other.show_vertical_scrollbar);
311 self.show_horizontal_scrollbar
312 .merge_from(&other.show_horizontal_scrollbar);
313 self.show_tilde.merge_from(&other.show_tilde);
314 self.use_terminal_bg.merge_from(&other.use_terminal_bg);
315 self.set_window_title.merge_from(&other.set_window_title);
316 self.rulers.merge_from(&other.rulers);
317 self.whitespace_show.merge_from(&other.whitespace_show);
318 self.whitespace_spaces_leading
319 .merge_from(&other.whitespace_spaces_leading);
320 self.whitespace_spaces_inner
321 .merge_from(&other.whitespace_spaces_inner);
322 self.whitespace_spaces_trailing
323 .merge_from(&other.whitespace_spaces_trailing);
324 self.whitespace_tabs_leading
325 .merge_from(&other.whitespace_tabs_leading);
326 self.whitespace_tabs_inner
327 .merge_from(&other.whitespace_tabs_inner);
328 self.whitespace_tabs_trailing
329 .merge_from(&other.whitespace_tabs_trailing);
330 }
331}
332
333#[derive(Debug, Clone, Default, Deserialize, Serialize)]
335#[serde(default)]
336pub struct PartialFileExplorerConfig {
337 pub respect_gitignore: Option<bool>,
338 pub show_hidden: Option<bool>,
339 pub show_gitignored: Option<bool>,
340 pub custom_ignore_patterns: Option<Vec<String>>,
341 #[serde(
342 default,
343 deserialize_with = "crate::config::explorer_width::deserialize_optional"
344 )]
345 pub width: Option<crate::config::ExplorerWidth>,
346 pub preview_tabs: Option<bool>,
347 pub side: Option<crate::config::FileExplorerSide>,
348 pub auto_open_on_last_buffer_close: Option<bool>,
349}
350
351impl Merge for PartialFileExplorerConfig {
352 fn merge_from(&mut self, other: &Self) {
353 self.respect_gitignore.merge_from(&other.respect_gitignore);
354 self.show_hidden.merge_from(&other.show_hidden);
355 self.show_gitignored.merge_from(&other.show_gitignored);
356 self.custom_ignore_patterns
357 .merge_from(&other.custom_ignore_patterns);
358 self.width.merge_from(&other.width);
359 self.preview_tabs.merge_from(&other.preview_tabs);
360 self.side.merge_from(&other.side);
361 self.auto_open_on_last_buffer_close
362 .merge_from(&other.auto_open_on_last_buffer_close);
363 }
364}
365
366#[derive(Debug, Clone, Default, Deserialize, Serialize)]
368#[serde(default)]
369pub struct PartialFileBrowserConfig {
370 pub show_hidden: Option<bool>,
371}
372
373impl Merge for PartialFileBrowserConfig {
374 fn merge_from(&mut self, other: &Self) {
375 self.show_hidden.merge_from(&other.show_hidden);
376 }
377}
378
379#[derive(Debug, Clone, Default, Deserialize, Serialize)]
381#[serde(default)]
382pub struct PartialClipboardConfig {
383 pub use_osc52: Option<bool>,
384 pub use_system_clipboard: Option<bool>,
385}
386
387impl Merge for PartialClipboardConfig {
388 fn merge_from(&mut self, other: &Self) {
389 self.use_osc52.merge_from(&other.use_osc52);
390 self.use_system_clipboard
391 .merge_from(&other.use_system_clipboard);
392 }
393}
394
395#[derive(Debug, Clone, Default, Deserialize, Serialize)]
397#[serde(default)]
398pub struct PartialTerminalConfig {
399 pub jump_to_end_on_output: Option<bool>,
400 pub shell: Option<crate::config::TerminalShellConfig>,
401}
402
403impl Merge for PartialTerminalConfig {
404 fn merge_from(&mut self, other: &Self) {
405 self.jump_to_end_on_output
406 .merge_from(&other.jump_to_end_on_output);
407 self.shell.merge_from(&other.shell);
408 }
409}
410
411#[derive(Debug, Clone, Default, Deserialize, Serialize)]
413#[serde(default)]
414pub struct PartialWarningsConfig {
415 pub show_status_indicator: Option<bool>,
416}
417
418impl Merge for PartialWarningsConfig {
419 fn merge_from(&mut self, other: &Self) {
420 self.show_status_indicator
421 .merge_from(&other.show_status_indicator);
422 }
423}
424
425#[derive(Debug, Clone, Default, Deserialize, Serialize)]
427#[serde(default)]
428pub struct PartialPackagesConfig {
429 pub sources: Option<Vec<String>>,
430}
431
432impl Merge for PartialPackagesConfig {
433 fn merge_from(&mut self, other: &Self) {
434 self.sources.merge_from(&other.sources);
435 }
436}
437
438#[derive(Debug, Clone, Default, Deserialize, Serialize)]
440#[serde(default)]
441pub struct PartialPluginConfig {
442 #[serde(skip_serializing_if = "Option::is_none")]
443 pub enabled: Option<bool>,
444 #[serde(skip_serializing_if = "Option::is_none")]
445 pub path: Option<std::path::PathBuf>,
446}
447
448impl Merge for PartialPluginConfig {
449 fn merge_from(&mut self, other: &Self) {
450 self.enabled.merge_from(&other.enabled);
451 self.path.merge_from(&other.path);
452 }
453}
454
455#[derive(Debug, Clone, Default, Deserialize, Serialize)]
457#[serde(default)]
458pub struct PartialLanguageConfig {
459 pub extensions: Option<Vec<String>>,
460 pub filenames: Option<Vec<String>>,
461 pub grammar: Option<String>,
462 pub comment_prefix: Option<String>,
463 pub auto_indent: Option<bool>,
464 pub auto_close: Option<bool>,
465 pub auto_surround: Option<bool>,
466 pub textmate_grammar: Option<std::path::PathBuf>,
467 pub show_whitespace_tabs: Option<bool>,
468 pub line_wrap: Option<bool>,
469 pub wrap_column: Option<Option<usize>>,
470 pub page_view: Option<bool>,
471 pub page_width: Option<Option<usize>>,
472 pub use_tabs: Option<bool>,
473 pub tab_size: Option<usize>,
474 pub formatter: Option<FormatterConfig>,
475 pub format_on_save: Option<bool>,
476 pub on_save: Option<Vec<OnSaveAction>>,
477 pub word_characters: Option<Option<String>>,
478}
479
480impl Merge for PartialLanguageConfig {
481 fn merge_from(&mut self, other: &Self) {
482 self.extensions.merge_from(&other.extensions);
483 self.filenames.merge_from(&other.filenames);
484 self.grammar.merge_from(&other.grammar);
485 self.comment_prefix.merge_from(&other.comment_prefix);
486 self.auto_indent.merge_from(&other.auto_indent);
487 self.auto_close.merge_from(&other.auto_close);
488 self.auto_surround.merge_from(&other.auto_surround);
489 self.textmate_grammar.merge_from(&other.textmate_grammar);
490 self.show_whitespace_tabs
491 .merge_from(&other.show_whitespace_tabs);
492 self.line_wrap.merge_from(&other.line_wrap);
493 self.wrap_column.merge_from(&other.wrap_column);
494 self.page_view.merge_from(&other.page_view);
495 self.page_width.merge_from(&other.page_width);
496 self.use_tabs.merge_from(&other.use_tabs);
497 self.tab_size.merge_from(&other.tab_size);
498 self.formatter.merge_from(&other.formatter);
499 self.format_on_save.merge_from(&other.format_on_save);
500 self.on_save.merge_from(&other.on_save);
501 self.word_characters.merge_from(&other.word_characters);
502 }
503}
504
505impl From<&crate::config::EditorConfig> for PartialEditorConfig {
508 fn from(cfg: &crate::config::EditorConfig) -> Self {
509 Self {
510 use_tabs: Some(cfg.use_tabs),
511 tab_size: Some(cfg.tab_size),
512 auto_indent: Some(cfg.auto_indent),
513 auto_close: Some(cfg.auto_close),
514 auto_surround: Some(cfg.auto_surround),
515 animations: Some(cfg.animations),
516 cursor_jump_animation: Some(cfg.cursor_jump_animation),
517 line_numbers: Some(cfg.line_numbers),
518 relative_line_numbers: Some(cfg.relative_line_numbers),
519 scroll_offset: Some(cfg.scroll_offset),
520 syntax_highlighting: Some(cfg.syntax_highlighting),
521 highlight_current_line: Some(cfg.highlight_current_line),
522 highlight_current_column: Some(cfg.highlight_current_column),
523 line_wrap: Some(cfg.line_wrap),
524 wrap_indent: Some(cfg.wrap_indent),
525 wrap_column: Some(cfg.wrap_column),
526 page_width: Some(cfg.page_width),
527 highlight_timeout_ms: Some(cfg.highlight_timeout_ms),
528 snapshot_interval: Some(cfg.snapshot_interval),
529 large_file_threshold_bytes: Some(cfg.large_file_threshold_bytes),
530 estimated_line_length: Some(cfg.estimated_line_length),
531 enable_inlay_hints: Some(cfg.enable_inlay_hints),
532 enable_semantic_tokens_full: Some(cfg.enable_semantic_tokens_full),
533 diagnostics_inline_text: Some(cfg.diagnostics_inline_text),
534 recovery_enabled: Some(cfg.recovery_enabled),
535 auto_recovery_save_interval_secs: Some(cfg.auto_recovery_save_interval_secs),
536 auto_save_enabled: Some(cfg.auto_save_enabled),
537 auto_save_interval_secs: Some(cfg.auto_save_interval_secs),
538 hot_exit: Some(cfg.hot_exit),
539 restore_previous_session: Some(cfg.restore_previous_session),
540 skip_session_restore_when_files_passed: Some(
541 cfg.skip_session_restore_when_files_passed,
542 ),
543 auto_create_empty_buffer_on_last_buffer_close: Some(
544 cfg.auto_create_empty_buffer_on_last_buffer_close,
545 ),
546 highlight_context_bytes: Some(cfg.highlight_context_bytes),
547 mouse_hover_enabled: Some(cfg.mouse_hover_enabled),
548 mouse_hover_delay_ms: Some(cfg.mouse_hover_delay_ms),
549 double_click_time_ms: Some(cfg.double_click_time_ms),
550 auto_revert_poll_interval_ms: Some(cfg.auto_revert_poll_interval_ms),
551 read_concurrency: Some(cfg.read_concurrency),
552 file_tree_poll_interval_ms: Some(cfg.file_tree_poll_interval_ms),
553 default_line_ending: Some(cfg.default_line_ending.clone()),
554 trim_trailing_whitespace_on_save: Some(cfg.trim_trailing_whitespace_on_save),
555 ensure_final_newline_on_save: Some(cfg.ensure_final_newline_on_save),
556 highlight_matching_brackets: Some(cfg.highlight_matching_brackets),
557 rainbow_brackets: Some(cfg.rainbow_brackets),
558 cursor_style: Some(cfg.cursor_style),
559 keyboard_disambiguate_escape_codes: Some(cfg.keyboard_disambiguate_escape_codes),
560 keyboard_report_event_types: Some(cfg.keyboard_report_event_types),
561 keyboard_report_alternate_keys: Some(cfg.keyboard_report_alternate_keys),
562 keyboard_report_all_keys_as_escape_codes: Some(
563 cfg.keyboard_report_all_keys_as_escape_codes,
564 ),
565 completion_popup_auto_show: Some(cfg.completion_popup_auto_show),
566 quick_suggestions: Some(cfg.quick_suggestions),
567 quick_suggestions_delay_ms: Some(cfg.quick_suggestions_delay_ms),
568 suggest_on_trigger_characters: Some(cfg.suggest_on_trigger_characters),
569 show_menu_bar: Some(cfg.show_menu_bar),
570 menu_bar_mnemonics: Some(cfg.menu_bar_mnemonics),
571 show_tab_bar: Some(cfg.show_tab_bar),
572 show_status_bar: Some(cfg.show_status_bar),
573 status_bar: Some(cfg.status_bar.clone()),
574 show_prompt_line: Some(cfg.show_prompt_line),
575 show_vertical_scrollbar: Some(cfg.show_vertical_scrollbar),
576 show_horizontal_scrollbar: Some(cfg.show_horizontal_scrollbar),
577 show_tilde: Some(cfg.show_tilde),
578 use_terminal_bg: Some(cfg.use_terminal_bg),
579 set_window_title: Some(cfg.set_window_title),
580 rulers: Some(cfg.rulers.clone()),
581 whitespace_show: Some(cfg.whitespace_show),
582 whitespace_spaces_leading: Some(cfg.whitespace_spaces_leading),
583 whitespace_spaces_inner: Some(cfg.whitespace_spaces_inner),
584 whitespace_spaces_trailing: Some(cfg.whitespace_spaces_trailing),
585 whitespace_tabs_leading: Some(cfg.whitespace_tabs_leading),
586 whitespace_tabs_inner: Some(cfg.whitespace_tabs_inner),
587 whitespace_tabs_trailing: Some(cfg.whitespace_tabs_trailing),
588 }
589 }
590}
591
592impl PartialEditorConfig {
593 pub fn resolve(self, defaults: &crate::config::EditorConfig) -> crate::config::EditorConfig {
595 crate::config::EditorConfig {
596 use_tabs: self.use_tabs.unwrap_or(defaults.use_tabs),
597 tab_size: self.tab_size.unwrap_or(defaults.tab_size),
598 auto_indent: self.auto_indent.unwrap_or(defaults.auto_indent),
599 auto_close: self.auto_close.unwrap_or(defaults.auto_close),
600 auto_surround: self.auto_surround.unwrap_or(defaults.auto_surround),
601 animations: self.animations.unwrap_or(defaults.animations),
602 cursor_jump_animation: self
603 .cursor_jump_animation
604 .unwrap_or(defaults.cursor_jump_animation),
605 line_numbers: self.line_numbers.unwrap_or(defaults.line_numbers),
606 relative_line_numbers: self
607 .relative_line_numbers
608 .unwrap_or(defaults.relative_line_numbers),
609 scroll_offset: self.scroll_offset.unwrap_or(defaults.scroll_offset),
610 syntax_highlighting: self
611 .syntax_highlighting
612 .unwrap_or(defaults.syntax_highlighting),
613 highlight_current_line: self
614 .highlight_current_line
615 .unwrap_or(defaults.highlight_current_line),
616 highlight_current_column: self
617 .highlight_current_column
618 .unwrap_or(defaults.highlight_current_column),
619 line_wrap: self.line_wrap.unwrap_or(defaults.line_wrap),
620 wrap_indent: self.wrap_indent.unwrap_or(defaults.wrap_indent),
621 wrap_column: self.wrap_column.unwrap_or(defaults.wrap_column),
622 page_width: self.page_width.unwrap_or(defaults.page_width),
623 highlight_timeout_ms: self
624 .highlight_timeout_ms
625 .unwrap_or(defaults.highlight_timeout_ms),
626 snapshot_interval: self.snapshot_interval.unwrap_or(defaults.snapshot_interval),
627 large_file_threshold_bytes: self
628 .large_file_threshold_bytes
629 .unwrap_or(defaults.large_file_threshold_bytes),
630 estimated_line_length: self
631 .estimated_line_length
632 .unwrap_or(defaults.estimated_line_length),
633 enable_inlay_hints: self
634 .enable_inlay_hints
635 .unwrap_or(defaults.enable_inlay_hints),
636 enable_semantic_tokens_full: self
637 .enable_semantic_tokens_full
638 .unwrap_or(defaults.enable_semantic_tokens_full),
639 diagnostics_inline_text: self
640 .diagnostics_inline_text
641 .unwrap_or(defaults.diagnostics_inline_text),
642 recovery_enabled: self.recovery_enabled.unwrap_or(defaults.recovery_enabled),
643 auto_recovery_save_interval_secs: self
644 .auto_recovery_save_interval_secs
645 .unwrap_or(defaults.auto_recovery_save_interval_secs),
646 auto_save_enabled: self.auto_save_enabled.unwrap_or(defaults.auto_save_enabled),
647 auto_save_interval_secs: self
648 .auto_save_interval_secs
649 .unwrap_or(defaults.auto_save_interval_secs),
650 hot_exit: self.hot_exit.unwrap_or(defaults.hot_exit),
651 restore_previous_session: self
652 .restore_previous_session
653 .unwrap_or(defaults.restore_previous_session),
654 skip_session_restore_when_files_passed: self
655 .skip_session_restore_when_files_passed
656 .unwrap_or(defaults.skip_session_restore_when_files_passed),
657 auto_create_empty_buffer_on_last_buffer_close: self
658 .auto_create_empty_buffer_on_last_buffer_close
659 .unwrap_or(defaults.auto_create_empty_buffer_on_last_buffer_close),
660 highlight_context_bytes: self
661 .highlight_context_bytes
662 .unwrap_or(defaults.highlight_context_bytes),
663 mouse_hover_enabled: self
664 .mouse_hover_enabled
665 .unwrap_or(defaults.mouse_hover_enabled),
666 mouse_hover_delay_ms: self
667 .mouse_hover_delay_ms
668 .unwrap_or(defaults.mouse_hover_delay_ms),
669 double_click_time_ms: self
670 .double_click_time_ms
671 .unwrap_or(defaults.double_click_time_ms),
672 auto_revert_poll_interval_ms: self
673 .auto_revert_poll_interval_ms
674 .unwrap_or(defaults.auto_revert_poll_interval_ms),
675 read_concurrency: self.read_concurrency.unwrap_or(defaults.read_concurrency),
676 file_tree_poll_interval_ms: self
677 .file_tree_poll_interval_ms
678 .unwrap_or(defaults.file_tree_poll_interval_ms),
679 default_line_ending: self
680 .default_line_ending
681 .unwrap_or(defaults.default_line_ending.clone()),
682 trim_trailing_whitespace_on_save: self
683 .trim_trailing_whitespace_on_save
684 .unwrap_or(defaults.trim_trailing_whitespace_on_save),
685 ensure_final_newline_on_save: self
686 .ensure_final_newline_on_save
687 .unwrap_or(defaults.ensure_final_newline_on_save),
688 highlight_matching_brackets: self
689 .highlight_matching_brackets
690 .unwrap_or(defaults.highlight_matching_brackets),
691 rainbow_brackets: self.rainbow_brackets.unwrap_or(defaults.rainbow_brackets),
692 cursor_style: self.cursor_style.unwrap_or(defaults.cursor_style),
693 keyboard_disambiguate_escape_codes: self
694 .keyboard_disambiguate_escape_codes
695 .unwrap_or(defaults.keyboard_disambiguate_escape_codes),
696 keyboard_report_event_types: self
697 .keyboard_report_event_types
698 .unwrap_or(defaults.keyboard_report_event_types),
699 keyboard_report_alternate_keys: self
700 .keyboard_report_alternate_keys
701 .unwrap_or(defaults.keyboard_report_alternate_keys),
702 keyboard_report_all_keys_as_escape_codes: self
703 .keyboard_report_all_keys_as_escape_codes
704 .unwrap_or(defaults.keyboard_report_all_keys_as_escape_codes),
705 completion_popup_auto_show: self
706 .completion_popup_auto_show
707 .unwrap_or(defaults.completion_popup_auto_show),
708 quick_suggestions: self.quick_suggestions.unwrap_or(defaults.quick_suggestions),
709 quick_suggestions_delay_ms: self
710 .quick_suggestions_delay_ms
711 .unwrap_or(defaults.quick_suggestions_delay_ms),
712 suggest_on_trigger_characters: self
713 .suggest_on_trigger_characters
714 .unwrap_or(defaults.suggest_on_trigger_characters),
715 show_menu_bar: self.show_menu_bar.unwrap_or(defaults.show_menu_bar),
716 menu_bar_mnemonics: self
717 .menu_bar_mnemonics
718 .unwrap_or(defaults.menu_bar_mnemonics),
719 show_tab_bar: self.show_tab_bar.unwrap_or(defaults.show_tab_bar),
720 show_status_bar: self.show_status_bar.unwrap_or(defaults.show_status_bar),
721 status_bar: self
722 .status_bar
723 .unwrap_or_else(|| defaults.status_bar.clone()),
724 show_prompt_line: self.show_prompt_line.unwrap_or(defaults.show_prompt_line),
725 show_vertical_scrollbar: self
726 .show_vertical_scrollbar
727 .unwrap_or(defaults.show_vertical_scrollbar),
728 show_horizontal_scrollbar: self
729 .show_horizontal_scrollbar
730 .unwrap_or(defaults.show_horizontal_scrollbar),
731 show_tilde: self.show_tilde.unwrap_or(defaults.show_tilde),
732 use_terminal_bg: self.use_terminal_bg.unwrap_or(defaults.use_terminal_bg),
733 set_window_title: self.set_window_title.unwrap_or(defaults.set_window_title),
734 rulers: self.rulers.unwrap_or_else(|| defaults.rulers.clone()),
735 whitespace_show: self.whitespace_show.unwrap_or(defaults.whitespace_show),
736 whitespace_spaces_leading: self
737 .whitespace_spaces_leading
738 .unwrap_or(defaults.whitespace_spaces_leading),
739 whitespace_spaces_inner: self
740 .whitespace_spaces_inner
741 .unwrap_or(defaults.whitespace_spaces_inner),
742 whitespace_spaces_trailing: self
743 .whitespace_spaces_trailing
744 .unwrap_or(defaults.whitespace_spaces_trailing),
745 whitespace_tabs_leading: self
746 .whitespace_tabs_leading
747 .unwrap_or(defaults.whitespace_tabs_leading),
748 whitespace_tabs_inner: self
749 .whitespace_tabs_inner
750 .unwrap_or(defaults.whitespace_tabs_inner),
751 whitespace_tabs_trailing: self
752 .whitespace_tabs_trailing
753 .unwrap_or(defaults.whitespace_tabs_trailing),
754 }
755 }
756}
757
758impl From<&FileExplorerConfig> for PartialFileExplorerConfig {
759 fn from(cfg: &FileExplorerConfig) -> Self {
760 Self {
761 respect_gitignore: Some(cfg.respect_gitignore),
762 show_hidden: Some(cfg.show_hidden),
763 show_gitignored: Some(cfg.show_gitignored),
764 custom_ignore_patterns: Some(cfg.custom_ignore_patterns.clone()),
765 width: Some(cfg.width),
766 preview_tabs: Some(cfg.preview_tabs),
767 side: Some(cfg.side),
768 auto_open_on_last_buffer_close: Some(cfg.auto_open_on_last_buffer_close),
769 }
770 }
771}
772
773impl PartialFileExplorerConfig {
774 pub fn resolve(self, defaults: &FileExplorerConfig) -> FileExplorerConfig {
775 FileExplorerConfig {
776 respect_gitignore: self.respect_gitignore.unwrap_or(defaults.respect_gitignore),
777 show_hidden: self.show_hidden.unwrap_or(defaults.show_hidden),
778 show_gitignored: self.show_gitignored.unwrap_or(defaults.show_gitignored),
779 custom_ignore_patterns: self
780 .custom_ignore_patterns
781 .unwrap_or_else(|| defaults.custom_ignore_patterns.clone()),
782 width: self.width.unwrap_or(defaults.width),
783 preview_tabs: self.preview_tabs.unwrap_or(defaults.preview_tabs),
784 side: self.side.unwrap_or(defaults.side),
785 auto_open_on_last_buffer_close: self
786 .auto_open_on_last_buffer_close
787 .unwrap_or(defaults.auto_open_on_last_buffer_close),
788 }
789 }
790}
791
792impl From<&FileBrowserConfig> for PartialFileBrowserConfig {
793 fn from(cfg: &FileBrowserConfig) -> Self {
794 Self {
795 show_hidden: Some(cfg.show_hidden),
796 }
797 }
798}
799
800impl PartialFileBrowserConfig {
801 pub fn resolve(self, defaults: &FileBrowserConfig) -> FileBrowserConfig {
802 FileBrowserConfig {
803 show_hidden: self.show_hidden.unwrap_or(defaults.show_hidden),
804 }
805 }
806}
807
808impl From<&ClipboardConfig> for PartialClipboardConfig {
809 fn from(cfg: &ClipboardConfig) -> Self {
810 Self {
811 use_osc52: Some(cfg.use_osc52),
812 use_system_clipboard: Some(cfg.use_system_clipboard),
813 }
814 }
815}
816
817impl PartialClipboardConfig {
818 pub fn resolve(self, defaults: &ClipboardConfig) -> ClipboardConfig {
819 ClipboardConfig {
820 use_osc52: self.use_osc52.unwrap_or(defaults.use_osc52),
821 use_system_clipboard: self
822 .use_system_clipboard
823 .unwrap_or(defaults.use_system_clipboard),
824 }
825 }
826}
827
828impl From<&TerminalConfig> for PartialTerminalConfig {
829 fn from(cfg: &TerminalConfig) -> Self {
830 Self {
831 jump_to_end_on_output: Some(cfg.jump_to_end_on_output),
832 shell: cfg.shell.clone(),
833 }
834 }
835}
836
837impl PartialTerminalConfig {
838 pub fn resolve(self, defaults: &TerminalConfig) -> TerminalConfig {
839 TerminalConfig {
840 jump_to_end_on_output: self
841 .jump_to_end_on_output
842 .unwrap_or(defaults.jump_to_end_on_output),
843 shell: self.shell.or_else(|| defaults.shell.clone()),
844 }
845 }
846}
847
848impl From<&WarningsConfig> for PartialWarningsConfig {
849 fn from(cfg: &WarningsConfig) -> Self {
850 Self {
851 show_status_indicator: Some(cfg.show_status_indicator),
852 }
853 }
854}
855
856impl PartialWarningsConfig {
857 pub fn resolve(self, defaults: &WarningsConfig) -> WarningsConfig {
858 WarningsConfig {
859 show_status_indicator: self
860 .show_status_indicator
861 .unwrap_or(defaults.show_status_indicator),
862 }
863 }
864}
865
866impl From<&crate::config::PackagesConfig> for PartialPackagesConfig {
867 fn from(cfg: &crate::config::PackagesConfig) -> Self {
868 Self {
869 sources: Some(cfg.sources.clone()),
870 }
871 }
872}
873
874impl PartialPackagesConfig {
875 pub fn resolve(
876 self,
877 defaults: &crate::config::PackagesConfig,
878 ) -> crate::config::PackagesConfig {
879 crate::config::PackagesConfig {
880 sources: self.sources.unwrap_or_else(|| defaults.sources.clone()),
881 }
882 }
883}
884
885impl From<&PluginConfig> for PartialPluginConfig {
886 fn from(cfg: &PluginConfig) -> Self {
887 Self {
888 enabled: Some(cfg.enabled),
889 path: cfg.path.clone(),
890 }
891 }
892}
893
894impl PartialPluginConfig {
895 pub fn resolve(self, defaults: &PluginConfig) -> PluginConfig {
896 PluginConfig {
897 enabled: self.enabled.unwrap_or(defaults.enabled),
898 path: self.path.or_else(|| defaults.path.clone()),
899 }
900 }
901}
902
903impl From<&LanguageConfig> for PartialLanguageConfig {
904 fn from(cfg: &LanguageConfig) -> Self {
905 Self {
906 extensions: Some(cfg.extensions.clone()),
907 filenames: Some(cfg.filenames.clone()),
908 grammar: Some(cfg.grammar.clone()),
909 comment_prefix: cfg.comment_prefix.clone(),
910 auto_indent: Some(cfg.auto_indent),
911 auto_close: cfg.auto_close,
912 auto_surround: cfg.auto_surround,
913 textmate_grammar: cfg.textmate_grammar.clone(),
914 show_whitespace_tabs: Some(cfg.show_whitespace_tabs),
915 line_wrap: cfg.line_wrap,
916 wrap_column: Some(cfg.wrap_column),
917 page_view: cfg.page_view,
918 page_width: Some(cfg.page_width),
919 use_tabs: cfg.use_tabs,
920 tab_size: cfg.tab_size,
921 formatter: cfg.formatter.clone(),
922 format_on_save: Some(cfg.format_on_save),
923 on_save: Some(cfg.on_save.clone()),
924 word_characters: Some(cfg.word_characters.clone()),
925 }
926 }
927}
928
929impl PartialLanguageConfig {
930 pub fn resolve(self, defaults: &LanguageConfig) -> LanguageConfig {
931 LanguageConfig {
932 extensions: self
933 .extensions
934 .unwrap_or_else(|| defaults.extensions.clone()),
935 filenames: self.filenames.unwrap_or_else(|| defaults.filenames.clone()),
936 grammar: self.grammar.unwrap_or_else(|| defaults.grammar.clone()),
937 comment_prefix: self
938 .comment_prefix
939 .or_else(|| defaults.comment_prefix.clone()),
940 auto_indent: self.auto_indent.unwrap_or(defaults.auto_indent),
941 auto_close: self.auto_close.or(defaults.auto_close),
942 auto_surround: self.auto_surround.or(defaults.auto_surround),
943 textmate_grammar: self
944 .textmate_grammar
945 .or_else(|| defaults.textmate_grammar.clone()),
946 show_whitespace_tabs: self
947 .show_whitespace_tabs
948 .unwrap_or(defaults.show_whitespace_tabs),
949 line_wrap: self.line_wrap.or(defaults.line_wrap),
950 wrap_column: self.wrap_column.unwrap_or(defaults.wrap_column),
951 page_view: self.page_view.or(defaults.page_view),
952 page_width: self.page_width.unwrap_or(defaults.page_width),
953 use_tabs: self.use_tabs.or(defaults.use_tabs),
954 tab_size: self.tab_size.or(defaults.tab_size),
955 formatter: self.formatter.or_else(|| defaults.formatter.clone()),
956 format_on_save: self.format_on_save.unwrap_or(defaults.format_on_save),
957 on_save: self.on_save.unwrap_or_else(|| defaults.on_save.clone()),
958 word_characters: self
959 .word_characters
960 .unwrap_or_else(|| defaults.word_characters.clone()),
961 }
962 }
963}
964
965impl From<&crate::config::Config> for PartialConfig {
966 fn from(cfg: &crate::config::Config) -> Self {
967 Self {
968 version: Some(cfg.version),
969 theme: Some(cfg.theme.clone()),
970 locale: cfg.locale.0.clone(),
971 check_for_updates: Some(cfg.check_for_updates),
972 editor: Some(PartialEditorConfig::from(&cfg.editor)),
973 file_explorer: Some(PartialFileExplorerConfig::from(&cfg.file_explorer)),
974 file_browser: Some(PartialFileBrowserConfig::from(&cfg.file_browser)),
975 clipboard: Some(PartialClipboardConfig::from(&cfg.clipboard)),
976 terminal: Some(PartialTerminalConfig::from(&cfg.terminal)),
977 keybindings: Some(cfg.keybindings.clone()),
978 keybinding_maps: Some(cfg.keybinding_maps.clone()),
979 active_keybinding_map: Some(cfg.active_keybinding_map.clone()),
980 languages: Some(
981 cfg.languages
982 .iter()
983 .map(|(k, v)| (k.clone(), PartialLanguageConfig::from(v)))
984 .collect(),
985 ),
986 default_language: cfg.default_language.clone(),
987 lsp: Some(
988 cfg.lsp
989 .iter()
990 .map(|(k, v)| {
991 let lang_config = match v {
995 LspLanguageConfig::Multi(vec) if vec.len() == 1 => {
996 LspLanguageConfig::Single(Box::new(vec[0].clone()))
997 }
998 other => other.clone(),
999 };
1000 (k.clone(), lang_config)
1001 })
1002 .collect(),
1003 ),
1004 universal_lsp: Some(
1005 cfg.universal_lsp
1006 .iter()
1007 .map(|(k, v)| {
1008 let lang_config = match v {
1009 LspLanguageConfig::Multi(vec) if vec.len() == 1 => {
1010 LspLanguageConfig::Single(Box::new(vec[0].clone()))
1011 }
1012 other => other.clone(),
1013 };
1014 (k.clone(), lang_config)
1015 })
1016 .collect(),
1017 ),
1018 warnings: Some(PartialWarningsConfig::from(&cfg.warnings)),
1019 plugins: {
1022 let default_plugin = crate::config::PluginConfig::default();
1023 let non_default_plugins: HashMap<String, PartialPluginConfig> = cfg
1024 .plugins
1025 .iter()
1026 .filter(|(_, v)| v.enabled != default_plugin.enabled)
1027 .map(|(k, v)| {
1028 (
1029 k.clone(),
1030 PartialPluginConfig {
1031 enabled: Some(v.enabled),
1032 path: None, },
1034 )
1035 })
1036 .collect();
1037 if non_default_plugins.is_empty() {
1038 None
1039 } else {
1040 Some(non_default_plugins)
1041 }
1042 },
1043 packages: Some(PartialPackagesConfig::from(&cfg.packages)),
1044 }
1045 }
1046}
1047
1048impl PartialConfig {
1049 pub fn resolve(self) -> crate::config::Config {
1051 let defaults = crate::config::Config::default();
1052 self.resolve_with_defaults(&defaults)
1053 }
1054
1055 pub fn resolve_with_defaults(self, defaults: &crate::config::Config) -> crate::config::Config {
1057 let languages = {
1059 let mut result = defaults.languages.clone();
1060 if let Some(partial_langs) = self.languages {
1061 for (key, partial_lang) in partial_langs {
1062 let default_lang = result.get(&key).cloned().unwrap_or_default();
1063 result.insert(key, partial_lang.resolve(&default_lang));
1064 }
1065 }
1066 result
1067 };
1068
1069 let lsp = {
1073 let mut result = defaults.lsp.clone();
1074 if let Some(partial_lsp) = self.lsp {
1075 for (key, lang_config) in partial_lsp {
1076 let user_configs = lang_config.into_vec();
1077 if let Some(default_configs) = result.get(&key) {
1078 let default_slice = default_configs.as_slice();
1079 if user_configs.len() == 1 && default_slice.len() == 1 {
1083 let merged = user_configs
1084 .into_iter()
1085 .next()
1086 .unwrap()
1087 .merge_with_defaults(&default_slice[0]);
1088 result.insert(key, LspLanguageConfig::Multi(vec![merged]));
1089 } else {
1090 result.insert(key, LspLanguageConfig::Multi(user_configs));
1091 }
1092 } else {
1093 result.insert(key, LspLanguageConfig::Multi(user_configs));
1095 }
1096 }
1097 }
1098 result
1099 };
1100
1101 let universal_lsp = {
1103 let mut result = defaults.universal_lsp.clone();
1104 if let Some(partial_universal_lsp) = self.universal_lsp {
1105 for (key, lang_config) in partial_universal_lsp {
1106 let user_configs = lang_config.into_vec();
1107 if let Some(default_configs) = result.get(&key) {
1108 let default_slice = default_configs.as_slice();
1109 if user_configs.len() == 1 && default_slice.len() == 1 {
1110 let merged = user_configs
1111 .into_iter()
1112 .next()
1113 .unwrap()
1114 .merge_with_defaults(&default_slice[0]);
1115 result.insert(key, LspLanguageConfig::Multi(vec![merged]));
1116 } else {
1117 result.insert(key, LspLanguageConfig::Multi(user_configs));
1118 }
1119 } else {
1120 result.insert(key, LspLanguageConfig::Multi(user_configs));
1121 }
1122 }
1123 }
1124 result
1125 };
1126
1127 let keybinding_maps = {
1129 let mut result = defaults.keybinding_maps.clone();
1130 if let Some(partial_maps) = self.keybinding_maps {
1131 for (key, config) in partial_maps {
1132 result.insert(key, config);
1133 }
1134 }
1135 result
1136 };
1137
1138 let plugins = {
1140 let mut result = defaults.plugins.clone();
1141 if let Some(partial_plugins) = self.plugins {
1142 for (key, partial_plugin) in partial_plugins {
1143 let default_plugin = result.get(&key).cloned().unwrap_or_default();
1144 result.insert(key, partial_plugin.resolve(&default_plugin));
1145 }
1146 }
1147 result
1148 };
1149
1150 crate::config::Config {
1151 version: self.version.unwrap_or(defaults.version),
1152 theme: self.theme.unwrap_or_else(|| defaults.theme.clone()),
1153 locale: crate::config::LocaleName::from(
1154 self.locale.or_else(|| defaults.locale.0.clone()),
1155 ),
1156 check_for_updates: self.check_for_updates.unwrap_or(defaults.check_for_updates),
1157 editor: self
1158 .editor
1159 .map(|e| e.resolve(&defaults.editor))
1160 .unwrap_or_else(|| defaults.editor.clone()),
1161 file_explorer: self
1162 .file_explorer
1163 .map(|e| e.resolve(&defaults.file_explorer))
1164 .unwrap_or_else(|| defaults.file_explorer.clone()),
1165 file_browser: self
1166 .file_browser
1167 .map(|e| e.resolve(&defaults.file_browser))
1168 .unwrap_or_else(|| defaults.file_browser.clone()),
1169 clipboard: self
1170 .clipboard
1171 .map(|e| e.resolve(&defaults.clipboard))
1172 .unwrap_or_else(|| defaults.clipboard.clone()),
1173 terminal: self
1174 .terminal
1175 .map(|e| e.resolve(&defaults.terminal))
1176 .unwrap_or_else(|| defaults.terminal.clone()),
1177 keybindings: self
1178 .keybindings
1179 .unwrap_or_else(|| defaults.keybindings.clone()),
1180 keybinding_maps,
1181 active_keybinding_map: self
1182 .active_keybinding_map
1183 .unwrap_or_else(|| defaults.active_keybinding_map.clone()),
1184 languages,
1185 default_language: self
1186 .default_language
1187 .or_else(|| defaults.default_language.clone()),
1188 lsp,
1189 universal_lsp,
1190 warnings: self
1191 .warnings
1192 .map(|e| e.resolve(&defaults.warnings))
1193 .unwrap_or_else(|| defaults.warnings.clone()),
1194 plugins,
1195 packages: self
1196 .packages
1197 .map(|e| e.resolve(&defaults.packages))
1198 .unwrap_or_else(|| defaults.packages.clone()),
1199 }
1200 }
1201}
1202
1203impl Default for LanguageConfig {
1205 fn default() -> Self {
1206 Self {
1207 extensions: Vec::new(),
1208 filenames: Vec::new(),
1209 grammar: String::new(),
1210 comment_prefix: None,
1211 auto_indent: true,
1212 auto_close: None,
1213 auto_surround: None,
1214 textmate_grammar: None,
1215 show_whitespace_tabs: true,
1216 line_wrap: None,
1217 wrap_column: None,
1218 page_view: None,
1219 page_width: None,
1220 use_tabs: None,
1221 tab_size: None,
1222 formatter: None,
1223 format_on_save: false,
1224 on_save: Vec::new(),
1225 word_characters: None,
1226 }
1227 }
1228}
1229
1230#[derive(Debug, Clone, Default, Deserialize, Serialize)]
1238#[serde(default)]
1239pub struct SessionConfig {
1240 pub theme: Option<ThemeName>,
1242
1243 pub editor: Option<PartialEditorConfig>,
1245
1246 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
1249 pub buffer_overrides: HashMap<std::path::PathBuf, PartialEditorConfig>,
1250}
1251
1252impl SessionConfig {
1253 pub fn new() -> Self {
1255 Self::default()
1256 }
1257
1258 pub fn set_theme(&mut self, theme: ThemeName) {
1260 self.theme = Some(theme);
1261 }
1262
1263 pub fn clear_theme(&mut self) {
1265 self.theme = None;
1266 }
1267
1268 pub fn set_editor_option<F>(&mut self, setter: F)
1270 where
1271 F: FnOnce(&mut PartialEditorConfig),
1272 {
1273 let editor = self.editor.get_or_insert_with(Default::default);
1274 setter(editor);
1275 }
1276
1277 pub fn set_buffer_override(&mut self, path: std::path::PathBuf, config: PartialEditorConfig) {
1279 self.buffer_overrides.insert(path, config);
1280 }
1281
1282 pub fn clear_buffer_override(&mut self, path: &std::path::Path) {
1284 self.buffer_overrides.remove(path);
1285 }
1286
1287 pub fn get_buffer_override(&self, path: &std::path::Path) -> Option<&PartialEditorConfig> {
1289 self.buffer_overrides.get(path)
1290 }
1291
1292 pub fn to_partial_config(&self) -> PartialConfig {
1294 PartialConfig {
1295 theme: self.theme.clone(),
1296 editor: self.editor.clone(),
1297 ..Default::default()
1298 }
1299 }
1300
1301 pub fn is_empty(&self) -> bool {
1303 self.theme.is_none() && self.editor.is_none() && self.buffer_overrides.is_empty()
1304 }
1305}
1306
1307impl From<PartialConfig> for SessionConfig {
1308 fn from(partial: PartialConfig) -> Self {
1309 Self {
1310 theme: partial.theme,
1311 editor: partial.editor,
1312 buffer_overrides: HashMap::new(),
1313 }
1314 }
1315}
1316
1317#[cfg(test)]
1318mod tests {
1319 use super::*;
1320
1321 #[test]
1322 fn merge_option_higher_precedence_wins() {
1323 let mut higher: Option<i32> = Some(10);
1324 let lower: Option<i32> = Some(5);
1325 higher.merge_from(&lower);
1326 assert_eq!(higher, Some(10));
1327 }
1328
1329 #[test]
1330 fn merge_option_fills_from_lower_when_none() {
1331 let mut higher: Option<i32> = None;
1332 let lower: Option<i32> = Some(5);
1333 higher.merge_from(&lower);
1334 assert_eq!(higher, Some(5));
1335 }
1336
1337 #[test]
1338 fn merge_editor_config_recursive() {
1339 let mut higher = PartialEditorConfig {
1340 tab_size: Some(2),
1341 ..Default::default()
1342 };
1343 let lower = PartialEditorConfig {
1344 tab_size: Some(4),
1345 line_numbers: Some(true),
1346 ..Default::default()
1347 };
1348
1349 higher.merge_from(&lower);
1350
1351 assert_eq!(higher.tab_size, Some(2)); assert_eq!(higher.line_numbers, Some(true)); }
1354
1355 #[test]
1356 fn merge_partial_config_combines_languages() {
1357 let mut higher = PartialConfig {
1358 languages: Some(HashMap::from([(
1359 "rust".to_string(),
1360 PartialLanguageConfig {
1361 tab_size: Some(4),
1362 ..Default::default()
1363 },
1364 )])),
1365 ..Default::default()
1366 };
1367 let lower = PartialConfig {
1368 languages: Some(HashMap::from([(
1369 "python".to_string(),
1370 PartialLanguageConfig {
1371 tab_size: Some(4),
1372 ..Default::default()
1373 },
1374 )])),
1375 ..Default::default()
1376 };
1377
1378 higher.merge_from(&lower);
1379
1380 let langs = higher.languages.unwrap();
1381 assert!(langs.contains_key("rust"));
1382 assert!(langs.contains_key("python"));
1383 }
1384
1385 #[test]
1386 fn merge_languages_same_key_higher_wins() {
1387 let mut higher = PartialConfig {
1388 languages: Some(HashMap::from([(
1389 "rust".to_string(),
1390 PartialLanguageConfig {
1391 tab_size: Some(2),
1392 use_tabs: Some(true),
1393 ..Default::default()
1394 },
1395 )])),
1396 ..Default::default()
1397 };
1398 let lower = PartialConfig {
1399 languages: Some(HashMap::from([(
1400 "rust".to_string(),
1401 PartialLanguageConfig {
1402 tab_size: Some(4),
1403 auto_indent: Some(false),
1404 ..Default::default()
1405 },
1406 )])),
1407 ..Default::default()
1408 };
1409
1410 higher.merge_from(&lower);
1411
1412 let langs = higher.languages.unwrap();
1413 let rust = langs.get("rust").unwrap();
1414 assert_eq!(rust.tab_size, Some(2)); assert_eq!(rust.use_tabs, Some(true)); assert_eq!(rust.auto_indent, Some(false)); }
1418
1419 #[test]
1420 fn resolve_fills_defaults() {
1421 let partial = PartialConfig {
1422 theme: Some(ThemeName::from("dark")),
1423 ..Default::default()
1424 };
1425
1426 let resolved = partial.resolve();
1427
1428 assert_eq!(resolved.theme.0, "dark");
1429 assert_eq!(resolved.editor.tab_size, 4); assert!(resolved.editor.line_numbers); }
1432
1433 #[test]
1434 fn resolve_preserves_set_values() {
1435 let partial = PartialConfig {
1436 editor: Some(PartialEditorConfig {
1437 tab_size: Some(2),
1438 line_numbers: Some(false),
1439 ..Default::default()
1440 }),
1441 ..Default::default()
1442 };
1443
1444 let resolved = partial.resolve();
1445
1446 assert_eq!(resolved.editor.tab_size, 2);
1447 assert!(!resolved.editor.line_numbers);
1448 }
1449
1450 #[test]
1451 fn roundtrip_config_to_partial_and_back() {
1452 let original = crate::config::Config::default();
1453 let partial = PartialConfig::from(&original);
1454 let resolved = partial.resolve();
1455
1456 assert_eq!(original.theme, resolved.theme);
1457 assert_eq!(original.editor.tab_size, resolved.editor.tab_size);
1458 assert_eq!(original.check_for_updates, resolved.check_for_updates);
1459 }
1460
1461 #[test]
1462 fn session_config_new_is_empty() {
1463 let session = SessionConfig::new();
1464 assert!(session.is_empty());
1465 }
1466
1467 #[test]
1468 fn session_config_set_theme() {
1469 let mut session = SessionConfig::new();
1470 session.set_theme(ThemeName::from("dark"));
1471 assert_eq!(session.theme, Some(ThemeName::from("dark")));
1472 assert!(!session.is_empty());
1473 }
1474
1475 #[test]
1476 fn session_config_clear_theme() {
1477 let mut session = SessionConfig::new();
1478 session.set_theme(ThemeName::from("dark"));
1479 session.clear_theme();
1480 assert!(session.theme.is_none());
1481 }
1482
1483 #[test]
1484 fn session_config_set_editor_option() {
1485 let mut session = SessionConfig::new();
1486 session.set_editor_option(|e| e.tab_size = Some(2));
1487 assert_eq!(session.editor.as_ref().unwrap().tab_size, Some(2));
1488 }
1489
1490 #[test]
1491 fn session_config_buffer_overrides() {
1492 let mut session = SessionConfig::new();
1493 let path = std::path::PathBuf::from("/test/file.rs");
1494 let config = PartialEditorConfig {
1495 tab_size: Some(8),
1496 ..Default::default()
1497 };
1498
1499 session.set_buffer_override(path.clone(), config);
1500 assert!(session.get_buffer_override(&path).is_some());
1501 assert_eq!(
1502 session.get_buffer_override(&path).unwrap().tab_size,
1503 Some(8)
1504 );
1505
1506 session.clear_buffer_override(&path);
1507 assert!(session.get_buffer_override(&path).is_none());
1508 }
1509
1510 #[test]
1511 fn session_config_to_partial_config() {
1512 let mut session = SessionConfig::new();
1513 session.set_theme(ThemeName::from("dark"));
1514 session.set_editor_option(|e| e.tab_size = Some(2));
1515
1516 let partial = session.to_partial_config();
1517 assert_eq!(partial.theme, Some(ThemeName::from("dark")));
1518 assert_eq!(partial.editor.as_ref().unwrap().tab_size, Some(2));
1519 }
1520
1521 #[test]
1524 fn plugins_with_default_enabled_not_serialized() {
1525 let mut config = crate::config::Config::default();
1527 config.plugins.insert(
1528 "test_plugin".to_string(),
1529 PluginConfig {
1530 enabled: true, path: Some(std::path::PathBuf::from("/path/to/plugin.ts")),
1532 },
1533 );
1534
1535 let partial = PartialConfig::from(&config);
1536
1537 assert!(
1539 partial.plugins.is_none(),
1540 "Plugins with default enabled=true should not be serialized"
1541 );
1542 }
1543
1544 #[test]
1545 fn plugins_with_disabled_are_serialized() {
1546 let mut config = crate::config::Config::default();
1548 config.plugins.insert(
1549 "enabled_plugin".to_string(),
1550 PluginConfig {
1551 enabled: true,
1552 path: Some(std::path::PathBuf::from("/path/to/enabled.ts")),
1553 },
1554 );
1555 config.plugins.insert(
1556 "disabled_plugin".to_string(),
1557 PluginConfig {
1558 enabled: false, path: Some(std::path::PathBuf::from("/path/to/disabled.ts")),
1560 },
1561 );
1562
1563 let partial = PartialConfig::from(&config);
1564
1565 assert!(partial.plugins.is_some());
1567 let plugins = partial.plugins.unwrap();
1568 assert_eq!(
1569 plugins.len(),
1570 1,
1571 "Only disabled plugins should be serialized"
1572 );
1573 assert!(plugins.contains_key("disabled_plugin"));
1574 assert!(!plugins.contains_key("enabled_plugin"));
1575
1576 let disabled = plugins.get("disabled_plugin").unwrap();
1578 assert_eq!(disabled.enabled, Some(false));
1579 assert!(disabled.path.is_none(), "Path should not be serialized");
1581 }
1582
1583 #[test]
1584 fn plugin_path_never_serialized() {
1585 let mut config = crate::config::Config::default();
1587 config.plugins.insert(
1588 "my_plugin".to_string(),
1589 PluginConfig {
1590 enabled: false,
1591 path: Some(std::path::PathBuf::from("/some/path/plugin.ts")),
1592 },
1593 );
1594
1595 let partial = PartialConfig::from(&config);
1596 let plugins = partial.plugins.unwrap();
1597 let plugin = plugins.get("my_plugin").unwrap();
1598
1599 assert!(
1600 plugin.path.is_none(),
1601 "Path is runtime-discovered and should never be serialized"
1602 );
1603 }
1604
1605 #[test]
1606 fn resolving_partial_with_disabled_plugin_preserves_state() {
1607 let partial = PartialConfig {
1609 plugins: Some(HashMap::from([(
1610 "my_plugin".to_string(),
1611 PartialPluginConfig {
1612 enabled: Some(false),
1613 path: None,
1614 },
1615 )])),
1616 ..Default::default()
1617 };
1618
1619 let resolved = partial.resolve();
1620
1621 let plugin = resolved.plugins.get("my_plugin");
1623 assert!(
1624 plugin.is_some(),
1625 "Disabled plugin should be in resolved config"
1626 );
1627 assert!(
1628 !plugin.unwrap().enabled,
1629 "Plugin should remain disabled after resolve"
1630 );
1631 }
1632
1633 #[test]
1634 fn merge_plugins_preserves_higher_precedence_disabled_state() {
1635 let mut higher = PartialConfig {
1637 plugins: Some(HashMap::from([(
1638 "my_plugin".to_string(),
1639 PartialPluginConfig {
1640 enabled: Some(false), path: None,
1642 },
1643 )])),
1644 ..Default::default()
1645 };
1646
1647 let lower = PartialConfig {
1648 plugins: Some(HashMap::from([(
1649 "my_plugin".to_string(),
1650 PartialPluginConfig {
1651 enabled: Some(true), path: None,
1653 },
1654 )])),
1655 ..Default::default()
1656 };
1657
1658 higher.merge_from(&lower);
1659
1660 let plugins = higher.plugins.unwrap();
1661 let plugin = plugins.get("my_plugin").unwrap();
1662 assert_eq!(
1663 plugin.enabled,
1664 Some(false),
1665 "Higher precedence disabled state should win"
1666 );
1667 }
1668
1669 #[test]
1670 fn roundtrip_disabled_plugin_only_saves_delta() {
1671 let mut config = crate::config::Config::default();
1674 config.plugins.insert(
1675 "plugin_a".to_string(),
1676 PluginConfig {
1677 enabled: true,
1678 path: Some(std::path::PathBuf::from("/a.ts")),
1679 },
1680 );
1681 config.plugins.insert(
1682 "plugin_b".to_string(),
1683 PluginConfig {
1684 enabled: false,
1685 path: Some(std::path::PathBuf::from("/b.ts")),
1686 },
1687 );
1688 config.plugins.insert(
1689 "plugin_c".to_string(),
1690 PluginConfig {
1691 enabled: true,
1692 path: Some(std::path::PathBuf::from("/c.ts")),
1693 },
1694 );
1695
1696 let partial = PartialConfig::from(&config);
1698
1699 let json = serde_json::to_string(&partial).unwrap();
1701
1702 assert!(
1704 json.contains("plugin_b"),
1705 "Disabled plugin should be in serialized JSON"
1706 );
1707 assert!(
1708 !json.contains("plugin_a"),
1709 "Enabled plugin_a should not be in serialized JSON"
1710 );
1711 assert!(
1712 !json.contains("plugin_c"),
1713 "Enabled plugin_c should not be in serialized JSON"
1714 );
1715
1716 let deserialized: PartialConfig = serde_json::from_str(&json).unwrap();
1718
1719 let plugins = deserialized.plugins.unwrap();
1721 assert_eq!(plugins.len(), 1);
1722 assert!(plugins.contains_key("plugin_b"));
1723 assert_eq!(plugins.get("plugin_b").unwrap().enabled, Some(false));
1724 }
1725}