feat: quick filter for all task states

This commit is contained in:
xeruf 2024-08-10 15:44:52 +03:00
parent ff74ac216b
commit b03ad00b6a
3 changed files with 72 additions and 21 deletions

View File

@ -112,7 +112,7 @@ Property Filters:
- `#TAG` - set tag filter (empty: list all used tags) - `#TAG` - set tag filter (empty: list all used tags)
- `+TAG` - add tag filter - `+TAG` - add tag filter
- `-TAG` - remove tag filters - `-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. 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. 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. because they need conceptualization.
Suggestions welcome! Suggestions welcome!
- Task Dependencies (change from tags to properties so they can be added later, or maybe as a state?) - Priorities
- Task Templates - Dependencies (change from tags to properties so they can be added later? or maybe as a state?)
- Task Ownership - Templates
- Ownership
- Combined formatting and recursion specifiers - Combined formatting and recursion specifiers
+ progress count/percentage and recursive or not + progress count/percentage and recursive or not
+ Subtask progress immediate/all/leafs + Subtask progress immediate/all/leafs

View File

@ -21,7 +21,7 @@ use xdg::BaseDirectories;
use crate::helpers::*; use crate::helpers::*;
use crate::kinds::{KINDS, PROPERTY_COLUMNS, TRACKING_KIND}; use crate::kinds::{KINDS, PROPERTY_COLUMNS, TRACKING_KIND};
use crate::task::State; use crate::task::State;
use crate::tasks::Tasks; use crate::tasks::{StateFilter, Tasks};
mod helpers; mod helpers;
mod task; mod task;
@ -343,7 +343,11 @@ async fn main() {
} }
Some('?') => { 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('!') => Some('!') =>

View File

@ -1,4 +1,5 @@
use std::collections::{BTreeSet, HashMap}; use std::collections::{BTreeSet, HashMap};
use std::fmt::{Display, Formatter};
use std::io::{Error, stdout, Write}; use std::io::{Error, stdout, Write};
use std::iter::once; use std::iter::once;
use std::ops::{Div, Rem}; use std::ops::{Div, Rem};
@ -40,13 +41,66 @@ pub(crate) struct Tasks {
/// Currently active tags /// Currently active tags
tags: BTreeSet<Tag>, tags: BTreeSet<Tag>,
/// Current active state /// Current active state
state: Option<String>, state: StateFilter,
/// A filtered view of the current tasks /// A filtered view of the current tasks
view: Vec<EventId>, view: Vec<EventId>,
sender: EventSender, 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 { impl Tasks {
pub(crate) fn from(url: Option<Url>, tx: &Sender<(Url, Events)>, keys: &Keys) -> Self { pub(crate) fn from(url: Option<Url>, tx: &Sender<(Url, Events)>, keys: &Keys) -> Self {
Self::with_sender(EventSender { Self::with_sender(EventSender {
@ -72,7 +126,7 @@ impl Tasks {
position: None, // TODO persist position position: None, // TODO persist position
view: Default::default(), view: Default::default(),
tags: Default::default(), tags: Default::default(),
state: None, state: Default::default(),
depth: 1, depth: 1,
sender, sender,
} }
@ -208,8 +262,7 @@ impl Tasks {
self.tags self.tags
.iter() .iter()
.map(|t| format!(" #{}", t.content().unwrap())) .map(|t| format!(" #{}", t.content().unwrap()))
.chain(self.state.as_ref().map(|s| format!(" ?{s}")).into_iter()) .chain(once(self.state.indicator()))
.collect::<Vec<String>>()
.join("") .join("")
} }
@ -306,14 +359,7 @@ impl Tasks {
.filter(|t| { .filter(|t| {
// TODO apply filters in transit // TODO apply filters in transit
let state = t.pure_state(); let state = t.pure_state();
self.state.as_ref().map_or_else(|| { self.state.matches(t) && (self.tags.is_empty()
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()
|| t.tags.as_ref().map_or(false, |tags| { || t.tags.as_ref().map_or(false, |tags| {
let mut iter = tags.iter(); let mut iter = tags.iter();
self.tags.iter().all(|tag| iter.any(|t| t == tag)) 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(); 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; self.state = state;
} }
@ -566,7 +612,7 @@ impl Tasks {
self.parse_task(input.trim()) self.parse_task(input.trim())
.add_tags(tag.into_iter()) .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 id
} }