1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8fn default_true() -> bool {
13 true
14}
15
16fn default_global_toggle_key() -> String {
17 "Ctrl+Shift+P".to_string()
18}
19
20fn default_detection_scope() -> String {
21 "all".to_string()
22}
23
24fn default_confidence_threshold() -> f32 {
25 0.6
26}
27
28fn default_max_scan_lines() -> usize {
29 500
30}
31
32fn default_debounce_ms() -> u64 {
33 100
34}
35
36fn default_clipboard_copy() -> String {
37 "rendered".to_string()
38}
39
40fn default_priority() -> i32 {
41 50
42}
43
44fn default_cache_max_entries() -> usize {
45 64
46}
47
48#[derive(Clone, Debug, Serialize, Deserialize)]
54pub struct PrettifierYamlConfig {
55 #[serde(default = "default_true")]
57 pub respect_alternate_screen: bool,
58
59 #[serde(default = "default_global_toggle_key")]
61 pub global_toggle_key: String,
62
63 #[serde(default = "default_true")]
65 pub per_block_toggle: bool,
66
67 #[serde(default)]
69 pub detection: DetectionConfig,
70
71 #[serde(default)]
73 pub clipboard: ClipboardConfig,
74
75 #[serde(default)]
77 pub renderers: RenderersConfig,
78
79 #[serde(default)]
81 pub custom_renderers: Vec<CustomRendererConfig>,
82
83 #[serde(default)]
85 pub claude_code_integration: ClaudeCodeConfig,
86
87 #[serde(default)]
89 pub detection_rules: HashMap<String, FormatDetectionRulesConfig>,
90
91 #[serde(default)]
93 pub cache: CacheConfig,
94}
95
96impl Default for PrettifierYamlConfig {
97 fn default() -> Self {
98 Self {
99 respect_alternate_screen: true,
100 global_toggle_key: default_global_toggle_key(),
101 per_block_toggle: true,
102 detection: DetectionConfig::default(),
103 clipboard: ClipboardConfig::default(),
104 renderers: RenderersConfig::default(),
105 custom_renderers: Vec::new(),
106 claude_code_integration: ClaudeCodeConfig::default(),
107 detection_rules: HashMap::new(),
108 cache: CacheConfig::default(),
109 }
110 }
111}
112
113#[derive(Clone, Debug, Serialize, Deserialize)]
119pub struct DetectionConfig {
120 #[serde(default = "default_detection_scope")]
122 pub scope: String,
123
124 #[serde(default = "default_confidence_threshold")]
126 pub confidence_threshold: f32,
127
128 #[serde(default = "default_max_scan_lines")]
130 pub max_scan_lines: usize,
131
132 #[serde(default = "default_debounce_ms")]
134 pub debounce_ms: u64,
135}
136
137impl Default for DetectionConfig {
138 fn default() -> Self {
139 Self {
140 scope: default_detection_scope(),
141 confidence_threshold: default_confidence_threshold(),
142 max_scan_lines: default_max_scan_lines(),
143 debounce_ms: default_debounce_ms(),
144 }
145 }
146}
147
148#[derive(Clone, Debug, Serialize, Deserialize)]
150pub struct ClipboardConfig {
151 #[serde(default = "default_clipboard_copy")]
153 pub default_copy: String,
154}
155
156impl Default for ClipboardConfig {
157 fn default() -> Self {
158 Self {
159 default_copy: default_clipboard_copy(),
160 }
161 }
162}
163
164#[derive(Clone, Debug, Default, Serialize, Deserialize)]
166pub struct RenderersConfig {
167 #[serde(default)]
168 pub markdown: RendererToggle,
169 #[serde(default)]
170 pub json: RendererToggle,
171 #[serde(default)]
172 pub yaml: RendererToggle,
173 #[serde(default)]
174 pub toml: RendererToggle,
175 #[serde(default)]
176 pub xml: RendererToggle,
177 #[serde(default)]
178 pub csv: RendererToggle,
179 #[serde(default)]
180 pub diff: DiffRendererConfig,
181 #[serde(default)]
182 pub log: RendererToggle,
183 #[serde(default)]
184 pub diagrams: DiagramRendererConfig,
185 #[serde(default)]
186 pub sql_results: RendererToggle,
187 #[serde(default)]
188 pub stack_trace: RendererToggle,
189}
190
191#[derive(Clone, Debug, Serialize, Deserialize)]
193pub struct RendererToggle {
194 #[serde(default = "default_true")]
196 pub enabled: bool,
197
198 #[serde(default = "default_priority")]
200 pub priority: i32,
201}
202
203impl Default for RendererToggle {
204 fn default() -> Self {
205 Self {
206 enabled: true,
207 priority: default_priority(),
208 }
209 }
210}
211
212#[derive(Clone, Debug, Serialize, Deserialize)]
214pub struct DiffRendererConfig {
215 #[serde(default = "default_true")]
216 pub enabled: bool,
217
218 #[serde(default = "default_priority")]
219 pub priority: i32,
220
221 #[serde(default)]
223 pub display_mode: Option<String>,
224}
225
226impl Default for DiffRendererConfig {
227 fn default() -> Self {
228 Self {
229 enabled: true,
230 priority: default_priority(),
231 display_mode: None,
232 }
233 }
234}
235
236#[derive(Clone, Debug, Serialize, Deserialize)]
238pub struct DiagramRendererConfig {
239 #[serde(default = "default_true")]
240 pub enabled: bool,
241
242 #[serde(default = "default_priority")]
243 pub priority: i32,
244
245 #[serde(default)]
249 pub engine: Option<String>,
250
251 #[serde(default)]
253 pub kroki_server: Option<String>,
254}
255
256impl Default for DiagramRendererConfig {
257 fn default() -> Self {
258 Self {
259 enabled: true,
260 priority: default_priority(),
261 engine: None,
262 kroki_server: None,
263 }
264 }
265}
266
267#[derive(Clone, Debug, Serialize, Deserialize)]
269pub struct CustomRendererConfig {
270 pub id: String,
272
273 pub name: String,
275
276 #[serde(default)]
278 pub detect_patterns: Vec<String>,
279
280 #[serde(default)]
282 pub render_command: Option<String>,
283
284 #[serde(default)]
286 pub render_args: Vec<String>,
287
288 #[serde(default = "default_priority")]
290 pub priority: i32,
291}
292
293#[derive(Clone, Debug, Serialize, Deserialize)]
295pub struct ClaudeCodeConfig {
296 #[serde(default = "default_true")]
298 pub auto_detect: bool,
299
300 #[serde(default = "default_true")]
302 pub render_markdown: bool,
303
304 #[serde(default = "default_true")]
306 pub render_diffs: bool,
307
308 #[serde(default = "default_true")]
310 pub auto_render_on_expand: bool,
311
312 #[serde(default = "default_true")]
314 pub show_format_badges: bool,
315}
316
317impl Default for ClaudeCodeConfig {
318 fn default() -> Self {
319 Self {
320 auto_detect: true,
321 render_markdown: true,
322 render_diffs: true,
323 auto_render_on_expand: true,
324 show_format_badges: true,
325 }
326 }
327}
328
329#[derive(Clone, Debug, Serialize, Deserialize, Default)]
331pub struct FormatDetectionRulesConfig {
332 #[serde(default)]
334 pub additional: Vec<UserDetectionRule>,
335
336 #[serde(default)]
338 pub overrides: Vec<RuleOverride>,
339}
340
341#[derive(Clone, Debug, Serialize, Deserialize)]
343pub struct UserDetectionRule {
344 pub id: String,
346
347 pub pattern: String,
349
350 #[serde(default = "default_rule_weight")]
352 pub weight: f32,
353
354 #[serde(default = "default_rule_scope")]
356 pub scope: String,
357
358 #[serde(default = "default_true")]
360 pub enabled: bool,
361
362 #[serde(default)]
364 pub description: String,
365}
366
367fn default_rule_weight() -> f32 {
368 0.3
369}
370
371fn default_rule_scope() -> String {
372 "any_line".to_string()
373}
374
375#[derive(Clone, Debug, Serialize, Deserialize)]
377pub struct RuleOverride {
378 pub id: String,
380
381 #[serde(default)]
383 pub enabled: Option<bool>,
384
385 #[serde(default)]
387 pub weight: Option<f32>,
388}
389
390#[derive(Clone, Debug, Serialize, Deserialize)]
392pub struct CacheConfig {
393 #[serde(default = "default_cache_max_entries")]
395 pub max_entries: usize,
396}
397
398impl Default for CacheConfig {
399 fn default() -> Self {
400 Self {
401 max_entries: default_cache_max_entries(),
402 }
403 }
404}
405
406#[derive(Clone, Debug, Serialize, Deserialize, Default)]
414pub struct PrettifierConfigOverride {
415 #[serde(default, skip_serializing_if = "Option::is_none")]
416 pub respect_alternate_screen: Option<bool>,
417
418 #[serde(default, skip_serializing_if = "Option::is_none")]
419 pub per_block_toggle: Option<bool>,
420
421 #[serde(default, skip_serializing_if = "Option::is_none")]
422 pub detection: Option<DetectionConfigOverride>,
423
424 #[serde(default, skip_serializing_if = "Option::is_none")]
425 pub renderers: Option<RenderersConfigOverride>,
426
427 #[serde(default, skip_serializing_if = "Option::is_none")]
428 pub claude_code_integration: Option<ClaudeCodeConfigOverride>,
429}
430
431#[derive(Clone, Debug, Serialize, Deserialize, Default)]
433pub struct DetectionConfigOverride {
434 #[serde(default, skip_serializing_if = "Option::is_none")]
435 pub scope: Option<String>,
436
437 #[serde(default, skip_serializing_if = "Option::is_none")]
438 pub confidence_threshold: Option<f32>,
439
440 #[serde(default, skip_serializing_if = "Option::is_none")]
441 pub max_scan_lines: Option<usize>,
442
443 #[serde(default, skip_serializing_if = "Option::is_none")]
444 pub debounce_ms: Option<u64>,
445}
446
447#[derive(Clone, Debug, Serialize, Deserialize, Default)]
449pub struct RenderersConfigOverride {
450 #[serde(default, skip_serializing_if = "Option::is_none")]
451 pub markdown: Option<RendererToggleOverride>,
452 #[serde(default, skip_serializing_if = "Option::is_none")]
453 pub json: Option<RendererToggleOverride>,
454 #[serde(default, skip_serializing_if = "Option::is_none")]
455 pub yaml: Option<RendererToggleOverride>,
456 #[serde(default, skip_serializing_if = "Option::is_none")]
457 pub toml: Option<RendererToggleOverride>,
458 #[serde(default, skip_serializing_if = "Option::is_none")]
459 pub xml: Option<RendererToggleOverride>,
460 #[serde(default, skip_serializing_if = "Option::is_none")]
461 pub csv: Option<RendererToggleOverride>,
462 #[serde(default, skip_serializing_if = "Option::is_none")]
463 pub diff: Option<RendererToggleOverride>,
464 #[serde(default, skip_serializing_if = "Option::is_none")]
465 pub log: Option<RendererToggleOverride>,
466 #[serde(default, skip_serializing_if = "Option::is_none")]
467 pub diagrams: Option<RendererToggleOverride>,
468 #[serde(default, skip_serializing_if = "Option::is_none")]
469 pub sql_results: Option<RendererToggleOverride>,
470 #[serde(default, skip_serializing_if = "Option::is_none")]
471 pub stack_trace: Option<RendererToggleOverride>,
472}
473
474#[derive(Clone, Debug, Serialize, Deserialize, Default)]
476pub struct RendererToggleOverride {
477 #[serde(default, skip_serializing_if = "Option::is_none")]
478 pub enabled: Option<bool>,
479 #[serde(default, skip_serializing_if = "Option::is_none")]
480 pub priority: Option<i32>,
481}
482
483#[derive(Clone, Debug, Serialize, Deserialize, Default)]
485pub struct ClaudeCodeConfigOverride {
486 #[serde(default, skip_serializing_if = "Option::is_none")]
487 pub auto_detect: Option<bool>,
488 #[serde(default, skip_serializing_if = "Option::is_none")]
489 pub render_markdown: Option<bool>,
490 #[serde(default, skip_serializing_if = "Option::is_none")]
491 pub render_diffs: Option<bool>,
492 #[serde(default, skip_serializing_if = "Option::is_none")]
493 pub auto_render_on_expand: Option<bool>,
494 #[serde(default, skip_serializing_if = "Option::is_none")]
495 pub show_format_badges: Option<bool>,
496}
497
498#[derive(Clone, Debug)]
504pub struct ResolvedPrettifierConfig {
505 pub enabled: bool,
506 pub respect_alternate_screen: bool,
507 pub global_toggle_key: String,
508 pub per_block_toggle: bool,
509 pub detection: DetectionConfig,
510 pub clipboard: ClipboardConfig,
511 pub renderers: RenderersConfig,
512 pub custom_renderers: Vec<CustomRendererConfig>,
513 pub claude_code_integration: ClaudeCodeConfig,
514 pub detection_rules: HashMap<String, FormatDetectionRulesConfig>,
515 pub cache: CacheConfig,
516}
517
518pub fn resolve_prettifier_config(
525 global_enabled: bool,
526 global_config: &PrettifierYamlConfig,
527 profile_enabled: Option<bool>,
528 profile_config: Option<&PrettifierConfigOverride>,
529) -> ResolvedPrettifierConfig {
530 let enabled = profile_enabled.unwrap_or(global_enabled);
531
532 let (detection, renderers, claude_code_integration, respect_alternate_screen, per_block_toggle) =
533 if let Some(overrides) = profile_config {
534 let detection = merge_detection(&global_config.detection, overrides.detection.as_ref());
535 let renderers = merge_renderers(&global_config.renderers, overrides.renderers.as_ref());
536 let claude = merge_claude_code(
537 &global_config.claude_code_integration,
538 overrides.claude_code_integration.as_ref(),
539 );
540 let respect_alt = overrides
541 .respect_alternate_screen
542 .unwrap_or(global_config.respect_alternate_screen);
543 let per_block = overrides
544 .per_block_toggle
545 .unwrap_or(global_config.per_block_toggle);
546 (detection, renderers, claude, respect_alt, per_block)
547 } else {
548 (
549 global_config.detection.clone(),
550 global_config.renderers.clone(),
551 global_config.claude_code_integration.clone(),
552 global_config.respect_alternate_screen,
553 global_config.per_block_toggle,
554 )
555 };
556
557 ResolvedPrettifierConfig {
558 enabled,
559 respect_alternate_screen,
560 global_toggle_key: global_config.global_toggle_key.clone(),
561 per_block_toggle,
562 detection,
563 clipboard: global_config.clipboard.clone(),
564 renderers,
565 custom_renderers: global_config.custom_renderers.clone(),
566 claude_code_integration,
567 detection_rules: global_config.detection_rules.clone(),
568 cache: global_config.cache.clone(),
569 }
570}
571
572fn merge_detection(
573 global: &DetectionConfig,
574 profile: Option<&DetectionConfigOverride>,
575) -> DetectionConfig {
576 let Some(p) = profile else {
577 return global.clone();
578 };
579 DetectionConfig {
580 scope: p.scope.clone().unwrap_or_else(|| global.scope.clone()),
581 confidence_threshold: p
582 .confidence_threshold
583 .unwrap_or(global.confidence_threshold),
584 max_scan_lines: p.max_scan_lines.unwrap_or(global.max_scan_lines),
585 debounce_ms: p.debounce_ms.unwrap_or(global.debounce_ms),
586 }
587}
588
589fn merge_renderers(
590 global: &RenderersConfig,
591 profile: Option<&RenderersConfigOverride>,
592) -> RenderersConfig {
593 let Some(p) = profile else {
594 return global.clone();
595 };
596
597 RenderersConfig {
598 markdown: merge_toggle(&global.markdown, p.markdown.as_ref()),
599 json: merge_toggle(&global.json, p.json.as_ref()),
600 yaml: merge_toggle(&global.yaml, p.yaml.as_ref()),
601 toml: merge_toggle(&global.toml, p.toml.as_ref()),
602 xml: merge_toggle(&global.xml, p.xml.as_ref()),
603 csv: merge_toggle(&global.csv, p.csv.as_ref()),
604 diff: DiffRendererConfig {
605 enabled: p
606 .diff
607 .as_ref()
608 .and_then(|d| d.enabled)
609 .unwrap_or(global.diff.enabled),
610 priority: p
611 .diff
612 .as_ref()
613 .and_then(|d| d.priority)
614 .unwrap_or(global.diff.priority),
615 display_mode: global.diff.display_mode.clone(),
616 },
617 log: merge_toggle(&global.log, p.log.as_ref()),
618 diagrams: DiagramRendererConfig {
619 enabled: p
620 .diagrams
621 .as_ref()
622 .and_then(|d| d.enabled)
623 .unwrap_or(global.diagrams.enabled),
624 priority: p
625 .diagrams
626 .as_ref()
627 .and_then(|d| d.priority)
628 .unwrap_or(global.diagrams.priority),
629 engine: global.diagrams.engine.clone(),
630 kroki_server: global.diagrams.kroki_server.clone(),
631 },
632 sql_results: merge_toggle(&global.sql_results, p.sql_results.as_ref()),
633 stack_trace: merge_toggle(&global.stack_trace, p.stack_trace.as_ref()),
634 }
635}
636
637fn merge_toggle(
638 global: &RendererToggle,
639 profile: Option<&RendererToggleOverride>,
640) -> RendererToggle {
641 let Some(p) = profile else {
642 return global.clone();
643 };
644 RendererToggle {
645 enabled: p.enabled.unwrap_or(global.enabled),
646 priority: p.priority.unwrap_or(global.priority),
647 }
648}
649
650fn merge_claude_code(
651 global: &ClaudeCodeConfig,
652 profile: Option<&ClaudeCodeConfigOverride>,
653) -> ClaudeCodeConfig {
654 let Some(p) = profile else {
655 return global.clone();
656 };
657 ClaudeCodeConfig {
658 auto_detect: p.auto_detect.unwrap_or(global.auto_detect),
659 render_markdown: p.render_markdown.unwrap_or(global.render_markdown),
660 render_diffs: p.render_diffs.unwrap_or(global.render_diffs),
661 auto_render_on_expand: p
662 .auto_render_on_expand
663 .unwrap_or(global.auto_render_on_expand),
664 show_format_badges: p.show_format_badges.unwrap_or(global.show_format_badges),
665 }
666}
667
668#[cfg(test)]
673mod tests {
674 use super::*;
675
676 #[test]
677 fn test_prettifier_yaml_config_defaults() {
678 let config = PrettifierYamlConfig::default();
679 assert!(config.respect_alternate_screen);
680 assert!(config.per_block_toggle);
681 assert_eq!(config.global_toggle_key, "Ctrl+Shift+P");
682 assert!(config.custom_renderers.is_empty());
683 assert!(config.detection_rules.is_empty());
684 }
685
686 #[test]
687 fn test_detection_config_defaults() {
688 let config = DetectionConfig::default();
689 assert_eq!(config.scope, "all");
690 assert!((config.confidence_threshold - 0.6).abs() < f32::EPSILON);
691 assert_eq!(config.max_scan_lines, 500);
692 assert_eq!(config.debounce_ms, 100);
693 }
694
695 #[test]
696 fn test_renderer_toggle_defaults() {
697 let toggle = RendererToggle::default();
698 assert!(toggle.enabled);
699 assert_eq!(toggle.priority, 50);
700 }
701
702 #[test]
703 fn test_renderers_config_defaults() {
704 let config = RenderersConfig::default();
705 assert!(config.markdown.enabled);
706 assert!(config.json.enabled);
707 assert!(config.diff.enabled);
708 assert!(config.diagrams.enabled);
709 }
710
711 #[test]
712 fn test_clipboard_config_defaults() {
713 let config = ClipboardConfig::default();
714 assert_eq!(config.default_copy, "rendered");
715 }
716
717 #[test]
718 fn test_claude_code_config_defaults() {
719 let config = ClaudeCodeConfig::default();
720 assert!(config.auto_detect);
721 assert!(config.render_markdown);
722 assert!(config.render_diffs);
723 assert!(config.auto_render_on_expand);
724 assert!(config.show_format_badges);
725 }
726
727 #[test]
728 fn test_cache_config_defaults() {
729 let config = CacheConfig::default();
730 assert_eq!(config.max_entries, 64);
731 }
732
733 #[test]
734 fn test_yaml_deserialization_empty() {
735 let yaml = "{}";
736 let config: PrettifierYamlConfig = serde_yaml::from_str(yaml).unwrap();
737 assert!(config.respect_alternate_screen);
738 assert_eq!(config.detection.scope, "all");
739 }
740
741 #[test]
742 fn test_yaml_deserialization_partial() {
743 let yaml = r#"
744detection:
745 scope: "all"
746 confidence_threshold: 0.8
747renderers:
748 markdown:
749 enabled: false
750 json:
751 priority: 100
752"#;
753 let config: PrettifierYamlConfig = serde_yaml::from_str(yaml).unwrap();
754 assert_eq!(config.detection.scope, "all");
755 assert!((config.detection.confidence_threshold - 0.8).abs() < f32::EPSILON);
756 assert!(!config.renderers.markdown.enabled);
757 assert_eq!(config.renderers.json.priority, 100);
758 assert!(config.renderers.yaml.enabled);
760 }
761
762 #[test]
763 fn test_yaml_deserialization_custom_renderers() {
764 let yaml = r#"
765custom_renderers:
766 - id: "protobuf"
767 name: "Protocol Buffers"
768 detect_patterns: ["^message\\s+\\w+"]
769 render_command: "protoc --decode_raw"
770 priority: 30
771"#;
772 let config: PrettifierYamlConfig = serde_yaml::from_str(yaml).unwrap();
773 assert_eq!(config.custom_renderers.len(), 1);
774 assert_eq!(config.custom_renderers[0].id, "protobuf");
775 assert_eq!(config.custom_renderers[0].priority, 30);
776 }
777
778 #[test]
779 fn test_yaml_deserialization_detection_rules() {
780 let yaml = r#"
781detection_rules:
782 markdown:
783 additional:
784 - id: "md_custom_fence"
785 pattern: "^```custom"
786 weight: 0.4
787 scope: "first_lines:5"
788 overrides:
789 - id: "md_atx_header"
790 enabled: false
791"#;
792 let config: PrettifierYamlConfig = serde_yaml::from_str(yaml).unwrap();
793 assert!(config.detection_rules.contains_key("markdown"));
794 let md_rules = &config.detection_rules["markdown"];
795 assert_eq!(md_rules.additional.len(), 1);
796 assert_eq!(md_rules.additional[0].id, "md_custom_fence");
797 assert_eq!(md_rules.overrides.len(), 1);
798 assert!(!md_rules.overrides[0].enabled.unwrap());
799 }
800
801 #[test]
802 fn test_override_struct_defaults() {
803 let override_config = PrettifierConfigOverride::default();
804 assert!(override_config.respect_alternate_screen.is_none());
805 assert!(override_config.per_block_toggle.is_none());
806 assert!(override_config.detection.is_none());
807 assert!(override_config.renderers.is_none());
808 assert!(override_config.claude_code_integration.is_none());
809 }
810
811 #[test]
812 fn test_override_serialization_skips_none() {
813 let override_config = PrettifierConfigOverride::default();
814 let yaml = serde_yaml::to_string(&override_config).unwrap();
815 assert_eq!(yaml.trim(), "{}");
817 }
818
819 #[test]
820 fn test_resolve_no_profile() {
821 let global = PrettifierYamlConfig::default();
822 let resolved = resolve_prettifier_config(true, &global, None, None);
823
824 assert!(resolved.enabled);
825 assert!(resolved.respect_alternate_screen);
826 assert_eq!(resolved.detection.scope, "all");
827 assert!(resolved.renderers.markdown.enabled);
828 }
829
830 #[test]
831 fn test_resolve_profile_overrides_enabled() {
832 let global = PrettifierYamlConfig::default();
833
834 let resolved = resolve_prettifier_config(true, &global, Some(false), None);
836 assert!(!resolved.enabled);
837
838 let resolved = resolve_prettifier_config(false, &global, Some(true), None);
840 assert!(resolved.enabled);
841 }
842
843 #[test]
844 fn test_resolve_profile_overrides_detection() {
845 let global = PrettifierYamlConfig::default();
846 let profile = PrettifierConfigOverride {
847 detection: Some(DetectionConfigOverride {
848 scope: Some("all".to_string()),
849 confidence_threshold: Some(0.9),
850 ..Default::default()
851 }),
852 ..Default::default()
853 };
854
855 let resolved = resolve_prettifier_config(true, &global, None, Some(&profile));
856 assert_eq!(resolved.detection.scope, "all");
857 assert!((resolved.detection.confidence_threshold - 0.9).abs() < f32::EPSILON);
858 assert_eq!(resolved.detection.max_scan_lines, 500);
860 assert_eq!(resolved.detection.debounce_ms, 100);
861 }
862
863 #[test]
864 fn test_resolve_profile_overrides_renderers() {
865 let global = PrettifierYamlConfig::default();
866 let profile = PrettifierConfigOverride {
867 renderers: Some(RenderersConfigOverride {
868 markdown: Some(RendererToggleOverride {
869 enabled: Some(false),
870 ..Default::default()
871 }),
872 json: Some(RendererToggleOverride {
873 priority: Some(100),
874 ..Default::default()
875 }),
876 ..Default::default()
877 }),
878 ..Default::default()
879 };
880
881 let resolved = resolve_prettifier_config(true, &global, None, Some(&profile));
882 assert!(!resolved.renderers.markdown.enabled);
883 assert_eq!(resolved.renderers.json.priority, 100);
884 assert!(resolved.renderers.yaml.enabled);
886 assert!(resolved.renderers.diff.enabled);
887 }
888
889 #[test]
890 fn test_resolve_profile_overrides_claude_code() {
891 let global = PrettifierYamlConfig::default();
892 let profile = PrettifierConfigOverride {
893 claude_code_integration: Some(ClaudeCodeConfigOverride {
894 render_markdown: Some(false),
895 ..Default::default()
896 }),
897 ..Default::default()
898 };
899
900 let resolved = resolve_prettifier_config(true, &global, None, Some(&profile));
901 assert!(!resolved.claude_code_integration.render_markdown);
902 assert!(resolved.claude_code_integration.auto_detect); assert!(resolved.claude_code_integration.render_diffs); }
905
906 #[test]
907 fn test_resolve_inherits_omitted_fields() {
908 let mut global = PrettifierYamlConfig::default();
909 global.respect_alternate_screen = false;
910 global.per_block_toggle = false;
911
912 let profile = PrettifierConfigOverride {
914 respect_alternate_screen: Some(true),
915 ..Default::default()
916 };
917
918 let resolved = resolve_prettifier_config(true, &global, None, Some(&profile));
919 assert!(resolved.respect_alternate_screen); assert!(!resolved.per_block_toggle); }
922}