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