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