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