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