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