feat: quick filter for all task states
This commit is contained in:
parent
ff74ac216b
commit
b03ad00b6a
|
@ -112,7 +112,7 @@ Property Filters:
|
|||
- `#TAG` - set tag filter (empty: list all used tags)
|
||||
- `+TAG` - add tag filter
|
||||
- `-TAG` - remove tag filters
|
||||
- `?STATE` - filter by state (type or description) - plain `?` to reset
|
||||
- `?STATE` - filter by state (type or description) - plain `?` to reset, `??` to show all
|
||||
|
||||
State descriptions can be used for example for Kanban columns or review flows.
|
||||
An active tag or state filter will also set that attribute for newly created tasks.
|
||||
|
@ -171,9 +171,10 @@ The following features are not ready to be implemented
|
|||
because they need conceptualization.
|
||||
Suggestions welcome!
|
||||
|
||||
- Task Dependencies (change from tags to properties so they can be added later, or maybe as a state?)
|
||||
- Task Templates
|
||||
- Task Ownership
|
||||
- Priorities
|
||||
- Dependencies (change from tags to properties so they can be added later? or maybe as a state?)
|
||||
- Templates
|
||||
- Ownership
|
||||
- Combined formatting and recursion specifiers
|
||||
+ progress count/percentage and recursive or not
|
||||
+ Subtask progress immediate/all/leafs
|
||||
|
|
|
@ -21,7 +21,7 @@ use xdg::BaseDirectories;
|
|||
use crate::helpers::*;
|
||||
use crate::kinds::{KINDS, PROPERTY_COLUMNS, TRACKING_KIND};
|
||||
use crate::task::State;
|
||||
use crate::tasks::Tasks;
|
||||
use crate::tasks::{StateFilter, Tasks};
|
||||
|
||||
mod helpers;
|
||||
mod task;
|
||||
|
@ -343,7 +343,11 @@ async fn main() {
|
|||
}
|
||||
|
||||
Some('?') => {
|
||||
tasks.set_state_filter(arg.map(|s| s.to_string()));
|
||||
match arg {
|
||||
None => tasks.set_state_filter(StateFilter::Default),
|
||||
Some("?") => tasks.set_state_filter(StateFilter::All),
|
||||
Some(arg) => tasks.set_state_filter(StateFilter::State(arg.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
Some('!') =>
|
||||
|
|
76
src/tasks.rs
76
src/tasks.rs
|
@ -1,4 +1,5 @@
|
|||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io::{Error, stdout, Write};
|
||||
use std::iter::once;
|
||||
use std::ops::{Div, Rem};
|
||||
|
@ -40,13 +41,66 @@ pub(crate) struct Tasks {
|
|||
/// Currently active tags
|
||||
tags: BTreeSet<Tag>,
|
||||
/// Current active state
|
||||
state: Option<String>,
|
||||
state: StateFilter,
|
||||
/// A filtered view of the current tasks
|
||||
view: Vec<EventId>,
|
||||
|
||||
sender: EventSender,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum StateFilter {
|
||||
Default,
|
||||
All,
|
||||
State(String),
|
||||
}
|
||||
impl StateFilter {
|
||||
fn indicator(&self) -> String {
|
||||
match self {
|
||||
StateFilter::Default => "".to_string(),
|
||||
StateFilter::All => " ?ALL".to_string(),
|
||||
StateFilter::State(str) => format!(" ?{str}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn matches(&self, task: &Task) -> bool {
|
||||
match self {
|
||||
StateFilter::Default => {
|
||||
let state = task.pure_state();
|
||||
state.is_open() || (state == State::Done && task.parent_id() != None)
|
||||
},
|
||||
StateFilter::All => true,
|
||||
StateFilter::State(filter) => task.state().is_some_and(|t| t.matches_label(filter)),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_option(&self) -> Option<String> {
|
||||
if let StateFilter::State(str) = self {
|
||||
Some(str.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Default for StateFilter {
|
||||
fn default() -> Self {
|
||||
StateFilter::Default
|
||||
}
|
||||
}
|
||||
impl Display for StateFilter {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
StateFilter::Default => "relevant tasks".to_string(),
|
||||
StateFilter::All => "all tasks".to_string(),
|
||||
StateFilter::State(s) => format!("state {s}"),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Tasks {
|
||||
pub(crate) fn from(url: Option<Url>, tx: &Sender<(Url, Events)>, keys: &Keys) -> Self {
|
||||
Self::with_sender(EventSender {
|
||||
|
@ -72,7 +126,7 @@ impl Tasks {
|
|||
position: None, // TODO persist position
|
||||
view: Default::default(),
|
||||
tags: Default::default(),
|
||||
state: None,
|
||||
state: Default::default(),
|
||||
depth: 1,
|
||||
sender,
|
||||
}
|
||||
|
@ -208,8 +262,7 @@ impl Tasks {
|
|||
self.tags
|
||||
.iter()
|
||||
.map(|t| format!(" #{}", t.content().unwrap()))
|
||||
.chain(self.state.as_ref().map(|s| format!(" ?{s}")).into_iter())
|
||||
.collect::<Vec<String>>()
|
||||
.chain(once(self.state.indicator()))
|
||||
.join("")
|
||||
}
|
||||
|
||||
|
@ -306,14 +359,7 @@ impl Tasks {
|
|||
.filter(|t| {
|
||||
// TODO apply filters in transit
|
||||
let state = t.pure_state();
|
||||
self.state.as_ref().map_or_else(|| {
|
||||
state.is_open() || (
|
||||
state == State::Done &&
|
||||
t.parent_id() != None
|
||||
)
|
||||
}, |filter| {
|
||||
t.state().is_some_and(|t| t.matches_label(filter))
|
||||
}) && (self.tags.is_empty()
|
||||
self.state.matches(t) && (self.tags.is_empty()
|
||||
|| t.tags.as_ref().map_or(false, |tags| {
|
||||
let mut iter = tags.iter();
|
||||
self.tags.iter().all(|tag| iter.any(|t| t == tag))
|
||||
|
@ -436,9 +482,9 @@ impl Tasks {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_state_filter(&mut self, state: Option<String>) {
|
||||
pub(crate) fn set_state_filter(&mut self, state: StateFilter) {
|
||||
self.view.clear();
|
||||
info!("Filtering for {}", state.as_ref().map_or("open tasks".to_string(), |s| format!("state {s}")));
|
||||
info!("Filtering for {}", state);
|
||||
self.state = state;
|
||||
}
|
||||
|
||||
|
@ -566,7 +612,7 @@ impl Tasks {
|
|||
self.parse_task(input.trim())
|
||||
.add_tags(tag.into_iter())
|
||||
);
|
||||
self.state.clone().inspect(|s| self.set_state_for_with(id, s));
|
||||
self.state.as_option().inspect(|s| self.set_state_for_with(id, s));
|
||||
id
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue