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