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