1use matchmaker_partial_macros::partial;
5
6use std::{fmt, ops::Deref};
7
8pub use crate::utils::{HorizontalSeparator, Percentage, serde::StringOrVec};
9
10use crate::{
11 MAX_SPLITS,
12 tui::IoStream,
13 utils::serde::{escaped_opt_char, escaped_opt_string, serde_duration_ms},
14};
15
16use cli_boilerplate_automation::define_transparent_wrapper;
17use cli_boilerplate_automation::serde::{
18 through_string,
20 transform::camelcase_normalized,
21};
22use ratatui::{
23 style::{Color, Modifier, Style},
24 text::Span,
25 widgets::{BorderType, Borders, Padding},
26};
27
28use regex::Regex;
29
30use serde::{
31 Deserialize, Deserializer, Serialize,
32 de::{self, IntoDeserializer, Visitor},
33 ser::SerializeSeq,
34};
35
36#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
40#[partial(recurse, path, derive(Debug, Deserialize))]
41pub struct MatcherConfig {
42 #[serde(flatten)]
43 #[partial(skip)]
44 pub matcher: NucleoMatcherConfig,
45 #[serde(flatten)]
46 pub worker: WorkerConfig,
47 #[serde(flatten)]
49 pub start: StartConfig,
50 #[serde(flatten)]
51 pub exit: ExitConfig,
52}
53
54#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
58#[serde(default)]
59#[partial(path, derive(Debug, Deserialize))]
60pub struct WorkerConfig {
61 #[partial(recurse)]
62 #[partial(alias = "c")]
63 pub columns: ColumnsConfig,
65 #[partial(alias = "t")]
67 pub trim: bool,
68 pub sort_threshold: u32,
70 #[partial(alias = "a")]
72 pub ansi: bool,
73
74 #[partial(alias = "r")]
76 pub raw: bool,
77 pub track: bool,
79 pub reverse: bool,
81}
82
83#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
87#[serde(default)]
88#[partial(path, derive(Debug, Deserialize))]
89pub struct StartConfig {
90 #[serde(deserialize_with = "escaped_opt_char")]
91 #[partial(alias = "is")]
92 pub input_separator: Option<char>,
93 #[serde(deserialize_with = "escaped_opt_string")]
94 #[partial(alias = "os")]
95 pub output_separator: Option<String>,
96
97 pub output_template: Option<String>,
99
100 #[partial(alias = "cmd", alias = "x")]
102 pub command: String,
103 #[partial(alias = "s")]
104 pub sync: bool,
105}
106
107#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
111#[serde(default)]
112#[partial(path, derive(Debug, Deserialize))]
113pub struct ExitConfig {
114 pub select_1: bool,
116 pub allow_empty: bool,
118 pub abort_empty: bool,
120}
121
122#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
124#[serde(default, deny_unknown_fields)]
125#[partial(recurse, path, derive(Debug, Deserialize))]
126pub struct RenderConfig {
127 pub ui: UiConfig,
129 #[partial(alias = "i")]
131 pub input: InputConfig,
132 #[partial(alias = "r")]
134 pub results: ResultsConfig,
135
136 pub status: StatusConfig,
138 #[partial(alias = "p")]
140 pub preview: PreviewConfig,
141 #[partial(alias = "f")]
142 pub footer: DisplayConfig,
143 #[partial(alias = "h")]
144 pub header: DisplayConfig,
145}
146
147impl RenderConfig {
148 pub fn tick_rate(&self) -> u8 {
149 self.ui.tick_rate
150 }
151}
152
153#[partial(path, derive(Debug, Deserialize))]
155#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
156#[serde(default, deny_unknown_fields)]
157pub struct TerminalConfig {
158 pub stream: IoStream, pub restore_fullscreen: bool,
160 pub redraw_on_resize: bool,
161 pub extended_keys: bool,
163 #[serde(with = "serde_duration_ms")]
164 pub sleep_ms: std::time::Duration, #[serde(flatten)]
167 #[partial(recurse)]
168 pub layout: Option<TerminalLayoutSettings>, pub clear_on_exit: bool,
170}
171
172impl Default for TerminalConfig {
173 fn default() -> Self {
174 Self {
175 stream: IoStream::default(),
176 restore_fullscreen: true,
177 redraw_on_resize: bool::default(),
178 sleep_ms: std::time::Duration::default(),
179 layout: Option::default(),
180 extended_keys: true,
181 clear_on_exit: true,
182 }
183 }
184}
185#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
186#[serde(default, deny_unknown_fields)]
187pub struct TerminalSettings {}
188
189#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
191#[serde(default, deny_unknown_fields)]
192#[partial(path, derive(Debug, Deserialize))]
193pub struct UiConfig {
194 #[partial(recurse)]
195 pub border: BorderSetting,
196 pub tick_rate: u8, }
198
199impl Default for UiConfig {
200 fn default() -> Self {
201 Self {
202 border: Default::default(),
203 tick_rate: 60,
204 }
205 }
206}
207
208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
210#[serde(default, deny_unknown_fields)]
211#[partial(path, derive(Debug, Deserialize))]
212pub struct InputConfig {
213 #[partial(recurse)]
214 pub border: BorderSetting,
215
216 #[serde(deserialize_with = "camelcase_normalized")]
218 pub fg: Color,
219 pub modifier: Modifier,
221
222 #[serde(deserialize_with = "camelcase_normalized")]
223 pub prompt_fg: Color,
224 pub prompt_modifier: Modifier,
226
227 #[serde(deserialize_with = "deserialize_string_or_char_as_double_width")]
229 pub prompt: String,
230 pub cursor: CursorSetting,
232
233 #[partial(alias = "i")]
235 pub initial: String,
236
237 pub scroll_padding: bool,
239}
240
241impl Default for InputConfig {
242 fn default() -> Self {
243 Self {
244 border: Default::default(),
245 fg: Default::default(),
246 modifier: Default::default(),
247 prompt_fg: Default::default(),
248 prompt_modifier: Default::default(),
249 prompt: "> ".to_string(),
250 cursor: Default::default(),
251 initial: Default::default(),
252
253 scroll_padding: true,
254 }
255 }
256}
257
258#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
259#[serde(default, deny_unknown_fields)]
260#[partial(path, derive(Debug, Deserialize))]
261pub struct OverlayConfig {
262 #[partial(recurse)]
263 pub border: BorderSetting,
264 pub outer_dim: bool,
265 pub layout: OverlayLayoutSettings,
266}
267
268#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
269#[partial(path, derive(Debug, Deserialize))]
270pub struct OverlayLayoutSettings {
271 #[partial(alias = "p")]
273 pub percentage: [Percentage; 2],
274 pub min: [u16; 2],
276 pub max: [u16; 2],
278}
279
280impl Default for OverlayLayoutSettings {
281 fn default() -> Self {
282 Self {
283 percentage: [Percentage::new(60), Percentage::new(30)],
284 min: [10, 5],
285 max: [200, 30],
286 }
287 }
288}
289
290#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
291pub enum RowConnectionStyle {
292 #[default]
293 Disjoint,
294 Capped,
295 Full,
296}
297
298#[partial(path, derive(Debug, Deserialize))]
301#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
302#[serde(default, deny_unknown_fields)]
303pub struct ResultsConfig {
304 #[partial(recurse)]
305 pub border: BorderSetting,
306
307 #[serde(deserialize_with = "deserialize_string_or_char_as_double_width")]
309 pub multi_prefix: String,
310 pub default_prefix: String,
311
312 #[serde(deserialize_with = "camelcase_normalized")]
314 pub fg: Color,
315 pub modifier: Modifier,
317
318 #[serde(deserialize_with = "camelcase_normalized")]
319 pub match_fg: Color,
320 pub match_modifier: Modifier,
322
323 #[serde(deserialize_with = "camelcase_normalized")]
325 pub current_fg: Color,
326 #[serde(deserialize_with = "camelcase_normalized")]
328 pub current_bg: Color,
329 pub current_modifier: Modifier,
332 #[serde(deserialize_with = "camelcase_normalized")]
334 pub row_connection_style: RowConnectionStyle,
335
336 #[partial(alias = "c")]
342 #[serde(alias = "cycle")]
343 pub scroll_wrap: bool,
344 #[partial(alias = "sp")]
345 pub scroll_padding: u16,
346 #[partial(alias = "r")]
347 pub reverse: Option<bool>,
348
349 #[partial(alias = "w")]
351 pub wrap: bool,
352 pub min_wrap_width: u16,
353
354 pub column_spacing: Count,
356 pub current_prefix: String,
357
358 #[partial(alias = "ra")]
360 pub right_align_last: bool,
361
362 #[partial(alias = "v")]
363 #[serde(alias = "vertical")]
364 pub stacked_columns: bool,
365
366 #[serde(alias = "hr")]
367 #[serde(deserialize_with = "camelcase_normalized")]
368 pub horizontal_separator: HorizontalSeparator,
369}
370define_transparent_wrapper!(
371 #[derive(Copy, Clone)]
372 Count: u16 = 1
373);
374
375impl Default for ResultsConfig {
382 fn default() -> Self {
383 ResultsConfig {
384 border: Default::default(),
385
386 multi_prefix: "▌ ".to_string(),
387 default_prefix: Default::default(),
388
389 fg: Default::default(),
390 modifier: Default::default(),
391 match_fg: Color::Green,
392 match_modifier: Modifier::ITALIC,
393
394 current_fg: Default::default(),
395 current_bg: Color::Black,
396 current_modifier: Modifier::BOLD,
397 row_connection_style: RowConnectionStyle::Disjoint,
398
399 scroll_wrap: true,
400 scroll_padding: 2,
401 reverse: Default::default(),
402
403 wrap: Default::default(),
404 min_wrap_width: 5,
405
406 column_spacing: Default::default(),
407 current_prefix: Default::default(),
408 right_align_last: false,
409 stacked_columns: false,
410 horizontal_separator: Default::default(),
411 }
412 }
413}
414
415#[partial(path, derive(Debug, Deserialize))]
416#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
417#[serde(default, deny_unknown_fields)]
418pub struct StatusConfig {
419 #[serde(deserialize_with = "camelcase_normalized")]
420 pub fg: Color,
421 pub modifier: Modifier,
423 pub show: bool,
424 pub match_indent: bool,
425 #[partial(alias = "t")]
426 pub template: String,
427}
428impl Default for StatusConfig {
429 fn default() -> Self {
430 Self {
431 fg: Color::Green,
432 modifier: Modifier::ITALIC,
433 show: true,
434 match_indent: true,
435 template: r#"\m/\t"#.to_string(),
436 }
437 }
438}
439
440#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
441#[serde(default, deny_unknown_fields)]
442#[partial(path, derive(Debug, Deserialize))]
443pub struct DisplayConfig {
444 #[partial(recurse)]
445 pub border: BorderSetting,
446
447 #[serde(deserialize_with = "camelcase_normalized")]
448 pub fg: Color,
449 pub modifier: Modifier,
451
452 pub match_indent: bool,
454 pub wrap: bool,
456
457 #[serde(deserialize_with = "deserialize_option_auto")]
459 #[partial(alias = "i")]
460 pub content: Option<StringOrVec>,
461
462 #[serde(deserialize_with = "camelcase_normalized")]
471 pub row_connection_style: RowConnectionStyle,
472
473 #[partial(alias = "h")]
478 pub header_lines: usize,
479}
480
481impl Default for DisplayConfig {
482 fn default() -> Self {
483 DisplayConfig {
484 border: Default::default(),
485 match_indent: true,
486 fg: Color::Green,
487 wrap: false,
488 row_connection_style: Default::default(),
489 modifier: Modifier::ITALIC, content: None,
491 header_lines: 0,
492 }
493 }
494}
495
496#[partial(path, derive(Debug, Deserialize))]
511#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
512#[serde(default)]
513pub struct PreviewConfig {
514 #[partial(recurse)]
515 pub border: BorderSetting,
516 #[partial(recurse)]
517 #[partial(alias = "l")]
518 pub layout: Vec<PreviewSetting>,
519 #[partial(recurse)]
520 #[serde(flatten)]
521 pub scroll: PreviewScrollSetting,
522 #[partial(alias = "c")]
524 #[serde(alias = "cycle")]
525 pub scroll_wrap: bool,
526 pub wrap: bool,
527 pub show: bool,
528}
529
530impl Default for PreviewConfig {
531 fn default() -> Self {
532 PreviewConfig {
533 border: BorderSetting {
534 padding: Padding::left(2),
535 ..Default::default()
536 },
537 scroll: Default::default(),
538 layout: Default::default(),
539 scroll_wrap: true,
540 wrap: Default::default(),
541 show: Default::default(),
542 }
543 }
544}
545
546#[partial(path, derive(Debug, Deserialize))]
548#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
549#[serde(default, deny_unknown_fields)]
550#[derive(Default)]
551pub struct PreviewScrollSetting {
552 pub index: Option<String>,
555 #[partial(alias = "o")]
557 pub offset: isize,
558 #[partial(alias = "p")]
560 pub percentage: Percentage,
561 #[partial(alias = "h")]
563 pub header_lines: usize,
564}
565
566#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
567#[serde(default, deny_unknown_fields)]
568pub struct PreviewerConfig {
569 pub try_lossy: bool,
570
571 pub cache: u8,
573
574 pub help_colors: TomlColorConfig,
575}
576
577#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
579pub struct TomlColorConfig {
580 #[serde(deserialize_with = "camelcase_normalized")]
581 pub section: Color,
582 #[serde(deserialize_with = "camelcase_normalized")]
583 pub key: Color,
584 #[serde(deserialize_with = "camelcase_normalized")]
585 pub string: Color,
586 #[serde(deserialize_with = "camelcase_normalized")]
587 pub number: Color,
588 pub section_bold: bool,
589}
590
591impl Default for TomlColorConfig {
592 fn default() -> Self {
593 Self {
594 section: Color::Blue,
595 key: Color::Yellow,
596 string: Color::Green,
597 number: Color::Cyan,
598 section_bold: true,
599 }
600 }
601}
602
603#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
607#[serde(transparent)]
608pub struct FormatString(String);
609
610impl Deref for FormatString {
611 type Target = str;
612
613 fn deref(&self) -> &Self::Target {
614 &self.0
615 }
616}
617
618#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)]
619#[serde(default, deny_unknown_fields)]
620#[partial(path, derive(Debug, Deserialize))]
621pub struct BorderSetting {
622 #[serde(with = "through_string")]
623 pub r#type: BorderType,
624 #[serde(deserialize_with = "camelcase_normalized")]
625 pub color: Color,
626 pub sides: Option<Borders>,
634 #[serde(with = "padding")]
642 pub padding: Padding,
643 pub title: String,
644 pub title_modifier: Modifier,
646 #[serde(deserialize_with = "camelcase_normalized")]
647 pub bg: Color,
648}
649
650impl BorderSetting {
651 pub fn as_block(&self) -> ratatui::widgets::Block<'_> {
652 let mut ret = ratatui::widgets::Block::default()
653 .padding(self.padding)
654 .style(Style::default().bg(self.bg));
655
656 if !self.title.is_empty() {
657 let title = Span::styled(
658 &self.title,
659 Style::default().add_modifier(self.title_modifier),
660 );
661
662 ret = ret.title(title)
663 };
664
665 if !self.is_empty() {
666 ret = ret
667 .borders(self.sides())
668 .border_type(self.r#type)
669 .border_style(ratatui::style::Style::default().fg(self.color))
670 }
671
672 ret
673 }
674
675 pub fn sides(&self) -> Borders {
676 if let Some(s) = self.sides {
677 s
678 } else if self.color != Default::default() || self.r#type != Default::default() {
679 Borders::ALL
680 } else {
681 Borders::NONE
682 }
683 }
684
685 pub fn as_static_block(&self) -> ratatui::widgets::Block<'static> {
686 let mut ret = ratatui::widgets::Block::default()
687 .padding(self.padding)
688 .style(Style::default().bg(self.bg));
689
690 if !self.title.is_empty() {
691 let title: Span<'static> = Span::styled(
692 self.title.clone(),
693 Style::default().add_modifier(self.title_modifier),
694 );
695
696 ret = ret.title(title)
697 };
698
699 if !self.is_empty() {
700 ret = ret
701 .borders(self.sides())
702 .border_type(self.r#type)
703 .border_style(ratatui::style::Style::default().fg(self.color))
704 }
705
706 ret
707 }
708
709 pub fn is_empty(&self) -> bool {
710 self.sides() == Borders::NONE
711 }
712
713 pub fn height(&self) -> u16 {
714 let mut height = 0;
715 height += 2 * !self.is_empty() as u16;
716 height += self.padding.top + self.padding.bottom;
717 height += (!self.title.is_empty() as u16).saturating_sub(!self.is_empty() as u16);
718
719 height
720 }
721
722 pub fn width(&self) -> u16 {
723 let mut width = 0;
724 width += 2 * !self.is_empty() as u16;
725 width += self.padding.left + self.padding.right;
726
727 width
728 }
729
730 pub fn left(&self) -> u16 {
731 let mut width = 0;
732 width += !self.is_empty() as u16;
733 width += self.padding.left;
734
735 width
736 }
737
738 pub fn top(&self) -> u16 {
739 let mut height = 0;
740 height += !self.is_empty() as u16;
741 height += self.padding.top;
742 height += (!self.title.is_empty() as u16).saturating_sub(!self.is_empty() as u16);
743
744 height
745 }
746}
747
748#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
750#[partial(path, derive(Debug, Deserialize))]
751pub struct TerminalLayoutSettings {
752 #[partial(alias = "p")]
754 pub percentage: Percentage,
755 pub min: u16,
756 pub max: u16, }
758
759impl Default for TerminalLayoutSettings {
760 fn default() -> Self {
761 Self {
762 percentage: Percentage::new(50),
763 min: 10,
764 max: 120,
765 }
766 }
767}
768
769#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
770#[serde(rename_all = "lowercase")]
771pub enum Side {
772 Top,
773 Bottom,
774 Left,
775 #[default]
776 Right,
777}
778
779#[partial(path, derive(Debug, Deserialize))]
780#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
781pub struct PreviewSetting {
782 #[serde(flatten)]
783 #[partial(recurse)]
784 pub layout: PreviewLayout,
785 #[serde(default, alias = "cmd", alias = "x")]
786 pub command: String,
787}
788
789#[partial(path, derive(Debug, Deserialize))]
790#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
791pub struct PreviewLayout {
792 pub side: Side,
793 #[serde(alias = "p")]
796 pub percentage: Percentage,
798 pub min: i16,
799 pub max: i16,
800}
801
802impl Default for PreviewLayout {
803 fn default() -> Self {
804 Self {
805 side: Side::Right,
806 percentage: Percentage::new(60),
807 min: 30,
808 max: 120,
809 }
810 }
811}
812
813#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
814#[serde(rename_all = "lowercase")]
815pub enum CursorSetting {
816 None,
817 #[default]
818 Default,
819}
820
821use crate::utils::serde::bounded_usize;
822#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
824#[serde(default, deny_unknown_fields)]
825#[partial(path, derive(Debug, Deserialize))]
826pub struct ColumnsConfig {
827 pub split: Split,
829 #[partial(alias = "n")]
831 pub names: Vec<ColumnSetting>,
832 #[serde(deserialize_with = "bounded_usize::<_, 1, {crate::MAX_SPLITS}>")]
834 max_columns: usize,
835}
836
837impl ColumnsConfig {
838 pub fn max_cols(&self) -> usize {
839 self.max_columns.min(MAX_SPLITS).max(1)
840 }
841}
842
843impl Default for ColumnsConfig {
844 fn default() -> Self {
845 Self {
846 split: Default::default(),
847 names: Default::default(),
848 max_columns: 5,
849 }
850 }
851}
852
853#[derive(Default, Debug, Clone, PartialEq)]
854pub struct ColumnSetting {
855 pub filter: bool,
856 pub hidden: bool,
857 pub name: String,
858}
859
860#[derive(Default, Debug, Clone)]
861pub enum Split {
862 Delimiter(Regex),
864 Regexes(Vec<Regex>),
866 #[default]
868 None,
869}
870
871impl PartialEq for Split {
872 fn eq(&self, other: &Self) -> bool {
873 match (self, other) {
874 (Split::Delimiter(r1), Split::Delimiter(r2)) => r1.as_str() == r2.as_str(),
875 (Split::Regexes(v1), Split::Regexes(v2)) => {
876 if v1.len() != v2.len() {
877 return false;
878 }
879 v1.iter()
880 .zip(v2.iter())
881 .all(|(r1, r2)| r1.as_str() == r2.as_str())
882 }
883 (Split::None, Split::None) => true,
884 _ => false,
885 }
886 }
887}
888
889pub fn serialize_borders<S>(borders: &Borders, serializer: S) -> Result<S::Ok, S::Error>
891where
892 S: serde::Serializer,
893{
894 use serde::ser::SerializeSeq;
895 let mut seq = serializer.serialize_seq(None)?;
896 if borders.contains(Borders::TOP) {
897 seq.serialize_element("top")?;
898 }
899 if borders.contains(Borders::BOTTOM) {
900 seq.serialize_element("bottom")?;
901 }
902 if borders.contains(Borders::LEFT) {
903 seq.serialize_element("left")?;
904 }
905 if borders.contains(Borders::RIGHT) {
906 seq.serialize_element("right")?;
907 }
908 seq.end()
909}
910
911pub fn deserialize_string_or_char_as_double_width<'de, D, T>(deserializer: D) -> Result<T, D::Error>
912where
913 D: Deserializer<'de>,
914 T: From<String>,
915{
916 struct GenericVisitor<T> {
917 _marker: std::marker::PhantomData<T>,
918 }
919
920 impl<'de, T> Visitor<'de> for GenericVisitor<T>
921 where
922 T: From<String>,
923 {
924 type Value = T;
925
926 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
927 formatter.write_str("a string or single character")
928 }
929
930 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
931 where
932 E: de::Error,
933 {
934 let s = if v.chars().count() == 1 {
935 let mut s = String::with_capacity(2);
936 s.push(v.chars().next().unwrap());
937 s.push(' ');
938 s
939 } else {
940 v.to_string()
941 };
942 Ok(T::from(s))
943 }
944
945 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
946 where
947 E: de::Error,
948 {
949 self.visit_str(&v)
950 }
951 }
952
953 deserializer.deserialize_string(GenericVisitor {
954 _marker: std::marker::PhantomData,
955 })
956}
957
958#[derive(Debug, Clone, PartialEq)]
960pub struct NucleoMatcherConfig(pub nucleo::Config);
961
962impl Default for NucleoMatcherConfig {
963 fn default() -> Self {
964 Self(nucleo::Config::DEFAULT)
965 }
966}
967
968#[derive(Debug, Clone, Serialize, Deserialize)]
969#[serde(default)]
970#[derive(Default)]
971struct MatcherConfigHelper {
972 pub normalize: Option<bool>,
973 pub ignore_case: Option<bool>,
974 pub prefer_prefix: Option<bool>,
975}
976
977impl serde::Serialize for NucleoMatcherConfig {
978 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
979 where
980 S: serde::Serializer,
981 {
982 let helper = MatcherConfigHelper {
983 normalize: Some(self.0.normalize),
984 ignore_case: Some(self.0.ignore_case),
985 prefer_prefix: Some(self.0.prefer_prefix),
986 };
987 helper.serialize(serializer)
988 }
989}
990
991impl<'de> Deserialize<'de> for NucleoMatcherConfig {
992 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
993 where
994 D: serde::Deserializer<'de>,
995 {
996 let helper = MatcherConfigHelper::deserialize(deserializer)?;
997 let mut config = nucleo::Config::DEFAULT;
998
999 if let Some(norm) = helper.normalize {
1000 config.normalize = norm;
1001 }
1002 if let Some(ic) = helper.ignore_case {
1003 config.ignore_case = ic;
1004 }
1005 if let Some(pp) = helper.prefer_prefix {
1006 config.prefer_prefix = pp;
1007 }
1008
1009 Ok(NucleoMatcherConfig(config))
1010 }
1011}
1012
1013impl serde::Serialize for Split {
1014 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1015 where
1016 S: serde::Serializer,
1017 {
1018 match self {
1019 Split::Delimiter(r) => serializer.serialize_str(r.as_str()),
1020 Split::Regexes(rs) => {
1021 let mut seq = serializer.serialize_seq(Some(rs.len()))?;
1022 for r in rs {
1023 seq.serialize_element(r.as_str())?;
1024 }
1025 seq.end()
1026 }
1027 Split::None => serializer.serialize_none(),
1028 }
1029 }
1030}
1031
1032impl<'de> Deserialize<'de> for Split {
1033 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1034 where
1035 D: Deserializer<'de>,
1036 {
1037 struct SplitVisitor;
1038
1039 impl<'de> Visitor<'de> for SplitVisitor {
1040 type Value = Split;
1041
1042 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1043 formatter.write_str("string for delimiter or array of strings for regexes")
1044 }
1045
1046 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1047 where
1048 E: de::Error,
1049 {
1050 Regex::new(value)
1052 .map(Split::Delimiter)
1053 .map_err(|e| E::custom(format!("Invalid regex: {}", e)))
1054 }
1055
1056 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
1057 where
1058 A: serde::de::SeqAccess<'de>,
1059 {
1060 let mut regexes = Vec::new();
1061 while let Some(s) = seq.next_element::<String>()? {
1062 let r = Regex::new(&s)
1063 .map_err(|e| de::Error::custom(format!("Invalid regex: {}", e)))?;
1064 regexes.push(r);
1065 }
1066 Ok(Split::Regexes(regexes))
1067 }
1068 }
1069
1070 deserializer.deserialize_any(SplitVisitor)
1071 }
1072}
1073
1074impl serde::Serialize for ColumnSetting {
1075 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1076 where
1077 S: serde::Serializer,
1078 {
1079 use serde::ser::SerializeStruct;
1080 let mut state = serializer.serialize_struct("ColumnSetting", 3)?;
1081 state.serialize_field("filter", &self.filter)?;
1082 state.serialize_field("hidden", &self.hidden)?;
1083 state.serialize_field("name", &self.name)?;
1084 state.end()
1085 }
1086}
1087
1088impl<'de> Deserialize<'de> for ColumnSetting {
1089 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1090 where
1091 D: Deserializer<'de>,
1092 {
1093 #[derive(Deserialize)]
1094 #[serde(deny_unknown_fields)]
1095 struct ColumnStruct {
1096 #[serde(default = "default_true")]
1097 filter: bool,
1098 #[serde(default)]
1099 hidden: bool,
1100 name: String,
1101 }
1102
1103 fn default_true() -> bool {
1104 true
1105 }
1106
1107 #[derive(Deserialize)]
1108 #[serde(untagged)]
1109 enum Input {
1110 Str(String),
1111 Obj(ColumnStruct),
1112 }
1113
1114 match Input::deserialize(deserializer)? {
1115 Input::Str(name) => Ok(ColumnSetting {
1116 filter: true,
1117 hidden: false,
1118 name,
1119 }),
1120 Input::Obj(obj) => Ok(ColumnSetting {
1121 filter: obj.filter,
1122 hidden: obj.hidden,
1123 name: obj.name,
1124 }),
1125 }
1126 }
1127}
1128
1129mod padding {
1130 use super::*;
1131
1132 pub fn serialize<S>(padding: &Padding, serializer: S) -> Result<S::Ok, S::Error>
1133 where
1134 S: serde::Serializer,
1135 {
1136 use serde::ser::SerializeSeq;
1137 if padding.top == padding.bottom
1138 && padding.left == padding.right
1139 && padding.top == padding.left
1140 {
1141 serializer.serialize_u16(padding.top)
1142 } else if padding.top == padding.bottom && padding.left == padding.right {
1143 let mut seq = serializer.serialize_seq(Some(2))?;
1144 seq.serialize_element(&padding.left)?;
1145 seq.serialize_element(&padding.top)?;
1146 seq.end()
1147 } else {
1148 let mut seq = serializer.serialize_seq(Some(4))?;
1149 seq.serialize_element(&padding.top)?;
1150 seq.serialize_element(&padding.right)?;
1151 seq.serialize_element(&padding.bottom)?;
1152 seq.serialize_element(&padding.left)?;
1153 seq.end()
1154 }
1155 }
1156
1157 pub fn deserialize<'de, D>(deserializer: D) -> Result<Padding, D::Error>
1158 where
1159 D: Deserializer<'de>,
1160 {
1161 struct PaddingVisitor;
1162
1163 impl<'de> Visitor<'de> for PaddingVisitor {
1164 type Value = Padding;
1165
1166 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1167 formatter.write_str("a number or an array of 1, 2, or 4 numbers")
1168 }
1169
1170 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
1171 where
1172 E: de::Error,
1173 {
1174 let v = u16::try_from(value).map_err(|_| {
1175 E::custom(format!("padding value {} is out of range for u16", value))
1176 })?;
1177
1178 Ok(Padding {
1179 top: v,
1180 right: v,
1181 bottom: v,
1182 left: v,
1183 })
1184 }
1185
1186 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
1187 where
1188 E: de::Error,
1189 {
1190 let v = u16::try_from(value).map_err(|_| {
1191 E::custom(format!("padding value {} is out of range for u16", value))
1192 })?;
1193
1194 Ok(Padding {
1195 top: v,
1196 right: v,
1197 bottom: v,
1198 left: v,
1199 })
1200 }
1201
1202 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
1204 where
1205 A: de::SeqAccess<'de>,
1206 {
1207 let first: u16 = seq
1208 .next_element()?
1209 .ok_or_else(|| de::Error::invalid_length(0, &self))?;
1210
1211 let second: Option<u16> = seq.next_element()?;
1212 let third: Option<u16> = seq.next_element()?;
1213 let fourth: Option<u16> = seq.next_element()?;
1214
1215 match (second, third, fourth) {
1216 (None, None, None) => Ok(Padding {
1217 top: first,
1218 right: first,
1219 bottom: first,
1220 left: first,
1221 }),
1222 (Some(v2), None, None) => Ok(Padding {
1223 top: first,
1224 bottom: first,
1225 left: v2,
1226 right: v2,
1227 }),
1228 (Some(v2), Some(v3), Some(v4)) => Ok(Padding {
1229 top: first,
1230 right: v2,
1231 bottom: v3,
1232 left: v4,
1233 }),
1234 _ => Err(de::Error::invalid_length(3, &self)),
1235 }
1236 }
1237 }
1238
1239 deserializer.deserialize_any(PaddingVisitor)
1240 }
1241}
1242
1243pub fn deserialize_option_auto<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
1244where
1245 D: serde::Deserializer<'de>,
1246 T: Deserialize<'de>,
1247{
1248 let opt = Option::<String>::deserialize(deserializer)?;
1249 match opt.as_deref() {
1250 Some("auto") => Ok(None),
1251 Some(s) => Ok(Some(T::deserialize(s.into_deserializer())?)),
1252 None => Ok(None),
1253 }
1254}