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 cli_boilerplate_automation::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
53 #[partial(alias = "r")]
55 pub raw: bool,
56 pub track: bool,
58 pub reverse: bool, }
61
62#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
65#[serde(default, deny_unknown_fields)]
66#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
67pub struct StartConfig {
68 #[serde(deserialize_with = "escaped_opt_char")]
69 #[partial(alias = "is")]
70 pub input_separator: Option<char>,
71 #[serde(deserialize_with = "escaped_opt_string")]
72 #[partial(alias = "os")]
73 pub output_separator: Option<String>,
74
75 #[partial(alias = "ot")]
77 #[serde(alias = "output")]
78 pub output_template: Option<String>,
79
80 #[partial(alias = "cmd", alias = "x")]
82 pub command: String,
83 pub sync: bool,
84
85 #[partial(alias = "a")]
87 pub ansi: bool,
88 #[partial(alias = "t")]
90 pub trim: bool,
91}
92
93#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
95#[serde(default, deny_unknown_fields)]
96#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
97pub struct ExitConfig {
98 pub select_1: bool,
100 pub allow_empty: bool,
102 pub abort_empty: bool,
104 pub last_key_path: Option<std::path::PathBuf>,
107}
108
109#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
111#[serde(default, deny_unknown_fields)]
112#[partial(recurse, path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
113pub struct RenderConfig {
114 pub ui: UiConfig,
116 #[partial(alias = "i")]
118 pub input: InputConfig,
119 #[partial(alias = "r")]
121 pub results: ResultsConfig,
122
123 pub status: StatusConfig,
125 #[partial(alias = "p")]
127 pub preview: PreviewConfig,
128 #[partial(alias = "f")]
129 pub footer: DisplayConfig,
130 #[partial(alias = "h")]
131 pub header: DisplayConfig,
132}
133
134impl RenderConfig {
135 pub fn tick_rate(&self) -> u8 {
136 self.ui.tick_rate
137 }
138}
139
140#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
142#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143#[serde(default, deny_unknown_fields)]
144pub struct TerminalConfig {
145 pub stream: IoStream, pub restore_fullscreen: bool,
147 pub redraw_on_resize: bool,
148 pub extended_keys: bool,
150 #[serde(with = "serde_duration_ms")]
151 pub sleep_ms: std::time::Duration, #[serde(flatten)]
153 #[partial(recurse)]
154 pub layout: Option<TerminalLayoutSettings>, pub clear_on_exit: bool,
156}
157
158impl Default for TerminalConfig {
159 fn default() -> Self {
160 Self {
161 stream: IoStream::default(),
162 restore_fullscreen: true,
163 redraw_on_resize: bool::default(),
164 sleep_ms: std::time::Duration::default(),
165 layout: Option::default(),
166 extended_keys: true,
167 clear_on_exit: true,
168 }
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
174#[serde(default, deny_unknown_fields)]
175#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
176pub struct UiConfig {
177 #[partial(recurse)]
178 pub border: BorderSetting,
179 pub tick_rate: u8, }
181
182impl Default for UiConfig {
183 fn default() -> Self {
184 Self {
185 border: Default::default(),
186 tick_rate: 60,
187 }
188 }
189}
190
191#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193#[serde(default, deny_unknown_fields)]
194#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
195pub struct InputConfig {
196 #[partial(recurse)]
197 pub border: BorderSetting,
198
199 #[serde(deserialize_with = "camelcase_normalized")]
201 pub fg: Color,
202 pub modifier: Modifier,
204
205 #[serde(deserialize_with = "camelcase_normalized")]
206 pub prompt_fg: Color,
207 pub prompt_modifier: Modifier,
209
210 #[serde(deserialize_with = "deserialize_string_or_char_as_double_width")]
212 pub prompt: String,
213 pub cursor: CursorSetting,
215
216 #[partial(alias = "i")]
218 pub initial: String,
219
220 pub scroll_padding: bool,
222}
223
224impl Default for InputConfig {
225 fn default() -> Self {
226 Self {
227 border: Default::default(),
228 fg: Default::default(),
229 modifier: Default::default(),
230 prompt_fg: Default::default(),
231 prompt_modifier: Default::default(),
232 prompt: "> ".to_string(),
233 cursor: Default::default(),
234 initial: Default::default(),
235
236 scroll_padding: true,
237 }
238 }
239}
240
241#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
242#[serde(default, deny_unknown_fields)]
243#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
244pub struct OverlayConfig {
245 #[partial(recurse)]
246 pub border: BorderSetting,
247 pub outer_dim: bool,
248 pub layout: OverlayLayoutSettings,
249}
250
251#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
252#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
253pub struct OverlayLayoutSettings {
254 #[partial(alias = "p")]
256 pub percentage: [Percentage; 2],
257 pub min: [u16; 2],
259 pub max: [u16; 2],
261
262 pub y_offset: Percentage,
264}
265
266impl Default for OverlayLayoutSettings {
267 fn default() -> Self {
268 Self {
269 percentage: [Percentage::new(60), Percentage::new(30)],
270 min: [10, 10],
271 max: [200, 30],
272 y_offset: Percentage::new(55),
273 }
274 }
275}
276
277#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
280#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
281#[serde(default, deny_unknown_fields)]
282pub struct ResultsConfig {
283 #[partial(recurse)]
284 pub border: BorderSetting,
285
286 #[serde(deserialize_with = "deserialize_string_or_char_as_double_width")]
288 pub multi_prefix: String,
289 pub default_prefix: String,
290
291 pub multi: bool,
293
294 #[serde(deserialize_with = "camelcase_normalized")]
296 pub fg: Color,
297 #[serde(deserialize_with = "camelcase_normalized")]
298 pub bg: Color,
299 pub modifier: Modifier,
301
302 #[serde(deserialize_with = "camelcase_normalized")]
304 pub inactive_fg: Color,
305 #[serde(deserialize_with = "camelcase_normalized")]
306 pub inactive_bg: Color,
307 pub inactive_modifier: Modifier,
309
310 #[serde(deserialize_with = "camelcase_normalized")]
311 pub match_fg: Color,
312 pub match_modifier: Modifier,
314
315 #[serde(deserialize_with = "camelcase_normalized")]
317 pub current_fg: Color,
318 #[serde(deserialize_with = "camelcase_normalized")]
320 pub current_bg: Color,
321 pub current_modifier: Modifier,
324
325 #[serde(deserialize_with = "camelcase_normalized")]
327 pub row_connection_style: RowConnectionStyle,
328
329 #[partial(alias = "c")]
335 #[serde(alias = "cycle")]
336 pub scroll_wrap: bool,
337 #[partial(alias = "sp")]
338 pub scroll_padding: u16,
339 #[partial(alias = "r")]
340 pub reverse: Option<bool>,
341
342 #[partial(alias = "w")]
344 pub wrap: bool,
345 pub min_wrap_width: u16,
346
347 pub column_spacing: Count,
351 pub current_prefix: String,
352 pub match_start_context: Option<usize>,
353
354 #[partial(alias = "ra")]
356 pub right_align_last: bool,
357
358 #[partial(alias = "v")]
359 #[serde(alias = "vertical")]
360 pub stacked_columns: bool,
361
362 #[serde(alias = "hr")]
363 #[serde(deserialize_with = "camelcase_normalized")]
364 pub horizontal_separator: HorizontalSeparator,
365}
366
367impl Default for ResultsConfig {
368 fn default() -> Self {
369 ResultsConfig {
370 border: Default::default(),
371
372 multi_prefix: "▌ ".to_string(),
373 default_prefix: Default::default(),
374 multi: true,
375
376 fg: Default::default(),
377 modifier: Default::default(),
378 bg: Default::default(),
379
380 inactive_fg: Default::default(),
381 inactive_modifier: Default::default(),
382 inactive_bg: Default::default(),
383
384 match_fg: Color::Green,
385 match_modifier: Modifier::ITALIC,
386
387 current_fg: Default::default(),
388 current_bg: Color::Black,
389 current_modifier: Modifier::BOLD,
390 row_connection_style: RowConnectionStyle::Disjoint,
391
392 scroll_wrap: true,
393 scroll_padding: 2,
394 reverse: Default::default(),
395
396 wrap: Default::default(),
397 min_wrap_width: 6,
398 match_start_context: Some(4),
399
400 column_spacing: Default::default(),
401 current_prefix: Default::default(),
402 right_align_last: false,
403 stacked_columns: false,
404 horizontal_separator: Default::default(),
405 }
406 }
407}
408
409#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
410#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
411#[serde(default, deny_unknown_fields)]
412pub struct StatusConfig {
413 #[serde(deserialize_with = "camelcase_normalized")]
414 pub fg: Color,
415 pub modifier: Modifier,
417 pub show: bool,
419 pub match_indent: bool,
421
422 #[partial(alias = "t")]
428 pub template: String,
429
430 pub row_connection_style: RowConnectionStyle,
434}
435impl Default for StatusConfig {
436 fn default() -> Self {
437 Self {
438 fg: Color::Green,
439 modifier: Modifier::ITALIC,
440 show: true,
441 match_indent: true,
442 template: r#"\m/\t"#.to_string(),
443 row_connection_style: RowConnectionStyle::Full,
444 }
445 }
446}
447
448#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
449#[serde(default, deny_unknown_fields)]
450#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
451pub struct DisplayConfig {
452 #[partial(recurse)]
453 pub border: BorderSetting,
454
455 #[serde(deserialize_with = "camelcase_normalized")]
456 pub fg: Color,
457 pub modifier: Modifier,
459
460 pub match_indent: bool,
462 pub wrap: bool,
464
465 pub content: Option<StringOrVec>,
467
468 #[serde(deserialize_with = "camelcase_normalized")]
477 pub row_connection_style: RowConnectionStyle,
478
479 #[partial(alias = "h")]
484 pub header_lines: usize,
485}
486
487impl Default for DisplayConfig {
488 fn default() -> Self {
489 DisplayConfig {
490 border: Default::default(),
491 match_indent: true,
492 fg: Color::Green,
493 wrap: false,
494 row_connection_style: Default::default(),
495 modifier: Modifier::ITALIC, content: None,
497 header_lines: 0,
498 }
499 }
500}
501
502#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
518#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
519#[serde(default)]
520pub struct PreviewConfig {
521 #[partial(recurse)]
522 pub border: BorderSetting,
523 #[partial(recurse, set = "recurse")]
524 #[partial(alias = "l")]
525 pub layout: Vec<PreviewSetting>,
526 #[partial(recurse)]
527 #[serde(flatten)]
528 pub scroll: PreviewScrollSetting,
529 #[partial(alias = "c")]
531 #[serde(alias = "cycle")]
532 pub scroll_wrap: bool,
533 pub wrap: bool,
534 pub show: ShowCondition,
537
538 pub reevaluate_show_on_resize: bool,
539}
540
541impl Default for PreviewConfig {
542 fn default() -> Self {
543 PreviewConfig {
544 border: BorderSetting {
545 padding: Padding(ratatui::widgets::Padding::left(2)),
546 ..Default::default()
547 },
548 scroll: Default::default(),
549 layout: Default::default(),
550 scroll_wrap: true,
551 wrap: Default::default(),
552 show: Default::default(),
553 reevaluate_show_on_resize: false,
554 }
555 }
556}
557
558#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
560#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
561#[serde(default, deny_unknown_fields)]
562pub struct PreviewScrollSetting {
563 pub index: Option<String>,
566 #[partial(alias = "o")]
568 pub offset: isize,
569 #[partial(alias = "p")]
571 pub percentage: Percentage,
572 #[partial(alias = "h")]
574 pub header_lines: usize,
575}
576
577impl Default for PreviewScrollSetting {
578 fn default() -> Self {
579 Self {
580 index: Default::default(),
581 offset: -1,
582 percentage: Default::default(),
583 header_lines: Default::default(),
584 }
585 }
586}
587
588#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
589#[serde(default, deny_unknown_fields)]
590pub struct PreviewerConfig {
591 pub try_lossy: bool,
592
593 pub cache: u8,
595
596 pub help_colors: TomlColorConfig,
597}
598
599#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
601pub struct TomlColorConfig {
602 #[serde(deserialize_with = "camelcase_normalized")]
603 pub section: Color,
604 #[serde(deserialize_with = "camelcase_normalized")]
605 pub key: Color,
606 #[serde(deserialize_with = "camelcase_normalized")]
607 pub string: Color,
608 #[serde(deserialize_with = "camelcase_normalized")]
609 pub number: Color,
610 pub section_bold: bool,
611}
612
613impl Default for TomlColorConfig {
614 fn default() -> Self {
615 Self {
616 section: Color::Blue,
617 key: Color::Yellow,
618 string: Color::Green,
619 number: Color::Cyan,
620 section_bold: true,
621 }
622 }
623}
624
625#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)]
628#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
629#[serde(default, deny_unknown_fields)]
630pub struct BorderSetting {
631 #[serde(deserialize_with = "camelcase_normalized_option")]
632 pub r#type: Option<BorderType>,
633 #[serde(deserialize_with = "camelcase_normalized")]
634 pub color: Color,
635 pub sides: Option<Borders>,
644 pub padding: Padding,
652 pub title: String,
653 pub title_modifier: Modifier,
655 pub modifier: Modifier,
656 #[serde(deserialize_with = "camelcase_normalized")]
657 pub bg: Color,
658}
659
660impl BorderSetting {
661 pub fn as_block(&self) -> ratatui::widgets::Block<'_> {
662 let mut ret = ratatui::widgets::Block::default()
663 .padding(self.padding.0)
664 .style(Style::default().bg(self.bg).add_modifier(self.modifier));
665
666 if !self.title.is_empty() {
667 let title = Span::styled(
668 &self.title,
669 Style::default().add_modifier(self.title_modifier),
670 );
671
672 ret = ret.title(title)
673 };
674
675 if !self.is_empty() {
676 ret = ret
677 .borders(self.sides())
678 .border_type(self.r#type.unwrap_or_default())
679 .border_style(ratatui::style::Style::default().fg(self.color))
680 }
681
682 ret
683 }
684
685 pub fn sides(&self) -> Borders {
686 if let Some(s) = self.sides {
687 s
688 } else if self.color != Default::default() || self.r#type != Default::default() {
689 Borders::ALL
690 } else {
691 Borders::NONE
692 }
693 }
694
695 pub fn as_static_block(&self) -> ratatui::widgets::Block<'static> {
696 let mut ret = ratatui::widgets::Block::default()
697 .padding(self.padding.0)
698 .style(Style::default().bg(self.bg).add_modifier(self.modifier));
699
700 if !self.title.is_empty() {
701 let title: Span<'static> = Span::styled(
702 self.title.clone(),
703 Style::default().add_modifier(self.title_modifier),
704 );
705
706 ret = ret.title(title)
707 };
708
709 if !self.is_empty() {
710 ret = ret
711 .borders(self.sides())
712 .border_type(self.r#type.unwrap_or_default())
713 .border_style(ratatui::style::Style::default().fg(self.color))
714 }
715
716 ret
717 }
718
719 pub fn is_empty(&self) -> bool {
720 self.sides() == Borders::NONE
721 }
722
723 pub fn height(&self) -> u16 {
724 let mut height = 0;
725 height += 2 * !self.is_empty() as u16;
726 height += self.padding.top + self.padding.bottom;
727 height += (!self.title.is_empty() as u16).saturating_sub(!self.is_empty() as u16);
728
729 height
730 }
731
732 pub fn width(&self) -> u16 {
733 let mut width = 0;
734 width += 2 * !self.is_empty() as u16;
735 width += self.padding.left + self.padding.right;
736
737 width
738 }
739
740 pub fn left(&self) -> u16 {
741 let mut width = 0;
742 width += !self.is_empty() as u16;
743 width += self.padding.left;
744
745 width
746 }
747
748 pub fn top(&self) -> u16 {
749 let mut height = 0;
750 height += !self.is_empty() as u16;
751 height += self.padding.top;
752 height += (!self.title.is_empty() as u16).saturating_sub(!self.is_empty() as u16);
753
754 height
755 }
756}
757
758#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
760#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
761pub struct TerminalLayoutSettings {
762 #[partial(alias = "p")]
764 pub percentage: Percentage,
765 pub min: u16,
766 pub max: u16, }
768
769impl Default for TerminalLayoutSettings {
770 fn default() -> Self {
771 Self {
772 percentage: Percentage::new(50),
773 min: 10,
774 max: 120,
775 }
776 }
777}
778
779#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
780#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
781pub struct PreviewSetting {
782 #[serde(flatten)]
783 #[partial(recurse)]
784 pub layout: PreviewLayout,
785 #[partial(recurse)]
786 pub border: Option<BorderSetting>,
787 #[serde(default, alias = "cmd", alias = "x")]
788 pub command: String,
789}
790
791#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
792#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
793pub struct PreviewLayout {
794 pub side: Side,
795 #[serde(alias = "p")]
797 pub percentage: Percentage,
799 pub min: i16,
800 pub max: i16,
801}
802
803impl Default for PreviewLayout {
804 fn default() -> Self {
805 Self {
806 side: Side::Right,
807 percentage: Percentage::new(60),
808 min: 30,
809 max: 120,
810 }
811 }
812}
813
814use crate::utils::serde::bounded_usize;
815#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
816#[serde(default, deny_unknown_fields)]
817#[partial(path, derive(Debug, Clone, PartialEq, Deserialize, Serialize))]
818pub struct ColumnsConfig {
819 #[partial(alias = "s")]
821 pub split: Split,
822 #[partial(alias = "n")]
824 pub names: Vec<ColumnSetting>,
825 #[serde(deserialize_with = "bounded_usize::<_, 1, {crate::MAX_SPLITS}>")]
827 max_columns: usize,
828}
829
830impl ColumnsConfig {
831 pub fn max_cols(&self) -> usize {
832 self.max_columns.min(MAX_SPLITS).max(1)
833 }
834}
835
836impl Default for ColumnsConfig {
837 fn default() -> Self {
838 Self {
839 split: Default::default(),
840 names: Default::default(),
841 max_columns: 6,
842 }
843 }
844}
845
846#[derive(Debug, Clone, PartialEq)]
848pub struct NucleoMatcherConfig(pub nucleo::Config);
849
850impl Default for NucleoMatcherConfig {
851 fn default() -> Self {
852 Self(nucleo::Config::DEFAULT)
853 }
854}
855
856#[derive(Debug, Clone, Serialize, Deserialize)]
857#[serde(default)]
858#[derive(Default)]
859struct MatcherConfigHelper {
860 pub normalize: Option<bool>,
861 pub ignore_case: Option<bool>,
862 pub prefer_prefix: Option<bool>,
863}
864
865impl serde::Serialize for NucleoMatcherConfig {
866 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
867 where
868 S: serde::Serializer,
869 {
870 let helper = MatcherConfigHelper {
871 normalize: Some(self.0.normalize),
872 ignore_case: Some(self.0.ignore_case),
873 prefer_prefix: Some(self.0.prefer_prefix),
874 };
875 helper.serialize(serializer)
876 }
877}
878
879impl<'de> Deserialize<'de> for NucleoMatcherConfig {
880 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
881 where
882 D: serde::Deserializer<'de>,
883 {
884 let helper = MatcherConfigHelper::deserialize(deserializer)?;
885 let mut config = nucleo::Config::DEFAULT;
886
887 if let Some(norm) = helper.normalize {
888 config.normalize = norm;
889 }
890 if let Some(ic) = helper.ignore_case {
891 config.ignore_case = ic;
892 }
893 if let Some(pp) = helper.prefer_prefix {
894 config.prefer_prefix = pp;
895 }
896
897 Ok(NucleoMatcherConfig(config))
898 }
899}