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