matchmaker/
config.rs

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