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