1mod colors;
2mod export;
3mod options;
4mod parsers;
5mod styles;
6mod text_style;
7mod traits;
8
9pub use self::{
10 colors::Color,
11 options::{
12 PasteBehavior, SavePolicy, SetFinalDateType, TaskSort, TextModifier, WidgetBorderType,
13 },
14 styles::CustomCategoryStyle,
15 text_style::{TextStyle, TextStyleList},
16 traits::{Conf, ConfMerge, ConfigDefaults},
17};
18
19use crate::{
20 layout::widget::WidgetType,
21 todo::{ToDoCategory, ToDoData},
22 ui::{EventHandlerUI, KeyShortcut, UIEvent},
23};
24use clap::{builder::styling::AnsiColor, FromArgMatches};
25use crossterm::event::{KeyCode, KeyModifiers};
26use export::Export;
27use std::{
28 env::var,
29 path::{Path, PathBuf},
30 time::Duration,
31};
32use todotxt_tui_macros::{Conf, ConfMerge};
33use traits::ExportConf;
34use tui::style::Color as tuiColor;
35
36#[derive(Conf, Clone, Debug, PartialEq, Eq)]
37pub struct FileWorkerConfig {
38 pub todo_path: PathBuf,
42 pub archive_path: Option<PathBuf>,
46 #[arg(short = 'd')]
48 pub autosave_duration: Duration,
49 #[arg(short = 'f')]
52 pub file_watcher: bool,
53 pub save_policy: SavePolicy,
56}
57
58impl Default for FileWorkerConfig {
59 fn default() -> Self {
60 Self {
61 todo_path: PathBuf::from(var("HOME").unwrap_or(String::from("~")) + "/todo.txt"),
62 archive_path: None,
63 autosave_duration: Duration::from_secs(900),
64 file_watcher: true,
65 save_policy: SavePolicy::default(),
66 }
67 }
68}
69
70#[derive(Conf, Clone, Debug, PartialEq, Eq)]
71pub struct ActiveColorConfig {
72 #[arg(short = 'A')]
74 list_active_color: TextStyle,
75 #[arg(short = 'P')]
78 pending_active_color: TextStyle,
79 #[arg(short = 'D')]
82 done_active_color: TextStyle,
83 category_active_color: TextStyle,
86 projects_active_color: TextStyle,
89 contexts_active_color: TextStyle,
92 tags_active_color: TextStyle,
95}
96
97impl ActiveColorConfig {
98 pub fn get_active_style(&self, data_type: &ToDoData) -> TextStyle {
101 self.list_active_color.combine(&match data_type {
102 ToDoData::Done => self.done_active_color,
103 ToDoData::Pending => self.pending_active_color,
104 })
105 }
106
107 pub fn get_active_config_style(&self, category: &ToDoCategory) -> TextStyle {
112 self.list_active_color
113 .combine(&self.category_active_color)
114 .combine(match category {
115 ToDoCategory::Projects => &self.projects_active_color,
116 ToDoCategory::Contexts => &self.contexts_active_color,
117 ToDoCategory::Hashtags => &self.tags_active_color,
118 })
119 }
120}
121
122impl Default for ActiveColorConfig {
123 fn default() -> Self {
124 Self {
125 list_active_color: TextStyle::default().bg(Color::lightred()),
126 pending_active_color: TextStyle::default(),
127 done_active_color: TextStyle::default(),
128 category_active_color: TextStyle::default(),
129 projects_active_color: TextStyle::default(),
130 contexts_active_color: TextStyle::default(),
131 tags_active_color: TextStyle::default(),
132 }
133 }
134}
135
136#[derive(Conf, Clone, Debug, PartialEq, Eq)]
137pub struct ListConfig {
138 #[arg(short = 's')]
141 pub list_shift: usize,
142 #[arg(short = 'L')]
144 pub list_keybind: EventHandlerUI,
145 pub pending_format: String,
147 pub done_format: String,
149}
150
151impl Default for ListConfig {
152 fn default() -> Self {
153 Self {
154 list_shift: 4,
155 list_keybind: EventHandlerUI::from([
156 (KeyShortcut::from(KeyCode::Char('j')), UIEvent::ListDown),
157 (KeyShortcut::from(KeyCode::Char('k')), UIEvent::ListUp),
158 (KeyShortcut::from(KeyCode::Char('g')), UIEvent::ListFirst),
159 (
160 KeyShortcut::new(KeyCode::Char('g'), KeyModifiers::SHIFT),
161 UIEvent::ListLast,
162 ),
163 (KeyShortcut::from(KeyCode::Char('h')), UIEvent::CleanSearch),
164 ]),
165 pending_format: String::from("[$subject](! priority)"),
166 done_format: String::from("[$subject](! priority)"),
167 }
168 }
169}
170
171#[derive(Conf, Clone, Debug, PartialEq, Eq)]
172pub struct PreviewConfig {
173 #[arg(short = 'p')]
176 pub preview_format: String,
177 #[arg(short = 'w')]
180 pub wrap_preview: bool,
181}
182
183impl Default for PreviewConfig {
184 fn default() -> Self {
185 Self {
186 preview_format: String::from(
187 "Pending: $pending Done: $done
188Subject: $subject
189Priority: $priority
190Create date: $create_date
191Link: $link",
192 ),
193 wrap_preview: true,
194 }
195 }
196}
197
198#[derive(Conf, Clone, Debug, PartialEq, Eq)]
199pub struct ToDoConfig {
200 pub use_done: bool,
203 #[arg(num_args = 0..)]
206 pub pending_sort: Vec<TaskSort>,
207 #[arg(num_args = 0..)]
210 pub done_sort: Vec<TaskSort>,
211 pub delete_final_date: bool,
213 pub set_final_date: SetFinalDateType,
216 pub set_created_date: bool,
220}
221
222impl Default for ToDoConfig {
223 fn default() -> Self {
224 Self {
225 use_done: false,
226 pending_sort: vec![],
227 done_sort: vec![],
228 delete_final_date: true,
229 set_final_date: SetFinalDateType::default(),
230 set_created_date: true,
231 }
232 }
233}
234
235#[derive(Conf, Clone, Debug, PartialEq, Eq)]
236pub struct UiConfig {
237 #[arg(short = 'i')]
239 pub init_widget: WidgetType,
240 #[arg(short = 't')]
242 pub window_title: String,
243 #[arg(short = 'W')]
245 pub window_keybinds: EventHandlerUI,
246 #[arg(short = 'R')]
248 pub list_refresh_rate: Duration,
249 #[arg(short = 'S')]
251 pub save_state_path: Option<PathBuf>,
252 #[arg(short = 'l', verbatim_doc_comment)]
261 pub layout: String,
262 pub paste_behavior: PasteBehavior,
268 pub enable_mouse: bool,
270}
271
272impl Default for UiConfig {
273 fn default() -> Self {
274 Self {
275 init_widget: WidgetType::List,
276 window_title: String::from("ToDo tui"),
277 window_keybinds: EventHandlerUI::from([
278 (KeyShortcut::from(KeyCode::Char('q')), UIEvent::Quit),
279 (
280 KeyShortcut::new(KeyCode::Char('s'), KeyModifiers::SHIFT),
281 UIEvent::Save,
282 ),
283 (KeyShortcut::from(KeyCode::Char('u')), UIEvent::Load),
284 (
285 KeyShortcut::new(KeyCode::Char('h'), KeyModifiers::SHIFT),
286 UIEvent::MoveLeft,
287 ),
288 (
289 KeyShortcut::new(KeyCode::Char('l'), KeyModifiers::SHIFT),
290 UIEvent::MoveRight,
291 ),
292 (
293 KeyShortcut::new(KeyCode::Char('k'), KeyModifiers::SHIFT),
294 UIEvent::MoveUp,
295 ),
296 (
297 KeyShortcut::new(KeyCode::Char('j'), KeyModifiers::SHIFT),
298 UIEvent::MoveDown,
299 ),
300 (
301 KeyShortcut::new(KeyCode::Char('i'), KeyModifiers::SHIFT),
302 UIEvent::InsertMode,
303 ),
304 (
305 KeyShortcut::new(KeyCode::Char('e'), KeyModifiers::SHIFT),
306 UIEvent::EditMode,
307 ),
308 (KeyShortcut::from(KeyCode::Char('/')), UIEvent::SearchMode),
309 (KeyShortcut::from(KeyCode::Char('?')), UIEvent::ShowHelp),
310 ]),
311 list_refresh_rate: Duration::from_secs(5),
312 save_state_path: None,
313 layout: String::from(concat!(
314 "[",
315 " Direction: Horizontal,",
316 " Size: 50%,",
317 " [",
318 " List: 80%, Preview: 20%,",
319 " ],",
320 " [",
321 " Direction: Vertical,",
322 " Done: 60%,",
323 " [",
324 " Contexts: 50%,",
325 " Projects: 50%,",
326 " ],",
327 " ],",
328 "]",
329 )),
330 paste_behavior: Default::default(),
331 enable_mouse: true,
332 }
333 }
334}
335
336#[derive(Conf, Clone, Debug, PartialEq, Eq)]
337pub struct WidgetBaseConfig {
338 #[arg(short = 'T')]
340 pub tasks_keybind: EventHandlerUI,
341 #[arg(short = 'C')]
343 pub category_keybind: EventHandlerUI,
344 pub border_type: WidgetBorderType,
346 pub pending_widget_name: String,
348 pub done_widget_name: String,
350 pub project_widget_name: String,
352 pub context_widget_name: String,
354 pub hashtag_widget_name: String,
356 pub preview_widget_name: String,
358 pub pending_live_preview_widget_name: String,
360 pub done_live_preview_widget_name: String,
362}
363
364impl Default for WidgetBaseConfig {
365 fn default() -> Self {
366 Self {
367 tasks_keybind: EventHandlerUI::from([
368 (
369 KeyShortcut::new(KeyCode::Char('u'), KeyModifiers::SHIFT),
370 UIEvent::SwapUpItem,
371 ),
372 (
373 KeyShortcut::new(KeyCode::Char('d'), KeyModifiers::SHIFT),
374 UIEvent::SwapDownItem,
375 ),
376 (KeyShortcut::from(KeyCode::Char('x')), UIEvent::RemoveItem),
377 (KeyShortcut::from(KeyCode::Char('d')), UIEvent::MoveItem),
378 (KeyShortcut::from(KeyCode::Enter), UIEvent::Select),
379 (KeyShortcut::from(KeyCode::Char('n')), UIEvent::NextSearch),
380 (
381 KeyShortcut::new(KeyCode::Char('n'), KeyModifiers::SHIFT),
382 UIEvent::PrevSearch,
383 ),
384 ]),
385 category_keybind: EventHandlerUI::from([
386 (KeyShortcut::from(KeyCode::Enter), UIEvent::Select),
387 (KeyShortcut::from(KeyCode::Backspace), UIEvent::Remove),
388 (KeyShortcut::from(KeyCode::Char('n')), UIEvent::NextSearch),
389 (
390 KeyShortcut::new(KeyCode::Char('n'), KeyModifiers::SHIFT),
391 UIEvent::PrevSearch,
392 ),
393 ]),
394 border_type: WidgetBorderType::default(),
395 pending_widget_name: String::from("list"),
396 done_widget_name: String::from("done"),
397 project_widget_name: String::from("project"),
398 context_widget_name: String::from("context"),
399 hashtag_widget_name: String::from("hashtag"),
400 preview_widget_name: String::from("preview"),
401 pending_live_preview_widget_name: String::from("pending live preview"),
402 done_live_preview_widget_name: String::from("done live preview"),
403 }
404 }
405}
406
407#[derive(Conf, Clone, Debug, PartialEq, Eq)]
408pub struct Styles {
409 pub active_color: Color,
411 pub priority_style: TextStyleList,
413 pub projects_style: TextStyle,
415 pub contexts_style: TextStyle,
418 pub hashtags_style: TextStyle,
421 pub category_style: TextStyle,
425 pub category_select_style: TextStyle,
427 pub category_remove_style: TextStyle,
429 pub custom_category_style: CustomCategoryStyle,
433 pub highlight: TextStyle,
436}
437
438impl Styles {
439 pub fn get_category_style(&self, category: &str) -> TextStyle {
443 match self.custom_category_style.get(category) {
444 Some(style) => *style,
445 None => self.get_category_base_style(category),
446 }
447 }
448
449 fn get_category_base_style(&self, category: &str) -> TextStyle {
454 match category.chars().next().unwrap() {
455 '+' => self.category_style.combine(&self.projects_style),
456 '@' => self.category_style.combine(&self.contexts_style),
457 '#' => self.category_style.combine(&self.hashtags_style),
458 _ => self.category_style,
459 }
460 }
461}
462
463impl Default for Styles {
464 fn default() -> Self {
465 let mut custom_category_style = CustomCategoryStyle::default();
466 custom_category_style.insert(
467 String::from("+todo-tui"),
468 TextStyle::default().fg(Color::lightblue()),
469 );
470 Self {
471 active_color: Color(tuiColor::Red),
472 priority_style: TextStyleList::default(),
473 projects_style: TextStyle::default(),
474 contexts_style: TextStyle::default(),
475 hashtags_style: TextStyle::default(),
476 category_style: TextStyle::default(),
477 category_select_style: TextStyle::default().fg(Color::green()),
478 category_remove_style: TextStyle::default().fg(Color::red()),
479 custom_category_style,
480 highlight: TextStyle::default().bg(Color::yellow()),
481 }
482 }
483}
484
485#[derive(Conf, Clone, Debug, PartialEq, Eq, Default)]
486pub struct HookPaths {
487 pub pre_new_task: Option<PathBuf>,
490 pub post_new_task: Option<PathBuf>,
493 pub pre_remove_task: Option<PathBuf>,
496 pub post_remove_task: Option<PathBuf>,
499 pub pre_move_task: Option<PathBuf>,
502 pub post_move_task: Option<PathBuf>,
505 pub pre_update_task: Option<PathBuf>,
508 pub post_update_task: Option<PathBuf>,
511}
512
513#[derive(ConfMerge, Default, Debug, PartialEq, Eq)]
514#[command(author, version, about, long_about = None)]
515#[export_option(Export)]
516pub struct Config {
517 pub ui_config: UiConfig,
518 pub todo_config: ToDoConfig,
519 pub file_worker_config: FileWorkerConfig,
520 pub widget_base_config: WidgetBaseConfig,
521 pub list_config: ListConfig,
522 pub preview_config: PreviewConfig,
523 pub active_color_config: ActiveColorConfig,
524 pub styles: Styles,
525 pub hook_paths: HookPaths,
526}
527
528impl Config {
529 pub fn config_folder() -> PathBuf {
530 match var("XDG_CONFIG_HOME") {
531 Ok(config_path) => PathBuf::from(config_path),
532 Err(_) => PathBuf::from(var("HOME").unwrap_or(String::from("~"))).join(".config"),
533 }
534 .join(env!("CARGO_PKG_NAME"))
535 }
536}
537
538impl ConfigDefaults for Config {
539 fn config_path() -> PathBuf {
540 Self::config_folder().join(concat!(env!("CARGO_PKG_NAME"), ".toml"))
541 }
542
543 fn help_colors() -> clap::builder::Styles {
544 clap::builder::Styles::styled()
545 .usage(AnsiColor::Green.on_default().bold())
546 .literal(AnsiColor::Cyan.on_default().bold())
547 .header(AnsiColor::Green.on_default().bold())
548 .invalid(AnsiColor::Yellow.on_default())
549 .error(AnsiColor::Red.on_default().bold())
550 .valid(AnsiColor::Green.on_default())
551 .placeholder(AnsiColor::Cyan.on_default())
552 }
553}
554
555#[cfg(test)]
556mod tests {
557 use self::parsers::*;
558 use super::*;
559 use anyhow::Result;
560 use pretty_assertions::assert_eq;
561 use std::{path::PathBuf, time::Duration};
562 use test_log::test;
563
564 pub fn get_test_dir() -> String {
565 var("TODO_TUI_TEST_DIR").unwrap()
566 }
567
568 pub fn get_test_file(name: &str) -> PathBuf {
569 let path = PathBuf::from(get_test_dir()).join(name);
570 log::trace!("Get test file {path:?}");
571 path
572 }
573
574 #[test]
575 fn test_deserialization() {
576 let deserialized = Config::from_reader(
577 r#"
578 active_color = "Green"
579 init_widget = "Done"
580 "#
581 .as_bytes(),
582 )
583 .unwrap();
584
585 assert_eq!(*deserialized.styles.active_color, tuiColor::Green);
586 assert_eq!(deserialized.ui_config.init_widget, WidgetType::Done);
587 assert_eq!(
588 deserialized.ui_config.window_title,
589 UiConfig::default().window_title
590 );
591 }
592
593 #[test]
594 fn get_active_style() {
595 {
596 let color = ActiveColorConfig {
597 list_active_color: TextStyle::default().bg(Color::red()),
598 pending_active_color: TextStyle::default().bg(Color::yellow()),
599 ..Default::default()
600 };
601 assert_eq!(
602 color.get_active_style(&ToDoData::Pending),
603 TextStyle::default().bg(Color::yellow())
604 );
605 }
606
607 {
608 let color = ActiveColorConfig {
609 list_active_color: TextStyle::default().bg(Color::red()),
610 ..Default::default()
611 };
612 assert_eq!(
613 color.get_active_style(&ToDoData::Pending),
614 TextStyle::default().bg(Color::red())
615 );
616 }
617
618 {
619 let color = ActiveColorConfig {
620 list_active_color: TextStyle::default().bg(Color::green()).fg(Color::blue()),
621 done_active_color: TextStyle::default()
622 .fg(Color::black())
623 .modifier(TextModifier::Bold),
624 ..Default::default()
625 };
626 assert_eq!(
627 color.get_active_style(&ToDoData::Done),
628 TextStyle::default()
629 .bg(Color::green())
630 .fg(Color::black())
631 .modifier(TextModifier::Bold)
632 );
633 }
634 }
635
636 #[test]
637 fn get_active_config_style() {
638 let color = ActiveColorConfig {
639 list_active_color: TextStyle::default().bg(Color::red()),
640 category_active_color: TextStyle::default().fg(Color::white()),
641 ..Default::default()
642 };
643 assert_eq!(
644 color.get_active_config_style(&ToDoCategory::Projects),
645 TextStyle::default().bg(Color::red()).fg(Color::white())
646 );
647 }
648
649 #[test]
650 fn test_load() -> Result<()> {
651 let s = r#"
652 active_color = "Blue"
653 window_title = "Title"
654 todo_path = "path to todo file"
655 "#;
656
657 let default = Config::default();
658 let c = Config::from_reader(s.as_bytes())?;
659 assert_eq!(*c.styles.active_color, tuiColor::Blue);
660 assert_eq!(c.ui_config.init_widget, default.ui_config.init_widget);
661 assert_eq!(c.ui_config.window_title, String::from("Title"));
662 assert_eq!(
663 c.file_worker_config.todo_path,
664 PathBuf::from("path to todo file")
665 );
666 assert_eq!(c.file_worker_config.archive_path, None);
667
668 Ok(())
669 }
670
671 #[test]
672 fn help_can_be_generated() -> Result<()> {
673 Config::from_args(Vec::<&str>::new())?;
674 Ok(())
675 }
676
677 #[test]
678 fn test_parse_duration() {
679 assert_eq!(parse_duration("1000"), Ok(Duration::from_secs(1000)));
680 assert!(parse_duration("-1000").is_err());
681 }
682
683 #[test]
684 fn empty_config() -> Result<()> {
685 let empty_config = get_test_file("empty_config.toml");
686 let default = Config::from_file(empty_config)?;
687 assert_eq!(default, Config::default());
688
689 Ok(())
690 }
691
692 #[test]
693 fn changed_config() -> Result<()> {
694 let testing_config = get_test_file("testing_config.toml");
695 let config = Config::from_file(testing_config)?;
696 let mut expected = Config::default();
697 expected.styles.active_color = Color::blue();
698 expected.ui_config.init_widget = WidgetType::Project;
699 expected.ui_config.window_title = String::from("Window title");
700 expected.ui_config.layout = String::from("Short invalid layout");
701 expected.file_worker_config.todo_path = PathBuf::from("invalid/path/to/todo.txt");
702 expected.file_worker_config.archive_path =
703 Some(PathBuf::from("invalid/path/to/archive.txt"));
704 expected.file_worker_config.file_watcher = false;
705 expected.list_config.list_shift = 0;
706 expected.todo_config.use_done = true;
707 expected.todo_config.pending_sort = vec![TaskSort::Priority];
708 expected.todo_config.done_sort = vec![TaskSort::Reverse];
709 expected.todo_config.delete_final_date = false;
710 expected.todo_config.set_final_date = SetFinalDateType::Never;
711 expected.preview_config.preview_format = String::from("unimportant preview");
712 expected.preview_config.wrap_preview = false;
713 expected.ui_config.window_keybinds = EventHandlerUI::from([
714 (KeyShortcut::from(KeyCode::Char('e')), UIEvent::EditMode),
715 (KeyShortcut::from(KeyCode::Char('q')), UIEvent::Quit),
716 (
717 KeyShortcut::new(KeyCode::Char('s'), KeyModifiers::SHIFT),
718 UIEvent::Save,
719 ),
720 (KeyShortcut::from(KeyCode::Char('u')), UIEvent::Load),
721 (
722 KeyShortcut::new(KeyCode::Char('h'), KeyModifiers::SHIFT),
723 UIEvent::MoveLeft,
724 ),
725 (
726 KeyShortcut::new(KeyCode::Char('l'), KeyModifiers::SHIFT),
727 UIEvent::MoveRight,
728 ),
729 (
730 KeyShortcut::new(KeyCode::Char('k'), KeyModifiers::SHIFT),
731 UIEvent::MoveUp,
732 ),
733 (
734 KeyShortcut::new(KeyCode::Char('j'), KeyModifiers::SHIFT),
735 UIEvent::MoveDown,
736 ),
737 (
738 KeyShortcut::new(KeyCode::Char('i'), KeyModifiers::SHIFT),
739 UIEvent::InsertMode,
740 ),
741 (
742 KeyShortcut::new(KeyCode::Char('e'), KeyModifiers::SHIFT),
743 UIEvent::EditMode,
744 ),
745 (KeyShortcut::from(KeyCode::Char('/')), UIEvent::SearchMode),
746 (KeyShortcut::from(KeyCode::Char('?')), UIEvent::ShowHelp),
747 ]);
748 expected.ui_config.list_refresh_rate = Duration::from_secs(10);
749 expected.active_color_config.list_active_color = TextStyle::default().bg(Color::green());
750 expected.file_worker_config.autosave_duration = Duration::from_secs(100);
751 expected.list_config.list_keybind = EventHandlerUI::from([
752 (KeyShortcut::from(KeyCode::Char('g')), UIEvent::ListLast),
753 (KeyShortcut::from(KeyCode::Char('j')), UIEvent::ListDown),
754 (KeyShortcut::from(KeyCode::Char('k')), UIEvent::ListUp),
755 (
756 KeyShortcut::new(KeyCode::Char('g'), KeyModifiers::SHIFT),
757 UIEvent::ListLast,
758 ),
759 (KeyShortcut::from(KeyCode::Char('h')), UIEvent::CleanSearch),
760 ]);
761 expected.widget_base_config.tasks_keybind = EventHandlerUI::from([
762 (KeyShortcut::from(KeyCode::Char('s')), UIEvent::Select),
763 (
764 KeyShortcut::new(KeyCode::Char('u'), KeyModifiers::SHIFT),
765 UIEvent::SwapUpItem,
766 ),
767 (
768 KeyShortcut::new(KeyCode::Char('d'), KeyModifiers::SHIFT),
769 UIEvent::SwapDownItem,
770 ),
771 (KeyShortcut::from(KeyCode::Char('x')), UIEvent::RemoveItem),
772 (KeyShortcut::from(KeyCode::Char('d')), UIEvent::MoveItem),
773 (KeyShortcut::from(KeyCode::Enter), UIEvent::Select),
774 (KeyShortcut::from(KeyCode::Char('n')), UIEvent::NextSearch),
775 (
776 KeyShortcut::new(KeyCode::Char('n'), KeyModifiers::SHIFT),
777 UIEvent::PrevSearch,
778 ),
779 ]);
780 expected.widget_base_config.category_keybind = EventHandlerUI::from([
781 (KeyShortcut::from(KeyCode::Char('r')), UIEvent::Remove),
782 (KeyShortcut::from(KeyCode::Enter), UIEvent::Select),
783 (KeyShortcut::from(KeyCode::Backspace), UIEvent::Remove),
784 (KeyShortcut::from(KeyCode::Char('n')), UIEvent::NextSearch),
785 (
786 KeyShortcut::new(KeyCode::Char('n'), KeyModifiers::SHIFT),
787 UIEvent::PrevSearch,
788 ),
789 ]);
790 expected.styles.category_select_style = TextStyle::default().fg(Color::red());
791 expected.styles.category_remove_style = TextStyle::default().fg(Color::green());
792 expected.styles.custom_category_style = CustomCategoryStyle::default();
793 expected.styles.custom_category_style.insert(
794 String::from("+project"),
795 TextStyle::default().fg(Color::green()),
796 );
797
798 assert_eq!(config.ui_config, expected.ui_config);
799 assert_eq!(config.todo_config, expected.todo_config);
800 assert_eq!(config.file_worker_config, expected.file_worker_config);
801 assert_eq!(config.widget_base_config, expected.widget_base_config);
802 assert_eq!(config.list_config, expected.list_config);
803 assert_eq!(config.preview_config, expected.preview_config);
804 assert_eq!(config.active_color_config, expected.active_color_config);
805 assert_eq!(config.styles, expected.styles);
806
807 Ok(())
808 }
809
810 #[test]
811 fn default_values_clap() -> Result<()> {
812 let empty_config = get_test_file("empty_config.toml");
813 let default = Config::from_args(vec![
814 "NAME",
815 "--config-path",
816 empty_config.to_str().unwrap(),
817 ])?;
818 assert_eq!(default, Config::default());
819 Ok(())
820 }
821
822 #[test]
823 fn custom_clap_arguments() -> Result<()> {
824 let testing_config = get_test_file("testing_config.toml");
825 let config = Config::from_args(vec![
826 "NAME",
827 "--config-path",
828 testing_config.to_str().unwrap(),
829 "--active-color",
830 "Green",
831 "--window-title",
832 "New window title",
833 "--layout",
834 "Shorter layout",
835 "--todo-path",
836 "todo.txt",
837 "--archive-path",
838 "archive.txt",
839 "--file-watcher",
840 "true",
841 "--list-shift",
842 "10",
843 "--pending-sort",
844 "reverse",
845 "alphanumeric",
846 "--done-sort",
847 "priority",
848 "--delete-final-date",
849 "true",
850 "--set-final-date",
851 "override",
852 "--preview-format",
853 "extra important preview",
854 "--wrap-preview",
855 "true",
856 "--list-refresh-rate",
857 "15",
858 "--list-active-color",
859 "yellow ^blue",
860 "--autosave-duration",
861 "150",
862 "--category-select-style",
863 "blue",
864 "--category-remove-style",
865 "yellow",
866 ])?;
867 let mut expected = Config::default();
868 expected.styles.active_color = Color::green();
869 expected.ui_config.init_widget = WidgetType::Project;
870 expected.ui_config.window_title = String::from("New window title");
871 expected.ui_config.layout = String::from("Shorter layout");
872 expected.file_worker_config.todo_path = PathBuf::from("todo.txt");
873 expected.file_worker_config.archive_path = Some(PathBuf::from("archive.txt"));
874 expected.file_worker_config.file_watcher = true;
875 expected.list_config.list_shift = 10;
876 expected.todo_config.use_done = true;
877 expected.todo_config.pending_sort = vec![TaskSort::Reverse, TaskSort::Alphanumeric];
878 expected.todo_config.done_sort = vec![TaskSort::Priority];
879 expected.todo_config.delete_final_date = true;
880 expected.todo_config.set_final_date = SetFinalDateType::Override;
881 expected.preview_config.preview_format = String::from("extra important preview");
882 expected.preview_config.wrap_preview = true;
883 expected.ui_config.window_keybinds = EventHandlerUI::from([
884 (KeyShortcut::from(KeyCode::Char('e')), UIEvent::EditMode),
885 (KeyShortcut::from(KeyCode::Char('q')), UIEvent::Quit),
886 (
887 KeyShortcut::new(KeyCode::Char('s'), KeyModifiers::SHIFT),
888 UIEvent::Save,
889 ),
890 (KeyShortcut::from(KeyCode::Char('u')), UIEvent::Load),
891 (
892 KeyShortcut::new(KeyCode::Char('h'), KeyModifiers::SHIFT),
893 UIEvent::MoveLeft,
894 ),
895 (
896 KeyShortcut::new(KeyCode::Char('l'), KeyModifiers::SHIFT),
897 UIEvent::MoveRight,
898 ),
899 (
900 KeyShortcut::new(KeyCode::Char('k'), KeyModifiers::SHIFT),
901 UIEvent::MoveUp,
902 ),
903 (
904 KeyShortcut::new(KeyCode::Char('j'), KeyModifiers::SHIFT),
905 UIEvent::MoveDown,
906 ),
907 (
908 KeyShortcut::new(KeyCode::Char('i'), KeyModifiers::SHIFT),
909 UIEvent::InsertMode,
910 ),
911 (
912 KeyShortcut::new(KeyCode::Char('e'), KeyModifiers::SHIFT),
913 UIEvent::EditMode,
914 ),
915 (KeyShortcut::from(KeyCode::Char('/')), UIEvent::SearchMode),
916 (KeyShortcut::from(KeyCode::Char('?')), UIEvent::ShowHelp),
917 ]);
918 expected.ui_config.list_refresh_rate = Duration::from_secs(15);
919 expected.active_color_config.list_active_color =
920 TextStyle::default().bg(Color::blue()).fg(Color::yellow());
921 expected.file_worker_config.autosave_duration = Duration::from_secs(150);
922 expected.list_config.list_keybind = EventHandlerUI::from([
923 (KeyShortcut::from(KeyCode::Char('g')), UIEvent::ListLast),
924 (KeyShortcut::from(KeyCode::Char('j')), UIEvent::ListDown),
925 (KeyShortcut::from(KeyCode::Char('k')), UIEvent::ListUp),
926 (
927 KeyShortcut::new(KeyCode::Char('g'), KeyModifiers::SHIFT),
928 UIEvent::ListLast,
929 ),
930 (KeyShortcut::from(KeyCode::Char('h')), UIEvent::CleanSearch),
931 ]);
932 expected.widget_base_config.tasks_keybind = EventHandlerUI::from([
933 (KeyShortcut::from(KeyCode::Char('s')), UIEvent::Select),
934 (
935 KeyShortcut::new(KeyCode::Char('u'), KeyModifiers::SHIFT),
936 UIEvent::SwapUpItem,
937 ),
938 (
939 KeyShortcut::new(KeyCode::Char('d'), KeyModifiers::SHIFT),
940 UIEvent::SwapDownItem,
941 ),
942 (KeyShortcut::from(KeyCode::Char('x')), UIEvent::RemoveItem),
943 (KeyShortcut::from(KeyCode::Char('d')), UIEvent::MoveItem),
944 (KeyShortcut::from(KeyCode::Enter), UIEvent::Select),
945 (KeyShortcut::from(KeyCode::Char('n')), UIEvent::NextSearch),
946 (
947 KeyShortcut::new(KeyCode::Char('n'), KeyModifiers::SHIFT),
948 UIEvent::PrevSearch,
949 ),
950 ]);
951 expected.widget_base_config.category_keybind = EventHandlerUI::from([
952 (KeyShortcut::from(KeyCode::Char('r')), UIEvent::Remove),
953 (KeyShortcut::from(KeyCode::Enter), UIEvent::Select),
954 (KeyShortcut::from(KeyCode::Backspace), UIEvent::Remove),
955 (KeyShortcut::from(KeyCode::Char('n')), UIEvent::NextSearch),
956 (
957 KeyShortcut::new(KeyCode::Char('n'), KeyModifiers::SHIFT),
958 UIEvent::PrevSearch,
959 ),
960 ]);
961 expected.styles.category_select_style = TextStyle::default().fg(Color::blue());
962 expected.styles.category_remove_style = TextStyle::default().fg(Color::yellow());
963 let mut custom_styles = CustomCategoryStyle::default();
964 custom_styles.insert(
965 String::from("+project"),
966 TextStyle::default().fg(Color::green()),
967 );
968 expected.styles.custom_category_style = custom_styles;
969
970 assert_eq!(config, expected);
971
972 Ok(())
973 }
974
975 #[test]
976 #[cfg(unix)]
977 fn export_default_is_possible() -> Result<()> {
978 Config::export_default(PathBuf::from("/dev/null"))?;
979 Ok(())
980 }
981}