forked from janek/mostr
fix: improve task filtering, especially with slash
- smart case - substring match - less movement needed
This commit is contained in:
parent
c93b2f2d91
commit
930c6b9c38
|
@ -93,17 +93,17 @@ To stop time-tracking completely, simply move to the root of all tasks.
|
|||
`TASK` creation syntax: `NAME: TAG1 TAG2 ...`
|
||||
|
||||
- `TASK` - create task (prefix with space if you want a task to start with a command character)
|
||||
- `.` - clear filters and reload
|
||||
- `.` - clear filters
|
||||
- `.TASK`
|
||||
+ activate task by id
|
||||
+ match by task name prefix: if one or more tasks match, filter / activate (tries case-sensitive then case-insensitive)
|
||||
+ no match: create & activate task
|
||||
- `.2` - set view depth to `2`, which can be substituted for any number (how many subtask levels to show, default 1)
|
||||
- `/[TEXT]` - like `.`, but never creates a task and filters beyond currently visible tasks
|
||||
- `.2` - set view depth to the given number (how many subtask levels to show, default is 1)
|
||||
- `/[TEXT]` - activate task or filter by smart-case substring match
|
||||
- `||TASK` - create and activate a new task procedure (where subtasks automatically depend on the previously created task)
|
||||
- `|[TASK]` - (un)mark current task as procedure or create a sibling task depending on the current one and move up
|
||||
|
||||
Dots and slashes can be repeated to move to parent tasks.
|
||||
Dot or slash can be repeated to move to parent tasks before acting.
|
||||
|
||||
- `:[IND][PROP]` - add property column PROP at IND or end, if it already exists remove property column PROP or IND (1-indexed)
|
||||
- `::[PROP]` - Sort by property PROP (multiple space-separated values allowed)
|
||||
|
|
29
src/main.rs
29
src/main.rs
|
@ -407,7 +407,7 @@ async fn main() {
|
|||
None => match tasks.get_position() {
|
||||
None => {
|
||||
tasks.set_filter(
|
||||
tasks.current_tasks().into_iter()
|
||||
tasks.filtered_tasks(None)
|
||||
.filter(|t| t.pure_state() == State::Procedure)
|
||||
.map(|t| t.event.id)
|
||||
.collect()
|
||||
|
@ -499,8 +499,8 @@ async fn main() {
|
|||
dots += 1;
|
||||
pos = tasks.get_parent(pos).cloned();
|
||||
}
|
||||
let slice = input[dots..].trim();
|
||||
|
||||
let slice = input[dots..].trim();
|
||||
if pos != tasks.get_position() || slice.is_empty() {
|
||||
tasks.move_to(pos);
|
||||
}
|
||||
|
@ -522,21 +522,30 @@ async fn main() {
|
|||
dots += 1;
|
||||
pos = tasks.get_parent(pos).cloned();
|
||||
}
|
||||
let slice = &input[dots..].trim().to_ascii_lowercase();
|
||||
|
||||
let slice = input[dots..].trim();
|
||||
if slice.is_empty() {
|
||||
tasks.move_to(pos);
|
||||
if dots > 1 {
|
||||
info!("Moving up {} tasks", dots - 1)
|
||||
}
|
||||
} else if let Ok(depth) = slice.parse::<i8>() {
|
||||
tasks.move_to(pos);
|
||||
tasks.set_depth(depth);
|
||||
} else {
|
||||
let filtered = tasks
|
||||
.children_of(pos)
|
||||
.into_iter()
|
||||
.filter_map(|child| tasks.get_by_id(&child))
|
||||
.filter(|t| t.event.content.to_ascii_lowercase().starts_with(slice))
|
||||
let mut transform: Box<dyn Fn(&str) -> String> = Box::new(|s: &str| s.to_string());
|
||||
if slice.chars().find(|c| c.is_ascii_uppercase()).is_none() {
|
||||
// Smart-case - case-sensitive if any uppercase char is entered
|
||||
transform = Box::new(|s| s.to_ascii_lowercase());
|
||||
}
|
||||
|
||||
let filtered = tasks.filtered_tasks(pos)
|
||||
.filter(|t| {
|
||||
transform(&t.event.content).contains(slice) || t.tags.iter().flatten().any(|tag|
|
||||
tag.content().is_some_and(|s| transform(s).contains(slice))
|
||||
)
|
||||
})
|
||||
.map(|t| t.event.id)
|
||||
.collect::<Vec<_>>();
|
||||
.collect_vec();
|
||||
if filtered.len() == 1 {
|
||||
tasks.move_to(filtered.into_iter().nth(0));
|
||||
} else {
|
||||
|
|
76
src/tasks.rs
76
src/tasks.rs
|
@ -354,17 +354,9 @@ impl Tasks {
|
|||
.map(|t| t.get_id())
|
||||
}
|
||||
|
||||
pub(crate) fn current_tasks(&self) -> Vec<&Task> {
|
||||
if self.depth == 0 {
|
||||
return self.get_current_task().into_iter().collect();
|
||||
}
|
||||
let res: Vec<&Task> = self.resolve_tasks(self.view.iter());
|
||||
if res.len() > 0 {
|
||||
// Currently ignores filtered view when it matches nothing
|
||||
return res;
|
||||
}
|
||||
pub(crate) fn filtered_tasks(&self, position: Option<EventId>) -> impl Iterator<Item=&Task> {
|
||||
// TODO use ChildrenIterator
|
||||
self.resolve_tasks(self.children_of(self.position)).into_iter()
|
||||
self.resolve_tasks(self.children_of(position)).into_iter()
|
||||
.filter(|t| {
|
||||
// TODO apply filters in transit
|
||||
self.state.matches(t) &&
|
||||
|
@ -377,7 +369,16 @@ impl Tasks {
|
|||
self.tags.iter().all(|tag| iter.any(|t| t == tag))
|
||||
}))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn visible_tasks(&self) -> Vec<&Task> {
|
||||
if self.depth == 0 {
|
||||
return self.get_current_task().into_iter().collect();
|
||||
}
|
||||
if self.view.len() > 0 {
|
||||
return self.resolve_tasks(self.view.iter());
|
||||
}
|
||||
self.filtered_tasks(self.position).collect()
|
||||
}
|
||||
|
||||
pub(crate) fn print_tasks(&self) -> Result<(), Error> {
|
||||
|
@ -408,7 +409,7 @@ impl Tasks {
|
|||
// TODO hide empty columns
|
||||
writeln!(lock, "{}", self.properties.join("\t").bold())?;
|
||||
let mut total_time = 0;
|
||||
let mut tasks = self.current_tasks();
|
||||
let mut tasks = self.visible_tasks();
|
||||
let count = tasks.len();
|
||||
tasks.sort_by_cached_key(|task| {
|
||||
self.sorting
|
||||
|
@ -534,11 +535,10 @@ impl Tasks {
|
|||
if let Ok(id) = EventId::parse(arg) {
|
||||
return vec![id];
|
||||
}
|
||||
let tasks = self.current_tasks();
|
||||
let mut filtered: Vec<EventId> = Vec::with_capacity(tasks.len());
|
||||
let mut filtered: Vec<EventId> = Vec::with_capacity(32);
|
||||
let lowercase_arg = arg.to_ascii_lowercase();
|
||||
let mut filtered_more: Vec<EventId> = Vec::with_capacity(tasks.len());
|
||||
for task in tasks {
|
||||
let mut filtered_more: Vec<EventId> = Vec::with_capacity(32);
|
||||
for task in self.filtered_tasks(self.position) {
|
||||
let lowercase = task.event.content.to_ascii_lowercase();
|
||||
if lowercase == lowercase_arg {
|
||||
return vec![task.event.id];
|
||||
|
@ -1112,59 +1112,59 @@ mod tasks_test {
|
|||
assert_eq!(tasks.depth, 1);
|
||||
assert_eq!(task1.pure_state(), State::Open);
|
||||
debug!("{:?}", tasks);
|
||||
assert_eq!(tasks.current_tasks().len(), 1);
|
||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||
tasks.depth = 0;
|
||||
assert_eq!(tasks.current_tasks().len(), 0);
|
||||
assert_eq!(tasks.visible_tasks().len(), 0);
|
||||
|
||||
tasks.move_to(Some(t1));
|
||||
tasks.depth = 2;
|
||||
assert_eq!(tasks.current_tasks().len(), 0);
|
||||
assert_eq!(tasks.visible_tasks().len(), 0);
|
||||
let t2 = tasks.make_task("t2");
|
||||
assert_eq!(tasks.current_tasks().len(), 1);
|
||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||
assert_eq!(tasks.get_task_path(Some(t2)), "t1>t2");
|
||||
assert_eq!(tasks.relative_path(t2), "t2");
|
||||
let t3 = tasks.make_task("t3");
|
||||
assert_eq!(tasks.current_tasks().len(), 2);
|
||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
||||
|
||||
tasks.move_to(Some(t2));
|
||||
assert_eq!(tasks.current_tasks().len(), 0);
|
||||
assert_eq!(tasks.visible_tasks().len(), 0);
|
||||
let t4 = tasks.make_task("t4");
|
||||
assert_eq!(tasks.current_tasks().len(), 1);
|
||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||
assert_eq!(tasks.get_task_path(Some(t4)), "t1>t2>t4");
|
||||
assert_eq!(tasks.relative_path(t4), "t4");
|
||||
tasks.depth = 2;
|
||||
assert_eq!(tasks.current_tasks().len(), 1);
|
||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||
tasks.depth = -1;
|
||||
assert_eq!(tasks.current_tasks().len(), 1);
|
||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||
|
||||
tasks.move_to(Some(t1));
|
||||
assert_eq!(tasks.relative_path(t4), "t2>t4");
|
||||
assert_eq!(tasks.current_tasks().len(), 2);
|
||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
||||
tasks.depth = 2;
|
||||
assert_eq!(tasks.current_tasks().len(), 3);
|
||||
assert_eq!(tasks.visible_tasks().len(), 3);
|
||||
tasks.set_filter(vec![t2]);
|
||||
assert_eq!(tasks.current_tasks().len(), 2);
|
||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
||||
tasks.depth = 1;
|
||||
assert_eq!(tasks.current_tasks().len(), 1);
|
||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||
tasks.depth = -1;
|
||||
assert_eq!(tasks.current_tasks().len(), 1);
|
||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||
tasks.set_filter(vec![t2, t3]);
|
||||
assert_eq!(tasks.current_tasks().len(), 2);
|
||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
||||
tasks.depth = 2;
|
||||
assert_eq!(tasks.current_tasks().len(), 3);
|
||||
assert_eq!(tasks.visible_tasks().len(), 3);
|
||||
tasks.depth = 1;
|
||||
assert_eq!(tasks.current_tasks().len(), 2);
|
||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
||||
|
||||
tasks.move_to(None);
|
||||
assert_eq!(tasks.current_tasks().len(), 1);
|
||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||
tasks.depth = 2;
|
||||
assert_eq!(tasks.current_tasks().len(), 3);
|
||||
assert_eq!(tasks.visible_tasks().len(), 3);
|
||||
tasks.depth = 3;
|
||||
assert_eq!(tasks.current_tasks().len(), 4);
|
||||
assert_eq!(tasks.visible_tasks().len(), 4);
|
||||
tasks.depth = 9;
|
||||
assert_eq!(tasks.current_tasks().len(), 4);
|
||||
assert_eq!(tasks.visible_tasks().len(), 4);
|
||||
tasks.depth = -1;
|
||||
assert_eq!(tasks.current_tasks().len(), 2);
|
||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in New Issue