forked from janek/mostr
fix: parse markers again by reducing EventId referencing
This commit is contained in:
parent
e186d034e5
commit
2400f7c45b
27
src/kinds.rs
27
src/kinds.rs
|
@ -53,6 +53,23 @@ Utilities:
|
||||||
- TBI `depends` - list all tasks this task depends on before it becomes actionable
|
- TBI `depends` - list all tasks this task depends on before it becomes actionable
|
||||||
Debugging: `kind`, `pubkey`, `props`, `alltags`, `descriptions`";
|
Debugging: `kind`, `pubkey`, `props`, `alltags`, `descriptions`";
|
||||||
|
|
||||||
|
pub struct EventTag {
|
||||||
|
pub id: EventId,
|
||||||
|
pub marker: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return event tag if existing
|
||||||
|
pub(crate) fn match_event_tag(tag: &Tag) -> Option<EventTag> {
|
||||||
|
let mut vec = tag.as_slice().into_iter();
|
||||||
|
if vec.next() == Some(&"e".to_string()) {
|
||||||
|
if let Some(id) = vec.next().and_then(|v| EventId::parse(v).ok()) {
|
||||||
|
vec.next();
|
||||||
|
return Some(EventTag { id, marker: vec.next().cloned() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn build_tracking<I>(id: I) -> EventBuilder
|
pub(crate) fn build_tracking<I>(id: I) -> EventBuilder
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item=EventId>,
|
I: IntoIterator<Item=EventId>,
|
||||||
|
@ -118,12 +135,12 @@ pub fn to_hashtag(tag: &str) -> Tag {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_tag(tag: &Tag) -> String {
|
fn format_tag(tag: &Tag) -> String {
|
||||||
|
if let Some(et) = match_event_tag(tag) {
|
||||||
|
return format!("{}: {:.8}",
|
||||||
|
et.marker.as_ref().map(|m| m.to_string()).unwrap_or(MARKER_PARENT.to_string()),
|
||||||
|
et.id)
|
||||||
|
}
|
||||||
match tag.as_standardized() {
|
match tag.as_standardized() {
|
||||||
Some(TagStandard::Event {
|
|
||||||
event_id,
|
|
||||||
marker,
|
|
||||||
..
|
|
||||||
}) => format!("{}: {:.8}", marker.as_ref().map(|m| m.to_string()).unwrap_or(MARKER_PARENT.to_string()), event_id),
|
|
||||||
Some(TagStandard::PublicKey {
|
Some(TagStandard::PublicKey {
|
||||||
public_key,
|
public_key,
|
||||||
alias,
|
alias,
|
||||||
|
|
18
src/main.rs
18
src/main.rs
|
@ -639,7 +639,7 @@ async fn main() -> Result<()> {
|
||||||
let pos = tasks.up_by(dots - 1);
|
let pos = tasks.up_by(dots - 1);
|
||||||
|
|
||||||
if remaining.is_empty() {
|
if remaining.is_empty() {
|
||||||
tasks.move_to(pos.cloned());
|
tasks.move_to(pos);
|
||||||
if dots > 1 {
|
if dots > 1 {
|
||||||
info!("Moving up {} tasks", dots - 1)
|
info!("Moving up {} tasks", dots - 1)
|
||||||
} else {
|
} else {
|
||||||
|
@ -648,13 +648,13 @@ async fn main() -> Result<()> {
|
||||||
} else {
|
} else {
|
||||||
match remaining.parse::<usize>() {
|
match remaining.parse::<usize>() {
|
||||||
Ok(depth) if depth < 10 => {
|
Ok(depth) if depth < 10 => {
|
||||||
if pos != tasks.get_position_ref() {
|
if pos != tasks.get_position() {
|
||||||
tasks.move_to(pos.cloned());
|
tasks.move_to(pos);
|
||||||
}
|
}
|
||||||
tasks.set_view_depth(depth);
|
tasks.set_view_depth(depth);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
tasks.filter_or_create(pos.cloned().as_ref(), &remaining).map(|id| tasks.move_to(Some(id)));
|
tasks.filter_or_create(pos, &remaining).map(|id| tasks.move_to(Some(id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -667,13 +667,13 @@ async fn main() -> Result<()> {
|
||||||
let pos = tasks.up_by(dots - 1);
|
let pos = tasks.up_by(dots - 1);
|
||||||
|
|
||||||
if remaining.is_empty() {
|
if remaining.is_empty() {
|
||||||
tasks.move_to(pos.cloned());
|
tasks.move_to(pos);
|
||||||
if dots > 1 {
|
if dots > 1 {
|
||||||
info!("Moving up {} tasks", dots - 1)
|
info!("Moving up {} tasks", dots - 1)
|
||||||
}
|
}
|
||||||
} else if let Ok(depth) = remaining.parse::<usize>() {
|
} else if let Ok(depth) = remaining.parse::<usize>() {
|
||||||
if pos != tasks.get_position_ref() {
|
if pos != tasks.get_position() {
|
||||||
tasks.move_to(pos.cloned());
|
tasks.move_to(pos);
|
||||||
}
|
}
|
||||||
tasks.set_search_depth(depth);
|
tasks.set_search_depth(depth);
|
||||||
} else {
|
} else {
|
||||||
|
@ -693,7 +693,7 @@ async fn main() -> Result<()> {
|
||||||
if filtered.len() == 1 {
|
if filtered.len() == 1 {
|
||||||
tasks.move_to(filtered.into_iter().next());
|
tasks.move_to(filtered.into_iter().next());
|
||||||
} else {
|
} else {
|
||||||
tasks.move_to(pos.cloned());
|
tasks.move_to(pos);
|
||||||
if !tasks.set_view(filtered) {
|
if !tasks.set_view(filtered) {
|
||||||
continue 'repl;
|
continue 'repl;
|
||||||
}
|
}
|
||||||
|
@ -726,7 +726,7 @@ async fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
tasks.filter_or_create(tasks.get_position().as_ref(), &command);
|
tasks.filter_or_create(tasks.get_position(), &command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tasks.custom_time = None;
|
tasks.custom_time = None;
|
||||||
|
|
12
src/task.rs
12
src/task.rs
|
@ -10,10 +10,10 @@ use colored::{ColoredString, Colorize};
|
||||||
use itertools::Either::{Left, Right};
|
use itertools::Either::{Left, Right};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use nostr_sdk::{Alphabet, Event, EventId, Kind, Tag, TagStandard, Timestamp};
|
use nostr_sdk::{Alphabet, Event, EventId, Kind, Tag, Timestamp};
|
||||||
|
|
||||||
use crate::helpers::{format_timestamp_local, some_non_empty};
|
use crate::helpers::{format_timestamp_local, some_non_empty};
|
||||||
use crate::kinds::{is_hashtag, Prio, PRIO, PROCEDURE_KIND, PROCEDURE_KIND_ID, TASK_KIND};
|
use crate::kinds::{is_hashtag, match_event_tag, Prio, PRIO, PROCEDURE_KIND, PROCEDURE_KIND_ID, TASK_KIND};
|
||||||
use crate::tasks::now;
|
use crate::tasks::now;
|
||||||
|
|
||||||
pub static MARKER_PARENT: &str = "parent";
|
pub static MARKER_PARENT: &str = "parent";
|
||||||
|
@ -52,10 +52,10 @@ impl Hash for Task {
|
||||||
|
|
||||||
impl Task {
|
impl Task {
|
||||||
pub(crate) fn new(event: Event) -> Task {
|
pub(crate) fn new(event: Event) -> Task {
|
||||||
let (refs, tags) = event.tags.iter().partition_map(|tag| match tag.as_standardized() {
|
let (refs, tags) = event.tags.iter().partition_map(|tag| if let Some(et) = match_event_tag(tag) {
|
||||||
Some(TagStandard::Event { event_id, marker, .. }) =>
|
Left((et.marker.as_ref().map_or(MARKER_PARENT.to_string(), |m| m.to_string()), et.id))
|
||||||
Left((marker.as_ref().map_or(MARKER_PARENT.to_string(), |m| m.to_string()), *event_id)),
|
} else {
|
||||||
_ => Right(tag.clone()),
|
Right(tag.clone())
|
||||||
});
|
});
|
||||||
// Separate refs for dependencies
|
// Separate refs for dependencies
|
||||||
Task {
|
Task {
|
||||||
|
|
376
src/tasks.rs
376
src/tasks.rs
|
@ -13,8 +13,7 @@ use crate::task::{State, Task, TaskState, MARKER_DEPENDS, MARKER_PARENT, MARKER_
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use nostr_sdk::prelude::Marker;
|
use nostr_sdk::{Alphabet, Event, EventBuilder, EventId, JsonUtil, Keys, Kind, Metadata, PublicKey, SingleLetterTag, Tag, TagKind, TagStandard, Timestamp, Url};
|
||||||
use nostr_sdk::{Alphabet, Event, EventBuilder, EventId, JsonUtil, Keys, Kind, Metadata, PublicKey, SingleLetterTag, Tag, TagKind, TagStandard, Timestamp, UncheckedUrl, Url};
|
|
||||||
use regex::bytes::Regex;
|
use regex::bytes::Regex;
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
use TagStandard::Hashtag;
|
use TagStandard::Hashtag;
|
||||||
|
@ -31,22 +30,20 @@ pub(crate) fn now() -> Timestamp {
|
||||||
type TaskMap = HashMap<EventId, Task>;
|
type TaskMap = HashMap<EventId, Task>;
|
||||||
trait TaskMapMethods {
|
trait TaskMapMethods {
|
||||||
fn children_of<'a>(&'a self, task: &'a Task) -> impl Iterator<Item=&Task> + 'a;
|
fn children_of<'a>(&'a self, task: &'a Task) -> impl Iterator<Item=&Task> + 'a;
|
||||||
fn children_for<'a>(&'a self, id: Option<&'a EventId>) -> impl Iterator<Item=&Task> + 'a;
|
fn children_for<'a>(&'a self, id: Option<EventId>) -> impl Iterator<Item=&Task> + 'a;
|
||||||
fn children_ids_for<'a>(&'a self, id: &'a EventId) -> impl Iterator<Item=&EventId> + 'a;
|
fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator<Item=&EventId> + 'a;
|
||||||
}
|
}
|
||||||
impl TaskMapMethods for TaskMap {
|
impl TaskMapMethods for TaskMap {
|
||||||
fn children_of<'a>(&'a self, task: &'a Task) -> impl Iterator<Item=&Task> + 'a {
|
fn children_of<'a>(&'a self, task: &'a Task) -> impl Iterator<Item=&Task> + 'a {
|
||||||
self.children_for(Some(task.get_id()))
|
self.children_for(Some(task.event.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn children_for<'a>(&'a self, id: Option<&'a EventId>) -> impl Iterator<Item=&Task> + 'a {
|
fn children_for<'a>(&'a self, id: Option<EventId>) -> impl Iterator<Item=&Task> + 'a {
|
||||||
self.values()
|
self.values().filter(move |t| t.parent_id() == id.as_ref())
|
||||||
.filter(move |t| t.parent_id() == id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn children_ids_for<'a>(&'a self, id: &'a EventId) -> impl Iterator<Item=&EventId> + 'a {
|
fn children_ids_for<'a>(&'a self, id: EventId) -> impl Iterator<Item=&EventId> + 'a {
|
||||||
self.children_for(Some(id))
|
self.children_for(Some(id)).map(|t| t.get_id())
|
||||||
.map(|t| t.get_id())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,16 +204,14 @@ impl TasksRelay {
|
||||||
// Accessors
|
// Accessors
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn get_by_id(&self, id: &EventId) -> Option<&Task> { self.tasks.get(id) }
|
pub(crate) fn get_by_id(&self, id: &EventId) -> Option<&Task> {
|
||||||
|
self.tasks.get(id)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn len(&self) -> usize { self.tasks.len() }
|
pub(crate) fn len(&self) -> usize { self.tasks.len() }
|
||||||
|
|
||||||
pub(crate) fn get_position(&self) -> Option<EventId> {
|
pub(crate) fn get_position(&self) -> Option<EventId> {
|
||||||
self.get_position_ref().cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_position_ref(&self) -> Option<&EventId> {
|
|
||||||
self.get_position_at(now()).1
|
self.get_position_at(now()).1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,18 +224,23 @@ impl TasksRelay {
|
||||||
|
|
||||||
// TODO binary search
|
// TODO binary search
|
||||||
/// Gets last position change before the given timestamp
|
/// Gets last position change before the given timestamp
|
||||||
fn get_position_at(&self, timestamp: Timestamp) -> (Timestamp, Option<&EventId>) {
|
fn get_position_at(&self, timestamp: Timestamp) -> (Timestamp, Option<EventId>) {
|
||||||
self.history_from(timestamp)
|
self.history_from(timestamp)
|
||||||
.last()
|
.last()
|
||||||
.filter(|e| e.created_at <= timestamp)
|
.filter(|e| e.created_at <= timestamp)
|
||||||
.map_or_else(
|
.map_or_else(
|
||||||
|| (Timestamp::now(), None),
|
|| (Timestamp::now(), None),
|
||||||
|e| (e.created_at, referenced_event(e)))
|
|e| (e.created_at, referenced_event(e)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nonclosed_tasks(&self) -> impl Iterator<Item=&Task> {
|
||||||
|
self.tasks.values()
|
||||||
|
.filter(|t| t.pure_state() != State::Closed)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn all_hashtags(&self) -> impl Iterator<Item=&str> {
|
pub(crate) fn all_hashtags(&self) -> impl Iterator<Item=&str> {
|
||||||
self.tasks.values()
|
self.nonclosed_tasks()
|
||||||
.filter(|t| t.pure_state() != State::Closed)
|
|
||||||
.flat_map(|t| t.get_hashtags())
|
.flat_map(|t| t.get_hashtags())
|
||||||
.filter_map(|tag| tag.content().map(|s| s.trim()))
|
.filter_map(|tag| tag.content().map(|s| s.trim()))
|
||||||
.sorted_unstable()
|
.sorted_unstable()
|
||||||
|
@ -252,8 +252,11 @@ impl TasksRelay {
|
||||||
self.times_tracked_for(&self.sender.pubkey())
|
self.times_tracked_for(&self.sender.pubkey())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn times_tracked_for(&self, key: &PublicKey) -> (String, Box<dyn DoubleEndedIterator<Item=String>>) {
|
pub(crate) fn times_tracked_for(
|
||||||
match self.get_position_ref() {
|
&self,
|
||||||
|
key: &PublicKey,
|
||||||
|
) -> (String, Box<dyn DoubleEndedIterator<Item=String>>) {
|
||||||
|
match self.get_position() {
|
||||||
None => {
|
None => {
|
||||||
if let Some(hist) = self.history.get(key) {
|
if let Some(hist) = self.history.get(key) {
|
||||||
let mut last = None;
|
let mut last = None;
|
||||||
|
@ -278,7 +281,7 @@ impl TasksRelay {
|
||||||
}
|
}
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
// TODO show current recursive with pubkey
|
// TODO show current recursive with pubkey
|
||||||
let ids = vec![id];
|
let ids = [id];
|
||||||
let history =
|
let history =
|
||||||
self.history.iter().flat_map(|(key, set)| {
|
self.history.iter().flat_map(|(key, set)| {
|
||||||
let mut vec = Vec::with_capacity(set.len() / 2);
|
let mut vec = Vec::with_capacity(set.len() / 2);
|
||||||
|
@ -297,21 +300,26 @@ impl TasksRelay {
|
||||||
vec.push(format!("{} started by {}", format_timestamp_local(stamp), self.get_username(key))));
|
vec.push(format!("{} started by {}", format_timestamp_local(stamp), self.get_username(key))));
|
||||||
vec
|
vec
|
||||||
}).sorted_unstable(); // TODO sorting depends on timestamp format - needed to interleave different people
|
}).sorted_unstable(); // TODO sorting depends on timestamp format - needed to interleave different people
|
||||||
(format!("Times Tracked on {:?}", self.get_task_title(id)), Box::from(history))
|
(
|
||||||
|
format!("Times Tracked on {:?}", self.get_task_title(&id)),
|
||||||
|
Box::from(history),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Total time in seconds tracked on this task by the current user.
|
/// Total time in seconds tracked on this task by the current user.
|
||||||
pub(crate) fn time_tracked(&self, id: EventId) -> u64 {
|
pub(crate) fn time_tracked(&self, id: EventId) -> u64 {
|
||||||
Durations::from(self.get_own_events_history(), &vec![&id]).sum::<Duration>().as_secs()
|
Durations::from(self.get_own_events_history(), &[id])
|
||||||
|
.sum::<Duration>()
|
||||||
|
.as_secs()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Total time in seconds tracked on this task and its subtasks by all users.
|
/// Total time in seconds tracked on this task and its subtasks by all users.
|
||||||
fn total_time_tracked(&self, id: EventId) -> u64 {
|
fn total_time_tracked(&self, id: EventId) -> u64 {
|
||||||
let mut total = 0;
|
let mut total = 0;
|
||||||
|
|
||||||
let children = ChildIterator::from(&self, &id).get_all();
|
let children = ChildIterator::from(&self, id).get_all();
|
||||||
for user in self.history.values() {
|
for user in self.history.values() {
|
||||||
total += Durations::from(user.values(), &children).sum::<Duration>().as_secs();
|
total += Durations::from(user.values(), &children).sum::<Duration>().as_secs();
|
||||||
}
|
}
|
||||||
|
@ -325,7 +333,10 @@ impl TasksRelay {
|
||||||
_ => {
|
_ => {
|
||||||
let mut sum = 0f32;
|
let mut sum = 0f32;
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
for prog in self.tasks.children_ids_for(task.get_id()).filter_map(|e| self.total_progress(e)) {
|
for prog in self.tasks
|
||||||
|
.children_ids_for(task.event.id)
|
||||||
|
.filter_map(|e| self.total_progress(e))
|
||||||
|
{
|
||||||
sum += prog;
|
sum += prog;
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
@ -342,12 +353,13 @@ impl TasksRelay {
|
||||||
|
|
||||||
// Parents
|
// Parents
|
||||||
|
|
||||||
pub(crate) fn up_by(&self, count: usize) -> Option<&EventId> {
|
pub(crate) fn up_by(&self, count: usize) -> Option<EventId> {
|
||||||
let mut pos = self.get_position_ref();
|
let pos = self.get_position();
|
||||||
|
let mut result = pos.as_ref();
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
pos = self.get_parent(pos);
|
result = self.get_parent(result);
|
||||||
}
|
}
|
||||||
pos
|
result.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_parent(&self, id: Option<&EventId>) -> Option<&EventId> {
|
pub(crate) fn get_parent(&self, id: Option<&EventId>) -> Option<&EventId> {
|
||||||
|
@ -383,7 +395,7 @@ impl TasksRelay {
|
||||||
fn relative_path(&self, id: EventId) -> String {
|
fn relative_path(&self, id: EventId) -> String {
|
||||||
join_tasks(
|
join_tasks(
|
||||||
self.traverse_up_from(Some(id))
|
self.traverse_up_from(Some(id))
|
||||||
.take_while(|t| Some(&t.event.id) != self.get_position_ref()),
|
.take_while(|t| Some(t.event.id) != self.get_position()),
|
||||||
false,
|
false,
|
||||||
).unwrap_or(id.to_string())
|
).unwrap_or(id.to_string())
|
||||||
}
|
}
|
||||||
|
@ -421,16 +433,19 @@ impl TasksRelay {
|
||||||
}).collect_vec()
|
}).collect_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executes the given function with each task referenced by this event without marker.
|
/// Executes the given function with each task referenced by this event with no or property marker.
|
||||||
/// Returns true if any task was found.
|
/// Returns true if any task was found.
|
||||||
pub(crate) fn referenced_tasks<F: Fn(&mut Task)>(&mut self, event: &Event, f: F) -> bool {
|
pub(crate) fn referenced_tasks<F: Fn(&mut Task)>(&mut self, event: &Event, f: F) -> bool {
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
for tag in event.tags.iter() {
|
for tag in event.tags.iter() {
|
||||||
if let Some(TagStandard::Event { event_id, marker, .. }) = tag.as_standardized() {
|
if let Some(event_tag) = match_event_tag(tag) {
|
||||||
if marker.as_ref().is_none_or(|m| m.to_string() == MARKER_PROPERTY) {
|
if event_tag.marker
|
||||||
self.tasks.get_mut(event_id).map(|t| {
|
.as_ref()
|
||||||
|
.is_none_or(|m| m.to_string() == MARKER_PROPERTY)
|
||||||
|
{
|
||||||
|
self.tasks.get_mut(&event_tag.id).map(|t| {
|
||||||
found = true;
|
found = true;
|
||||||
f(t)
|
f(t);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -440,7 +455,7 @@ impl TasksRelay {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn get_current_task(&self) -> Option<&Task> {
|
pub(crate) fn get_current_task(&self) -> Option<&Task> {
|
||||||
self.get_position_ref().and_then(|id| self.get_by_id(id))
|
self.get_position().and_then(|id| self.get_by_id(&id))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn filter(&self, task: &Task) -> bool {
|
fn filter(&self, task: &Task) -> bool {
|
||||||
|
@ -455,12 +470,17 @@ impl TasksRelay {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn filtered_tasks<'a>(&'a self, position: Option<&'a EventId>, sparse: bool) -> Vec<&'a Task> {
|
// TODO sparse is deprecated
|
||||||
|
pub(crate) fn filtered_tasks(
|
||||||
|
&self,
|
||||||
|
position: Option<EventId>,
|
||||||
|
sparse: bool,
|
||||||
|
) -> Vec<&Task> {
|
||||||
let roots = self.tasks.children_for(position);
|
let roots = self.tasks.children_for(position);
|
||||||
let mut current = self.resolve_tasks_rec(roots, sparse, self.search_depth + self.view_depth);
|
let mut current = self.resolve_tasks_rec(roots, sparse, self.search_depth + self.view_depth);
|
||||||
if current.is_empty() {
|
if current.is_empty() {
|
||||||
if !self.tags.is_empty() {
|
if !self.tags.is_empty() {
|
||||||
let mut children = self.tasks.children_for(self.get_position_ref()).peekable();
|
let mut children = self.tasks.children_for(position).peekable();
|
||||||
if children.peek().is_some() {
|
if children.peek().is_some() {
|
||||||
current = self.resolve_tasks_rec(children, true, 9);
|
current = self.resolve_tasks_rec(children, true, 9);
|
||||||
if sparse {
|
if sparse {
|
||||||
|
@ -481,7 +501,7 @@ impl TasksRelay {
|
||||||
} else {
|
} else {
|
||||||
// TODO highlight bookmarks
|
// TODO highlight bookmarks
|
||||||
self.bookmarks.iter()
|
self.bookmarks.iter()
|
||||||
.filter(|id| !position.is_some_and(|p| &p == id) && !ids.contains(id))
|
.filter(|id| !position.is_some_and(|p| &&p == id) && !ids.contains(id))
|
||||||
.filter_map(|id| self.get_by_id(id))
|
.filter_map(|id| self.get_by_id(id))
|
||||||
.filter(|t| self.filter(t))
|
.filter(|t| self.filter(t))
|
||||||
.collect_vec()
|
.collect_vec()
|
||||||
|
@ -499,7 +519,7 @@ impl TasksRelay {
|
||||||
if !self.view.is_empty() {
|
if !self.view.is_empty() {
|
||||||
return self.view.iter().flat_map(|id| self.get_by_id(id)).collect();
|
return self.view.iter().flat_map(|id| self.get_by_id(id)).collect();
|
||||||
}
|
}
|
||||||
self.filtered_tasks(self.get_position_ref(), true)
|
self.filtered_tasks(self.get_position(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_property(&self, task: &Task, str: &str) -> String {
|
fn get_property(&self, task: &Task, str: &str) -> String {
|
||||||
|
@ -597,7 +617,7 @@ impl TasksRelay {
|
||||||
self.set_filter(|t| t.last_state_update() > time)
|
self.set_filter(|t| t.last_state_update() > time)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_filtered<P>(&self, position: Option<&EventId>, predicate: P) -> Vec<EventId>
|
pub(crate) fn get_filtered<P>(&self, position: Option<EventId>, predicate: P) -> Vec<EventId>
|
||||||
where
|
where
|
||||||
P: Fn(&&Task) -> bool,
|
P: Fn(&&Task) -> bool,
|
||||||
{
|
{
|
||||||
|
@ -612,7 +632,7 @@ impl TasksRelay {
|
||||||
where
|
where
|
||||||
P: Fn(&&Task) -> bool,
|
P: Fn(&&Task) -> bool,
|
||||||
{
|
{
|
||||||
self.set_view(self.get_filtered(self.get_position_ref(), predicate))
|
self.set_view(self.get_filtered(self.get_position(), predicate))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_view_bookmarks(&mut self) -> bool {
|
pub(crate) fn set_view_bookmarks(&mut self) -> bool {
|
||||||
|
@ -717,7 +737,7 @@ impl TasksRelay {
|
||||||
/// - single case-insensitive exact name match in all tasks
|
/// - single case-insensitive exact name match in all tasks
|
||||||
/// - visible tasks starting with given arg case-sensitive
|
/// - visible tasks starting with given arg case-sensitive
|
||||||
/// - visible tasks where any word starts with given arg case-insensitive
|
/// - visible tasks where any word starts with given arg case-insensitive
|
||||||
pub(crate) fn get_matching(&self, position: Option<&EventId>, arg: &str) -> Vec<EventId> {
|
pub(crate) fn get_matching(&self, position: Option<EventId>, arg: &str) -> Vec<EventId> {
|
||||||
if let Ok(id) = EventId::parse(arg) {
|
if let Ok(id) = EventId::parse(arg) {
|
||||||
return vec![id];
|
return vec![id];
|
||||||
}
|
}
|
||||||
|
@ -751,7 +771,7 @@ impl TasksRelay {
|
||||||
filtered = filtered_fuzzy;
|
filtered = filtered_fuzzy;
|
||||||
}
|
}
|
||||||
let immediate = filtered.iter().filter(
|
let immediate = filtered.iter().filter(
|
||||||
|t| self.get_by_id(t).is_some_and(|t| t.parent_id() == position)).collect_vec();
|
|t| self.get_by_id(t).is_some_and(|t| t.parent_id() == position.as_ref())).collect_vec();
|
||||||
if immediate.len() == 1 {
|
if immediate.len() == 1 {
|
||||||
return immediate.into_iter().cloned().collect_vec();
|
return immediate.into_iter().cloned().collect_vec();
|
||||||
}
|
}
|
||||||
|
@ -763,7 +783,11 @@ impl TasksRelay {
|
||||||
/// - entering the only matching task
|
/// - entering the only matching task
|
||||||
/// - creating a new task
|
/// - creating a new task
|
||||||
/// Returns an EventId if a new Task was created.
|
/// Returns an EventId if a new Task was created.
|
||||||
pub(crate) fn filter_or_create(&mut self, position: Option<&EventId>, arg: &str) -> Option<EventId> {
|
pub(crate) fn filter_or_create(
|
||||||
|
&mut self,
|
||||||
|
position: Option<EventId>,
|
||||||
|
arg: &str,
|
||||||
|
) -> Option<EventId> {
|
||||||
let filtered = self.get_matching(position, arg);
|
let filtered = self.get_matching(position, arg);
|
||||||
match filtered.len() {
|
match filtered.len() {
|
||||||
0 => {
|
0 => {
|
||||||
|
@ -782,7 +806,7 @@ impl TasksRelay {
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Multiple match, filter
|
// Multiple match, filter
|
||||||
self.move_to(position.cloned());
|
self.move_to(position);
|
||||||
self.set_view(filtered);
|
self.set_view(filtered);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -798,14 +822,17 @@ impl TasksRelay {
|
||||||
|
|
||||||
pub(crate) fn move_to(&mut self, target: Option<EventId>) {
|
pub(crate) fn move_to(&mut self, target: Option<EventId>) {
|
||||||
self.view.clear();
|
self.view.clear();
|
||||||
let pos = self.get_position_ref();
|
let pos = self.get_position();
|
||||||
if target.as_ref() == pos {
|
if target == pos {
|
||||||
debug!("Flushing Tasks because of move in place");
|
debug!("Flushing Tasks because of move in place");
|
||||||
self.flush();
|
self.flush();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !target.and_then(|id| self.tasks.get(&id)).is_some_and(|t| t.parent_id() == pos) {
|
if !target
|
||||||
|
.and_then(|id| self.get_by_id(&id))
|
||||||
|
.is_some_and(|t| t.parent_id() == pos.as_ref())
|
||||||
|
{
|
||||||
debug!("Flushing Tasks because of move beyond child");
|
debug!("Flushing Tasks because of move beyond child");
|
||||||
self.flush();
|
self.flush();
|
||||||
}
|
}
|
||||||
|
@ -841,21 +868,22 @@ impl TasksRelay {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parent_tag(&self) -> Option<Tag> {
|
pub(crate) fn parent_tag(&self) -> Option<Tag> {
|
||||||
self.get_position_ref().map(|p| self.make_event_tag_from_id(*p, MARKER_PARENT))
|
self.get_position()
|
||||||
|
.map(|p| self.make_event_tag_from_id(p, MARKER_PARENT))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn position_tags(&self) -> Vec<Tag> {
|
pub(crate) fn position_tags(&self) -> Vec<Tag> {
|
||||||
self.position_tags_for(self.get_position_ref())
|
self.position_tags_for(self.get_position())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn position_tags_for(&self, position: Option<&EventId>) -> Vec<Tag> {
|
pub(crate) fn position_tags_for(&self, position: Option<EventId>) -> Vec<Tag> {
|
||||||
position.map_or(vec![], |pos| {
|
position.map_or(vec![], |pos| {
|
||||||
let mut tags = Vec::with_capacity(2);
|
let mut tags = Vec::with_capacity(2);
|
||||||
tags.push(self.make_event_tag_from_id(*pos, MARKER_PARENT));
|
tags.push(self.make_event_tag_from_id(pos, MARKER_PARENT));
|
||||||
self.get_by_id(pos)
|
self.get_by_id(&pos).map(|task| {
|
||||||
.map(|task| {
|
|
||||||
if task.pure_state() == State::Procedure {
|
if task.pure_state() == State::Procedure {
|
||||||
self.tasks.children_of(task)
|
self.tasks
|
||||||
|
.children_of(task)
|
||||||
.max()
|
.max()
|
||||||
.map(|t| tags.push(self.make_event_tag(&t.event, MARKER_DEPENDS)));
|
.map(|t| tags.push(self.make_event_tag(&t.event, MARKER_DEPENDS)));
|
||||||
}
|
}
|
||||||
|
@ -938,23 +966,31 @@ impl TasksRelay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let current_pos = self.get_position_at(time);
|
let current_pos = self.get_position_at(time);
|
||||||
if (time < Timestamp::now() || target.is_none()) && current_pos.1 == target.as_ref() {
|
if (time < Timestamp::now() || target.is_none()) && current_pos.1 == target {
|
||||||
warn!("Already {} from {}",
|
warn!(
|
||||||
target.map_or("stopped time-tracking".to_string(),
|
"Already {} from {}",
|
||||||
|id| format!("tracking \"{}\"", self.get_task_title(&id))),
|
target.map_or("stopped time-tracking".to_string(), |id| format!(
|
||||||
|
"tracking \"{}\"",
|
||||||
|
self.get_task_title(&id)
|
||||||
|
)),
|
||||||
format_timestamp_relative(¤t_pos.0),
|
format_timestamp_relative(¤t_pos.0),
|
||||||
);
|
);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
info!("{}", match target {
|
info!("{}", match target {
|
||||||
None => format!("Stopping time-tracking of \"{}\" at {}",
|
None => format!(
|
||||||
current_pos.1.map_or("???".to_string(), |id| self.get_task_title(id)),
|
"Stopping time-tracking of \"{}\" at {}",
|
||||||
format_timestamp_relative(&time)),
|
current_pos.1.map_or("???".to_string(), |id| self.get_task_title(&id)),
|
||||||
Some(new_id) => format!("Tracking \"{}\" from {}{}",
|
format_timestamp_relative(&time)
|
||||||
|
),
|
||||||
|
Some(new_id) => format!(
|
||||||
|
"Tracking \"{}\" from {}{}",
|
||||||
self.get_task_title(&new_id),
|
self.get_task_title(&new_id),
|
||||||
format_timestamp_relative(&time),
|
format_timestamp_relative(&time),
|
||||||
current_pos.1.filter(|id| id != &&new_id).map(
|
current_pos.1.filter(|id| id != &new_id).map(|id|
|
||||||
|id| format!(" replacing \"{}\"", self.get_task_title(id))).unwrap_or_default()),
|
format!(" replacing \"{}\"", self.get_task_title(&id)))
|
||||||
|
.unwrap_or_default()
|
||||||
|
)
|
||||||
});
|
});
|
||||||
self.submit(
|
self.submit(
|
||||||
build_tracking(target)
|
build_tracking(target)
|
||||||
|
@ -983,14 +1019,19 @@ impl TasksRelay {
|
||||||
}
|
}
|
||||||
Kind::Bookmarks => {
|
Kind::Bookmarks => {
|
||||||
if event.pubkey == self.sender.pubkey() {
|
if event.pubkey == self.sender.pubkey() {
|
||||||
self.bookmarks = referenced_events(&event).cloned().collect_vec()
|
self.bookmarks = referenced_events(&event).collect_vec()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if event.kind == TRACKING_KIND {
|
if event.kind == TRACKING_KIND {
|
||||||
match self.history.get_mut(&event.pubkey) {
|
match self.history.get_mut(&event.pubkey) {
|
||||||
Some(c) => { c.insert(event.created_at, event); }
|
Some(c) => {
|
||||||
None => { self.history.insert(event.pubkey, BTreeMap::from([(event.created_at, event)])); }
|
c.insert(event.created_at, event);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.history
|
||||||
|
.insert(event.pubkey, BTreeMap::from([(event.created_at, event)]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Some(event) = self.add_prop(event) {
|
if let Some(event) = self.add_prop(event) {
|
||||||
|
@ -1044,12 +1085,13 @@ impl TasksRelay {
|
||||||
|
|
||||||
pub(crate) fn move_back_to(&mut self, str: &str) -> bool {
|
pub(crate) fn move_back_to(&mut self, str: &str) -> bool {
|
||||||
let lower = str.to_ascii_lowercase();
|
let lower = str.to_ascii_lowercase();
|
||||||
let found = self.history_before_now()
|
let found = self.history_before_now().find(|e| {
|
||||||
.find(|e| referenced_event(e)
|
referenced_event(e)
|
||||||
.and_then(|id| self.get_by_id(id))
|
.and_then(|id| self.get_by_id(&id))
|
||||||
.is_some_and(|t| t.event.content.to_ascii_lowercase().contains(&lower)));
|
.is_some_and(|t| t.event.content.to_ascii_lowercase().contains(&lower))
|
||||||
|
});
|
||||||
if let Some(event) = found {
|
if let Some(event) = found {
|
||||||
self.move_to(referenced_event(event).cloned());
|
self.move_to(referenced_event(event));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
|
@ -1058,7 +1100,7 @@ impl TasksRelay {
|
||||||
pub(crate) fn move_back_by(&mut self, steps: usize) {
|
pub(crate) fn move_back_by(&mut self, steps: usize) {
|
||||||
let id = self.history_before_now().nth(steps)
|
let id = self.history_before_now().nth(steps)
|
||||||
.and_then(|e| referenced_event(e));
|
.and_then(|e| referenced_event(e));
|
||||||
self.move_to(id.cloned())
|
self.move_to(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn undo(&mut self) {
|
pub(crate) fn undo(&mut self) {
|
||||||
|
@ -1073,9 +1115,12 @@ impl TasksRelay {
|
||||||
fn remove(&mut self, event: &Event) {
|
fn remove(&mut self, event: &Event) {
|
||||||
self.tasks.remove(&event.id);
|
self.tasks.remove(&event.id);
|
||||||
self.history.get_mut(&self.sender.pubkey())
|
self.history.get_mut(&self.sender.pubkey())
|
||||||
.map(|t| t.retain(|t, e| e != event &&
|
.map(|t| {
|
||||||
!referenced_event(e).is_some_and(|id| id == &event.id)));
|
t.retain(|_, e| e != event && !referenced_event(e).is_some_and(|id| id == event.id))
|
||||||
self.referenced_tasks(event, |t| { t.props.remove(event); });
|
});
|
||||||
|
self.referenced_tasks(event, |t| {
|
||||||
|
t.props.remove(event);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_state_for_with(&mut self, id: EventId, comment: &str) {
|
pub(crate) fn set_state_for_with(&mut self, id: EventId, comment: &str) {
|
||||||
|
@ -1086,26 +1131,33 @@ impl TasksRelay {
|
||||||
let ids =
|
let ids =
|
||||||
if state == State::Closed {
|
if state == State::Closed {
|
||||||
// Close whole subtree
|
// Close whole subtree
|
||||||
ChildIterator::from(self, &id).get_all()
|
ChildIterator::from(self, id).get_all()
|
||||||
} else {
|
} else {
|
||||||
vec![&id]
|
vec![id]
|
||||||
};
|
};
|
||||||
|
let (desc, tags) = extract_tags(comment);
|
||||||
let prop = EventBuilder::new(
|
let prop = EventBuilder::new(
|
||||||
state.into(),
|
state.into(),
|
||||||
comment,
|
desc,
|
||||||
ids.into_iter().map(|e| self.make_event_tag_from_id(*e, MARKER_PROPERTY)),
|
ids.into_iter()
|
||||||
|
.map(|e| self.make_event_tag_from_id(e, MARKER_PROPERTY))
|
||||||
|
.chain(tags),
|
||||||
);
|
);
|
||||||
// if self.custom_time.is_none() && self.get_by_id(id).map(|task| {}) {}
|
// if self.custom_time.is_none() && self.get_by_id(id).map(|task| {}) {}
|
||||||
info!("Task status {} set for \"{}\"{}",
|
info!(
|
||||||
|
"Task status {} set for \"{}\"{}",
|
||||||
TaskState::get_label_for(&state, comment),
|
TaskState::get_label_for(&state, comment),
|
||||||
self.get_task_title(&id),
|
self.get_task_title(&id),
|
||||||
self.custom_time.map(|ts| format!(" at {}", format_timestamp_relative(&ts))).unwrap_or_default());
|
self.custom_time
|
||||||
|
.map(|ts| format!(" at {}", format_timestamp_relative(&ts)))
|
||||||
|
.unwrap_or_default()
|
||||||
|
);
|
||||||
self.submit(prop)
|
self.submit(prop)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_state(&mut self, comment: &str, state: State) -> Option<EventId> {
|
pub(crate) fn update_state(&mut self, comment: &str, state: State) -> Option<EventId> {
|
||||||
let id = self.get_position_ref()?;
|
let id = self.get_position()?;
|
||||||
Some(self.set_state_for(*id, comment, state))
|
Some(self.set_state_for(id, comment, state))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a note or activity, depending on whether the parent is a task.
|
/// Creates a note or activity, depending on whether the parent is a task.
|
||||||
|
@ -1172,7 +1224,7 @@ impl Display for TasksRelay {
|
||||||
let now = &now();
|
let now = &now();
|
||||||
let mut tracking_stamp: Option<Timestamp> = None;
|
let mut tracking_stamp: Option<Timestamp> = None;
|
||||||
for elem in
|
for elem in
|
||||||
timestamps(self.get_own_events_history(), &[t.get_id()])
|
timestamps(self.get_own_events_history(), &[t.event.id])
|
||||||
.map(|(e, _)| e) {
|
.map(|(e, _)| e) {
|
||||||
if tracking_stamp.is_some() && elem > now {
|
if tracking_stamp.is_some() && elem > now {
|
||||||
break;
|
break;
|
||||||
|
@ -1190,11 +1242,17 @@ impl Display for TasksRelay {
|
||||||
writeln!(lock, "{}", t.descriptions().join("\n"))?;
|
writeln!(lock, "{}", t.descriptions().join("\n"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let position = self.get_position_ref();
|
let position = self.get_position();
|
||||||
let mut current: Vec<&Task>;
|
let mut current: Vec<&Task>;
|
||||||
let roots = self.view.iter().flat_map(|id| self.get_by_id(id)).collect_vec();
|
let view = self.view.iter()
|
||||||
if self.search_depth > 0 && roots.is_empty() {
|
.flat_map(|id| self.get_by_id(id))
|
||||||
current = self.resolve_tasks_rec(self.tasks.children_for(position), true, self.search_depth + self.view_depth);
|
.collect_vec();
|
||||||
|
if self.search_depth > 0 && view.is_empty() {
|
||||||
|
current = self.resolve_tasks_rec(
|
||||||
|
self.tasks.children_for(position),
|
||||||
|
true,
|
||||||
|
self.search_depth + self.view_depth,
|
||||||
|
);
|
||||||
if current.is_empty() {
|
if current.is_empty() {
|
||||||
if !self.tags.is_empty() {
|
if !self.tags.is_empty() {
|
||||||
let mut children = self.tasks.children_for(position).peekable();
|
let mut children = self.tasks.children_for(position).peekable();
|
||||||
|
@ -1209,7 +1267,7 @@ impl Display for TasksRelay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
current = self.resolve_tasks_rec(roots.iter().cloned(), true, self.view_depth);
|
current = self.resolve_tasks_rec(view.into_iter(), true, self.view_depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
if current.is_empty() {
|
if current.is_empty() {
|
||||||
|
@ -1221,8 +1279,10 @@ impl Display for TasksRelay {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let tree = current.iter().flat_map(|task| self.traverse_up_from(Some(task.event.id))).unique();
|
let tree = current.iter()
|
||||||
let ids: HashSet<&EventId> = tree.map(|t| t.get_id()).chain(position).collect();
|
.flat_map(|task| self.traverse_up_from(Some(task.event.id)))
|
||||||
|
.unique();
|
||||||
|
let ids: HashSet<&EventId> = tree.map(|t| t.get_id()).chain(position.as_ref()).collect();
|
||||||
if self.view.is_empty() {
|
if self.view.is_empty() {
|
||||||
let mut bookmarks =
|
let mut bookmarks =
|
||||||
// TODO add recent tasks (most time tracked + recently created)
|
// TODO add recent tasks (most time tracked + recently created)
|
||||||
|
@ -1355,25 +1415,27 @@ pub(crate) fn join_tasks<'a>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn referenced_events(event: &Event) -> impl Iterator<Item=&EventId> {
|
fn referenced_events(event: &Event) -> impl Iterator<Item=EventId> + use < '_ > {
|
||||||
event.tags.iter().filter_map(|tag| match tag.as_standardized() {
|
event.tags.iter()
|
||||||
Some(TagStandard::Event { event_id, .. }) => Some(event_id),
|
.filter_map(| tag | match_event_tag(tag).map(| t | t.id))
|
||||||
_ => None
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn referenced_event(event: &Event) -> Option<&EventId> {
|
fn referenced_event(event: &Event) -> Option<EventId> {
|
||||||
referenced_events(event).next()
|
referenced_events(event).next()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the id of a referenced event if it is contained in the provided ids list.
|
/// Returns the id of a referenced event if it is contained in the provided ids list.
|
||||||
fn matching_tag_id<'a>(event: &'a Event, ids: &'a [&'a EventId]) -> Option<&'a EventId> {
|
fn matching_tag_id<'a>(event: &'a Event, ids: &'a [EventId]) -> Option<EventId> {
|
||||||
referenced_events(event).find(|id| ids.contains(id))
|
referenced_events(event).find(|id| ids.contains(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Filters out event timestamps to those that start or stop one of the given events
|
/// Filters out event timestamps to those that start or stop one of the given events
|
||||||
fn timestamps<'a>(events: impl Iterator<Item=&'a Event>, ids: &'a [&'a EventId]) -> impl Iterator<Item=(&Timestamp, Option<&EventId>)> {
|
fn timestamps<'a>(
|
||||||
events.map(|event| (&event.created_at, matching_tag_id(event, ids)))
|
events: impl Iterator<Item=&'a Event>,
|
||||||
|
ids: &'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)
|
.dedup_by(|(_, e1), (_, e2)| e1 == e2)
|
||||||
.skip_while(|element| element.1.is_none())
|
.skip_while(|element| element.1.is_none())
|
||||||
}
|
}
|
||||||
|
@ -1382,11 +1444,14 @@ fn timestamps<'a>(events: impl Iterator<Item=&'a Event>, ids: &'a [&'a EventId])
|
||||||
/// Expects a sorted iterator
|
/// Expects a sorted iterator
|
||||||
struct Durations<'a> {
|
struct Durations<'a> {
|
||||||
events: Box<dyn Iterator<Item=&'a Event> + 'a>,
|
events: Box<dyn Iterator<Item=&'a Event> + 'a>,
|
||||||
ids: &'a Vec<&'a EventId>,
|
ids: &'a [EventId],
|
||||||
threshold: Option<Timestamp>,
|
threshold: Option<Timestamp>,
|
||||||
}
|
}
|
||||||
impl Durations<'_> {
|
impl Durations<'_> {
|
||||||
fn from<'b>(events: impl IntoIterator<Item=&'b Event> + 'b, ids: &'b Vec<&EventId>) -> Durations<'b> {
|
fn from<'b>(
|
||||||
|
events: impl IntoIterator<Item=&'b Event> + 'b,
|
||||||
|
ids: &'b [EventId],
|
||||||
|
) -> Durations<'b> {
|
||||||
Durations {
|
Durations {
|
||||||
events: Box::new(events.into_iter()),
|
events: Box::new(events.into_iter()),
|
||||||
ids,
|
ids,
|
||||||
|
@ -1438,7 +1503,7 @@ impl ChildIteratorFilter {
|
||||||
struct ChildIterator<'a> {
|
struct ChildIterator<'a> {
|
||||||
tasks: &'a TaskMap,
|
tasks: &'a TaskMap,
|
||||||
/// Found Events
|
/// Found Events
|
||||||
queue: Vec<&'a EventId>,
|
queue: Vec<EventId>,
|
||||||
/// Index of the next element in the queue
|
/// Index of the next element in the queue
|
||||||
index: usize,
|
index: usize,
|
||||||
/// Depth of the next element
|
/// Depth of the next element
|
||||||
|
@ -1453,13 +1518,13 @@ impl<'a> ChildIterator<'a> {
|
||||||
&mut tasks
|
&mut tasks
|
||||||
.values()
|
.values()
|
||||||
.filter(move |t| t.parent_id() == id)
|
.filter(move |t| t.parent_id() == id)
|
||||||
.map(|t| t.get_id())
|
.map(|t| t.event.id)
|
||||||
.collect_vec()
|
.collect_vec()
|
||||||
);
|
);
|
||||||
Self::with_queue(tasks, queue)
|
Self::with_queue(tasks, queue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_queue(tasks: &'a TaskMap, queue: Vec<&'a EventId>) -> Self {
|
fn with_queue(tasks: &'a TaskMap, queue: Vec<EventId>) -> Self {
|
||||||
ChildIterator {
|
ChildIterator {
|
||||||
tasks: &tasks,
|
tasks: &tasks,
|
||||||
next_depth_at: queue.len(),
|
next_depth_at: queue.len(),
|
||||||
|
@ -1469,8 +1534,8 @@ impl<'a> ChildIterator<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from(tasks: &'a TasksRelay, id: &'a EventId) -> Self {
|
fn from(tasks: &'a TasksRelay, id: EventId) -> Self {
|
||||||
let mut queue = Vec::with_capacity(30);
|
let mut queue = Vec::with_capacity(64);
|
||||||
queue.push(id);
|
queue.push(id);
|
||||||
ChildIterator {
|
ChildIterator {
|
||||||
tasks: &tasks.tasks,
|
tasks: &tasks.tasks,
|
||||||
|
@ -1493,40 +1558,17 @@ impl<'a> ChildIterator<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all children
|
/// Get all children
|
||||||
fn get_all(mut self) -> Vec<&'a EventId> {
|
fn get_all(mut self) -> Vec<EventId> {
|
||||||
while self.next().is_some() {}
|
while self.next().is_some() {}
|
||||||
self.queue
|
self.queue
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all tasks until the specified depth
|
/// Get all tasks until the specified depth
|
||||||
fn get_depth(mut self, depth: usize) -> Vec<&'a EventId> {
|
fn get_depth(mut self, depth: usize) -> Vec<EventId> {
|
||||||
self.process_depth(depth);
|
self.process_depth(depth);
|
||||||
self.queue
|
self.queue
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all tasks until the specified depth matching the filter
|
|
||||||
fn get_depth_filtered<F>(mut self, depth: usize, filter: F) -> Vec<&'a EventId>
|
|
||||||
where
|
|
||||||
F: Fn(&Task) -> ChildIteratorFilter,
|
|
||||||
{
|
|
||||||
while self.depth < depth {
|
|
||||||
if self.next_filtered(&filter).is_none() {
|
|
||||||
// TODO this can easily recurse beyond the intended depth
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while self.index < self.queue.len() {
|
|
||||||
if let Some(task) = self.tasks.get(self.queue[self.index]) {
|
|
||||||
if !filter(task).takes_self() {
|
|
||||||
self.queue.remove(self.index);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.index += 1;
|
|
||||||
}
|
|
||||||
self.queue
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_depth(&mut self) {
|
fn check_depth(&mut self) {
|
||||||
if self.next_depth_at == self.index {
|
if self.next_depth_at == self.index {
|
||||||
self.depth += 1;
|
self.depth += 1;
|
||||||
|
@ -1535,7 +1577,7 @@ impl<'a> ChildIterator<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get next id and advance, without adding children
|
/// Get next id and advance, without adding children
|
||||||
fn next_task(&mut self) -> Option<&'a EventId> {
|
fn next_task(&mut self) -> Option<EventId> {
|
||||||
if self.index >= self.queue.len() {
|
if self.index >= self.queue.len() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -1550,7 +1592,7 @@ impl<'a> ChildIterator<'a> {
|
||||||
F: Fn(&Task) -> ChildIteratorFilter,
|
F: Fn(&Task) -> ChildIteratorFilter,
|
||||||
{
|
{
|
||||||
self.next_task().and_then(|id| {
|
self.next_task().and_then(|id| {
|
||||||
if let Some(task) = self.tasks.get(id) {
|
if let Some(task) = self.tasks.get(&id) {
|
||||||
let take = filter(task);
|
let take = filter(task);
|
||||||
if take.takes_children() {
|
if take.takes_children() {
|
||||||
self.queue_children_of(&task);
|
self.queue_children_of(&task);
|
||||||
|
@ -1566,12 +1608,12 @@ impl<'a> ChildIterator<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queue_children_of(&mut self, task: &'a Task) {
|
fn queue_children_of(&mut self, task: &'a Task) {
|
||||||
self.queue.extend(self.tasks.children_ids_for(task.get_id()));
|
self.queue.extend(self.tasks.children_ids_for(task.event.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl FusedIterator for ChildIterator<'_> {}
|
impl FusedIterator for ChildIterator<'_> {}
|
||||||
impl<'a> Iterator for ChildIterator<'a> {
|
impl<'a> Iterator for ChildIterator<'a> {
|
||||||
type Item = &'a EventId;
|
type Item = EventId;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
self.next_task().inspect(|id| {
|
self.next_task().inspect(|id| {
|
||||||
|
@ -1579,8 +1621,8 @@ impl<'a> Iterator for ChildIterator<'a> {
|
||||||
None => {
|
None => {
|
||||||
// Unknown task, might still find children, just slower
|
// Unknown task, might still find children, just slower
|
||||||
for task in self.tasks.values() {
|
for task in self.tasks.values() {
|
||||||
if task.parent_id().is_some_and(|i| i == *id) {
|
if task.parent_id().is_some_and(|i| i == id) {
|
||||||
self.queue.push(task.get_id());
|
self.queue.push(task.event.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1630,7 +1672,7 @@ mod tasks_test {
|
||||||
|
|
||||||
macro_rules! assert_position {
|
macro_rules! assert_position {
|
||||||
($left:expr, $right:expr $(,)?) => {
|
($left:expr, $right:expr $(,)?) => {
|
||||||
assert_eq!($left.get_position_ref(), Some(&$right))
|
assert_eq!($left.get_position(), Some($right))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1667,6 +1709,8 @@ mod tasks_test {
|
||||||
tasks.custom_time = Some(now());
|
tasks.custom_time = Some(now());
|
||||||
tasks.update_state("Closing Down", State::Closed);
|
tasks.update_state("Closing Down", State::Closed);
|
||||||
assert_eq!(tasks.get_by_id(&sub).unwrap().pure_state(), State::Closed);
|
assert_eq!(tasks.get_by_id(&sub).unwrap().pure_state(), State::Closed);
|
||||||
|
assert_eq!(tasks.get_by_id(&parent).unwrap().pure_state(), State::Closed);
|
||||||
|
assert_eq!(tasks.nonclosed_tasks().next(), None);
|
||||||
assert_eq!(tasks.all_hashtags().next(), None);
|
assert_eq!(tasks.all_hashtags().next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1701,10 +1745,10 @@ mod tasks_test {
|
||||||
tasks.search_depth = 1;
|
tasks.search_depth = 1;
|
||||||
assert_eq!(tasks.filtered_tasks(None, true).len(), 2);
|
assert_eq!(tasks.filtered_tasks(None, true).len(), 2);
|
||||||
assert_eq!(tasks.filtered_tasks(None, false).len(), 2);
|
assert_eq!(tasks.filtered_tasks(None, false).len(), 2);
|
||||||
assert_eq!(tasks.filtered_tasks(Some(&zero), false).len(), 0);
|
assert_eq!(tasks.filtered_tasks(Some(zero), false).len(), 0);
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
assert_eq!(tasks.filtered_tasks(Some(parent), false).len(), 1);
|
||||||
assert_eq!(tasks.filtered_tasks(Some(&pin), false).len(), 0);
|
assert_eq!(tasks.filtered_tasks(Some(pin), false).len(), 0);
|
||||||
assert_eq!(tasks.filtered_tasks(Some(&zero), false).len(), 0);
|
assert_eq!(tasks.filtered_tasks(Some(zero), false).len(), 0);
|
||||||
|
|
||||||
tasks.submit(EventBuilder::new(
|
tasks.submit(EventBuilder::new(
|
||||||
Kind::Bookmarks,
|
Kind::Bookmarks,
|
||||||
|
@ -1712,11 +1756,11 @@ mod tasks_test {
|
||||||
[Tag::event(pin), Tag::event(zero)],
|
[Tag::event(pin), Tag::event(zero)],
|
||||||
));
|
));
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||||
assert_eq!(tasks.filtered_tasks(Some(&pin), true).len(), 0);
|
assert_eq!(tasks.filtered_tasks(Some(pin), true).len(), 0);
|
||||||
assert_eq!(tasks.filtered_tasks(Some(&pin), false).len(), 0);
|
assert_eq!(tasks.filtered_tasks(Some(pin), false).len(), 0);
|
||||||
assert_eq!(tasks.filtered_tasks(Some(&zero), true).len(), 0);
|
assert_eq!(tasks.filtered_tasks(Some(zero), true).len(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tasks.filtered_tasks(Some(&zero), false),
|
tasks.filtered_tasks(Some(zero), false),
|
||||||
vec![tasks.get_by_id(&pin).unwrap()]
|
vec![tasks.get_by_id(&pin).unwrap()]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1761,19 +1805,19 @@ mod tasks_test {
|
||||||
fn test_filter_or_create() {
|
fn test_filter_or_create() {
|
||||||
let mut tasks = stub_tasks();
|
let mut tasks = stub_tasks();
|
||||||
let zeros = EventId::all_zeros();
|
let zeros = EventId::all_zeros();
|
||||||
let zero = Some(&zeros);
|
let zero = Some(zeros);
|
||||||
|
|
||||||
let id1 = tasks.filter_or_create(zero, "newer");
|
let id1 = tasks.filter_or_create(zero, "newer");
|
||||||
assert_eq!(tasks.len(), 1);
|
assert_eq!(tasks.len(), 1);
|
||||||
assert_eq!(tasks.visible_tasks().len(), 0);
|
assert_eq!(tasks.visible_tasks().len(), 0);
|
||||||
assert_eq!(tasks.get_by_id(&id1.unwrap()).unwrap().parent_id(), zero);
|
assert_eq!(tasks.get_by_id(&id1.unwrap()).unwrap().parent_id(), zero.as_ref());
|
||||||
|
|
||||||
tasks.move_to(zero.cloned());
|
tasks.move_to(zero);
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||||
let sub = tasks.make_task("test");
|
let sub = tasks.make_task("test");
|
||||||
assert_eq!(tasks.len(), 2);
|
assert_eq!(tasks.len(), 2);
|
||||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
assert_eq!(tasks.visible_tasks().len(), 2);
|
||||||
assert_eq!(tasks.get_by_id(&sub).unwrap().parent_id(), zero);
|
assert_eq!(tasks.get_by_id(&sub).unwrap().parent_id(), zero.as_ref());
|
||||||
|
|
||||||
// Do not substring match invisible subtask
|
// Do not substring match invisible subtask
|
||||||
let id2 = tasks.filter_or_create(None, "#new-is gold wrapped").unwrap();
|
let id2 = tasks.filter_or_create(None, "#new-is gold wrapped").unwrap();
|
||||||
|
@ -1783,8 +1827,8 @@ mod tasks_test {
|
||||||
assert_eq!(new2.props, Default::default());
|
assert_eq!(new2.props, Default::default());
|
||||||
|
|
||||||
tasks.move_up();
|
tasks.move_up();
|
||||||
assert_eq!(tasks.get_matching(tasks.get_position_ref(), "wrapped").len(), 1);
|
assert_eq!(tasks.get_matching(tasks.get_position(), "wrapped").len(), 1);
|
||||||
assert_eq!(tasks.get_matching(tasks.get_position_ref(), "new-i").len(), 1);
|
assert_eq!(tasks.get_matching(tasks.get_position(), "new-i").len(), 1);
|
||||||
tasks.filter_or_create(None, "is gold");
|
tasks.filter_or_create(None, "is gold");
|
||||||
assert_position!(tasks, id2);
|
assert_position!(tasks, id2);
|
||||||
|
|
||||||
|
@ -1836,7 +1880,7 @@ mod tasks_test {
|
||||||
|
|
||||||
tasks.track_at(Timestamp::from(Timestamp::now().as_u64() + 100), Some(zero));
|
tasks.track_at(Timestamp::from(Timestamp::now().as_u64() + 100), Some(zero));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
timestamps(tasks.get_own_events_history(), &vec![&zero])
|
timestamps(tasks.get_own_events_history(), &[zero])
|
||||||
.collect_vec()
|
.collect_vec()
|
||||||
.len(),
|
.len(),
|
||||||
2
|
2
|
||||||
|
@ -1881,13 +1925,13 @@ mod tasks_test {
|
||||||
tasks.view_depth = 2;
|
tasks.view_depth = 2;
|
||||||
assert_tasks!(tasks, [t111]);
|
assert_tasks!(tasks, [t111]);
|
||||||
|
|
||||||
assert_eq!(ChildIterator::from(&tasks, &EventId::all_zeros()).get_all().len(), 1);
|
assert_eq!(ChildIterator::from(&tasks, EventId::all_zeros()).get_all().len(), 1);
|
||||||
assert_eq!(ChildIterator::from(&tasks, &EventId::all_zeros()).get_depth(0).len(), 1);
|
assert_eq!(ChildIterator::from(&tasks, EventId::all_zeros()).get_depth(0).len(), 1);
|
||||||
assert_eq!(ChildIterator::from(&tasks, &t1).get_depth(0).len(), 1);
|
assert_eq!(ChildIterator::from(&tasks, t1).get_depth(0).len(), 1);
|
||||||
assert_eq!(ChildIterator::from(&tasks, &t1).get_depth(1).len(), 3);
|
assert_eq!(ChildIterator::from(&tasks, t1).get_depth(1).len(), 3);
|
||||||
assert_eq!(ChildIterator::from(&tasks, &t1).get_depth(2).len(), 4);
|
assert_eq!(ChildIterator::from(&tasks, t1).get_depth(2).len(), 4);
|
||||||
assert_eq!(ChildIterator::from(&tasks, &t1).get_depth(9).len(), 4);
|
assert_eq!(ChildIterator::from(&tasks, t1).get_depth(9).len(), 4);
|
||||||
assert_eq!(ChildIterator::from(&tasks, &t1).get_all().len(), 4);
|
assert_eq!(ChildIterator::from(&tasks, t1).get_all().len(), 4);
|
||||||
|
|
||||||
tasks.move_to(Some(t1));
|
tasks.move_to(Some(t1));
|
||||||
assert_position!(tasks, t1);
|
assert_position!(tasks, t1);
|
||||||
|
|
Loading…
Reference in New Issue