From 2a3be003015bac9c6a13549029b9fb4595e88384 Mon Sep 17 00:00:00 2001 From: Akshay Date: Mon, 20 Jul 2020 21:13:45 +0530 Subject: allow "untracking" of habits, closes #18 --- src/habit/bit.rs | 17 ++++++++++++++--- src/habit/count.rs | 7 +++++-- src/habit/prelude.rs | 1 + 3 files changed, 20 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/habit/bit.rs b/src/habit/bit.rs index 3386182..8fa14c2 100644 --- a/src/habit/bit.rs +++ b/src/habit/bit.rs @@ -100,11 +100,22 @@ impl Habit for Bit { fn goal(&self) -> u32 { return 1; } - fn modify(&mut self, date: NaiveDate, _: TrackEvent) { + fn modify(&mut self, date: NaiveDate, event: TrackEvent) { if let Some(val) = self.stats.get_mut(&date) { - *val = (val.0 ^ true).into(); + match event { + TrackEvent::Increment => *val = (val.0 ^ true).into(), + TrackEvent::Decrement => { + if val.0 { + *val = false.into(); + } else { + self.stats.remove(&date); + } + } + } } else { - self.insert_entry(date, CustomBool(true)); + if event == TrackEvent::Increment { + self.insert_entry(date, CustomBool(true)); + } } } fn set_view_month_offset(&mut self, offset: u32) { diff --git a/src/habit/count.rs b/src/habit/count.rs index 1bdf920..d351758 100644 --- a/src/habit/count.rs +++ b/src/habit/count.rs @@ -84,12 +84,15 @@ impl Habit for Count { if *val > 0 { *val -= 1 } else { - *val = 0 + self.stats.remove(&date); }; } } } else { - self.insert_entry(date, 1); + match event { + TrackEvent::Increment => self.insert_entry(date, 1), + _ => {} + }; } } fn set_view_month_offset(&mut self, offset: u32) { diff --git a/src/habit/prelude.rs b/src/habit/prelude.rs index 19b00a7..8335d6c 100644 --- a/src/habit/prelude.rs +++ b/src/habit/prelude.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; use std::default; use std::fmt; +#[derive(Debug, PartialEq)] pub enum TrackEvent { Increment, Decrement, -- cgit v1.2.3 From 76a6e78ccbea125ab5b8c06ff254260759d3140c Mon Sep 17 00:00:00 2001 From: Akshay Date: Tue, 21 Jul 2020 11:24:42 +0530 Subject: switch to local time over utc time --- src/app/impl_self.rs | 4 ++-- src/views.rs | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/app/impl_self.rs b/src/app/impl_self.rs index 744f906..2450bff 100644 --- a/src/app/impl_self.rs +++ b/src/app/impl_self.rs @@ -118,7 +118,7 @@ impl App { } pub fn status(&self) -> StatusLine { - let today = chrono::Local::now().naive_utc().date(); + let today = chrono::Local::now().naive_local().date(); let remaining = self.habits.iter().map(|h| h.remaining(today)).sum::(); let total = self.habits.iter().map(|h| h.goal()).sum::(); let completed = total - remaining; @@ -207,7 +207,7 @@ impl App { .iter_mut() .find(|x| x.name() == name && x.is_auto()); if let Some(h) = target_habit { - h.modify(Local::now().naive_utc().date(), event); + h.modify(Local::now().naive_local().date(), event); } }; match result { diff --git a/src/views.rs b/src/views.rs index 24c8a4d..da077ac 100644 --- a/src/views.rs +++ b/src/views.rs @@ -43,7 +43,7 @@ where let strikethrough = Style::from(Effect::Strikethrough); let goal_status = - self.view_month_offset() == 0 && self.reached_goal(Local::now().naive_utc().date()); + self.view_month_offset() == 0 && self.reached_goal(Local::now().naive_local().date()); printer.with_style( Style::merge(&[ @@ -77,12 +77,20 @@ where .collect::>(); for (week, line_nr) in days.chunks(7).zip(2..) { let weekly_goal = self.goal() * week.len() as u32; - let is_this_week = week.contains(&Local::now().naive_utc().date()); + let is_this_week = week.contains(&Local::now().naive_local().date()); let remaining = week.iter().map(|&i| self.remaining(i)).sum::(); let completions = weekly_goal - remaining; let full = CONFIGURATION.view_width - 8; - let bars_to_fill = if weekly_goal > 0 {(completions * full as u32) / weekly_goal} else {0}; - let percentage = if weekly_goal > 0 {(completions as f64 * 100.) / weekly_goal as f64} else {0.0}; + let bars_to_fill = if weekly_goal > 0 { + (completions * full as u32) / weekly_goal + } else { + 0 + }; + let percentage = if weekly_goal > 0 { + (completions as f64 * 100.) / weekly_goal as f64 + } else { + 0.0 + }; printer.with_style(future_style, |p| { p.print((4, line_nr), &"─".repeat(full)); }); @@ -141,7 +149,7 @@ where } fn on_event(&mut self, e: Event) -> EventResult { - let now = Local::now().naive_utc().date(); + let now = Local::now().naive_local().date(); if self.is_auto() { return EventResult::Ignored; } -- cgit v1.2.3 From 4f76460efa869317bd7065b4770c16d0146a92da Mon Sep 17 00:00:00 2001 From: Akshay Date: Tue, 21 Jul 2020 20:33:22 +0530 Subject: add help command --- src/app/impl_self.rs | 23 +++++++++++++++++++++++ src/command.rs | 13 ++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/app/impl_self.rs b/src/app/impl_self.rs index 2450bff..95f1871 100644 --- a/src/app/impl_self.rs +++ b/src/app/impl_self.rs @@ -230,6 +230,29 @@ impl App { Command::TrackDown(name) => { _track(&name, TrackEvent::Decrement); } + Command::Help(input) => { + if let Some(topic) = input.as_ref().map(String::as_ref) { + self.message.set_message( + match topic { + "a" | "add" => "add [goal] (alias: a)", + "aa" | "add-auto" => "add-auto [goal] (alias: aa)", + "d" | "delete" => "delete (alias: d)", + "mprev" | "month-prev" => "month-prev (alias: mprev)", + "mnext" | "month-next" => "month-next (alias: mnext)", + "tup" | "track-up" => "track-up (alias: tup)", + "tdown" | "track-down" => "track-down (alias: tdown)", + "q" | "quit" => "quit", + "h"|"?" | "help" => "help [|commands|keys] (aliases: h, ?)", + "cmds" | "commands" => "add, add-auto, delete, month-{prev,next}, track-{up,down}, help, quit", + "keys" => "TODO", // TODO (view?) + _ => "unknown command or help topic.", + } + ) + } else { + // TODO (view?) + self.message.set_message("help |commands|keys") + } + } Command::Quit => self.save_state(), Command::MonthNext => self.sift_forward(), Command::MonthPrev => self.sift_backward(), diff --git a/src/command.rs b/src/command.rs index f856b00..29908f4 100644 --- a/src/command.rs +++ b/src/command.rs @@ -59,15 +59,16 @@ pub enum Command { Delete(String), TrackUp(String), TrackDown(String), + Help(Option), Quit, Blank, } #[derive(Debug)] pub enum CommandLineError { - InvalidCommand(String), - InvalidArg(u32), // position - NotEnoughArgs(String, u32), + InvalidCommand(String), // command name + InvalidArg(u32), // position + NotEnoughArgs(String, u32), // command name, required no. of args } impl std::error::Error for CommandLineError {} @@ -134,6 +135,12 @@ impl Command { } return Ok(Command::TrackDown(args[0].to_string())); } + "h" | "?" | "help" => { + if args.is_empty() { + return Ok(Command::Help(None)); + } + return Ok(Command::Help(Some(args[0].to_string()))); + } "mprev" | "month-prev" => return Ok(Command::MonthPrev), "mnext" | "month-next" => return Ok(Command::MonthNext), "q" | "quit" => return Ok(Command::Quit), -- cgit v1.2.3 From 9dfe454cd8bc816522446e7e3b9f45630c886112 Mon Sep 17 00:00:00 2001 From: Guillaume Hormiere Date: Wed, 22 Jul 2020 00:38:01 +0200 Subject: Add list command for shell script purpose Usage dijo -l for printing the habit names list Add check on habit add to avoid duplicate habits --- src/app/impl_self.rs | 21 ++++++++++++++++++++- src/main.rs | 14 ++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/app/impl_self.rs b/src/app/impl_self.rs index 95f1871..38162ee 100644 --- a/src/app/impl_self.rs +++ b/src/app/impl_self.rs @@ -33,8 +33,27 @@ impl App { }; } + pub fn list_habit(&self) -> Vec { + let mut habits_names: Vec = vec![]; + for h in self.habits.iter() { + habits_names.push(h.name()) + } + return habits_names; + } + pub fn add_habit(&mut self, h: Box) { - self.habits.push(h); + if self + .habits + .iter() + .filter(|hab| hab.name() == h.name()) + .count() + > 0 + { + self.message + .set_message(format!("Habit `{}` allready exist", h.name())) + } else { + self.habits.push(h); + } } pub fn delete_by_name(&mut self, name: &str) { diff --git a/src/main.rs b/src/main.rs index d96119e..050a296 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,14 @@ fn main() { .value_name("CMD") .help("run a dijo command"), ) + .arg( + Arg::with_name("list") + .short("l") + .long("list") + .takes_value(false) + .help("list dijo habits") + .conflicts_with("command"), + ) .get_matches(); if let Some(c) = matches.value_of("command") { let command = Command::from_string(c); @@ -49,6 +57,12 @@ fn main() { "Commands other than `track-up` and `track-down` are currently not supported!" ), } + } else if matches.is_present("list") { + let app = App::load_state(); + let _habit_names = app.list_habit(); + for h in _habit_names { + println!("{}", h); + } } else { let mut s = termion().unwrap(); let app = App::load_state(); -- cgit v1.2.3 From 3f64deb152c31f2a04612d9c525537a72605d678 Mon Sep 17 00:00:00 2001 From: Akshay Date: Wed, 22 Jul 2020 21:00:38 +0530 Subject: remove `d` keybind, add tab completion --- src/app/impl_self.rs | 4 +++ src/app/impl_view.rs | 8 ------ src/command.rs | 72 +++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 67 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/app/impl_self.rs b/src/app/impl_self.rs index 95f1871..7eae853 100644 --- a/src/app/impl_self.rs +++ b/src/app/impl_self.rs @@ -37,6 +37,10 @@ impl App { self.habits.push(h); } + pub fn list_habits(&self) -> Vec { + self.habits.iter().map(|x| x.name()).collect::>() + } + pub fn delete_by_name(&mut self, name: &str) { let old_len = self.habits.len(); self.habits.retain(|h| h.name() != name); diff --git a/src/app/impl_view.rs b/src/app/impl_view.rs index 892b00c..b8c4589 100644 --- a/src/app/impl_view.rs +++ b/src/app/impl_view.rs @@ -102,14 +102,6 @@ impl View for App { self.set_focus(Absolute::Down); return EventResult::Consumed(None); } - Event::Char('d') => { - if self.habits.is_empty() { - return EventResult::Consumed(None); - } - self.habits.remove(self.focus); - self.focus = self.focus.checked_sub(1).unwrap_or(0); - return EventResult::Consumed(None); - } Event::Char('w') => { // helper bind to test write to file let j = serde_json::to_string_pretty(&self.habits).unwrap(); diff --git a/src/command.rs b/src/command.rs index 29908f4..2f6b48a 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,21 +1,75 @@ use std::fmt; +use std::rc::Rc; +use cursive::event::{Event, EventResult, Key}; use cursive::theme::{BaseColor, Color, ColorStyle}; use cursive::view::Resizable; -use cursive::views::{EditView, LinearLayout, TextView}; +use cursive::views::{EditView, LinearLayout, OnEventView, TextView}; use cursive::Cursive; use crate::{app::App, CONFIGURATION}; +static COMMANDS: &'static [&'static str] = &[ + "add", + "add-auto", + "delete", + "track-up", + "track-down", + "month-prev", + "month-next", + "quit", + "help", +]; + +fn get_command_completion(prefix: &str) -> Option { + let first_match = COMMANDS.iter().filter(|&x| x.starts_with(prefix)).next(); + return first_match.map(|&x| x.into()); +} + +fn get_habit_completion(prefix: &str, habit_names: &[String]) -> Option { + let first_match = habit_names.iter().filter(|&x| x.starts_with(prefix)).next(); + eprintln!("{:?}| {:?}", prefix, first_match); + return first_match.map(|x| x.into()); +} + pub fn open_command_window(s: &mut Cursive) { - let command_window = EditView::new() - .filler(" ") - .on_submit(call_on_app) - .style(ColorStyle::new( - Color::Dark(BaseColor::Black), - Color::Dark(BaseColor::White), - )) - .fixed_width(CONFIGURATION.view_width * CONFIGURATION.grid_width); + let habit_list: Vec = s + .call_on_name("Main", |view: &mut App| { + return view.list_habits(); + }) + .unwrap(); + let style = ColorStyle::new(Color::Dark(BaseColor::Black), Color::Dark(BaseColor::White)); + let command_window = OnEventView::new( + EditView::new() + .filler(" ") + .on_submit(call_on_app) + .style(style), + ) + .on_event_inner( + Event::Key(Key::Tab), + move |view: &mut EditView, _: &Event| { + let contents = view.get_content(); + if !contents.contains(" ") { + let completion = get_command_completion(&*contents); + if let Some(c) = completion { + let cb = view.set_content(c); + return Some(EventResult::Consumed(Some(cb))); + }; + return None; + } else { + let word = contents.split(' ').last().unwrap(); + let completion = get_habit_completion(word, &habit_list); + eprintln!("{:?} | {:?}", completion, contents); + if let Some(c) = completion { + let cb = + view.set_content(format!("{}", contents) + c.strip_prefix(word).unwrap()); + return Some(EventResult::Consumed(Some(cb))); + }; + return None; + } + }, + ) + .fixed_width(CONFIGURATION.view_width * CONFIGURATION.grid_width); s.call_on_name("Frame", |view: &mut LinearLayout| { let mut commandline = LinearLayout::horizontal() .child(TextView::new(":")) -- cgit v1.2.3 From 23e85aab0571f9c7214532a18a490ba6b6573f0e Mon Sep 17 00:00:00 2001 From: Akshay Date: Wed, 22 Jul 2020 21:51:41 +0530 Subject: remove unused import --- src/command.rs | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/command.rs b/src/command.rs index 2f6b48a..850261c 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,5 +1,4 @@ use std::fmt; -use std::rc::Rc; use cursive::event::{Event, EventResult, Key}; use cursive::theme::{BaseColor, Color, ColorStyle}; -- cgit v1.2.3 From 5c910bc6cf193725575ef6146dcc8620e7a4a800 Mon Sep 17 00:00:00 2001 From: Akshay Date: Wed, 22 Jul 2020 22:08:50 +0530 Subject: remove debug key binds, add :write command --- src/app/impl_self.rs | 2 +- src/app/impl_view.rs | 11 ----------- src/command.rs | 3 +++ 3 files changed, 4 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/app/impl_self.rs b/src/app/impl_self.rs index 7eae853..cf0e97f 100644 --- a/src/app/impl_self.rs +++ b/src/app/impl_self.rs @@ -257,7 +257,7 @@ impl App { self.message.set_message("help |commands|keys") } } - Command::Quit => self.save_state(), + Command::Quit | Command::Write => self.save_state(), Command::MonthNext => self.sift_forward(), Command::MonthPrev => self.sift_backward(), Command::Blank => {} diff --git a/src/app/impl_view.rs b/src/app/impl_view.rs index b8c4589..0dfd20b 100644 --- a/src/app/impl_view.rs +++ b/src/app/impl_view.rs @@ -102,17 +102,6 @@ impl View for App { self.set_focus(Absolute::Down); return EventResult::Consumed(None); } - Event::Char('w') => { - // helper bind to test write to file - let j = serde_json::to_string_pretty(&self.habits).unwrap(); - let mut file = File::create("foo.txt").unwrap(); - file.write_all(j.as_bytes()).unwrap(); - return EventResult::Consumed(None); - } - Event::Char('q') => { - self.save_state(); - return EventResult::with_cb(|s| s.quit()); - } Event::Char('v') => { if self.habits.is_empty() { return EventResult::Consumed(None); diff --git a/src/command.rs b/src/command.rs index 850261c..38d48e9 100644 --- a/src/command.rs +++ b/src/command.rs @@ -17,6 +17,7 @@ static COMMANDS: &'static [&'static str] = &[ "month-prev", "month-next", "quit", + "write", "help", ]; @@ -113,6 +114,7 @@ pub enum Command { TrackUp(String), TrackDown(String), Help(Option), + Write, Quit, Blank, } @@ -197,6 +199,7 @@ impl Command { "mprev" | "month-prev" => return Ok(Command::MonthPrev), "mnext" | "month-next" => return Ok(Command::MonthNext), "q" | "quit" => return Ok(Command::Quit), + "w" | "write" => return Ok(Command::Write), "" => return Ok(Command::Blank), s => return Err(CommandLineError::InvalidCommand(s.into())), } -- cgit v1.2.3 From a0c57162b2026e37220e31a39d821c2a2e31cc51 Mon Sep 17 00:00:00 2001 From: Guillaume Hormiere Date: Wed, 22 Jul 2020 22:26:19 +0200 Subject: Use immutable vector instead of mutable one and change the message kind to Error Apply code review --- src/app/impl_self.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/app/impl_self.rs b/src/app/impl_self.rs index 38162ee..8a84cb2 100644 --- a/src/app/impl_self.rs +++ b/src/app/impl_self.rs @@ -34,10 +34,7 @@ impl App { } pub fn list_habit(&self) -> Vec { - let mut habits_names: Vec = vec![]; - for h in self.habits.iter() { - habits_names.push(h.name()) - } + let habits_names = self.habits.iter().map(|x| x.name()).collect::>(); return habits_names; } @@ -49,6 +46,7 @@ impl App { .count() > 0 { + self.message.set_kind(MessageKind::Error); self.message .set_message(format!("Habit `{}` allready exist", h.name())) } else { -- cgit v1.2.3 From f5940ea5fc507ec34d686a888a1aa1aba50be277 Mon Sep 17 00:00:00 2001 From: Akshay Date: Thu, 23 Jul 2020 10:06:34 +0530 Subject: fix tiemzones in statusline --- src/app/impl_self.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/app/impl_self.rs b/src/app/impl_self.rs index cf0e97f..1ed19e6 100644 --- a/src/app/impl_self.rs +++ b/src/app/impl_self.rs @@ -128,7 +128,7 @@ impl App { let completed = total - remaining; let timestamp = if self.view_month_offset == 0 { - format!("{}", Local::now().date().format("%d/%b/%y"),) + format!("{}", Local::now().naive_local().date().format("%d/%b/%y"),) } else { let months = self.view_month_offset; format!("{}", format!("{} months ago", months),) -- cgit v1.2.3 From b0b6c04a052955834f0603df79db7a0a517a9b9d Mon Sep 17 00:00:00 2001 From: Akshay Date: Thu, 23 Jul 2020 13:19:15 +0530 Subject: move duplicate check to command parsing block --- src/app/impl_self.rs | 25 +++++++------------------ src/main.rs | 4 +--- 2 files changed, 8 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/app/impl_self.rs b/src/app/impl_self.rs index 1dfe268..a806dc5 100644 --- a/src/app/impl_self.rs +++ b/src/app/impl_self.rs @@ -33,25 +33,8 @@ impl App { }; } - pub fn list_habit(&self) -> Vec { - let habits_names = self.habits.iter().map(|x| x.name()).collect::>(); - return habits_names; - } - pub fn add_habit(&mut self, h: Box) { - if self - .habits - .iter() - .filter(|hab| hab.name() == h.name()) - .count() - > 0 - { - self.message.set_kind(MessageKind::Error); - self.message - .set_message(format!("Habit `{}` allready exist", h.name())) - } else { - self.habits.push(h); - } + self.habits.push(h); } pub fn list_habits(&self) -> Vec { @@ -234,6 +217,12 @@ impl App { match result { Ok(c) => match c { Command::Add(name, goal, auto) => { + if let Some(_) = self.habits.iter().find(|x| x.name() == name) { + self.message.set_kind(MessageKind::Error); + self.message + .set_message(format!("Habit `{}` already exist", &name)); + return; + } let kind = if goal == Some(1) { "bit" } else { "count" }; if kind == "count" { self.add_habit(Box::new(Count::new(name, goal.unwrap_or(0), auto))); diff --git a/src/main.rs b/src/main.rs index 050a296..5523073 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,9 +58,7 @@ fn main() { ), } } else if matches.is_present("list") { - let app = App::load_state(); - let _habit_names = app.list_habit(); - for h in _habit_names { + for h in App::load_state().list_habits() { println!("{}", h); } } else { -- cgit v1.2.3 From 7322949561fdbdc36a811b1368222fb5a2bf050a Mon Sep 17 00:00:00 2001 From: Akshay Date: Mon, 20 Jul 2020 11:38:20 +0530 Subject: switch to crossterm backend --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/main.rs b/src/main.rs index 5523073..3e35ebc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ use crate::command::{open_command_window, Command}; use crate::utils::{load_configuration_file, AppConfig}; use clap::{App as ClapApp, Arg}; -use cursive::termion; +use cursive::crossterm; use cursive::views::{LinearLayout, NamedView}; use lazy_static::lazy_static; @@ -62,7 +62,7 @@ fn main() { println!("{}", h); } } else { - let mut s = termion().unwrap(); + let mut s = crossterm().unwrap(); let app = App::load_state(); let layout = NamedView::new( "Frame", -- cgit v1.2.3