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