feat: allow setting priority context for creating tasks

This commit is contained in:
xeruf 2024-11-09 19:18:42 +01:00
parent dc8df51e0f
commit e9bee3c114
4 changed files with 38 additions and 13 deletions

View File

@ -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]` - close active task and move up, with optional status description
- `!TEXT` - set status for current task from text and move up; empty: Open - `!TEXT` - set status for current task from text and move up; empty: Open
- `!TIME: REASON` - defer current task to date - `!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) - `,[TEXT]` - list notes or add text (activity / task description)
- TBI: `;[TEXT]` - list comments or comment on task - TBI: `;[TEXT]` - list comments or comment on task
- TBI: show status history and creation with attribution - TBI: show status history and creation with attribution
@ -171,9 +170,9 @@ Property Filters:
- `#TAG1 TAG2` - set tag filter - `#TAG1 TAG2` - set tag filter
- `+TAG` - add tag filter (empty: list all used tags) - `+TAG` - add tag filter (empty: list all used tags)
- `-TAG` - remove tag filters (by prefix) - `-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) - `@[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. 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. An active tag or status filter will also set that attribute for newly created tasks.

View File

@ -26,6 +26,7 @@ pub const PROP_KINDS: [Kind; 6] = [
PROCEDURE_KIND, PROCEDURE_KIND,
]; ];
pub type Prio = u16;
pub const PRIO: &str = "priority"; pub const PRIO: &str = "priority";
// TODO: use formatting - bold / heading / italics - and generate from code // TODO: use formatting - bold / heading / italics - and generate from code
@ -95,7 +96,7 @@ pub(crate) fn extract_tags(input: &str) -> (String, Vec<Tag>) {
prio = Some(HIGH_PRIO); prio = Some(HIGH_PRIO);
return false return false
} }
return match s[1..].parse::<u16>() { return match s[1..].parse::<Prio>() {
Ok(num) => { Ok(num) => {
prio = Some(num * (if s.len() > 2 { 1 } else { 10 })); prio = Some(num * (if s.len() > 2 { 1 } else { 10 }));
false false
@ -109,7 +110,7 @@ pub(crate) fn extract_tags(input: &str) -> (String, Vec<Tag>) {
let main = split.next().unwrap().join(" "); let main = split.next().unwrap().join(" ");
let tags = extract_hashtags(&main) let tags = extract_hashtags(&main)
.chain(split.flatten().map(|s| to_hashtag(&s))) .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) (main, tags)
} }
@ -143,8 +144,8 @@ pub(crate) fn is_hashtag(tag: &Tag) -> bool {
.is_some_and(|letter| letter.character == Alphabet::T) .is_some_and(|letter| letter.character == Alphabet::T)
} }
pub(crate) fn to_prio_tag(value: &str) -> Tag { pub(crate) fn to_prio_tag(value: Prio) -> Tag {
Tag::custom(TagKind::Custom(Cow::from(PRIO)), [if value.len() < 2 { format!("{value}0") } else { value.to_string() }]) Tag::custom(TagKind::Custom(Cow::from(PRIO)), [value.to_string()])
} }
#[test] #[test]

View File

@ -27,7 +27,7 @@ use tokio::time::error::Elapsed;
use tokio::time::timeout; use tokio::time::timeout;
use crate::helpers::*; 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::task::{State, Task, TaskState, MARKER_DEPENDS};
use crate::tasks::{PropertyCollection, StateFilter, TasksRelay}; use crate::tasks::{PropertyCollection, StateFilter, TasksRelay};
@ -536,8 +536,7 @@ async fn main() -> Result<()> {
match arg { match arg {
None => match tasks.get_position() { None => match tasks.get_position() {
None => { None => {
info!("Filtering for bookmarked tasks"); tasks.set_priority(None);
tasks.set_view_bookmarks();
} }
Some(pos) => Some(pos) =>
match or_warn!(tasks.toggle_bookmark(pos)) { match or_warn!(tasks.toggle_bookmark(pos)) {
@ -546,7 +545,16 @@ async fn main() -> Result<()> {
None => {} 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 })));
}
},
} }
} }

View File

@ -19,8 +19,8 @@ use regex::bytes::Regex;
use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::Sender;
use TagStandard::Hashtag; use TagStandard::Hashtag;
const DEFAULT_PRIO: u16 = 25; const DEFAULT_PRIO: Prio = 25;
pub const HIGH_PRIO: u16 = 85; pub const HIGH_PRIO: Prio = 85;
/// Amount of seconds to treat as "now" /// Amount of seconds to treat as "now"
const MAX_OFFSET: u64 = 9; const MAX_OFFSET: u64 = 9;
@ -80,6 +80,8 @@ pub(crate) struct TasksRelay {
tags_excluded: BTreeSet<Tag>, tags_excluded: BTreeSet<Tag>,
/// Current active state /// Current active state
state: StateFilter, state: StateFilter,
/// Current priority for filtering and new tasks
priority: Option<Prio>,
sender: EventSender, sender: EventSender,
overflow: VecDeque<Event>, overflow: VecDeque<Event>,
@ -171,6 +173,8 @@ impl TasksRelay {
tags: Default::default(), tags: Default::default(),
tags_excluded: Default::default(), tags_excluded: Default::default(),
state: Default::default(), state: Default::default(),
priority: None,
search_depth: 4, search_depth: 4,
view_depth: 0, view_depth: 0,
recurse_activities: true, recurse_activities: true,
@ -356,6 +360,7 @@ impl TasksRelay {
.chain(self.tags_excluded.iter() .chain(self.tags_excluded.iter()
.map(|t| format!(" -#{}", t.content().unwrap()))) .map(|t| format!(" -#{}", t.content().unwrap())))
.chain(once(self.state.indicator())) .chain(once(self.state.indicator()))
.chain(self.priority.map(|p| format!(" *{:02}", p)))
.join("") .join("")
} }
@ -655,6 +660,15 @@ impl TasksRelay {
} }
} }
pub(crate) fn set_priority(&mut self, priority: Option<Prio>) {
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) { pub(crate) fn set_state_filter(&mut self, state: StateFilter) {
self.view.clear(); self.view.clear();
info!("Filtering for {}", state); info!("Filtering for {}", state);
@ -858,10 +872,13 @@ impl TasksRelay {
/// Sanitizes input /// Sanitizes input
pub(crate) fn make_task_with(&mut self, input: &str, tags: impl IntoIterator<Item=Tag>, set_state: bool) -> EventId { pub(crate) fn make_task_with(&mut self, input: &str, tags: impl IntoIterator<Item=Tag>, set_state: bool) -> EventId {
let (input, input_tags) = extract_tags(input.trim()); 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( let id = self.submit(
build_task(&input, input_tags, None) build_task(&input, input_tags, None)
.add_tags(self.tags.iter().cloned()) .add_tags(self.tags.iter().cloned())
.add_tags(tags) .add_tags(tags)
.add_tags(prio)
); );
if set_state { if set_state {
self.state.as_option().inspect(|s| self.set_state_for_with(id, s)); self.state.as_option().inspect(|s| self.set_state_for_with(id, s));