Compare commits
3 Commits
516acadd4a
...
a297f61363
Author | SHA1 | Date |
---|---|---|
xeruf | a297f61363 | |
xeruf | 96ca945263 | |
xeruf | d4bca1c26f |
|
@ -111,12 +111,13 @@ Append `@TIME` to any task creation or change command to record the action with
|
|||
- `:[IND][PROP]` - add property column PROP at IND or end,
|
||||
if it already exists remove property column PROP or IND; empty: list properties
|
||||
- `::[PROP]` - sort by property PROP (multiple space-separated values allowed)
|
||||
- `([TIME]` - list tracked times or insert timetracking with the specified offset
|
||||
- `([TIME]` - list tracked times or insert timetracking with the specified offset (double to view all history)
|
||||
such as `-1d`, `-15 minutes`, `yesterday 17:20`, `in 2 fortnights`
|
||||
- `)[TIME]` - stop timetracking with optional offset - also convenience helper to move to root
|
||||
- `>[TEXT]` - complete 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
|
||||
- `!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 note (stateless task / task description)
|
||||
- TBI: `;[TEXT]` - list comments or comment on task
|
||||
|
|
77
src/main.rs
77
src/main.rs
|
@ -29,7 +29,7 @@ use xdg::BaseDirectories;
|
|||
|
||||
use crate::helpers::*;
|
||||
use crate::kinds::{BASIC_KINDS, PROPERTY_COLUMNS, PROP_KINDS, TRACKING_KIND};
|
||||
use crate::task::{State, MARKER_DEPENDS};
|
||||
use crate::task::{State, Task, TaskState, MARKER_DEPENDS};
|
||||
use crate::tasks::{PropertyCollection, StateFilter, TasksRelay};
|
||||
|
||||
mod helpers;
|
||||
|
@ -262,7 +262,7 @@ async fn main() -> Result<()> {
|
|||
or_warn!(client.set_metadata(meta).await, "Unable to set metadata");
|
||||
}
|
||||
|
||||
loop {
|
||||
'repl: loop {
|
||||
let result_received = timeout(Duration::from_secs(INACTVITY_DELAY), rx.recv()).await;
|
||||
match result_received {
|
||||
Ok(Some(MostrMessage::NewRelay(url))) => {
|
||||
|
@ -300,7 +300,7 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
Ok(None) => {
|
||||
debug!("Finalizing nostr communication thread because communication channel was closed");
|
||||
break;
|
||||
break 'repl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -325,7 +325,7 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
'repl: loop {
|
||||
println!();
|
||||
let tasks = relays.get(&selected_relay).unwrap();
|
||||
let prompt = format!(
|
||||
|
@ -370,7 +370,7 @@ async fn main() -> Result<()> {
|
|||
debug!("Flushing Tasks because of empty command");
|
||||
tasks.flush();
|
||||
or_warn!(tasks.print_tasks());
|
||||
continue;
|
||||
continue 'repl;
|
||||
}
|
||||
Some('@') => {}
|
||||
Some(_) => {
|
||||
|
@ -413,7 +413,7 @@ async fn main() -> Result<()> {
|
|||
tasks.get_columns().add_or_remove(arg.to_string());
|
||||
} else {
|
||||
println!("{}", PROPERTY_COLUMNS);
|
||||
continue;
|
||||
continue 'repl;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -424,12 +424,12 @@ async fn main() -> Result<()> {
|
|||
|| info!("With a task selected, use ,NOTE to attach NOTE and , to list all its notes"),
|
||||
|task| println!("{}", task.description_events().map(|e| format!("{} {}", format_timestamp_local(&e.created_at), e.content)).join("\n")),
|
||||
);
|
||||
continue;
|
||||
continue 'repl;
|
||||
}
|
||||
Some(arg) => {
|
||||
if arg.len() < CHARACTER_THRESHOLD {
|
||||
warn!("Note needs at least {CHARACTER_THRESHOLD} characters!");
|
||||
continue;
|
||||
continue 'repl;
|
||||
}
|
||||
tasks.make_note(arg)
|
||||
}
|
||||
|
@ -437,12 +437,12 @@ async fn main() -> Result<()> {
|
|||
|
||||
Some('>') => {
|
||||
tasks.update_state(arg_default, State::Done);
|
||||
tasks.move_up();
|
||||
if tasks.custom_time.is_none() { tasks.move_up(); }
|
||||
}
|
||||
|
||||
Some('<') => {
|
||||
tasks.update_state(arg_default, State::Closed);
|
||||
tasks.move_up();
|
||||
if tasks.custom_time.is_none() { tasks.move_up(); }
|
||||
}
|
||||
|
||||
Some('&') => {
|
||||
|
@ -455,7 +455,7 @@ async fn main() -> Result<()> {
|
|||
_ => {
|
||||
if !tasks.move_back_to(text) {
|
||||
warn!("Did not find a match in history for \"{text}\"");
|
||||
continue;
|
||||
continue 'repl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -489,7 +489,7 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
};
|
||||
if !success {
|
||||
continue;
|
||||
continue 'repl;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -559,9 +559,22 @@ async fn main() -> Result<()> {
|
|||
tasks.set_state_for(id, right, state);
|
||||
break 'block;
|
||||
}
|
||||
if let Some(time) = parse_hour(left, 20)
|
||||
.map(|dt| dt.to_utc())
|
||||
.or_else(|| parse_date(left)) {
|
||||
let stamp = time.to_timestamp();
|
||||
let state = tasks.get_by_id(&id).and_then(Task::state);
|
||||
tasks.set_state_for(id, right, State::Pending);
|
||||
tasks.custom_time = Some(stamp);
|
||||
tasks.set_state_for(id,
|
||||
&state.as_ref().map(TaskState::get_label).unwrap_or_default(),
|
||||
state.map(|ts| ts.state).unwrap_or(State::Open));
|
||||
break 'block;
|
||||
}
|
||||
}
|
||||
tasks.set_state_for_with(id, arg_default);
|
||||
}
|
||||
tasks.custom_time = None;
|
||||
tasks.move_up();
|
||||
}
|
||||
}
|
||||
|
@ -577,7 +590,7 @@ async fn main() -> Result<()> {
|
|||
if tasks.has_tag_filter() {
|
||||
println!("Use # to remove tag filters and . to remove all filters.")
|
||||
}
|
||||
continue;
|
||||
continue 'repl;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -589,17 +602,33 @@ async fn main() -> Result<()> {
|
|||
|
||||
Some('(') => {
|
||||
if let Some(arg) = arg {
|
||||
if tasks.track_from(arg) {
|
||||
let (label, times) = tasks.times_tracked();
|
||||
let (first, remaining) = arg.split_at(1);
|
||||
if first == "(" {
|
||||
let mut max = usize::MAX;
|
||||
match remaining.parse::<usize>() {
|
||||
Ok(number) => max = number,
|
||||
Err(e) => warn!("Unsure what to do with {:?}", e),
|
||||
}
|
||||
let (label, mut times) = tasks.times_tracked();
|
||||
println!("{}\n{}", label.italic(),
|
||||
times.rev().take(15).collect_vec().iter().rev().join("\n"));
|
||||
times.rev().take(max).collect_vec().iter().rev().join("\n"));
|
||||
} else if let Ok(key) = PublicKey::parse(arg) { // TODO also match name
|
||||
let (label, mut times) = tasks.times_tracked_for(&key);
|
||||
println!("{}\n{}", label.italic(),
|
||||
times.join("\n"));
|
||||
} else {
|
||||
if tasks.track_from(arg) {
|
||||
let (label, times) = tasks.times_tracked();
|
||||
println!("{}\n{}", label.italic(),
|
||||
times.rev().take(15).collect_vec().iter().rev().join("\n"));
|
||||
}
|
||||
}
|
||||
// TODO show history of author / pubkey
|
||||
} else {
|
||||
let (label, mut times) = tasks.times_tracked();
|
||||
println!("{}\n{}", label.italic(), times.join("\n"));
|
||||
println!("{}\n{}", label.italic(),
|
||||
times.rev().take(80).collect_vec().iter().rev().join("\n"));
|
||||
}
|
||||
continue;
|
||||
continue 'repl;
|
||||
}
|
||||
|
||||
Some(')') => {
|
||||
|
@ -612,7 +641,7 @@ async fn main() -> Result<()> {
|
|||
times.rev().take(15).collect_vec().iter().rev().join("\n"));
|
||||
}
|
||||
// So the error message is not covered up
|
||||
continue;
|
||||
continue 'repl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -677,7 +706,7 @@ async fn main() -> Result<()> {
|
|||
if let Some((url, tasks)) = relays.iter().find(|(key, _)| key.as_ref().is_some_and(|url| url.as_str().starts_with(&command))) {
|
||||
selected_relay.clone_from(url);
|
||||
or_warn!(tasks.print_tasks());
|
||||
continue;
|
||||
continue 'repl;
|
||||
}
|
||||
or_warn!(Url::parse(&command), "Failed to parse url {}", command).map(|url| {
|
||||
match tx.try_send(MostrMessage::NewRelay(url.clone())) {
|
||||
|
@ -689,7 +718,7 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
});
|
||||
continue;
|
||||
continue 'repl;
|
||||
} else if command.contains('\n') {
|
||||
command.split('\n').for_each(|line| {
|
||||
if !line.trim().is_empty() {
|
||||
|
@ -703,8 +732,8 @@ async fn main() -> Result<()> {
|
|||
tasks.custom_time = None;
|
||||
or_warn!(tasks.print_tasks());
|
||||
}
|
||||
Err(ReadlineError::Eof) => break,
|
||||
Err(ReadlineError::Interrupted) => break, // TODO exit if prompt was empty, or clear
|
||||
Err(ReadlineError::Eof) => break 'repl,
|
||||
Err(ReadlineError::Interrupted) => break 'repl, // TODO exit if prompt was empty, or clear
|
||||
Err(e) => warn!("{}", e),
|
||||
}
|
||||
}
|
||||
|
|
18
src/tasks.rs
18
src/tasks.rs
|
@ -174,16 +174,18 @@ impl TasksRelay {
|
|||
|
||||
pub(crate) fn process_overflow(&mut self) {
|
||||
let elements = self.overflow.len();
|
||||
let mut issues = 0;
|
||||
for _ in 0..elements {
|
||||
if let Some(event) = self.overflow.pop_front() {
|
||||
if let Some(event) = self.overflow.pop_back() {
|
||||
if let Some(event) = self.add_prop(event) {
|
||||
warn!("Unable to sort Event {:?}", event);
|
||||
issues += 1;
|
||||
//self.overflow.push_back(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
if elements > 0 {
|
||||
info!("Reprocessed {elements} updates{}", self.sender.url.clone().map(|url| format!(" from {url}")).unwrap_or_default());
|
||||
info!("Reprocessed {elements} updates with {issues} issues{}", self.sender.url.clone().map(|url| format!(" from {url}")).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,9 +228,13 @@ impl TasksRelay {
|
|||
|
||||
/// Dynamic time tracking overview for current task or current user.
|
||||
pub(crate) fn times_tracked(&self) -> (String, Box<dyn DoubleEndedIterator<Item=String>>) {
|
||||
self.times_tracked_for(&self.sender.pubkey())
|
||||
}
|
||||
|
||||
pub(crate) fn times_tracked_for(&self, key: &PublicKey) -> (String, Box<dyn DoubleEndedIterator<Item=String>>) {
|
||||
match self.get_position_ref() {
|
||||
None => {
|
||||
if let Some(hist) = self.history.get(&self.sender.pubkey()) {
|
||||
if let Some(hist) = self.history.get(key) {
|
||||
let mut last = None;
|
||||
let mut full = Vec::with_capacity(hist.len());
|
||||
for event in hist.values() {
|
||||
|
@ -249,6 +255,7 @@ impl TasksRelay {
|
|||
}
|
||||
}
|
||||
Some(id) => {
|
||||
// TODO consider pubkey
|
||||
let ids = vec![id];
|
||||
let history =
|
||||
self.history.iter().flat_map(|(key, set)| {
|
||||
|
@ -1052,7 +1059,10 @@ impl TasksRelay {
|
|||
comment,
|
||||
id,
|
||||
);
|
||||
info!("Task status {} set for \"{}\"", TaskState::get_label_for(&state, comment), self.get_task_title(&id));
|
||||
info!("Task status {} set for \"{}\"{}",
|
||||
TaskState::get_label_for(&state, comment),
|
||||
self.get_task_title(&id),
|
||||
self.custom_time.map(|ts| format!(" at {}", format_timestamp_relative(&ts))).unwrap_or_default());
|
||||
self.submit(prop)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue