feat: greatly revamp filtering
This commit is contained in:
parent
ddc57dc36a
commit
132ea048a5
|
@ -143,9 +143,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.81"
|
version = "0.1.82"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
|
checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -166,9 +166,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-wsocket"
|
name = "async-wsocket"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5725a0615e4eb98e82e9cb963529398114e3fccfbf0e8b9111d605e2ac443e46"
|
checksum = "1eee6fcc818b89848df37050215603de0e2e072734e4730c03060feb2d0abebb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"futures",
|
"futures",
|
||||||
|
@ -372,9 +372,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.1.13"
|
version = "1.1.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
|
checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
@ -956,9 +956,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.4.0"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
|
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
@ -1188,7 +1188,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mostr"
|
name = "mostr"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-english",
|
"chrono-english",
|
||||||
|
@ -1257,9 +1257,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.34.0"
|
version = "0.34.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5897e4142fcc33c4f1d58ad17f665e87dcba70de7e370c0bda1aa0fb73212c2a"
|
checksum = "a1c3c32439eef3ea4d9079b2a8f557992d27259c26527e43d4228dd321e43a77"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
|
@ -1301,9 +1301,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-relay-pool"
|
name = "nostr-relay-pool"
|
||||||
version = "0.34.0"
|
version = "0.34.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c6480cf60564957a2a64bd050d047ee0717e08dced7a389e22ef4e9fc104edd2"
|
checksum = "d0e37c5ea991802a91728d4c09d5a7276938104ead8bf140a63a60acabc5c756"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"async-wsocket",
|
"async-wsocket",
|
||||||
|
@ -1386,9 +1386,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.3"
|
version = "0.36.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
|
checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -1543,9 +1543,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quinn"
|
name = "quinn"
|
||||||
version = "0.11.3"
|
version = "0.11.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156"
|
checksum = "a2d2fb862b7ba45e615c1429def928f2e15f815bdf933b27a2d3824e224c1f46"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
@ -1561,9 +1561,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quinn-proto"
|
name = "quinn-proto"
|
||||||
version = "0.11.6"
|
version = "0.11.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd"
|
checksum = "ea0a9b3a42929fad8a7c3de7f86ce0814cfa893328157672680e9fb1145549c5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"rand",
|
"rand",
|
||||||
|
@ -1779,9 +1779,9 @@ checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.34"
|
version = "0.38.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"errno",
|
"errno",
|
||||||
|
@ -1822,9 +1822,9 @@ checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.102.6"
|
version = "0.102.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
|
checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
|
@ -1908,18 +1908,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.208"
|
version = "1.0.209"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
|
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.208"
|
version = "1.0.209"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
|
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1928,9 +1928,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.125"
|
version = "1.0.127"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
|
checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
@ -2033,9 +2033,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.75"
|
version = "2.0.77"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9"
|
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2101,9 +2101,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.39.3"
|
version = "1.40.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
|
checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -2445,9 +2445,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "0.26.3"
|
version = "0.26.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
|
checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,7 +5,7 @@ repository = "https://forge.ftt.gmbh/janek/mostr"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "GPL 3.0"
|
license = "GPL 3.0"
|
||||||
authors = ["melonion"]
|
authors = ["melonion"]
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "mostr"
|
default-run = "mostr"
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ colored = "2.1"
|
||||||
parse_datetime = "0.5.0"
|
parse_datetime = "0.5.0"
|
||||||
interim = { version = "0.1", features = ["chrono"] }
|
interim = { version = "0.1", features = ["chrono"] }
|
||||||
nostr-sdk = "0.34" # { git = "https://github.com/rust-nostr/nostr" }
|
nostr-sdk = "0.34" # { git = "https://github.com/rust-nostr/nostr" }
|
||||||
tokio = { version = "1.37", features = ["rt", "rt-multi-thread", "macros"] }
|
tokio = { version = "1.40", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
regex = "1.10.5"
|
regex = "1.10.6"
|
||||||
rustyline = { git = "https://github.com/xeruf/rustyline", rev = "465b14d" }
|
rustyline = { git = "https://github.com/xeruf/rustyline", rev = "465b14d" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
68
src/main.rs
68
src/main.rs
|
@ -14,13 +14,13 @@ use chrono::Local;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use env_logger::{Builder, Target, WriteStyle};
|
use env_logger::{Builder, Target, WriteStyle};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{debug, error, info, LevelFilter, trace, warn};
|
use log::{debug, error, info, trace, warn, LevelFilter};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use nostr_sdk::TagStandard::Hashtag;
|
use nostr_sdk::TagStandard::Hashtag;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rustyline::config::Configurer;
|
use rustyline::config::Configurer;
|
||||||
use rustyline::DefaultEditor;
|
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
|
use rustyline::DefaultEditor;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
use tokio::time::error::Elapsed;
|
use tokio::time::error::Elapsed;
|
||||||
|
@ -28,8 +28,8 @@ use tokio::time::timeout;
|
||||||
use xdg::BaseDirectories;
|
use xdg::BaseDirectories;
|
||||||
|
|
||||||
use crate::helpers::*;
|
use crate::helpers::*;
|
||||||
use crate::kinds::{BASIC_KINDS, PROP_KINDS, PROPERTY_COLUMNS, TRACKING_KIND};
|
use crate::kinds::{BASIC_KINDS, PROPERTY_COLUMNS, PROP_KINDS, TRACKING_KIND};
|
||||||
use crate::task::{MARKER_DEPENDS, State};
|
use crate::task::{State, MARKER_DEPENDS};
|
||||||
use crate::tasks::{PropertyCollection, StateFilter, Tasks};
|
use crate::tasks::{PropertyCollection, StateFilter, Tasks};
|
||||||
|
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
@ -450,51 +450,37 @@ async fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Some('@') => {
|
Some('@') => {
|
||||||
match arg {
|
let success = match arg {
|
||||||
None => {
|
None => {
|
||||||
let today = Timestamp::now() - 80_000;
|
let today = Timestamp::now() - 80_000;
|
||||||
info!("Filtering for tasks opened in the last 22 hours");
|
info!("Filtering for tasks from the last 22 hours");
|
||||||
tasks.set_filter(
|
tasks.set_filter_from(today)
|
||||||
tasks.filtered_tasks(tasks.get_position_ref())
|
|
||||||
.filter(|t| t.last_state_update() > today)
|
|
||||||
.map(|t| t.event.id)
|
|
||||||
.collect()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Some(arg) => {
|
Some(arg) => {
|
||||||
if arg == "@" {
|
if arg == "@" {
|
||||||
let key = keys.public_key();
|
|
||||||
info!("Filtering for own tasks");
|
info!("Filtering for own tasks");
|
||||||
tasks.set_filter(
|
tasks.set_filter_author(keys.public_key())
|
||||||
tasks.filtered_tasks(tasks.get_position_ref())
|
|
||||||
.filter(|t| t.event.pubkey == key)
|
|
||||||
.map(|t| t.event.id)
|
|
||||||
.collect()
|
|
||||||
)
|
|
||||||
} else if let Ok(key) = PublicKey::from_str(arg) {
|
} else if let Ok(key) = PublicKey::from_str(arg) {
|
||||||
let author = tasks.get_author(&key);
|
let author = tasks.get_author(&key);
|
||||||
info!("Filtering for tasks by {author}");
|
info!("Filtering for tasks by {author}");
|
||||||
tasks.set_filter(
|
tasks.set_filter_author(key)
|
||||||
tasks.filtered_tasks(tasks.get_position_ref())
|
|
||||||
.filter(|t| t.event.pubkey == key)
|
|
||||||
.map(|t| t.event.id)
|
|
||||||
.collect()
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
parse_hour(arg, 1)
|
parse_hour(arg, 1)
|
||||||
.or_else(|| parse_date(arg).map(|utc| utc.with_timezone(&Local)))
|
.or_else(|| parse_date(arg).map(|utc| utc.with_timezone(&Local)))
|
||||||
.map(|time| {
|
.map(|time| {
|
||||||
info!("Filtering for tasks opened after {}", format_datetime_relative(time));
|
info!("Filtering for tasks from {}", format_datetime_relative(time));
|
||||||
let threshold = time.to_utc().timestamp();
|
let threshold = time.to_utc().timestamp();
|
||||||
tasks.set_filter(
|
tasks.set_filter_from(
|
||||||
tasks.filtered_tasks(tasks.get_position_ref())
|
if let Some(t) = 0u64.checked_add_signed(threshold) {
|
||||||
.filter(|t| t.last_state_update().as_u64() as i64 > threshold)
|
Timestamp::from(t)
|
||||||
.map(|t| t.event.id)
|
} else { Timestamp::zero() })
|
||||||
.collect()
|
})
|
||||||
);
|
.unwrap_or(false)
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
if !success {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -503,8 +489,8 @@ async fn main() -> Result<()> {
|
||||||
None => match tasks.get_position_ref() {
|
None => match tasks.get_position_ref() {
|
||||||
None => {
|
None => {
|
||||||
info!("Filtering for bookmarked tasks");
|
info!("Filtering for bookmarked tasks");
|
||||||
tasks.set_filter_bookmarks()
|
tasks.set_view_bookmarks();
|
||||||
},
|
}
|
||||||
Some(pos) => {
|
Some(pos) => {
|
||||||
info!("Toggling bookmark");
|
info!("Toggling bookmark");
|
||||||
or_warn!(tasks.toggle_bookmark(*pos));
|
or_warn!(tasks.toggle_bookmark(*pos));
|
||||||
|
@ -636,7 +622,7 @@ async fn main() -> Result<()> {
|
||||||
} else {
|
} else {
|
||||||
tasks.clear_filters();
|
tasks.clear_filters();
|
||||||
}
|
}
|
||||||
} else if let Ok(depth) = slice.parse::<i8>() {
|
} else if let Ok(depth) = slice.parse::<usize>() {
|
||||||
if pos != tasks.get_position_ref() {
|
if pos != tasks.get_position_ref() {
|
||||||
tasks.move_to(pos.cloned());
|
tasks.move_to(pos.cloned());
|
||||||
}
|
}
|
||||||
|
@ -667,19 +653,17 @@ async fn main() -> Result<()> {
|
||||||
transform = Box::new(|s| s.to_ascii_lowercase());
|
transform = Box::new(|s| s.to_ascii_lowercase());
|
||||||
}
|
}
|
||||||
|
|
||||||
let filtered = tasks.filtered_tasks(pos)
|
let filtered =
|
||||||
.filter(|t| {
|
tasks.get_filtered(|t| {
|
||||||
transform(&t.event.content).contains(slice) ||
|
transform(&t.event.content).contains(slice) ||
|
||||||
t.tags.iter().flatten().any(
|
t.tags.iter().flatten().any(
|
||||||
|tag| tag.content().is_some_and(|s| transform(s).contains(slice)))
|
|tag| tag.content().is_some_and(|s| transform(s).contains(slice)))
|
||||||
})
|
});
|
||||||
.map(|t| t.event.id)
|
|
||||||
.collect_vec();
|
|
||||||
if filtered.len() == 1 {
|
if filtered.len() == 1 {
|
||||||
tasks.move_to(filtered.into_iter().next());
|
tasks.move_to(filtered.into_iter().next());
|
||||||
} else {
|
} else {
|
||||||
tasks.move_to(pos.cloned());
|
tasks.move_to(pos.cloned());
|
||||||
tasks.set_filter(filtered);
|
tasks.set_view(filtered);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
399
src/tasks.rs
399
src/tasks.rs
|
@ -7,16 +7,16 @@ use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use itertools::Itertools;
|
use itertools::{Either, Itertools};
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use nostr_sdk::{Event, EventBuilder, EventId, JsonUtil, Keys, Kind, Metadata, PublicKey, Tag, TagStandard, Timestamp, UncheckedUrl, Url};
|
|
||||||
use nostr_sdk::prelude::Marker;
|
use nostr_sdk::prelude::Marker;
|
||||||
|
use nostr_sdk::{Event, EventBuilder, EventId, JsonUtil, Keys, Kind, Metadata, PublicKey, Tag, TagStandard, Timestamp, UncheckedUrl, Url};
|
||||||
use TagStandard::Hashtag;
|
use TagStandard::Hashtag;
|
||||||
|
|
||||||
use crate::{EventSender, MostrMessage};
|
use crate::helpers::{format_timestamp_local, format_timestamp_relative, format_timestamp_relative_to, parse_tracking_stamp, some_non_empty, CHARACTER_THRESHOLD};
|
||||||
use crate::helpers::{CHARACTER_THRESHOLD, format_timestamp_local, format_timestamp_relative, format_timestamp_relative_to, parse_tracking_stamp, some_non_empty};
|
|
||||||
use crate::kinds::*;
|
use crate::kinds::*;
|
||||||
use crate::task::{MARKER_DEPENDS, MARKER_PARENT, State, Task, TaskState};
|
use crate::task::{State, Task, TaskState, MARKER_DEPENDS, MARKER_PARENT};
|
||||||
|
use crate::{EventSender, MostrMessage};
|
||||||
|
|
||||||
const MAX_OFFSET: u64 = 9;
|
const MAX_OFFSET: u64 = 9;
|
||||||
fn now() -> Timestamp {
|
fn now() -> Timestamp {
|
||||||
|
@ -41,10 +41,10 @@ pub(crate) struct Tasks {
|
||||||
sorting: VecDeque<String>,
|
sorting: VecDeque<String>,
|
||||||
|
|
||||||
/// A filtered view of the current tasks
|
/// A filtered view of the current tasks
|
||||||
|
/// Would like this to be Task references but that doesn't work
|
||||||
|
/// unless I start meddling with Rc everywhere.
|
||||||
view: Vec<EventId>,
|
view: Vec<EventId>,
|
||||||
/// Zero: Only Active node
|
depth: usize,
|
||||||
/// Positive: Go down the respective level
|
|
||||||
depth: i8,
|
|
||||||
|
|
||||||
/// Currently active tags
|
/// Currently active tags
|
||||||
tags: BTreeSet<Tag>,
|
tags: BTreeSet<Tag>,
|
||||||
|
@ -74,10 +74,7 @@ impl StateFilter {
|
||||||
|
|
||||||
fn matches(&self, task: &Task) -> bool {
|
fn matches(&self, task: &Task) -> bool {
|
||||||
match self {
|
match self {
|
||||||
StateFilter::Default => {
|
StateFilter::Default => task.pure_state().is_open(),
|
||||||
let state = task.pure_state();
|
|
||||||
state.is_open() || (state == State::Done && task.parent_id().is_some())
|
|
||||||
}
|
|
||||||
StateFilter::All => true,
|
StateFilter::All => true,
|
||||||
StateFilter::State(filter) => task.state().is_some_and(|t| t.matches_label(filter)),
|
StateFilter::State(filter) => task.state().is_some_and(|t| t.matches_label(filter)),
|
||||||
}
|
}
|
||||||
|
@ -97,7 +94,7 @@ impl Display for StateFilter {
|
||||||
f,
|
f,
|
||||||
"{}",
|
"{}",
|
||||||
match self {
|
match self {
|
||||||
StateFilter::Default => "relevant tasks".to_string(),
|
StateFilter::Default => "open tasks".to_string(),
|
||||||
StateFilter::All => "all tasks".to_string(),
|
StateFilter::All => "all tasks".to_string(),
|
||||||
StateFilter::State(s) => format!("state {s}"),
|
StateFilter::State(s) => format!("state {s}"),
|
||||||
}
|
}
|
||||||
|
@ -176,11 +173,6 @@ impl Tasks {
|
||||||
|e| (e.created_at, referenced_event(e)))
|
|e| (e.created_at, referenced_event(e)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ids of all subtasks recursively found for id, including itself
|
|
||||||
fn get_task_tree<'a>(&'a self, id: &'a EventId) -> ChildIterator {
|
|
||||||
ChildIterator::from(self, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn all_hashtags(&self) -> impl Iterator<Item=&str> {
|
pub(crate) fn all_hashtags(&self) -> impl Iterator<Item=&str> {
|
||||||
self.tasks.values()
|
self.tasks.values()
|
||||||
.filter(|t| t.pure_state() != State::Closed)
|
.filter(|t| t.pure_state() != State::Closed)
|
||||||
|
@ -246,7 +238,7 @@ impl Tasks {
|
||||||
fn total_time_tracked(&self, id: EventId) -> u64 {
|
fn total_time_tracked(&self, id: EventId) -> u64 {
|
||||||
let mut total = 0;
|
let mut total = 0;
|
||||||
|
|
||||||
let children = self.get_task_tree(&id).get_all();
|
let children = ChildIterator::from(&self, &id).get_all();
|
||||||
for user in self.history.values() {
|
for user in self.history.values() {
|
||||||
total += Durations::from(user.values(), &children).sum::<Duration>().as_secs();
|
total += Durations::from(user.values(), &children).sum::<Duration>().as_secs();
|
||||||
}
|
}
|
||||||
|
@ -317,35 +309,35 @@ impl Tasks {
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
fn resolve_tasks<'a>(&'a self, iter: impl Iterator<Item=&'a EventId>) -> impl Iterator<Item=&'a Task> {
|
fn resolve_tasks<'a>(
|
||||||
self.resolve_tasks_rec(iter, self.depth)
|
&'a self,
|
||||||
|
iter: impl Iterator<Item=&'a EventId>,
|
||||||
|
sparse: bool,
|
||||||
|
) -> Vec<&'a Task> {
|
||||||
|
self.resolve_tasks_rec(iter, sparse, self.depth)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_tasks_rec<'a>(
|
fn resolve_tasks_rec<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
iter: impl Iterator<Item=&'a EventId>,
|
iter: impl Iterator<Item=&'a EventId>,
|
||||||
depth: i8,
|
sparse: bool,
|
||||||
) -> Box<impl Iterator<Item=&'a Task>> {
|
depth: usize,
|
||||||
|
) -> Vec<&'a Task> {
|
||||||
iter.filter_map(|id| self.get_by_id(id))
|
iter.filter_map(|id| self.get_by_id(id))
|
||||||
.flat_map(move |task| {
|
.flat_map(move |task| {
|
||||||
let new_depth = depth - 1;
|
let new_depth = depth - 1;
|
||||||
if new_depth == 0 {
|
if new_depth > 0 {
|
||||||
vec![task]
|
let mut children = self.resolve_tasks_rec(task.children.iter(), sparse, new_depth);
|
||||||
} else {
|
if !children.is_empty() {
|
||||||
let tasks_iter = self.resolve_tasks_rec(task.children.iter(), new_depth);
|
if !sparse {
|
||||||
if new_depth < 0 {
|
children.push(task);
|
||||||
let tasks: Vec<&Task> = tasks_iter.collect();
|
|
||||||
if tasks.is_empty() {
|
|
||||||
vec![task]
|
|
||||||
} else {
|
|
||||||
tasks
|
|
||||||
}
|
}
|
||||||
} else {
|
return children;
|
||||||
tasks_iter.chain(once(task)).collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return if self.filter(task) { vec![task] } else { vec![] };
|
||||||
})
|
})
|
||||||
.into()
|
.collect_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executes the given function with each task referenced by this event without marker.
|
/// Executes the given function with each task referenced by this event without marker.
|
||||||
|
@ -377,32 +369,33 @@ impl Tasks {
|
||||||
.map(|t| t.get_id())
|
.map(|t| t.get_id())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn filtered_tasks<'a>(&'a self, position: Option<&'a EventId>) -> impl Iterator<Item=&Task> + 'a {
|
fn filter(&self, task: &Task) -> bool {
|
||||||
let current: HashMap<&EventId, &Task> = self.resolve_tasks(self.children_of(position)).map(|t| (t.get_id(), t)).collect();
|
self.state.matches(task) &&
|
||||||
let bookmarks =
|
task.tags.as_ref().map_or(true, |tags| {
|
||||||
if current.is_empty() {
|
|
||||||
vec![]
|
|
||||||
} else {
|
|
||||||
self.bookmarks.iter()
|
|
||||||
.filter(|id| !position.is_some_and(|p| &p == id) && !current.contains_key(id))
|
|
||||||
.filter_map(|id| self.get_by_id(id))
|
|
||||||
.collect_vec()
|
|
||||||
};
|
|
||||||
// TODO use ChildIterator
|
|
||||||
current.into_values().chain(
|
|
||||||
bookmarks
|
|
||||||
).filter(move |t| {
|
|
||||||
// TODO apply filters in transit
|
|
||||||
self.state.matches(t) &&
|
|
||||||
t.tags.as_ref().map_or(true, |tags| {
|
|
||||||
!tags.iter().any(|tag| self.tags_excluded.contains(tag))
|
!tags.iter().any(|tag| self.tags_excluded.contains(tag))
|
||||||
}) &&
|
}) &&
|
||||||
(self.tags.is_empty() ||
|
(self.tags.is_empty() ||
|
||||||
t.tags.as_ref().map_or(false, |tags| {
|
task.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))
|
||||||
}))
|
}))
|
||||||
})
|
}
|
||||||
|
|
||||||
|
pub(crate) fn filtered_tasks<'a>(&'a self, position: Option<&'a EventId>, sparse: bool) -> Vec<&'a Task> {
|
||||||
|
let mut current = self.resolve_tasks(self.children_of(position), sparse);
|
||||||
|
let ids = current.iter().map(|t| t.get_id()).collect_vec();
|
||||||
|
let mut bookmarks =
|
||||||
|
if sparse && current.is_empty() {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
self.bookmarks.iter()
|
||||||
|
.filter(|id| !position.is_some_and(|p| &p == id) && !ids.contains(id))
|
||||||
|
.filter_map(|id| self.get_by_id(id))
|
||||||
|
.filter(|t| self.filter(t))
|
||||||
|
.collect_vec()
|
||||||
|
};
|
||||||
|
current.append(&mut bookmarks);
|
||||||
|
current
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn visible_tasks(&self) -> Vec<&Task> {
|
pub(crate) fn visible_tasks(&self) -> Vec<&Task> {
|
||||||
|
@ -410,9 +403,9 @@ impl Tasks {
|
||||||
return vec![];
|
return vec![];
|
||||||
}
|
}
|
||||||
if !self.view.is_empty() {
|
if !self.view.is_empty() {
|
||||||
return self.resolve_tasks(self.view.iter()).collect();
|
return self.view.iter().flat_map(|id| self.get_by_id(id)).collect();
|
||||||
}
|
}
|
||||||
self.filtered_tasks(self.get_position_ref()).collect()
|
self.filtered_tasks(self.get_position_ref(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn print_tasks(&self) -> Result<(), Error> {
|
pub(crate) fn print_tasks(&self) -> Result<(), Error> {
|
||||||
|
@ -540,18 +533,50 @@ impl Tasks {
|
||||||
self.bookmarks.iter().map(|id| Tag::event(*id))))
|
self.bookmarks.iter().map(|id| Tag::event(*id))))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_filter_bookmarks(&mut self) {
|
pub(crate) fn set_filter_author(&mut self, key: PublicKey) -> bool {
|
||||||
self.set_filter(self.bookmarks.clone())
|
self.set_filter(|t| t.event.pubkey == key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_filter(&mut self, view: Vec<EventId>) {
|
pub(crate) fn set_filter_from(&mut self, time: Timestamp) -> bool {
|
||||||
|
self.set_filter(|t| t.last_state_update() > time)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_filtered<P>(&self, predicate: P) -> Vec<EventId>
|
||||||
|
where
|
||||||
|
P: Fn(&&Task) -> bool,
|
||||||
|
{
|
||||||
|
self.filtered_tasks(self.get_position_ref(), false)
|
||||||
|
.into_iter()
|
||||||
|
.filter(predicate)
|
||||||
|
.map(|t| t.event.id)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_filter<P>(&mut self, predicate: P) -> bool
|
||||||
|
where
|
||||||
|
P: Fn(&&Task) -> bool,
|
||||||
|
{
|
||||||
|
self.set_view(self.get_filtered(predicate))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_view_bookmarks(&mut self) -> bool {
|
||||||
|
self.set_view(self.bookmarks.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set currently visible tasks.
|
||||||
|
/// Returns whether there are any.
|
||||||
|
pub(crate) fn set_view(&mut self, view: Vec<EventId>) -> bool {
|
||||||
if view.is_empty() {
|
if view.is_empty() {
|
||||||
warn!("No match for filter!")
|
warn!("No match for filter!");
|
||||||
|
self.view = view;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
self.view = view;
|
self.view = view;
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn clear_filters(&mut self) {
|
pub(crate) fn clear_filters(&mut self) {
|
||||||
|
self.state = StateFilter::Default;
|
||||||
self.view.clear();
|
self.view.clear();
|
||||||
self.tags.clear();
|
self.tags.clear();
|
||||||
self.tags_excluded.clear();
|
self.tags_excluded.clear();
|
||||||
|
@ -601,14 +626,14 @@ impl Tasks {
|
||||||
self.sender.flush();
|
self.sender.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns ids of tasks starting with the given string.
|
/// Returns ids of tasks matching the given string.
|
||||||
///
|
///
|
||||||
/// Tries, in order:
|
/// Tries, in order:
|
||||||
/// - single case-insensitive exact name match in visible tasks
|
/// - single case-insensitive exact name match in visible tasks
|
||||||
/// - single case-insensitive exact name match in all tasks
|
/// - single case-insensitive exact name match in all tasks
|
||||||
/// - visible tasks starting with given arg case-sensitive
|
/// - visible tasks starting with given arg case-sensitive
|
||||||
/// - visible tasks where any word starts with given arg case-insensitive
|
/// - visible tasks where any word starts with given arg case-insensitive
|
||||||
pub(crate) fn get_filtered(&self, position: Option<&EventId>, arg: &str) -> Vec<EventId> {
|
pub(crate) fn get_matching(&self, position: Option<&EventId>, arg: &str) -> Vec<EventId> {
|
||||||
if let Ok(id) = EventId::parse(arg) {
|
if let Ok(id) = EventId::parse(arg) {
|
||||||
return vec![id];
|
return vec![id];
|
||||||
}
|
}
|
||||||
|
@ -617,7 +642,7 @@ impl Tasks {
|
||||||
|
|
||||||
let mut filtered: Vec<EventId> = Vec::with_capacity(32);
|
let mut filtered: Vec<EventId> = Vec::with_capacity(32);
|
||||||
let mut filtered_fuzzy: Vec<EventId> = Vec::with_capacity(32);
|
let mut filtered_fuzzy: Vec<EventId> = Vec::with_capacity(32);
|
||||||
for task in self.filtered_tasks(position) {
|
for task in self.filtered_tasks(position, false) {
|
||||||
let lowercase = task.event.content.to_ascii_lowercase();
|
let lowercase = task.event.content.to_ascii_lowercase();
|
||||||
if lowercase == lowercase_arg {
|
if lowercase == lowercase_arg {
|
||||||
return vec![task.event.id];
|
return vec![task.event.id];
|
||||||
|
@ -651,7 +676,7 @@ impl Tasks {
|
||||||
/// Finds out what to do with the given string.
|
/// Finds out what to do with the given string.
|
||||||
/// Returns an EventId if a new Task was created.
|
/// Returns an EventId if a new Task was created.
|
||||||
pub(crate) fn filter_or_create(&mut self, position: Option<&EventId>, arg: &str) -> Option<EventId> {
|
pub(crate) fn filter_or_create(&mut self, position: Option<&EventId>, arg: &str) -> Option<EventId> {
|
||||||
let filtered = self.get_filtered(position, arg);
|
let filtered = self.get_matching(position, arg);
|
||||||
match filtered.len() {
|
match filtered.len() {
|
||||||
0 => {
|
0 => {
|
||||||
// No match, new task
|
// No match, new task
|
||||||
|
@ -670,7 +695,7 @@ impl Tasks {
|
||||||
_ => {
|
_ => {
|
||||||
// Multiple match, filter
|
// Multiple match, filter
|
||||||
self.move_to(position.cloned());
|
self.move_to(position.cloned());
|
||||||
self.set_filter(filtered);
|
self.set_view(filtered);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -976,10 +1001,10 @@ impl Tasks {
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
|
|
||||||
pub(crate) fn set_depth(&mut self, depth: i8) {
|
pub(crate) fn set_depth(&mut self, depth: usize) {
|
||||||
if depth < self.depth && !self.view.is_empty() {
|
if !self.view.is_empty() {
|
||||||
self.view.clear();
|
self.view.clear();
|
||||||
info!("Cleared search and reduced view depth to {depth}");
|
info!("Cleared search and changed view depth to {depth}");
|
||||||
} else {
|
} else {
|
||||||
info!("Changed view depth to {depth}");
|
info!("Changed view depth to {depth}");
|
||||||
}
|
}
|
||||||
|
@ -1136,6 +1161,24 @@ impl Iterator for Durations<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
enum ChildIteratorFilter {
|
||||||
|
Reject = 0b00,
|
||||||
|
TakeSelf = 0b01,
|
||||||
|
TakeChildren = 0b10,
|
||||||
|
Take = 0b11,
|
||||||
|
}
|
||||||
|
impl ChildIteratorFilter {
|
||||||
|
fn takes_children(&self) -> bool {
|
||||||
|
self == &ChildIteratorFilter::Take ||
|
||||||
|
self == &ChildIteratorFilter::TakeChildren
|
||||||
|
}
|
||||||
|
fn takes_self(&self) -> bool {
|
||||||
|
self == &ChildIteratorFilter::Take ||
|
||||||
|
self == &ChildIteratorFilter::TakeSelf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Breadth-First Iterator over Tasks and recursive children
|
/// Breadth-First Iterator over Tasks and recursive children
|
||||||
struct ChildIterator<'a> {
|
struct ChildIterator<'a> {
|
||||||
tasks: &'a TaskMap,
|
tasks: &'a TaskMap,
|
||||||
|
@ -1149,6 +1192,28 @@ struct ChildIterator<'a> {
|
||||||
next_depth_at: usize,
|
next_depth_at: usize,
|
||||||
}
|
}
|
||||||
impl<'a> ChildIterator<'a> {
|
impl<'a> ChildIterator<'a> {
|
||||||
|
fn rooted(tasks: &'a TaskMap, id: Option<&EventId>) -> Self {
|
||||||
|
let mut queue = Vec::with_capacity(tasks.len());
|
||||||
|
queue.append(
|
||||||
|
&mut tasks
|
||||||
|
.values()
|
||||||
|
.filter(move |t| t.parent_id() == id)
|
||||||
|
.map(|t| t.get_id())
|
||||||
|
.collect_vec()
|
||||||
|
);
|
||||||
|
Self::with_queue(tasks, queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_queue(tasks: &'a TaskMap, queue: Vec<&'a EventId>) -> Self {
|
||||||
|
ChildIterator {
|
||||||
|
tasks: &tasks,
|
||||||
|
next_depth_at: queue.len(),
|
||||||
|
index: 0,
|
||||||
|
depth: 1,
|
||||||
|
queue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn from(tasks: &'a Tasks, id: &'a EventId) -> Self {
|
fn from(tasks: &'a Tasks, id: &'a EventId) -> Self {
|
||||||
let mut queue = Vec::with_capacity(30);
|
let mut queue = Vec::with_capacity(30);
|
||||||
queue.push(id);
|
queue.push(id);
|
||||||
|
@ -1166,52 +1231,110 @@ impl<'a> ChildIterator<'a> {
|
||||||
fn process_depth(&mut self, depth: usize) -> bool {
|
fn process_depth(&mut self, depth: usize) -> bool {
|
||||||
while self.depth < depth {
|
while self.depth < depth {
|
||||||
if self.next().is_none() {
|
if self.next().is_none() {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all children
|
||||||
|
fn get_all(mut self) -> Vec<&'a EventId> {
|
||||||
|
while self.next().is_some() {}
|
||||||
|
self.queue
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all tasks until the specified depth
|
/// Get all tasks until the specified depth
|
||||||
fn get_depth(mut self, depth: usize) -> Vec<&'a EventId> {
|
fn get_depth(mut self, depth: usize) -> Vec<&'a EventId> {
|
||||||
self.process_depth(depth);
|
self.process_depth(depth);
|
||||||
self.queue
|
self.queue
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all children
|
/// Get all tasks until the specified depth matching the filter
|
||||||
fn get_all(mut self) -> Vec<&'a EventId> {
|
fn get_depth_filtered<F>(mut self, depth: usize, filter: F) -> Vec<&'a EventId>
|
||||||
while self.next().is_some() {}
|
where
|
||||||
self.queue
|
F: Fn(&Task) -> ChildIteratorFilter,
|
||||||
|
{
|
||||||
|
while self.depth < depth {
|
||||||
|
if self.next_filtered(&filter).is_none() {
|
||||||
|
// TODO this can easily recurse beyond the intended depth
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl<'a> Iterator for ChildIterator<'a> {
|
|
||||||
type Item = &'a EventId;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if self.index >= self.queue.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let id = self.queue[self.index];
|
|
||||||
if let Some(task) = self.tasks.get(id) {
|
|
||||||
self.queue.reserve(task.children.len());
|
|
||||||
self.queue.extend(task.children.iter());
|
|
||||||
} else {
|
|
||||||
// Unknown task, might still find children, just slower
|
|
||||||
for task in self.tasks.values() {
|
|
||||||
if task.parent_id().is_some_and(|i| i == id) {
|
|
||||||
self.queue.push(task.get_id());
|
|
||||||
}
|
}
|
||||||
|
while self.index < self.queue.len() {
|
||||||
|
if let Some(task) = self.tasks.get(self.queue[self.index]) {
|
||||||
|
if !filter(task).takes_self() {
|
||||||
|
self.queue.remove(self.index);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.index += 1;
|
self.index += 1;
|
||||||
|
}
|
||||||
|
self.queue
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_depth(&mut self) {
|
||||||
if self.next_depth_at == self.index {
|
if self.next_depth_at == self.index {
|
||||||
self.depth += 1;
|
self.depth += 1;
|
||||||
self.next_depth_at = self.queue.len();
|
self.next_depth_at = self.queue.len();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get next id and advance, without adding children
|
||||||
|
fn next_task(&mut self) -> Option<&'a EventId> {
|
||||||
|
if self.index >= self.queue.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let id = self.queue[self.index];
|
||||||
|
self.index += 1;
|
||||||
Some(id)
|
Some(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the next known task and run it through the filter
|
||||||
|
fn next_filtered<F>(&mut self, filter: &F) -> Option<&'a Task>
|
||||||
|
where
|
||||||
|
F: Fn(&Task) -> ChildIteratorFilter,
|
||||||
|
{
|
||||||
|
self.next_task().and_then(|id| {
|
||||||
|
if let Some(task) = self.tasks.get(id) {
|
||||||
|
let take = filter(task);
|
||||||
|
if take.takes_children() {
|
||||||
|
self.queue.reserve(task.children.len());
|
||||||
|
self.queue.extend(task.children.iter());
|
||||||
|
}
|
||||||
|
if take.takes_self() {
|
||||||
|
self.check_depth();
|
||||||
|
return Some(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.check_depth();
|
||||||
|
self.next_filtered(filter)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl FusedIterator for ChildIterator<'_> {}
|
impl FusedIterator for ChildIterator<'_> {}
|
||||||
|
impl<'a> Iterator for ChildIterator<'a> {
|
||||||
|
type Item = &'a EventId;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.next_task().inspect(|id| {
|
||||||
|
match self.tasks.get(id) {
|
||||||
|
None => {
|
||||||
|
// Unknown task, might still find children, just slower
|
||||||
|
for task in self.tasks.values() {
|
||||||
|
if task.parent_id().is_some_and(|i| i == *id) {
|
||||||
|
self.queue.push(task.get_id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(task) => {
|
||||||
|
self.queue.reserve(task.children.len());
|
||||||
|
self.queue.extend(task.children.iter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.check_depth();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
struct ParentIterator<'a> {
|
struct ParentIterator<'a> {
|
||||||
|
@ -1265,6 +1388,13 @@ mod tasks_test {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! assert_tasks {
|
||||||
|
($left:expr, $right:expr $(,)?) => {
|
||||||
|
assert_eq!($left.visible_tasks().iter().map(|t| t.event.id).collect::<HashSet<EventId>>(),
|
||||||
|
HashSet::from($right))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bookmarks() {
|
fn test_bookmarks() {
|
||||||
let mut tasks = stub_tasks();
|
let mut tasks = stub_tasks();
|
||||||
|
@ -1275,29 +1405,33 @@ mod tasks_test {
|
||||||
tasks.move_to(Some(parent));
|
tasks.move_to(Some(parent));
|
||||||
let pin = tasks.make_task("pin");
|
let pin = tasks.make_task("pin");
|
||||||
|
|
||||||
assert_eq!(tasks.filtered_tasks(None).count(), 2);
|
assert_eq!(tasks.filtered_tasks(None, true).len(), 2);
|
||||||
assert_eq!(tasks.filtered_tasks(Some(&zero)).count(), 0);
|
assert_eq!(tasks.filtered_tasks(None, false).len(), 2);
|
||||||
|
assert_eq!(tasks.filtered_tasks(Some(&zero), false).len(), 0);
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||||
assert_eq!(tasks.filtered_tasks(Some(&pin)).count(), 0);
|
assert_eq!(tasks.filtered_tasks(Some(&pin), false).len(), 0);
|
||||||
assert_eq!(tasks.filtered_tasks(Some(&zero)).count(), 0);
|
assert_eq!(tasks.filtered_tasks(Some(&zero), false).len(), 0);
|
||||||
|
|
||||||
tasks.submit(EventBuilder::new(Kind::Bookmarks, "", [Tag::event(pin), Tag::event(zero)]));
|
tasks.submit(EventBuilder::new(Kind::Bookmarks, "", [Tag::event(pin), Tag::event(zero)]));
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||||
assert_eq!(tasks.filtered_tasks(Some(&pin)).count(), 0);
|
assert_eq!(tasks.filtered_tasks(Some(&pin), true).len(), 0);
|
||||||
assert_eq!(tasks.filtered_tasks(Some(&zero)).count(), 0);
|
assert_eq!(tasks.filtered_tasks(Some(&pin), false).len(), 0);
|
||||||
|
assert_eq!(tasks.filtered_tasks(Some(&zero), true).len(), 0);
|
||||||
|
assert_eq!(tasks.filtered_tasks(Some(&zero), false), vec![tasks.get_by_id(&pin).unwrap()]);
|
||||||
|
|
||||||
tasks.move_to(None);
|
tasks.move_to(None);
|
||||||
assert_eq!(tasks.visible_tasks().len(), 3);
|
assert_eq!(tasks.depth, 1);
|
||||||
|
assert_tasks!(tasks, [pin, test, parent]);
|
||||||
tasks.set_depth(2);
|
tasks.set_depth(2);
|
||||||
assert_eq!(tasks.visible_tasks().len(), 3);
|
assert_tasks!(tasks, [pin, test]);
|
||||||
tasks.add_tag("tag".to_string());
|
tasks.add_tag("tag".to_string());
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
assert_tasks!(tasks, [test]);
|
||||||
assert_eq!(tasks.filtered_tasks(None).collect_vec(), vec![tasks.get_by_id(&test).unwrap()]);
|
assert_eq!(tasks.filtered_tasks(None, true), vec![tasks.get_by_id(&test).unwrap()]);
|
||||||
tasks.submit(EventBuilder::new(Kind::Bookmarks, "", []));
|
tasks.submit(EventBuilder::new(Kind::Bookmarks, "", []));
|
||||||
tasks.clear_filters();
|
tasks.clear_filters();
|
||||||
assert_eq!(tasks.visible_tasks().len(), 3);
|
assert_tasks!(tasks, [pin, test]);
|
||||||
tasks.set_depth(1);
|
tasks.set_depth(1);
|
||||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
assert_tasks!(tasks, [test, parent]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1406,24 +1540,22 @@ mod tasks_test {
|
||||||
assert_position!(tasks, t1);
|
assert_position!(tasks, t1);
|
||||||
tasks.depth = 2;
|
tasks.depth = 2;
|
||||||
assert_eq!(tasks.visible_tasks().len(), 0);
|
assert_eq!(tasks.visible_tasks().len(), 0);
|
||||||
let t2 = tasks.make_task("t2");
|
let t11 = tasks.make_task("t11: tag");
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
assert_eq!(tasks.visible_tasks().len(), 1);
|
||||||
assert_eq!(tasks.get_task_path(Some(t2)), "t1>t2");
|
assert_eq!(tasks.get_task_path(Some(t11)), "t1>t11");
|
||||||
assert_eq!(tasks.relative_path(t2), "t2");
|
assert_eq!(tasks.relative_path(t11), "t11");
|
||||||
let t3 = tasks.make_task("t3");
|
let t12 = tasks.make_task("t12");
|
||||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
assert_eq!(tasks.visible_tasks().len(), 2);
|
||||||
|
|
||||||
tasks.move_to(Some(t2));
|
tasks.move_to(Some(t11));
|
||||||
assert_position!(tasks, t2);
|
assert_position!(tasks, t11);
|
||||||
assert_eq!(tasks.visible_tasks().len(), 0);
|
assert_eq!(tasks.visible_tasks().len(), 0);
|
||||||
let t4 = tasks.make_task("t4");
|
let t111 = tasks.make_task("t111");
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
assert_tasks!(tasks, [t111]);
|
||||||
assert_eq!(tasks.get_task_path(Some(t4)), "t1>t2>t4");
|
assert_eq!(tasks.get_task_path(Some(t111)), "t1>t11>t111");
|
||||||
assert_eq!(tasks.relative_path(t4), "t4");
|
assert_eq!(tasks.relative_path(t111), "t111");
|
||||||
tasks.depth = 2;
|
tasks.depth = 2;
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
assert_tasks!(tasks, [t111]);
|
||||||
tasks.depth = -1;
|
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
|
||||||
|
|
||||||
assert_eq!(ChildIterator::from(&tasks, &EventId::all_zeros()).get_all().len(), 1);
|
assert_eq!(ChildIterator::from(&tasks, &EventId::all_zeros()).get_all().len(), 1);
|
||||||
assert_eq!(ChildIterator::from(&tasks, &EventId::all_zeros()).get_depth(0).len(), 1);
|
assert_eq!(ChildIterator::from(&tasks, &EventId::all_zeros()).get_depth(0).len(), 1);
|
||||||
|
@ -1436,33 +1568,22 @@ mod tasks_test {
|
||||||
tasks.move_to(Some(t1));
|
tasks.move_to(Some(t1));
|
||||||
assert_position!(tasks, t1);
|
assert_position!(tasks, t1);
|
||||||
assert_eq!(tasks.get_own_events_history().count(), 3);
|
assert_eq!(tasks.get_own_events_history().count(), 3);
|
||||||
assert_eq!(tasks.relative_path(t4), "t2>t4");
|
assert_eq!(tasks.relative_path(t111), "t11>t111");
|
||||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
assert_eq!(tasks.depth, 2);
|
||||||
tasks.depth = 2;
|
assert_tasks!(tasks, [t111, t12]);
|
||||||
assert_eq!(tasks.visible_tasks().len(), 3);
|
tasks.set_view(vec![t11]);
|
||||||
tasks.set_filter(vec![t2]);
|
assert_tasks!(tasks, [t11]); // No more depth applied to view
|
||||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
tasks.set_depth(1);
|
||||||
tasks.depth = 1;
|
assert_tasks!(tasks, [t11, t12]);
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
|
||||||
tasks.depth = -1;
|
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
|
||||||
tasks.set_filter(vec![t2, t3]);
|
|
||||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
|
||||||
tasks.depth = 2;
|
|
||||||
assert_eq!(tasks.visible_tasks().len(), 3);
|
|
||||||
tasks.depth = 1;
|
|
||||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
|
||||||
|
|
||||||
tasks.move_to(None);
|
tasks.move_to(None);
|
||||||
assert_eq!(tasks.visible_tasks().len(), 1);
|
assert_tasks!(tasks, [t1]);
|
||||||
tasks.depth = 2;
|
tasks.depth = 2;
|
||||||
assert_eq!(tasks.visible_tasks().len(), 3);
|
assert_tasks!(tasks, [t11, t12]);
|
||||||
tasks.depth = 3;
|
tasks.depth = 3;
|
||||||
assert_eq!(tasks.visible_tasks().len(), 4);
|
assert_tasks!(tasks, [t111, t12]);
|
||||||
tasks.depth = 9;
|
tasks.depth = 9;
|
||||||
assert_eq!(tasks.visible_tasks().len(), 4);
|
assert_tasks!(tasks, [t111, t12]);
|
||||||
tasks.depth = -1;
|
|
||||||
assert_eq!(tasks.visible_tasks().len(), 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue