forked from janek/mostr
feat: filter with slash
This commit is contained in:
parent
03f9e60c6f
commit
bf802e3195
|
@ -37,7 +37,7 @@ To exit the application, press `Ctrl-D`.
|
||||||
### Navigation and Nesting
|
### Navigation and Nesting
|
||||||
|
|
||||||
Create tasks and navigate using the shortcuts below.
|
Create tasks and navigate using the shortcuts below.
|
||||||
Whichever task is selected / "active"
|
Whichever task is active (selected)
|
||||||
will be the parent task for newly created tasks
|
will be the parent task for newly created tasks
|
||||||
and automatically has time-tracking running.
|
and automatically has time-tracking running.
|
||||||
To track task progress,
|
To track task progress,
|
||||||
|
@ -48,7 +48,7 @@ Generally a flat hierarchy is recommended
|
||||||
with tags for filtering,
|
with tags for filtering,
|
||||||
since hierarchies cannot be changed.
|
since hierarchies cannot be changed.
|
||||||
Filtering by a tag is just as easy
|
Filtering by a tag is just as easy
|
||||||
as selecting a task and more flexible.
|
as activating a task and more flexible.
|
||||||
|
|
||||||
Using subtasks has two main advantages:
|
Using subtasks has two main advantages:
|
||||||
- ability to accumulate time tracked
|
- ability to accumulate time tracked
|
||||||
|
@ -91,10 +91,11 @@ when the application is terminated regularly.
|
||||||
- `TASK` - create task
|
- `TASK` - create task
|
||||||
- `.` - clear filters and reload
|
- `.` - clear filters and reload
|
||||||
- `.TASK`
|
- `.TASK`
|
||||||
+ select task by id
|
+ activate task by id
|
||||||
+ match by task name prefix: if one or more tasks match, filter / activate (tries case-sensitive then case-insensitive)
|
+ match by task name prefix: if one or more tasks match, filter / activate (tries case-sensitive then case-insensitive)
|
||||||
+ no match: create & activate task
|
+ 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)
|
- `.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
|
||||||
|
|
||||||
Dots can be repeated to move to parent tasks.
|
Dots can be repeated to move to parent tasks.
|
||||||
|
|
||||||
|
|
32
src/main.rs
32
src/main.rs
|
@ -365,6 +365,38 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some('/') => {
|
||||||
|
let mut dots = 1;
|
||||||
|
let mut pos = tasks.get_position();
|
||||||
|
for _ in iter.take_while(|c| c == &'/') {
|
||||||
|
dots += 1;
|
||||||
|
pos = tasks.get_parent(pos).cloned();
|
||||||
|
}
|
||||||
|
let slice = &input[dots..].to_ascii_lowercase();
|
||||||
|
if slice.is_empty() {
|
||||||
|
tasks.move_to(pos);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Ok(depth) = slice.parse::<i8>() {
|
||||||
|
tasks.move_to(pos);
|
||||||
|
tasks.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))
|
||||||
|
.map(|t| t.event.id)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if filtered.len() == 1 {
|
||||||
|
tasks.move_to(filtered.into_iter().nth(0));
|
||||||
|
} else {
|
||||||
|
tasks.move_to(pos);
|
||||||
|
tasks.set_filter(filtered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
tasks.filter_or_create(&input);
|
tasks.filter_or_create(&input);
|
||||||
}
|
}
|
||||||
|
|
35
src/tasks.rs
35
src/tasks.rs
|
@ -256,6 +256,13 @@ impl Tasks {
|
||||||
self.position.and_then(|id| self.get_by_id(&id))
|
self.position.and_then(|id| self.get_by_id(&id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn children_of(&self, id: Option<EventId>) -> impl IntoIterator<Item=&EventId> + '_ {
|
||||||
|
self.tasks
|
||||||
|
.values()
|
||||||
|
.filter(move |t| t.parent_id() == id.as_ref())
|
||||||
|
.map(|t| t.get_id())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn current_tasks(&self) -> Vec<&Task> {
|
pub(crate) fn current_tasks(&self) -> Vec<&Task> {
|
||||||
if self.depth == 0 {
|
if self.depth == 0 {
|
||||||
return self.current_task().into_iter().collect();
|
return self.current_task().into_iter().collect();
|
||||||
|
@ -265,13 +272,9 @@ impl Tasks {
|
||||||
// Currently ignores filter when it matches nothing
|
// Currently ignores filter when it matches nothing
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
self.resolve_tasks(
|
self.resolve_tasks(self.children_of(self.position)).into_iter()
|
||||||
self.tasks
|
|
||||||
.values()
|
|
||||||
.filter(|t| t.parent_id() == self.position.as_ref())
|
|
||||||
.map(|t| t.get_id()),
|
|
||||||
).into_iter()
|
|
||||||
.filter(|t| {
|
.filter(|t| {
|
||||||
|
// TODO apply filters in transit
|
||||||
let state = t.pure_state();
|
let state = t.pure_state();
|
||||||
self.state.as_ref().map_or_else(|| {
|
self.state.as_ref().map_or_else(|| {
|
||||||
state == State::Open || (
|
state == State::Open || (
|
||||||
|
@ -389,12 +392,10 @@ impl Tasks {
|
||||||
self.sender.flush();
|
self.sender.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds out what to do with the given string.
|
/// Returns ids of tasks matching the filter.
|
||||||
/// Returns an EventId when a new Task was created.
|
pub(crate) fn get_filtered(&self, arg: &str) -> Vec<EventId> {
|
||||||
pub(crate) fn filter_or_create(&mut self, arg: &str) -> Option<EventId> {
|
|
||||||
if let Ok(id) = EventId::parse(arg) {
|
if let Ok(id) = EventId::parse(arg) {
|
||||||
self.move_to(Some(id));
|
return vec![id];
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
let tasks = self.current_tasks();
|
let tasks = self.current_tasks();
|
||||||
let mut filtered: Vec<EventId> = Vec::with_capacity(tasks.len());
|
let mut filtered: Vec<EventId> = Vec::with_capacity(tasks.len());
|
||||||
|
@ -403,8 +404,7 @@ impl Tasks {
|
||||||
for task in tasks {
|
for task in tasks {
|
||||||
let lowercase = task.event.content.to_ascii_lowercase();
|
let lowercase = task.event.content.to_ascii_lowercase();
|
||||||
if lowercase == lowercase_arg {
|
if lowercase == lowercase_arg {
|
||||||
self.move_to(Some(task.event.id));
|
return vec![task.event.id]
|
||||||
return None
|
|
||||||
} else if task.event.content.starts_with(arg) {
|
} else if task.event.content.starts_with(arg) {
|
||||||
filtered.push(task.event.id)
|
filtered.push(task.event.id)
|
||||||
} else if lowercase.starts_with(&lowercase_arg) {
|
} else if lowercase.starts_with(&lowercase_arg) {
|
||||||
|
@ -412,8 +412,15 @@ impl Tasks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if filtered.len() == 0 {
|
if filtered.len() == 0 {
|
||||||
filtered = filtered_more
|
return filtered_more
|
||||||
}
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds out what to do with the given string.
|
||||||
|
/// Returns an EventId if a new Task was created.
|
||||||
|
pub(crate) fn filter_or_create(&mut self, arg: &str) -> Option<EventId> {
|
||||||
|
let filtered = self.get_filtered(arg);
|
||||||
match filtered.len() {
|
match filtered.len() {
|
||||||
0 => {
|
0 => {
|
||||||
// No match, new task
|
// No match, new task
|
||||||
|
|
Loading…
Reference in New Issue