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.
- `:[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)

View File

@ -378,22 +378,27 @@ async fn main() {
None => tasks.clear_filter()
}
Some('*') =>
Some('(') =>
match arg {
Some(arg) => {
if let Ok(num) = arg.parse::<i64>() {
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;

View File

@ -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::<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 {
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();