Compare commits

..

3 Commits

Author SHA1 Message Date
xeruf 9f84fd7ef1 refactor(tasks): find task tree as references 2024-08-12 12:17:03 +03:00
xeruf 67a19d61f2 feat(tasks): implement fallback for finding task children 2024-08-12 12:09:46 +03:00
xeruf 8f3552aeba feat: move up after setting state manually 2024-08-12 11:57:21 +03:00
3 changed files with 26 additions and 19 deletions

View File

@ -108,9 +108,9 @@ Dots and slashes can be repeated to move to parent tasks.
- `::[PROP]` - Sort by property PROP (multiple space-separated values allowed)
- `([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]` - complete active task and move up, with optional state description
- `<[TEXT]` - close active task and move up, with optional state description
- `!TEXT` - set state for current task from text and move up
- `,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)

View File

@ -364,6 +364,7 @@ async fn main() {
None => warn!("First select a task to set its state!"),
Some(id) => {
tasks.set_state_for_with(id, arg_default);
tasks.move_up();
}
}

View File

@ -150,19 +150,25 @@ impl Tasks {
#[inline]
pub(crate) fn len(&self) -> usize { self.tasks.len() }
/// Ids of all subtasks found for id, including itself
fn get_subtasks(&self, id: EventId) -> Vec<EventId> {
/// Ids of all subtasks recursively found for id, including itself
fn get_task_tree<'a>(&'a self, id: &'a EventId) -> Vec<&'a EventId> {
let mut children = Vec::with_capacity(32);
let mut index = 0;
children.push(id);
while index < children.len() {
self.tasks.get(&children[index]).map(|t| {
children.reserve(t.children.len());
for child in t.children.iter() {
children.push(child.clone());
let id = children[index];
if let Some(task) = self.tasks.get(&id) {
children.reserve(task.children.len());
children.extend(task.children.iter());
} else {
// Unknown task, can still find children
for task in self.tasks.values() {
if task.parent_id().is_some_and(|i| i == id) {
children.push(task.get_id());
}
}
}
});
index += 1;
}
@ -204,7 +210,7 @@ impl Tasks {
}
}
Some(id) => {
let ids = vec![id];
let ids = vec![&id];
once(format!("Times tracked on {}", self.get_task_title(&id))).chain(
self.history.iter().flat_map(|(key, set)| {
let mut vec = Vec::with_capacity(set.len() / 2);
@ -223,7 +229,7 @@ impl Tasks {
/// Total time in seconds tracked on this task by the current user.
pub(crate) fn time_tracked(&self, id: EventId) -> u64 {
TimesTracked::from(self.history.get(&self.sender.pubkey()).into_iter().flatten(), &vec![id]).sum::<Duration>().as_secs()
TimesTracked::from(self.history.get(&self.sender.pubkey()).into_iter().flatten(), &vec![&id]).sum::<Duration>().as_secs()
}
@ -231,7 +237,7 @@ impl Tasks {
fn total_time_tracked(&self, id: EventId) -> u64 {
let mut total = 0;
let children = self.get_subtasks(id);
let children = self.get_task_tree(&id);
for user in self.history.values() {
total += TimesTracked::from(user, &children).into_iter().sum::<Duration>().as_secs();
}
@ -888,14 +894,14 @@ pub(crate) fn join_tasks<'a>(
})
}
fn matching_tag_id<'a>(event: &'a Event, ids: &'a Vec<EventId>) -> Option<&'a EventId> {
fn matching_tag_id<'a>(event: &'a Event, ids: &'a Vec<&'a EventId>) -> Option<&'a EventId> {
event.tags.iter().find_map(|tag| match tag.as_standardized() {
Some(TagStandard::Event { event_id, .. }) if ids.contains(event_id) => Some(event_id),
Some(TagStandard::Event { event_id, .. }) if ids.contains(&event_id) => Some(event_id),
_ => None
})
}
fn timestamps<'a>(events: impl Iterator<Item=&'a Event>, ids: &'a Vec<EventId>) -> impl Iterator<Item=(&Timestamp, Option<&EventId>)> {
fn timestamps<'a>(events: impl Iterator<Item=&'a Event>, ids: &'a Vec<&'a EventId>) -> impl Iterator<Item=(&Timestamp, Option<&EventId>)> {
events.map(|event| (&event.created_at, matching_tag_id(event, ids)))
.dedup_by(|(_, e1), (_, e2)| e1 == e2)
.skip_while(|element| element.1 == None)
@ -905,11 +911,11 @@ fn timestamps<'a>(events: impl Iterator<Item=&'a Event>, ids: &'a Vec<EventId>)
/// Expects a sorted iterator
struct TimesTracked<'a> {
events: Box<dyn Iterator<Item=&'a Event> + 'a>,
ids: &'a Vec<EventId>,
ids: &'a Vec<&'a EventId>,
threshold: Option<Timestamp>,
}
impl TimesTracked<'_> {
fn from<'b>(events: impl IntoIterator<Item=&'b Event> + 'b, ids: &'b Vec<EventId>) -> TimesTracked<'b> {
fn from<'b>(events: impl IntoIterator<Item=&'b Event> + 'b, ids: &'b Vec<&EventId>) -> TimesTracked<'b> {
TimesTracked {
events: Box::new(events.into_iter()),
ids,
@ -1006,7 +1012,7 @@ mod tasks_test {
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)
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
}