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