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