diff --git a/README.md b/README.md index eb0d9d7..9e17acf 100644 --- a/README.md +++ b/README.md @@ -104,12 +104,15 @@ To stop time-tracking completely, simply move to the root of all tasks. Dots and slashes can be repeated to move to parent tasks. -- `:[IND][COL]` - add property column COL at IND or end, if it already exists remove property column COL or IND (1-indexed) -- `*[TIME]` - add timetracking with the specified offset (empty: list tracked times) +- `:[IND][PROP]` - add property column PROP at IND or end, if it already exists remove property column PROP or IND (1-indexed) +- `::[PROP]` - Sort by property PROP +- `([TIME]` - insert timetracking with the specified offset in minutes (empty: list tracked times) +- `)[TIME]` - stop timetracking with the specified offset in minutes - convenience helper to move to root (empty: stop now) - `>[TEXT]` - complete active task and move to parent, with optional state description - `<[TEXT]` - close active task and move to parent, with optional state description - `!TEXT` - set state for current task from text - `,TEXT` - add text note (comment / description) +- TBI: `*[INT]` - set priority - can also be used in task, with any digit - `@` - undoes last action (moving in place or upwards or waiting a minute confirms pending actions) - `wss://...` - switch or subscribe to relay (prefix with space to forcibly add a new one) diff --git a/src/main.rs b/src/main.rs index f84c08b..a9ce310 100644 --- a/src/main.rs +++ b/src/main.rs @@ -378,22 +378,27 @@ async fn main() { None => tasks.clear_filter() } - Some('*') => + Some('(') => match arg { - Some(arg) => { - if let Ok(num) = arg.parse::() { - tasks.track_at(Timestamp::from(Timestamp::now().as_u64().saturating_add_signed(num))); - } else if let Ok(date) = DateTime::parse_from_rfc3339(arg) { - tasks.track_at(Timestamp::from(date.to_utc().timestamp() as u64)); - } else { - warn!("Cannot parse {arg}"); + Some(arg) => + if !tasks.track_from(arg) { + continue; } - } None => { println!("{}", tasks.times_tracked()); continue; } + } + + Some(')') => { + tasks.move_to(None); + if let Some(arg) = arg { + if !tasks.track_from(arg) { + continue; + } + } + } Some('.') => { let mut dots = 1; diff --git a/src/tasks.rs b/src/tasks.rs index 0f799bf..bd11773 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -7,7 +7,7 @@ use std::str::FromStr; use std::sync::mpsc::Sender; use std::time::Duration; -use chrono::{Local, TimeZone}; +use chrono::{DateTime, Local, TimeZone}; use chrono::LocalResult::Single; use colored::Colorize; use itertools::Itertools; @@ -633,10 +633,25 @@ impl Tasks { self.tasks.get(id).map_or(id.to_string(), |t| t.get_title()) } + /// Parse string and set tracking + /// Returns false if parsing failed + pub(crate) fn track_from(&mut self, str: &str) -> bool { + if let Ok(num) = str.parse::() { + self.track_at(Timestamp::from(Timestamp::now().as_u64().saturating_add_signed(num * 60))); + } else if let Ok(date) = DateTime::parse_from_rfc3339(str) { + self.track_at(Timestamp::from(date.to_utc().timestamp() as u64)); + } else { + warn!("Cannot parse {str}"); + return false; + } + true + } + pub(crate) fn track_at(&mut self, time: Timestamp) -> EventId { - info!("Tracking \"{:?}\" from {}", self.position.map(|id| self.get_task_title(&id)), time.to_human_datetime()); + info!("{} from {}", self.position.map_or(String::from("Stopping time-tracking"), |id| format!("Tracking \"{}\"", self.get_task_title(&id))), time.to_human_datetime()); // TODO omit seconds let pos = self.get_position(); let tracking = build_tracking(pos); + // TODO this can lead to funny deletions self.get_own_history().map(|events| { if let Some(event) = events.pop_last() { if event.kind.as_u16() == TRACKING_KIND && @@ -914,6 +929,18 @@ mod tasks_test { // TODO test received events } + #[test] + #[ignore] + fn test_timestamps() { + let mut tasks = stub_tasks(); + let zero = EventId::all_zeros(); + tasks.move_to(Some(zero)); + tasks.track_at(Timestamp::from(Timestamp::now().as_u64() + 100)); + assert_eq!(timestamps(tasks.history.values().nth(0).unwrap().into_iter(), &vec![zero]).collect_vec().len(), 2) + // TODO Does not show both future and current tracking properly, need to split by now + } + + #[test] fn test_depth() { let mut tasks = stub_tasks();