diff --git a/README.md b/README.md index eb81fff..a0c0393 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,6 @@ Append `@TIME` to any task creation or change command to record the action with - `<[TEXT]` - close active task and move up, with optional status description - `!TEXT` - set status for current task from text and move up; empty: Open - `!TIME: REASON` - defer current task to date -- TBI: `*[INT]` - set priority - can also be used in task creation, with any digit - `,[TEXT]` - list notes or add text (activity / task description) - TBI: `;[TEXT]` - list comments or comment on task - TBI: show status history and creation with attribution @@ -171,9 +170,9 @@ Property Filters: - `#TAG1 TAG2` - set tag filter - `+TAG` - add tag filter (empty: list all used tags) - `-TAG` - remove tag filters (by prefix) -- `?STATUS` - filter by status (type or description) - plain `?` to reset, `??` to show all +- `?STATUS` - set status filter (type or description) - plain `?` to reset, `??` to show all +- `*INT` - set priority filter - `@[AUTHOR|TIME]` - filter by time or author (pubkey, or `@` for self, TBI: id prefix, name prefix) -- TBI: `**INT` - filter by priority Status descriptions can be used for example for Kanban columns or review flows. An active tag or status filter will also set that attribute for newly created tasks. diff --git a/src/kinds.rs b/src/kinds.rs index dcf2a41..675a3a4 100644 --- a/src/kinds.rs +++ b/src/kinds.rs @@ -26,6 +26,7 @@ pub const PROP_KINDS: [Kind; 6] = [ PROCEDURE_KIND, ]; +pub type Prio = u16; pub const PRIO: &str = "priority"; // TODO: use formatting - bold / heading / italics - and generate from code @@ -95,7 +96,7 @@ pub(crate) fn extract_tags(input: &str) -> (String, Vec) { prio = Some(HIGH_PRIO); return false } - return match s[1..].parse::() { + return match s[1..].parse::() { Ok(num) => { prio = Some(num * (if s.len() > 2 { 1 } else { 10 })); false @@ -109,7 +110,7 @@ pub(crate) fn extract_tags(input: &str) -> (String, Vec) { let main = split.next().unwrap().join(" "); let tags = extract_hashtags(&main) .chain(split.flatten().map(|s| to_hashtag(&s))) - .chain(prio.map(|p| to_prio_tag(&p.to_string()))).collect(); + .chain(prio.map(|p| to_prio_tag(p))).collect(); (main, tags) } @@ -143,8 +144,8 @@ pub(crate) fn is_hashtag(tag: &Tag) -> bool { .is_some_and(|letter| letter.character == Alphabet::T) } -pub(crate) fn to_prio_tag(value: &str) -> Tag { - Tag::custom(TagKind::Custom(Cow::from(PRIO)), [if value.len() < 2 { format!("{value}0") } else { value.to_string() }]) +pub(crate) fn to_prio_tag(value: Prio) -> Tag { + Tag::custom(TagKind::Custom(Cow::from(PRIO)), [value.to_string()]) } #[test] diff --git a/src/main.rs b/src/main.rs index 1fb65cd..a5f6640 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,7 @@ use tokio::time::error::Elapsed; use tokio::time::timeout; use crate::helpers::*; -use crate::kinds::{BASIC_KINDS, PROPERTY_COLUMNS, PROP_KINDS, TRACKING_KIND}; +use crate::kinds::{Prio, BASIC_KINDS, PROPERTY_COLUMNS, PROP_KINDS, TRACKING_KIND}; use crate::task::{State, Task, TaskState, MARKER_DEPENDS}; use crate::tasks::{PropertyCollection, StateFilter, TasksRelay}; @@ -536,8 +536,7 @@ async fn main() -> Result<()> { match arg { None => match tasks.get_position() { None => { - info!("Filtering for bookmarked tasks"); - tasks.set_view_bookmarks(); + tasks.set_priority(None); } Some(pos) => match or_warn!(tasks.toggle_bookmark(pos)) { @@ -546,7 +545,16 @@ async fn main() -> Result<()> { None => {} } }, - Some(arg) => info!("Setting priority not yet implemented"), + Some(arg) => { + if arg == "*" { + info!("Showing only bookmarked tasks"); + tasks.set_view_bookmarks(); + } else { + tasks.set_priority(arg.parse() + .inspect_err(|e| warn!("Invalid Priority {arg}: {e}")).ok() + .map(|p: Prio| p * (if arg.len() < 2 { 10 } else { 1 }))); + } + }, } } diff --git a/src/tasks.rs b/src/tasks.rs index 3175093..8239a0c 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -19,8 +19,8 @@ use regex::bytes::Regex; use tokio::sync::mpsc::Sender; use TagStandard::Hashtag; -const DEFAULT_PRIO: u16 = 25; -pub const HIGH_PRIO: u16 = 85; +const DEFAULT_PRIO: Prio = 25; +pub const HIGH_PRIO: Prio = 85; /// Amount of seconds to treat as "now" const MAX_OFFSET: u64 = 9; @@ -80,6 +80,8 @@ pub(crate) struct TasksRelay { tags_excluded: BTreeSet, /// Current active state state: StateFilter, + /// Current priority for filtering and new tasks + priority: Option, sender: EventSender, overflow: VecDeque, @@ -171,6 +173,8 @@ impl TasksRelay { tags: Default::default(), tags_excluded: Default::default(), state: Default::default(), + priority: None, + search_depth: 4, view_depth: 0, recurse_activities: true, @@ -356,6 +360,7 @@ impl TasksRelay { .chain(self.tags_excluded.iter() .map(|t| format!(" -#{}", t.content().unwrap()))) .chain(once(self.state.indicator())) + .chain(self.priority.map(|p| format!(" *{:02}", p))) .join("") } @@ -655,6 +660,15 @@ impl TasksRelay { } } + pub(crate) fn set_priority(&mut self, priority: Option) { + self.view.clear(); + match priority { + None => info!("Removing priority filter"), + Some(prio) => info!("Filtering for priority {}", prio), + } + self.priority = priority; + } + pub(crate) fn set_state_filter(&mut self, state: StateFilter) { self.view.clear(); info!("Filtering for {}", state); @@ -858,10 +872,13 @@ impl TasksRelay { /// Sanitizes input pub(crate) fn make_task_with(&mut self, input: &str, tags: impl IntoIterator, set_state: bool) -> EventId { let (input, input_tags) = extract_tags(input.trim()); + let prio = + if input_tags.iter().find(|t| t.kind().to_string() == PRIO).is_some() { None } else { self.priority.map(|p| to_prio_tag(p)) }; let id = self.submit( build_task(&input, input_tags, None) .add_tags(self.tags.iter().cloned()) .add_tags(tags) + .add_tags(prio) ); if set_state { self.state.as_option().inspect(|s| self.set_state_for_with(id, s));