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