feat: implement task stopping shortcut

This commit is contained in:
xeruf 2024-08-10 20:48:57 +03:00
parent 06bfe8e18a
commit 9fbe3e27cb
3 changed files with 48 additions and 13 deletions

View File

@ -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. 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) - `:[IND][PROP]` - add property column PROP at IND or end, if it already exists remove property column PROP or IND (1-indexed)
- `*[TIME]` - add timetracking with the specified offset (empty: list tracked times) - `::[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]` - complete active task and move to parent, with optional state description
- `<[TEXT]` - close 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` - set state for current task from text
- `,TEXT` - add text note (comment / description) - `,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) - `@` - 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) - `wss://...` - switch or subscribe to relay (prefix with space to forcibly add a new one)

View File

@ -378,22 +378,27 @@ async fn main() {
None => tasks.clear_filter() None => tasks.clear_filter()
} }
Some('*') => Some('(') =>
match arg { match arg {
Some(arg) => { Some(arg) =>
if let Ok(num) = arg.parse::<i64>() { if !tasks.track_from(arg) {
tasks.track_at(Timestamp::from(Timestamp::now().as_u64().saturating_add_signed(num))); continue;
} 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}");
} }
}
None => { None => {
println!("{}", tasks.times_tracked()); println!("{}", tasks.times_tracked());
continue; continue;
} }
} }
Some(')') => {
tasks.move_to(None);
if let Some(arg) = arg {
if !tasks.track_from(arg) {
continue;
}
}
}
Some('.') => { Some('.') => {
let mut dots = 1; let mut dots = 1;

View File

@ -7,7 +7,7 @@ use std::str::FromStr;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::time::Duration; use std::time::Duration;
use chrono::{Local, TimeZone}; use chrono::{DateTime, Local, TimeZone};
use chrono::LocalResult::Single; use chrono::LocalResult::Single;
use colored::Colorize; use colored::Colorize;
use itertools::Itertools; use itertools::Itertools;
@ -633,10 +633,25 @@ impl Tasks {
self.tasks.get(id).map_or(id.to_string(), |t| t.get_title()) 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::<i64>() {
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 { 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 pos = self.get_position();
let tracking = build_tracking(pos); let tracking = build_tracking(pos);
// TODO this can lead to funny deletions
self.get_own_history().map(|events| { self.get_own_history().map(|events| {
if let Some(event) = events.pop_last() { if let Some(event) = events.pop_last() {
if event.kind.as_u16() == TRACKING_KIND && if event.kind.as_u16() == TRACKING_KIND &&
@ -914,6 +929,18 @@ mod tasks_test {
// TODO test received events // 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] #[test]
fn test_depth() { fn test_depth() {
let mut tasks = stub_tasks(); let mut tasks = stub_tasks();