1use matchmaker_partial_macros::partial;
5
6pub use crate::config_types::*;
7pub use crate::utils::{Percentage, serde::StringOrVec};
8
9use crate::{
10 MAX_SPLITS,
11 tui::IoStream,
12 utils::serde::{escaped_opt_char, escaped_opt_string, serde_duration_ms},
13};
14
15use cba::serde::transform::{
16 camelcase_normalized, camelcase_normalized_option,
17};
18use ratatui::{
19 style::{Color, Modifier, Style},
20 text::Span,
21 widgets::{BorderType, Borders},
22};
23
24use serde::{Deserialize, Serialize};
25
26#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
30#[partial(recurse, path, derive(Debug, Deserialize))]
31pub struct MatcherConfig {
32 #[serde(flatten)]
33 #[partial(skip)]
34 pub matcher: NucleoMatcherConfig,
35 #[serde(flatten)]
36 pub worker: WorkerConfig,
37}
38
39#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
43#[serde(default)]
44#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
45pub struct WorkerConfig {
46 #[partial(recurse)]
47 #[serde(flatten)]
48 pub columns: ColumnsConfig,
50 pub sort_threshold: u32,
52 #[partial(alias = "i")]
54 pub default_column: String,
55
56 #[partial(alias = "r")]
58 pub raw: bool,
59 pub track: bool,
61 pub reverse: bool, }
64
65#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
68#[serde(default, deny_unknown_fields)]
69#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
70pub struct StartConfig {
71 #[serde(deserialize_with = "escaped_opt_char")]
72 #[partial(alias = "is")]
73 pub input_separator: Option<char>,
74 #[serde(deserialize_with = "escaped_opt_string")]
75 #[partial(alias = "os")]
76 pub output_separator: Option<String>,
77
78 #[partial(alias = "ot")]
80 #[serde(alias = "output")]
81 pub output_template: Option<String>,
82
83 #[partial(alias = "cmd", alias = "x")]
85 pub command: String,
86 #[partial(alias = "ax")]
88 pub additional_commands: Vec<String>,
89 pub sync: bool,
90
91 #[partial(alias = "a")]
93 pub ansi: bool,
94 #[partial(alias = "t")]
96 pub trim: bool,
97}
98
99#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
101#[serde(default, deny_unknown_fields)]
102#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
103pub struct ExitConfig {
104 pub select_1: bool,
106 pub allow_empty: bool,
108 pub abort_empty: bool,
110 pub last_key_path: Option<std::path::PathBuf>,
113}
114
115#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
117#[serde(default, deny_unknown_fields)]
118#[partial(recurse, path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
119pub struct RenderConfig {
120 pub ui: UiConfig,
122 #[partial(alias = "i")]
124 pub input: InputConfig,
125 #[partial(alias = "r")]
127 pub results: ResultsConfig,
128
129 pub status: StatusConfig,
131 #[partial(alias = "p")]
133 pub preview: PreviewConfig,
134 #[partial(alias = "f")]
135 pub footer: DisplayConfig,
136 #[partial(alias = "h")]
137 pub header: DisplayConfig,
138}
139
140impl RenderConfig {
141 pub fn tick_rate(&self) -> u8 {
142 self.ui.tick_rate
143 }
144}
145
146#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
148#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
149#[serde(default, deny_unknown_fields)]
150pub struct TerminalConfig {
151 pub stream: IoStream, pub restore_fullscreen: bool,
153 pub redraw_on_resize: bool,
154 pub extended_keys: bool,
156 #[serde(with = "serde_duration_ms")]
157 pub sleep_ms: std::time::Duration, #[serde(flatten)]
159 #[partial(recurse)]
160 pub layout: Option<TerminalLayoutSettings>, pub clear_on_exit: bool,
162 pub move_up_on_exit: bool,
164}
165
166impl Default for TerminalConfig {
167 fn default() -> Self {
168 Self {
169 stream: IoStream::default(),
170 restore_fullscreen: true,
171 redraw_on_resize: bool::default(),
172 sleep_ms: std::time::Duration::default(),
173 layout: Option::default(),
174 extended_keys: true,
175 clear_on_exit: true,
176 move_up_on_exit: false,
177 }
178 }
179}
180
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
183#[serde(default, deny_unknown_fields)]
184#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
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, Clone, PartialEq, Deserialize, Serialize))]
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, Clone, PartialEq, Deserialize, Serialize))]
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, Clone, PartialEq, Deserialize, Serialize))]
262pub struct OverlayLayoutSettings {
263 #[partial(alias = "p")]
265 pub percentage: [Percentage; 2],
266 pub min: [u16; 2],
268 pub max: [u16; 2],
270
271 pub y_offset: Percentage,
273}
274
275impl Default for OverlayLayoutSettings {
276 fn default() -> Self {
277 Self {
278 percentage: [Percentage::new(60), Percentage::new(30)],
279 min: [10, 10],
280 max: [200, 30],
281 y_offset: Percentage::new(55),
282 }
283 }
284}
285
286#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
289#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
290#[serde(default, deny_unknown_fields)]
291pub struct ResultsConfig {
292 #[partial(recurse)]
293 pub border: BorderSetting,
294
295 #[serde(deserialize_with = "deserialize_string_or_char_as_double_width")]
297 pub multi_prefix: String,
298 pub default_prefix: String,
299
300 pub multi: bool,
302
303 #[serde(deserialize_with = "camelcase_normalized")]
305 pub fg: Color,
306 #[serde(deserialize_with = "camelcase_normalized")]
307 pub bg: Color,
308 pub modifier: Modifier,
310
311 #[serde(deserialize_with = "camelcase_normalized")]
313 pub inactive_fg: Color,
314 #[serde(deserialize_with = "camelcase_normalized")]
315 pub inactive_bg: Color,
316 pub inactive_modifier: Modifier,
318
319 #[serde(deserialize_with = "camelcase_normalized")]
321 pub inactive_current_fg: Color,
322 #[serde(deserialize_with = "camelcase_normalized")]
323 pub inactive_current_bg: Color,
324 pub inactive_current_modifier: Modifier,
326
327 #[serde(deserialize_with = "camelcase_normalized")]
328 pub match_fg: Color,
329 pub match_modifier: Modifier,
331
332 #[serde(deserialize_with = "camelcase_normalized")]
334 pub current_fg: Color,
335 #[serde(deserialize_with = "camelcase_normalized")]
337 pub current_bg: Color,
338 pub current_modifier: Modifier,
341
342 #[serde(deserialize_with = "camelcase_normalized")]
347 pub row_connection_style: RowConnectionStyle,
348
349 #[partial(alias = "c")]
351 #[serde(alias = "cycle")]
352 pub scroll_wrap: bool,
353 #[partial(alias = "sp")]
354 pub scroll_padding: u16,
355 #[partial(alias = "r")]
356 pub reverse: Option<bool>,
357
358 #[partial(alias = "w")]
360 pub wrap: bool,
361 pub min_wrap_width: u16,
362
363 pub column_spacing: Count,
367 pub current_prefix: String,
368 pub match_start_context: Option<usize>,
369
370 #[partial(alias = "ra")]
372 pub right_align_last: bool,
373
374 #[partial(alias = "v")]
375 #[serde(alias = "vertical")]
376 pub stacked_columns: bool,
377
378 #[serde(alias = "hr")]
379 #[serde(deserialize_with = "camelcase_normalized")]
380 pub horizontal_separator: HorizontalSeparator,
381}
382
383impl Default for ResultsConfig {
384 fn default() -> Self {
385 ResultsConfig {
386 border: Default::default(),
387
388 multi_prefix: "▌ ".to_string(),
389 default_prefix: Default::default(),
390 multi: true,
391
392 fg: Default::default(),
393 modifier: Default::default(),
394 bg: Default::default(),
395
396 inactive_fg: Default::default(),
397 inactive_modifier: Modifier::DIM,
398 inactive_bg: Default::default(),
399
400 inactive_current_fg: Default::default(),
401 inactive_current_modifier: Default::default(),
402 inactive_current_bg: Default::default(),
403
404 match_fg: Color::Green,
405 match_modifier: Modifier::ITALIC,
406
407 current_fg: Default::default(),
408 current_bg: Color::Black,
409 current_modifier: Modifier::BOLD,
410 row_connection_style: RowConnectionStyle::Disjoint,
411
412 scroll_wrap: true,
413 scroll_padding: 2,
414 reverse: Default::default(),
415
416 wrap: Default::default(),
417 min_wrap_width: 6,
418 match_start_context: Some(4),
419
420 column_spacing: Default::default(),
421 current_prefix: Default::default(),
422 right_align_last: false,
423 stacked_columns: false,
424 horizontal_separator: Default::default(),
425 }
426 }
427}
428
429#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
430#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
431#[serde(default, deny_unknown_fields)]
432pub struct StatusConfig {
433 #[serde(deserialize_with = "camelcase_normalized")]
434 pub fg: Color,
435 pub modifier: Modifier,
437 pub show: bool,
439 pub match_indent: bool,
441
442 #[partial(alias = "t")]
448 pub template: String,
449
450 pub row_connection_style: RowConnectionStyle,
454}
455impl Default for StatusConfig {
456 fn default() -> Self {
457 Self {
458 fg: Color::Green,
459 modifier: Modifier::ITALIC,
460 show: true,
461 match_indent: true,
462 template: r#"\m/\t"#.to_string(),
463 row_connection_style: RowConnectionStyle::Full,
464 }
465 }
466}
467
468#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
469#[serde(default, deny_unknown_fields)]
470#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
471pub struct DisplayConfig {
472 #[partial(recurse)]
473 pub border: BorderSetting,
474
475 #[serde(deserialize_with = "camelcase_normalized")]
476 pub fg: Color,
477 pub modifier: Modifier,
479
480 pub match_indent: bool,
482 pub wrap: bool,
484
485 pub content: Option<StringOrVec>,
487
488 #[serde(deserialize_with = "camelcase_normalized")]
497 pub row_connection_style: RowConnectionStyle,
498
499 #[partial(alias = "h")]
501 pub header_lines: usize,
502}
503
504impl Default for DisplayConfig {
505 fn default() -> Self {
506 DisplayConfig {
507 border: Default::default(),
508 match_indent: true,
509 fg: Color::Green,
510 wrap: false,
511 row_connection_style: Default::default(),
512 modifier: Modifier::ITALIC, content: None,
514 header_lines: 0,
515 }
516 }
517}
518
519#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
535#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
536#[serde(default)]
537pub struct PreviewConfig {
538 #[partial(recurse)]
539 pub border: BorderSetting,
540 #[partial(recurse, set = "recurse")]
541 #[partial(alias = "l")]
542 pub layout: Vec<PreviewSetting>,
543 #[partial(recurse)]
544 #[serde(flatten)]
545 pub scroll: PreviewScrollSetting,
546 #[partial(alias = "c")]
548 #[serde(alias = "cycle")]
549 pub scroll_wrap: bool,
550 pub wrap: bool,
551 pub show: ShowCondition,
554
555 pub reevaluate_show_on_resize: bool,
556}
557
558impl Default for PreviewConfig {
559 fn default() -> Self {
560 PreviewConfig {
561 border: BorderSetting {
562 padding: Padding(ratatui::widgets::Padding::left(2)),
563 ..Default::default()
564 },
565 scroll: Default::default(),
566 layout: Default::default(),
567 scroll_wrap: true,
568 wrap: Default::default(),
569 show: Default::default(),
570 reevaluate_show_on_resize: false,
571 }
572 }
573}
574
575#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
577#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
578#[serde(default, deny_unknown_fields)]
579pub struct PreviewScrollSetting {
580 pub index: Option<String>,
583 #[partial(alias = "o")]
585 pub offset: isize,
586 #[partial(alias = "p")]
588 pub percentage: Percentage,
589 #[partial(alias = "h")]
591 pub header_lines: usize,
592}
593
594impl Default for PreviewScrollSetting {
595 fn default() -> Self {
596 Self {
597 index: Default::default(),
598 offset: -1,
599 percentage: Default::default(),
600 header_lines: Default::default(),
601 }
602 }
603}
604
605#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
606#[serde(default, deny_unknown_fields)]
607pub struct PreviewerConfig {
608 pub try_lossy: bool,
609
610 pub cache: u8,
612
613 pub help_colors: HelpColorConfig,
614}
615
616#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
618pub struct HelpColorConfig {
619 #[serde(deserialize_with = "camelcase_normalized")]
620 pub section: Color,
621 #[serde(deserialize_with = "camelcase_normalized")]
622 pub key: Color,
623 #[serde(deserialize_with = "camelcase_normalized")]
624 pub value: Color,
625}
626
627impl Default for HelpColorConfig {
628 fn default() -> Self {
629 Self {
630 section: Color::Blue,
631 key: Color::Green,
632 value: Color::White,
633 }
634 }
635}
636
637#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)]
640#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
641#[serde(default, deny_unknown_fields)]
642pub struct BorderSetting {
643 #[serde(deserialize_with = "camelcase_normalized_option")]
644 pub r#type: Option<BorderType>,
645 #[serde(deserialize_with = "camelcase_normalized")]
646 pub color: Color,
647 pub sides: Option<Borders>,
656 pub padding: Padding,
664 pub title: String,
665 pub title_modifier: Modifier,
667 pub modifier: Modifier,
668 #[serde(deserialize_with = "camelcase_normalized")]
669 pub bg: Color,
670}
671
672impl BorderSetting {
673 pub fn as_block(&self) -> ratatui::widgets::Block<'_> {
674 let mut ret = ratatui::widgets::Block::default()
675 .padding(self.padding.0)
676 .style(Style::default().bg(self.bg).add_modifier(self.modifier));
677
678 if !self.title.is_empty() {
679 let title = Span::styled(
680 &self.title,
681 Style::default().add_modifier(self.title_modifier),
682 );
683
684 ret = ret.title(title)
685 };
686
687 if !self.is_empty() {
688 ret = ret
689 .borders(self.sides())
690 .border_type(self.r#type.unwrap_or_default())
691 .border_style(ratatui::style::Style::default().fg(self.color))
692 }
693
694 ret
695 }
696
697 pub fn sides(&self) -> Borders {
698 if let Some(s) = self.sides {
699 s
700 } else if self.color != Default::default() || self.r#type != Default::default() {
701 Borders::ALL
702 } else {
703 Borders::NONE
704 }
705 }
706
707 pub fn as_static_block(&self) -> ratatui::widgets::Block<'static> {
708 let mut ret = ratatui::widgets::Block::default()
709 .padding(self.padding.0)
710 .style(Style::default().bg(self.bg).add_modifier(self.modifier));
711
712 if !self.title.is_empty() {
713 let title: Span<'static> = Span::styled(
714 self.title.clone(),
715 Style::default().add_modifier(self.title_modifier),
716 );
717
718 ret = ret.title(title)
719 };
720
721 if !self.is_empty() {
722 ret = ret
723 .borders(self.sides())
724 .border_type(self.r#type.unwrap_or_default())
725 .border_style(ratatui::style::Style::default().fg(self.color))
726 }
727
728 ret
729 }
730
731 pub fn is_empty(&self) -> bool {
732 self.sides() == Borders::NONE
733 }
734
735 pub fn height(&self) -> u16 {
736 let mut height = 0;
737 height += 2 * !self.is_empty() as u16;
738 height += self.padding.top + self.padding.bottom;
739 height += (!self.title.is_empty() as u16).saturating_sub(!self.is_empty() as u16);
740
741 height
742 }
743
744 pub fn width(&self) -> u16 {
745 let mut width = 0;
746 width += 2 * !self.is_empty() as u16;
747 width += self.padding.left + self.padding.right;
748
749 width
750 }
751
752 pub fn left(&self) -> u16 {
753 let mut width = 0;
754 width += !self.is_empty() as u16;
755 width += self.padding.left;
756
757 width
758 }
759
760 pub fn top(&self) -> u16 {
761 let mut height = 0;
762 height += !self.is_empty() as u16;
763 height += self.padding.top;
764 height += (!self.title.is_empty() as u16).saturating_sub(!self.is_empty() as u16);
765
766 height
767 }
768}
769
770#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
772#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
773pub struct TerminalLayoutSettings {
774 #[partial(alias = "p")]
776 pub percentage: Percentage,
777 pub min: u16,
778 pub max: u16, }
780
781impl Default for TerminalLayoutSettings {
782 fn default() -> Self {
783 Self {
784 percentage: Percentage::new(50),
785 min: 10,
786 max: 120,
787 }
788 }
789}
790
791#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
792#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
793pub struct PreviewSetting {
794 #[serde(flatten)]
795 #[partial(recurse)]
796 pub layout: PreviewLayout,
797 #[partial(recurse)]
798 pub border: Option<BorderSetting>,
799 #[serde(default, alias = "cmd", alias = "x")]
800 pub command: String,
801}
802
803#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
804#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
805pub struct PreviewLayout {
806 pub side: Side,
807 #[serde(alias = "p")]
809 pub percentage: Percentage,
811 pub min: i16,
812 pub max: i16,
813}
814
815impl Default for PreviewLayout {
816 fn default() -> Self {
817 Self {
818 side: Side::Right,
819 percentage: Percentage::new(60),
820 min: 30,
821 max: 120,
822 }
823 }
824}
825
826use crate::utils::serde::bounded_usize;
827#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
828#[serde(default, deny_unknown_fields)]
829#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
830pub struct ColumnsConfig {
831 #[partial(alias = "s")]
833 pub split: Split,
834 #[partial(alias = "n")]
836 pub names: Vec<ColumnSetting>,
837 #[serde(deserialize_with = "bounded_usize::<_, 1, {crate::MAX_SPLITS}>")]
839 #[partial(alias = "mc")]
840 max_columns: usize,
841}
842
843impl ColumnsConfig {
844 pub fn max_cols(&self) -> usize {
845 self.max_columns.min(MAX_SPLITS).max(1)
846 }
847}
848
849impl Default for ColumnsConfig {
850 fn default() -> Self {
851 Self {
852 split: Default::default(),
853 names: Default::default(),
854 max_columns: 6,
855 }
856 }
857}
858
859#[derive(Debug, Clone, PartialEq)]
861pub struct NucleoMatcherConfig(pub nucleo::Config);
862
863impl Default for NucleoMatcherConfig {
864 fn default() -> Self {
865 Self(nucleo::Config::DEFAULT)
866 }
867}
868
869#[derive(Debug, Clone, Serialize, Deserialize)]
870#[serde(default)]
871#[derive(Default)]
872struct MatcherConfigHelper {
873 pub normalize: Option<bool>,
874 pub ignore_case: Option<bool>,
875 pub prefer_prefix: Option<bool>,
876}
877
878impl serde::Serialize for NucleoMatcherConfig {
879 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
880 where
881 S: serde::Serializer,
882 {
883 let helper = MatcherConfigHelper {
884 normalize: Some(self.0.normalize),
885 ignore_case: Some(self.0.ignore_case),
886 prefer_prefix: Some(self.0.prefer_prefix),
887 };
888 helper.serialize(serializer)
889 }
890}
891
892impl<'de> Deserialize<'de> for NucleoMatcherConfig {
893 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
894 where
895 D: serde::Deserializer<'de>,
896 {
897 let helper = MatcherConfigHelper::deserialize(deserializer)?;
898 let mut config = nucleo::Config::DEFAULT;
899
900 if let Some(norm) = helper.normalize {
901 config.normalize = norm;
902 }
903 if let Some(ic) = helper.ignore_case {
904 config.ignore_case = ic;
905 }
906 if let Some(pp) = helper.prefer_prefix {
907 config.prefer_prefix = pp;
908 }
909
910 Ok(NucleoMatcherConfig(config))
911 }
912}