Compare commits
3 Commits
dedda147c0
...
298c1eceeb
Author | SHA1 | Date |
---|---|---|
xeruf | 298c1eceeb | |
xeruf | a801e12b57 | |
xeruf | f381608b4c |
|
@ -95,7 +95,7 @@ To stop time-tracking completely, simply move to the root of all tasks.
|
|||
- `TASK` - create task
|
||||
+ prefix with space if you want a task to start with a command character
|
||||
+ copy in text with newlines to create one task per line
|
||||
- `.` - clear filters
|
||||
- `.` - clear all filters
|
||||
- `.TASK`
|
||||
+ activate task by id
|
||||
+ match by task name prefix: if one or more tasks match, filter / activate (tries case-sensitive then case-insensitive)
|
||||
|
@ -125,9 +125,9 @@ Dot or slash can be repeated to move to parent tasks before acting.
|
|||
|
||||
Property Filters:
|
||||
|
||||
- `#TAG1 TAG2` - set tag filter (empty: list all used tags)
|
||||
- `+TAG` - add tag filter
|
||||
- `-TAG` - remove tag filters by prefix
|
||||
- `#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
|
||||
- `@AUTHOR` - filter by time or author (pubkey, or `@` for self, TBI: id prefix, name prefix)
|
||||
- TBI: `**INT` - filter by priority
|
||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -18,6 +18,7 @@ use log::{debug, error, info, LevelFilter, trace, warn};
|
|||
use nostr_sdk::prelude::*;
|
||||
use nostr_sdk::TagStandard::Hashtag;
|
||||
use regex::Regex;
|
||||
use rustyline::config::Configurer;
|
||||
use rustyline::DefaultEditor;
|
||||
use rustyline::error::ReadlineError;
|
||||
use tokio::sync::mpsc;
|
||||
|
@ -143,6 +144,7 @@ pub(crate) enum MostrMessage {
|
|||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let mut rl = DefaultEditor::new()?;
|
||||
rl.set_auto_add_history(true);
|
||||
|
||||
let mut args = args().skip(1).peekable();
|
||||
let mut builder = if args.peek().is_some_and(|arg| arg == "--debug") {
|
||||
|
@ -536,24 +538,24 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
|
||||
Some('#') =>
|
||||
match arg {
|
||||
Some(arg) => tasks.set_tags(arg.split_whitespace().map(|s| Hashtag(s.to_string()).into())),
|
||||
None => {
|
||||
println!("Hashtags of all known tasks:\n{}", tasks.all_hashtags().join(" "));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
tasks.set_tags(arg_default.split_whitespace().map(|s| Hashtag(s.to_string()).into())),
|
||||
|
||||
Some('+') =>
|
||||
match arg {
|
||||
Some(arg) => tasks.add_tag(arg.to_string()),
|
||||
None => tasks.clear_filter()
|
||||
None => {
|
||||
println!("Hashtags of all known tasks:\n{}", tasks.all_hashtags().join(" ").italic());
|
||||
if tasks.has_tag_filter() {
|
||||
println!("Use # to remove tag filters and . to remove all filters.")
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Some('-') =>
|
||||
match arg {
|
||||
Some(arg) => tasks.remove_tag(arg),
|
||||
None => tasks.clear_filter()
|
||||
None => tasks.clear_filters()
|
||||
}
|
||||
|
||||
Some('(') => {
|
||||
|
@ -574,7 +576,7 @@ async fn main() -> Result<()> {
|
|||
match arg {
|
||||
None => tasks.move_to(None),
|
||||
Some(arg) => {
|
||||
if parse_tracking_stamp(arg).map(|stamp| tasks.track_at(stamp, None)).is_some() {
|
||||
if parse_tracking_stamp(arg).and_then(|stamp| tasks.track_at(stamp, None)).is_some() {
|
||||
let (label, times) = tasks.times_tracked();
|
||||
println!("{}\n{}", label.italic(), times.rev().take(15).join("\n"));
|
||||
}
|
||||
|
@ -597,6 +599,8 @@ async fn main() -> Result<()> {
|
|||
tasks.move_to(pos.cloned());
|
||||
if dots > 1 {
|
||||
info!("Moving up {} tasks", dots - 1)
|
||||
} else {
|
||||
tasks.clear_filters();
|
||||
}
|
||||
} else if let Ok(depth) = slice.parse::<i8>() {
|
||||
if pos != tasks.get_position_ref() {
|
||||
|
|
66
src/tasks.rs
66
src/tasks.rs
|
@ -18,6 +18,11 @@ use crate::helpers::{CHARACTER_THRESHOLD, format_timestamp_local, format_timesta
|
|||
use crate::kinds::*;
|
||||
use crate::task::{MARKER_DEPENDS, MARKER_PARENT, State, Task, TaskState};
|
||||
|
||||
const MAX_OFFSET: u64 = 9;
|
||||
fn now() -> Timestamp {
|
||||
Timestamp::now() + MAX_OFFSET
|
||||
}
|
||||
|
||||
type TaskMap = HashMap<EventId, Task>;
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Tasks {
|
||||
|
@ -146,14 +151,17 @@ impl Tasks {
|
|||
self.get_position_ref().cloned()
|
||||
}
|
||||
|
||||
fn now() -> Timestamp {
|
||||
Timestamp::now() + Self::MAX_OFFSET
|
||||
pub(crate) fn get_position_ref(&self) -> Option<&EventId> {
|
||||
self.get_position_at(now()).1
|
||||
}
|
||||
|
||||
pub(crate) fn get_position_ref(&self) -> Option<&EventId> {
|
||||
self.history_from(Self::now())
|
||||
fn get_position_at(&self, timestamp: Timestamp) -> (Timestamp, Option<&EventId>) {
|
||||
self.history_from(timestamp)
|
||||
.last()
|
||||
.and_then(|e| referenced_events(e))
|
||||
.filter(|e| e.created_at <= timestamp)
|
||||
.map_or_else(
|
||||
|| (Timestamp::now(), None),
|
||||
|e| (e.created_at, referenced_events(e)))
|
||||
}
|
||||
|
||||
/// Ids of all subtasks recursively found for id, including itself
|
||||
|
@ -387,7 +395,7 @@ impl Tasks {
|
|||
let mut lock = stdout().lock();
|
||||
if let Some(t) = self.get_current_task() {
|
||||
let state = t.state_or_default();
|
||||
let now = &Self::now();
|
||||
let now = &now();
|
||||
let mut tracking_stamp: Option<Timestamp> = None;
|
||||
for elem in
|
||||
timestamps(self.history.get(&self.sender.pubkey()).into_iter().flatten(), &[t.get_id()])
|
||||
|
@ -505,15 +513,18 @@ impl Tasks {
|
|||
self.view = view;
|
||||
}
|
||||
|
||||
pub(crate) fn clear_filter(&mut self) {
|
||||
pub(crate) fn clear_filters(&mut self) {
|
||||
self.view.clear();
|
||||
self.tags.clear();
|
||||
self.tags_excluded.clear();
|
||||
info!("Removed all filters");
|
||||
}
|
||||
|
||||
pub(crate) fn has_tag_filter(&self) -> bool {
|
||||
!self.tags.is_empty() || !self.tags_excluded.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn set_tags(&mut self, tags: impl IntoIterator<Item=Tag>) {
|
||||
self.tags_excluded.clear();
|
||||
self.tags.clear();
|
||||
self.tags.extend(tags);
|
||||
}
|
||||
|
@ -624,8 +635,6 @@ impl Tasks {
|
|||
}).into_iter().flatten()
|
||||
}
|
||||
|
||||
const MAX_OFFSET: u64 = 9;
|
||||
|
||||
pub(crate) fn move_to(&mut self, target: Option<EventId>) {
|
||||
self.view.clear();
|
||||
let pos = self.get_position_ref();
|
||||
|
@ -641,8 +650,8 @@ impl Tasks {
|
|||
}
|
||||
|
||||
let now = Timestamp::now();
|
||||
let offset: u64 = self.history_from(now).skip_while(|e| e.created_at.as_u64() > now.as_u64() + Self::MAX_OFFSET).count() as u64;
|
||||
if offset >= Self::MAX_OFFSET {
|
||||
let offset: u64 = self.history_from(now).skip_while(|e| e.created_at.as_u64() > now.as_u64() + MAX_OFFSET).count() as u64;
|
||||
if offset >= MAX_OFFSET {
|
||||
warn!("Whoa you are moving around quickly! Give me a few seconds to process.")
|
||||
}
|
||||
self.submit(
|
||||
|
@ -729,22 +738,39 @@ impl Tasks {
|
|||
self.tasks.get(id).map_or(id.to_string(), |t| t.get_title())
|
||||
}
|
||||
|
||||
/// Parse string and set tracking
|
||||
/// Parse relative time string and track for current position
|
||||
///
|
||||
/// Returns false and prints a message if parsing failed
|
||||
pub(crate) fn track_from(&mut self, str: &str) -> bool {
|
||||
parse_tracking_stamp(str)
|
||||
.map(|stamp| self.track_at(stamp, self.get_position()))
|
||||
.and_then(|stamp| self.track_at(stamp, self.get_position()))
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn track_at(&mut self, time: Timestamp, task: Option<EventId>) -> EventId {
|
||||
info!("{} {}", task.map_or(
|
||||
String::from("Stopping time-tracking at"),
|
||||
|id| format!("Tracking \"{}\" from", self.get_task_title(&id))), format_timestamp_relative(&time));
|
||||
pub(crate) fn track_at(&mut self, time: Timestamp, target: Option<EventId>) -> Option<EventId> {
|
||||
let current_pos = self.get_position_at(time);
|
||||
if (time < Timestamp::now() || target.is_none()) && current_pos.1 == target.as_ref() {
|
||||
warn!("Already {} from {}",
|
||||
target.map_or("stopped time-tracking".to_string(),
|
||||
|id| format!("tracking \"{}\"", self.get_task_title(&id))),
|
||||
format_timestamp_relative(¤t_pos.0),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
info!("{}", match target {
|
||||
None => format!("Stopping time-tracking of \"{}\" at {}",
|
||||
current_pos.1.map_or("???".to_string(), |id| self.get_task_title(id)),
|
||||
format_timestamp_relative(&time)),
|
||||
Some(new_id) => format!("Tracking \"{}\" from {}{}",
|
||||
self.get_task_title(&new_id),
|
||||
format_timestamp_relative(&time),
|
||||
current_pos.1.filter(|id| id != &&new_id).map(
|
||||
|id| format!(" replacing \"{}\"", self.get_task_title(id))).unwrap_or_default()),
|
||||
});
|
||||
self.submit(
|
||||
build_tracking(task)
|
||||
build_tracking(target)
|
||||
.custom_created_at(time)
|
||||
)
|
||||
).into()
|
||||
}
|
||||
|
||||
/// Sign and queue the event to the relay, returning its id
|
||||
|
|
Loading…
Reference in New Issue