feat(tasks): derive position from history

This commit is contained in:
xeruf 2024-08-19 13:06:20 +03:00
parent a4f9398846
commit ebfe632497
1 changed files with 50 additions and 27 deletions

View File

@ -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;