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