Compare commits

...

5 Commits

Author SHA1 Message Date
xeruf 3d389e8d52 feat: toggleable activity recursion 2024-10-12 14:17:46 +02:00
xeruf 28d1f4c983 fix(tasks): properly set search depth 2024-10-12 11:54:29 +02:00
xeruf 93fde86169 test(tasks): adjust view and search depth 2024-10-12 11:35:43 +02:00
xeruf 769b9578fe refactor: do not import xdg 2024-10-12 11:34:44 +02:00
xeruf c27ccb8282 refactor: rename stateless tasks to activities 2024-10-11 22:06:18 +02:00
4 changed files with 65 additions and 39 deletions

View File

@ -65,8 +65,8 @@ where
/// Build a task with informational output and optional labeled kind
pub(crate) fn build_task(name: &str, tags: Vec<Tag>, kind: Option<(&str, Kind)>) -> EventBuilder {
info!("Created {}task \"{name}\" with tags [{}]",
kind.map(|k| k.0).unwrap_or_default(),
info!("Created {} \"{name}\" with tags [{}]",
kind.map(|k| k.0).unwrap_or("task"),
tags.iter().map(format_tag).join(", "));
EventBuilder::new(kind.map(|k| k.1).unwrap_or(TASK_KIND), name, tags)
}

View File

@ -25,7 +25,6 @@ use tokio::sync::mpsc;
use tokio::sync::mpsc::Sender;
use tokio::time::error::Elapsed;
use tokio::time::timeout;
use xdg::BaseDirectories;
use crate::helpers::*;
use crate::kinds::{BASIC_KINDS, PROPERTY_COLUMNS, PROP_KINDS, TRACKING_KIND};
@ -169,7 +168,7 @@ async fn main() -> Result<()> {
);
builder.init();
let config_dir = or_warn!(BaseDirectories::new(), "Could not determine config directory")
let config_dir = or_warn!(xdg::BaseDirectories::new(), "Could not determine config directory")
.and_then(|d| or_warn!(d.create_config_directory("mostr"), "Could not create config directory"))
.unwrap_or(PathBuf::new());
let keysfile = config_dir.join("key");
@ -438,18 +437,27 @@ async fn main() -> Result<()> {
Some(',') =>
match arg {
None => {
tasks.get_current_task().map_or_else(
|| info!("With a task selected, use ,NOTE to attach NOTE and , to list all its notes"),
|task| println!("{}", task.description_events().map(|e| format!("{} {}", format_timestamp_local(&e.created_at), e.content)).join("\n")),
);
continue 'repl;
match tasks.get_current_task() {
None => {
info!("With a task selected, use ,NOTE to attach NOTE and , to list all its notes");
tasks.recurse_activities = !tasks.recurse_activities;
info!("Toggled activities recursion to {}", tasks.recurse_activities);
}
Some(task) => {
println!("{}",
task.description_events()
.map(|e| format!("{} {}", format_timestamp_local(&e.created_at), e.content))
.join("\n"));
continue 'repl;
}
}
}
Some(arg) => {
if arg.len() < CHARACTER_THRESHOLD {
warn!("Note needs at least {CHARACTER_THRESHOLD} characters!");
continue 'repl;
}
tasks.make_note(arg)
tasks.make_note(arg);
}
}

View File

@ -94,9 +94,10 @@ impl Task {
self.event.kind == TASK_KIND
}
/// Whether this is an actionable task - false if stateless
pub(crate) fn is_task(&self) -> bool {
self.is_task_kind() ||
self.states().next().is_some()
self.props.iter().any(|event| State::try_from(event.kind).is_ok())
}
fn states(&self) -> impl DoubleEndedIterator<Item=TaskState> + '_ {
@ -134,7 +135,7 @@ impl Task {
self.state().unwrap_or_else(|| self.default_state())
}
/// Returns None for a stateless task.
/// Returns None for activities.
pub(crate) fn state_label(&self) -> Option<ColoredString> {
self.state()
.or_else(|| Some(self.default_state()).filter(|_| self.is_task()))

View File

@ -68,7 +68,7 @@ pub(crate) struct TasksRelay {
view: Vec<EventId>,
search_depth: usize,
view_depth: usize,
pub(crate) recurse_stateless_tasks: bool,
pub(crate) recurse_activities: bool,
/// Currently active tags
tags: BTreeSet<Tag>,
@ -169,7 +169,7 @@ impl TasksRelay {
state: Default::default(),
search_depth: 4,
view_depth: 0,
recurse_stateless_tasks: true,
recurse_activities: true,
sender,
overflow: Default::default(),
@ -388,9 +388,12 @@ impl TasksRelay {
if !self.state.matches(task) {
return vec![]
}
let mut new_depth = depth - 1;
if sparse && new_depth > self.view_depth && self.filter(task) {
new_depth = self.view_depth;
let mut new_depth = depth;
if !self.recurse_activities || task.is_task() {
new_depth = depth - 1;
if sparse && new_depth > self.view_depth && self.filter(task) {
new_depth = self.view_depth;
}
}
if new_depth > 0 {
let mut children = self.resolve_tasks_rec(self.tasks.children_of(&task), sparse, new_depth);
@ -450,7 +453,7 @@ impl TasksRelay {
if current.is_empty() {
println!("No tasks here matching{}", self.get_prompt_suffix());
} else {
println!("Found some matching tasks beyond specified search depth:");
println!("Found matching tasks beyond specified search depth:");
}
}
}
@ -1082,26 +1085,25 @@ impl TasksRelay {
Some(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) -> EventId {
if let Some(id) = self.get_position_ref() {
if self.get_by_id(id).is_some_and(|t| t.is_task()) {
let prop = build_prop(Kind::TextNote, note.trim(), *id);
self.submit(prop);
return;
return self.submit(prop)
}
}
let (input, tags) = extract_tags(note.trim());
self.submit(
build_task(input, tags, Some(("stateless ", Kind::TextNote)))
build_task(input, tags, Some(("activity", Kind::TextNote)))
.add_tags(self.parent_tag())
.add_tags(self.tags.iter().cloned())
);
)
}
// Properties
pub(crate) fn set_view_depth(&mut self, depth: usize) {
info!("Changed view depth to {depth}");
info!("Showing {depth} subtask levels");
self.view_depth = depth;
}
@ -1112,7 +1114,7 @@ impl TasksRelay {
} else {
info!("Changed search depth to {depth}");
}
self.view_depth = depth;
self.search_depth = depth;
}
pub(crate) fn get_columns(&mut self) -> &mut Vec<String> {
@ -1500,6 +1502,7 @@ mod tasks_test {
tasks.move_to(Some(parent));
let pin = tasks.make_task("pin");
tasks.search_depth = 1;
assert_eq!(tasks.filtered_tasks(None, true).len(), 2);
assert_eq!(tasks.filtered_tasks(None, false).len(), 2);
assert_eq!(tasks.filtered_tasks(Some(&zero), false).len(), 0);
@ -1515,17 +1518,18 @@ mod tasks_test {
assert_eq!(tasks.filtered_tasks(Some(&zero), false), vec![tasks.get_by_id(&pin).unwrap()]);
tasks.move_to(None);
assert_eq!(tasks.view_depth, 1);
assert_eq!(tasks.view_depth, 0);
assert_tasks!(tasks, [pin, test, parent]);
tasks.set_view_depth(2);
tasks.set_view_depth(1);
assert_tasks!(tasks, [pin, test]);
tasks.add_tag("tag".to_string());
assert_tasks!(tasks, [test]);
assert_eq!(tasks.filtered_tasks(None, true), vec![tasks.get_by_id(&test).unwrap()]);
tasks.submit(EventBuilder::new(Kind::Bookmarks, "", []));
tasks.clear_filters();
assert_tasks!(tasks, [pin, test]);
tasks.set_view_depth(1);
tasks.set_view_depth(0);
assert_tasks!(tasks, [test, parent]);
}
@ -1534,7 +1538,8 @@ mod tasks_test {
let mut tasks = stub_tasks();
tasks.make_task_and_enter("proc: tags", State::Procedure);
assert_eq!(tasks.get_own_events_history().count(), 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.visible_tasks(),
Vec::<&Task>::new());
let sub_id = tasks.make_task("sub");
@ -1632,18 +1637,19 @@ mod tasks_test {
fn test_depth() {
let mut tasks = stub_tasks();
let t1 = tasks.make_task("t1");
let task1 = tasks.get_by_id(&t1).unwrap();
assert_eq!(tasks.view_depth, 1);
assert_eq!(task1.pure_state(), State::Open);
let t1 = tasks.make_note("t1");
let activity_t1 = tasks.get_by_id(&t1).unwrap();
assert!(!activity_t1.is_task());
assert_eq!(tasks.view_depth, 0);
assert_eq!(activity_t1.pure_state(), State::Open);
debug!("{:?}", tasks);
assert_eq!(tasks.visible_tasks().len(), 1);
tasks.view_depth = 0;
tasks.search_depth = 0;
assert_eq!(tasks.visible_tasks().len(), 0);
tasks.move_to(Some(t1));
assert_position!(tasks, t1);
tasks.view_depth = 2;
tasks.search_depth = 2;
assert_eq!(tasks.visible_tasks().len(), 0);
let t11 = tasks.make_task("t11: tag");
assert_eq!(tasks.visible_tasks().len(), 1);
@ -1678,17 +1684,28 @@ mod tasks_test {
assert_tasks!(tasks, [t111, t12]);
tasks.set_view(vec![t11]);
assert_tasks!(tasks, [t11]); // No more depth applied to view
tasks.set_view_depth(1);
tasks.set_search_depth(1); // resets view
assert_tasks!(tasks, [t111, t12]);
tasks.set_view_depth(0);
assert_tasks!(tasks, [t11, t12]);
tasks.move_to(None);
assert_tasks!(tasks, [t1]);
tasks.view_depth = 2;
assert_tasks!(tasks, [t11, t12]);
tasks.view_depth = 3;
tasks.recurse_activities = false;
assert_tasks!(tasks, [t1]);
tasks.view_depth = 1;
assert_tasks!(tasks, [t11, t12]);
tasks.view_depth = 2;
assert_tasks!(tasks, [t111, t12]);
tasks.view_depth = 9;
assert_tasks!(tasks, [t111, t12]);
tasks.add_tag("tag".to_string());
tasks.view_depth = 0;
assert_tasks!(tasks, [t11]);
tasks.search_depth = 0;
assert_eq!(tasks.view, []);
assert_tasks!(tasks, []);
}
#[test]