matchmaker/
config.rs

1//! Config Types.
2//! See `src/bin/mm/config.rs` for an example
3
4use std::{fmt, ops::Deref};
5
6use crate::MAX_SPLITS;
7use crate::{Result, action::Count, tui::IoStream};
8
9use ratatui::style::Style;
10use ratatui::text::Span;
11use ratatui::{
12    style::{Color, Modifier},
13    widgets::{BorderType, Borders, Padding},
14};
15use regex::Regex;
16use serde::de::IntoDeserializer;
17use serde::ser::SerializeSeq;
18use serde::{
19    Deserialize, Deserializer,
20    de::{self, Visitor},
21};
22use serde::{Serialize, Serializer};
23
24pub use crate::utils::{
25    Percentage,
26    serde::{StringOrVec, escaped_opt_char, escaped_opt_string, serde_duration_ms},
27};
28
29/// Settings unrelated to event loop/picker_ui.
30///
31/// Does not deny unknown fields.
32#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub struct MatcherConfig {
34    #[serde(flatten)]
35    pub matcher: NucleoMatcherConfig,
36    #[serde(flatten)]
37    pub worker: WorkerConfig,
38    // only nit is that we would really prefer this config sit at top level since its irrelevant to [`crate::Matchmaker::new_from_config`] but it makes more sense under matcher in the config file
39    #[serde(flatten)]
40    pub start: StartConfig,
41}
42
43/// "Input/output specific". Configures the matchmaker worker.
44///
45/// Does not deny unknown fields.
46#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
47#[serde(default)]
48pub struct WorkerConfig {
49    pub columns: ColumnsConfig,
50    #[serde(flatten)]
51    pub exit: ExitConfig,
52    pub trim: bool,           // todo
53    pub format: FormatString, // todo: implement
54}
55
56/// Configures how input is fed to to the worker(s).
57///
58/// Does not deny unknown fields.
59#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
60#[serde(default)]
61pub struct StartConfig {
62    #[serde(default, deserialize_with = "escaped_opt_char")]
63    pub input_separator: Option<char>,
64    #[serde(default, deserialize_with = "escaped_opt_string")]
65    pub output_separator: Option<String>,
66    pub default_command: String,
67    pub sync: bool,
68}
69
70/// Exit conditions of the render loop.
71///
72/// Does not deny unknown fields.
73#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
74#[serde(default)]
75pub struct ExitConfig {
76    pub select_1: bool,
77    pub allow_empty: bool,
78}
79
80/// The ui config.
81#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
82#[serde(default, deny_unknown_fields)]
83pub struct RenderConfig {
84    pub ui: UiConfig,
85    pub input: InputConfig,
86    pub results: ResultsConfig,
87    pub preview: PreviewConfig,
88    pub footer: DisplayConfig,
89    pub header: DisplayConfig,
90    pub overlay: Option<OverlayConfig>, // pub overlay: OverlayConfig // overlay_config is seperate due to specificity
91}
92
93impl RenderConfig {
94    pub fn tick_rate(&self) -> u8 {
95        self.ui.tick_rate
96    }
97}
98
99/// Terminal settings.
100#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
101#[serde(default, deny_unknown_fields)]
102pub struct TerminalConfig {
103    pub stream: IoStream, // consumed
104    pub restore_fullscreen: bool,
105    pub redraw_on_resize: bool,
106    #[serde(with = "serde_duration_ms")]
107    pub sleep_ms: std::time::Duration, // necessary to give ratatui a small delay before resizing after entering and exiting
108    // todo: lowpri: will need a value which can deserialize to none when implementing cli parsing
109    #[serde(flatten)]
110    pub layout: Option<TerminalLayoutSettings>, // None for fullscreen
111}
112
113impl Default for TerminalConfig {
114    fn default() -> Self {
115        Self {
116            stream: IoStream::default(),
117            restore_fullscreen: true,
118            redraw_on_resize: bool::default(),
119            sleep_ms: std::time::Duration::default(),
120            layout: Option::default(),
121        }
122    }
123}
124#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
125#[serde(default, deny_unknown_fields)]
126pub struct TerminalSettings {}
127
128/// The container ui.
129#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
130#[serde(default, deny_unknown_fields)]
131pub struct UiConfig {
132    pub border: BorderSetting,
133    pub tick_rate: u8, // seperate from render, but best place ig
134}
135
136impl Default for UiConfig {
137    fn default() -> Self {
138        Self {
139            border: Default::default(),
140            tick_rate: 60,
141        }
142    }
143}
144
145/// The input bar ui.
146#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
147#[serde(default, deny_unknown_fields)]
148pub struct InputConfig {
149    pub border: BorderSetting,
150
151    // text styles
152    pub fg: Color,
153    #[serde(
154        deserialize_with = "deserialize_modifier",
155        serialize_with = "serialize_modifier"
156    )]
157    pub modifier: Modifier,
158
159    pub prompt_fg: Color,
160    #[serde(
161        deserialize_with = "deserialize_modifier",
162        serialize_with = "serialize_modifier"
163    )]
164    pub prompt_modifier: Modifier,
165
166    #[serde(deserialize_with = "deserialize_string_or_char_as_double_width")]
167    pub prompt: String,
168    pub cursor: CursorSetting,
169    pub initial: String,
170}
171
172impl Default for InputConfig {
173    fn default() -> Self {
174        Self {
175            border: Default::default(),
176            fg: Default::default(),
177            modifier: Default::default(),
178            prompt_fg: Default::default(),
179            prompt_modifier: Default::default(),
180            prompt: "> ".to_string(),
181            cursor: Default::default(),
182            initial: Default::default(),
183        }
184    }
185}
186
187#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
188#[serde(default, deny_unknown_fields)]
189pub struct OverlayConfig {
190    pub border: BorderSetting,
191    pub outer_dim: bool,
192    pub layout: OverlayLayoutSettings,
193}
194
195#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
196pub struct OverlayLayoutSettings {
197    /// w, h
198    pub percentage: [Percentage; 2],
199    /// w, h
200    pub min: [u16; 2],
201    /// w, h
202    pub max: [u16; 2],
203}
204
205impl Default for OverlayLayoutSettings {
206    fn default() -> Self {
207        Self {
208            percentage: [Percentage::new(60), Percentage::new(30)],
209            min: [10, 5],
210            max: [200, 30],
211        }
212    }
213}
214
215// pub struct OverlaySize
216
217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
218#[serde(default, deny_unknown_fields)]
219pub struct ResultsConfig {
220    pub border: BorderSetting,
221
222    // prefixes
223    #[serde(deserialize_with = "deserialize_string_or_char_as_double_width")]
224    pub multi_prefix: String,
225    pub default_prefix: String,
226
227    // text styles
228    pub fg: Color,
229    #[serde(
230        deserialize_with = "deserialize_modifier",
231        serialize_with = "serialize_modifier"
232    )]
233    pub modifier: Modifier,
234
235    #[serde(default = "default_green")]
236    pub match_fg: Color,
237    #[serde(
238        deserialize_with = "deserialize_modifier",
239        serialize_with = "serialize_modifier"
240    )]
241    pub match_modifier: Modifier,
242
243    pub current_fg: Color,
244    #[serde(default = "default_black")]
245    pub current_bg: Color,
246    #[serde(
247        deserialize_with = "deserialize_modifier",
248        serialize_with = "serialize_modifier",
249        default = "default_bold"
250    )]
251    pub current_modifier: Modifier,
252
253    // status
254    #[serde(default = "default_green")]
255    pub count_fg: Color,
256    #[serde(
257        deserialize_with = "deserialize_modifier",
258        serialize_with = "serialize_modifier",
259        default = "default_italic"
260    )]
261    pub count_modifier: Modifier,
262
263    // todo(?): lowpri
264    // pub selected_fg: Color,
265    // pub selected_bg: Color,
266    // pub selected_modifier: Color,
267
268    // scroll
269    #[serde(default = "default_true")]
270    pub scroll_wrap: bool,
271    pub scroll_padding: u16,
272    #[serde(deserialize_with = "deserialize_option_auto")]
273    pub reverse: Option<bool>,
274
275    // wrap
276    pub wrap: bool,
277    pub wrap_scaling_min_width: u8,
278
279    // experimental
280    pub column_spacing: Count,
281    pub current_prefix: String,
282    pub right_align_last: bool,
283}
284
285impl Default for ResultsConfig {
286    fn default() -> Self {
287        ResultsConfig {
288            border: Default::default(),
289
290            multi_prefix: "▌ ".to_string(),
291            default_prefix: Default::default(),
292
293            fg: Default::default(),
294            modifier: Default::default(), // or whatever your deserialize_modifier default function returns
295            match_fg: default_green(),
296            match_modifier: default_italic(),
297
298            current_fg: Default::default(),
299            current_bg: default_black(),
300            current_modifier: default_bold(),
301
302            count_fg: default_green(),
303            count_modifier: default_italic(),
304
305            scroll_wrap: default_true(),
306            scroll_padding: 2,
307            reverse: None,
308
309            wrap: Default::default(),
310            wrap_scaling_min_width: 5,
311
312            column_spacing: Default::default(),
313            current_prefix: Default::default(),
314            right_align_last: true,
315        }
316    }
317}
318
319#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
320#[serde(default, deny_unknown_fields)]
321pub struct DisplayConfig {
322    pub border: BorderSetting,
323
324    #[serde(default = "default_green")]
325    pub fg: Color,
326    #[serde(
327        deserialize_with = "deserialize_modifier",
328        serialize_with = "serialize_modifier",
329        default = "default_italic"
330    )]
331    pub modifier: Modifier,
332
333    #[serde(default = "default_true")]
334    pub match_indent: bool,
335    pub wrap: bool,
336    #[serde(deserialize_with = "deserialize_option_auto")]
337    pub content: Option<StringOrVec>,
338}
339
340impl Default for DisplayConfig {
341    fn default() -> Self {
342        DisplayConfig {
343            border: Default::default(),
344            match_indent: true,
345            fg: default_green(),
346            wrap: false,
347            modifier: default_italic(), // whatever your `deserialize_modifier` default uses
348            content: None,
349        }
350    }
351}
352
353#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
354#[serde(default, deny_unknown_fields)]
355pub struct PreviewConfig {
356    pub border: BorderSetting,
357
358    pub layout: Vec<PreviewSetting>,
359    #[serde(default = "default_true")]
360    pub scroll_wrap: bool,
361    pub wrap: bool,
362    pub show: bool,
363}
364
365impl Default for PreviewConfig {
366    fn default() -> Self {
367        PreviewConfig {
368            border: Default::default(),
369            // layout: vec![
370            // PreviewSetting {
371            //     layout: PreviewLayoutSetting::default(),
372            //     command: String::new()
373            // }
374            // ],
375            layout: Default::default(),
376            scroll_wrap: default_true(),
377            wrap: Default::default(),
378            show: Default::default(),
379        }
380    }
381}
382
383#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
384#[serde(default, deny_unknown_fields)]
385pub struct PreviewerConfig {
386    pub try_lossy: bool,
387
388    // todo
389    pub cache: u8,
390
391    #[serde(default)]
392    pub help_colors: TomlColorConfig,
393}
394
395/// Help coloring
396#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
397pub struct TomlColorConfig {
398    pub section: Color,
399    pub key: Color,
400    pub string: Color,
401    pub number: Color,
402    pub section_bold: bool,
403}
404
405impl Default for TomlColorConfig {
406    fn default() -> Self {
407        Self {
408            section: Color::Blue,
409            key: Color::Yellow,
410            string: Color::Green,
411            number: Color::Cyan,
412            section_bold: true,
413        }
414    }
415}
416
417// ----------- SETTING TYPES -------------------------
418// Default config file -> write if not exists, then load
419
420#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
421#[serde(transparent)]
422pub struct FormatString(String);
423
424impl Deref for FormatString {
425    type Target = str;
426
427    fn deref(&self) -> &Self::Target {
428        &self.0
429    }
430}
431
432#[derive(Default, Debug, Clone, PartialEq, Serialize)]
433#[serde(default, deny_unknown_fields)]
434pub struct BorderSetting {
435    #[serde(with = "crate::utils::serde::fromstr")]
436    pub r#type: BorderType,
437    pub color: Color,
438    #[serde(
439        serialize_with = "serialize_borders",
440        deserialize_with = "deserialize_borders"
441    )]
442    pub sides: Borders,
443    #[serde(
444        serialize_with = "serialize_padding",
445        deserialize_with = "deserialize_padding"
446    )]
447    pub padding: Padding,
448    pub title: String,
449    #[serde(
450        deserialize_with = "deserialize_modifier",
451        serialize_with = "serialize_modifier"
452    )]
453    pub title_modifier: Modifier,
454    pub bg: Color,
455}
456
457impl BorderSetting {
458    pub fn as_block(&self) -> ratatui::widgets::Block<'_> {
459        let mut ret = ratatui::widgets::Block::default()
460            .padding(self.padding)
461            .style(Style::default().bg(self.bg));
462
463        if !self.title.is_empty() {
464            let title = Span::styled(
465                &self.title,
466                Style::default().add_modifier(self.title_modifier),
467            );
468
469            ret = ret.title(title)
470        };
471
472        if self.sides != Borders::NONE {
473            ret = ret
474                .borders(self.sides)
475                .border_type(self.r#type)
476                .border_style(ratatui::style::Style::default().fg(self.color))
477        }
478
479        ret
480    }
481
482    pub fn as_static_block(&self) -> ratatui::widgets::Block<'static> {
483        let mut ret = ratatui::widgets::Block::default()
484            .padding(self.padding)
485            .style(Style::default().bg(self.bg));
486
487        if !self.title.is_empty() {
488            let title: Span<'static> = Span::styled(
489                self.title.clone(),
490                Style::default().add_modifier(self.title_modifier),
491            );
492
493            ret = ret.title(title)
494        };
495
496        if self.sides != Borders::NONE {
497            ret = ret
498                .borders(self.sides)
499                .border_type(self.r#type)
500                .border_style(ratatui::style::Style::default().fg(self.color))
501        }
502
503        ret
504    }
505
506    pub fn height(&self) -> u16 {
507        let mut height = 0;
508        height += 2 * !self.sides.is_empty() as u16;
509        height += self.padding.top + self.padding.bottom;
510        height += (!self.title.is_empty() as u16).saturating_sub(!self.sides.is_empty() as u16);
511
512        height
513    }
514
515    pub fn width(&self) -> u16 {
516        let mut width = 0;
517        width += 2 * !self.sides.is_empty() as u16;
518        width += self.padding.left + self.padding.right;
519
520        width
521    }
522
523    pub fn left(&self) -> u16 {
524        let mut width = 0;
525        width += !self.sides.is_empty() as u16;
526        width += self.padding.left;
527
528        width
529    }
530
531    pub fn top(&self) -> u16 {
532        let mut height = 0;
533        height += !self.sides.is_empty() as u16;
534        height += self.padding.top;
535        height += (!self.title.is_empty() as u16).saturating_sub(!self.sides.is_empty() as u16);
536
537        height
538    }
539}
540
541// how to determine how many rows to allocate?
542#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
543pub struct TerminalLayoutSettings {
544    pub percentage: Percentage,
545    pub min: u16,
546    pub max: u16, // 0 for terminal height cap
547}
548
549impl Default for TerminalLayoutSettings {
550    fn default() -> Self {
551        Self {
552            percentage: Percentage::new(50),
553            min: 10,
554            max: 120,
555        }
556    }
557}
558
559#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
560#[serde(rename_all = "lowercase")]
561pub enum Side {
562    Top,
563    Bottom,
564    Left,
565    #[default]
566    Right,
567}
568
569#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
570pub struct PreviewSetting {
571    #[serde(flatten)]
572    pub layout: PreviewLayoutSetting,
573    #[serde(default)]
574    pub command: String,
575}
576
577#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
578pub struct PreviewLayoutSetting {
579    pub side: Side,
580    pub percentage: Percentage,
581    pub min: i16,
582    pub max: i16,
583}
584
585impl Default for PreviewLayoutSetting {
586    fn default() -> Self {
587        Self {
588            side: Side::Right,
589            percentage: Percentage::new(60),
590            min: 30,
591            max: 120,
592        }
593    }
594}
595
596#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
597#[serde(rename_all = "lowercase")]
598pub enum CursorSetting {
599    None,
600    #[default]
601    Default,
602}
603
604use crate::utils::serde::bounded_usize;
605// todo: pass filter and hidden to mm
606#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
607#[serde(default, deny_unknown_fields)]
608pub struct ColumnsConfig {
609    pub split: Split,
610    pub names: Vec<ColumnSetting>,
611    #[serde(deserialize_with = "bounded_usize::<{crate::MAX_SPLITS},_>")]
612    max_columns: usize,
613}
614
615impl ColumnsConfig {
616    pub fn max_cols(&self) -> usize {
617        self.max_columns.min(MAX_SPLITS)
618    }
619}
620
621#[derive(Default, Debug, Clone, PartialEq)]
622pub struct ColumnSetting {
623    pub filter: bool,
624    pub hidden: bool,
625    pub name: String,
626}
627
628#[derive(Default, Debug, Clone)]
629pub enum Split {
630    Delimiter(Regex),
631    Regexes(Vec<Regex>),
632    #[default]
633    None,
634}
635
636impl PartialEq for Split {
637    fn eq(&self, other: &Self) -> bool {
638        match (self, other) {
639            (Split::Delimiter(r1), Split::Delimiter(r2)) => r1.as_str() == r2.as_str(),
640            (Split::Regexes(v1), Split::Regexes(v2)) => {
641                if v1.len() != v2.len() {
642                    return false;
643                }
644                v1.iter()
645                    .zip(v2.iter())
646                    .all(|(r1, r2)| r1.as_str() == r2.as_str())
647            }
648            (Split::None, Split::None) => true,
649            _ => false,
650        }
651    }
652}
653
654// --------- Deserialize Helpers ------------
655pub fn serialize_borders<S>(borders: &Borders, serializer: S) -> Result<S::Ok, S::Error>
656where
657    S: serde::Serializer,
658{
659    use serde::ser::SerializeSeq;
660    let mut seq = serializer.serialize_seq(None)?;
661    if borders.contains(Borders::TOP) {
662        seq.serialize_element("top")?;
663    }
664    if borders.contains(Borders::BOTTOM) {
665        seq.serialize_element("bottom")?;
666    }
667    if borders.contains(Borders::LEFT) {
668        seq.serialize_element("left")?;
669    }
670    if borders.contains(Borders::RIGHT) {
671        seq.serialize_element("right")?;
672    }
673    seq.end()
674}
675
676pub fn deserialize_borders<'de, D>(deserializer: D) -> Result<Borders, D::Error>
677where
678    D: Deserializer<'de>,
679{
680    let input = StringOrVec::deserialize(deserializer)?;
681    let mut borders = Borders::NONE;
682
683    let borders = match input {
684        StringOrVec::String(s) => match s.as_str() {
685            "none" => Borders::NONE,
686            "all" => Borders::ALL,
687            other => {
688                return Err(de::Error::custom(format!(
689                    "invalid border value '{}'",
690                    other
691                )));
692            }
693        },
694        StringOrVec::Vec(list) => {
695            for item in list {
696                match item.as_str() {
697                    "top" => borders |= Borders::TOP,
698                    "bottom" => borders |= Borders::BOTTOM,
699                    "left" => borders |= Borders::LEFT,
700                    "right" => borders |= Borders::RIGHT,
701                    "all" => borders |= Borders::ALL,
702                    "none" => borders = Borders::NONE,
703                    other => return Err(de::Error::custom(format!("invalid side '{}'", other))),
704                }
705            }
706            borders
707        }
708    };
709
710    Ok(borders)
711}
712
713pub fn deserialize_borders_option<'de, D>(deserializer: D) -> Result<Option<Borders>, D::Error>
714where
715    D: Deserializer<'de>,
716{
717    let input = Option::<StringOrVec>::deserialize(deserializer)?;
718    match input {
719        Some(input) => {
720            let mut borders = Borders::NONE;
721
722            let borders = match input {
723                StringOrVec::String(s) => match s.as_str() {
724                    "none" => Borders::NONE,
725                    "all" => Borders::ALL,
726                    other => {
727                        return Err(de::Error::custom(format!(
728                            "invalid border value '{}'",
729                            other
730                        )));
731                    }
732                },
733                StringOrVec::Vec(list) => {
734                    for item in list {
735                        match item.as_str() {
736                            "top" => borders |= Borders::TOP,
737                            "bottom" => borders |= Borders::BOTTOM,
738                            "left" => borders |= Borders::LEFT,
739                            "right" => borders |= Borders::RIGHT,
740                            "all" => borders |= Borders::ALL,
741                            "none" => borders = Borders::NONE,
742                            other => {
743                                return Err(de::Error::custom(format!("invalid side '{}'", other)));
744                            }
745                        }
746                    }
747                    borders
748                }
749            };
750
751            Ok(Some(borders))
752        }
753        None => Ok(None),
754    }
755}
756
757pub fn deserialize_modifier<'de, D>(deserializer: D) -> Result<Modifier, D::Error>
758where
759    D: Deserializer<'de>,
760{
761    let input = StringOrVec::deserialize(deserializer)?;
762    let mut modifier = Modifier::empty();
763
764    let add_modifier = |name: &str, m: &mut Modifier| -> Result<(), D::Error> {
765        match name.to_lowercase().as_str() {
766            "bold" => {
767                *m |= Modifier::BOLD;
768                Ok(())
769            }
770            "italic" => {
771                *m |= Modifier::ITALIC;
772                Ok(())
773            }
774            "underlined" => {
775                *m |= Modifier::UNDERLINED;
776                Ok(())
777            }
778            // "slow_blink" => {
779            //     *m |= Modifier::SLOW_BLINK;
780            //     Ok(())
781            // }
782            // "rapid_blink" => {
783            //     *m |= Modifier::RAPID_BLINK;
784            //     Ok(())
785            // }
786            // "reversed" => {
787            //     *m |= Modifier::REVERSED;
788            //     Ok(())
789            // }
790            // "dim" => {
791            //     *m |= Modifier::DIM;
792            //     Ok(())
793            // }
794            // "crossed_out" => {
795            //     *m |= Modifier::CROSSED_OUT;
796            //     Ok(())
797            // }
798            "none" => {
799                *m = Modifier::empty();
800                Ok(())
801            } // reset all modifiers
802            other => Err(de::Error::custom(format!("invalid modifier '{}'", other))),
803        }
804    };
805
806    match input {
807        StringOrVec::String(s) => add_modifier(&s, &mut modifier)?,
808        StringOrVec::Vec(list) => {
809            for item in list {
810                add_modifier(&item, &mut modifier)?;
811            }
812        }
813    }
814
815    Ok(modifier)
816}
817
818pub fn serialize_modifier<S>(modifier: &Modifier, serializer: S) -> Result<S::Ok, S::Error>
819where
820    S: Serializer,
821{
822    let mut mods = Vec::new();
823
824    if modifier.contains(Modifier::BOLD) {
825        mods.push("bold");
826    }
827    if modifier.contains(Modifier::ITALIC) {
828        mods.push("italic");
829    }
830    if modifier.contains(Modifier::UNDERLINED) {
831        mods.push("underlined");
832    }
833    // add other flags if needed
834    // if modifier.contains(Modifier::DIM) { mods.push("dim"); }
835
836    match mods.len() {
837        0 => serializer.serialize_str("none"),
838        1 => serializer.serialize_str(mods[0]),
839        _ => mods.serialize(serializer),
840    }
841}
842
843pub fn deserialize_string_or_char_as_double_width<'de, D, T>(deserializer: D) -> Result<T, D::Error>
844where
845    D: Deserializer<'de>,
846    T: From<String>,
847{
848    struct GenericVisitor<T> {
849        _marker: std::marker::PhantomData<T>,
850    }
851
852    impl<'de, T> Visitor<'de> for GenericVisitor<T>
853    where
854        T: From<String>,
855    {
856        type Value = T;
857
858        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
859            formatter.write_str("a string or single character")
860        }
861
862        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
863        where
864            E: de::Error,
865        {
866            let s = if v.chars().count() == 1 {
867                let mut s = String::with_capacity(2);
868                s.push(v.chars().next().unwrap());
869                s.push(' ');
870                s
871            } else {
872                v.to_string()
873            };
874            Ok(T::from(s))
875        }
876
877        fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
878        where
879            E: de::Error,
880        {
881            self.visit_str(&v)
882        }
883    }
884
885    deserializer.deserialize_string(GenericVisitor {
886        _marker: std::marker::PhantomData,
887    })
888}
889
890// ----------- Nucleo config helper
891#[derive(Debug, Clone, PartialEq)]
892pub struct NucleoMatcherConfig(pub nucleo::Config);
893
894impl Default for NucleoMatcherConfig {
895    fn default() -> Self {
896        Self(nucleo::Config::DEFAULT)
897    }
898}
899
900#[derive(Debug, Clone, Serialize, Deserialize)]
901#[serde(default)]
902#[derive(Default)]
903struct MatcherConfigHelper {
904    pub normalize: Option<bool>,
905    pub ignore_case: Option<bool>,
906    pub prefer_prefix: Option<bool>,
907}
908
909impl serde::Serialize for NucleoMatcherConfig {
910    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
911    where
912        S: serde::Serializer,
913    {
914        let helper = MatcherConfigHelper {
915            normalize: Some(self.0.normalize),
916            ignore_case: Some(self.0.ignore_case),
917            prefer_prefix: Some(self.0.prefer_prefix),
918        };
919        helper.serialize(serializer)
920    }
921}
922
923impl<'de> Deserialize<'de> for NucleoMatcherConfig {
924    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
925    where
926        D: serde::Deserializer<'de>,
927    {
928        let helper = MatcherConfigHelper::deserialize(deserializer)?;
929        let mut config = nucleo::Config::DEFAULT;
930
931        if let Some(norm) = helper.normalize {
932            config.normalize = norm;
933        }
934        if let Some(ic) = helper.ignore_case {
935            config.ignore_case = ic;
936        }
937        if let Some(pp) = helper.prefer_prefix {
938            config.prefer_prefix = pp;
939        }
940
941        Ok(NucleoMatcherConfig(config))
942    }
943}
944
945impl serde::Serialize for Split {
946    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
947    where
948        S: serde::Serializer,
949    {
950        match self {
951            Split::Delimiter(r) => serializer.serialize_str(r.as_str()),
952            Split::Regexes(rs) => {
953                let mut seq = serializer.serialize_seq(Some(rs.len()))?;
954                for r in rs {
955                    seq.serialize_element(r.as_str())?;
956                }
957                seq.end()
958            }
959            Split::None => serializer.serialize_none(),
960        }
961    }
962}
963
964impl<'de> Deserialize<'de> for Split {
965    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
966    where
967        D: Deserializer<'de>,
968    {
969        struct SplitVisitor;
970
971        impl<'de> Visitor<'de> for SplitVisitor {
972            type Value = Split;
973
974            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
975                formatter.write_str("string for delimiter or array of strings for regexes")
976            }
977
978            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
979            where
980                E: de::Error,
981            {
982                // Try to compile single regex
983                Regex::new(value)
984                    .map(Split::Delimiter)
985                    .map_err(|e| E::custom(format!("Invalid regex: {}", e)))
986            }
987
988            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
989            where
990                A: serde::de::SeqAccess<'de>,
991            {
992                let mut regexes = Vec::new();
993                while let Some(s) = seq.next_element::<String>()? {
994                    let r = Regex::new(&s)
995                        .map_err(|e| de::Error::custom(format!("Invalid regex: {}", e)))?;
996                    regexes.push(r);
997                }
998                Ok(Split::Regexes(regexes))
999            }
1000        }
1001
1002        deserializer.deserialize_any(SplitVisitor)
1003    }
1004}
1005
1006impl serde::Serialize for ColumnSetting {
1007    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1008    where
1009        S: serde::Serializer,
1010    {
1011        use serde::ser::SerializeStruct;
1012        let mut state = serializer.serialize_struct("ColumnSetting", 3)?;
1013        state.serialize_field("filter", &self.filter)?;
1014        state.serialize_field("hidden", &self.hidden)?;
1015        state.serialize_field("name", &self.name)?;
1016        state.end()
1017    }
1018}
1019
1020impl<'de> Deserialize<'de> for ColumnSetting {
1021    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1022    where
1023        D: Deserializer<'de>,
1024    {
1025        #[derive(Deserialize)]
1026        #[serde(deny_unknown_fields)]
1027        struct ColumnStruct {
1028            #[serde(default = "default_true")]
1029            filter: bool,
1030            #[serde(default)]
1031            hidden: bool,
1032            name: String,
1033        }
1034
1035        fn default_true() -> bool {
1036            true
1037        }
1038
1039        #[derive(Deserialize)]
1040        #[serde(untagged)]
1041        enum Input {
1042            Str(String),
1043            Obj(ColumnStruct),
1044        }
1045
1046        match Input::deserialize(deserializer)? {
1047            Input::Str(name) => Ok(ColumnSetting {
1048                filter: true,
1049                hidden: false,
1050                name,
1051            }),
1052            Input::Obj(obj) => Ok(ColumnSetting {
1053                filter: obj.filter,
1054                hidden: obj.hidden,
1055                name: obj.name,
1056            }),
1057        }
1058    }
1059}
1060
1061pub fn serialize_padding<S>(padding: &Padding, serializer: S) -> Result<S::Ok, S::Error>
1062where
1063    S: serde::Serializer,
1064{
1065    use serde::ser::SerializeSeq;
1066    if padding.top == padding.bottom && padding.left == padding.right && padding.top == padding.left
1067    {
1068        serializer.serialize_u16(padding.top)
1069    } else if padding.top == padding.bottom && padding.left == padding.right {
1070        let mut seq = serializer.serialize_seq(Some(2))?;
1071        seq.serialize_element(&padding.left)?;
1072        seq.serialize_element(&padding.top)?;
1073        seq.end()
1074    } else {
1075        let mut seq = serializer.serialize_seq(Some(4))?;
1076        seq.serialize_element(&padding.top)?;
1077        seq.serialize_element(&padding.right)?;
1078        seq.serialize_element(&padding.bottom)?;
1079        seq.serialize_element(&padding.left)?;
1080        seq.end()
1081    }
1082}
1083
1084pub fn deserialize_padding<'de, D>(deserializer: D) -> Result<Padding, D::Error>
1085where
1086    D: Deserializer<'de>,
1087{
1088    struct PaddingVisitor;
1089
1090    impl<'de> Visitor<'de> for PaddingVisitor {
1091        type Value = Padding;
1092
1093        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1094            formatter.write_str("a number or an array of 1, 2, or 4 numbers")
1095        }
1096
1097        fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
1098        where
1099            E: de::Error,
1100        {
1101            let v = u16::try_from(value).map_err(|_| {
1102                E::custom(format!("padding value {} is out of range for u16", value))
1103            })?;
1104
1105            Ok(Padding {
1106                top: v,
1107                right: v,
1108                bottom: v,
1109                left: v,
1110            })
1111        }
1112
1113        fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
1114        where
1115            E: de::Error,
1116        {
1117            let v = u16::try_from(value).map_err(|_| {
1118                E::custom(format!("padding value {} is out of range for u16", value))
1119            })?;
1120
1121            Ok(Padding {
1122                top: v,
1123                right: v,
1124                bottom: v,
1125                left: v,
1126            })
1127        }
1128
1129        // 3. Handle Sequences (Arrays)
1130        fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
1131        where
1132            A: de::SeqAccess<'de>,
1133        {
1134            let first: u16 = seq
1135                .next_element()?
1136                .ok_or_else(|| de::Error::invalid_length(0, &self))?;
1137
1138            let second: Option<u16> = seq.next_element()?;
1139            let third: Option<u16> = seq.next_element()?;
1140            let fourth: Option<u16> = seq.next_element()?;
1141
1142            match (second, third, fourth) {
1143                (None, None, None) => Ok(Padding {
1144                    top: first,
1145                    right: first,
1146                    bottom: first,
1147                    left: first,
1148                }),
1149                (Some(v2), None, None) => Ok(Padding {
1150                    top: first,
1151                    bottom: first,
1152                    left: v2,
1153                    right: v2,
1154                }),
1155                (Some(v2), Some(v3), Some(v4)) => Ok(Padding {
1156                    top: first,
1157                    right: v2,
1158                    bottom: v3,
1159                    left: v4,
1160                }),
1161                _ => Err(de::Error::invalid_length(3, &self)),
1162            }
1163        }
1164    }
1165
1166    deserializer.deserialize_any(PaddingVisitor)
1167}
1168
1169pub fn deserialize_option_auto<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
1170where
1171    D: serde::Deserializer<'de>,
1172    T: Deserialize<'de>,
1173{
1174    let opt = Option::<String>::deserialize(deserializer)?;
1175    match opt.as_deref() {
1176        Some("auto") => Ok(None),
1177        Some(s) => Ok(Some(T::deserialize(s.into_deserializer())?)),
1178        None => Ok(None),
1179    }
1180}
1181
1182impl<'de> Deserialize<'de> for BorderSetting {
1183    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1184    where
1185        D: Deserializer<'de>,
1186    {
1187        #[derive(Deserialize)]
1188        struct Helper {
1189            #[serde(default)]
1190            r#type: Option<String>,
1191            #[serde(default)]
1192            color: Option<Color>,
1193            #[serde(default, deserialize_with = "deserialize_borders_option")]
1194            sides: Option<Borders>, // <-- raw string first
1195            #[serde(default, deserialize_with = "deserialize_padding")]
1196            padding: Padding,
1197            #[serde(default)]
1198            title: String,
1199            #[serde(
1200                default,
1201                deserialize_with = "deserialize_modifier",
1202                serialize_with = "serialize_modifier"
1203            )]
1204            title_modifier: Modifier,
1205            #[serde(default)]
1206            bg: Color,
1207        }
1208
1209        let h = Helper::deserialize(deserializer)?;
1210
1211        // parse type
1212        let r#type = match h.r#type {
1213            Some(ref s) => s.parse::<BorderType>().map_err(serde::de::Error::custom)?,
1214            None => BorderType::default(),
1215        };
1216
1217        // parse sides: if type or color is specified, override to ALL
1218        let sides = if let Some(sides) = h.sides {
1219            sides
1220        } else if h.r#type.is_some() || h.color.is_some() {
1221            Borders::ALL
1222        } else {
1223            Borders::NONE
1224        };
1225
1226        Ok(BorderSetting {
1227            r#type,
1228            color: h.color.unwrap_or_default(),
1229            sides,
1230            padding: h.padding,
1231            title: h.title,
1232            title_modifier: h.title_modifier,
1233            bg: h.bg,
1234        })
1235    }
1236}
1237
1238// --------------------------- DEFAULTS
1239
1240fn default_true() -> bool {
1241    true
1242}
1243
1244fn default_black() -> Color {
1245    Color::Black
1246}
1247
1248fn default_green() -> Color {
1249    Color::Green
1250}
1251
1252fn default_bold() -> Modifier {
1253    Modifier::BOLD
1254}
1255
1256fn default_italic() -> Modifier {
1257    Modifier::ITALIC
1258}