forked from janek/mostr
feat(tasks): derive position from history
This commit is contained in:
parent
a4f9398846
commit
ebfe632497
77
src/tasks.rs
77
src/tasks.rs
|
@ -35,8 +35,6 @@ pub(crate) struct Tasks {
|
||||||
/// The task properties sorted by
|
/// The task properties sorted by
|
||||||
sorting: VecDeque<String>,
|
sorting: VecDeque<String>,
|
||||||
|
|
||||||
/// Currently active task
|
|
||||||
position: Option<EventId>,
|
|
||||||
/// A filtered view of the current tasks
|
/// A filtered view of the current tasks
|
||||||
view: Vec<EventId>,
|
view: Vec<EventId>,
|
||||||
/// Negative: Only Leaf nodes
|
/// Negative: Only Leaf nodes
|
||||||
|
@ -135,7 +133,6 @@ impl Tasks {
|
||||||
"rtime".into(),
|
"rtime".into(),
|
||||||
"name".into(),
|
"name".into(),
|
||||||
]),
|
]),
|
||||||
position: None, // TODO persist position
|
|
||||||
view: Default::default(),
|
view: Default::default(),
|
||||||
tags: Default::default(),
|
tags: Default::default(),
|
||||||
tags_excluded: Default::default(),
|
tags_excluded: Default::default(),
|
||||||
|
@ -150,12 +147,16 @@ impl Tasks {
|
||||||
#[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]
|
|
||||||
pub(crate) fn get_position(&self) -> Option<EventId> { self.position }
|
|
||||||
|
|
||||||
#[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> {
|
||||||
|
self.history.get(&self.sender.pubkey())
|
||||||
|
.and_then(|tree| tree.last())
|
||||||
|
.and_then(|e| referenced_events(e))
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
/// Ids of all subtasks recursively found for id, including itself
|
/// Ids of all subtasks recursively found for id, including itself
|
||||||
pub(crate) fn get_task_tree<'a>(&'a self, id: &'a EventId) -> ChildIterator {
|
pub(crate) fn get_task_tree<'a>(&'a self, id: &'a EventId) -> ChildIterator {
|
||||||
ChildIterator::from(&self.tasks, id)
|
ChildIterator::from(&self.tasks, id)
|
||||||
|
@ -291,7 +292,7 @@ impl Tasks {
|
||||||
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.position),
|
.take_while(|t| Some(t.event.id) != self.get_position()),
|
||||||
false,
|
false,
|
||||||
).unwrap_or(id.to_string())
|
).unwrap_or(id.to_string())
|
||||||
}
|
}
|
||||||
|
@ -348,7 +349,7 @@ impl Tasks {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn get_current_task(&self) -> Option<&Task> {
|
pub(crate) fn get_current_task(&self) -> Option<&Task> {
|
||||||
self.position.and_then(|id| self.get_by_id(&id))
|
self.get_position().and_then(|id| self.get_by_id(&id))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn children_of(&self, id: Option<EventId>) -> impl Iterator<Item=&EventId> + '_ {
|
pub(crate) fn children_of(&self, id: Option<EventId>) -> impl Iterator<Item=&EventId> + '_ {
|
||||||
|
@ -382,7 +383,7 @@ impl Tasks {
|
||||||
if self.view.len() > 0 {
|
if self.view.len() > 0 {
|
||||||
return self.resolve_tasks(self.view.iter()).collect();
|
return self.resolve_tasks(self.view.iter()).collect();
|
||||||
}
|
}
|
||||||
self.filtered_tasks(self.position).collect()
|
self.filtered_tasks(self.get_position()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn print_tasks(&self) -> Result<(), Error> {
|
pub(crate) fn print_tasks(&self) -> Result<(), Error> {
|
||||||
|
@ -429,7 +430,7 @@ impl Tasks {
|
||||||
.map(|p| self.get_property(task, p.as_str()))
|
.map(|p| self.get_property(task, p.as_str()))
|
||||||
.join(" \t")
|
.join(" \t")
|
||||||
)?;
|
)?;
|
||||||
if self.depth < 2 || task.parent_id() == self.position.as_ref() {
|
if self.depth < 2 || task.parent_id() == self.get_position().as_ref() {
|
||||||
total_time += self.total_time_tracked(task.event.id)
|
total_time += self.total_time_tracked(task.event.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -553,7 +554,7 @@ impl Tasks {
|
||||||
let mut filtered: Vec<EventId> = Vec::with_capacity(32);
|
let mut filtered: Vec<EventId> = Vec::with_capacity(32);
|
||||||
let lowercase_arg = arg.to_ascii_lowercase();
|
let lowercase_arg = arg.to_ascii_lowercase();
|
||||||
let mut filtered_more: Vec<EventId> = Vec::with_capacity(32);
|
let mut filtered_more: Vec<EventId> = Vec::with_capacity(32);
|
||||||
for task in self.filtered_tasks(self.position) {
|
for task in self.filtered_tasks(self.get_position()) {
|
||||||
let lowercase = task.event.content.to_ascii_lowercase();
|
let lowercase = task.event.content.to_ascii_lowercase();
|
||||||
if lowercase == lowercase_arg {
|
if lowercase == lowercase_arg {
|
||||||
return vec![task.event.id];
|
return vec![task.event.id];
|
||||||
|
@ -599,17 +600,24 @@ impl Tasks {
|
||||||
|
|
||||||
pub(crate) fn move_to(&mut self, id: Option<EventId>) {
|
pub(crate) fn move_to(&mut self, id: Option<EventId>) {
|
||||||
self.view.clear();
|
self.view.clear();
|
||||||
if id == self.position {
|
if id == self.get_position() {
|
||||||
debug!("Flushing Tasks because of move in place");
|
debug!("Flushing Tasks because of move in place");
|
||||||
self.flush();
|
self.flush();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.submit(build_tracking(id));
|
|
||||||
if !id.and_then(|id| self.tasks.get(&id)).is_some_and(|t| t.parent_id() == self.position.as_ref()) {
|
let now = Timestamp::now();
|
||||||
|
let offset: u64 = self.get_own_history().map_or(0, |hist| {
|
||||||
|
hist.iter().rev().take_while(|e| e.created_at >= now).skip_while(|e| e.created_at.as_u64() > now.as_u64() + 99).count() as u64
|
||||||
|
});
|
||||||
|
self.submit(
|
||||||
|
build_tracking(id)
|
||||||
|
.custom_created_at(Timestamp::from(now.as_u64() + offset))
|
||||||
|
);
|
||||||
|
if !id.and_then(|id| self.tasks.get(&id)).is_some_and(|t| t.parent_id() == self.get_position().as_ref()) {
|
||||||
debug!("Flushing Tasks because of move beyond child");
|
debug!("Flushing Tasks because of move beyond child");
|
||||||
self.flush();
|
self.flush();
|
||||||
}
|
}
|
||||||
self.position = id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates
|
// Updates
|
||||||
|
@ -633,7 +641,7 @@ impl Tasks {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parent_tag(&self) -> Option<Tag> {
|
pub(crate) fn parent_tag(&self) -> Option<Tag> {
|
||||||
self.position.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> {
|
||||||
|
@ -657,10 +665,11 @@ impl Tasks {
|
||||||
self.make_task_with(input, empty(), true)
|
self.make_task_with(input, empty(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn make_task_and_enter(&mut self, input: &str, state: State) {
|
pub(crate) fn make_task_and_enter(&mut self, input: &str, state: State) -> EventId {
|
||||||
let id = self.make_task_with(input, empty(), false);
|
let id = self.make_task_with(input, empty(), false);
|
||||||
self.set_state_for(id, "", state);
|
self.set_state_for(id, "", state);
|
||||||
self.move_to(Some(id));
|
self.move_to(Some(id));
|
||||||
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a task with tags from filter and position
|
/// Creates a task with tags from filter and position
|
||||||
|
@ -764,7 +773,7 @@ impl Tasks {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&mut self, event: &Event) {
|
fn remove(&mut self, event: &Event) {
|
||||||
if let Some(pos) = self.position {
|
if let Some(pos) = self.get_position() {
|
||||||
if pos == event.id {
|
if pos == event.id {
|
||||||
self.move_up()
|
self.move_up()
|
||||||
}
|
}
|
||||||
|
@ -789,12 +798,12 @@ impl Tasks {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_state(&mut self, comment: &str, state: State) {
|
pub(crate) fn update_state(&mut self, comment: &str, state: State) {
|
||||||
self.position
|
self.get_position()
|
||||||
.map(|id| self.set_state_for(id, comment, state));
|
.map(|id| self.set_state_for(id, comment, state));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn make_note(&mut self, note: &str) {
|
pub(crate) fn make_note(&mut self, note: &str) {
|
||||||
if let Some(id) = self.position {
|
if let Some(id) = self.get_position() {
|
||||||
if self.get_by_id(&id).is_some_and(|t| t.is_task()) {
|
if self.get_by_id(&id).is_some_and(|t| t.is_task()) {
|
||||||
let prop = build_prop(Kind::TextNote, note.trim(), id);
|
let prop = build_prop(Kind::TextNote, note.trim(), id);
|
||||||
self.submit(prop);
|
self.submit(prop);
|
||||||
|
@ -906,6 +915,13 @@ pub(crate) fn join_tasks<'a>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn referenced_events(event: &Event) -> Option<&EventId> {
|
||||||
|
event.tags.iter().find_map(|tag| match tag.as_standardized() {
|
||||||
|
Some(TagStandard::Event { event_id, .. }) => Some(event_id),
|
||||||
|
_ => None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn matching_tag_id<'a>(event: &'a Event, ids: &'a Vec<&'a 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() {
|
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),
|
||||||
|
@ -1047,7 +1063,9 @@ mod tasks_test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_procedures() {
|
fn test_procedures() {
|
||||||
let mut tasks = stub_tasks();
|
let mut tasks = stub_tasks();
|
||||||
tasks.make_task_and_enter("proc: tags", State::Procedure);
|
let id = tasks.make_task_and_enter("proc: tags", State::Procedure);
|
||||||
|
assert_eq!(tasks.get_position(), Some(id));
|
||||||
|
assert_eq!(tasks.get_own_history().unwrap().len(), 1);
|
||||||
let side = tasks.submit(build_task("side", vec![tasks.make_event_tag(&tasks.get_current_task().unwrap().event, MARKER_DEPENDS)], None));
|
let side = tasks.submit(build_task("side", vec![tasks.make_event_tag(&tasks.get_current_task().unwrap().event, MARKER_DEPENDS)], None));
|
||||||
assert_eq!(tasks.get_current_task().unwrap().children, HashSet::<EventId>::new());
|
assert_eq!(tasks.get_current_task().unwrap().children, HashSet::<EventId>::new());
|
||||||
let sub_id = tasks.make_task("sub");
|
let sub_id = tasks.make_task("sub");
|
||||||
|
@ -1065,13 +1083,14 @@ mod tasks_test {
|
||||||
tasks.track_at(Timestamp::from(0), None);
|
tasks.track_at(Timestamp::from(0), None);
|
||||||
assert_eq!(tasks.history.len(), 1);
|
assert_eq!(tasks.history.len(), 1);
|
||||||
|
|
||||||
let now: Timestamp = Timestamp::now() - 2u64;
|
let almost_now: Timestamp = Timestamp::now() - 12u64;
|
||||||
tasks.track_at(Timestamp::from(1), Some(zero));
|
tasks.track_at(Timestamp::from(11), Some(zero));
|
||||||
assert!(tasks.time_tracked(zero) > now.as_u64());
|
tasks.track_at(Timestamp::from(13), Some(zero));
|
||||||
|
assert!(tasks.time_tracked(zero) > almost_now.as_u64());
|
||||||
|
|
||||||
tasks.track_at(Timestamp::from(2), None);
|
tasks.track_at(Timestamp::from(22), None);
|
||||||
assert_eq!(tasks.get_own_history().unwrap().len(), 3);
|
assert_eq!(tasks.get_own_history().unwrap().len(), 4);
|
||||||
assert_eq!(tasks.time_tracked(zero), 1);
|
assert_eq!(tasks.time_tracked(zero), 11);
|
||||||
|
|
||||||
// TODO test received events
|
// TODO test received events
|
||||||
}
|
}
|
||||||
|
@ -1102,6 +1121,7 @@ mod tasks_test {
|
||||||
assert_eq!(tasks.visible_tasks().len(), 0);
|
assert_eq!(tasks.visible_tasks().len(), 0);
|
||||||
|
|
||||||
tasks.move_to(Some(t1));
|
tasks.move_to(Some(t1));
|
||||||
|
assert_eq!(tasks.get_position(), Some(t1));
|
||||||
tasks.depth = 2;
|
tasks.depth = 2;
|
||||||
assert_eq!(tasks.visible_tasks().len(), 0);
|
assert_eq!(tasks.visible_tasks().len(), 0);
|
||||||
let t2 = tasks.make_task("t2");
|
let t2 = tasks.make_task("t2");
|
||||||
|
@ -1112,6 +1132,7 @@ mod tasks_test {
|
||||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
assert_eq!(tasks.visible_tasks().len(), 2);
|
||||||
|
|
||||||
tasks.move_to(Some(t2));
|
tasks.move_to(Some(t2));
|
||||||
|
assert_eq!(tasks.get_position(), Some(t2));
|
||||||
assert_eq!(tasks.visible_tasks().len(), 0);
|
assert_eq!(tasks.visible_tasks().len(), 0);
|
||||||
let t4 = tasks.make_task("t4");
|
let t4 = tasks.make_task("t4");
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||||
|
@ -1123,6 +1144,8 @@ mod tasks_test {
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||||
|
|
||||||
tasks.move_to(Some(t1));
|
tasks.move_to(Some(t1));
|
||||||
|
assert_eq!(tasks.get_own_history().unwrap().len(), 3);
|
||||||
|
assert_eq!(tasks.get_position(), Some(t1));
|
||||||
assert_eq!(tasks.relative_path(t4), "t2>t4");
|
assert_eq!(tasks.relative_path(t4), "t2>t4");
|
||||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
assert_eq!(tasks.visible_tasks().len(), 2);
|
||||||
tasks.depth = 2;
|
tasks.depth = 2;
|
||||||
|
|
Loading…
Reference in New Issue