1use std::{fmt, ops::Deref};
2
3use crate::utils::text::parse_escapes;
4
5use crate::{Result, action::{Count}, binds::BindMap, tui::IoStream};
6use ratatui::style::Style;
7use ratatui::text::{Span};
8use ratatui::{
9 style::{Color, Modifier}, widgets::{BorderType, Borders, Padding}
10};
11use regex::Regex;
12use serde::{Serialize, Serializer};
13use serde::de::IntoDeserializer;
14use serde::ser::SerializeSeq;
15use serde::{Deserialize, Deserializer, de::{self, Visitor}};
16
17#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
20#[serde(default, deny_unknown_fields)]
21pub struct Config {
22 #[serde(flatten)]
24 pub render: RenderConfig,
25
26 pub binds: BindMap,
28
29 pub tui: TerminalConfig,
30}
31
32#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
33#[serde(default)]
34pub struct MainConfig {
35 #[serde(flatten)]
36 pub config: Config,
37
38 pub previewer: PreviewerConfig,
39
40 pub matcher: MatcherConfig,
42}
43
44#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
46pub struct MatcherConfig {
47 #[serde(flatten)]
48 pub matcher: NucleoMatcherConfig,
49 #[serde(flatten)]
50 pub mm: MMConfig,
51 #[serde(flatten)]
53 pub run: StartConfig,
54
55 #[serde(default)]
56 pub help_colors: TomlColorConfig
57}
58
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
60pub struct TomlColorConfig {
61 pub section: Color,
62 pub key: Color,
63 pub string: Color,
64 pub number: Color,
65 pub section_bold: bool,
66}
67
68impl Default for TomlColorConfig {
69 fn default() -> Self {
70 Self {
71 section: Color::Blue,
72 key: Color::Yellow,
73 string: Color::Green,
74 number: Color::Cyan,
75 section_bold: true,
76 }
77 }
78}
79
80#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
81#[serde(default)]
82pub struct MMConfig {
83 pub columns: ColumnsConfig,
84 #[serde(flatten)]
85 pub exit: ExitConfig,
86 pub trim: bool, pub format: FormatString, }
89
90#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
91#[serde(default)]
92pub struct StartConfig {
93 #[serde(default, deserialize_with = "parse_escaped_char_opt")]
94 pub input_separator: Option<char>,
95 #[serde(default, deserialize_with = "parse_escaped_opt")]
96 pub output_separator: Option<String>,
97 pub default_command: String,
98 pub sync: bool
99}
100
101
102#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
104#[serde(default)]
105pub struct ExitConfig {
106 pub select_1: bool,
107 pub allow_empty: bool,
108}
109
110#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
111#[serde(default, deny_unknown_fields)]
112pub struct RenderConfig {
113 pub ui: UiConfig,
114 pub input: InputConfig,
115 pub results: ResultsConfig,
116 pub preview: PreviewConfig,
117 pub footer: DisplayConfig,
118 pub header: DisplayConfig
119}
120
121impl RenderConfig {
122 pub fn tick_rate(&self) -> u8 {
123 self.ui.tick_rate
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
128#[serde(default, deny_unknown_fields)]
129pub struct UiConfig {
130 pub border: BorderSetting,
131 pub tick_rate: u8, }
133
134impl Default for UiConfig {
135 fn default() -> Self {
136 Self {
137 border: Default::default(),
138 tick_rate: 60,
139 }
140 }
141}
142#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
143#[serde(default, deny_unknown_fields)]
144pub struct TerminalConfig {
145 pub stream: IoStream,
146 pub sleep: u16, #[serde(flatten)]
149 pub layout: Option<TerminalLayoutSettings> }
151
152#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
153#[serde(default, deny_unknown_fields)]
154pub struct InputConfig {
155 pub border: BorderSetting,
156
157 pub fg: Color,
159 #[serde(deserialize_with = "deserialize_modifier", serialize_with = "serialize_modifier")]
160 pub modifier: Modifier,
161 #[serde(deserialize_with = "deserialize_string_or_char_as_double_width")]
164 pub prompt: String,
165 pub cursor: CursorSetting,
166 pub initial: String,
167}
168
169impl Default for InputConfig {
170 fn default() -> Self {
171 Self {
172 border: Default::default(),
173 fg: Default::default(),
174 modifier: Default::default(),
175 prompt: "> ".to_string(),
176 cursor: Default::default(),
177 initial: Default::default(),
178 }
179 }
180}
181
182#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
183#[serde(default, deny_unknown_fields)]
184pub struct ResultsConfig {
185 pub border: BorderSetting,
186
187 #[serde(deserialize_with = "deserialize_string_or_char_as_double_width")]
189 pub multi_prefix: String,
190 pub default_prefix: String,
191
192 pub fg: Color,
194 #[serde(deserialize_with = "deserialize_modifier", serialize_with = "serialize_modifier")]
195 pub modifier: Modifier,
196 #[serde(default = "default_green")]
197 pub match_fg: Color,
198 #[serde(deserialize_with = "deserialize_modifier", serialize_with = "serialize_modifier")]
199 pub match_modifier: Modifier,
200
201 pub current_fg: Color,
202 #[serde(default = "default_black")]
203 pub current_bg: Color,
204 #[serde(deserialize_with = "deserialize_modifier", serialize_with = "serialize_modifier", default = "default_bold")]
205 pub current_modifier: Modifier,
206
207 #[serde(default = "default_green")]
209 pub count_fg: Color,
210 #[serde(deserialize_with = "deserialize_modifier", serialize_with = "serialize_modifier", default = "default_italic")]
211 pub count_modifier: Modifier,
212
213 #[serde(default = "default_true")]
220 pub scroll_wrap: bool,
221 pub scroll_padding: u16,
222 #[serde(deserialize_with = "deserialize_option_auto")]
223 pub reverse: Option<bool>,
224
225 pub wrap: bool,
227 pub wrap_scaling_min_width: u8,
228
229 pub column_spacing: Count,
231 pub current_prefix: String,
232}
233
234impl Default for ResultsConfig {
235 fn default() -> Self {
236 ResultsConfig {
237 border: Default::default(),
238
239 multi_prefix: "▌ ".to_string(),
240 default_prefix: Default::default(),
241
242 fg: Default::default(),
243 modifier: Default::default(), match_fg: default_green(),
245 match_modifier: default_italic(),
246
247 current_fg: Default::default(),
248 current_bg: default_black(),
249 current_modifier: default_bold(),
250
251 count_fg: default_green(),
252 count_modifier: default_italic(),
253
254 scroll_wrap: default_true(),
255 scroll_padding: 2,
256 reverse: None,
257
258 wrap: Default::default(),
259 wrap_scaling_min_width: 5,
260
261 column_spacing: Default::default(),
262 current_prefix: Default::default(),
263 }
264 }
265}
266
267
268#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
269#[serde(default, deny_unknown_fields)]
270pub struct DisplayConfig {
271 pub border: BorderSetting,
272
273 #[serde(default = "default_green")]
274 pub fg: Color,
275 #[serde(deserialize_with = "deserialize_modifier", serialize_with = "serialize_modifier", default = "default_italic")]
276 pub modifier: Modifier,
277
278 #[serde(default = "default_true")]
279 pub match_indent: bool,
280
281 #[serde(deserialize_with = "deserialize_option_auto")]
282 pub content: Option<StringOrVec>,
283}
284
285impl Default for DisplayConfig {
286 fn default() -> Self {
287 DisplayConfig {
288 border: Default::default(),
289 match_indent: true,
290 fg: default_green(),
291 modifier: default_italic(), content: None,
293 }
294 }
295}
296
297
298#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
299#[serde(default, deny_unknown_fields)]
300pub struct PreviewConfig {
301 pub border: BorderSetting,
302
303 pub layout: Vec<PreviewSetting>,
304 #[serde(default = "default_true")]
305 pub scroll_wrap: bool,
306 pub wrap: bool,
307 pub show: bool,
308}
309
310impl Default for PreviewConfig {
311 fn default() -> Self {
312 PreviewConfig {
313 border: Default::default(),
314 layout: Default::default(),
315 scroll_wrap: default_true(),
316 wrap: Default::default(),
317 show: Default::default(),
318 }
319 }
320}
321
322#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
323#[serde(default, deny_unknown_fields)]
324pub struct PreviewerConfig {
325 pub try_lossy: bool,
326
327 pub cache: u8,
329}
330
331#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
335#[serde(transparent)]
336pub struct FormatString(String);
337
338impl Deref for FormatString {
339 type Target = str;
340
341 fn deref(&self) -> &Self::Target {
342 &self.0
343 }
344}
345
346#[derive(Default, Debug, Clone, PartialEq, Serialize)]
347#[serde(default, deny_unknown_fields)]
348pub struct BorderSetting {
349 #[serde(with = "serde_from_str")]
350 pub r#type: BorderType,
351 pub color: Color,
352 #[serde(serialize_with = "serialize_borders", deserialize_with = "deserialize_borders")]
353 pub sides: Borders,
354 #[serde(serialize_with = "serialize_padding", deserialize_with = "deserialize_padding")]
355 pub padding: Padding,
356 pub title: String,
357 #[serde(deserialize_with = "deserialize_modifier", serialize_with = "serialize_modifier")]
358 pub title_modifier: Modifier,
359 pub bg: Color,
360}
361
362impl BorderSetting {
363 pub fn as_block(&self) -> ratatui::widgets::Block<'_> {
364 let mut ret = ratatui::widgets::Block::default()
365 .padding(self.padding)
366 .style(Style::default().bg(self.bg));
367
368 if !self.title.is_empty() {
369 let title = Span::styled(&self.title, Style::default().add_modifier(self.title_modifier));
370
371 ret = ret.title(title)
372 };
373
374 if self.sides != Borders::NONE {
375 ret = ret.borders(self.sides)
376 .border_type(self.r#type)
377 .border_style(
378 ratatui::style::Style::default()
379 .fg(self.color)
380 )
381 }
382
383 ret
384 }
385
386 pub fn height(&self) -> u16 {
387 let mut height = 0;
388 height += 2 * !self.sides.is_empty() as u16;
389 height += self.padding.top + self.padding.bottom;
390 height += (!self.title.is_empty() as u16).saturating_sub(!self.sides.is_empty() as u16);
391
392 height
393 }
394
395 pub fn width(&self) -> u16 {
396 let mut width = 0;
397 width += 2 * !self.sides.is_empty() as u16;
398 width += self.padding.left + self.padding.right;
399
400 width
401 }
402
403 pub fn left(&self) -> u16 {
404 let mut width = 0;
405 width += !self.sides.is_empty() as u16;
406 width += self.padding.left;
407
408 width
409 }
410
411 pub fn top(&self) -> u16 {
412 let mut height = 0;
413 height += !self.sides.is_empty() as u16;
414 height += self.padding.top;
415 height += (!self.title.is_empty() as u16).saturating_sub(!self.sides.is_empty() as u16);
416
417 height
418 }
419}
420
421#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
423pub struct TerminalLayoutSettings {
424 pub percentage: Percentage,
425 pub min: u16,
426 pub max: u16, }
428
429impl Default for TerminalLayoutSettings {
430 fn default() -> Self {
431 Self {
432 percentage: Percentage(50),
433 min: 10,
434 max: 120
435 }
436 }
437}
438
439
440#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
441#[serde(rename_all = "lowercase")]
442pub enum Side {
443 Top,
444 Bottom,
445 Left,
446 #[default]
447 Right,
448}
449
450#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
451pub struct PreviewSetting {
452 #[serde(flatten)]
453 pub layout: PreviewLayoutSetting,
454 #[serde(default)]
455 pub command: String
456}
457
458#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
459pub struct PreviewLayoutSetting {
460 pub side: Side,
461 pub percentage: Percentage,
462 pub min: i16,
463 pub max: i16,
464}
465
466impl Default for PreviewLayoutSetting {
467 fn default() -> Self {
468 Self {
469 side: Side::Right,
470 percentage: Percentage(40),
471 min: 30,
472 max: 120
473 }
474 }
475}
476
477
478#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
479#[serde(rename_all = "lowercase")]
480pub enum CursorSetting {
481 None,
482 #[default]
483 Default,
484}
485
486
487#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
489#[serde(default, deny_unknown_fields)]
490pub struct ColumnsConfig {
491 pub split: Split,
492 pub names: Vec<ColumnSetting>,
493 pub max_columns: u8,
494}
495
496impl Default for ColumnsConfig {
497 fn default() -> Self {
498 Self {
499 split: Default::default(),
500 names: Default::default(),
501 max_columns: u8::MAX,
502 }
503 }
504}
505
506#[derive(Default, Debug, Clone, PartialEq)]
507pub struct ColumnSetting {
508 pub filter: bool,
509 pub hidden: bool,
510 pub name: String,
511}
512
513#[derive(Default, Debug, Clone)]
514pub enum Split {
515 Delimiter(Regex),
516 Regexes(Vec<Regex>),
517 #[default]
518 None
519}
520
521impl PartialEq for Split {
522 fn eq(&self, other: &Self) -> bool {
523 match (self, other) {
524 (Split::Delimiter(r1), Split::Delimiter(r2)) => r1.as_str() == r2.as_str(),
525 (Split::Regexes(v1), Split::Regexes(v2)) => {
526 if v1.len() != v2.len() { return false; }
527 v1.iter().zip(v2.iter()).all(|(r1, r2)| r1.as_str() == r2.as_str())
528 },
529 (Split::None, Split::None) => true,
530 _ => false,
531 }
532 }
533}
534
535
536#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
537#[serde(untagged)]
538pub enum StringOrVec {
539 String(String),
540 Vec(Vec<String>)
541}
542impl Default for StringOrVec {
543 fn default() -> Self {
544 StringOrVec::String(String::new())
545 }
546}
547
548
549pub mod serde_from_str {
552 use std::fmt::Display;
553 use std::str::FromStr;
554 use serde::{Deserialize, Deserializer, Serializer, de};
555
556 pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
557 where
558 T: Display,
559 S: Serializer,
560 {
561 serializer.serialize_str(&value.to_string())
562 }
563
564 pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
565 where
566 T: FromStr,
567 T::Err: Display,
568 D: Deserializer<'de>,
569 {
570 let s = String::deserialize(deserializer)?;
571 T::from_str(&s).map_err(de::Error::custom)
572 }
573}
574
575pub mod utils {
576 use crate::Result;
577 use crate::config::{MainConfig};
578 use std::borrow::Cow;
579 use std::path::{Path};
580
581 pub fn get_config(dir: &Path) -> Result<MainConfig> {
582 let config_path = dir.join("config.toml");
583
584 let config_content: Cow<'static, str> = if !config_path.exists() {
585 Cow::Borrowed(include_str!("../assets/config.toml"))
586 } else {
587 Cow::Owned(std::fs::read_to_string(config_path)?)
588 };
589
590 let config: MainConfig = toml::from_str(&config_content)?;
591
592 Ok(config)
593 }
594
595 pub fn write_config(dir: &Path) -> Result<()> {
596 let config_path = dir.join("config.toml");
597
598 let default_config_content = include_str!("../assets/config.toml");
599 let parent_dir = config_path.parent().unwrap();
600 std::fs::create_dir_all(parent_dir)?;
601 std::fs::write(&config_path, default_config_content)?;
602
603 println!("Config written to: {}", config_path.display());
604 Ok(())
605 }
606
607 #[cfg(debug_assertions)]
608 pub fn write_config_dev(dir: &Path) -> Result<()> {
609 let config_path = dir.join("config.toml");
610
611 let default_config_content = include_str!("../assets/dev.toml");
612 let parent_dir = config_path.parent().unwrap();
613 std::fs::create_dir_all(parent_dir)?;
614 std::fs::write(&config_path, default_config_content)?;
615
616 Ok(())
617 }
618}
619
620
621
622pub fn serialize_borders<S>(borders: &Borders, serializer: S) -> Result<S::Ok, S::Error>
624where
625S: serde::Serializer,
626{
627 use serde::ser::SerializeSeq;
628 let mut seq = serializer.serialize_seq(None)?;
629 if borders.contains(Borders::TOP) {
630 seq.serialize_element("top")?;
631 }
632 if borders.contains(Borders::BOTTOM) {
633 seq.serialize_element("bottom")?;
634 }
635 if borders.contains(Borders::LEFT) {
636 seq.serialize_element("left")?;
637 }
638 if borders.contains(Borders::RIGHT) {
639 seq.serialize_element("right")?;
640 }
641 seq.end()
642}
643
644pub fn deserialize_borders<'de, D>(deserializer: D) -> Result<Borders, D::Error>
645where
646D: Deserializer<'de>,
647{
648 let input = StringOrVec::deserialize(deserializer)?;
649 let mut borders = Borders::NONE;
650
651 let borders = match input {
652 StringOrVec::String(s) => match s.as_str() {
653 "none" => Borders::NONE,
654 "all" => Borders::ALL,
655 other => {
656 return Err(de::Error::custom(format!(
657 "invalid border value '{}'",
658 other
659 )))
660 }
661 },
662 StringOrVec::Vec(list) => {
663 for item in list {
664 match item.as_str() {
665 "top" => borders |= Borders::TOP,
666 "bottom" => borders |= Borders::BOTTOM,
667 "left" => borders |= Borders::LEFT,
668 "right" => borders |= Borders::RIGHT,
669 "all" => borders |= Borders::ALL,
670 "none" => borders = Borders::NONE,
671 other => return Err(de::Error::custom(format!("invalid side '{}'", other))),
672 }
673 }
674 borders
675 }
676 };
677
678 Ok(borders)
679}
680
681pub fn deserialize_borders_option<'de, D>(deserializer: D) -> Result<Option<Borders>, D::Error>
682where
683D: Deserializer<'de>,
684{
685 let input = Option::<StringOrVec>::deserialize(deserializer)?;
686 match input {
687 Some(input) => {
688 let mut borders = Borders::NONE;
689
690 let borders = match input {
691 StringOrVec::String(s) => match s.as_str() {
692 "none" => Borders::NONE,
693 "all" => Borders::ALL,
694 other => {
695 return Err(de::Error::custom(format!(
696 "invalid border value '{}'",
697 other
698 )))
699 }
700 },
701 StringOrVec::Vec(list) => {
702 for item in list {
703 match item.as_str() {
704 "top" => borders |= Borders::TOP,
705 "bottom" => borders |= Borders::BOTTOM,
706 "left" => borders |= Borders::LEFT,
707 "right" => borders |= Borders::RIGHT,
708 "all" => borders |= Borders::ALL,
709 "none" => borders = Borders::NONE,
710 other => return Err(de::Error::custom(format!("invalid side '{}'", other))),
711 }
712 }
713 borders
714 }
715 };
716
717 Ok(Some(borders))
718 },
719 None => Ok(None),
720 }
721}
722
723pub fn deserialize_modifier<'de, D>(deserializer: D) -> Result<Modifier, D::Error>
724where
725D: Deserializer<'de>,
726{
727 let input = StringOrVec::deserialize(deserializer)?;
728 let mut modifier = Modifier::empty();
729
730 let add_modifier = |name: &str, m: &mut Modifier| -> Result<(), D::Error> {
731 match name.to_lowercase().as_str() {
732 "bold" => {
733 *m |= Modifier::BOLD;
734 Ok(())
735 }
736 "italic" => {
737 *m |= Modifier::ITALIC;
738 Ok(())
739 }
740 "underlined" => {
741 *m |= Modifier::UNDERLINED;
742 Ok(())
743 }
744 "none" => {
765 *m = Modifier::empty();
766 Ok(())
767 } other => Err(de::Error::custom(format!("invalid modifier '{}'", other))),
769 }
770 };
771
772 match input {
773 StringOrVec::String(s) => add_modifier(&s, &mut modifier)?,
774 StringOrVec::Vec(list) => {
775 for item in list {
776 add_modifier(&item, &mut modifier)?;
777 }
778 }
779 }
780
781 Ok(modifier)
782}
783
784pub fn serialize_modifier<S>(modifier: &Modifier, serializer: S) -> Result<S::Ok, S::Error>
785where
786 S: Serializer,
787{
788 let mut mods = Vec::new();
789
790 if modifier.contains(Modifier::BOLD) {
791 mods.push("bold");
792 }
793 if modifier.contains(Modifier::ITALIC) {
794 mods.push("italic");
795 }
796 if modifier.contains(Modifier::UNDERLINED) {
797 mods.push("underlined");
798 }
799 match mods.len() {
803 0 => serializer.serialize_str("none"),
804 1 => serializer.serialize_str(mods[0]),
805 _ => mods.serialize(serializer),
806 }
807}
808
809
810pub fn deserialize_string_or_char_as_double_width<'de, D, T>(deserializer: D) -> Result<T, D::Error>
811where
812D: Deserializer<'de>,
813T: From<String>,
814{
815 struct GenericVisitor<T> {
816 _marker: std::marker::PhantomData<T>,
817 }
818
819 impl<'de, T> Visitor<'de> for GenericVisitor<T>
820 where
821 T: From<String>,
822 {
823 type Value = T;
824
825 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
826 formatter.write_str("a string or single character")
827 }
828
829 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
830 where
831 E: de::Error,
832 {
833 let s = if v.chars().count() == 1 {
834 let mut s = String::with_capacity(2);
835 s.push(v.chars().next().unwrap());
836 s.push(' ');
837 s
838 } else {
839 v.to_string()
840 };
841 Ok(T::from(s))
842 }
843
844 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
845 where
846 E: de::Error,
847 {
848 self.visit_str(&v)
849 }
850 }
851
852 deserializer.deserialize_string(GenericVisitor { _marker: std::marker::PhantomData })
853}
854
855
856#[derive(Debug, Clone, PartialEq)]
858pub struct NucleoMatcherConfig(pub nucleo::Config);
859
860impl Default for NucleoMatcherConfig {
861 fn default() -> Self {
862 Self(nucleo::Config::DEFAULT)
863 }
864}
865
866#[derive(Debug, Clone, Serialize, Deserialize)]
867#[serde(default)]
868#[derive(Default)]
869struct MatcherConfigHelper {
870 pub normalize: Option<bool>,
871 pub ignore_case: Option<bool>,
872 pub prefer_prefix: Option<bool>,
873}
874
875
876impl serde::Serialize for NucleoMatcherConfig {
877 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
878 where
879 S: serde::Serializer,
880 {
881 let helper = MatcherConfigHelper {
882 normalize: Some(self.0.normalize),
883 ignore_case: Some(self.0.ignore_case),
884 prefer_prefix: Some(self.0.prefer_prefix),
885 };
886 helper.serialize(serializer)
887 }
888}
889
890impl<'de> Deserialize<'de> for NucleoMatcherConfig {
891 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
892 where
893 D: serde::Deserializer<'de>, {
894 let helper = MatcherConfigHelper::deserialize(deserializer)?;
895 let mut config = nucleo::Config::DEFAULT;
896
897 if let Some(norm) = helper.normalize {
898 config.normalize = norm;
899 }
900 if let Some(ic) = helper.ignore_case {
901 config.ignore_case = ic;
902 }
903 if let Some(pp) = helper.prefer_prefix {
904 config.prefer_prefix = pp;
905 }
906
907 Ok(NucleoMatcherConfig(config))
908 }
909}
910
911impl serde::Serialize for Split {
912 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
913 where
914 S: serde::Serializer,
915 {
916 match self {
917 Split::Delimiter(r) => serializer.serialize_str(r.as_str()),
918 Split::Regexes(rs) => {
919 let mut seq = serializer.serialize_seq(Some(rs.len()))?;
920 for r in rs {
921 seq.serialize_element(r.as_str())?;
922 }
923 seq.end()
924 }
925 Split::None => serializer.serialize_none(),
926 }
927 }
928}
929
930impl<'de> Deserialize<'de> for Split {
931 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
932 where
933 D: Deserializer<'de>, {
934 struct SplitVisitor;
935
936 impl<'de> Visitor<'de> for SplitVisitor {
937 type Value = Split;
938
939 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
940 formatter.write_str("string for delimiter or array of strings for regexes")
941 }
942
943 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
944 where
945 E: de::Error,
946 {
947 Regex::new(value)
949 .map(Split::Delimiter)
950 .map_err(|e| E::custom(format!("Invalid regex: {}", e)))
951 }
952
953 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
954 where
955 A: serde::de::SeqAccess<'de>,
956 {
957 let mut regexes = Vec::new();
958 while let Some(s) = seq.next_element::<String>()? {
959 let r = Regex::new(&s).map_err(|e| de::Error::custom(format!("Invalid regex: {}", e)))?;
960 regexes.push(r);
961 }
962 Ok(Split::Regexes(regexes))
963 }
964 }
965
966 deserializer.deserialize_any(SplitVisitor)
967 }
968}
969
970
971impl serde::Serialize for ColumnSetting {
972 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
973 where
974 S: serde::Serializer,
975 {
976 use serde::ser::SerializeStruct;
977 let mut state = serializer.serialize_struct("ColumnSetting", 3)?;
978 state.serialize_field("filter", &self.filter)?;
979 state.serialize_field("hidden", &self.hidden)?;
980 state.serialize_field("name", &self.name)?;
981 state.end()
982 }
983}
984
985impl<'de> Deserialize<'de> for ColumnSetting {
986 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
987 where
988 D: Deserializer<'de>, {
989 #[derive(Deserialize)]
990 #[serde(deny_unknown_fields)]
991 struct ColumnStruct {
992 #[serde(default = "default_true")]
993 filter: bool,
994 #[serde(default)]
995 hidden: bool,
996 name: String,
997 }
998
999 fn default_true() -> bool { true }
1000
1001 #[derive(Deserialize)]
1002 #[serde(untagged)]
1003 enum Input {
1004 Str(String),
1005 Obj(ColumnStruct),
1006 }
1007
1008 match Input::deserialize(deserializer)? {
1009 Input::Str(name) => Ok(ColumnSetting {
1010 filter: true,
1011 hidden: false,
1012 name,
1013 }),
1014 Input::Obj(obj) => Ok(ColumnSetting {
1015 filter: obj.filter,
1016 hidden: obj.hidden,
1017 name: obj.name,
1018 }),
1019 }
1020 }
1021}
1022
1023
1024#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1025#[serde(transparent)]
1026pub struct Percentage(u8);
1027
1028impl Percentage {
1029 pub fn new(value: u8) -> Option<Self> {
1030 if value <= 100 {
1031 Some(Self(value))
1032 } else {
1033 None
1034 }
1035 }
1036
1037 pub fn get(&self) -> u16 {
1038 self.0 as u16
1039 }
1040
1041 pub fn get_max(&self, total: u16, max: u16) -> u16 {
1042 let pct_height = (total * self.get()).div_ceil(100);
1043 let max_height = if max == 0 { total } else { max };
1044 pct_height.min(max_height)
1045 }
1046}
1047
1048
1049impl Deref for Percentage {
1050 type Target = u8;
1051
1052 fn deref(&self) -> &Self::Target {
1053 &self.0
1054 }
1055}
1056
1057impl fmt::Display for Percentage {
1058 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1059 write!(f, "{}%", self.0)
1060 }
1061}
1062
1063impl<'de> Deserialize<'de> for Percentage {
1064 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1065 where
1066 D: Deserializer<'de>,
1067 {
1068 let v = u8::deserialize(deserializer)?;
1069 if v <= 100 {
1070 Ok(Percentage(v))
1071 } else {
1072 Err(serde::de::Error::custom(format!("percentage out of range: {}", v)))
1073 }
1074 }
1075}
1076impl std::str::FromStr for Percentage {
1077 type Err = String;
1078
1079 fn from_str(s: &str) -> Result<Self, Self::Err> {
1080 let s = s.trim_end_matches('%'); let value: u8 = s
1082 .parse()
1083 .map_err(|e: std::num::ParseIntError| format!("Invalid number: {}", e))?;
1084 Self::new(value).ok_or_else(|| format!("Percentage out of range: {}", value))
1085 }
1086}
1087
1088
1089pub fn serialize_padding<S>(padding: &Padding, serializer: S) -> Result<S::Ok, S::Error>
1090where
1091S: serde::Serializer,
1092{
1093 use serde::ser::SerializeSeq;
1094 if padding.top == padding.bottom && padding.left == padding.right && padding.top == padding.left {
1095 serializer.serialize_u16(padding.top)
1096 } else if padding.top == padding.bottom && padding.left == padding.right {
1097 let mut seq = serializer.serialize_seq(Some(2))?;
1098 seq.serialize_element(&padding.left)?;
1099 seq.serialize_element(&padding.top)?;
1100 seq.end()
1101 } else {
1102 let mut seq = serializer.serialize_seq(Some(4))?;
1103 seq.serialize_element(&padding.top)?;
1104 seq.serialize_element(&padding.right)?;
1105 seq.serialize_element(&padding.bottom)?;
1106 seq.serialize_element(&padding.left)?;
1107 seq.end()
1108 }
1109}
1110
1111pub fn deserialize_padding<'de, D>(deserializer: D) -> Result<Padding, D::Error>
1112where
1113D: Deserializer<'de>,
1114{
1115 struct PaddingVisitor;
1116
1117 impl<'de> Visitor<'de> for PaddingVisitor {
1118 type Value = Padding;
1119
1120 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1121 formatter.write_str("a number or an array of 1, 2, or 4 numbers")
1122 }
1123
1124 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
1125 where
1126 E: de::Error,
1127 {
1128 let v = u16::try_from(value)
1129 .map_err(|_| E::custom(format!("padding value {} is out of range for u16", value)))?;
1130
1131 Ok(Padding {
1132 top: v,
1133 right: v,
1134 bottom: v,
1135 left: v,
1136 })
1137 }
1138
1139 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
1140 where
1141 E: de::Error,
1142 {
1143 let v = u16::try_from(value)
1144 .map_err(|_| E::custom(format!("padding value {} is out of range for u16", value)))?;
1145
1146 Ok(Padding {
1147 top: v,
1148 right: v,
1149 bottom: v,
1150 left: v,
1151 })
1152 }
1153
1154 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
1156 where
1157 A: de::SeqAccess<'de>,
1158 {
1159 let first: u16 = seq
1160 .next_element()?
1161 .ok_or_else(|| de::Error::invalid_length(0, &self))?;
1162
1163 let second: Option<u16> = seq.next_element()?;
1164 let third: Option<u16> = seq.next_element()?;
1165 let fourth: Option<u16> = seq.next_element()?;
1166
1167 match (second, third, fourth) {
1168 (None, None, None) => Ok(Padding {
1169 top: first,
1170 right: first,
1171 bottom: first,
1172 left: first,
1173 }),
1174 (Some(v2), None, None) => Ok(Padding {
1175 top: first,
1176 bottom: first,
1177 left: v2,
1178 right: v2,
1179 }),
1180 (Some(v2), Some(v3), Some(v4)) => Ok(Padding {
1181 top: first,
1182 right: v2,
1183 bottom: v3,
1184 left: v4,
1185 }),
1186 _ => Err(de::Error::invalid_length(3, &self)),
1187 }
1188 }
1189 }
1190
1191 deserializer.deserialize_any(PaddingVisitor)
1192}
1193
1194pub fn deserialize_option_auto<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
1195where
1196D: serde::Deserializer<'de>,
1197T: Deserialize<'de>,
1198{
1199 let opt = Option::<String>::deserialize(deserializer)?;
1200 match opt.as_deref() {
1201 Some("auto") => Ok(None),
1202 Some(s) => Ok(Some(T::deserialize(s.into_deserializer())?)),
1203 None => Ok(None),
1204 }
1205}
1206
1207
1208fn parse_escaped_opt<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
1209where
1210D: serde::Deserializer<'de>,
1211{
1212 let opt = Option::<String>::deserialize(deserializer)?;
1213 Ok(opt.map(|s| parse_escapes(&s)))
1214}
1215
1216fn parse_escaped_char_opt<'de, D>(deserializer: D) -> Result<Option<char>, D::Error>
1217where
1218D: serde::Deserializer<'de>,
1219{
1220 let opt = Option::<String>::deserialize(deserializer)?;
1221 match opt {
1222 Some(s) => {
1223 let parsed = parse_escapes(&s);
1224 let mut chars = parsed.chars();
1225 let first = chars.next().ok_or_else(|| {
1226 serde::de::Error::custom("escaped string is empty")
1227 })?;
1228 if chars.next().is_some() {
1229 return Err(serde::de::Error::custom(
1230 "escaped string must be exactly one character",
1231 ));
1232 }
1233 Ok(Some(first))
1234 }
1235 None => Ok(None),
1236 }
1237}
1238
1239
1240impl<'de> Deserialize<'de> for BorderSetting {
1241 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1242 where
1243 D: Deserializer<'de>,
1244 {
1245 #[derive(Deserialize)]
1246 struct Helper {
1247 #[serde(default)]
1248 r#type: Option<String>,
1249 #[serde(default)]
1250 color: Option<Color>,
1251 #[serde(default, deserialize_with = "deserialize_borders_option")]
1252 sides: Option<Borders>, #[serde(default, deserialize_with = "deserialize_padding")]
1254 padding: Padding,
1255 #[serde(default)]
1256 title: String,
1257 #[serde(default, deserialize_with = "deserialize_modifier", serialize_with = "serialize_modifier")]
1258 title_modifier: Modifier,
1259 #[serde(default)]
1260 bg: Color,
1261 }
1262
1263 let h = Helper::deserialize(deserializer)?;
1264
1265 let r#type = match h.r#type {
1267 Some(ref s) => s.parse::<BorderType>().map_err(serde::de::Error::custom)?,
1268 None => BorderType::default(),
1269 };
1270
1271 let sides = if let Some(sides) = h.sides {
1273 sides
1274 } else if h.r#type.is_some() || h.color.is_some() {
1275 Borders::ALL
1276 } else {
1277 Borders::NONE
1278 };
1279
1280 Ok(BorderSetting {
1281 r#type,
1282 color: h.color.unwrap_or_default(),
1283 sides,
1284 padding: h.padding,
1285 title: h.title,
1286 title_modifier: h.title_modifier,
1287 bg: h.bg,
1288 })
1289 }
1290}
1291
1292fn default_true() -> bool {
1295 true
1296}
1297
1298fn default_black() -> Color {
1299 Color::Black
1300}
1301
1302fn default_green() -> Color {
1303 Color::Green
1304}
1305
1306fn default_bold() -> Modifier {
1307 Modifier::BOLD
1308}
1309
1310fn default_italic() -> Modifier {
1311 Modifier::ITALIC
1312}
1313
1314#[cfg(test)]
1315mod tests {
1316 use super::*;
1317 use toml;
1318
1319 #[test]
1320 fn config_round_trip() {
1321 let default_toml = include_str!("../assets/dev.toml");
1322 let config: MainConfig = toml::from_str(default_toml)
1323 .expect("failed to parse default TOML");
1324 let serialized = toml::to_string_pretty(&config)
1325 .expect("failed to serialize to TOML");
1326 let deserialized: MainConfig = toml::from_str(&serialized)
1327 .unwrap_or_else(|e| panic!("failed to parse serialized TOML:\n{}\n{e}", serialized));
1328
1329 assert_eq!(config, deserialized);
1331 }
1332}