From 89cda4088e0b8775a76d014906f6d4baf4a90f1a Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 7 Dec 2022 01:36:22 +0100 Subject: [PATCH 001/266] Add some &rl2dev history bug workarounds --- CHANGELOG.md | 4 ++++ src/euph/room.rs | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53b8c86..c32f659 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ Procedure when bumping the version number: ## Unreleased +### Changed +- Always connect to &rl2dev in ephemeral mode +- Reduce amount of messages per &rl2dev log request + ## v0.5.1 - 2022-11-27 ### Changed diff --git a/src/euph/room.rs b/src/euph/room.rs index 3d332fd..ca25c4c 100644 --- a/src/euph/room.rs +++ b/src/euph/room.rs @@ -382,7 +382,13 @@ impl State { None => None, }; - let _ = conn_tx.send(Log { n: 1000, before }).await?; + // &rl2dev's message history is broken and requesting old messages past + // a certain point results in errors. By reducing the amount of messages + // in each log request, we can get closer to this point. Since &rl2dev + // is fairly low in activity, this should be fine. + let n = if vault.room() == "rl2dev" { 50 } else { 1000 }; + + let _ = conn_tx.send(Log { n, before }).await?; // The code handling incoming events and replies also handles // `LogReply`s, so we don't need to do anything special here. @@ -462,7 +468,13 @@ impl Room { let (canary_tx, canary_rx) = oneshot::channel(); let (event_tx, event_rx) = mpsc::unbounded_channel(); let (euph_room_event_tx, euph_room_event_rx) = mpsc::unbounded_channel(); - let ephemeral = vault.vault().vault().ephemeral(); + + // &rl2dev's message history is broken and requesting old messages past + // a certain point results in errors. Cove should not keep retrying log + // requests when hitting that limit, so &rl2dev is always opened in + // ephemeral mode. + let room_name = vault.room().to_string(); + let ephemeral = vault.vault().vault().ephemeral() || room_name == "rl2dev"; let state = State { name: vault.room().to_string(), From 008554a2bdddf8c96a6090caf4aafa7c109b1d5f Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 10 Dec 2022 02:49:46 +0100 Subject: [PATCH 002/266] Update euphoxide and tokio-tungstenite --- Cargo.lock | 38 +++++++++++++++++++------------------- Cargo.toml | 4 ++-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba11328..f6def3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,8 +301,8 @@ dependencies = [ [[package]] name = "euphoxide" -version = "0.1.0" -source = "git+https://github.com/Garmelon/euphoxide.git?rev=f5ba899a364b78afdf063d864cfa3f6287ddf11b#f5ba899a364b78afdf063d864cfa3f6287ddf11b" +version = "0.2.0" +source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.2.0#58e39fa5bc4d5de4fcc0d467a123823760381b51" dependencies = [ "futures", "serde", @@ -973,18 +973,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.147" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" dependencies = [ "proc-macro2", "quote", @@ -1003,10 +1003,10 @@ dependencies = [ ] [[package]] -name = "sha-1" -version = "0.10.0" +name = "sha1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", @@ -1082,9 +1082,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" dependencies = [ "proc-macro2", "quote", @@ -1178,9 +1178,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" dependencies = [ "autocfg", "bytes", @@ -1193,7 +1193,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -1220,9 +1220,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" dependencies = [ "futures-util", "log", @@ -1256,9 +1256,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" dependencies = [ "base64", "byteorder", @@ -1268,7 +1268,7 @@ dependencies = [ "log", "rand", "rustls", - "sha-1", + "sha1", "thiserror", "url", "utf-8", diff --git a/Cargo.toml b/Cargo.toml index 22bd317..ad6b745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,12 +29,12 @@ version = "0.3.17" features = ["macros", "formatting", "parsing", "serde"] [dependencies.tokio-tungstenite] -version = "0.17.2" +version = "0.18.0" features = ["rustls-tls-native-roots"] [dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -rev = "f5ba899a364b78afdf063d864cfa3f6287ddf11b" +tag = "v0.2.0" # [patch."https://github.com/Garmelon/euphoxide.git"] # euphoxide = { path = "../euphoxide/" } From 5acf49d01898344c8763afc3dac7352d08d75108 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 11 Dec 2022 20:36:41 +0100 Subject: [PATCH 003/266] Simplify lints --- src/main.rs | 9 ++------- src/ui/input.rs | 1 + 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2412ef4..adf33e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,11 @@ -#![deny(unsafe_code)] +#![forbid(unsafe_code)] // Rustc lint groups #![warn(future_incompatible)] #![warn(rust_2018_idioms)] +#![warn(unused)] // Rustc lints #![warn(noop_method_call)] #![warn(single_use_lifetimes)] -#![warn(trivial_numeric_casts)] -#![warn(unused_crate_dependencies)] -#![warn(unused_extern_crates)] -#![warn(unused_import_braces)] -#![warn(unused_lifetimes)] -#![warn(unused_qualifications)] // Clippy lints #![warn(clippy::use_self)] diff --git a/src/ui/input.rs b/src/ui/input.rs index 00ec1cd..d8cf209 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -54,6 +54,7 @@ impl From for KeyEvent { } #[rustfmt::skip] +#[allow(unused_macro_rules)] macro_rules! key { // key!(Paste text) ( Paste $text:ident ) => { crate::ui::input::InputEvent::Paste($text) }; From 20186bda5ce9e7c0abf6b87c2f3448f99f200594 Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 5 Jan 2023 14:21:50 +0100 Subject: [PATCH 004/266] Satisfy clippy --- src/ui/chat/tree/layout.rs | 2 ++ src/ui/rooms.rs | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ui/chat/tree/layout.rs b/src/ui/chat/tree/layout.rs index 3a22bc0..bc6f4de 100644 --- a/src/ui/chat/tree/layout.rs +++ b/src/ui/chat/tree/layout.rs @@ -388,6 +388,7 @@ impl> InnerTreeViewState { // message should always be visible. I'm not using top_line.clamp(...) // because the order of the min and max matters. let top_line = block.top_line; + #[allow(clippy::manual_clamp)] let new_top_line = top_line.min(max_line).max(min_line); if new_top_line != top_line { blocks.blocks_mut().offset(new_top_line - top_line); @@ -415,6 +416,7 @@ impl> InnerTreeViewState { // because the order of the min and max matters. let top_line = block.top_line; let new_top_line = (height - block.height) / 2; + #[allow(clippy::manual_clamp)] let new_top_line = new_top_line.min(max_line).max(min_line); if new_top_line != top_line { blocks.blocks_mut().offset(new_top_line - top_line); diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index 3e4d9d0..f79cb1e 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -265,13 +265,13 @@ impl Rooms { match (status, unseen) { (None, None) => Styled::default(), (None, Some(u)) => Styled::new_plain(" (") - .then(&u, unseen_style) + .then(u, unseen_style) .then_plain(")"), - (Some(s), None) => Styled::new_plain(" (").then_plain(&s).then_plain(")"), + (Some(s), None) => Styled::new_plain(" (").then_plain(s).then_plain(")"), (Some(s), Some(u)) => Styled::new_plain(" (") - .then_plain(&s) + .then_plain(s) .then_plain(", ") - .then(&u, unseen_style) + .then(u, unseen_style) .then_plain(")"), } } From acb03b1f09f17cf68d6379aeb7764ad43b1900fb Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 14 Jan 2023 17:23:25 +0100 Subject: [PATCH 005/266] Open room present link with p --- CHANGELOG.md | 3 +++ src/ui/euph/room.rs | 13 ++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c32f659..c35b87d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ Procedure when bumping the version number: ## Unreleased +### Added +- Key binding to open present page + ### Changed - Always connect to &rl2dev in ephemeral mode - Reduce amount of messages per &rl2dev log request diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 6d415cd..2f1f0f9 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -388,6 +388,7 @@ impl EuphRoom { // Inspecting messages bindings.binding("i", "inspect message"); bindings.binding("I", "show message links"); + bindings.binding("ctrl+p", "open room's plugh.de/present page"); } async fn handle_room_input_event(&mut self, event: &InputEvent, status: &RoomStatus) -> bool { @@ -425,7 +426,7 @@ impl EuphRoom { _ => {} } - // Inspecting messages + // Always applicable match event { key!('i') => { if let Some(id) = self.chat.cursor().await { @@ -443,6 +444,16 @@ impl EuphRoom { } return true; } + key!(Ctrl + 'p') => { + let link = format!("https://plugh.de/present/{}/", self.vault.room()); + if let Err(error) = open::that(&link) { + self.popups.push_front(RoomPopup::Error { + description: format!("Failed to open link: {link}"), + reason: format!("{error}"), + }); + } + return true; + } _ => {} } From f61c03cf0a7f50bbfa6e73ce51683a31adcf0599 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 14 Jan 2023 17:31:02 +0100 Subject: [PATCH 006/266] Remove redundant vault --- src/ui/euph/room.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 2f1f0f9..c8e1b52 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -70,7 +70,6 @@ pub struct EuphRoom { ui_event_tx: mpsc::UnboundedSender, - vault: EuphRoomVault, room: Option, focus: Focus, @@ -92,7 +91,6 @@ impl EuphRoom { Self { config, ui_event_tx, - vault: vault.clone(), room: None, focus: Focus::Chat, state: State::Normal, @@ -103,6 +101,14 @@ impl EuphRoom { } } + fn vault(&self) -> &EuphRoomVault { + self.chat.store() + } + + fn name(&self) -> &str { + self.vault().room() + } + async fn shovel_room_events( name: String, mut euph_room_event_rx: mpsc::UnboundedReceiver, @@ -120,10 +126,8 @@ impl EuphRoom { pub fn connect(&mut self) { if self.room.is_none() { - let store = self.chat.store().clone(); - let name = store.room().to_string(); let (room, euph_room_event_rx) = euph::Room::new( - store, + self.vault().clone(), self.config.username.clone(), self.config.force_username, self.config.password.clone(), @@ -132,7 +136,7 @@ impl EuphRoom { self.room = Some(room); tokio::task::spawn(Self::shovel_room_events( - name, + self.name().to_string(), euph_room_event_rx, self.ui_event_tx.clone(), )); @@ -167,7 +171,7 @@ impl EuphRoom { } pub async fn unseen_msgs_count(&self) -> usize { - self.vault.unseen_msgs_count().await + self.vault().unseen_msgs_count().await } async fn stabilize_pseudo_msg(&mut self) { @@ -290,9 +294,8 @@ impl EuphRoom { } async fn status_widget(&self, status: &RoomStatus) -> BoxedWidget { - let room = self.chat.store().room(); let room_style = ContentStyle::default().bold().blue(); - let mut info = Styled::new(format!("&{room}"), room_style); + let mut info = Styled::new(format!("&{}", self.name()), room_style); info = match status { RoomStatus::NoRoom | RoomStatus::Stopped => info.then_plain(", archive"), @@ -430,7 +433,7 @@ impl EuphRoom { match event { key!('i') => { if let Some(id) = self.chat.cursor().await { - if let Some(msg) = self.vault.full_msg(id).await { + if let Some(msg) = self.vault().full_msg(id).await { self.state = State::InspectMessage(msg); } } @@ -438,14 +441,14 @@ impl EuphRoom { } key!('I') => { if let Some(id) = self.chat.cursor().await { - if let Some(msg) = self.vault.msg(id).await { + if let Some(msg) = self.vault().msg(id).await { self.state = State::Links(LinksState::new(&msg.content)); } } return true; } key!(Ctrl + 'p') => { - let link = format!("https://plugh.de/present/{}/", self.vault.room()); + let link = format!("https://plugh.de/present/{}/", self.name()); if let Err(error) = open::that(&link) { self.popups.push_front(RoomPopup::Error { description: format!("Failed to open link: {link}"), From 1585b2e8a11dc53a2c47eb1cfb59ce669f14cdb5 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 14 Jan 2023 18:00:58 +0100 Subject: [PATCH 007/266] Update dependencies --- Cargo.lock | 265 +++++++++++++++++++++-------------------------------- Cargo.toml | 22 ++--- 2 files changed, 117 insertions(+), 170 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6def3f..be8370a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,15 +24,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282" dependencies = [ "proc-macro2", "quote", @@ -51,6 +51,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "bitflags" version = "1.3.2" @@ -86,9 +92,9 @@ checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cc" -version = "1.0.77" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cfg-if" @@ -98,9 +104,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.0.27" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0acbd8d28a0a60d7108d7ae850af6ba34cf2d1257fc646980e5f97ce14275966" +checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" dependencies = [ "bitflags", "clap_derive", @@ -113,9 +119,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.0.21" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" dependencies = [ "heck", "proc-macro-error", @@ -126,18 +132,18 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" dependencies = [ "os_str_bytes", ] [[package]] name = "cookie" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "time", "version_check", @@ -458,15 +464,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.2.6" @@ -514,31 +511,31 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e394faa0efb47f9f227f1cd89978f854542b318a6f64fa695489c9c993056656" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" dependencies = [ "libc", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "is-terminal" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae5bc6e2eb41c9def29a3e0f1306382807764b9b53112030eff57435667352d" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "io-lifetimes", "rustix", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" @@ -549,17 +546,11 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" -version = "0.2.137" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libsqlite3-sys" @@ -583,9 +574,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "lock_api" @@ -621,24 +612,24 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi", "libc", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "open" @@ -647,7 +638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" dependencies = [ "pathdiff", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -674,15 +665,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.4" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -747,18 +738,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -815,9 +806,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -871,23 +862,23 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.3" +version = "0.36.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1fbb4dfc4eb1d390c02df47760bb19a84bb80b301ecc947ab5406394d8223e" +checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "rustls" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", @@ -909,27 +900,26 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64", + "base64 0.21.0", ] [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys", ] [[package]] @@ -973,18 +963,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.149" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.149" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -993,9 +983,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ "itoa", "ryu", @@ -1082,9 +1072,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -1116,18 +1106,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -1178,9 +1168,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.23.0" +version = "1.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" +checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" dependencies = [ "autocfg", "bytes", @@ -1193,14 +1183,14 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -1236,9 +1226,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" dependencies = [ "serde", ] @@ -1246,7 +1236,7 @@ dependencies = [ [[package]] name = "toss" version = "0.1.0" -source = "git+https://github.com/Garmelon/toss.git?rev=06aefd562bd66f5564f7c1ea73a4959f51be74e7#06aefd562bd66f5564f7c1ea73a4959f51be74e7" +source = "git+https://github.com/Garmelon/toss.git?rev=0a3b193f796bcb5e1a211d0e3c1589565d9848c2#0a3b193f796bcb5e1a211d0e3c1589565d9848c2" dependencies = [ "crossterm", "unicode-linebreak", @@ -1260,7 +1250,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", "bytes", "http", @@ -1277,9 +1267,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" @@ -1289,9 +1279,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-linebreak" @@ -1481,19 +1471,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - [[package]] name = "windows-sys" version = "0.42.0" @@ -1501,82 +1478,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" diff --git a/Cargo.toml b/Cargo.toml index ad6b745..2d5244d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,25 +4,25 @@ version = "0.5.1" edition = "2021" [dependencies] -anyhow = "1.0.66" -async-trait = "0.1.58" -clap = { version = "4.0.27", features = ["derive", "deprecated"] } -cookie = "0.16.1" +anyhow = "1.0.68" +async-trait = "0.1.61" +clap = { version = "4.1.1", features = ["derive", "deprecated"] } +cookie = "0.16.2" crossterm = "0.25.0" directories = "4.0.1" edit = "0.1.4" +linkify = "0.9.0" log = { version = "0.4.17", features = ["std"] } open = "3.2.0" parking_lot = "0.12.1" rusqlite = { version = "0.28.0", features = ["bundled", "time"] } -serde = { version = "1.0.147", features = ["derive"] } -serde_json = "1.0.89" -thiserror = "1.0.37" -tokio = { version = "1.22.0", features = ["full"] } -toml = "0.5.9" +serde = { version = "1.0.152", features = ["derive"] } +serde_json = "1.0.91" +thiserror = "1.0.38" +tokio = { version = "1.24.1", features = ["full"] } +toml = "0.5.10" unicode-segmentation = "1.10.0" unicode-width = "0.1.10" -linkify = "0.9.0" [dependencies.time] version = "0.3.17" @@ -41,7 +41,7 @@ tag = "v0.2.0" [dependencies.toss] git = "https://github.com/Garmelon/toss.git" -rev = "06aefd562bd66f5564f7c1ea73a4959f51be74e7" +rev = "0a3b193f796bcb5e1a211d0e3c1589565d9848c2" # [patch."https://github.com/Garmelon/toss.git"] # toss = { path = "../toss/" } From 82d6738e491d3d07ba94946c14d00851854aff0a Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 14 Jan 2023 18:02:13 +0100 Subject: [PATCH 008/266] Bump version to 0.5.2 --- CHANGELOG.md | 2 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c35b87d..c039516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ Procedure when bumping the version number: ## Unreleased +## v0.5.2 - 2023-01-14 + ### Added - Key binding to open present page diff --git a/Cargo.lock b/Cargo.lock index be8370a..ec6c6ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,7 +167,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cove" -version = "0.5.1" +version = "0.5.2" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 2d5244d..a8fdbe7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cove" -version = "0.5.1" +version = "0.5.2" edition = "2021" [dependencies] From 9324517c568dda6815656d27e93c562ba58afcee Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 20 Jan 2023 19:33:52 +0100 Subject: [PATCH 009/266] Update euphoxide --- Cargo.lock | 49 ++++++++++++------------------------------ Cargo.toml | 2 +- src/euph/room.rs | 32 +++++++++++++-------------- src/euph/util.rs | 3 ++- src/ui/euph/account.rs | 4 ++-- src/ui/euph/room.rs | 36 +++++++++++++++---------------- src/ui/rooms.rs | 8 +++---- 7 files changed, 57 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec6c6ed..3d86a8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -308,13 +308,14 @@ dependencies = [ [[package]] name = "euphoxide" version = "0.2.0" -source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.2.0#58e39fa5bc4d5de4fcc0d467a123823760381b51" +source = "git+https://github.com/Garmelon/euphoxide.git?rev=c15c05a64e82c7c5f7af0539745855c0f6821869#c15c05a64e82c7c5f7af0539745855c0f6821869" dependencies = [ - "futures", + "futures-util", "serde", "serde_json", "time", "tokio", + "tokio-stream", "tokio-tungstenite", ] @@ -354,42 +355,12 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" -dependencies = [ - "futures-core", - "futures-sink", -] - [[package]] name = "futures-core" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" -[[package]] -name = "futures-io" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" - [[package]] name = "futures-sink" version = "0.3.25" @@ -408,12 +379,9 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ - "futures-channel", "futures-core", - "futures-io", "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1208,6 +1176,17 @@ dependencies = [ "webpki", ] +[[package]] +name = "tokio-stream" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-tungstenite" version = "0.18.0" diff --git a/Cargo.toml b/Cargo.toml index a8fdbe7..7394fa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ features = ["rustls-tls-native-roots"] [dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -tag = "v0.2.0" +rev = "c15c05a64e82c7c5f7af0539745855c0f6821869" # [patch."https://github.com/Garmelon/euphoxide.git"] # euphoxide = { path = "../euphoxide/" } diff --git a/src/euph/room.rs b/src/euph/room.rs index ca25c4c..39207a9 100644 --- a/src/euph/room.rs +++ b/src/euph/room.rs @@ -9,7 +9,7 @@ use euphoxide::api::packet::ParsedPacket; use euphoxide::api::{ Auth, AuthOption, Data, Log, Login, Logout, MessageId, Nick, Send, Time, UserId, }; -use euphoxide::conn::{ConnRx, ConnTx, Joining, Status}; +use euphoxide::conn::{Conn, ConnTx, Joining, State as ConnState}; use log::{error, info, warn}; use parking_lot::Mutex; use tokio::sync::{mpsc, oneshot}; @@ -44,7 +44,7 @@ enum Event { Disconnected, Packet(Box), // Commands - Status(oneshot::Sender>), + Status(oneshot::Sender>), // TODO Rename to State RequestLogs, Auth(String), Nick(String), @@ -116,7 +116,7 @@ impl State { info!("e&{}: connected", name); event_tx.send(Event::Connected(conn_tx))?; - while let Some(packet) = conn_rx.recv().await { + while let Ok(packet) = conn_rx.recv().await { event_tx.send(Event::Packet(Box::new(packet)))?; } @@ -161,19 +161,17 @@ impl State { vault.set_cookies(cookie_jar); } - async fn connect( - vault: &EuphRoomVault, - name: &str, - ) -> anyhow::Result> { + // TODO Simplify return type, remove ConnTx + async fn connect(vault: &EuphRoomVault, name: &str) -> anyhow::Result> { // TODO Set user agent? let cookies = Self::get_cookies(vault.vault()).await; let cookies = HeaderValue::from_str(&cookies).expect("valid cookies"); - match euphoxide::connect("euphoria.io", name, true, Some(cookies), TIMEOUT).await { - Ok((tx, rx, set_cookies)) => { + match Conn::connect("euphoria.io", name, true, Some(cookies), TIMEOUT).await { + Ok((rx, set_cookies)) => { Self::update_cookies(vault.vault(), &set_cookies); - Ok(Some((tx, rx))) + Ok(Some((rx.tx().clone(), rx))) } Err(tungstenite::Error::Http(resp)) if resp.status().is_client_error() => { bail!("room {name} doesn't exist"); @@ -251,9 +249,9 @@ impl State { } async fn own_user_id(&self) -> Option { - Some(match self.conn_tx.as_ref()?.status().await.ok()? { - Status::Joining(Joining { hello, .. }) => hello?.session.id, - Status::Joined(joined) => joined.session.id, + Some(match self.conn_tx.as_ref()?.state().await.ok()? { + ConnState::Joining(Joining { hello, .. }) => hello?.session.id, + ConnState::Joined(joined) => joined.session.id, }) } @@ -341,9 +339,10 @@ impl State { Ok(()) } - async fn on_status(&self, reply_tx: oneshot::Sender>) { + // TODO Rename to on_state + async fn on_status(&self, reply_tx: oneshot::Sender>) { let status = if let Some(conn_tx) = &self.conn_tx { - conn_tx.status().await.ok() + conn_tx.state().await.ok() } else { None }; @@ -506,7 +505,8 @@ impl Room { self.event_tx.is_closed() } - pub async fn status(&self) -> Result, Error> { + // TODO Rename to state + pub async fn status(&self) -> Result, Error> { let (tx, rx) = oneshot::channel(); self.event_tx .send(Event::Status(tx)) diff --git a/src/euph/util.rs b/src/euph/util.rs index 05eb467..cf05c76 100644 --- a/src/euph/util.rs +++ b/src/euph/util.rs @@ -33,7 +33,8 @@ fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (u8, u8, u8) { } pub fn nick_color(nick: &str) -> (u8, u8, u8) { - let hue = euphoxide::nick_hue(nick) as f32; + // TODO Use nick hue with emoji (lazy init emoji?) + let hue = euphoxide::nick_hue_without_removing_emoji(nick) as f32; hsl_to_rgb(hue, 1.0, 0.72) } diff --git a/src/ui/euph/account.rs b/src/ui/euph/account.rs index 0a22479..6fee35e 100644 --- a/src/ui/euph/account.rs +++ b/src/ui/euph/account.rs @@ -1,6 +1,6 @@ use crossterm::style::{ContentStyle, Stylize}; use euphoxide::api::PersonalAccountView; -use euphoxide::conn::Status; +use euphoxide::conn::State as ConnState; use toss::terminal::Terminal; use crate::euph::Room; @@ -98,7 +98,7 @@ impl AccountUiState { /// Returns `false` if the account UI should not be displayed any longer. pub fn stabilize(&mut self, status: &RoomStatus) -> bool { - if let RoomStatus::Connected(Status::Joined(status)) = status { + if let RoomStatus::Connected(ConnState::Joined(status)) = status { match (&self, &status.account) { (Self::LoggedOut(_), Some(view)) => *self = Self::LoggedIn(LoggedIn(view.clone())), (Self::LoggedIn(_), None) => *self = Self::LoggedOut(LoggedOut::new()), diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index c8e1b52..882fcd1 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use crossterm::style::{ContentStyle, Stylize}; use euphoxide::api::{Data, Message, MessageId, PacketType, SessionId}; -use euphoxide::conn::{Joined, Joining, SessionInfo, Status}; +use euphoxide::conn::{Joined, Joining, SessionInfo, State as ConnState}; use parking_lot::FairMutex; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::{mpsc, oneshot}; @@ -53,7 +53,7 @@ pub enum RoomStatus { NoRoom, Stopped, Connecting, - Connected(Status), + Connected(ConnState), } impl RoomStatus { @@ -192,7 +192,7 @@ impl EuphRoom { fn stabilize_focus(&mut self, status: &RoomStatus) { match status { - RoomStatus::Connected(Status::Joined(_)) => {} + RoomStatus::Connected(ConnState::Joined(_)) => {} _ => self.focus = Focus::Chat, // There is no nick list to focus on } } @@ -202,7 +202,7 @@ impl EuphRoom { State::Auth(_) if !matches!( status, - RoomStatus::Connected(Status::Joining(Joining { + RoomStatus::Connected(ConnState::Joining(Joining { bounce: Some(_), .. })) @@ -210,7 +210,7 @@ impl EuphRoom { { self.state = State::Normal } - State::Nick(_) if !matches!(status, RoomStatus::Connected(Status::Joined(_))) => { + State::Nick(_) if !matches!(status, RoomStatus::Connected(ConnState::Joined(_))) => { self.state = State::Normal } State::Account(account) => { @@ -232,7 +232,7 @@ impl EuphRoom { let status = self.status().await; self.stabilize(&status).await; - let chat = if let RoomStatus::Connected(Status::Joined(joined)) = &status { + let chat = if let RoomStatus::Connected(ConnState::Joined(joined)) = &status { self.widget_with_nick_list(&status, joined).await } else { self.widget_without_nick_list(&status).await @@ -300,11 +300,11 @@ impl EuphRoom { info = match status { RoomStatus::NoRoom | RoomStatus::Stopped => info.then_plain(", archive"), RoomStatus::Connecting => info.then_plain(", connecting..."), - RoomStatus::Connected(Status::Joining(j)) if j.bounce.is_some() => { + RoomStatus::Connected(ConnState::Joining(j)) if j.bounce.is_some() => { info.then_plain(", auth required") } - RoomStatus::Connected(Status::Joining(_)) => info.then_plain(", joining..."), - RoomStatus::Connected(Status::Joined(j)) => { + RoomStatus::Connected(ConnState::Joining(_)) => info.then_plain(", joining..."), + RoomStatus::Connected(ConnState::Joined(j)) => { let nick = &j.session.name; if nick.is_empty() { info.then_plain(", present without nick") @@ -327,7 +327,7 @@ impl EuphRoom { } async fn list_chat_key_bindings(&self, bindings: &mut KeyBindingsList, status: &RoomStatus) { - let can_compose = matches!(status, RoomStatus::Connected(Status::Joined(_))); + let can_compose = matches!(status, RoomStatus::Connected(ConnState::Joined(_))); self.chat.list_key_bindings(bindings, can_compose).await; } @@ -338,7 +338,7 @@ impl EuphRoom { event: &InputEvent, status: &RoomStatus, ) -> bool { - let can_compose = matches!(status, RoomStatus::Connected(Status::Joined(_))); + let can_compose = matches!(status, RoomStatus::Connected(ConnState::Joined(_))); match self .chat @@ -371,14 +371,14 @@ impl EuphRoom { fn list_room_key_bindings(&self, bindings: &mut KeyBindingsList, status: &RoomStatus) { match status { // Authenticating - RoomStatus::Connected(Status::Joining(Joining { + RoomStatus::Connected(ConnState::Joining(Joining { bounce: Some(_), .. })) => { bindings.binding("a", "authenticate"); } // Connected - RoomStatus::Connected(Status::Joined(_)) => { + RoomStatus::Connected(ConnState::Joined(_)) => { bindings.binding("n", "change nick"); bindings.binding("m", "download more messages"); bindings.binding("A", "show account ui"); @@ -397,7 +397,7 @@ impl EuphRoom { async fn handle_room_input_event(&mut self, event: &InputEvent, status: &RoomStatus) -> bool { match status { // Authenticating - RoomStatus::Connected(Status::Joining(Joining { + RoomStatus::Connected(ConnState::Joining(Joining { bounce: Some(_), .. })) => { if let key!('a') = event { @@ -407,7 +407,7 @@ impl EuphRoom { } // Joined - RoomStatus::Connected(Status::Joined(joined)) => match event { + RoomStatus::Connected(ConnState::Joined(joined)) => match event { key!('n') | key!('N') => { self.state = State::Nick(nick::new(joined.clone())); return true; @@ -512,7 +512,7 @@ impl EuphRoom { } if let key!('i') = event { - if let RoomStatus::Connected(Status::Joined(joined)) = status { + if let RoomStatus::Connected(ConnState::Joined(joined)) = status { if let Some(id) = self.nick_list.cursor() { if id == joined.session.session_id { self.state = @@ -536,7 +536,7 @@ impl EuphRoom { match self.focus { Focus::Chat => { - if let RoomStatus::Connected(Status::Joined(_)) = status { + if let RoomStatus::Connected(ConnState::Joined(_)) = status { bindings.binding("tab", "focus on nick list"); } @@ -570,7 +570,7 @@ impl EuphRoom { return true; } - if let RoomStatus::Connected(Status::Joined(_)) = status { + if let RoomStatus::Connected(ConnState::Joined(_)) = status { if let key!(Tab) = event { self.focus = Focus::NickList; return true; diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index f79cb1e..c0261be 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use crossterm::style::{ContentStyle, Stylize}; use euphoxide::api::SessionType; -use euphoxide::conn::{Joined, Status}; +use euphoxide::conn::{Joined, State as ConnState}; use parking_lot::FairMutex; use tokio::sync::mpsc; use toss::styled::Styled; @@ -240,11 +240,11 @@ impl Rooms { match status { RoomStatus::NoRoom | RoomStatus::Stopped => None, RoomStatus::Connecting => Some("connecting".to_string()), - RoomStatus::Connected(Status::Joining(j)) if j.bounce.is_some() => { + RoomStatus::Connected(ConnState::Joining(j)) if j.bounce.is_some() => { Some("auth required".to_string()) } - RoomStatus::Connected(Status::Joining(_)) => Some("joining".to_string()), - RoomStatus::Connected(Status::Joined(joined)) => Some(Self::format_pbln(&joined)), + RoomStatus::Connected(ConnState::Joining(_)) => Some("joining".to_string()), + RoomStatus::Connected(ConnState::Joined(joined)) => Some(Self::format_pbln(&joined)), } } From 9f7c1fb9c0657a14a627bdca63221be73a41c190 Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 20 Jan 2023 19:42:54 +0100 Subject: [PATCH 010/266] Respect emoji when calculating nick hue --- CHANGELOG.md | 3 +++ Cargo.lock | 1 + Cargo.toml | 1 + src/euph/util.rs | 7 +++++-- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c039516..fae8b58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ Procedure when bumping the version number: ## Unreleased +### Changed +- Respect colon-delimited emoji when calculating nick hue + ## v0.5.2 - 2023-01-14 ### Added diff --git a/Cargo.lock b/Cargo.lock index 3d86a8a..650c4f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,6 +179,7 @@ dependencies = [ "euphoxide", "linkify", "log", + "once_cell", "open", "parking_lot", "rusqlite", diff --git a/Cargo.toml b/Cargo.toml index 7394fa2..bf0469b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ directories = "4.0.1" edit = "0.1.4" linkify = "0.9.0" log = { version = "0.4.17", features = ["std"] } +once_cell = "1.17.0" open = "3.2.0" parking_lot = "0.12.1" rusqlite = { version = "0.28.0", features = ["bundled", "time"] } diff --git a/src/euph/util.rs b/src/euph/util.rs index cf05c76..3285a07 100644 --- a/src/euph/util.rs +++ b/src/euph/util.rs @@ -1,4 +1,8 @@ use crossterm::style::{Color, ContentStyle, Stylize}; +use euphoxide::emoji::Emoji; +use once_cell::sync::Lazy; + +static EMOJI: Lazy = Lazy::new(Emoji::load); /// Convert HSL to RGB following [this approach from wikipedia][1]. /// @@ -33,8 +37,7 @@ fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (u8, u8, u8) { } pub fn nick_color(nick: &str) -> (u8, u8, u8) { - // TODO Use nick hue with emoji (lazy init emoji?) - let hue = euphoxide::nick_hue_without_removing_emoji(nick) as f32; + let hue = euphoxide::nick_hue(&EMOJI, nick) as f32; hsl_to_rgb(hue, 1.0, 0.72) } From 16011a267dc025f2bc275fb5dde7ef86078405d1 Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 20 Jan 2023 20:19:03 +0100 Subject: [PATCH 011/266] Display colon-delimited emoji in nicks --- CHANGELOG.md | 1 + src/euph/small_message.rs | 4 ++-- src/euph/util.rs | 15 ++++++++++++--- src/ui/euph/nick.rs | 4 ++-- src/ui/euph/nick_list.rs | 9 ++++++--- src/ui/euph/room.rs | 4 ++-- 6 files changed, 25 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fae8b58..c1be0e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Procedure when bumping the version number: ### Changed - Respect colon-delimited emoji when calculating nick hue +- Display colon-delimited emoji in nicks properly ## v0.5.2 - 2023-01-14 diff --git a/src/euph/small_message.rs b/src/euph/small_message.rs index f2e8901..4784c60 100644 --- a/src/euph/small_message.rs +++ b/src/euph/small_message.rs @@ -107,13 +107,13 @@ fn style_me() -> ContentStyle { fn styled_nick(nick: &str) -> Styled { Styled::new_plain("[") - .then(nick, util::nick_style(nick)) + .and_then(util::style_nick(nick, ContentStyle::default())) .then_plain("]") } fn styled_nick_me(nick: &str) -> Styled { let style = style_me(); - Styled::new("*", style).then(nick, util::nick_style(nick).italic()) + Styled::new("*", style).and_then(util::style_nick(nick, style)) } fn styled_content(content: &str) -> Styled { diff --git a/src/euph/util.rs b/src/euph/util.rs index 3285a07..cd2a443 100644 --- a/src/euph/util.rs +++ b/src/euph/util.rs @@ -1,8 +1,9 @@ use crossterm::style::{Color, ContentStyle, Stylize}; use euphoxide::emoji::Emoji; use once_cell::sync::Lazy; +use toss::styled::Styled; -static EMOJI: Lazy = Lazy::new(Emoji::load); +pub static EMOJI: Lazy = Lazy::new(Emoji::load); /// Convert HSL to RGB following [this approach from wikipedia][1]. /// @@ -41,7 +42,15 @@ pub fn nick_color(nick: &str) -> (u8, u8, u8) { hsl_to_rgb(hue, 1.0, 0.72) } -pub fn nick_style(nick: &str) -> ContentStyle { +pub fn nick_style(nick: &str, base: ContentStyle) -> ContentStyle { let (r, g, b) = nick_color(nick); - ContentStyle::default().bold().with(Color::Rgb { r, g, b }) + base.bold().with(Color::Rgb { r, g, b }) +} + +pub fn style_nick(nick: &str, base: ContentStyle) -> Styled { + Styled::new(EMOJI.replace(nick), nick_style(nick, base)) +} + +pub fn style_nick_exact(nick: &str, base: ContentStyle) -> Styled { + Styled::new(nick, nick_style(nick, base)) } diff --git a/src/ui/euph/nick.rs b/src/ui/euph/nick.rs index 9237c57..e520fef 100644 --- a/src/ui/euph/nick.rs +++ b/src/ui/euph/nick.rs @@ -1,5 +1,5 @@ +use crossterm::style::ContentStyle; use euphoxide::conn::Joined; -use toss::styled::Styled; use toss::terminal::Terminal; use crate::euph::{self, Room}; @@ -17,7 +17,7 @@ pub fn new(joined: Joined) -> EditorState { pub fn widget(editor: &EditorState) -> BoxedWidget { let editor = editor .widget() - .highlight(|s| Styled::new(s, euph::nick_style(s))); + .highlight(|s| euph::style_nick_exact(s, ContentStyle::default())); Popup::new(Padding::new(editor).left(1)) .title("Choose nick") .inner_padding(false) diff --git a/src/ui/euph/nick_list.rs b/src/ui/euph/nick_list.rs index 431edb3..926ca68 100644 --- a/src/ui/euph/nick_list.rs +++ b/src/ui/euph/nick_list.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::iter; use crossterm::style::{Color, ContentStyle, Stylize}; @@ -118,7 +119,7 @@ fn render_row(list: &mut List, session: &HalfSession, own_session: &S let name = "lurk"; let style = ContentStyle::default().grey(); let style_inv = ContentStyle::default().black().on_grey(); - (name, style, style_inv, style_inv) + (Cow::Borrowed(name), style, style_inv, style_inv) } else { let name = &session.name as &str; let (r, g, b) = euph::nick_color(name); @@ -126,7 +127,7 @@ fn render_row(list: &mut List, session: &HalfSession, own_session: &S let style = ContentStyle::default().bold().with(color); let style_inv = ContentStyle::default().bold().black().on(color); let perms_style_inv = ContentStyle::default().black().on(color); - (name, style, style_inv, perms_style_inv) + (euph::EMOJI.replace(name), style, style_inv, perms_style_inv) }; let perms = if session.is_staff { @@ -145,7 +146,9 @@ fn render_row(list: &mut List, session: &HalfSession, own_session: &S " " }; - let normal = Styled::new_plain(owner).then(name, style).then_plain(perms); + let normal = Styled::new_plain(owner) + .then(&name, style) + .then_plain(perms); let selected = Styled::new_plain(owner) .then(name, style_inv) .then(perms, perms_style_inv); diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 882fcd1..8fefda0 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -309,8 +309,8 @@ impl EuphRoom { if nick.is_empty() { info.then_plain(", present without nick") } else { - let nick_style = euph::nick_style(nick); - info.then_plain(", present as ").then(nick, nick_style) + info.then_plain(", present as ") + .and_then(euph::style_nick(nick, ContentStyle::default())) } } }; From c38b8c2ee20494ad882c823ab5f953be79b1fb73 Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 20 Jan 2023 21:29:21 +0100 Subject: [PATCH 012/266] Display colon-delimited emoji in messages --- CHANGELOG.md | 2 +- src/euph/small_message.rs | 226 ++++++++++++++++++++++++++++---------- 2 files changed, 171 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1be0e9..7b163ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Procedure when bumping the version number: ### Changed - Respect colon-delimited emoji when calculating nick hue -- Display colon-delimited emoji in nicks properly +- Display colon-delimited emoji in nicks and messages ## v0.5.2 - 2023-01-14 diff --git a/src/euph/small_message.rs b/src/euph/small_message.rs index 4784c60..b0615ff 100644 --- a/src/euph/small_message.rs +++ b/src/euph/small_message.rs @@ -1,4 +1,6 @@ -use crossterm::style::{Color, ContentStyle, Stylize}; +use std::mem; + +use crossterm::style::{ContentStyle, Stylize}; use euphoxide::api::{MessageId, Snowflake, Time}; use time::OffsetDateTime; use toss::styled::Styled; @@ -17,74 +19,186 @@ fn nick_char(ch: char) -> bool { } } -fn nick_char_(ch: Option<&char>) -> bool { - ch.filter(|c| nick_char(**c)).is_some() -} - fn room_char(ch: char) -> bool { // Basically just \w, see also // https://github.com/euphoria-io/heim/blob/978c921063e6b06012fc8d16d9fbf1b3a0be1191/client/lib/ui/MessageText.js#L66 ch.is_ascii_alphanumeric() || ch == '_' } -fn room_char_(ch: Option<&char>) -> bool { - ch.filter(|c| room_char(**c)).is_some() +enum Span { + Nothing, + Mention, + Room, + Emoji, } -// TODO Allocate less? -fn highlight_content(content: &str, base_style: ContentStyle) -> Styled { - let mut result = Styled::default(); - let mut current = String::new(); - let mut chars = content.chars().peekable(); - let mut possible_room_or_mention = true; +struct Highlighter<'a> { + content: &'a str, + base_style: ContentStyle, + exact: bool, - while let Some(char) = chars.next() { - match char { - '@' if possible_room_or_mention && nick_char_(chars.peek()) => { - result = result.then(¤t, base_style); - current.clear(); + span: Span, + span_start: usize, + room_or_mention_possible: bool, - let mut nick = String::new(); - while let Some(ch) = chars.peek() { - if nick_char(*ch) { - nick.push(*ch); - } else { - break; - } - chars.next(); - } + result: Styled, +} - let (r, g, b) = util::nick_color(&nick); - let style = base_style.with(Color::Rgb { r, g, b }).bold(); - result = result.then("@", style).then(nick, style); - } - '&' if possible_room_or_mention && room_char_(chars.peek()) => { - result = result.then(¤t, base_style); - current.clear(); - - let mut room = "&".to_string(); - while let Some(ch) = chars.peek() { - if room_char(*ch) { - room.push(*ch); - } else { - break; - } - chars.next(); - } - - let style = base_style.blue().bold(); - result = result.then(room, style); - } - _ => current.push(char), +impl<'a> Highlighter<'a> { + /// Does *not* guarantee `self.span_start == idx` after running! + fn close_mention(&mut self, idx: usize) { + let span_length = idx.saturating_sub(self.span_start); + if span_length <= 1 { + // We can repurpose the current span + self.span = Span::Nothing; + return; } - // More permissive than the heim web client - possible_room_or_mention = !char.is_alphanumeric(); + let text = &self.content[self.span_start..idx]; // Includes @ + self.result = mem::take(&mut self.result).and_then(if self.exact { + util::style_nick_exact(text, self.base_style) + } else { + util::style_nick(text, self.base_style) + }); + + self.span = Span::Nothing; + self.span_start = idx; } - result = result.then(current, base_style); + /// Does *not* guarantee `self.span_start == idx` after running! + fn close_room(&mut self, idx: usize) { + let span_length = idx.saturating_sub(self.span_start); + if span_length <= 1 { + // We can repurpose the current span + self.span = Span::Nothing; + return; + } - result + self.result = mem::take(&mut self.result).then( + &self.content[self.span_start..idx], + self.base_style.blue().bold(), + ); + + self.span = Span::Nothing; + self.span_start = idx; + } + + // Warning: `idx` is the index of the closing colon. + fn close_emoji(&mut self, idx: usize) { + let name = &self.content[self.span_start + 1..idx]; + if let Some(replace) = util::EMOJI.get(name) { + match replace { + Some(replace) if !self.exact => { + let style = self.base_style.on_dark_magenta(); + self.result = mem::take(&mut self.result).then(replace, style); + } + _ => { + let text = &self.content[self.span_start..=idx]; + let style = self.base_style.magenta(); + self.result = mem::take(&mut self.result).then(text, style); + } + } + + self.span = Span::Nothing; + self.span_start = idx + 1; + } else { + self.close_plain(idx); + self.span = Span::Emoji; + } + } + + /// Guarantees `self.span_start == idx` after running. + fn close_plain(&mut self, idx: usize) { + if self.span_start == idx { + // Span has length 0 + return; + } + + self.result = mem::take(&mut self.result).then_plain(&self.content[self.span_start..idx]); + + self.span = Span::Nothing; + self.span_start = idx; + } + + fn close_span_before_current_char(&mut self, idx: usize, char: char) { + match self.span { + Span::Mention if !nick_char(char) => self.close_mention(idx), + Span::Room if !room_char(char) => self.close_room(idx), + Span::Emoji if char == '&' || char == '@' => { + self.span = Span::Nothing; + } + _ => {} + } + } + + fn update_span_with_current_char(&mut self, idx: usize, char: char) { + match self.span { + Span::Nothing if char == '@' && self.room_or_mention_possible => { + self.close_plain(idx); + self.span = Span::Mention; + } + Span::Nothing if char == '&' && self.room_or_mention_possible => { + self.close_plain(idx); + self.span = Span::Room; + } + Span::Nothing if char == ':' => { + self.close_plain(idx); + self.span = Span::Emoji; + } + Span::Emoji if char == ':' => self.close_emoji(idx), + _ => {} + } + } + + fn close_final_span(&mut self) { + let idx = self.content.len(); + if self.span_start >= idx { + return; // Span has no contents + } + + match self.span { + Span::Mention => self.close_mention(idx), + Span::Room => self.close_room(idx), + _ => {} + } + + self.close_plain(idx); + } + + fn step(&mut self, idx: usize, char: char) { + if self.span_start < idx { + self.close_span_before_current_char(idx, char); + } + + self.update_span_with_current_char(idx, char); + + // More permissive than the heim web client + self.room_or_mention_possible = !char.is_alphanumeric(); + } + + fn highlight(content: &'a str, base_style: ContentStyle, exact: bool) -> Styled { + let mut this = Self { + content: if exact { content } else { content.trim() }, + base_style, + exact, + span: Span::Nothing, + span_start: 0, + room_or_mention_possible: true, + result: Styled::default(), + }; + + for (idx, char) in (if exact { content } else { content.trim() }).char_indices() { + this.step(idx, char); + } + + this.close_final_span(); + + this.result + } +} + +fn highlight_content(content: &str, base_style: ContentStyle, exact: bool) -> Styled { + Highlighter::highlight(content, base_style, exact) } #[derive(Debug, Clone)] @@ -117,12 +231,12 @@ fn styled_nick_me(nick: &str) -> Styled { } fn styled_content(content: &str) -> Styled { - highlight_content(content.trim(), ContentStyle::default()) + highlight_content(content.trim(), ContentStyle::default(), false) } fn styled_content_me(content: &str) -> Styled { let style = style_me(); - highlight_content(content.trim(), style).then("*", style) + highlight_content(content.trim(), style, false).then("*", style) } fn styled_editor_content(content: &str) -> Styled { @@ -131,7 +245,7 @@ fn styled_editor_content(content: &str) -> Styled { } else { ContentStyle::default() }; - highlight_content(content, style) + highlight_content(content, style, true) } impl Msg for SmallMessage { From f72da101719f40b2631c9bc0e94cc33ae9d10644 Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 20 Jan 2023 21:34:23 +0100 Subject: [PATCH 013/266] Don't set bg color on replaced emoji --- src/euph/small_message.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/euph/small_message.rs b/src/euph/small_message.rs index b0615ff..ece759f 100644 --- a/src/euph/small_message.rs +++ b/src/euph/small_message.rs @@ -89,8 +89,7 @@ impl<'a> Highlighter<'a> { if let Some(replace) = util::EMOJI.get(name) { match replace { Some(replace) if !self.exact => { - let style = self.base_style.on_dark_magenta(); - self.result = mem::take(&mut self.result).then(replace, style); + self.result = mem::take(&mut self.result).then(replace, self.base_style); } _ => { let text = &self.content[self.span_start..=idx]; From 23352e70275fd18c405748b11f3440df0ccda365 Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 20 Jan 2023 21:42:40 +0100 Subject: [PATCH 014/266] Rename "status" to "state" in most places This follows the name change of euphoxide, which renamed its connection Status to State. --- src/euph/room.rs | 16 +++--- src/ui/euph/account.rs | 8 +-- src/ui/euph/room.rs | 124 ++++++++++++++++++++--------------------- src/ui/rooms.rs | 32 +++++------ 4 files changed, 89 insertions(+), 91 deletions(-) diff --git a/src/euph/room.rs b/src/euph/room.rs index 39207a9..607b5d6 100644 --- a/src/euph/room.rs +++ b/src/euph/room.rs @@ -44,7 +44,7 @@ enum Event { Disconnected, Packet(Box), // Commands - Status(oneshot::Sender>), // TODO Rename to State + State(oneshot::Sender>), RequestLogs, Auth(String), Nick(String), @@ -236,7 +236,7 @@ impl State { self.on_packet(&packet).await?; let _ = euph_room_event_tx.send(EuphRoomEvent::Packet(packet)); } - Event::Status(reply_tx) => self.on_status(reply_tx).await, + Event::State(reply_tx) => self.on_state(reply_tx).await, Event::RequestLogs => self.on_request_logs(), Event::Auth(password) => self.on_auth(password), Event::Nick(name) => self.on_nick(name), @@ -339,15 +339,14 @@ impl State { Ok(()) } - // TODO Rename to on_state - async fn on_status(&self, reply_tx: oneshot::Sender>) { - let status = if let Some(conn_tx) = &self.conn_tx { + async fn on_state(&self, reply_tx: oneshot::Sender>) { + let state = if let Some(conn_tx) = &self.conn_tx { conn_tx.state().await.ok() } else { None }; - let _ = reply_tx.send(status); + let _ = reply_tx.send(state); } fn on_request_logs(&self) { @@ -505,11 +504,10 @@ impl Room { self.event_tx.is_closed() } - // TODO Rename to state - pub async fn status(&self) -> Result, Error> { + pub async fn state(&self) -> Result, Error> { let (tx, rx) = oneshot::channel(); self.event_tx - .send(Event::Status(tx)) + .send(Event::State(tx)) .map_err(|_| Error::Stopped)?; rx.await.map_err(|_| Error::Stopped) } diff --git a/src/ui/euph/account.rs b/src/ui/euph/account.rs index 6fee35e..32569f7 100644 --- a/src/ui/euph/account.rs +++ b/src/ui/euph/account.rs @@ -14,7 +14,7 @@ use crate::ui::widgets::resize::Resize; use crate::ui::widgets::text::Text; use crate::ui::widgets::BoxedWidget; -use super::room::RoomStatus; +use super::room::RoomState; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Focus { @@ -97,9 +97,9 @@ impl AccountUiState { } /// Returns `false` if the account UI should not be displayed any longer. - pub fn stabilize(&mut self, status: &RoomStatus) -> bool { - if let RoomStatus::Connected(ConnState::Joined(status)) = status { - match (&self, &status.account) { + pub fn stabilize(&mut self, state: &RoomState) -> bool { + if let RoomState::Connected(ConnState::Joined(state)) = state { + match (&self, &state.account) { (Self::LoggedOut(_), Some(view)) => *self = Self::LoggedIn(LoggedIn(view.clone())), (Self::LoggedIn(_), None) => *self = Self::LoggedOut(LoggedOut::new()), _ => {} diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 8fefda0..da95dfb 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -49,14 +49,14 @@ enum State { } #[allow(clippy::large_enum_variant)] -pub enum RoomStatus { +pub enum RoomState { NoRoom, Stopped, Connecting, Connected(ConnState), } -impl RoomStatus { +impl RoomState { pub fn connecting_or_connected(&self) -> bool { match self { Self::NoRoom | Self::Stopped => false, @@ -147,14 +147,14 @@ impl EuphRoom { self.room = None; } - pub async fn status(&self) -> RoomStatus { + pub async fn state(&self) -> RoomState { match &self.room { - Some(room) => match room.status().await { - Ok(Some(status)) => RoomStatus::Connected(status), - Ok(None) => RoomStatus::Connecting, - Err(_) => RoomStatus::Stopped, + Some(room) => match room.state().await { + Ok(Some(state)) => RoomState::Connected(state), + Ok(None) => RoomState::Connecting, + Err(_) => RoomState::Stopped, }, - None => RoomStatus::NoRoom, + None => RoomState::NoRoom, } } @@ -190,19 +190,19 @@ impl EuphRoom { } } - fn stabilize_focus(&mut self, status: &RoomStatus) { - match status { - RoomStatus::Connected(ConnState::Joined(_)) => {} + fn stabilize_focus(&mut self, state: &RoomState) { + match state { + RoomState::Connected(ConnState::Joined(_)) => {} _ => self.focus = Focus::Chat, // There is no nick list to focus on } } - fn stabilize_state(&mut self, status: &RoomStatus) { + fn stabilize_state(&mut self, state: &RoomState) { match &mut self.state { State::Auth(_) if !matches!( - status, - RoomStatus::Connected(ConnState::Joining(Joining { + state, + RoomState::Connected(ConnState::Joining(Joining { bounce: Some(_), .. })) @@ -210,11 +210,11 @@ impl EuphRoom { { self.state = State::Normal } - State::Nick(_) if !matches!(status, RoomStatus::Connected(ConnState::Joined(_))) => { + State::Nick(_) if !matches!(state, RoomState::Connected(ConnState::Joined(_))) => { self.state = State::Normal } State::Account(account) => { - if !account.stabilize(status) { + if !account.stabilize(state) { self.state = State::Normal } } @@ -222,20 +222,20 @@ impl EuphRoom { } } - async fn stabilize(&mut self, status: &RoomStatus) { + async fn stabilize(&mut self, state: &RoomState) { self.stabilize_pseudo_msg().await; - self.stabilize_focus(status); - self.stabilize_state(status); + self.stabilize_focus(state); + self.stabilize_state(state); } pub async fn widget(&mut self) -> BoxedWidget { - let status = self.status().await; - self.stabilize(&status).await; + let state = self.state().await; + self.stabilize(&state).await; - let chat = if let RoomStatus::Connected(ConnState::Joined(joined)) = &status { - self.widget_with_nick_list(&status, joined).await + let chat = if let RoomState::Connected(ConnState::Joined(joined)) = &state { + self.widget_with_nick_list(&state, joined).await } else { - self.widget_without_nick_list(&status).await + self.widget_without_nick_list(&state).await }; let mut layers = vec![chat]; @@ -257,10 +257,10 @@ impl EuphRoom { Layer::new(layers).into() } - async fn widget_without_nick_list(&self, status: &RoomStatus) -> BoxedWidget { + async fn widget_without_nick_list(&self, state: &RoomState) -> BoxedWidget { VJoin::new(vec![ Segment::new(Border::new( - Padding::new(self.status_widget(status).await).horizontal(1), + Padding::new(self.status_widget(state).await).horizontal(1), )), // TODO Use last known nick? Segment::new(self.chat.widget(String::new(), true)).expanding(true), @@ -268,11 +268,11 @@ impl EuphRoom { .into() } - async fn widget_with_nick_list(&self, status: &RoomStatus, joined: &Joined) -> BoxedWidget { + async fn widget_with_nick_list(&self, state: &RoomState, joined: &Joined) -> BoxedWidget { HJoin::new(vec![ Segment::new(VJoin::new(vec![ Segment::new(Border::new( - Padding::new(self.status_widget(status).await).horizontal(1), + Padding::new(self.status_widget(state).await).horizontal(1), )), Segment::new( self.chat @@ -293,18 +293,18 @@ impl EuphRoom { .into() } - async fn status_widget(&self, status: &RoomStatus) -> BoxedWidget { + async fn status_widget(&self, state: &RoomState) -> BoxedWidget { let room_style = ContentStyle::default().bold().blue(); let mut info = Styled::new(format!("&{}", self.name()), room_style); - info = match status { - RoomStatus::NoRoom | RoomStatus::Stopped => info.then_plain(", archive"), - RoomStatus::Connecting => info.then_plain(", connecting..."), - RoomStatus::Connected(ConnState::Joining(j)) if j.bounce.is_some() => { + info = match state { + RoomState::NoRoom | RoomState::Stopped => info.then_plain(", archive"), + RoomState::Connecting => info.then_plain(", connecting..."), + RoomState::Connected(ConnState::Joining(j)) if j.bounce.is_some() => { info.then_plain(", auth required") } - RoomStatus::Connected(ConnState::Joining(_)) => info.then_plain(", joining..."), - RoomStatus::Connected(ConnState::Joined(j)) => { + RoomState::Connected(ConnState::Joining(_)) => info.then_plain(", joining..."), + RoomState::Connected(ConnState::Joined(j)) => { let nick = &j.session.name; if nick.is_empty() { info.then_plain(", present without nick") @@ -326,8 +326,8 @@ impl EuphRoom { Text::new(info).into() } - async fn list_chat_key_bindings(&self, bindings: &mut KeyBindingsList, status: &RoomStatus) { - let can_compose = matches!(status, RoomStatus::Connected(ConnState::Joined(_))); + async fn list_chat_key_bindings(&self, bindings: &mut KeyBindingsList, state: &RoomState) { + let can_compose = matches!(state, RoomState::Connected(ConnState::Joined(_))); self.chat.list_key_bindings(bindings, can_compose).await; } @@ -336,9 +336,9 @@ impl EuphRoom { terminal: &mut Terminal, crossterm_lock: &Arc>, event: &InputEvent, - status: &RoomStatus, + state: &RoomState, ) -> bool { - let can_compose = matches!(status, RoomStatus::Connected(ConnState::Joined(_))); + let can_compose = matches!(state, RoomState::Connected(ConnState::Joined(_))); match self .chat @@ -368,17 +368,17 @@ impl EuphRoom { false } - fn list_room_key_bindings(&self, bindings: &mut KeyBindingsList, status: &RoomStatus) { - match status { + fn list_room_key_bindings(&self, bindings: &mut KeyBindingsList, state: &RoomState) { + match state { // Authenticating - RoomStatus::Connected(ConnState::Joining(Joining { + RoomState::Connected(ConnState::Joining(Joining { bounce: Some(_), .. })) => { bindings.binding("a", "authenticate"); } // Connected - RoomStatus::Connected(ConnState::Joined(_)) => { + RoomState::Connected(ConnState::Joined(_)) => { bindings.binding("n", "change nick"); bindings.binding("m", "download more messages"); bindings.binding("A", "show account ui"); @@ -394,10 +394,10 @@ impl EuphRoom { bindings.binding("ctrl+p", "open room's plugh.de/present page"); } - async fn handle_room_input_event(&mut self, event: &InputEvent, status: &RoomStatus) -> bool { - match status { + async fn handle_room_input_event(&mut self, event: &InputEvent, state: &RoomState) -> bool { + match state { // Authenticating - RoomStatus::Connected(ConnState::Joining(Joining { + RoomState::Connected(ConnState::Joining(Joining { bounce: Some(_), .. })) => { if let key!('a') = event { @@ -407,7 +407,7 @@ impl EuphRoom { } // Joined - RoomStatus::Connected(ConnState::Joined(joined)) => match event { + RoomState::Connected(ConnState::Joined(joined)) => match event { key!('n') | key!('N') => { self.state = State::Nick(nick::new(joined.clone())); return true; @@ -466,11 +466,11 @@ impl EuphRoom { async fn list_chat_focus_key_bindings( &self, bindings: &mut KeyBindingsList, - status: &RoomStatus, + state: &RoomState, ) { - self.list_room_key_bindings(bindings, status); + self.list_room_key_bindings(bindings, state); bindings.empty(); - self.list_chat_key_bindings(bindings, status).await; + self.list_chat_key_bindings(bindings, state).await; } async fn handle_chat_focus_input_event( @@ -478,18 +478,18 @@ impl EuphRoom { terminal: &mut Terminal, crossterm_lock: &Arc>, event: &InputEvent, - status: &RoomStatus, + state: &RoomState, ) -> bool { // We need to handle chat input first, otherwise the other // key bindings will shadow characters in the editor. if self - .handle_chat_input_event(terminal, crossterm_lock, event, status) + .handle_chat_input_event(terminal, crossterm_lock, event, state) .await { return true; } - if self.handle_room_input_event(event, status).await { + if self.handle_room_input_event(event, state).await { return true; } @@ -505,14 +505,14 @@ impl EuphRoom { fn handle_nick_list_focus_input_event( &mut self, event: &InputEvent, - status: &RoomStatus, + state: &RoomState, ) -> bool { if util::handle_list_input_event(&mut self.nick_list, event) { return true; } if let key!('i') = event { - if let RoomStatus::Connected(ConnState::Joined(joined)) = status { + if let RoomState::Connected(ConnState::Joined(joined)) = state { if let Some(id) = self.nick_list.cursor() { if id == joined.session.session_id { self.state = @@ -532,15 +532,15 @@ impl EuphRoom { // Handled in rooms list, not here bindings.binding("esc", "leave room"); - let status = self.status().await; + let state = self.state().await; match self.focus { Focus::Chat => { - if let RoomStatus::Connected(ConnState::Joined(_)) = status { + if let RoomState::Connected(ConnState::Joined(_)) = state { bindings.binding("tab", "focus on nick list"); } - self.list_chat_focus_key_bindings(bindings, &status).await; + self.list_chat_focus_key_bindings(bindings, &state).await; } Focus::NickList => { bindings.binding("tab, esc", "focus on chat"); @@ -557,20 +557,20 @@ impl EuphRoom { crossterm_lock: &Arc>, event: &InputEvent, ) -> bool { - let status = self.status().await; + let state = self.state().await; match self.focus { Focus::Chat => { // Needs to be handled first or the tab key may be shadowed // during editing. if self - .handle_chat_focus_input_event(terminal, crossterm_lock, event, &status) + .handle_chat_focus_input_event(terminal, crossterm_lock, event, &state) .await { return true; } - if let RoomStatus::Connected(ConnState::Joined(_)) = status { + if let RoomState::Connected(ConnState::Joined(_)) = state { if let key!(Tab) = event { self.focus = Focus::NickList; return true; @@ -583,7 +583,7 @@ impl EuphRoom { return true; } - if self.handle_nick_list_focus_input_event(event, &status) { + if self.handle_nick_list_focus_input_event(event, &state) { return true; } } diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index c0261be..b622394 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -14,7 +14,7 @@ use crate::config::{Config, RoomsSortOrder}; use crate::euph::EuphRoomEvent; use crate::vault::Vault; -use super::euph::room::{EuphRoom, RoomStatus}; +use super::euph::room::{EuphRoom, RoomState}; use super::input::{key, InputEvent, KeyBindingsList}; use super::widgets::editor::EditorState; use super::widgets::join::{HJoin, Segment, VJoin}; @@ -236,15 +236,15 @@ impl Rooms { result.join(" ") } - fn format_status(status: RoomStatus) -> Option { - match status { - RoomStatus::NoRoom | RoomStatus::Stopped => None, - RoomStatus::Connecting => Some("connecting".to_string()), - RoomStatus::Connected(ConnState::Joining(j)) if j.bounce.is_some() => { + fn format_room_state(state: RoomState) -> Option { + match state { + RoomState::NoRoom | RoomState::Stopped => None, + RoomState::Connecting => Some("connecting".to_string()), + RoomState::Connected(ConnState::Joining(j)) if j.bounce.is_some() => { Some("auth required".to_string()) } - RoomStatus::Connected(ConnState::Joining(_)) => Some("joining".to_string()), - RoomStatus::Connected(ConnState::Joined(joined)) => Some(Self::format_pbln(&joined)), + RoomState::Connected(ConnState::Joining(_)) => Some("joining".to_string()), + RoomState::Connected(ConnState::Joined(joined)) => Some(Self::format_pbln(&joined)), } } @@ -256,13 +256,13 @@ impl Rooms { } } - fn format_room_info(status: RoomStatus, unseen: usize) -> Styled { + fn format_room_info(state: RoomState, unseen: usize) -> Styled { let unseen_style = ContentStyle::default().bold().green(); - let status = Self::format_status(status); + let state = Self::format_room_state(state); let unseen = Self::format_unseen_msgs(unseen); - match (status, unseen) { + match (state, unseen) { (None, None) => Styled::default(), (None, Some(u)) => Styled::new_plain(" (") .then(u, unseen_style) @@ -276,7 +276,7 @@ impl Rooms { } } - fn sort_rooms(&self, rooms: &mut [(&String, RoomStatus, usize)]) { + fn sort_rooms(&self, rooms: &mut [(&String, RoomState, usize)]) { match self.order { Order::Alphabet => rooms.sort_unstable_by_key(|(n, _, _)| *n), Order::Importance => { @@ -295,19 +295,19 @@ impl Rooms { let mut rooms = vec![]; for (name, room) in &self.euph_rooms { - let status = room.status().await; + let state = room.state().await; let unseen = room.unseen_msgs_count().await; - rooms.push((name, status, unseen)); + rooms.push((name, state, unseen)); } self.sort_rooms(&mut rooms); - for (name, status, unseen) in rooms { + for (name, state, unseen) in rooms { let room_style = ContentStyle::default().bold().blue(); let room_sel_style = ContentStyle::default().bold().black().on_white(); let mut normal = Styled::new(format!("&{name}"), room_style); let mut selected = Styled::new(format!("&{name}"), room_sel_style); - let info = Self::format_room_info(status, unseen); + let info = Self::format_room_info(state, unseen); normal = normal.and_then(info.clone()); selected = selected.and_then(info); From 875f8be181136e4926db7f410e356f389a4ea366 Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 20 Jan 2023 21:45:08 +0100 Subject: [PATCH 015/266] Simplify return type --- src/euph/room.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/euph/room.rs b/src/euph/room.rs index 607b5d6..f36036d 100644 --- a/src/euph/room.rs +++ b/src/euph/room.rs @@ -111,12 +111,11 @@ impl State { ) -> anyhow::Result<()> { loop { info!("e&{}: connecting", name); - let connected = if let Some((conn_tx, mut conn_rx)) = Self::connect(vault, name).await? - { + let connected = if let Some(mut conn) = Self::connect(vault, name).await? { info!("e&{}: connected", name); - event_tx.send(Event::Connected(conn_tx))?; + event_tx.send(Event::Connected(conn.tx().clone()))?; - while let Ok(packet) = conn_rx.recv().await { + while let Ok(packet) = conn.recv().await { event_tx.send(Event::Packet(Box::new(packet)))?; } @@ -161,8 +160,7 @@ impl State { vault.set_cookies(cookie_jar); } - // TODO Simplify return type, remove ConnTx - async fn connect(vault: &EuphRoomVault, name: &str) -> anyhow::Result> { + async fn connect(vault: &EuphRoomVault, name: &str) -> anyhow::Result> { // TODO Set user agent? let cookies = Self::get_cookies(vault.vault()).await; @@ -171,7 +169,7 @@ impl State { match Conn::connect("euphoria.io", name, true, Some(cookies), TIMEOUT).await { Ok((rx, set_cookies)) => { Self::update_cookies(vault.vault(), &set_cookies); - Ok(Some((rx.tx().clone(), rx))) + Ok(Some(rx)) } Err(tungstenite::Error::Http(resp)) if resp.status().is_client_error() => { bail!("room {name} doesn't exist"); From 0ff3e94690808ca1ac1dbc7a4cab1be81f3a7b8f Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 21 Jan 2023 14:24:03 +0100 Subject: [PATCH 016/266] Fix rendering of /me --- src/euph/small_message.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/euph/small_message.rs b/src/euph/small_message.rs index ece759f..1aec5f8 100644 --- a/src/euph/small_message.rs +++ b/src/euph/small_message.rs @@ -113,7 +113,8 @@ impl<'a> Highlighter<'a> { return; } - self.result = mem::take(&mut self.result).then_plain(&self.content[self.span_start..idx]); + self.result = + mem::take(&mut self.result).then(&self.content[self.span_start..idx], self.base_style); self.span = Span::Nothing; self.span_start = idx; From b94dfbdc3188c0e935fd2af035b1a27efa88315a Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 23 Jan 2023 18:58:46 +0100 Subject: [PATCH 017/266] Update euphoxide and enable feature "bot" --- Cargo.lock | 4 +++- Cargo.toml | 3 ++- src/euph/util.rs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 650c4f3..e73fda6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,9 +309,11 @@ dependencies = [ [[package]] name = "euphoxide" version = "0.2.0" -source = "git+https://github.com/Garmelon/euphoxide.git?rev=c15c05a64e82c7c5f7af0539745855c0f6821869#c15c05a64e82c7c5f7af0539745855c0f6821869" +source = "git+https://github.com/Garmelon/euphoxide.git?rev=63464fdc59f24774cb96b7a8038326a9dc7d587a#63464fdc59f24774cb96b7a8038326a9dc7d587a" dependencies = [ + "cookie", "futures-util", + "log", "serde", "serde_json", "time", diff --git a/Cargo.toml b/Cargo.toml index bf0469b..93ffc6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,8 @@ features = ["rustls-tls-native-roots"] [dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -rev = "c15c05a64e82c7c5f7af0539745855c0f6821869" +rev = "63464fdc59f24774cb96b7a8038326a9dc7d587a" +features = ["bot"] # [patch."https://github.com/Garmelon/euphoxide.git"] # euphoxide = { path = "../euphoxide/" } diff --git a/src/euph/util.rs b/src/euph/util.rs index cd2a443..77212ae 100644 --- a/src/euph/util.rs +++ b/src/euph/util.rs @@ -1,5 +1,5 @@ use crossterm::style::{Color, ContentStyle, Stylize}; -use euphoxide::emoji::Emoji; +use euphoxide::Emoji; use once_cell::sync::Lazy; use toss::styled::Styled; From 8dd5db5888a75f54e818985fd8255b030a3e02d0 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 23 Jan 2023 22:35:00 +0100 Subject: [PATCH 018/266] Switch euph::Room to use euphoxide's Instance --- src/euph/room.rs | 669 ++++++++++++++--------------------------- src/ui.rs | 15 +- src/ui/euph/account.rs | 10 +- src/ui/euph/room.rs | 285 +++++++++--------- src/ui/rooms.rs | 69 +++-- 5 files changed, 408 insertions(+), 640 deletions(-) diff --git a/src/euph/room.rs b/src/euph/room.rs index f36036d..41bd7ec 100644 --- a/src/euph/room.rs +++ b/src/euph/room.rs @@ -1,187 +1,143 @@ use std::convert::Infallible; -use std::str::FromStr; -use std::sync::Arc; use std::time::Duration; -use anyhow::bail; -use cookie::{Cookie, CookieJar}; use euphoxide::api::packet::ParsedPacket; use euphoxide::api::{ - Auth, AuthOption, Data, Log, Login, Logout, MessageId, Nick, Send, Time, UserId, + Auth, AuthOption, Data, Log, Login, Logout, MessageId, Nick, Send, SendEvent, SendReply, Time, + UserId, }; -use euphoxide::conn::{Conn, ConnTx, Joining, State as ConnState}; -use log::{error, info, warn}; -use parking_lot::Mutex; -use tokio::sync::{mpsc, oneshot}; -use tokio::{select, task}; -use tokio_tungstenite::tungstenite; -use tokio_tungstenite::tungstenite::http::HeaderValue; +use euphoxide::bot::instance::{Event, Instance, InstanceConfig, Snapshot}; +use euphoxide::conn::{self, ConnTx}; +use log::{debug, error, info, warn}; +use tokio::select; +use tokio::sync::oneshot; use crate::macros::ok_or_return; -use crate::vault::{EuphRoomVault, EuphVault}; +use crate::vault::EuphRoomVault; -const TIMEOUT: Duration = Duration::from_secs(30); -const RECONNECT_INTERVAL: Duration = Duration::from_secs(60); const LOG_INTERVAL: Duration = Duration::from_secs(10); -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("room stopped")] - Stopped, -} - -pub enum EuphRoomEvent { - Connected, - Disconnected, - Packet(Box), - Stopped, -} - #[derive(Debug)] -enum Event { - // Events - Connected(ConnTx), +pub enum State { Disconnected, - Packet(Box), - // Commands - State(oneshot::Sender>), - RequestLogs, - Auth(String), - Nick(String), - Send(Option, String, oneshot::Sender), - Login { email: String, password: String }, - Logout, -} - -#[derive(Debug)] -struct State { - name: String, - username: Option, - force_username: bool, - password: Option, - vault: EuphRoomVault, - - conn_tx: Option, - /// `None` before any `snapshot-event`, then either `Some(None)` or - /// `Some(Some(id))`. - last_msg_id: Option>, - requesting_logs: Arc>, + Connecting, + Connected(ConnTx, conn::State), + Stopped, } impl State { - async fn run( - mut self, - canary: oneshot::Receiver, - event_tx: mpsc::UnboundedSender, - mut event_rx: mpsc::UnboundedReceiver, - euph_room_event_tx: mpsc::UnboundedSender, - ephemeral: bool, - ) { - let vault = self.vault.clone(); - let name = self.name.clone(); - let result = if ephemeral { - select! { - _ = canary => Ok(()), - _ = Self::reconnect(&vault, &name, &event_tx) => Ok(()), - e = self.handle_events(&mut event_rx, &euph_room_event_tx) => e, - } + pub fn conn_tx(&self) -> Option<&ConnTx> { + if let Self::Connected(conn_tx, _) = self { + Some(conn_tx) } else { - select! { - _ = canary => Ok(()), - _ = Self::reconnect(&vault, &name, &event_tx) => Ok(()), - e = self.handle_events(&mut event_rx, &euph_room_event_tx) => e, - _ = Self::regularly_request_logs(&event_tx) => Ok(()), - } - }; - - if let Err(e) = result { - error!("e&{name}: {}", e); + None } + } +} - // Ensure that whoever is using this room knows that it's gone. - // Otherwise, the users of the Room may be left in an inconsistent or - // outdated state, and the UI may not update correctly. - let _ = euph_room_event_tx.send(EuphRoomEvent::Stopped); +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("not connected to room")] + NotConnected, +} + +#[derive(Debug)] +pub struct Room { + vault: EuphRoomVault, + ephemeral: bool, + + instance: Instance, + state: State, + + /// `None` before any `snapshot-event`, then either `Some(None)` or + /// `Some(Some(id))`. Reset whenever connection is lost. + last_msg_id: Option>, + + /// `Some` while `Self::regularly_request_logs` is running. Set to `None` to + /// drop the sender and stop the task. + log_request_canary: Option>, +} + +impl Room { + pub fn new(vault: EuphRoomVault, instance_config: InstanceConfig, on_event: F) -> Self + where + F: Fn(Event) + std::marker::Send + Sync + 'static, + { + // &rl2dev's message history is broken and requesting old messages past + // a certain point results in errors. Cove should not keep retrying log + // requests when hitting that limit, so &rl2dev is always opened in + // ephemeral mode. + let ephemeral = vault.vault().vault().ephemeral() || vault.room() == "rl2dev"; + + Self { + vault, + ephemeral, + instance: instance_config.build(on_event), + state: State::Disconnected, + last_msg_id: None, + log_request_canary: None, + } } - async fn reconnect( - vault: &EuphRoomVault, - name: &str, - event_tx: &mpsc::UnboundedSender, - ) -> anyhow::Result<()> { - loop { - info!("e&{}: connecting", name); - let connected = if let Some(mut conn) = Self::connect(vault, name).await? { - info!("e&{}: connected", name); - event_tx.send(Event::Connected(conn.tx().clone()))?; + pub fn stopped(&self) -> bool { + self.instance.stopped() + } - while let Ok(packet) = conn.recv().await { - event_tx.send(Event::Packet(Box::new(packet)))?; + pub fn state(&self) -> &State { + &self.state + } + + fn conn_tx(&self) -> Result<&ConnTx, Error> { + self.state.conn_tx().ok_or(Error::NotConnected) + } + + pub fn handle_event(&mut self, event: Event) { + match event { + Event::Connecting(_) => { + self.state = State::Connecting; + + // Juuust to make sure + self.last_msg_id = None; + self.log_request_canary = None; + } + Event::Connected(_, Snapshot { conn_tx, state }) => { + if !self.ephemeral { + let (tx, rx) = oneshot::channel(); + self.log_request_canary = Some(tx); + let vault_clone = self.vault.clone(); + let conn_tx_clone = conn_tx.clone(); + debug!("{}: spawning log request task", self.instance.config().name); + tokio::task::spawn(async move { + select! { + _ = rx => {}, + _ = Self::regularly_request_logs(vault_clone, conn_tx_clone) => {}, + } + }); } - info!("e&{}: disconnected", name); - event_tx.send(Event::Disconnected)?; + self.state = State::Connected(conn_tx, state); - true - } else { - info!("e&{}: could not connect", name); - event_tx.send(Event::Disconnected)?; - false - }; - - // Only delay reconnecting if the previous attempt failed. This way, - // we'll reconnect immediately if we login or logout. - if !connected { - tokio::time::sleep(RECONNECT_INTERVAL).await; + let cookies = &*self.instance.config().server.cookies; + let cookies = cookies.lock().unwrap().clone(); + self.vault.vault().set_cookies(cookies); + } + Event::Packet(_, packet, Snapshot { conn_tx, state }) => { + self.state = State::Connected(conn_tx, state); + self.on_packet(packet); + } + Event::Disconnected(_) => { + self.state = State::Disconnected; + self.last_msg_id = None; + self.log_request_canary = None; + } + Event::Stopped(_) => { + // TODO Remove room somewhere if this happens? If it doesn't already happen during stabilization + self.state = State::Stopped; } } } - async fn get_cookies(vault: &EuphVault) -> String { - let cookie_jar = vault.cookies().await; - let cookies = cookie_jar - .iter() - .map(|c| format!("{}", c.stripped())) - .collect::>(); - cookies.join("; ") - } - - fn update_cookies(vault: &EuphVault, set_cookies: &[HeaderValue]) { - let mut cookie_jar = CookieJar::new(); - - for cookie in set_cookies { - if let Ok(cookie) = cookie.to_str() { - if let Ok(cookie) = Cookie::from_str(cookie) { - cookie_jar.add(cookie) - } - } - } - - vault.set_cookies(cookie_jar); - } - - async fn connect(vault: &EuphRoomVault, name: &str) -> anyhow::Result> { - // TODO Set user agent? - - let cookies = Self::get_cookies(vault.vault()).await; - let cookies = HeaderValue::from_str(&cookies).expect("valid cookies"); - - match Conn::connect("euphoria.io", name, true, Some(cookies), TIMEOUT).await { - Ok((rx, set_cookies)) => { - Self::update_cookies(vault.vault(), &set_cookies); - Ok(Some(rx)) - } - Err(tungstenite::Error::Http(resp)) if resp.status().is_client_error() => { - bail!("room {name} doesn't exist"); - } - Err(tungstenite::Error::Url(_) | tungstenite::Error::HttpFormat(_)) => { - bail!("format error for room {name}"); - } - Err(_) => Ok(None), - } - } - - async fn regularly_request_logs(event_tx: &mpsc::UnboundedSender) { + async fn regularly_request_logs(vault: EuphRoomVault, conn_tx: ConnTx) { // TODO Make log downloading smarter // Possible log-related mechanics. Some of these could also run in some @@ -210,322 +166,130 @@ impl State { loop { tokio::time::sleep(LOG_INTERVAL).await; - let _ = event_tx.send(Event::RequestLogs); + Self::request_logs(&vault, &conn_tx).await; } } - async fn handle_events( - &mut self, - event_rx: &mut mpsc::UnboundedReceiver, - euph_room_event_tx: &mpsc::UnboundedSender, - ) -> anyhow::Result<()> { - while let Some(event) = event_rx.recv().await { - match event { - Event::Connected(conn_tx) => { - self.conn_tx = Some(conn_tx); - let _ = euph_room_event_tx.send(EuphRoomEvent::Connected); - } - Event::Disconnected => { - self.conn_tx = None; - self.last_msg_id = None; - let _ = euph_room_event_tx.send(EuphRoomEvent::Disconnected); - } - Event::Packet(packet) => { - self.on_packet(&packet).await?; - let _ = euph_room_event_tx.send(EuphRoomEvent::Packet(packet)); - } - Event::State(reply_tx) => self.on_state(reply_tx).await, - Event::RequestLogs => self.on_request_logs(), - Event::Auth(password) => self.on_auth(password), - Event::Nick(name) => self.on_nick(name), - Event::Send(parent, content, id_tx) => self.on_send(parent, content, id_tx), - Event::Login { email, password } => self.on_login(email, password), - Event::Logout => self.on_logout(), - } - } - Ok(()) - } - - async fn own_user_id(&self) -> Option { - Some(match self.conn_tx.as_ref()?.state().await.ok()? { - ConnState::Joining(Joining { hello, .. }) => hello?.session.id, - ConnState::Joined(joined) => joined.session.id, - }) - } - - async fn on_packet(&mut self, packet: &ParsedPacket) -> anyhow::Result<()> { - let data = ok_or_return!(&packet.content, Ok(())); - match data { - Data::BounceEvent(_) => { - if let Some(password) = &self.password { - // Try to authenticate with the configured password, but no - // promises if it doesn't work. In particular, we only ever - // try this password once. - self.on_auth(password.clone()); - } - } - Data::DisconnectEvent(d) => { - warn!("e&{}: disconnected for reason {:?}", self.name, d.reason); - } - Data::HelloEvent(_) => {} - Data::JoinEvent(d) => { - info!("e&{}: {:?} joined", self.name, d.0.name); - } - Data::LoginEvent(_) => {} - Data::LogoutEvent(_) => {} - Data::NetworkEvent(d) => { - info!("e&{}: network event ({})", self.name, d.r#type); - } - Data::NickEvent(d) => { - info!("e&{}: {:?} renamed to {:?}", self.name, d.from, d.to); - } - Data::EditMessageEvent(_) => { - info!("e&{}: a message was edited", self.name); - } - Data::PartEvent(d) => { - info!("e&{}: {:?} left", self.name, d.0.name); - } - Data::PingEvent(_) => {} - Data::PmInitiateEvent(d) => { - // TODO Show info popup and automatically join PM room - info!( - "e&{}: {:?} initiated a pm from &{}", - self.name, d.from_nick, d.from_room - ); - } - Data::SendEvent(d) => { - let own_user_id = self.own_user_id().await; - if let Some(last_msg_id) = &mut self.last_msg_id { - let id = d.0.id; - self.vault - .add_msg(Box::new(d.0.clone()), *last_msg_id, own_user_id); - *last_msg_id = Some(id); - } else { - bail!("send event before snapshot event"); - } - } - Data::SnapshotEvent(d) => { - info!("e&{}: successfully joined", self.name); - self.vault.join(Time::now()); - self.last_msg_id = Some(d.log.last().map(|m| m.id)); - let own_user_id = self.own_user_id().await; - self.vault.add_msgs(d.log.clone(), None, own_user_id); - - if let Some(username) = &self.username { - if self.force_username || d.nick.is_none() { - self.on_nick(username.clone()); - } - } - } - Data::LogReply(d) => { - let own_user_id = self.own_user_id().await; - self.vault.add_msgs(d.log.clone(), d.before, own_user_id); - } - Data::SendReply(d) => { - let own_user_id = self.own_user_id().await; - if let Some(last_msg_id) = &mut self.last_msg_id { - let id = d.0.id; - self.vault - .add_msg(Box::new(d.0.clone()), *last_msg_id, own_user_id); - *last_msg_id = Some(id); - } else { - bail!("send reply before snapshot event"); - } - } - _ => {} - } - Ok(()) - } - - async fn on_state(&self, reply_tx: oneshot::Sender>) { - let state = if let Some(conn_tx) = &self.conn_tx { - conn_tx.state().await.ok() - } else { - None - }; - - let _ = reply_tx.send(state); - } - - fn on_request_logs(&self) { - if let Some(conn_tx) = &self.conn_tx { - // Check whether logs are already being requested - let mut guard = self.requesting_logs.lock(); - if *guard { - return; - } else { - *guard = true; - } - drop(guard); - - // No logs are being requested and we've reserved our spot, so let's - // request some logs! - let vault = self.vault.clone(); - let conn_tx = conn_tx.clone(); - let requesting_logs = self.requesting_logs.clone(); - task::spawn(async move { - let result = Self::request_logs(vault, conn_tx).await; - *requesting_logs.lock() = false; - result - }); - } - } - - async fn request_logs(vault: EuphRoomVault, conn_tx: ConnTx) -> anyhow::Result<()> { + async fn request_logs(vault: &EuphRoomVault, conn_tx: &ConnTx) { let before = match vault.last_span().await { - Some((None, _)) => return Ok(()), // Already at top of room history + Some((None, _)) => return, // Already at top of room history Some((Some(before), _)) => Some(before), None => None, }; + debug!("{}: requesting logs", vault.room()); + // &rl2dev's message history is broken and requesting old messages past // a certain point results in errors. By reducing the amount of messages // in each log request, we can get closer to this point. Since &rl2dev // is fairly low in activity, this should be fine. let n = if vault.room() == "rl2dev" { 50 } else { 1000 }; - let _ = conn_tx.send(Log { n, before }).await?; + let _ = conn_tx.send(Log { n, before }).await; // The code handling incoming events and replies also handles // `LogReply`s, so we don't need to do anything special here. - - Ok(()) } - fn on_auth(&self, password: String) { - if let Some(conn_tx) = &self.conn_tx { - let conn_tx = conn_tx.clone(); - task::spawn(async move { - let _ = conn_tx - .send(Auth { - r#type: AuthOption::Passcode, - passcode: Some(password), - }) - .await; - }); + fn own_user_id(&self) -> Option { + if let State::Connected(_, state) = &self.state { + Some(match state { + conn::State::Joining(joining) => joining.hello.as_ref()?.session.id.clone(), + conn::State::Joined(joined) => joined.session.id.clone(), + }) + } else { + None } } - fn on_nick(&self, name: String) { - if let Some(conn_tx) = &self.conn_tx { - let conn_tx = conn_tx.clone(); - task::spawn(async move { - let _ = conn_tx.send(Nick { name }).await; - }); - } - } - - fn on_send( - &self, - parent: Option, - content: String, - id_tx: oneshot::Sender, - ) { - if let Some(conn_tx) = &self.conn_tx { - let conn_tx = conn_tx.clone(); - task::spawn(async move { - if let Ok(reply) = conn_tx.send(Send { content, parent }).await { - let _ = id_tx.send(reply.0.id); + fn on_packet(&mut self, packet: ParsedPacket) { + let instance_name = &self.instance.config().name; + let data = ok_or_return!(&packet.content); + match data { + Data::BounceEvent(_) => { + if let Some(password) = &self.instance.config().password { + // Try to authenticate with the configured password, but no + // promises if it doesn't work. In particular, we only ever + // try this password once. + let _ = self.auth(password.clone()); } - }); + } + Data::DisconnectEvent(d) => { + warn!("{instance_name}: disconnected for reason {:?}", d.reason); + } + Data::HelloEvent(_) => {} + Data::JoinEvent(d) => { + info!("{instance_name}: {:?} joined", d.0.name); + } + Data::LoginEvent(_) => {} + Data::LogoutEvent(_) => {} + Data::NetworkEvent(d) => { + info!("{instance_name}: network event ({})", d.r#type); + } + Data::NickEvent(d) => { + info!("{instance_name}: {:?} renamed to {:?}", d.from, d.to); + } + Data::EditMessageEvent(_) => { + info!("{instance_name}: a message was edited"); + } + Data::PartEvent(d) => { + info!("{instance_name}: {:?} left", d.0.name); + } + Data::PingEvent(_) => {} + Data::PmInitiateEvent(d) => { + // TODO Show info popup and automatically join PM room + info!( + "{instance_name}: {:?} initiated a pm from &{}", + d.from_nick, d.from_room + ); + } + Data::SendEvent(SendEvent(msg)) => { + let own_user_id = self.own_user_id(); + if let Some(last_msg_id) = &mut self.last_msg_id { + self.vault + .add_msg(Box::new(msg.clone()), *last_msg_id, own_user_id); + *last_msg_id = Some(msg.id); + } + } + Data::SnapshotEvent(d) => { + info!("{instance_name}: successfully joined"); + self.vault.join(Time::now()); + self.last_msg_id = Some(d.log.last().map(|m| m.id)); + self.vault.add_msgs(d.log.clone(), None, self.own_user_id()); + } + Data::LogReply(d) => { + self.vault + .add_msgs(d.log.clone(), d.before, self.own_user_id()); + } + Data::SendReply(SendReply(msg)) => { + let own_user_id = self.own_user_id(); + if let Some(last_msg_id) = &mut self.last_msg_id { + self.vault + .add_msg(Box::new(msg.clone()), *last_msg_id, own_user_id); + *last_msg_id = Some(msg.id); + } + } + _ => {} } } - fn on_login(&self, email: String, password: String) { - if let Some(conn_tx) = &self.conn_tx { - let _ = conn_tx.send(Login { - namespace: "email".to_string(), - id: email, - password, - }); - } - } - - fn on_logout(&self) { - if let Some(conn_tx) = &self.conn_tx { - let _ = conn_tx.send(Logout); - } - } -} - -#[derive(Debug)] -pub struct Room { - #[allow(dead_code)] - canary: oneshot::Sender, - event_tx: mpsc::UnboundedSender, -} - -impl Room { - pub fn new( - vault: EuphRoomVault, - username: Option, - force_username: bool, - password: Option, - ) -> (Self, mpsc::UnboundedReceiver) { - let (canary_tx, canary_rx) = oneshot::channel(); - let (event_tx, event_rx) = mpsc::unbounded_channel(); - let (euph_room_event_tx, euph_room_event_rx) = mpsc::unbounded_channel(); - - // &rl2dev's message history is broken and requesting old messages past - // a certain point results in errors. Cove should not keep retrying log - // requests when hitting that limit, so &rl2dev is always opened in - // ephemeral mode. - let room_name = vault.room().to_string(); - let ephemeral = vault.vault().vault().ephemeral() || room_name == "rl2dev"; - - let state = State { - name: vault.room().to_string(), - username, - force_username, - password, - vault, - conn_tx: None, - last_msg_id: None, - requesting_logs: Arc::new(Mutex::new(false)), - }; - - task::spawn(state.run( - canary_rx, - event_tx.clone(), - event_rx, - euph_room_event_tx, - ephemeral, - )); - - let new_room = Self { - canary: canary_tx, - event_tx, - }; - (new_room, euph_room_event_rx) - } - - pub fn stopped(&self) -> bool { - self.event_tx.is_closed() - } - - pub async fn state(&self) -> Result, Error> { - let (tx, rx) = oneshot::channel(); - self.event_tx - .send(Event::State(tx)) - .map_err(|_| Error::Stopped)?; - rx.await.map_err(|_| Error::Stopped) - } - pub fn auth(&self, password: String) -> Result<(), Error> { - self.event_tx - .send(Event::Auth(password)) - .map_err(|_| Error::Stopped) + let _ = self.conn_tx()?.send(Auth { + r#type: AuthOption::Passcode, + passcode: Some(password), + }); + Ok(()) } pub fn log(&self) -> Result<(), Error> { - self.event_tx - .send(Event::RequestLogs) - .map_err(|_| Error::Stopped) + let conn_tx_clone = self.conn_tx()?.clone(); + let vault_clone = self.vault.clone(); + tokio::task::spawn(async move { Self::request_logs(&vault_clone, &conn_tx_clone).await }); + Ok(()) } pub fn nick(&self, name: String) -> Result<(), Error> { - self.event_tx - .send(Event::Nick(name)) - .map_err(|_| Error::Stopped) + let _ = self.conn_tx()?.send(Nick { name }); + Ok(()) } pub fn send( @@ -533,22 +297,27 @@ impl Room { parent: Option, content: String, ) -> Result, Error> { - let (id_tx, id_rx) = oneshot::channel(); - self.event_tx - .send(Event::Send(parent, content, id_tx)) - .map(|_| id_rx) - .map_err(|_| Error::Stopped) + let reply = self.conn_tx()?.send(Send { content, parent }); + let (tx, rx) = oneshot::channel(); + tokio::spawn(async move { + if let Ok(reply) = reply.await { + let _ = tx.send(reply.0.id); + } + }); + Ok(rx) } pub fn login(&self, email: String, password: String) -> Result<(), Error> { - self.event_tx - .send(Event::Login { email, password }) - .map_err(|_| Error::Stopped) + let _ = self.conn_tx()?.send(Login { + namespace: "email".to_string(), + id: email, + password, + }); + Ok(()) } pub fn logout(&self) -> Result<(), Error> { - self.event_tx - .send(Event::Logout) - .map_err(|_| Error::Stopped) + let _ = self.conn_tx()?.send(Logout); + Ok(()) } } diff --git a/src/ui.rs b/src/ui.rs index 6393212..a24061e 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -17,7 +17,6 @@ use tokio::task; use toss::terminal::Terminal; use crate::config::Config; -use crate::euph::EuphRoomEvent; use crate::logger::{LogMsg, Logger}; use crate::macros::{ok_or_return, some_or_return}; use crate::vault::Vault; @@ -37,7 +36,7 @@ pub enum UiEvent { GraphemeWidthsChanged, LogChanged, Term(crossterm::event::Event), - EuphRoom { name: String, event: EuphRoomEvent }, + Euph(euphoxide::bot::instance::Event), } enum EventHandleResult { @@ -94,7 +93,7 @@ impl Ui { let mut ui = Self { event_tx: event_tx.clone(), mode: Mode::Main, - rooms: Rooms::new(config, vault, event_tx.clone()), + rooms: Rooms::new(config, vault, event_tx.clone()).await, log_chat: ChatState::new(logger), key_bindings_list: None, }; @@ -230,9 +229,8 @@ impl Ui { self.handle_term_event(terminal, crossterm_lock, event) .await } - UiEvent::EuphRoom { name, event } => { - let handled = self.handle_euph_room_event(name, event).await; - if self.mode == Mode::Main && handled { + UiEvent::Euph(event) => { + if self.rooms.handle_euph_event(event) { EventHandleResult::Redraw } else { EventHandleResult::Continue @@ -311,9 +309,4 @@ impl Ui { EventHandleResult::Continue } } - - async fn handle_euph_room_event(&mut self, name: String, event: EuphRoomEvent) -> bool { - let handled = self.rooms.handle_euph_room_event(name, event); - handled && self.mode == Mode::Main - } } diff --git a/src/ui/euph/account.rs b/src/ui/euph/account.rs index 32569f7..3112719 100644 --- a/src/ui/euph/account.rs +++ b/src/ui/euph/account.rs @@ -1,9 +1,9 @@ use crossterm::style::{ContentStyle, Stylize}; use euphoxide::api::PersonalAccountView; -use euphoxide::conn::State as ConnState; +use euphoxide::conn; use toss::terminal::Terminal; -use crate::euph::Room; +use crate::euph::{self, Room}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::util; use crate::ui::widgets::editor::EditorState; @@ -14,8 +14,6 @@ use crate::ui::widgets::resize::Resize; use crate::ui::widgets::text::Text; use crate::ui::widgets::BoxedWidget; -use super::room::RoomState; - #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Focus { Email, @@ -97,8 +95,8 @@ impl AccountUiState { } /// Returns `false` if the account UI should not be displayed any longer. - pub fn stabilize(&mut self, state: &RoomState) -> bool { - if let RoomState::Connected(ConnState::Joined(state)) = state { + pub fn stabilize(&mut self, state: Option<&euph::State>) -> bool { + if let Some(euph::State::Connected(_, conn::State::Joined(state))) = state { match (&self, &state.account) { (Self::LoggedOut(_), Some(view)) => *self = Self::LoggedIn(LoggedIn(view.clone())), (Self::LoggedIn(_), None) => *self = Self::LoggedOut(LoggedOut::new()), diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index da95dfb..0639a4a 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -3,7 +3,8 @@ use std::sync::Arc; use crossterm::style::{ContentStyle, Stylize}; use euphoxide::api::{Data, Message, MessageId, PacketType, SessionId}; -use euphoxide::conn::{Joined, Joining, SessionInfo, State as ConnState}; +use euphoxide::bot::instance::{Event, ServerConfig}; +use euphoxide::conn::{self, Joined, Joining, SessionInfo}; use parking_lot::FairMutex; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::{mpsc, oneshot}; @@ -11,8 +12,7 @@ use toss::styled::Styled; use toss::terminal::Terminal; use crate::config; -use crate::euph::{self, EuphRoomEvent}; -use crate::macros::{ok_or_return, some_or_return}; +use crate::euph; use crate::ui::chat::{ChatState, Reaction}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::widgets::border::Border; @@ -48,26 +48,9 @@ enum State { InspectSession(SessionInfo), } -#[allow(clippy::large_enum_variant)] -pub enum RoomState { - NoRoom, - Stopped, - Connecting, - Connected(ConnState), -} - -impl RoomState { - pub fn connecting_or_connected(&self) -> bool { - match self { - Self::NoRoom | Self::Stopped => false, - Self::Connecting | Self::Connected(_) => true, - } - } -} - pub struct EuphRoom { + server_config: ServerConfig, config: config::EuphRoom, - ui_event_tx: mpsc::UnboundedSender, room: Option, @@ -84,11 +67,13 @@ pub struct EuphRoom { impl EuphRoom { pub fn new( + server_config: ServerConfig, config: config::EuphRoom, vault: EuphRoomVault, ui_event_tx: mpsc::UnboundedSender, ) -> Self { Self { + server_config, config, ui_event_tx, room: None, @@ -109,36 +94,23 @@ impl EuphRoom { self.vault().room() } - async fn shovel_room_events( - name: String, - mut euph_room_event_rx: mpsc::UnboundedReceiver, - ui_event_tx: mpsc::UnboundedSender, - ) { - loop { - let event = some_or_return!(euph_room_event_rx.recv().await); - let event = UiEvent::EuphRoom { - name: name.clone(), - event, - }; - ok_or_return!(ui_event_tx.send(event)); - } - } - pub fn connect(&mut self) { if self.room.is_none() { - let (room, euph_room_event_rx) = euph::Room::new( + let instance_config = self + .server_config + .clone() + .room(self.vault().room().to_string()) + .username(self.config.username.clone()) + .force_username(self.config.force_username) + .password(self.config.password.clone()); + + let tx = self.ui_event_tx.clone(); + self.room = Some(euph::Room::new( self.vault().clone(), - self.config.username.clone(), - self.config.force_username, - self.config.password.clone(), - ); - - self.room = Some(room); - - tokio::task::spawn(Self::shovel_room_events( - self.name().to_string(), - euph_room_event_rx, - self.ui_event_tx.clone(), + instance_config, + move |e| { + let _ = tx.send(UiEvent::Euph(e)); + }, )); } } @@ -147,17 +119,16 @@ impl EuphRoom { self.room = None; } - pub async fn state(&self) -> RoomState { - match &self.room { - Some(room) => match room.state().await { - Ok(Some(state)) => RoomState::Connected(state), - Ok(None) => RoomState::Connecting, - Err(_) => RoomState::Stopped, - }, - None => RoomState::NoRoom, + pub fn room_state(&self) -> Option<&euph::State> { + if let Some(room) = &self.room { + Some(room.state()) + } else { + None } } + // TODO fn room_state_joined(&self) -> Option<&Joined> {} + pub fn stopped(&self) -> bool { self.room.as_ref().map(|r| r.stopped()).unwrap_or(true) } @@ -190,52 +161,55 @@ impl EuphRoom { } } - fn stabilize_focus(&mut self, state: &RoomState) { - match state { - RoomState::Connected(ConnState::Joined(_)) => {} + fn stabilize_focus(&mut self) { + match self.room_state() { + Some(euph::State::Connected(_, conn::State::Joined(_))) => {} _ => self.focus = Focus::Chat, // There is no nick list to focus on } } - fn stabilize_state(&mut self, state: &RoomState) { - match &mut self.state { - State::Auth(_) - if !matches!( - state, - RoomState::Connected(ConnState::Joining(Joining { - bounce: Some(_), - .. - })) - ) => - { - self.state = State::Normal - } - State::Nick(_) if !matches!(state, RoomState::Connected(ConnState::Joined(_))) => { - self.state = State::Normal - } - State::Account(account) => { + fn stabilize_state(&mut self) { + let room_state = self.room.as_ref().map(|r| r.state()); + match (&mut self.state, room_state) { + ( + State::Auth(_), + Some(euph::State::Connected( + _, + conn::State::Joining(Joining { + bounce: Some(_), .. + }), + )), + ) => {} // Nothing to see here + (State::Auth(_), _) => self.state = State::Normal, + + (State::Nick(_), Some(euph::State::Connected(_, conn::State::Joined(_)))) => {} + (State::Nick(_), _) => self.state = State::Normal, + + (State::Account(account), state) => { if !account.stabilize(state) { self.state = State::Normal } } + _ => {} } } - async fn stabilize(&mut self, state: &RoomState) { + async fn stabilize(&mut self) { self.stabilize_pseudo_msg().await; - self.stabilize_focus(state); - self.stabilize_state(state); + self.stabilize_focus(); + self.stabilize_state(); } pub async fn widget(&mut self) -> BoxedWidget { - let state = self.state().await; - self.stabilize(&state).await; + self.stabilize().await; - let chat = if let RoomState::Connected(ConnState::Joined(joined)) = &state { - self.widget_with_nick_list(&state, joined).await + let room_state = self.room_state(); + let chat = if let Some(euph::State::Connected(_, conn::State::Joined(joined))) = room_state + { + self.widget_with_nick_list(room_state, joined).await } else { - self.widget_without_nick_list(&state).await + self.widget_without_nick_list(room_state).await }; let mut layers = vec![chat]; @@ -257,7 +231,7 @@ impl EuphRoom { Layer::new(layers).into() } - async fn widget_without_nick_list(&self, state: &RoomState) -> BoxedWidget { + async fn widget_without_nick_list(&self, state: Option<&euph::State>) -> BoxedWidget { VJoin::new(vec![ Segment::new(Border::new( Padding::new(self.status_widget(state).await).horizontal(1), @@ -268,7 +242,11 @@ impl EuphRoom { .into() } - async fn widget_with_nick_list(&self, state: &RoomState, joined: &Joined) -> BoxedWidget { + async fn widget_with_nick_list( + &self, + state: Option<&euph::State>, + joined: &Joined, + ) -> BoxedWidget { HJoin::new(vec![ Segment::new(VJoin::new(vec![ Segment::new(Border::new( @@ -293,18 +271,21 @@ impl EuphRoom { .into() } - async fn status_widget(&self, state: &RoomState) -> BoxedWidget { + async fn status_widget(&self, state: Option<&euph::State>) -> BoxedWidget { let room_style = ContentStyle::default().bold().blue(); let mut info = Styled::new(format!("&{}", self.name()), room_style); info = match state { - RoomState::NoRoom | RoomState::Stopped => info.then_plain(", archive"), - RoomState::Connecting => info.then_plain(", connecting..."), - RoomState::Connected(ConnState::Joining(j)) if j.bounce.is_some() => { + None | Some(euph::State::Stopped) => info.then_plain(", archive"), + Some(euph::State::Disconnected) => info.then_plain(", waiting..."), + Some(euph::State::Connecting) => info.then_plain(", connecting..."), + Some(euph::State::Connected(_, conn::State::Joining(j))) if j.bounce.is_some() => { info.then_plain(", auth required") } - RoomState::Connected(ConnState::Joining(_)) => info.then_plain(", joining..."), - RoomState::Connected(ConnState::Joined(j)) => { + Some(euph::State::Connected(_, conn::State::Joining(_))) => { + info.then_plain(", joining...") + } + Some(euph::State::Connected(_, conn::State::Joined(j))) => { let nick = &j.session.name; if nick.is_empty() { info.then_plain(", present without nick") @@ -326,8 +307,11 @@ impl EuphRoom { Text::new(info).into() } - async fn list_chat_key_bindings(&self, bindings: &mut KeyBindingsList, state: &RoomState) { - let can_compose = matches!(state, RoomState::Connected(ConnState::Joined(_))); + async fn list_chat_key_bindings(&self, bindings: &mut KeyBindingsList) { + let can_compose = matches!( + self.room_state(), + Some(euph::State::Connected(_, conn::State::Joined(_))) + ); self.chat.list_key_bindings(bindings, can_compose).await; } @@ -336,9 +320,11 @@ impl EuphRoom { terminal: &mut Terminal, crossterm_lock: &Arc>, event: &InputEvent, - state: &RoomState, ) -> bool { - let can_compose = matches!(state, RoomState::Connected(ConnState::Joined(_))); + let can_compose = matches!( + self.room_state(), + Some(euph::State::Connected(_, conn::State::Joined(_))) + ); match self .chat @@ -368,17 +354,20 @@ impl EuphRoom { false } - fn list_room_key_bindings(&self, bindings: &mut KeyBindingsList, state: &RoomState) { - match state { + fn list_room_key_bindings(&self, bindings: &mut KeyBindingsList) { + match self.room_state() { // Authenticating - RoomState::Connected(ConnState::Joining(Joining { - bounce: Some(_), .. - })) => { + Some(euph::State::Connected( + _, + conn::State::Joining(Joining { + bounce: Some(_), .. + }), + )) => { bindings.binding("a", "authenticate"); } // Connected - RoomState::Connected(ConnState::Joined(_)) => { + Some(euph::State::Connected(_, conn::State::Joined(_))) => { bindings.binding("n", "change nick"); bindings.binding("m", "download more messages"); bindings.binding("A", "show account ui"); @@ -394,12 +383,15 @@ impl EuphRoom { bindings.binding("ctrl+p", "open room's plugh.de/present page"); } - async fn handle_room_input_event(&mut self, event: &InputEvent, state: &RoomState) -> bool { - match state { + async fn handle_room_input_event(&mut self, event: &InputEvent) -> bool { + match self.room_state() { // Authenticating - RoomState::Connected(ConnState::Joining(Joining { - bounce: Some(_), .. - })) => { + Some(euph::State::Connected( + _, + conn::State::Joining(Joining { + bounce: Some(_), .. + }), + )) => { if let key!('a') = event { self.state = State::Auth(auth::new()); return true; @@ -407,7 +399,7 @@ impl EuphRoom { } // Joined - RoomState::Connected(ConnState::Joined(joined)) => match event { + Some(euph::State::Connected(_, conn::State::Joined(joined))) => match event { key!('n') | key!('N') => { self.state = State::Nick(nick::new(joined.clone())); return true; @@ -463,14 +455,10 @@ impl EuphRoom { false } - async fn list_chat_focus_key_bindings( - &self, - bindings: &mut KeyBindingsList, - state: &RoomState, - ) { - self.list_room_key_bindings(bindings, state); + async fn list_chat_focus_key_bindings(&self, bindings: &mut KeyBindingsList) { + self.list_room_key_bindings(bindings); bindings.empty(); - self.list_chat_key_bindings(bindings, state).await; + self.list_chat_key_bindings(bindings).await; } async fn handle_chat_focus_input_event( @@ -478,18 +466,17 @@ impl EuphRoom { terminal: &mut Terminal, crossterm_lock: &Arc>, event: &InputEvent, - state: &RoomState, ) -> bool { // We need to handle chat input first, otherwise the other // key bindings will shadow characters in the editor. if self - .handle_chat_input_event(terminal, crossterm_lock, event, state) + .handle_chat_input_event(terminal, crossterm_lock, event) .await { return true; } - if self.handle_room_input_event(event, state).await { + if self.handle_room_input_event(event).await { return true; } @@ -502,17 +489,14 @@ impl EuphRoom { bindings.binding("i", "inspect session"); } - fn handle_nick_list_focus_input_event( - &mut self, - event: &InputEvent, - state: &RoomState, - ) -> bool { + fn handle_nick_list_focus_input_event(&mut self, event: &InputEvent) -> bool { if util::handle_list_input_event(&mut self.nick_list, event) { return true; } if let key!('i') = event { - if let RoomState::Connected(ConnState::Joined(joined)) = state { + if let Some(euph::State::Connected(_, conn::State::Joined(joined))) = self.room_state() + { if let Some(id) = self.nick_list.cursor() { if id == joined.session.session_id { self.state = @@ -532,15 +516,13 @@ impl EuphRoom { // Handled in rooms list, not here bindings.binding("esc", "leave room"); - let state = self.state().await; - match self.focus { Focus::Chat => { - if let RoomState::Connected(ConnState::Joined(_)) = state { + if let Some(euph::State::Connected(_, conn::State::Joined(_))) = self.room_state() { bindings.binding("tab", "focus on nick list"); } - self.list_chat_focus_key_bindings(bindings, &state).await; + self.list_chat_focus_key_bindings(bindings).await; } Focus::NickList => { bindings.binding("tab, esc", "focus on chat"); @@ -557,20 +539,18 @@ impl EuphRoom { crossterm_lock: &Arc>, event: &InputEvent, ) -> bool { - let state = self.state().await; - match self.focus { Focus::Chat => { // Needs to be handled first or the tab key may be shadowed // during editing. if self - .handle_chat_focus_input_event(terminal, crossterm_lock, event, &state) + .handle_chat_focus_input_event(terminal, crossterm_lock, event) .await { return true; } - if let RoomState::Connected(ConnState::Joined(_)) = state { + if let Some(euph::State::Connected(_, conn::State::Joined(_))) = self.room_state() { if let key!(Tab) = event { self.focus = Focus::NickList; return true; @@ -583,7 +563,7 @@ impl EuphRoom { return true; } - if self.handle_nick_list_focus_input_event(event, &state) { + if self.handle_nick_list_focus_input_event(event) { return true; } } @@ -690,20 +670,31 @@ impl EuphRoom { } } - pub fn handle_euph_room_event(&mut self, event: EuphRoomEvent) -> bool { - match event { - EuphRoomEvent::Connected | EuphRoomEvent::Disconnected | EuphRoomEvent::Stopped => true, - EuphRoomEvent::Packet(packet) => match packet.content { - Ok(data) => self.handle_euph_data(data), - Err(reason) => self.handle_euph_error(packet.r#type, reason), - }, + pub fn handle_event(&mut self, event: Event) -> bool { + let handled = if self.room.is_some() { + if let Event::Packet(_, packet, _) = &event { + match &packet.content { + Ok(data) => self.handle_euph_data(data), + Err(reason) => self.handle_euph_error(packet.r#type, reason), + } + } else { + true + } + } else { + false + }; + + if let Some(room) = &mut self.room { + room.handle_event(event); } + + handled } - fn handle_euph_data(&mut self, data: Data) -> bool { + fn handle_euph_data(&mut self, data: &Data) -> bool { // These packets don't result in any noticeable change in the UI. #[allow(clippy::match_like_matches_macro)] - let handled = match &data { + let handled = match data { Data::PingEvent(_) | Data::PingReply(_) => { // Pings are displayed nowhere in the room UI. false @@ -720,8 +711,10 @@ impl EuphRoom { // consistency, some failures are not normal errors but instead // error-free replies that encode their own error. let error = match data { - Data::AuthReply(reply) if !reply.success => Some(("authenticate", reply.reason)), - Data::LoginReply(reply) if !reply.success => Some(("login", reply.reason)), + Data::AuthReply(reply) if !reply.success => { + Some(("authenticate", reply.reason.clone())) + } + Data::LoginReply(reply) if !reply.success => Some(("login", reply.reason.clone())), _ => None, }; if let Some((action, reason)) = error { @@ -736,7 +729,7 @@ impl EuphRoom { handled } - fn handle_euph_error(&mut self, r#type: PacketType, reason: String) -> bool { + fn handle_euph_error(&mut self, r#type: PacketType, reason: &str) -> bool { let action = match r#type { PacketType::AuthReply => "authenticate", PacketType::NickReply => "set nick", @@ -762,7 +755,7 @@ impl EuphRoom { let description = format!("Failed to {action}."); self.popups.push_front(RoomPopup::Error { description, - reason, + reason: reason.to_string(), }); true } diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index b622394..bd03130 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -1,20 +1,21 @@ use std::collections::{HashMap, HashSet}; use std::iter; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use crossterm::style::{ContentStyle, Stylize}; use euphoxide::api::SessionType; -use euphoxide::conn::{Joined, State as ConnState}; +use euphoxide::bot::instance::{Event, ServerConfig}; +use euphoxide::conn::{self, Joined}; use parking_lot::FairMutex; use tokio::sync::mpsc; use toss::styled::Styled; use toss::terminal::Terminal; use crate::config::{Config, RoomsSortOrder}; -use crate::euph::EuphRoomEvent; +use crate::euph; use crate::vault::Vault; -use super::euph::room::{EuphRoom, RoomState}; +use super::euph::room::EuphRoom; use super::input::{key, InputEvent, KeyBindingsList}; use super::widgets::editor::EditorState; use super::widgets::join::{HJoin, Segment, VJoin}; @@ -57,15 +58,20 @@ pub struct Rooms { list: ListState, order: Order, + + euph_server_config: ServerConfig, euph_rooms: HashMap, } impl Rooms { - pub fn new( + pub async fn new( config: &'static Config, vault: Vault, ui_event_tx: mpsc::UnboundedSender, ) -> Self { + let euph_server_config = + ServerConfig::default().cookies(Arc::new(Mutex::new(vault.euph().cookies().await))); + let mut result = Self { config, vault, @@ -73,6 +79,7 @@ impl Rooms { state: State::ShowList, list: ListState::new(), order: Order::from_rooms_sort_order(config.rooms_sort_order), + euph_server_config, euph_rooms: HashMap::new(), }; @@ -90,6 +97,7 @@ impl Rooms { fn get_or_insert_room(&mut self, name: String) -> &mut EuphRoom { self.euph_rooms.entry(name.clone()).or_insert_with(|| { EuphRoom::new( + self.euph_server_config.clone(), self.config.euph_room(&name), self.vault.euph().room(name), self.ui_event_tx.clone(), @@ -236,15 +244,18 @@ impl Rooms { result.join(" ") } - fn format_room_state(state: RoomState) -> Option { + fn format_room_state(state: Option<&euph::State>) -> Option { match state { - RoomState::NoRoom | RoomState::Stopped => None, - RoomState::Connecting => Some("connecting".to_string()), - RoomState::Connected(ConnState::Joining(j)) if j.bounce.is_some() => { - Some("auth required".to_string()) - } - RoomState::Connected(ConnState::Joining(_)) => Some("joining".to_string()), - RoomState::Connected(ConnState::Joined(joined)) => Some(Self::format_pbln(&joined)), + None | Some(euph::State::Stopped) => None, + Some(euph::State::Disconnected) => Some("waiting".to_string()), + Some(euph::State::Connecting) => Some("connecting".to_string()), + Some(euph::State::Connected(_, connected)) => match connected { + conn::State::Joining(joining) if joining.bounce.is_some() => { + Some("auth required".to_string()) + } + conn::State::Joining(_) => Some("joining".to_string()), + conn::State::Joined(joined) => Some(Self::format_pbln(joined)), + }, } } @@ -256,7 +267,7 @@ impl Rooms { } } - fn format_room_info(state: RoomState, unseen: usize) -> Styled { + fn format_room_info(state: Option<&euph::State>, unseen: usize) -> Styled { let unseen_style = ContentStyle::default().bold().green(); let state = Self::format_room_state(state); @@ -276,12 +287,16 @@ impl Rooms { } } - fn sort_rooms(&self, rooms: &mut [(&String, RoomState, usize)]) { + fn sort_rooms(&self, rooms: &mut [(&String, Option<&euph::State>, usize)]) { match self.order { Order::Alphabet => rooms.sort_unstable_by_key(|(n, _, _)| *n), - Order::Importance => { - rooms.sort_unstable_by_key(|(n, s, u)| (!s.connecting_or_connected(), *u == 0, *n)) - } + Order::Importance => rooms.sort_unstable_by_key(|(n, s, u)| { + let connecting_or_connected = matches!( + s, + Some(euph::State::Connecting | euph::State::Connected(_, _)) + ); + (!connecting_or_connected, *u == 0, *n) + }), } } @@ -295,7 +310,7 @@ impl Rooms { let mut rooms = vec![]; for (name, room) in &self.euph_rooms { - let state = room.state().await; + let state = room.room_state(); let unseen = room.unseen_msgs_count().await; rooms.push((name, state, unseen)); } @@ -536,15 +551,15 @@ impl Rooms { false } - pub fn handle_euph_room_event(&mut self, name: String, event: EuphRoomEvent) -> bool { - let room_visible = if let State::ShowRoom(n) = &self.state { - *n == name - } else { - true - }; + pub fn handle_euph_event(&mut self, event: Event) -> bool { + let instance_name = event.config().name.clone(); + let room = self.get_or_insert_room(instance_name.clone()); + let handled = room.handle_event(event); - let room = self.get_or_insert_room(name); - let handled = room.handle_euph_room_event(event); + let room_visible = match &self.state { + State::ShowRoom(name) => *name == instance_name, + _ => true, + }; handled && room_visible } } From 1be5fb5f39d899c3862cbe7e839851df719f72e8 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 23 Jan 2023 22:40:04 +0100 Subject: [PATCH 019/266] Limit logged messages --- src/logger.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/logger.rs b/src/logger.rs index db2126b..29da638 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -178,8 +178,17 @@ impl MsgStore for Logger { } impl Log for Logger { - fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool { - true + fn enabled(&self, metadata: &log::Metadata<'_>) -> bool { + if metadata.level() <= Level::Info { + return true; + } + + let target = metadata.target(); + if target.starts_with("cove") || target.starts_with("euphoxide::bot") { + return true; + } + + false } fn log(&self, record: &log::Record<'_>) { From c2e739abf9aab74be1fe0ab3ff475529bea67a7d Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 23 Jan 2023 22:50:39 +0100 Subject: [PATCH 020/266] Fix auth-auth-disconnect-reconnect loop Both euphoxide and cove would try to authenticate, leading to the server disconnecting the session. The Instance would then immediately reconnect because the previous initial connection was successful. Rinse and repeat --- src/euph/room.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/euph/room.rs b/src/euph/room.rs index 41bd7ec..1235ca5 100644 --- a/src/euph/room.rs +++ b/src/euph/room.rs @@ -205,14 +205,7 @@ impl Room { let instance_name = &self.instance.config().name; let data = ok_or_return!(&packet.content); match data { - Data::BounceEvent(_) => { - if let Some(password) = &self.instance.config().password { - // Try to authenticate with the configured password, but no - // promises if it doesn't work. In particular, we only ever - // try this password once. - let _ = self.auth(password.clone()); - } - } + Data::BounceEvent(_) => {} Data::DisconnectEvent(d) => { warn!("{instance_name}: disconnected for reason {:?}", d.reason); } From f9533d811962240c715ca679e371c62f0d52f1ad Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 23 Jan 2023 22:52:25 +0100 Subject: [PATCH 021/266] Update debug logging Some things euphoxide already logs. The priorities for the other messages were adjusted to make more sense (hopefully). --- src/euph/room.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/euph/room.rs b/src/euph/room.rs index 1235ca5..d35b106 100644 --- a/src/euph/room.rs +++ b/src/euph/room.rs @@ -206,26 +206,24 @@ impl Room { let data = ok_or_return!(&packet.content); match data { Data::BounceEvent(_) => {} - Data::DisconnectEvent(d) => { - warn!("{instance_name}: disconnected for reason {:?}", d.reason); - } + Data::DisconnectEvent(_) => {} Data::HelloEvent(_) => {} Data::JoinEvent(d) => { - info!("{instance_name}: {:?} joined", d.0.name); + debug!("{instance_name}: {:?} joined", d.0.name); } Data::LoginEvent(_) => {} Data::LogoutEvent(_) => {} Data::NetworkEvent(d) => { - info!("{instance_name}: network event ({})", d.r#type); + warn!("{instance_name}: network event ({})", d.r#type); } Data::NickEvent(d) => { - info!("{instance_name}: {:?} renamed to {:?}", d.from, d.to); + debug!("{instance_name}: {:?} renamed to {:?}", d.from, d.to); } Data::EditMessageEvent(_) => { info!("{instance_name}: a message was edited"); } Data::PartEvent(d) => { - info!("{instance_name}: {:?} left", d.0.name); + debug!("{instance_name}: {:?} left", d.0.name); } Data::PingEvent(_) => {} Data::PmInitiateEvent(d) => { From 2f7234189b17a0b04a0e9223dc1818141345d5fd Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 23 Jan 2023 23:03:17 +0100 Subject: [PATCH 022/266] Add --verbose flag --- CHANGELOG.md | 3 +++ src/logger.rs | 11 ++++++++--- src/main.rs | 7 +++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b163ad..f985868 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ Procedure when bumping the version number: ## Unreleased +### Added +- `--verbose` flag + ### Changed - Respect colon-delimited emoji when calculating nick hue - Display colon-delimited emoji in nicks and messages diff --git a/src/logger.rs b/src/logger.rs index 29da638..692c0b6 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -3,7 +3,7 @@ use std::vec; use async_trait::async_trait; use crossterm::style::{ContentStyle, Stylize}; -use log::{Level, Log}; +use log::{Level, LevelFilter, Log}; use parking_lot::Mutex; use time::OffsetDateTime; use tokio::sync::mpsc; @@ -212,7 +212,7 @@ impl Log for Logger { } impl Logger { - pub fn init(level: Level) -> (Self, LoggerGuard, mpsc::UnboundedReceiver<()>) { + pub fn init(verbose: bool) -> (Self, LoggerGuard, mpsc::UnboundedReceiver<()>) { let (event_tx, event_rx) = mpsc::unbounded_channel(); let logger = Self { event_tx, @@ -222,8 +222,13 @@ impl Logger { messages: logger.messages.clone(), }; + log::set_max_level(if verbose { + LevelFilter::Debug + } else { + LevelFilter::Info + }); + log::set_boxed_logger(Box::new(logger.clone())).expect("logger already set"); - log::set_max_level(level.to_level_filter()); (logger, guard, event_rx) } diff --git a/src/main.rs b/src/main.rs index adf33e9..4fb274c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,6 +57,10 @@ impl Default for Command { #[derive(Debug, clap::Parser)] #[command(version)] struct Args { + /// Show more detailed log messages. + #[arg(long, short)] + verbose: bool, + /// Path to the config file. /// /// Relative paths are interpreted relative to the current directory. @@ -114,9 +118,8 @@ fn set_offline(config: &mut Config, args_offline: bool) { #[tokio::main] async fn main() -> anyhow::Result<()> { - let (logger, logger_guard, logger_rx) = Logger::init(log::Level::Debug); - let args = Args::parse(); + let (logger, logger_guard, logger_rx) = Logger::init(args.verbose); let dirs = ProjectDirs::from("de", "plugh", "cove").expect("unable to determine directories"); let config_path = args From f2d70f99eb29486bc1b222bda2f53af8bcc13fbb Mon Sep 17 00:00:00 2001 From: Joscha Date: Tue, 24 Jan 2023 18:22:52 +0100 Subject: [PATCH 023/266] Fix rooms not reconnecting properly --- Cargo.lock | 12 +++++++----- Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e73fda6..85be4d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,9 @@ checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "async-trait" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282" +checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" dependencies = [ "proc-macro2", "quote", @@ -104,9 +104,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.1.1" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" +checksum = "d8d93d855ce6a0aa87b8473ef9169482f40abaa2e9e0993024c35c902cbd5920" dependencies = [ "bitflags", "clap_derive", @@ -309,8 +309,10 @@ dependencies = [ [[package]] name = "euphoxide" version = "0.2.0" -source = "git+https://github.com/Garmelon/euphoxide.git?rev=63464fdc59f24774cb96b7a8038326a9dc7d587a#63464fdc59f24774cb96b7a8038326a9dc7d587a" +source = "git+https://github.com/Garmelon/euphoxide.git?rev=2719ab33014f790b8af76fc028c077b90e9716cb#2719ab33014f790b8af76fc028c077b90e9716cb" dependencies = [ + "async-trait", + "clap", "cookie", "futures-util", "log", diff --git a/Cargo.toml b/Cargo.toml index 93ffc6d..addcb4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ features = ["rustls-tls-native-roots"] [dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -rev = "63464fdc59f24774cb96b7a8038326a9dc7d587a" +rev = "2719ab33014f790b8af76fc028c077b90e9716cb" features = ["bot"] # [patch."https://github.com/Garmelon/euphoxide.git"] From b6d69ce0b5a90c56642514963149b4e16a79c01f Mon Sep 17 00:00:00 2001 From: Joscha Date: Tue, 24 Jan 2023 18:23:06 +0100 Subject: [PATCH 024/266] Fix sort order for rooms waiting to reconnect --- src/ui/rooms.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index bd03130..07ef09a 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -291,11 +291,8 @@ impl Rooms { match self.order { Order::Alphabet => rooms.sort_unstable_by_key(|(n, _, _)| *n), Order::Importance => rooms.sort_unstable_by_key(|(n, s, u)| { - let connecting_or_connected = matches!( - s, - Some(euph::State::Connecting | euph::State::Connected(_, _)) - ); - (!connecting_or_connected, *u == 0, *n) + let no_instance = matches!(s, None | Some(euph::State::Disconnected)); + (no_instance, *u == 0, *n) }), } } From 56373135c7f38f82487e2dbab0a8bef6e03178a8 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 30 Jan 2023 17:59:46 +0100 Subject: [PATCH 025/266] Fix mentions not being stopped by > --- CHANGELOG.md | 3 +++ src/euph/small_message.rs | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f985868..660d36c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ Procedure when bumping the version number: - Respect colon-delimited emoji when calculating nick hue - Display colon-delimited emoji in nicks and messages +### Fixed +- Mentions not being stopped by `>` + ## v0.5.2 - 2023-01-14 ### Added diff --git a/src/euph/small_message.rs b/src/euph/small_message.rs index 1aec5f8..4d2f4f4 100644 --- a/src/euph/small_message.rs +++ b/src/euph/small_message.rs @@ -13,8 +13,9 @@ use super::util; fn nick_char(ch: char) -> bool { // Closely following the heim mention regex: // https://github.com/euphoria-io/heim/blob/978c921063e6b06012fc8d16d9fbf1b3a0be1191/client/lib/stores/chat.js#L14-L15 + // `>` has been experimentally confirmed to delimit mentions as well. match ch { - ',' | '.' | '!' | '?' | ';' | '&' | '<' | '\'' | '"' => false, + ',' | '.' | '!' | '?' | ';' | '&' | '<' | '>' | '\'' | '"' => false, _ => !ch.is_whitespace(), } } From ecedad8f0f18416827428002ed94f8adf5550f36 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 30 Jan 2023 19:04:24 +0100 Subject: [PATCH 026/266] Update euphoxide --- Cargo.lock | 14 +++++++++++++- Cargo.toml | 2 +- src/euph/room.rs | 8 ++++---- src/euph/util.rs | 2 +- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85be4d6..423c010 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,6 +90,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +[[package]] +name = "caseless" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808dab3318747be122cb31d36de18d4d1c81277a76f8332a02b81a3d73463d7f" +dependencies = [ + "regex", + "unicode-normalization", +] + [[package]] name = "cc" version = "1.0.78" @@ -309,9 +319,10 @@ dependencies = [ [[package]] name = "euphoxide" version = "0.2.0" -source = "git+https://github.com/Garmelon/euphoxide.git?rev=2719ab33014f790b8af76fc028c077b90e9716cb#2719ab33014f790b8af76fc028c077b90e9716cb" +source = "git+https://github.com/Garmelon/euphoxide.git?rev=f394ddc5a67f53223f977ccc414bb4c949790f6b#f394ddc5a67f53223f977ccc414bb4c949790f6b" dependencies = [ "async-trait", + "caseless", "clap", "cookie", "futures-util", @@ -322,6 +333,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-tungstenite", + "unicode-normalization", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index addcb4b..555cf92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ features = ["rustls-tls-native-roots"] [dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -rev = "2719ab33014f790b8af76fc028c077b90e9716cb" +rev = "f394ddc5a67f53223f977ccc414bb4c949790f6b" features = ["bot"] # [patch."https://github.com/Garmelon/euphoxide.git"] diff --git a/src/euph/room.rs b/src/euph/room.rs index d35b106..f9cb9b9 100644 --- a/src/euph/room.rs +++ b/src/euph/room.rs @@ -264,7 +264,7 @@ impl Room { } pub fn auth(&self, password: String) -> Result<(), Error> { - let _ = self.conn_tx()?.send(Auth { + self.conn_tx()?.send_only(Auth { r#type: AuthOption::Passcode, passcode: Some(password), }); @@ -279,7 +279,7 @@ impl Room { } pub fn nick(&self, name: String) -> Result<(), Error> { - let _ = self.conn_tx()?.send(Nick { name }); + self.conn_tx()?.send_only(Nick { name }); Ok(()) } @@ -299,7 +299,7 @@ impl Room { } pub fn login(&self, email: String, password: String) -> Result<(), Error> { - let _ = self.conn_tx()?.send(Login { + self.conn_tx()?.send_only(Login { namespace: "email".to_string(), id: email, password, @@ -308,7 +308,7 @@ impl Room { } pub fn logout(&self) -> Result<(), Error> { - let _ = self.conn_tx()?.send(Logout); + self.conn_tx()?.send_only(Logout); Ok(()) } } diff --git a/src/euph/util.rs b/src/euph/util.rs index 77212ae..4769c7b 100644 --- a/src/euph/util.rs +++ b/src/euph/util.rs @@ -38,7 +38,7 @@ fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (u8, u8, u8) { } pub fn nick_color(nick: &str) -> (u8, u8, u8) { - let hue = euphoxide::nick_hue(&EMOJI, nick) as f32; + let hue = euphoxide::nick::hue(&EMOJI, nick) as f32; hsl_to_rgb(hue, 1.0, 0.72) } From 55cc8a5d09b5c02c94ea350e7b10874565a30695 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 11 Feb 2023 21:15:33 +0100 Subject: [PATCH 027/266] Update dependencies --- Cargo.lock | 261 +++++++++++++++++++++++++++++++++++------------------ Cargo.toml | 22 ++--- 2 files changed, 183 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 423c010..c95586d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,15 +24,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "async-trait" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" +checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote", @@ -74,9 +74,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byteorder" @@ -86,9 +86,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "caseless" @@ -102,9 +102,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -114,9 +114,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.1.3" +version = "4.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d93d855ce6a0aa87b8473ef9169482f40abaa2e9e0993024c35c902cbd5920" +checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" dependencies = [ "bitflags", "clap_derive", @@ -151,9 +151,9 @@ dependencies = [ [[package]] name = "cookie" -version = "0.16.2" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" dependencies = [ "time", "version_check", @@ -216,9 +216,9 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +checksum = "77f67c7faacd4db07a939f55d66a983a5355358a1f17d32cc9a8d01d1266b9ce" dependencies = [ "bitflags", "crossterm_winapi", @@ -291,9 +291,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "errno" @@ -318,8 +318,8 @@ dependencies = [ [[package]] name = "euphoxide" -version = "0.2.0" -source = "git+https://github.com/Garmelon/euphoxide.git?rev=f394ddc5a67f53223f977ccc414bb4c949790f6b#f394ddc5a67f53223f977ccc414bb4c949790f6b" +version = "0.3.0" +source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.3.0#c5be90cd60ff1fac78c2063c812d88dd36858481" dependencies = [ "async-trait", "caseless", @@ -374,27 +374,27 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-core", "futures-sink", @@ -445,9 +445,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -458,6 +458,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "http" version = "0.2.8" @@ -485,6 +491,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "instant" version = "0.1.12" @@ -496,24 +512,24 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "is-terminal" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -524,9 +540,9 @@ checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -597,7 +613,16 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.42.0", +] + +[[package]] +name = "nom8" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" +dependencies = [ + "memchr", ] [[package]] @@ -606,7 +631,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -623,7 +648,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" dependencies = [ "pathdiff", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -650,15 +675,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -723,9 +748,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] @@ -847,16 +872,16 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.6" +version = "0.36.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -904,7 +929,7 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -925,9 +950,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.7.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ "bitflags", "core-foundation", @@ -938,9 +963,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", @@ -968,15 +993,24 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", +] + [[package]] name = "sha1" version = "0.10.5" @@ -990,9 +1024,9 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" dependencies = [ "libc", "signal-hook-registry", @@ -1011,9 +1045,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] @@ -1082,9 +1116,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] @@ -1147,15 +1181,15 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.24.1" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg", "bytes", @@ -1168,7 +1202,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1222,17 +1256,42 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.10" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" +checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" dependencies = [ "serde", ] +[[package]] +name = "toml_edit" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6a7712b49e1775fb9a7b998de6635b299237f48b404dde71704f2e0e7f37e5" +dependencies = [ + "indexmap", + "nom8", + "serde", + "serde_spanned", + "toml_datetime", +] + [[package]] name = "toss" version = "0.1.0" -source = "git+https://github.com/Garmelon/toss.git?rev=0a3b193f796bcb5e1a211d0e3c1589565d9848c2#0a3b193f796bcb5e1a211d0e3c1589565d9848c2" +source = "git+https://github.com/Garmelon/toss.git?rev=0d59116012a51516a821991e2969b1cf4779770f#0d59116012a51516a821991e2969b1cf4779770f" dependencies = [ "crossterm", "unicode-linebreak", @@ -1269,9 +1328,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" @@ -1300,9 +1359,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" @@ -1353,9 +1412,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1363,9 +1422,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", @@ -1378,9 +1437,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1388,9 +1447,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", @@ -1401,15 +1460,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", @@ -1427,9 +1486,9 @@ dependencies = [ [[package]] name = "which" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" dependencies = [ "either", "libc", @@ -1482,6 +1541,30 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.1" diff --git a/Cargo.toml b/Cargo.toml index 555cf92..db7ea53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,11 @@ version = "0.5.2" edition = "2021" [dependencies] -anyhow = "1.0.68" -async-trait = "0.1.61" -clap = { version = "4.1.1", features = ["derive", "deprecated"] } -cookie = "0.16.2" -crossterm = "0.25.0" +anyhow = "1.0.69" +async-trait = "0.1.64" +clap = { version = "4.1.4", features = ["derive", "deprecated"] } +cookie = "0.17.0" +crossterm = "0.26.0" directories = "4.0.1" edit = "0.1.4" linkify = "0.9.0" @@ -18,11 +18,11 @@ open = "3.2.0" parking_lot = "0.12.1" rusqlite = { version = "0.28.0", features = ["bundled", "time"] } serde = { version = "1.0.152", features = ["derive"] } -serde_json = "1.0.91" +serde_json = "1.0.93" thiserror = "1.0.38" -tokio = { version = "1.24.1", features = ["full"] } -toml = "0.5.10" -unicode-segmentation = "1.10.0" +tokio = { version = "1.25.0", features = ["full"] } +toml = "0.7.2" +unicode-segmentation = "1.10.1" unicode-width = "0.1.10" [dependencies.time] @@ -35,7 +35,7 @@ features = ["rustls-tls-native-roots"] [dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -rev = "f394ddc5a67f53223f977ccc414bb4c949790f6b" +tag = "v0.3.0" features = ["bot"] # [patch."https://github.com/Garmelon/euphoxide.git"] @@ -43,7 +43,7 @@ features = ["bot"] [dependencies.toss] git = "https://github.com/Garmelon/toss.git" -rev = "0a3b193f796bcb5e1a211d0e3c1589565d9848c2" +rev = "0d59116012a51516a821991e2969b1cf4779770f" # [patch."https://github.com/Garmelon/toss.git"] # toss = { path = "../toss/" } From ba1b8b419c5f8a73bf91095260bb9a1c0b82d1d4 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 12 Feb 2023 00:23:47 +0100 Subject: [PATCH 028/266] Add todo --- src/euph/room.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/euph/room.rs b/src/euph/room.rs index f9cb9b9..8eeb8e5 100644 --- a/src/euph/room.rs +++ b/src/euph/room.rs @@ -1,3 +1,5 @@ +// TODO Stop if room does not exist (e. g. 404) + use std::convert::Infallible; use std::time::Duration; From 0ceaffc608b2401f16e6422a05871a74bd3ff145 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 12 Feb 2023 00:53:50 +0100 Subject: [PATCH 029/266] Add json-stream export format --- CHANGELOG.md | 1 + src/export.rs | 8 ++++++-- src/export/json.rs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 660d36c..e4e4dcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Procedure when bumping the version number: ### Added - `--verbose` flag +- `json-stream` room export format ### Changed - Respect colon-delimited emoji when calculating nick hue diff --git a/src/export.rs b/src/export.rs index c76009c..a2831a4 100644 --- a/src/export.rs +++ b/src/export.rs @@ -12,8 +12,10 @@ use crate::vault::EuphVault; pub enum Format { /// Human-readable tree-structured messages. Text, - /// List of message objects in the same format as the euphoria API uses. + /// Array of message objects in the same format as the euphoria API uses. Json, + /// Message objects in the same format as the euphoria API uses, one per line. + JsonStream, } impl Format { @@ -21,13 +23,14 @@ impl Format { match self { Self::Text => "text", Self::Json => "json", + Self::JsonStream => "json stream", } } fn extension(&self) -> &'static str { match self { Self::Text => "txt", - Self::Json => "json", + Self::Json | Self::JsonStream => "json", } } } @@ -88,6 +91,7 @@ pub async fn export(vault: &EuphVault, mut args: Args) -> anyhow::Result<()> { match args.format { Format::Text => text::export_to_file(&vault, &mut file).await?, Format::Json => json::export_to_file(&vault, &mut file).await?, + Format::JsonStream => json::export_stream_to_file(&vault, &mut file).await?, } file.flush()?; } diff --git a/src/export/json.rs b/src/export/json.rs index 5b9222c..998087e 100644 --- a/src/export/json.rs +++ b/src/export/json.rs @@ -42,3 +42,33 @@ pub async fn export_to_file( Ok(()) } + +pub async fn export_stream_to_file( + vault: &EuphRoomVault, + file: &mut BufWriter, +) -> anyhow::Result<()> { + let mut total = 0; + let mut offset = 0; + loop { + let messages = vault.chunk_at_offset(CHUNK_SIZE, offset).await; + offset += messages.len(); + + if messages.is_empty() { + break; + } + + for message in messages { + serde_json::to_writer(&mut *file, &message)?; // Fancy reborrow! :D + writeln!(file)?; + total += 1; + } + + if total % 100000 == 0 { + println!(" {total} messages"); + } + } + + println!(" {total} messages in total"); + + Ok(()) +} From ca10ca277b1421d75ecdf3a504ca4593dcadd2f6 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 12 Feb 2023 00:54:09 +0100 Subject: [PATCH 030/266] Add option to export to stdout --- CHANGELOG.md | 1 + src/export.rs | 45 ++++++++++++++++++++++++++++++++------------- src/export/json.rs | 15 +++------------ src/export/text.rs | 26 +++++++++++--------------- 4 files changed, 47 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4e4dcb..10bf4a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Procedure when bumping the version number: ### Added - `--verbose` flag - `json-stream` room export format +- Option to export to stdout ### Changed - Respect colon-delimited emoji when calculating nick hue diff --git a/src/export.rs b/src/export.rs index a2831a4..15db9b7 100644 --- a/src/export.rs +++ b/src/export.rs @@ -4,9 +4,9 @@ mod json; mod text; use std::fs::File; -use std::io::{BufWriter, Write}; +use std::io::{self, BufWriter, Write}; -use crate::vault::EuphVault; +use crate::vault::{EuphRoomVault, EuphVault}; #[derive(Debug, Clone, Copy, clap::ValueEnum)] pub enum Format { @@ -57,12 +57,28 @@ pub struct Args { /// If the value ends with a `/`, it is assumed to point to a directory and /// `%r.%e` will be appended. /// + /// If the value is a literal `-`, the export will be written to stdout. To + /// write to a file named `-`, you can use `./-`. + /// /// Must be a valid utf-8 encoded string. #[arg(long, short, default_value_t = Into::into("%r.%e"))] #[arg(verbatim_doc_comment)] out: String, } +async fn export_room( + vault: &EuphRoomVault, + out: &mut W, + format: Format, +) -> anyhow::Result<()> { + match format { + Format::Text => text::export(vault, out).await?, + Format::Json => json::export(vault, out).await?, + Format::JsonStream => json::export_stream(vault, out).await?, + } + Ok(()) +} + pub async fn export(vault: &EuphVault, mut args: Args) -> anyhow::Result<()> { if args.out.ends_with('/') { args.out.push_str("%r.%e"); @@ -79,21 +95,24 @@ pub async fn export(vault: &EuphVault, mut args: Args) -> anyhow::Result<()> { }; if rooms.is_empty() { - println!("No rooms to export"); + eprintln!("No rooms to export"); } for room in rooms { - let out = format_out(&args.out, &room, args.format); - println!("Exporting &{room} as {} to {out}", args.format.name()); - - let vault = vault.room(room); - let mut file = BufWriter::new(File::create(out)?); - match args.format { - Format::Text => text::export_to_file(&vault, &mut file).await?, - Format::Json => json::export_to_file(&vault, &mut file).await?, - Format::JsonStream => json::export_stream_to_file(&vault, &mut file).await?, + if args.out == "-" { + eprintln!("Exporting &{room} as {} to stdout", args.format.name()); + let vault = vault.room(room); + let mut stdout = BufWriter::new(io::stdout()); + export_room(&vault, &mut stdout, args.format).await?; + stdout.flush()?; + } else { + let out = format_out(&args.out, &room, args.format); + eprintln!("Exporting &{room} as {} to {out}", args.format.name()); + let vault = vault.room(room); + let mut file = BufWriter::new(File::create(out)?); + export_room(&vault, &mut file, args.format).await?; + file.flush()?; } - file.flush()?; } Ok(()) diff --git a/src/export/json.rs b/src/export/json.rs index 998087e..6fb3d13 100644 --- a/src/export/json.rs +++ b/src/export/json.rs @@ -1,14 +1,10 @@ -use std::fs::File; -use std::io::{BufWriter, Write}; +use std::io::Write; use crate::vault::EuphRoomVault; const CHUNK_SIZE: usize = 10000; -pub async fn export_to_file( - vault: &EuphRoomVault, - file: &mut BufWriter, -) -> anyhow::Result<()> { +pub async fn export(vault: &EuphRoomVault, file: &mut W) -> anyhow::Result<()> { write!(file, "[")?; let mut total = 0; @@ -39,14 +35,10 @@ pub async fn export_to_file( write!(file, "\n]")?; println!(" {total} messages in total"); - Ok(()) } -pub async fn export_stream_to_file( - vault: &EuphRoomVault, - file: &mut BufWriter, -) -> anyhow::Result<()> { +pub async fn export_stream(vault: &EuphRoomVault, file: &mut W) -> anyhow::Result<()> { let mut total = 0; let mut offset = 0; loop { @@ -69,6 +61,5 @@ pub async fn export_stream_to_file( } println!(" {total} messages in total"); - Ok(()) } diff --git a/src/export/text.rs b/src/export/text.rs index b4b55db..9dd7c6b 100644 --- a/src/export/text.rs +++ b/src/export/text.rs @@ -1,5 +1,4 @@ -use std::fs::File; -use std::io::{BufWriter, Write}; +use std::io::Write; use euphoxide::api::MessageId; use time::format_description::FormatItem; @@ -14,16 +13,13 @@ const TIME_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); const TIME_EMPTY: &str = " "; -pub async fn export_to_file( - vault: &EuphRoomVault, - file: &mut BufWriter, -) -> anyhow::Result<()> { +pub async fn export(vault: &EuphRoomVault, out: &mut W) -> anyhow::Result<()> { let mut exported_trees = 0; let mut exported_msgs = 0; let mut root_id = vault.first_root_id().await; while let Some(some_root_id) = root_id { let tree = vault.tree(some_root_id).await; - write_tree(file, &tree, some_root_id, 0)?; + write_tree(out, &tree, some_root_id, 0)?; root_id = vault.next_root_id(some_root_id).await; exported_trees += 1; @@ -38,8 +34,8 @@ pub async fn export_to_file( Ok(()) } -fn write_tree( - file: &mut BufWriter, +fn write_tree( + out: &mut W, tree: &Tree, id: MessageId, indent: usize, @@ -47,22 +43,22 @@ fn write_tree( let indent_string = "| ".repeat(indent); if let Some(msg) = tree.msg(&id) { - write_msg(file, &indent_string, msg)?; + write_msg(out, &indent_string, msg)?; } else { - write_placeholder(file, &indent_string)?; + write_placeholder(out, &indent_string)?; } if let Some(children) = tree.children(&id) { for child in children { - write_tree(file, tree, *child, indent + 1)?; + write_tree(out, tree, *child, indent + 1)?; } } Ok(()) } -fn write_msg( - file: &mut BufWriter, +fn write_msg( + file: &mut W, indent_string: &str, msg: &SmallMessage, ) -> anyhow::Result<()> { @@ -85,7 +81,7 @@ fn write_msg( Ok(()) } -fn write_placeholder(file: &mut BufWriter, indent_string: &str) -> anyhow::Result<()> { +fn write_placeholder(file: &mut W, indent_string: &str) -> anyhow::Result<()> { writeln!(file, "{TIME_EMPTY} {indent_string}[...]")?; Ok(()) } From 84279d680060e2c41ae65438d427a6746faa9368 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 12 Feb 2023 00:55:44 +0100 Subject: [PATCH 031/266] Print non-export output on stderr --- CHANGELOG.md | 1 + src/config.rs | 2 +- src/export/json.rs | 8 ++++---- src/export/text.rs | 4 ++-- src/main.rs | 12 ++++++------ src/vault.rs | 4 ++-- src/vault/migrate.rs | 2 +- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10bf4a1..df432be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Procedure when bumping the version number: ### Changed - Respect colon-delimited emoji when calculating nick hue - Display colon-delimited emoji in nicks and messages +- Non-export info is now printed to stderr instead of stdout ### Fixed - Mentions not being stopped by `>` diff --git a/src/config.rs b/src/config.rs index c6c98b5..2a8d13f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -49,7 +49,7 @@ impl Config { match toml::from_str(&content) { Ok(config) => config, Err(err) => { - println!("Error loading config file: {err}"); + eprintln!("Error loading config file: {err}"); Self::default() } } diff --git a/src/export/json.rs b/src/export/json.rs index 6fb3d13..8921229 100644 --- a/src/export/json.rs +++ b/src/export/json.rs @@ -28,13 +28,13 @@ pub async fn export(vault: &EuphRoomVault, file: &mut W) -> anyhow::Re } if total % 100000 == 0 { - println!(" {total} messages"); + eprintln!(" {total} messages"); } } write!(file, "\n]")?; - println!(" {total} messages in total"); + eprintln!(" {total} messages in total"); Ok(()) } @@ -56,10 +56,10 @@ pub async fn export_stream(vault: &EuphRoomVault, file: &mut W) -> any } if total % 100000 == 0 { - println!(" {total} messages"); + eprintln!(" {total} messages"); } } - println!(" {total} messages in total"); + eprintln!(" {total} messages in total"); Ok(()) } diff --git a/src/export/text.rs b/src/export/text.rs index 9dd7c6b..0cdd138 100644 --- a/src/export/text.rs +++ b/src/export/text.rs @@ -26,10 +26,10 @@ pub async fn export(vault: &EuphRoomVault, out: &mut W) -> anyhow::Res exported_msgs += tree.len(); if exported_trees % 10000 == 0 { - println!(" {exported_trees} trees, {exported_msgs} messages") + eprintln!(" {exported_trees} trees, {exported_msgs} messages") } } - println!(" {exported_trees} trees, {exported_msgs} messages in total"); + eprintln!(" {exported_trees} trees, {exported_msgs} messages in total"); Ok(()) } diff --git a/src/main.rs b/src/main.rs index 4fb274c..d2886db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -125,7 +125,7 @@ async fn main() -> anyhow::Result<()> { let config_path = args .config .unwrap_or_else(|| dirs.config_dir().join("config.toml")); - println!("Config file: {}", config_path.to_string_lossy()); + eprintln!("Config file: {}", config_path.to_string_lossy()); let mut config = Config::load(&config_path); set_data_dir(&mut config, args.data_dir); set_ephemeral(&mut config, args.ephemeral); @@ -139,7 +139,7 @@ async fn main() -> anyhow::Result<()> { .data_dir .clone() .unwrap_or_else(|| dirs.data_dir().to_path_buf()); - println!("Data dir: {}", data_dir.to_string_lossy()); + eprintln!("Data dir: {}", data_dir.to_string_lossy()); vault::launch(&data_dir.join("vault.db"))? }; @@ -147,12 +147,12 @@ async fn main() -> anyhow::Result<()> { Command::Run => run(logger, logger_rx, config, &vault, args.measure_widths).await?, Command::Export(args) => export::export(&vault.euph(), args).await?, Command::Gc => { - println!("Cleaning up and compacting vault"); - println!("This may take a while..."); + eprintln!("Cleaning up and compacting vault"); + eprintln!("This may take a while..."); vault.gc().await; } Command::ClearCookies => { - println!("Clearing cookies"); + eprintln!("Clearing cookies"); vault.euph().set_cookies(CookieJar::new()); } } @@ -164,7 +164,7 @@ async fn main() -> anyhow::Result<()> { // this, it is not implemented via a normal function call. drop(logger_guard); - println!("Goodbye!"); + eprintln!("Goodbye!"); Ok(()) } diff --git a/src/vault.rs b/src/vault.rs index a490154..9352e7d 100644 --- a/src/vault.rs +++ b/src/vault.rs @@ -50,7 +50,7 @@ fn run(mut conn: Connection, mut rx: mpsc::UnboundedReceiver) { while let Some(request) = rx.blocking_recv() { match request { Request::Close(tx) => { - println!("Closing vault"); + eprintln!("Closing vault"); if let Err(e) = conn.execute_batch("PRAGMA optimize") { error!("{e}"); } @@ -79,7 +79,7 @@ fn launch_from_connection(mut conn: Connection, ephemeral: bool) -> rusqlite::Re conn.pragma_update(None, "foreign_keys", true)?; conn.pragma_update(None, "trusted_schema", false)?; - println!("Opening vault"); + eprintln!("Opening vault"); migrate::migrate(&mut conn)?; prepare::prepare(&mut conn)?; diff --git a/src/vault/migrate.rs b/src/vault/migrate.rs index cbb4f6b..8cd42c6 100644 --- a/src/vault/migrate.rs +++ b/src/vault/migrate.rs @@ -9,7 +9,7 @@ pub fn migrate(conn: &mut Connection) -> rusqlite::Result<()> { let total = MIGRATIONS.len(); assert!(user_version <= total, "malformed database schema"); for (i, migration) in MIGRATIONS.iter().enumerate().skip(user_version) { - println!("Migrating vault from {} to {} (out of {})", i, i + 1, total); + eprintln!("Migrating vault from {} to {} (out of {})", i, i + 1, total); migration(&mut tx)?; } From 8bd58417dd32a4fe6aa700e9073b0ce096859a7e Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 12 Feb 2023 20:15:10 +0100 Subject: [PATCH 032/266] Fix import grouping --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index d2886db..1c6071b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,11 +30,11 @@ use directories::{BaseDirs, ProjectDirs}; use log::info; use tokio::sync::mpsc; use toss::terminal::Terminal; -use ui::Ui; -use vault::Vault; use crate::config::Config; use crate::logger::Logger; +use crate::ui::Ui; +use crate::vault::Vault; #[derive(Debug, clap::Parser)] enum Command { From 5581fc1fc243bca7f7be2781d3f380c8dfc1e090 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 12 Feb 2023 20:15:47 +0100 Subject: [PATCH 033/266] Add vscode settings --- .vscode/settings.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a89179 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.insertFinalNewline": true, + "rust-analyzer.cargo.features": "all", + "rust-analyzer.imports.granularity.enforce": true, + "rust-analyzer.imports.granularity.group": "module", + "rust-analyzer.imports.group.enable": true, + "evenBetterToml.formatter.columnWidth": 100, +} From 35a140e21fececd5ca3b76463f51412a475629c1 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 12 Feb 2023 21:08:38 +0100 Subject: [PATCH 034/266] Make MsgStore fallible --- src/logger.rs | 67 ++++++++------- src/store.rs | 37 ++++---- src/ui.rs | 20 +++-- src/ui/chat.rs | 5 +- src/ui/chat/tree.rs | 103 +++++++++++++---------- src/ui/chat/tree/cursor.rs | 168 +++++++++++++++++++++---------------- src/ui/chat/tree/layout.rs | 82 +++++++++--------- src/ui/euph/room.rs | 11 ++- src/vault/euph.rs | 73 ++++++++-------- 9 files changed, 324 insertions(+), 242 deletions(-) diff --git a/src/logger.rs b/src/logger.rs index 692c0b6..c80d794 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,3 +1,4 @@ +use std::convert::Infallible; use std::sync::Arc; use std::vec; @@ -100,81 +101,87 @@ pub struct Logger { #[async_trait] impl MsgStore for Logger { - async fn path(&self, id: &usize) -> Path { - Path::new(vec![*id]) + type Error = Infallible; + + async fn path(&self, id: &usize) -> Result, Self::Error> { + Ok(Path::new(vec![*id])) } - async fn msg(&self, id: &usize) -> Option { - self.messages.lock().get(*id).cloned() + async fn msg(&self, id: &usize) -> Result, Self::Error> { + Ok(self.messages.lock().get(*id).cloned()) } - async fn tree(&self, root_id: &usize) -> Tree { + async fn tree(&self, root_id: &usize) -> Result, Self::Error> { let msgs = self .messages .lock() .get(*root_id) .map(|msg| vec![msg.clone()]) .unwrap_or_default(); - Tree::new(*root_id, msgs) + Ok(Tree::new(*root_id, msgs)) } - async fn first_root_id(&self) -> Option { + async fn first_root_id(&self) -> Result, Self::Error> { let empty = self.messages.lock().is_empty(); - Some(0).filter(|_| !empty) + Ok(Some(0).filter(|_| !empty)) } - async fn last_root_id(&self) -> Option { - self.messages.lock().len().checked_sub(1) + async fn last_root_id(&self) -> Result, Self::Error> { + Ok(self.messages.lock().len().checked_sub(1)) } - async fn prev_root_id(&self, root_id: &usize) -> Option { - root_id.checked_sub(1) + async fn prev_root_id(&self, root_id: &usize) -> Result, Self::Error> { + Ok(root_id.checked_sub(1)) } - async fn next_root_id(&self, root_id: &usize) -> Option { + async fn next_root_id(&self, root_id: &usize) -> Result, Self::Error> { let len = self.messages.lock().len(); - root_id.checked_add(1).filter(|t| *t < len) + Ok(root_id.checked_add(1).filter(|t| *t < len)) } - async fn oldest_msg_id(&self) -> Option { + async fn oldest_msg_id(&self) -> Result, Self::Error> { self.first_root_id().await } - async fn newest_msg_id(&self) -> Option { + async fn newest_msg_id(&self) -> Result, Self::Error> { self.last_root_id().await } - async fn older_msg_id(&self, id: &usize) -> Option { + async fn older_msg_id(&self, id: &usize) -> Result, Self::Error> { self.prev_root_id(id).await } - async fn newer_msg_id(&self, id: &usize) -> Option { + async fn newer_msg_id(&self, id: &usize) -> Result, Self::Error> { self.next_root_id(id).await } - async fn oldest_unseen_msg_id(&self) -> Option { - None + async fn oldest_unseen_msg_id(&self) -> Result, Self::Error> { + Ok(None) } - async fn newest_unseen_msg_id(&self) -> Option { - None + async fn newest_unseen_msg_id(&self) -> Result, Self::Error> { + Ok(None) } - async fn older_unseen_msg_id(&self, _id: &usize) -> Option { - None + async fn older_unseen_msg_id(&self, _id: &usize) -> Result, Self::Error> { + Ok(None) } - async fn newer_unseen_msg_id(&self, _id: &usize) -> Option { - None + async fn newer_unseen_msg_id(&self, _id: &usize) -> Result, Self::Error> { + Ok(None) } - async fn unseen_msgs_count(&self) -> usize { - 0 + async fn unseen_msgs_count(&self) -> Result { + Ok(0) } - async fn set_seen(&self, _id: &usize, _seen: bool) {} + async fn set_seen(&self, _id: &usize, _seen: bool) -> Result<(), Self::Error> { + Ok(()) + } - async fn set_older_seen(&self, _id: &usize, _seen: bool) {} + async fn set_older_seen(&self, _id: &usize, _seen: bool) -> Result<(), Self::Error> { + Ok(()) + } } impl Log for Logger { diff --git a/src/store.rs b/src/store.rs index 9625c74..d762752 100644 --- a/src/store.rs +++ b/src/store.rs @@ -132,22 +132,23 @@ impl Tree { #[async_trait] pub trait MsgStore { - async fn path(&self, id: &M::Id) -> Path; - async fn msg(&self, id: &M::Id) -> Option; - async fn tree(&self, root_id: &M::Id) -> Tree; - async fn first_root_id(&self) -> Option; - async fn last_root_id(&self) -> Option; - async fn prev_root_id(&self, root_id: &M::Id) -> Option; - async fn next_root_id(&self, root_id: &M::Id) -> Option; - async fn oldest_msg_id(&self) -> Option; - async fn newest_msg_id(&self) -> Option; - async fn older_msg_id(&self, id: &M::Id) -> Option; - async fn newer_msg_id(&self, id: &M::Id) -> Option; - async fn oldest_unseen_msg_id(&self) -> Option; - async fn newest_unseen_msg_id(&self) -> Option; - async fn older_unseen_msg_id(&self, id: &M::Id) -> Option; - async fn newer_unseen_msg_id(&self, id: &M::Id) -> Option; - async fn unseen_msgs_count(&self) -> usize; - async fn set_seen(&self, id: &M::Id, seen: bool); - async fn set_older_seen(&self, id: &M::Id, seen: bool); + type Error; + async fn path(&self, id: &M::Id) -> Result, Self::Error>; + async fn msg(&self, id: &M::Id) -> Result, Self::Error>; + async fn tree(&self, root_id: &M::Id) -> Result, Self::Error>; + async fn first_root_id(&self) -> Result, Self::Error>; + async fn last_root_id(&self) -> Result, Self::Error>; + async fn prev_root_id(&self, root_id: &M::Id) -> Result, Self::Error>; + async fn next_root_id(&self, root_id: &M::Id) -> Result, Self::Error>; + async fn oldest_msg_id(&self) -> Result, Self::Error>; + async fn newest_msg_id(&self) -> Result, Self::Error>; + async fn older_msg_id(&self, id: &M::Id) -> Result, Self::Error>; + async fn newer_msg_id(&self, id: &M::Id) -> Result, Self::Error>; + async fn oldest_unseen_msg_id(&self) -> Result, Self::Error>; + async fn newest_unseen_msg_id(&self) -> Result, Self::Error>; + async fn older_unseen_msg_id(&self, id: &M::Id) -> Result, Self::Error>; + async fn newer_unseen_msg_id(&self, id: &M::Id) -> Result, Self::Error>; + async fn unseen_msgs_count(&self) -> Result; + async fn set_seen(&self, id: &M::Id, seen: bool) -> Result<(), Self::Error>; + async fn set_older_seen(&self, id: &M::Id, seen: bool) -> Result<(), Self::Error>; } diff --git a/src/ui.rs b/src/ui.rs index a24061e..8ff3e13 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -10,6 +10,7 @@ use std::io; use std::sync::{Arc, Weak}; use std::time::{Duration, Instant}; +use log::error; use parking_lot::FairMutex; use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; @@ -286,11 +287,20 @@ impl Ui { .handle_input_event(terminal, crossterm_lock, &event) .await } - Mode::Log => self - .log_chat - .handle_input_event(terminal, crossterm_lock, &event, false) - .await - .handled(), + Mode::Log => { + let reaction = match self + .log_chat + .handle_input_event(terminal, crossterm_lock, &event, false) + .await + { + Ok(reaction) => reaction, + Err(err) => { + error!("{err}"); + panic!("{err}"); + } + }; + reaction.handled() + } }; // Pressing '?' should only open the key bindings list if it doesn't diff --git a/src/ui/chat.rs b/src/ui/chat.rs index 7e7021c..124d95d 100644 --- a/src/ui/chat.rs +++ b/src/ui/chat.rs @@ -5,8 +5,8 @@ mod blocks; mod tree; -use std::io; use std::sync::Arc; +use std::{fmt, io}; use async_trait::async_trait; use parking_lot::FairMutex; @@ -102,7 +102,7 @@ impl> ChatState { crossterm_lock: &Arc>, event: &InputEvent, can_compose: bool, - ) -> Reaction { + ) -> Result, S::Error> { match self.mode { Mode::Tree => { self.tree @@ -144,6 +144,7 @@ where M: Msg + ChatMsg, M::Id: Send + Sync, S: MsgStore + Send + Sync, + S::Error: fmt::Display, { fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { match self { diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs index 18942d4..9325d3e 100644 --- a/src/ui/chat/tree.rs +++ b/src/ui/chat/tree.rs @@ -6,9 +6,11 @@ mod tree_blocks; mod widgets; use std::collections::HashSet; +use std::fmt; use std::sync::Arc; use async_trait::async_trait; +use log::error; use parking_lot::FairMutex; use tokio::sync::Mutex; use toss::frame::{Frame, Pos, Size}; @@ -82,21 +84,25 @@ impl> InnerTreeViewState { // TODO Bindings inspired by vim's ()/[]/{} bindings? } - async fn handle_movement_input_event(&mut self, frame: &mut Frame, event: &InputEvent) -> bool { + async fn handle_movement_input_event( + &mut self, + frame: &mut Frame, + event: &InputEvent, + ) -> Result { let chat_height = frame.size().height - 3; match event { - key!('k') | key!(Up) => self.move_cursor_up().await, - key!('j') | key!(Down) => self.move_cursor_down().await, - key!('K') | key!(Ctrl + Up) => self.move_cursor_up_sibling().await, - key!('J') | key!(Ctrl + Down) => self.move_cursor_down_sibling().await, - key!('p') => self.move_cursor_to_parent().await, - key!('P') => self.move_cursor_to_root().await, - key!('h') | key!(Left) => self.move_cursor_older().await, - key!('l') | key!(Right) => self.move_cursor_newer().await, - key!('H') | key!(Ctrl + Left) => self.move_cursor_older_unseen().await, - key!('L') | key!(Ctrl + Right) => self.move_cursor_newer_unseen().await, - key!('g') | key!(Home) => self.move_cursor_to_top().await, + key!('k') | key!(Up) => self.move_cursor_up().await?, + key!('j') | key!(Down) => self.move_cursor_down().await?, + key!('K') | key!(Ctrl + Up) => self.move_cursor_up_sibling().await?, + key!('J') | key!(Ctrl + Down) => self.move_cursor_down_sibling().await?, + key!('p') => self.move_cursor_to_parent().await?, + key!('P') => self.move_cursor_to_root().await?, + key!('h') | key!(Left) => self.move_cursor_older().await?, + key!('l') | key!(Right) => self.move_cursor_newer().await?, + key!('H') | key!(Ctrl + Left) => self.move_cursor_older_unseen().await?, + key!('L') | key!(Ctrl + Right) => self.move_cursor_newer_unseen().await?, + key!('g') | key!(Home) => self.move_cursor_to_top().await?, key!('G') | key!(End) => self.move_cursor_to_bottom().await, key!(Ctrl + 'y') => self.scroll_up(1), key!(Ctrl + 'e') => self.scroll_down(1), @@ -107,10 +113,10 @@ impl> InnerTreeViewState { self.scroll_down(chat_height.saturating_sub(1).into()) } key!('z') => self.center_cursor(), - _ => return false, + _ => return Ok(false), } - true + Ok(true) } pub fn list_action_key_bindings(&self, bindings: &mut KeyBindingsList) { @@ -120,43 +126,47 @@ impl> InnerTreeViewState { bindings.binding("ctrl+s", "mark all older messages as seen"); } - async fn handle_action_input_event(&mut self, event: &InputEvent, id: Option<&M::Id>) -> bool { + async fn handle_action_input_event( + &mut self, + event: &InputEvent, + id: Option<&M::Id>, + ) -> Result { match event { key!(' ') => { if let Some(id) = id { if !self.folded.remove(id) { self.folded.insert(id.clone()); } - return true; + return Ok(true); } } key!('s') => { if let Some(id) = id { - if let Some(msg) = self.store.tree(id).await.msg(id) { - self.store.set_seen(id, !msg.seen()).await; + if let Some(msg) = self.store.tree(id).await?.msg(id) { + self.store.set_seen(id, !msg.seen()).await?; } - return true; + return Ok(true); } } key!('S') => { for id in &self.last_visible_msgs { - self.store.set_seen(id, true).await; + self.store.set_seen(id, true).await?; } - return true; + return Ok(true); } key!(Ctrl + 's') => { if let Some(id) = id { - self.store.set_older_seen(id, true).await; + self.store.set_older_seen(id, true).await?; } else { self.store .set_older_seen(&M::last_possible_id(), true) - .await; + .await?; } - return true; + return Ok(true); } _ => {} } - false + Ok(false) } pub fn list_edit_initiating_key_bindings(&self, bindings: &mut KeyBindingsList) { @@ -169,16 +179,16 @@ impl> InnerTreeViewState { &mut self, event: &InputEvent, id: Option, - ) -> bool { + ) -> Result { match event { key!('r') => { - if let Some(parent) = self.parent_for_normal_reply().await { + if let Some(parent) = self.parent_for_normal_reply().await? { self.cursor = Cursor::editor(id, parent); self.correction = Some(Correction::MakeCursorVisible); } } key!('R') => { - if let Some(parent) = self.parent_for_alternate_reply().await { + if let Some(parent) = self.parent_for_alternate_reply().await? { self.cursor = Cursor::editor(id, parent); self.correction = Some(Correction::MakeCursorVisible); } @@ -187,10 +197,10 @@ impl> InnerTreeViewState { self.cursor = Cursor::editor(id, None); self.correction = Some(Correction::MakeCursorVisible); } - _ => return false, + _ => return Ok(false), } - true + Ok(true) } pub fn list_normal_key_bindings(&self, bindings: &mut KeyBindingsList, can_compose: bool) { @@ -209,17 +219,17 @@ impl> InnerTreeViewState { event: &InputEvent, can_compose: bool, id: Option, - ) -> bool { + ) -> Result { #[allow(clippy::if_same_then_else)] - if self.handle_movement_input_event(frame, event).await { + Ok(if self.handle_movement_input_event(frame, event).await? { true - } else if self.handle_action_input_event(event, id.as_ref()).await { + } else if self.handle_action_input_event(event, id.as_ref()).await? { true } else if can_compose { - self.handle_edit_initiating_input_event(event, id).await + self.handle_edit_initiating_input_event(event, id).await? } else { false - } + }) } fn list_editor_key_bindings(&self, bindings: &mut KeyBindingsList) { @@ -294,12 +304,12 @@ impl> InnerTreeViewState { crossterm_lock: &Arc>, event: &InputEvent, can_compose: bool, - ) -> Reaction { - match &self.cursor { + ) -> Result, S::Error> { + Ok(match &self.cursor { Cursor::Bottom => { if self .handle_normal_input_event(terminal.frame(), event, can_compose, None) - .await + .await? { Reaction::Handled } else { @@ -310,7 +320,7 @@ impl> InnerTreeViewState { let id = id.clone(); if self .handle_normal_input_event(terminal.frame(), event, can_compose, Some(id)) - .await + .await? { Reaction::Handled } else { @@ -330,14 +340,14 @@ impl> InnerTreeViewState { Cursor::Pseudo { .. } => { if self .handle_movement_input_event(terminal.frame(), event) - .await + .await? { Reaction::Handled } else { Reaction::NotHandled } } - } + }) } fn cursor(&self) -> Option { @@ -388,7 +398,7 @@ impl> TreeViewState { crossterm_lock: &Arc>, event: &InputEvent, can_compose: bool, - ) -> Reaction { + ) -> Result, S::Error> { self.0 .lock() .await @@ -421,6 +431,7 @@ where M: Msg + ChatMsg, M::Id: Send + Sync, S: MsgStore + Send + Sync, + S::Error: fmt::Display, { fn size(&self, _frame: &mut Frame, _max_width: Option, _max_height: Option) -> Size { Size::ZERO @@ -428,7 +439,13 @@ where async fn render(self: Box, frame: &mut Frame) { let mut guard = self.inner.lock().await; - let blocks = guard.relayout(self.nick, self.focused, frame).await; + let blocks = match guard.relayout(self.nick, self.focused, frame).await { + Ok(blocks) => blocks, + Err(err) => { + error!("{err}"); + panic!("{err}"); + } + }; let size = frame.size(); for block in blocks.into_blocks().blocks { diff --git a/src/ui/chat/tree/cursor.rs b/src/ui/chat/tree/cursor.rs index bd303a0..e154708 100644 --- a/src/ui/chat/tree/cursor.rs +++ b/src/ui/chat/tree/cursor.rs @@ -94,15 +94,19 @@ impl> InnerTreeViewState { /// Move to the previous sibling, or don't move if this is not possible. /// /// Always stays at the same level of indentation. - async fn find_prev_sibling(store: &S, tree: &mut Tree, id: &mut M::Id) -> bool { - if let Some(prev_sibling) = tree.prev_sibling(id) { + async fn find_prev_sibling( + store: &S, + tree: &mut Tree, + id: &mut M::Id, + ) -> Result { + let moved = if let Some(prev_sibling) = tree.prev_sibling(id) { *id = prev_sibling; true } else if tree.parent(id).is_none() { // We're at the root of our tree, so we need to move to the root of // the previous tree. - if let Some(prev_root_id) = store.prev_root_id(tree.root()).await { - *tree = store.tree(&prev_root_id).await; + if let Some(prev_root_id) = store.prev_root_id(tree.root()).await? { + *tree = store.tree(&prev_root_id).await?; *id = prev_root_id; true } else { @@ -110,21 +114,26 @@ impl> InnerTreeViewState { } } else { false - } + }; + Ok(moved) } /// Move to the next sibling, or don't move if this is not possible. /// /// Always stays at the same level of indentation. - async fn find_next_sibling(store: &S, tree: &mut Tree, id: &mut M::Id) -> bool { - if let Some(next_sibling) = tree.next_sibling(id) { + async fn find_next_sibling( + store: &S, + tree: &mut Tree, + id: &mut M::Id, + ) -> Result { + let moved = if let Some(next_sibling) = tree.next_sibling(id) { *id = next_sibling; true } else if tree.parent(id).is_none() { // We're at the root of our tree, so we need to move to the root of // the next tree. - if let Some(next_root_id) = store.next_root_id(tree.root()).await { - *tree = store.tree(&next_root_id).await; + if let Some(next_root_id) = store.next_root_id(tree.root()).await? { + *tree = store.tree(&next_root_id).await?; *id = next_root_id; true } else { @@ -132,7 +141,8 @@ impl> InnerTreeViewState { } } else { false - } + }; + Ok(moved) } /// Move to the previous message, or don't move if this is not possible. @@ -141,15 +151,16 @@ impl> InnerTreeViewState { folded: &HashSet, tree: &mut Tree, id: &mut M::Id, - ) -> bool { + ) -> Result { // Move to previous sibling, then to its last child // If not possible, move to parent - if Self::find_prev_sibling(store, tree, id).await { + let moved = if Self::find_prev_sibling(store, tree, id).await? { while Self::find_last_child(folded, tree, id) {} true } else { Self::find_parent(tree, id) - } + }; + Ok(moved) } /// Move to the next message, or don't move if this is not possible. @@ -158,63 +169,64 @@ impl> InnerTreeViewState { folded: &HashSet, tree: &mut Tree, id: &mut M::Id, - ) -> bool { + ) -> Result { if Self::find_first_child(folded, tree, id) { - return true; + return Ok(true); } - if Self::find_next_sibling(store, tree, id).await { - return true; + if Self::find_next_sibling(store, tree, id).await? { + return Ok(true); } // Temporary id to avoid modifying the original one if no parent-sibling // can be found. let mut tmp_id = id.clone(); while Self::find_parent(tree, &mut tmp_id) { - if Self::find_next_sibling(store, tree, &mut tmp_id).await { + if Self::find_next_sibling(store, tree, &mut tmp_id).await? { *id = tmp_id; - return true; + return Ok(true); } } - false + Ok(false) } - pub async fn move_cursor_up(&mut self) { + pub async fn move_cursor_up(&mut self) -> Result<(), S::Error> { match &mut self.cursor { Cursor::Bottom | Cursor::Pseudo { parent: None, .. } => { - if let Some(last_root_id) = self.store.last_root_id().await { - let tree = self.store.tree(&last_root_id).await; + if let Some(last_root_id) = self.store.last_root_id().await? { + let tree = self.store.tree(&last_root_id).await?; let mut id = last_root_id; while Self::find_last_child(&self.folded, &tree, &mut id) {} self.cursor = Cursor::Msg(id); } } Cursor::Msg(msg) => { - let path = self.store.path(msg).await; - let mut tree = self.store.tree(path.first()).await; - Self::find_prev_msg(&self.store, &self.folded, &mut tree, msg).await; + let path = self.store.path(msg).await?; + let mut tree = self.store.tree(path.first()).await?; + Self::find_prev_msg(&self.store, &self.folded, &mut tree, msg).await?; } Cursor::Editor { .. } => {} Cursor::Pseudo { parent: Some(parent), .. } => { - let tree = self.store.tree(parent).await; + let tree = self.store.tree(parent).await?; let mut id = parent.clone(); while Self::find_last_child(&self.folded, &tree, &mut id) {} self.cursor = Cursor::Msg(id); } } self.correction = Some(Correction::MakeCursorVisible); + Ok(()) } - pub async fn move_cursor_down(&mut self) { + pub async fn move_cursor_down(&mut self) -> Result<(), S::Error> { match &mut self.cursor { Cursor::Msg(msg) => { - let path = self.store.path(msg).await; - let mut tree = self.store.tree(path.first()).await; - if !Self::find_next_msg(&self.store, &self.folded, &mut tree, msg).await { + let path = self.store.path(msg).await?; + let mut tree = self.store.tree(path.first()).await?; + if !Self::find_next_msg(&self.store, &self.folded, &mut tree, msg).await? { self.cursor = Cursor::Bottom; } } @@ -225,11 +237,11 @@ impl> InnerTreeViewState { parent: Some(parent), .. } => { - let mut tree = self.store.tree(parent).await; + let mut tree = self.store.tree(parent).await?; let mut id = parent.clone(); while Self::find_last_child(&self.folded, &tree, &mut id) {} // Now we're at the previous message - if Self::find_next_msg(&self.store, &self.folded, &mut tree, &mut id).await { + if Self::find_next_msg(&self.store, &self.folded, &mut tree, &mut id).await? { self.cursor = Cursor::Msg(id); } else { self.cursor = Cursor::Bottom; @@ -238,27 +250,28 @@ impl> InnerTreeViewState { _ => {} } self.correction = Some(Correction::MakeCursorVisible); + Ok(()) } - pub async fn move_cursor_up_sibling(&mut self) { + pub async fn move_cursor_up_sibling(&mut self) -> Result<(), S::Error> { match &mut self.cursor { Cursor::Bottom | Cursor::Pseudo { parent: None, .. } => { - if let Some(last_root_id) = self.store.last_root_id().await { + if let Some(last_root_id) = self.store.last_root_id().await? { self.cursor = Cursor::Msg(last_root_id); } } Cursor::Msg(msg) => { - let path = self.store.path(msg).await; - let mut tree = self.store.tree(path.first()).await; - Self::find_prev_sibling(&self.store, &mut tree, msg).await; + let path = self.store.path(msg).await?; + let mut tree = self.store.tree(path.first()).await?; + Self::find_prev_sibling(&self.store, &mut tree, msg).await?; } Cursor::Editor { .. } => {} Cursor::Pseudo { parent: Some(parent), .. } => { - let path = self.store.path(parent).await; - let tree = self.store.tree(path.first()).await; + let path = self.store.path(parent).await?; + let tree = self.store.tree(path.first()).await?; if let Some(children) = tree.children(parent) { if let Some(last_child) = children.last() { self.cursor = Cursor::Msg(last_child.clone()); @@ -267,14 +280,15 @@ impl> InnerTreeViewState { } } self.correction = Some(Correction::MakeCursorVisible); + Ok(()) } - pub async fn move_cursor_down_sibling(&mut self) { + pub async fn move_cursor_down_sibling(&mut self) -> Result<(), S::Error> { match &mut self.cursor { Cursor::Msg(msg) => { - let path = self.store.path(msg).await; - let mut tree = self.store.tree(path.first()).await; - if !Self::find_next_sibling(&self.store, &mut tree, msg).await + let path = self.store.path(msg).await?; + let mut tree = self.store.tree(path.first()).await?; + if !Self::find_next_sibling(&self.store, &mut tree, msg).await? && tree.parent(msg).is_none() { self.cursor = Cursor::Bottom; @@ -286,9 +300,10 @@ impl> InnerTreeViewState { _ => {} } self.correction = Some(Correction::MakeCursorVisible); + Ok(()) } - pub async fn move_cursor_to_parent(&mut self) { + pub async fn move_cursor_to_parent(&mut self) -> Result<(), S::Error> { match &mut self.cursor { Cursor::Pseudo { parent: Some(parent), @@ -297,53 +312,56 @@ impl> InnerTreeViewState { Cursor::Msg(id) => { // Could also be done via retrieving the path, but it doesn't // really matter here - let tree = self.store.tree(id).await; + let tree = self.store.tree(id).await?; Self::find_parent(&tree, id); } _ => {} } self.correction = Some(Correction::MakeCursorVisible); + Ok(()) } - pub async fn move_cursor_to_root(&mut self) { + pub async fn move_cursor_to_root(&mut self) -> Result<(), S::Error> { match &mut self.cursor { Cursor::Pseudo { parent: Some(parent), .. } => { - let path = self.store.path(parent).await; + let path = self.store.path(parent).await?; self.cursor = Cursor::Msg(path.first().clone()); } Cursor::Msg(msg) => { - let path = self.store.path(msg).await; + let path = self.store.path(msg).await?; *msg = path.first().clone(); } _ => {} } self.correction = Some(Correction::MakeCursorVisible); + Ok(()) } - pub async fn move_cursor_older(&mut self) { + pub async fn move_cursor_older(&mut self) -> Result<(), S::Error> { match &mut self.cursor { Cursor::Msg(id) => { - if let Some(prev_id) = self.store.older_msg_id(id).await { + if let Some(prev_id) = self.store.older_msg_id(id).await? { *id = prev_id; } } Cursor::Bottom | Cursor::Pseudo { .. } => { - if let Some(id) = self.store.newest_msg_id().await { + if let Some(id) = self.store.newest_msg_id().await? { self.cursor = Cursor::Msg(id); } } _ => {} } self.correction = Some(Correction::MakeCursorVisible); + Ok(()) } - pub async fn move_cursor_newer(&mut self) { + pub async fn move_cursor_newer(&mut self) -> Result<(), S::Error> { match &mut self.cursor { Cursor::Msg(id) => { - if let Some(prev_id) = self.store.newer_msg_id(id).await { + if let Some(prev_id) = self.store.newer_msg_id(id).await? { *id = prev_id; } else { self.cursor = Cursor::Bottom; @@ -355,29 +373,31 @@ impl> InnerTreeViewState { _ => {} } self.correction = Some(Correction::MakeCursorVisible); + Ok(()) } - pub async fn move_cursor_older_unseen(&mut self) { + pub async fn move_cursor_older_unseen(&mut self) -> Result<(), S::Error> { match &mut self.cursor { Cursor::Msg(id) => { - if let Some(prev_id) = self.store.older_unseen_msg_id(id).await { + if let Some(prev_id) = self.store.older_unseen_msg_id(id).await? { *id = prev_id; } } Cursor::Bottom | Cursor::Pseudo { .. } => { - if let Some(id) = self.store.newest_unseen_msg_id().await { + if let Some(id) = self.store.newest_unseen_msg_id().await? { self.cursor = Cursor::Msg(id); } } _ => {} } self.correction = Some(Correction::MakeCursorVisible); + Ok(()) } - pub async fn move_cursor_newer_unseen(&mut self) { + pub async fn move_cursor_newer_unseen(&mut self) -> Result<(), S::Error> { match &mut self.cursor { Cursor::Msg(id) => { - if let Some(prev_id) = self.store.newer_unseen_msg_id(id).await { + if let Some(prev_id) = self.store.newer_unseen_msg_id(id).await? { *id = prev_id; } else { self.cursor = Cursor::Bottom; @@ -389,13 +409,15 @@ impl> InnerTreeViewState { _ => {} } self.correction = Some(Correction::MakeCursorVisible); + Ok(()) } - pub async fn move_cursor_to_top(&mut self) { - if let Some(first_root_id) = self.store.first_root_id().await { + pub async fn move_cursor_to_top(&mut self) -> Result<(), S::Error> { + if let Some(first_root_id) = self.store.first_root_id().await? { self.cursor = Cursor::Msg(first_root_id); self.correction = Some(Correction::MakeCursorVisible); } + Ok(()) } pub async fn move_cursor_to_bottom(&mut self) { @@ -418,12 +440,14 @@ impl> InnerTreeViewState { self.correction = Some(Correction::CenterCursor); } - pub async fn parent_for_normal_reply(&self) -> Option> { - match &self.cursor { + /// The outer `Option` shows whether a parent exists or not. The inner + /// `Option` shows if that parent has an id. + pub async fn parent_for_normal_reply(&self) -> Result>, S::Error> { + Ok(match &self.cursor { Cursor::Bottom => Some(None), Cursor::Msg(id) => { - let path = self.store.path(id).await; - let tree = self.store.tree(path.first()).await; + let path = self.store.path(id).await?; + let tree = self.store.tree(path.first()).await?; Some(Some(if tree.next_sibling(id).is_some() { // A reply to a message that has further siblings should be a @@ -444,15 +468,17 @@ impl> InnerTreeViewState { })) } _ => None, - } + }) } - pub async fn parent_for_alternate_reply(&self) -> Option> { - match &self.cursor { + /// The outer `Option` shows whether a parent exists or not. The inner + /// `Option` shows if that parent has an id. + pub async fn parent_for_alternate_reply(&self) -> Result>, S::Error> { + Ok(match &self.cursor { Cursor::Bottom => Some(None), Cursor::Msg(id) => { - let path = self.store.path(id).await; - let tree = self.store.tree(path.first()).await; + let path = self.store.path(id).await?; + let tree = self.store.tree(path.first()).await?; Some(Some(if tree.next_sibling(id).is_none() { // The opposite of replying normally @@ -467,6 +493,6 @@ impl> InnerTreeViewState { })) } _ => None, - } + }) } } diff --git a/src/ui/chat/tree/layout.rs b/src/ui/chat/tree/layout.rs index bc6f4de..baccfbe 100644 --- a/src/ui/chat/tree/layout.rs +++ b/src/ui/chat/tree/layout.rs @@ -22,9 +22,9 @@ struct Context { } impl> InnerTreeViewState { - async fn cursor_path(&self, cursor: &Cursor) -> Path { - match cursor { - Cursor::Msg(id) => self.store.path(id).await, + async fn cursor_path(&self, cursor: &Cursor) -> Result, S::Error> { + Ok(match cursor { + Cursor::Msg(id) => self.store.path(id).await?, Cursor::Bottom | Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } => Path::new(vec![M::last_possible_id()]), @@ -36,11 +36,11 @@ impl> InnerTreeViewState { parent: Some(parent), .. } => { - let mut path = self.store.path(parent).await; + let mut path = self.store.path(parent).await?; path.push(M::last_possible_id()); path } - } + }) } fn make_path_visible(&mut self, path: &Path) { @@ -202,22 +202,24 @@ impl> InnerTreeViewState { context: &Context, frame: &mut Frame, blocks: &mut TreeBlocks, - ) { + ) -> Result<(), S::Error> { let top_line = 0; while blocks.blocks().top_line > top_line { let top_root = blocks.top_root(); let prev_root_id = match top_root { - Root::Bottom => self.store.last_root_id().await, - Root::Tree(root_id) => self.store.prev_root_id(root_id).await, + Root::Bottom => self.store.last_root_id().await?, + Root::Tree(root_id) => self.store.prev_root_id(root_id).await?, }; let prev_root_id = match prev_root_id { Some(id) => id, None => break, }; - let prev_tree = self.store.tree(&prev_root_id).await; + let prev_tree = self.store.tree(&prev_root_id).await?; blocks.prepend(self.layout_tree(context, frame, prev_tree)); } + + Ok(()) } async fn expand_to_bottom( @@ -225,22 +227,24 @@ impl> InnerTreeViewState { context: &Context, frame: &mut Frame, blocks: &mut TreeBlocks, - ) { + ) -> Result<(), S::Error> { let bottom_line = frame.size().height as i32 - 1; while blocks.blocks().bottom_line < bottom_line { let bottom_root = blocks.bottom_root(); let next_root_id = match bottom_root { Root::Bottom => break, - Root::Tree(root_id) => self.store.next_root_id(root_id).await, + Root::Tree(root_id) => self.store.next_root_id(root_id).await?, }; if let Some(next_root_id) = next_root_id { - let next_tree = self.store.tree(&next_root_id).await; + let next_tree = self.store.tree(&next_root_id).await?; blocks.append(self.layout_tree(context, frame, next_tree)); } else { blocks.append(self.layout_bottom(context, frame)); } } + + Ok(()) } async fn fill_screen_and_clamp_scrolling( @@ -248,23 +252,25 @@ impl> InnerTreeViewState { context: &Context, frame: &mut Frame, blocks: &mut TreeBlocks, - ) { + ) -> Result<(), S::Error> { let top_line = 0; let bottom_line = frame.size().height as i32 - 1; - self.expand_to_top(context, frame, blocks).await; + self.expand_to_top(context, frame, blocks).await?; if blocks.blocks().top_line > top_line { blocks.blocks_mut().set_top_line(0); } - self.expand_to_bottom(context, frame, blocks).await; + self.expand_to_bottom(context, frame, blocks).await?; if blocks.blocks().bottom_line < bottom_line { blocks.blocks_mut().set_bottom_line(bottom_line); } - self.expand_to_top(context, frame, blocks).await; + self.expand_to_top(context, frame, blocks).await?; + + Ok(()) } async fn layout_last_cursor_seed( @@ -272,8 +278,8 @@ impl> InnerTreeViewState { context: &Context, frame: &mut Frame, last_cursor_path: &Path, - ) -> TreeBlocks { - match &self.last_cursor { + ) -> Result, S::Error> { + Ok(match &self.last_cursor { Cursor::Bottom => { let mut blocks = self.layout_bottom(context, frame); @@ -299,7 +305,7 @@ impl> InnerTreeViewState { parent: Some(_), .. } => { let root = last_cursor_path.first(); - let tree = self.store.tree(root).await; + let tree = self.store.tree(root).await?; let mut blocks = self.layout_tree(context, frame, tree); blocks @@ -308,7 +314,7 @@ impl> InnerTreeViewState { blocks } - } + }) } async fn layout_cursor_seed( @@ -317,10 +323,10 @@ impl> InnerTreeViewState { frame: &mut Frame, last_cursor_path: &Path, cursor_path: &Path, - ) -> TreeBlocks { + ) -> Result, S::Error> { let bottom_line = frame.size().height as i32 - 1; - match &self.cursor { + Ok(match &self.cursor { Cursor::Bottom | Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } => { @@ -338,7 +344,7 @@ impl> InnerTreeViewState { parent: Some(_), .. } => { let root = cursor_path.first(); - let tree = self.store.tree(root).await; + let tree = self.store.tree(root).await?; let mut blocks = self.layout_tree(context, frame, tree); let cursor_above_last = cursor_path < last_cursor_path; @@ -349,7 +355,7 @@ impl> InnerTreeViewState { blocks } - } + }) } async fn layout_initial_seed( @@ -358,7 +364,7 @@ impl> InnerTreeViewState { frame: &mut Frame, last_cursor_path: &Path, cursor_path: &Path, - ) -> TreeBlocks { + ) -> Result, S::Error> { if let Cursor::Bottom = self.cursor { self.layout_cursor_seed(context, frame, last_cursor_path, cursor_path) .await @@ -513,7 +519,7 @@ impl> InnerTreeViewState { nick: String, focused: bool, frame: &mut Frame, - ) -> TreeBlocks { + ) -> Result, S::Error> { // The basic idea is this: // // First, layout a full screen of blocks around self.last_cursor, using @@ -534,30 +540,30 @@ impl> InnerTreeViewState { let context = Context { nick, focused }; - let last_cursor_path = self.cursor_path(&self.last_cursor).await; - let cursor_path = self.cursor_path(&self.cursor).await; + let last_cursor_path = self.cursor_path(&self.last_cursor).await?; + let cursor_path = self.cursor_path(&self.cursor).await?; self.make_path_visible(&cursor_path); let mut blocks = self .layout_initial_seed(&context, frame, &last_cursor_path, &cursor_path) - .await; + .await?; blocks.blocks_mut().offset(self.scroll); self.fill_screen_and_clamp_scrolling(&context, frame, &mut blocks) - .await; + .await?; if !self.contains_cursor(&blocks) { blocks = self .layout_cursor_seed(&context, frame, &last_cursor_path, &cursor_path) - .await; + .await?; self.fill_screen_and_clamp_scrolling(&context, frame, &mut blocks) - .await; + .await?; } match self.correction { Some(Correction::MakeCursorVisible) => { self.scroll_so_cursor_is_visible(frame, &mut blocks); self.fill_screen_and_clamp_scrolling(&context, frame, &mut blocks) - .await; + .await?; } Some(Correction::MoveCursorToVisibleArea) => { let new_cursor_msg_id = self.move_cursor_so_it_is_visible(frame, &blocks); @@ -572,18 +578,18 @@ impl> InnerTreeViewState { self.scroll = 0; self.correction = None; - let last_cursor_path = self.store.path(&cursor_msg_id).await; + let last_cursor_path = self.store.path(&cursor_msg_id).await?; blocks = self .layout_last_cursor_seed(&context, frame, &last_cursor_path) - .await; + .await?; self.fill_screen_and_clamp_scrolling(&context, frame, &mut blocks) - .await; + .await?; } } Some(Correction::CenterCursor) => { self.scroll_so_cursor_is_centered(frame, &mut blocks); self.fill_screen_and_clamp_scrolling(&context, frame, &mut blocks) - .await; + .await?; } None => {} } @@ -594,6 +600,6 @@ impl> InnerTreeViewState { self.scroll = 0; self.correction = None; - blocks + Ok(blocks) } } diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 0639a4a..4572cf6 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -5,6 +5,7 @@ use crossterm::style::{ContentStyle, Stylize}; use euphoxide::api::{Data, Message, MessageId, PacketType, SessionId}; use euphoxide::bot::instance::{Event, ServerConfig}; use euphoxide::conn::{self, Joined, Joining, SessionInfo}; +use log::error; use parking_lot::FairMutex; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::{mpsc, oneshot}; @@ -326,11 +327,19 @@ impl EuphRoom { Some(euph::State::Connected(_, conn::State::Joined(_))) ); - match self + let reaction = match self .chat .handle_input_event(terminal, crossterm_lock, event, can_compose) .await { + Ok(reaction) => reaction, + Err(err) => { + error!("{err}"); + panic!("{err}"); + } + }; + + match reaction { Reaction::NotHandled => {} Reaction::Handled => return true, Reaction::Composed { parent, content } => { diff --git a/src/vault/euph.rs b/src/vault/euph.rs index 897a2fd..9fd5fdc 100644 --- a/src/vault/euph.rs +++ b/src/vault/euph.rs @@ -1,3 +1,4 @@ +use std::convert::Infallible; use std::mem; use std::str::FromStr; @@ -86,76 +87,80 @@ impl EuphRoomVault { #[async_trait] impl MsgStore for EuphRoomVault { - async fn path(&self, id: &MessageId) -> Path { - self.path(*id).await + type Error = Infallible; + + async fn path(&self, id: &MessageId) -> Result, Self::Error> { + Ok(self.path(*id).await) } - async fn msg(&self, id: &MessageId) -> Option { - self.msg(*id).await + async fn msg(&self, id: &MessageId) -> Result, Self::Error> { + Ok(self.msg(*id).await) } - async fn tree(&self, root_id: &MessageId) -> Tree { - self.tree(*root_id).await + async fn tree(&self, root_id: &MessageId) -> Result, Self::Error> { + Ok(self.tree(*root_id).await) } - async fn first_root_id(&self) -> Option { - self.first_root_id().await + async fn first_root_id(&self) -> Result, Self::Error> { + Ok(self.first_root_id().await) } - async fn last_root_id(&self) -> Option { - self.last_root_id().await + async fn last_root_id(&self) -> Result, Self::Error> { + Ok(self.last_root_id().await) } - async fn prev_root_id(&self, root_id: &MessageId) -> Option { - self.prev_root_id(*root_id).await + async fn prev_root_id(&self, root_id: &MessageId) -> Result, Self::Error> { + Ok(self.prev_root_id(*root_id).await) } - async fn next_root_id(&self, root_id: &MessageId) -> Option { - self.next_root_id(*root_id).await + async fn next_root_id(&self, root_id: &MessageId) -> Result, Self::Error> { + Ok(self.next_root_id(*root_id).await) } - async fn oldest_msg_id(&self) -> Option { - self.oldest_msg_id().await + async fn oldest_msg_id(&self) -> Result, Self::Error> { + Ok(self.oldest_msg_id().await) } - async fn newest_msg_id(&self) -> Option { - self.newest_msg_id().await + async fn newest_msg_id(&self) -> Result, Self::Error> { + Ok(self.newest_msg_id().await) } - async fn older_msg_id(&self, id: &MessageId) -> Option { - self.older_msg_id(*id).await + async fn older_msg_id(&self, id: &MessageId) -> Result, Self::Error> { + Ok(self.older_msg_id(*id).await) } - async fn newer_msg_id(&self, id: &MessageId) -> Option { - self.newer_msg_id(*id).await + async fn newer_msg_id(&self, id: &MessageId) -> Result, Self::Error> { + Ok(self.newer_msg_id(*id).await) } - async fn oldest_unseen_msg_id(&self) -> Option { - self.oldest_unseen_msg_id().await + async fn oldest_unseen_msg_id(&self) -> Result, Self::Error> { + Ok(self.oldest_unseen_msg_id().await) } - async fn newest_unseen_msg_id(&self) -> Option { - self.newest_unseen_msg_id().await + async fn newest_unseen_msg_id(&self) -> Result, Self::Error> { + Ok(self.newest_unseen_msg_id().await) } - async fn older_unseen_msg_id(&self, id: &MessageId) -> Option { - self.older_unseen_msg_id(*id).await + async fn older_unseen_msg_id(&self, id: &MessageId) -> Result, Self::Error> { + Ok(self.older_unseen_msg_id(*id).await) } - async fn newer_unseen_msg_id(&self, id: &MessageId) -> Option { - self.newer_unseen_msg_id(*id).await + async fn newer_unseen_msg_id(&self, id: &MessageId) -> Result, Self::Error> { + Ok(self.newer_unseen_msg_id(*id).await) } - async fn unseen_msgs_count(&self) -> usize { - self.unseen_msgs_count().await + async fn unseen_msgs_count(&self) -> Result { + Ok(self.unseen_msgs_count().await) } - async fn set_seen(&self, id: &MessageId, seen: bool) { + async fn set_seen(&self, id: &MessageId, seen: bool) -> Result<(), Self::Error> { self.set_seen(*id, seen); + Ok(()) } - async fn set_older_seen(&self, id: &MessageId, seen: bool) { + async fn set_older_seen(&self, id: &MessageId, seen: bool) -> Result<(), Self::Error> { self.set_older_seen(*id, seen); + Ok(()) } } From 7e9e441c1e4085b8bde74a1b1a594248a9a7ef36 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 12 Feb 2023 23:09:59 +0100 Subject: [PATCH 035/266] Use Garmelon/vault --- Cargo.lock | 10 + Cargo.toml | 8 + src/euph/room.rs | 41 ++- src/export.rs | 2 +- src/export/json.rs | 4 +- src/export/text.rs | 6 +- src/macros.rs | 14 + src/main.rs | 4 +- src/ui.rs | 17 +- src/ui/chat/tree.rs | 10 +- src/ui/euph/room.rs | 24 +- src/ui/rooms.rs | 20 +- src/vault.rs | 77 ++---- src/vault/euph.rs | 635 ++++++++++++++++++++++--------------------- src/vault/migrate.rs | 28 +- 15 files changed, 442 insertions(+), 458 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c95586d..20b9f53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,6 +203,7 @@ dependencies = [ "toss", "unicode-segmentation", "unicode-width", + "vault", ] [[package]] @@ -1392,6 +1393,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "vault" +version = "0.1.0" +source = "git+https://github.com/Garmelon/vault.git?tag=v0.1.0#028c72cac4e84bfbbf9fb03b15acb59989a31df9" +dependencies = [ + "rusqlite", + "tokio", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index db7ea53..4fd5a56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,3 +47,11 @@ rev = "0d59116012a51516a821991e2969b1cf4779770f" # [patch."https://github.com/Garmelon/toss.git"] # toss = { path = "../toss/" } + +[dependencies.vault] +git = "https://github.com/Garmelon/vault.git" +tag = "v0.1.0" +features = ["tokio"] + +# [patch."https://github.com/Garmelon/vault.git"] +# vault = { path = "../vault/" } diff --git a/src/euph/room.rs b/src/euph/room.rs index 8eeb8e5..c26685f 100644 --- a/src/euph/room.rs +++ b/src/euph/room.rs @@ -14,7 +14,7 @@ use log::{debug, error, info, warn}; use tokio::select; use tokio::sync::oneshot; -use crate::macros::ok_or_return; +use crate::macros::{logging_unwrap, ok_or_return}; use crate::vault::EuphRoomVault; const LOG_INTERVAL: Duration = Duration::from_secs(10); @@ -93,7 +93,7 @@ impl Room { self.state.conn_tx().ok_or(Error::NotConnected) } - pub fn handle_event(&mut self, event: Event) { + pub async fn handle_event(&mut self, event: Event) { match event { Event::Connecting(_) => { self.state = State::Connecting; @@ -121,11 +121,11 @@ impl Room { let cookies = &*self.instance.config().server.cookies; let cookies = cookies.lock().unwrap().clone(); - self.vault.vault().set_cookies(cookies); + logging_unwrap!(self.vault.vault().set_cookies(cookies).await); } Event::Packet(_, packet, Snapshot { conn_tx, state }) => { self.state = State::Connected(conn_tx, state); - self.on_packet(packet); + self.on_packet(packet).await; } Event::Disconnected(_) => { self.state = State::Disconnected; @@ -173,7 +173,7 @@ impl Room { } async fn request_logs(vault: &EuphRoomVault, conn_tx: &ConnTx) { - let before = match vault.last_span().await { + let before = match logging_unwrap!(vault.last_span().await) { Some((None, _)) => return, // Already at top of room history Some((Some(before), _)) => Some(before), None => None, @@ -203,7 +203,7 @@ impl Room { } } - fn on_packet(&mut self, packet: ParsedPacket) { + async fn on_packet(&mut self, packet: ParsedPacket) { let instance_name = &self.instance.config().name; let data = ok_or_return!(&packet.content); match data { @@ -238,26 +238,39 @@ impl Room { Data::SendEvent(SendEvent(msg)) => { let own_user_id = self.own_user_id(); if let Some(last_msg_id) = &mut self.last_msg_id { - self.vault - .add_msg(Box::new(msg.clone()), *last_msg_id, own_user_id); + logging_unwrap!( + self.vault + .add_msg(Box::new(msg.clone()), *last_msg_id, own_user_id) + .await + ); *last_msg_id = Some(msg.id); } } Data::SnapshotEvent(d) => { info!("{instance_name}: successfully joined"); - self.vault.join(Time::now()); + logging_unwrap!(self.vault.join(Time::now()).await); self.last_msg_id = Some(d.log.last().map(|m| m.id)); - self.vault.add_msgs(d.log.clone(), None, self.own_user_id()); + logging_unwrap!( + self.vault + .add_msgs(d.log.clone(), None, self.own_user_id()) + .await + ); } Data::LogReply(d) => { - self.vault - .add_msgs(d.log.clone(), d.before, self.own_user_id()); + logging_unwrap!( + self.vault + .add_msgs(d.log.clone(), d.before, self.own_user_id()) + .await + ); } Data::SendReply(SendReply(msg)) => { let own_user_id = self.own_user_id(); if let Some(last_msg_id) = &mut self.last_msg_id { - self.vault - .add_msg(Box::new(msg.clone()), *last_msg_id, own_user_id); + logging_unwrap!( + self.vault + .add_msg(Box::new(msg.clone()), *last_msg_id, own_user_id) + .await + ); *last_msg_id = Some(msg.id); } } diff --git a/src/export.rs b/src/export.rs index 15db9b7..545f48b 100644 --- a/src/export.rs +++ b/src/export.rs @@ -85,7 +85,7 @@ pub async fn export(vault: &EuphVault, mut args: Args) -> anyhow::Result<()> { } let rooms = if args.all { - let mut rooms = vault.rooms().await; + let mut rooms = vault.rooms().await?; rooms.sort_unstable(); rooms } else { diff --git a/src/export/json.rs b/src/export/json.rs index 8921229..258b7bd 100644 --- a/src/export/json.rs +++ b/src/export/json.rs @@ -10,7 +10,7 @@ pub async fn export(vault: &EuphRoomVault, file: &mut W) -> anyhow::Re let mut total = 0; let mut offset = 0; loop { - let messages = vault.chunk_at_offset(CHUNK_SIZE, offset).await; + let messages = vault.chunk_at_offset(CHUNK_SIZE, offset).await?; offset += messages.len(); if messages.is_empty() { @@ -42,7 +42,7 @@ pub async fn export_stream(vault: &EuphRoomVault, file: &mut W) -> any let mut total = 0; let mut offset = 0; loop { - let messages = vault.chunk_at_offset(CHUNK_SIZE, offset).await; + let messages = vault.chunk_at_offset(CHUNK_SIZE, offset).await?; offset += messages.len(); if messages.is_empty() { diff --git a/src/export/text.rs b/src/export/text.rs index 0cdd138..bb3cfa1 100644 --- a/src/export/text.rs +++ b/src/export/text.rs @@ -16,11 +16,11 @@ const TIME_EMPTY: &str = " "; pub async fn export(vault: &EuphRoomVault, out: &mut W) -> anyhow::Result<()> { let mut exported_trees = 0; let mut exported_msgs = 0; - let mut root_id = vault.first_root_id().await; + let mut root_id = vault.first_root_id().await?; while let Some(some_root_id) = root_id { - let tree = vault.tree(some_root_id).await; + let tree = vault.tree(some_root_id).await?; write_tree(out, &tree, some_root_id, 0)?; - root_id = vault.next_root_id(some_root_id).await; + root_id = vault.next_root_id(some_root_id).await?; exported_trees += 1; exported_msgs += tree.len(); diff --git a/src/macros.rs b/src/macros.rs index 36372d7..3e03b07 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -29,3 +29,17 @@ macro_rules! ok_or_return { }; } pub(crate) use ok_or_return; + +// TODO Get rid of this macro as much as possible +macro_rules! logging_unwrap { + ($e:expr) => { + match $e { + Ok(value) => value, + Err(err) => { + log::error!("{err}"); + panic!("{err}"); + } + } + }; +} +pub(crate) use logging_unwrap; diff --git a/src/main.rs b/src/main.rs index 1c6071b..6023c76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -149,11 +149,11 @@ async fn main() -> anyhow::Result<()> { Command::Gc => { eprintln!("Cleaning up and compacting vault"); eprintln!("This may take a while..."); - vault.gc().await; + vault.gc().await?; } Command::ClearCookies => { eprintln!("Clearing cookies"); - vault.euph().set_cookies(CookieJar::new()); + vault.euph().set_cookies(CookieJar::new()).await?; } } diff --git a/src/ui.rs b/src/ui.rs index 8ff3e13..5d9007e 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -10,7 +10,6 @@ use std::io; use std::sync::{Arc, Weak}; use std::time::{Duration, Instant}; -use log::error; use parking_lot::FairMutex; use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; @@ -19,7 +18,7 @@ use toss::terminal::Terminal; use crate::config::Config; use crate::logger::{LogMsg, Logger}; -use crate::macros::{ok_or_return, some_or_return}; +use crate::macros::{logging_unwrap, ok_or_return, some_or_return}; use crate::vault::Vault; pub use self::chat::ChatMsg; @@ -231,7 +230,7 @@ impl Ui { .await } UiEvent::Euph(event) => { - if self.rooms.handle_euph_event(event) { + if self.rooms.handle_euph_event(event).await { EventHandleResult::Redraw } else { EventHandleResult::Continue @@ -288,17 +287,11 @@ impl Ui { .await } Mode::Log => { - let reaction = match self + let reaction = self .log_chat .handle_input_event(terminal, crossterm_lock, &event, false) - .await - { - Ok(reaction) => reaction, - Err(err) => { - error!("{err}"); - panic!("{err}"); - } - }; + .await; + let reaction = logging_unwrap!(reaction); reaction.handled() } }; diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs index 9325d3e..ad8e6ab 100644 --- a/src/ui/chat/tree.rs +++ b/src/ui/chat/tree.rs @@ -10,12 +10,12 @@ use std::fmt; use std::sync::Arc; use async_trait::async_trait; -use log::error; use parking_lot::FairMutex; use tokio::sync::Mutex; use toss::frame::{Frame, Pos, Size}; use toss::terminal::Terminal; +use crate::macros::logging_unwrap; use crate::store::{Msg, MsgStore}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::util; @@ -439,13 +439,7 @@ where async fn render(self: Box, frame: &mut Frame) { let mut guard = self.inner.lock().await; - let blocks = match guard.relayout(self.nick, self.focused, frame).await { - Ok(blocks) => blocks, - Err(err) => { - error!("{err}"); - panic!("{err}"); - } - }; + let blocks = logging_unwrap!(guard.relayout(self.nick, self.focused, frame).await); let size = frame.size(); for block in blocks.into_blocks().blocks { diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 4572cf6..59a113a 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -5,7 +5,6 @@ use crossterm::style::{ContentStyle, Stylize}; use euphoxide::api::{Data, Message, MessageId, PacketType, SessionId}; use euphoxide::bot::instance::{Event, ServerConfig}; use euphoxide::conn::{self, Joined, Joining, SessionInfo}; -use log::error; use parking_lot::FairMutex; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::{mpsc, oneshot}; @@ -14,6 +13,7 @@ use toss::terminal::Terminal; use crate::config; use crate::euph; +use crate::macros::logging_unwrap; use crate::ui::chat::{ChatState, Reaction}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::widgets::border::Border; @@ -143,7 +143,7 @@ impl EuphRoom { } pub async fn unseen_msgs_count(&self) -> usize { - self.vault().unseen_msgs_count().await + logging_unwrap!(self.vault().unseen_msgs_count().await) } async fn stabilize_pseudo_msg(&mut self) { @@ -327,17 +327,11 @@ impl EuphRoom { Some(euph::State::Connected(_, conn::State::Joined(_))) ); - let reaction = match self + let reaction = self .chat .handle_input_event(terminal, crossterm_lock, event, can_compose) - .await - { - Ok(reaction) => reaction, - Err(err) => { - error!("{err}"); - panic!("{err}"); - } - }; + .await; + let reaction = logging_unwrap!(reaction); match reaction { Reaction::NotHandled => {} @@ -434,7 +428,7 @@ impl EuphRoom { match event { key!('i') => { if let Some(id) = self.chat.cursor().await { - if let Some(msg) = self.vault().full_msg(id).await { + if let Some(msg) = logging_unwrap!(self.vault().full_msg(id).await) { self.state = State::InspectMessage(msg); } } @@ -442,7 +436,7 @@ impl EuphRoom { } key!('I') => { if let Some(id) = self.chat.cursor().await { - if let Some(msg) = self.vault().msg(id).await { + if let Some(msg) = logging_unwrap!(self.vault().msg(id).await) { self.state = State::Links(LinksState::new(&msg.content)); } } @@ -679,7 +673,7 @@ impl EuphRoom { } } - pub fn handle_event(&mut self, event: Event) -> bool { + pub async fn handle_event(&mut self, event: Event) -> bool { let handled = if self.room.is_some() { if let Event::Packet(_, packet, _) = &event { match &packet.content { @@ -694,7 +688,7 @@ impl EuphRoom { }; if let Some(room) = &mut self.room { - room.handle_event(event); + room.handle_event(event).await; } handled diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index 07ef09a..56dac95 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -13,6 +13,7 @@ use toss::terminal::Terminal; use crate::config::{Config, RoomsSortOrder}; use crate::euph; +use crate::macros::logging_unwrap; use crate::vault::Vault; use super::euph::room::EuphRoom; @@ -69,8 +70,8 @@ impl Rooms { vault: Vault, ui_event_tx: mpsc::UnboundedSender, ) -> Self { - let euph_server_config = - ServerConfig::default().cookies(Arc::new(Mutex::new(vault.euph().cookies().await))); + let cookies = logging_unwrap!(vault.euph().cookies().await); + let euph_server_config = ServerConfig::default().cookies(Arc::new(Mutex::new(cookies))); let mut result = Self { config, @@ -112,13 +113,8 @@ impl Rooms { /// - failed connection attempts, or /// - rooms that were deleted from the db. async fn stabilize_rooms(&mut self) { - let mut rooms_set = self - .vault - .euph() - .rooms() - .await - .into_iter() - .collect::>(); + let rooms = logging_unwrap!(self.vault.euph().rooms().await); + let mut rooms_set = rooms.into_iter().collect::>(); // Prevent room that is currently being shown from being removed. This // could otherwise happen when connecting to a room that doesn't exist. @@ -533,7 +529,7 @@ impl Rooms { } key!(Enter) if editor.text() == *name => { self.euph_rooms.remove(name); - self.vault.euph().delete(name.clone()); + logging_unwrap!(self.vault.euph().room(name.clone()).delete().await); self.state = State::ShowList; return true; } @@ -548,10 +544,10 @@ impl Rooms { false } - pub fn handle_euph_event(&mut self, event: Event) -> bool { + pub async fn handle_euph_event(&mut self, event: Event) -> bool { let instance_name = event.config().name.clone(); let room = self.get_or_insert_room(instance_name.clone()); - let handled = room.handle_event(event); + let handled = room.handle_event(event).await; let room_visible = match &self.state { State::ShowRoom(name) => *name == instance_name, diff --git a/src/vault.rs b/src/vault.rs index 9352e7d..4f49e45 100644 --- a/src/vault.rs +++ b/src/vault.rs @@ -2,43 +2,42 @@ mod euph; mod migrate; mod prepare; +use std::fs; use std::path::Path; -use std::{fs, thread}; -use log::error; use rusqlite::Connection; -use tokio::sync::{mpsc, oneshot}; +use vault::tokio::TokioVault; +use vault::Action; -use self::euph::EuphRequest; pub use self::euph::{EuphRoomVault, EuphVault}; -enum Request { - Close(oneshot::Sender<()>), - Gc(oneshot::Sender<()>), - Euph(EuphRequest), -} - #[derive(Debug, Clone)] pub struct Vault { - tx: mpsc::UnboundedSender, + tokio_vault: TokioVault, ephemeral: bool, } +struct GcAction; + +impl Action for GcAction { + type Result = (); + + fn run(self, conn: &mut Connection) -> rusqlite::Result { + conn.execute_batch("ANALYZE; VACUUM;") + } +} + impl Vault { pub fn ephemeral(&self) -> bool { self.ephemeral } pub async fn close(&self) { - let (tx, rx) = oneshot::channel(); - let _ = self.tx.send(Request::Close(tx)); - let _ = rx.await; + self.tokio_vault.stop().await; } - pub async fn gc(&self) { - let (tx, rx) = oneshot::channel(); - let _ = self.tx.send(Request::Gc(tx)); - let _ = rx.await; + pub async fn gc(&self) -> vault::tokio::Result<()> { + self.tokio_vault.execute(GcAction).await } pub fn euph(&self) -> EuphVault { @@ -46,47 +45,17 @@ impl Vault { } } -fn run(mut conn: Connection, mut rx: mpsc::UnboundedReceiver) { - while let Some(request) = rx.blocking_recv() { - match request { - Request::Close(tx) => { - eprintln!("Closing vault"); - if let Err(e) = conn.execute_batch("PRAGMA optimize") { - error!("{e}"); - } - // Ensure `Vault::close` exits only after the sqlite connection - // has been closed properly. - drop(conn); - drop(tx); - break; - } - Request::Gc(tx) => { - if let Err(e) = conn.execute_batch("ANALYZE; VACUUM;") { - error!("{e}"); - } - drop(tx); - } - Request::Euph(r) => { - if let Err(e) = r.perform(&mut conn) { - error!("{e}"); - } - } - } - } -} - -fn launch_from_connection(mut conn: Connection, ephemeral: bool) -> rusqlite::Result { +fn launch_from_connection(conn: Connection, ephemeral: bool) -> rusqlite::Result { conn.pragma_update(None, "foreign_keys", true)?; conn.pragma_update(None, "trusted_schema", false)?; eprintln!("Opening vault"); - migrate::migrate(&mut conn)?; - prepare::prepare(&mut conn)?; - - let (tx, rx) = mpsc::unbounded_channel(); - thread::spawn(move || run(conn, rx)); - Ok(Vault { tx, ephemeral }) + let tokio_vault = TokioVault::launch_and_prepare(conn, &migrate::MIGRATIONS, prepare::prepare)?; + Ok(Vault { + tokio_vault, + ephemeral, + }) } pub fn launch(path: &Path) -> rusqlite::Result { diff --git a/src/vault/euph.rs b/src/vault/euph.rs index 9fd5fdc..ff32754 100644 --- a/src/vault/euph.rs +++ b/src/vault/euph.rs @@ -1,4 +1,3 @@ -use std::convert::Infallible; use std::mem; use std::str::FromStr; @@ -8,11 +7,15 @@ use euphoxide::api::{Message, MessageId, SessionId, SessionView, Snowflake, Time use rusqlite::types::{FromSql, FromSqlError, ToSqlOutput, Value, ValueRef}; use rusqlite::{named_params, params, Connection, OptionalExtension, ToSql, Transaction}; use time::OffsetDateTime; -use tokio::sync::oneshot; +use vault::Action; use crate::euph::SmallMessage; use crate::store::{MsgStore, Path, Tree}; +/////////////////// +// Wrapper types // +/////////////////// + /// Wrapper for [`Snowflake`] that implements useful rusqlite traits. struct WSnowflake(Snowflake); @@ -47,6 +50,10 @@ impl FromSql for WTime { } } +/////////////// +// EuphVault // +/////////////// + #[derive(Debug, Clone)] pub struct EuphVault { vault: super::Vault, @@ -69,216 +76,36 @@ impl EuphVault { } } -#[derive(Debug, Clone)] -pub struct EuphRoomVault { - vault: EuphVault, - room: String, -} - -impl EuphRoomVault { - pub fn vault(&self) -> &EuphVault { - &self.vault - } - - pub fn room(&self) -> &str { - &self.room - } -} - -#[async_trait] -impl MsgStore for EuphRoomVault { - type Error = Infallible; - - async fn path(&self, id: &MessageId) -> Result, Self::Error> { - Ok(self.path(*id).await) - } - - async fn msg(&self, id: &MessageId) -> Result, Self::Error> { - Ok(self.msg(*id).await) - } - - async fn tree(&self, root_id: &MessageId) -> Result, Self::Error> { - Ok(self.tree(*root_id).await) - } - - async fn first_root_id(&self) -> Result, Self::Error> { - Ok(self.first_root_id().await) - } - - async fn last_root_id(&self) -> Result, Self::Error> { - Ok(self.last_root_id().await) - } - - async fn prev_root_id(&self, root_id: &MessageId) -> Result, Self::Error> { - Ok(self.prev_root_id(*root_id).await) - } - - async fn next_root_id(&self, root_id: &MessageId) -> Result, Self::Error> { - Ok(self.next_root_id(*root_id).await) - } - - async fn oldest_msg_id(&self) -> Result, Self::Error> { - Ok(self.oldest_msg_id().await) - } - - async fn newest_msg_id(&self) -> Result, Self::Error> { - Ok(self.newest_msg_id().await) - } - - async fn older_msg_id(&self, id: &MessageId) -> Result, Self::Error> { - Ok(self.older_msg_id(*id).await) - } - - async fn newer_msg_id(&self, id: &MessageId) -> Result, Self::Error> { - Ok(self.newer_msg_id(*id).await) - } - - async fn oldest_unseen_msg_id(&self) -> Result, Self::Error> { - Ok(self.oldest_unseen_msg_id().await) - } - - async fn newest_unseen_msg_id(&self) -> Result, Self::Error> { - Ok(self.newest_unseen_msg_id().await) - } - - async fn older_unseen_msg_id(&self, id: &MessageId) -> Result, Self::Error> { - Ok(self.older_unseen_msg_id(*id).await) - } - - async fn newer_unseen_msg_id(&self, id: &MessageId) -> Result, Self::Error> { - Ok(self.newer_unseen_msg_id(*id).await) - } - - async fn unseen_msgs_count(&self) -> Result { - Ok(self.unseen_msgs_count().await) - } - - async fn set_seen(&self, id: &MessageId, seen: bool) -> Result<(), Self::Error> { - self.set_seen(*id, seen); - Ok(()) - } - - async fn set_older_seen(&self, id: &MessageId, seen: bool) -> Result<(), Self::Error> { - self.set_older_seen(*id, seen); - Ok(()) - } -} - -trait Request { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()>; -} - -macro_rules! requests_vault_fn { - ( $var:ident : $fn:ident( $( $arg:ident : $ty:ty ),* ) ) => { - pub fn $fn(&self $( , $arg: $ty )* ) { - let request = EuphRequest::$var($var { $( $arg, )* }); - let _ = self.vault.tx.send(super::Request::Euph(request)); - } - }; - ( $var:ident : $fn:ident( $( $arg:ident : $ty:ty ),* ) -> $res:ty ) => { - pub async fn $fn(&self $( , $arg: $ty )* ) -> $res { - let (tx, rx) = oneshot::channel(); - let request = EuphRequest::$var($var { - $( $arg, )* - result: tx, - }); - let _ = self.vault.tx.send(super::Request::Euph(request)); - rx.await.unwrap() - } - }; -} - -// This doesn't match the type of the `room` argument because that's apparently -// impossible to match to `String`. See also the readme of -// https://github.com/danielhenrymantilla/rust-defile for a description of this -// phenomenon and some examples. -macro_rules! requests_room_vault_fn { - ( $fn:ident ( room: $mustbestring:ty $( , $arg:ident : $ty:ty )* ) ) => { - pub fn $fn(&self $( , $arg: $ty )* ) { - self.vault.$fn(self.room.clone() $( , $arg )* ); - } - }; - ( $fn:ident ( room: $mustbestring:ty $( , $arg:ident : $ty:ty )* ) -> $res:ty ) => { - pub async fn $fn(&self $( , $arg: $ty )* ) -> $res { - self.vault.$fn(self.room.clone() $( , $arg )* ).await - } - }; - ( $( $tt:tt )* ) => { }; -} - -macro_rules! requests { +macro_rules! euph_vault_actions { ( $( - $var:ident : $fn:ident ( $( $arg:ident : $ty:ty ),* ) $( -> $res:ty )? ; + $struct:ident : $fn:ident ( $( $arg:ident : $arg_ty:ty ),* ) -> $res:ty ; )* ) => { $( - pub(super) struct $var { - $( $arg: $ty, )* - $( result: oneshot::Sender<$res>, )? + struct $struct { + $( $arg: $arg_ty, )* } )* - pub(super) enum EuphRequest { - $( $var($var), )* - } - - impl EuphRequest { - pub(super) fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { - match self { - $( Self::$var(request) => request.perform(conn), )* - } - } - } - - #[allow(dead_code)] impl EuphVault { - $( requests_vault_fn!($var : $fn( $( $arg: $ty ),* ) $( -> $res )? ); )* - } - - #[allow(dead_code)] - impl EuphRoomVault { - $( requests_room_vault_fn!($fn( $( $arg: $ty ),* ) $( -> $res )? ); )* + $( + pub async fn $fn(&self, $( $arg: $arg_ty, )* ) -> vault::tokio::Result<$res> { + self.vault.tokio_vault.execute($struct { $( $arg, )* }).await + } + )* } }; } -requests! { - // Cookies +euph_vault_actions! { GetCookies : cookies() -> CookieJar; - SetCookies : set_cookies(cookies: CookieJar); - - // Rooms + SetCookies : set_cookies(cookies: CookieJar) -> (); GetRooms : rooms() -> Vec; - Join : join(room: String, time: Time); - Delete : delete(room: String); - - // Message - AddMsg : add_msg(room: String, msg: Box, prev_msg_id: Option, own_user_id: Option); - AddMsgs : add_msgs(room: String, msgs: Vec, next_msg_id: Option, own_user_id: Option); - GetLastSpan : last_span(room: String) -> Option<(Option, Option)>; - GetPath : path(room: String, id: MessageId) -> Path; - GetMsg : msg(room: String, id: MessageId) -> Option; - GetFullMsg : full_msg(room: String, id: MessageId) -> Option; - GetTree : tree(room: String, root_id: MessageId) -> Tree; - GetFirstRootId : first_root_id(room: String) -> Option; - GetLastRootId : last_root_id(room: String) -> Option; - GetPrevRootId : prev_root_id(room: String, root_id: MessageId) -> Option; - GetNextRootId : next_root_id(room: String, root_id: MessageId) -> Option; - GetOldestMsgId : oldest_msg_id(room: String) -> Option; - GetNewestMsgId : newest_msg_id(room: String) -> Option; - GetOlderMsgId : older_msg_id(room: String, id: MessageId) -> Option; - GetNewerMsgId : newer_msg_id(room: String, id: MessageId) -> Option; - GetOldestUnseenMsgId : oldest_unseen_msg_id(room: String) -> Option; - GetNewestUnseenMsgId : newest_unseen_msg_id(room: String) -> Option; - GetOlderUnseenMsgId : older_unseen_msg_id(room: String, id: MessageId) -> Option; - GetNewerUnseenMsgId : newer_unseen_msg_id(room: String, id: MessageId) -> Option; - GetUnseenMsgsCount : unseen_msgs_count(room: String) -> usize; - SetSeen : set_seen(room: String, id: MessageId, seen: bool); - SetOlderSeen : set_older_seen(room: String, id: MessageId, seen: bool); - GetChunkAtOffset : chunk_at_offset(room: String, amount: usize, offset: usize) -> Vec; } -impl Request for GetCookies { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +impl Action for GetCookies { + type Result = CookieJar; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { let cookies = conn .prepare( " @@ -296,14 +123,14 @@ impl Request for GetCookies { for cookie in cookies { cookie_jar.add_original(cookie); } - - let _ = self.result.send(cookie_jar); - Ok(()) + Ok(cookie_jar) } } -impl Request for SetCookies { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +impl Action for SetCookies { + type Result = (); + + fn run(self, conn: &mut Connection) -> rusqlite::Result { let tx = conn.transaction()?; // Since euphoria sets all cookies on every response, we can just delete @@ -326,24 +153,100 @@ impl Request for SetCookies { } } -impl Request for GetRooms { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { - let rooms = conn - .prepare( - " +impl Action for GetRooms { + type Result = Vec; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { + conn.prepare( + " SELECT room FROM euph_rooms ", - )? - .query_map([], |row| row.get(0))? - .collect::>()?; - let _ = self.result.send(rooms); - Ok(()) + )? + .query_map([], |row| row.get(0))? + .collect::>() } } -impl Request for Join { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +/////////////////// +// EuphRoomVault // +/////////////////// + +#[derive(Debug, Clone)] +pub struct EuphRoomVault { + vault: EuphVault, + room: String, +} + +impl EuphRoomVault { + pub fn vault(&self) -> &EuphVault { + &self.vault + } + + pub fn room(&self) -> &str { + &self.room + } +} + +macro_rules! euph_room_vault_actions { + ( $( + $struct:ident : $fn:ident ( $( $arg:ident : $arg_ty:ty ),* ) -> $res:ty ; + )* ) => { + $( + struct $struct { + room: String, + $( $arg: $arg_ty, )* + } + )* + + impl EuphRoomVault { + $( + pub async fn $fn(&self, $( $arg: $arg_ty, )* ) -> vault::tokio::Result<$res> { + self.vault.vault.tokio_vault.execute($struct { + room: self.room.clone(), + $( $arg, )* + }).await + } + )* + } + }; +} + +euph_room_vault_actions! { + // Room + Join : join(time: Time) -> (); + Delete : delete() -> (); + + // Message + AddMsg : add_msg(msg: Box, prev_msg_id: Option, own_user_id: Option) -> (); + AddMsgs : add_msgs(msgs: Vec, next_msg_id: Option, own_user_id: Option) -> (); + GetLastSpan : last_span() -> Option<(Option, Option)>; + GetPath : path(id: MessageId) -> Path; + GetMsg : msg(id: MessageId) -> Option; + GetFullMsg : full_msg(id: MessageId) -> Option; + GetTree : tree(root_id: MessageId) -> Tree; + GetFirstRootId : first_root_id() -> Option; + GetLastRootId : last_root_id() -> Option; + GetPrevRootId : prev_root_id(root_id: MessageId) -> Option; + GetNextRootId : next_root_id(root_id: MessageId) -> Option; + GetOldestMsgId : oldest_msg_id() -> Option; + GetNewestMsgId : newest_msg_id() -> Option; + GetOlderMsgId : older_msg_id(id: MessageId) -> Option; + GetNewerMsgId : newer_msg_id(id: MessageId) -> Option; + GetOldestUnseenMsgId : oldest_unseen_msg_id() -> Option; + GetNewestUnseenMsgId : newest_unseen_msg_id() -> Option; + GetOlderUnseenMsgId : older_unseen_msg_id(id: MessageId) -> Option; + GetNewerUnseenMsgId : newer_unseen_msg_id(id: MessageId) -> Option; + GetUnseenMsgsCount : unseen_msgs_count() -> usize; + SetSeen : set_seen(id: MessageId, seen: bool) -> (); + SetOlderSeen : set_older_seen(id: MessageId, seen: bool) -> (); + GetChunkAtOffset : chunk_at_offset(amount: usize, offset: usize) -> Vec; +} + +impl Action for Join { + type Result = (); + + fn run(self, conn: &mut Connection) -> rusqlite::Result { conn.execute( " INSERT INTO euph_rooms (room, first_joined, last_joined) @@ -357,8 +260,10 @@ impl Request for Join { } } -impl Request for Delete { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +impl Action for Delete { + type Result = (); + + fn run(self, conn: &mut Connection) -> rusqlite::Result { conn.execute( " DELETE FROM euph_rooms @@ -525,8 +430,10 @@ fn add_span( Ok(()) } -impl Request for AddMsg { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +impl Action for AddMsg { + type Result = (); + + fn run(self, conn: &mut Connection) -> rusqlite::Result { let tx = conn.transaction()?; let end = self.msg.id; @@ -538,8 +445,10 @@ impl Request for AddMsg { } } -impl Request for AddMsgs { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +impl Action for AddMsgs { + type Result = (); + + fn run(self, conn: &mut Connection) -> rusqlite::Result { let tx = conn.transaction()?; if self.msgs.is_empty() { @@ -559,8 +468,10 @@ impl Request for AddMsgs { } } -impl Request for GetLastSpan { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +impl Action for GetLastSpan { + type Result = Option<(Option, Option)>; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { let span = conn .prepare( " @@ -578,13 +489,14 @@ impl Request for GetLastSpan { )) }) .optional()?; - let _ = self.result.send(span); - Ok(()) + Ok(span) } } -impl Request for GetPath { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +impl Action for GetPath { + type Result = Path; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { let path = conn .prepare( " @@ -606,14 +518,14 @@ impl Request for GetPath { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) })? .collect::>()?; - let path = Path::new(path); - let _ = self.result.send(path); - Ok(()) + Ok(Path::new(path)) } } -impl Request for GetMsg { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +impl Action for GetMsg { + type Result = Option; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { let msg = conn .query_row( " @@ -635,13 +547,14 @@ impl Request for GetMsg { }, ) .optional()?; - let _ = self.result.send(msg); - Ok(()) + Ok(msg) } } -impl Request for GetFullMsg { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +impl Action for GetFullMsg { + type Result = Option; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { let mut query = conn.prepare( " SELECT @@ -679,13 +592,14 @@ impl Request for GetFullMsg { }) }) .optional()?; - let _ = self.result.send(msg); - Ok(()) + Ok(msg) } } -impl Request for GetTree { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +impl Action for GetTree { + type Result = Tree; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { let msgs = conn .prepare( " @@ -716,15 +630,15 @@ impl Request for GetTree { }) })? .collect::>()?; - let tree = Tree::new(self.root_id, msgs); - let _ = self.result.send(tree); - Ok(()) + Ok(Tree::new(self.root_id, msgs)) } } -impl Request for GetFirstRootId { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { - let tree = conn +impl Action for GetFirstRootId { + type Result = Option; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { + let root_id = conn .prepare( " SELECT id @@ -738,14 +652,15 @@ impl Request for GetFirstRootId { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; - let _ = self.result.send(tree); - Ok(()) + Ok(root_id) } } -impl Request for GetLastRootId { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { - let tree = conn +impl Action for GetLastRootId { + type Result = Option; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { + let root_id = conn .prepare( " SELECT id @@ -759,14 +674,15 @@ impl Request for GetLastRootId { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; - let _ = self.result.send(tree); - Ok(()) + Ok(root_id) } } -impl Request for GetPrevRootId { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { - let tree = conn +impl Action for GetPrevRootId { + type Result = Option; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { + let root_id = conn .prepare( " SELECT id @@ -781,14 +697,15 @@ impl Request for GetPrevRootId { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; - let _ = self.result.send(tree); - Ok(()) + Ok(root_id) } } -impl Request for GetNextRootId { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { - let tree = conn +impl Action for GetNextRootId { + type Result = Option; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { + let root_id = conn .prepare( " SELECT id @@ -803,14 +720,15 @@ impl Request for GetNextRootId { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; - let _ = self.result.send(tree); - Ok(()) + Ok(root_id) } } -impl Request for GetOldestMsgId { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { - let tree = conn +impl Action for GetOldestMsgId { + type Result = Option; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { + let msg_id = conn .prepare( " SELECT id @@ -824,14 +742,15 @@ impl Request for GetOldestMsgId { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; - let _ = self.result.send(tree); - Ok(()) + Ok(msg_id) } } -impl Request for GetNewestMsgId { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { - let tree = conn +impl Action for GetNewestMsgId { + type Result = Option; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { + let msg_id = conn .prepare( " SELECT id @@ -845,14 +764,15 @@ impl Request for GetNewestMsgId { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; - let _ = self.result.send(tree); - Ok(()) + Ok(msg_id) } } -impl Request for GetOlderMsgId { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { - let tree = conn +impl Action for GetOlderMsgId { + type Result = Option; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { + let msg_id = conn .prepare( " SELECT id @@ -867,13 +787,14 @@ impl Request for GetOlderMsgId { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; - let _ = self.result.send(tree); - Ok(()) + Ok(msg_id) } } -impl Request for GetNewerMsgId { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { - let tree = conn +impl Action for GetNewerMsgId { + type Result = Option; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { + let msg_id = conn .prepare( " SELECT id @@ -888,14 +809,15 @@ impl Request for GetNewerMsgId { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; - let _ = self.result.send(tree); - Ok(()) + Ok(msg_id) } } -impl Request for GetOldestUnseenMsgId { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { - let tree = conn +impl Action for GetOldestUnseenMsgId { + type Result = Option; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { + let msg_id = conn .prepare( " SELECT id @@ -910,14 +832,15 @@ impl Request for GetOldestUnseenMsgId { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; - let _ = self.result.send(tree); - Ok(()) + Ok(msg_id) } } -impl Request for GetNewestUnseenMsgId { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { - let tree = conn +impl Action for GetNewestUnseenMsgId { + type Result = Option; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { + let msg_id = conn .prepare( " SELECT id @@ -932,14 +855,15 @@ impl Request for GetNewestUnseenMsgId { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; - let _ = self.result.send(tree); - Ok(()) + Ok(msg_id) } } -impl Request for GetOlderUnseenMsgId { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { - let tree = conn +impl Action for GetOlderUnseenMsgId { + type Result = Option; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { + let msg_id = conn .prepare( " SELECT id @@ -955,14 +879,15 @@ impl Request for GetOlderUnseenMsgId { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; - let _ = self.result.send(tree); - Ok(()) + Ok(msg_id) } } -impl Request for GetNewerUnseenMsgId { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { - let tree = conn +impl Action for GetNewerUnseenMsgId { + type Result = Option; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { + let msg_id = conn .prepare( " SELECT id @@ -978,13 +903,14 @@ impl Request for GetNewerUnseenMsgId { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; - let _ = self.result.send(tree); - Ok(()) + Ok(msg_id) } } -impl Request for GetUnseenMsgsCount { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +impl Action for GetUnseenMsgsCount { + type Result = usize; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { let amount = conn .prepare( " @@ -996,13 +922,14 @@ impl Request for GetUnseenMsgsCount { .query_row(params![self.room], |row| row.get(0)) .optional()? .unwrap_or(0); - let _ = self.result.send(amount); - Ok(()) + Ok(amount) } } -impl Request for SetSeen { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +impl Action for SetSeen { + type Result = (); + + fn run(self, conn: &mut Connection) -> rusqlite::Result { conn.execute( " UPDATE euph_msgs @@ -1016,8 +943,10 @@ impl Request for SetSeen { } } -impl Request for SetOlderSeen { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +impl Action for SetOlderSeen { + type Result = (); + + fn run(self, conn: &mut Connection) -> rusqlite::Result { conn.execute( " UPDATE euph_msgs @@ -1032,8 +961,10 @@ impl Request for SetOlderSeen { } } -impl Request for GetChunkAtOffset { - fn perform(self, conn: &mut Connection) -> rusqlite::Result<()> { +impl Action for GetChunkAtOffset { + type Result = Vec; + + fn run(self, conn: &mut Connection) -> rusqlite::Result { let mut query = conn.prepare( " SELECT @@ -1073,7 +1004,83 @@ impl Request for GetChunkAtOffset { }) })? .collect::>()?; - let _ = self.result.send(messages); - Ok(()) + Ok(messages) + } +} + +#[async_trait] +impl MsgStore for EuphRoomVault { + type Error = vault::tokio::Error; + + async fn path(&self, id: &MessageId) -> Result, Self::Error> { + self.path(*id).await + } + + async fn msg(&self, id: &MessageId) -> Result, Self::Error> { + self.msg(*id).await + } + + async fn tree(&self, root_id: &MessageId) -> Result, Self::Error> { + self.tree(*root_id).await + } + + async fn first_root_id(&self) -> Result, Self::Error> { + self.first_root_id().await + } + + async fn last_root_id(&self) -> Result, Self::Error> { + self.last_root_id().await + } + + async fn prev_root_id(&self, root_id: &MessageId) -> Result, Self::Error> { + self.prev_root_id(*root_id).await + } + + async fn next_root_id(&self, root_id: &MessageId) -> Result, Self::Error> { + self.next_root_id(*root_id).await + } + + async fn oldest_msg_id(&self) -> Result, Self::Error> { + self.oldest_msg_id().await + } + + async fn newest_msg_id(&self) -> Result, Self::Error> { + self.newest_msg_id().await + } + + async fn older_msg_id(&self, id: &MessageId) -> Result, Self::Error> { + self.older_msg_id(*id).await + } + + async fn newer_msg_id(&self, id: &MessageId) -> Result, Self::Error> { + self.newer_msg_id(*id).await + } + + async fn oldest_unseen_msg_id(&self) -> Result, Self::Error> { + self.oldest_unseen_msg_id().await + } + + async fn newest_unseen_msg_id(&self) -> Result, Self::Error> { + self.newest_unseen_msg_id().await + } + + async fn older_unseen_msg_id(&self, id: &MessageId) -> Result, Self::Error> { + self.older_unseen_msg_id(*id).await + } + + async fn newer_unseen_msg_id(&self, id: &MessageId) -> Result, Self::Error> { + self.newer_unseen_msg_id(*id).await + } + + async fn unseen_msgs_count(&self) -> Result { + self.unseen_msgs_count().await + } + + async fn set_seen(&self, id: &MessageId, seen: bool) -> Result<(), Self::Error> { + self.set_seen(*id, seen).await + } + + async fn set_older_seen(&self, id: &MessageId, seen: bool) -> Result<(), Self::Error> { + self.set_older_seen(*id, seen).await } } diff --git a/src/vault/migrate.rs b/src/vault/migrate.rs index 8cd42c6..e5d16da 100644 --- a/src/vault/migrate.rs +++ b/src/vault/migrate.rs @@ -1,25 +1,10 @@ -use rusqlite::{Connection, Transaction}; +use rusqlite::Transaction; +use vault::Migration; -pub fn migrate(conn: &mut Connection) -> rusqlite::Result<()> { - let mut tx = conn.transaction()?; +pub const MIGRATIONS: [Migration; 2] = [m1, m2]; - let user_version: usize = - tx.query_row("SELECT * FROM pragma_user_version", [], |r| r.get(0))?; - - let total = MIGRATIONS.len(); - assert!(user_version <= total, "malformed database schema"); - for (i, migration) in MIGRATIONS.iter().enumerate().skip(user_version) { - eprintln!("Migrating vault from {} to {} (out of {})", i, i + 1, total); - migration(&mut tx)?; - } - - tx.pragma_update(None, "user_version", total)?; - tx.commit() -} - -const MIGRATIONS: [fn(&mut Transaction<'_>) -> rusqlite::Result<()>; 2] = [m1, m2]; - -fn m1(tx: &mut Transaction<'_>) -> rusqlite::Result<()> { +fn m1(tx: &mut Transaction<'_>, nr: usize, total: usize) -> rusqlite::Result<()> { + eprintln!("Migrating vault from {} to {} (out of {total})", nr, nr + 1); tx.execute_batch( " CREATE TABLE euph_rooms ( @@ -81,7 +66,8 @@ fn m1(tx: &mut Transaction<'_>) -> rusqlite::Result<()> { ) } -fn m2(tx: &mut Transaction<'_>) -> rusqlite::Result<()> { +fn m2(tx: &mut Transaction<'_>, nr: usize, total: usize) -> rusqlite::Result<()> { + eprintln!("Migrating vault from {} to {} (out of {total})", nr, nr + 1); tx.execute_batch( " ALTER TABLE euph_msgs From fb164eeaa9c0edd3b39e266ed8c94dd341f4df57 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 12 Feb 2023 23:22:23 +0100 Subject: [PATCH 036/266] Add todos --- src/ui.rs | 2 ++ src/ui/widgets.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/ui.rs b/src/ui.rs index 5d9007e..a9caaf9 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -51,6 +51,8 @@ enum Mode { Log, } +// TODO Add Error for anything that can go wrong while rendering + pub struct Ui { event_tx: UnboundedSender, diff --git a/src/ui/widgets.rs b/src/ui/widgets.rs index f9ebba1..33c2c49 100644 --- a/src/ui/widgets.rs +++ b/src/ui/widgets.rs @@ -21,6 +21,8 @@ pub mod text; use async_trait::async_trait; use toss::frame::{Frame, Size}; +// TODO Add Error type and return Result-s (at least in Widget::render) + #[async_trait] pub trait Widget { fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size; From d74282581c9fdb520fe82dbb0d2defcbb984ef32 Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 17 Feb 2023 15:56:09 +0100 Subject: [PATCH 037/266] Deduplicate code --- src/euph/room.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/euph/room.rs b/src/euph/room.rs index c26685f..fa577d0 100644 --- a/src/euph/room.rs +++ b/src/euph/room.rs @@ -235,7 +235,7 @@ impl Room { d.from_nick, d.from_room ); } - Data::SendEvent(SendEvent(msg)) => { + Data::SendEvent(SendEvent(msg)) | Data::SendReply(SendReply(msg)) => { let own_user_id = self.own_user_id(); if let Some(last_msg_id) = &mut self.last_msg_id { logging_unwrap!( @@ -263,17 +263,6 @@ impl Room { .await ); } - Data::SendReply(SendReply(msg)) => { - let own_user_id = self.own_user_id(); - if let Some(last_msg_id) = &mut self.last_msg_id { - logging_unwrap!( - self.vault - .add_msg(Box::new(msg.clone()), *last_msg_id, own_user_id) - .await - ); - *last_msg_id = Some(msg.id); - } - } _ => {} } } From 293112777a25cb290efa9a3e185164b56cf389dd Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 17 Feb 2023 17:29:12 +0100 Subject: [PATCH 038/266] Fix bugged room state from lingering connection When disconnecting from a room whose instance is "waiting" and then reconnecting, the old instance would not be stopped immediately. Instead, it would continue to run until it managed to reconnect, sending status updates to the main event bus in the process. These events led to the euph::Room entering a state where it was connected but no last_msg_id was set. This meant that no new messages could be entered into the vault, including messages sent by the user. The result was UI weirdness when sending a message. As a fix, euphoxide instances are now identified via an u32 id. This id is unique across all rooms. Packets by unknown ids are rejected and have no effect on room states. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/euph/room.rs | 22 +++++++++------- src/ui/euph/room.rs | 44 +++++++++++++++++++++----------- src/ui/rooms.rs | 61 ++++++++++++++++++++++++++++++++------------- 5 files changed, 87 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20b9f53..42a6604 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -320,7 +320,7 @@ dependencies = [ [[package]] name = "euphoxide" version = "0.3.0" -source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.3.0#c5be90cd60ff1fac78c2063c812d88dd36858481" +source = "git+https://github.com/Garmelon/euphoxide.git?rev=069e4e02c77e5bc649cd770af8183d8be8cd52f5#069e4e02c77e5bc649cd770af8183d8be8cd52f5" dependencies = [ "async-trait", "caseless", diff --git a/Cargo.toml b/Cargo.toml index 4fd5a56..bc38f83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ features = ["rustls-tls-native-roots"] [dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -tag = "v0.3.0" +rev = "069e4e02c77e5bc649cd770af8183d8be8cd52f5" features = ["bot"] # [patch."https://github.com/Garmelon/euphoxide.git"] diff --git a/src/euph/room.rs b/src/euph/room.rs index fa577d0..e38c2ed 100644 --- a/src/euph/room.rs +++ b/src/euph/room.rs @@ -85,6 +85,10 @@ impl Room { self.instance.stopped() } + pub fn instance(&self) -> &Instance { + &self.instance + } + pub fn state(&self) -> &State { &self.state } @@ -108,7 +112,7 @@ impl Room { self.log_request_canary = Some(tx); let vault_clone = self.vault.clone(); let conn_tx_clone = conn_tx.clone(); - debug!("{}: spawning log request task", self.instance.config().name); + debug!("{}: spawning log request task", self.instance.config().room); tokio::task::spawn(async move { select! { _ = rx => {}, @@ -204,34 +208,34 @@ impl Room { } async fn on_packet(&mut self, packet: ParsedPacket) { - let instance_name = &self.instance.config().name; + let room_name = &self.instance.config().room; let data = ok_or_return!(&packet.content); match data { Data::BounceEvent(_) => {} Data::DisconnectEvent(_) => {} Data::HelloEvent(_) => {} Data::JoinEvent(d) => { - debug!("{instance_name}: {:?} joined", d.0.name); + debug!("{room_name}: {:?} joined", d.0.name); } Data::LoginEvent(_) => {} Data::LogoutEvent(_) => {} Data::NetworkEvent(d) => { - warn!("{instance_name}: network event ({})", d.r#type); + warn!("{room_name}: network event ({})", d.r#type); } Data::NickEvent(d) => { - debug!("{instance_name}: {:?} renamed to {:?}", d.from, d.to); + debug!("{room_name}: {:?} renamed to {:?}", d.from, d.to); } Data::EditMessageEvent(_) => { - info!("{instance_name}: a message was edited"); + info!("{room_name}: a message was edited"); } Data::PartEvent(d) => { - debug!("{instance_name}: {:?} left", d.0.name); + debug!("{room_name}: {:?} left", d.0.name); } Data::PingEvent(_) => {} Data::PmInitiateEvent(d) => { // TODO Show info popup and automatically join PM room info!( - "{instance_name}: {:?} initiated a pm from &{}", + "{room_name}: {:?} initiated a pm from &{}", d.from_nick, d.from_room ); } @@ -247,7 +251,7 @@ impl Room { } } Data::SnapshotEvent(d) => { - info!("{instance_name}: successfully joined"); + info!("{room_name}: successfully joined"); logging_unwrap!(self.vault.join(Time::now()).await); self.last_msg_id = Some(d.log.last().map(|m| m.id)); logging_unwrap!( diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 59a113a..0d48d57 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -95,15 +95,16 @@ impl EuphRoom { self.vault().room() } - pub fn connect(&mut self) { + pub fn connect(&mut self, next_instance_id: &mut u32) { if self.room.is_none() { let instance_config = self .server_config .clone() - .room(self.vault().room().to_string()) + .room(*next_instance_id, self.vault().room().to_string()) .username(self.config.username.clone()) .force_username(self.config.force_username) .password(self.config.password.clone()); + *next_instance_id = next_instance_id.wrapping_add(1); let tx = self.ui_event_tx.clone(); self.room = Some(euph::Room::new( @@ -674,23 +675,36 @@ impl EuphRoom { } pub async fn handle_event(&mut self, event: Event) -> bool { - let handled = if self.room.is_some() { - if let Event::Packet(_, packet, _) = &event { - match &packet.content { - Ok(data) => self.handle_euph_data(data), - Err(reason) => self.handle_euph_error(packet.r#type, reason), - } - } else { - true - } - } else { - false + let room = match &self.room { + None => return false, + Some(room) => room, }; - if let Some(room) = &mut self.room { - room.handle_event(event).await; + if event.config().id != room.instance().config().id { + // If we allowed ids other than the current one, old instances that + // haven't yet shut down properly could mess up our state. + return false; } + // We handle the packet internally first because the room event handling + // will consume it while we only need a reference. + let handled = if let Event::Packet(_, packet, _) = &event { + match &packet.content { + Ok(data) => self.handle_euph_data(data), + Err(reason) => self.handle_euph_error(packet.r#type, reason), + } + } else { + // The room state changes, which always means a redraw. + true + }; + + self.room + .as_mut() + // See check at the beginning of the function. + .expect("no room even though we checked earlier") + .handle_event(event) + .await; + handled } diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index 56dac95..2718eb0 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -61,6 +61,7 @@ pub struct Rooms { order: Order, euph_server_config: ServerConfig, + euph_next_instance_id: u32, euph_rooms: HashMap, } @@ -81,13 +82,14 @@ impl Rooms { list: ListState::new(), order: Order::from_rooms_sort_order(config.rooms_sort_order), euph_server_config, + euph_next_instance_id: 0, euph_rooms: HashMap::new(), }; if !config.offline { for (name, config) in &config.euph.rooms { if config.autojoin { - result.get_or_insert_room(name.clone()).connect(); + result.connect_to_room(name.clone()); } } } @@ -106,6 +108,36 @@ impl Rooms { }) } + fn connect_to_room(&mut self, name: String) { + let room = self.euph_rooms.entry(name.clone()).or_insert_with(|| { + EuphRoom::new( + self.euph_server_config.clone(), + self.config.euph_room(&name), + self.vault.euph().room(name), + self.ui_event_tx.clone(), + ) + }); + room.connect(&mut self.euph_next_instance_id); + } + + fn connect_to_all_rooms(&mut self) { + for room in self.euph_rooms.values_mut() { + room.connect(&mut self.euph_next_instance_id); + } + } + + fn disconnect_from_room(&mut self, name: &str) { + if let Some(room) = self.euph_rooms.get_mut(name) { + room.disconnect(); + } + } + + fn disconnect_from_all_rooms(&mut self) { + for room in self.euph_rooms.values_mut() { + room.disconnect(); + } + } + /// Remove rooms that are not running any more and can't be found in the db. /// Insert rooms that are in the db but not yet in in the hash map. /// @@ -370,36 +402,28 @@ impl Rooms { } key!('c') => { if let Some(name) = self.list.cursor() { - if let Some(room) = self.euph_rooms.get_mut(&name) { - room.connect(); - } + self.connect_to_room(name); } return true; } key!('C') => { - for room in self.euph_rooms.values_mut() { - room.connect(); - } + self.connect_to_all_rooms(); return true; } key!('d') => { if let Some(name) = self.list.cursor() { - if let Some(room) = self.euph_rooms.get_mut(&name) { - room.disconnect(); - } + self.disconnect_from_room(&name); } return true; } key!('D') => { - for room in self.euph_rooms.values_mut() { - room.disconnect(); - } + self.disconnect_from_all_rooms(); return true; } key!('a') => { for (name, options) in &self.config.euph.rooms { if options.autojoin { - self.get_or_insert_room(name.clone()).connect(); + self.connect_to_room(name.clone()); } } return true; @@ -511,7 +535,7 @@ impl Rooms { key!(Enter) => { let name = ed.text(); if !name.is_empty() { - self.get_or_insert_room(name.clone()).connect(); + self.connect_to_room(name.clone()); self.state = State::ShowRoom(name); } return true; @@ -545,12 +569,13 @@ impl Rooms { } pub async fn handle_euph_event(&mut self, event: Event) -> bool { - let instance_name = event.config().name.clone(); - let room = self.get_or_insert_room(instance_name.clone()); + let room_name = event.config().room.clone(); + let Some(room) = self.euph_rooms.get_mut(&room_name) else { return false; }; + let handled = room.handle_event(event).await; let room_visible = match &self.state { - State::ShowRoom(name) => *name == instance_name, + State::ShowRoom(name) => *name == room_name, _ => true, }; handled && room_visible From 5738fe391a2338c31f722cb30388fb9782c47287 Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 17 Feb 2023 18:01:58 +0100 Subject: [PATCH 039/266] Include instance log messages again --- src/logger.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/logger.rs b/src/logger.rs index c80d794..f1d3cc0 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -191,7 +191,10 @@ impl Log for Logger { } let target = metadata.target(); - if target.starts_with("cove") || target.starts_with("euphoxide::bot") { + if target.starts_with("cove") + || target.starts_with("euphoxide::bot") + || target.starts_with("euphoxide::live") + { return true; } From 7568fb3434c2816ecfbbf1c22e5f9c2d894be8a3 Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 17 Feb 2023 23:04:58 +0100 Subject: [PATCH 040/266] Add todo --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index 6023c76..945ba7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ // TODO Enable warn(unreachable_pub)? // TODO Remove unnecessary Debug impls and compare compile times // TODO Time zones other than UTC +// TODO Fix password room auth mod config; mod euph; From 65fa1b8afda0c67f693b79f108fe93297daeec46 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 26 Feb 2023 20:51:28 +0100 Subject: [PATCH 041/266] Update euphoxide This fixes authentication for rooms requiring passwords --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- src/ui/euph/room.rs | 12 +++++++----- src/ui/rooms.rs | 2 +- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42a6604..45fdbf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,9 +114,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.1.4" +version = "4.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" +checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" dependencies = [ "bitflags", "clap_derive", @@ -319,8 +319,8 @@ dependencies = [ [[package]] name = "euphoxide" -version = "0.3.0" -source = "git+https://github.com/Garmelon/euphoxide.git?rev=069e4e02c77e5bc649cd770af8183d8be8cd52f5#069e4e02c77e5bc649cd770af8183d8be8cd52f5" +version = "0.3.1" +source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.3.1#1844cddcc01696ef4d04c957de8b84d6247d9332" dependencies = [ "async-trait", "caseless", @@ -1146,9 +1146,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ "itoa", "serde", @@ -1164,9 +1164,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" dependencies = [ "time-core", ] @@ -1230,9 +1230,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" dependencies = [ "futures-core", "pin-project-lite", diff --git a/Cargo.toml b/Cargo.toml index bc38f83..f8bc8e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ features = ["rustls-tls-native-roots"] [dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -rev = "069e4e02c77e5bc649cd770af8183d8be8cd52f5" +tag = "v0.3.1" features = ["bot"] # [patch."https://github.com/Garmelon/euphoxide.git"] diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 0d48d57..a1aa873 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -95,12 +95,14 @@ impl EuphRoom { self.vault().room() } - pub fn connect(&mut self, next_instance_id: &mut u32) { + pub fn connect(&mut self, next_instance_id: &mut usize) { if self.room.is_none() { + let room = self.vault().room(); let instance_config = self .server_config .clone() - .room(*next_instance_id, self.vault().room().to_string()) + .room(self.vault().room().to_string()) + .name(format!("{room}-{}", next_instance_id)) .username(self.config.username.clone()) .force_username(self.config.force_username) .password(self.config.password.clone()); @@ -680,9 +682,9 @@ impl EuphRoom { Some(room) => room, }; - if event.config().id != room.instance().config().id { - // If we allowed ids other than the current one, old instances that - // haven't yet shut down properly could mess up our state. + if event.config().name != room.instance().config().name { + // If we allowed names other than the current one, old instances + // that haven't yet shut down properly could mess up our state. return false; } diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index 2718eb0..60cb9ee 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -61,7 +61,7 @@ pub struct Rooms { order: Order, euph_server_config: ServerConfig, - euph_next_instance_id: u32, + euph_next_instance_id: usize, euph_rooms: HashMap, } From 582cac84210f6f4c5a0b7b807e6df9397ad1eca2 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 4 Mar 2023 18:45:07 +0100 Subject: [PATCH 042/266] Turn repo into flake --- CHANGELOG.md | 1 + README.md | 9 ++++++++ flake.lock | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 21 ++++++++++++++++++ 4 files changed, 94 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/CHANGELOG.md b/CHANGELOG.md index df432be..f9bf818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Procedure when bumping the version number: - `--verbose` flag - `json-stream` room export format - Option to export to stdout +- `flake.nix`, making cove available as a nix flake ### Changed - Respect colon-delimited emoji when calculating nick hue diff --git a/README.md b/README.md index e9b1ee8..4659f61 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,15 @@ things in) won't automatically shrink. If it takes up too much space, try running `cove gc` and waiting for it to finish. This isn't done automatically because it can take quite a while. +## Installation + +At this point, cove is not available via any package manager. + +Cove is available as a Nix Flake. To try it out, you can use +```bash +$ nix run --override-input nixpkgs nixpkgs github:Garmelon/cove/latest +``` + ## Manual installation This section contains instructions on how to install cove by compiling it yourself. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..ae07f3b --- /dev/null +++ b/flake.lock @@ -0,0 +1,63 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1676283394, + "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "naersk": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1671096816, + "narHash": "sha256-ezQCsNgmpUHdZANDCILm3RvtO1xH8uujk/+EqNvzIOg=", + "owner": "nix-community", + "repo": "naersk", + "rev": "d998160d6a076cfe8f9741e56aeec7e267e3e114", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "naersk", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1677950239, + "narHash": "sha256-TAX92aTbRyRD8dF6luUyYdSXPuULpntWQukknm2AliM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5aee86add3e576f6b1862d1e6c005e63c409f669", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "naersk": "naersk", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..17b2241 --- /dev/null +++ b/flake.nix @@ -0,0 +1,21 @@ +{ + description = "TUI client for euphoria.io, a threaded real-time chat platform"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs"; + flake-utils.url = "github:numtide/flake-utils"; + + naersk.url = "github:nix-community/naersk"; + naersk.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = { self, nixpkgs, flake-utils, naersk }: flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + naersk' = pkgs.callPackage naersk { }; + in + rec { + packages.default = naersk'.buildPackage { src = ./.; }; + } + ); +} From da3d84c9d87a146bdc091b37fb38c45bcf013f63 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 4 Mar 2023 22:00:37 +0100 Subject: [PATCH 043/266] Fix connecting to rooms as bot instead of human --- src/ui/euph/room.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index a1aa873..740d9f7 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -103,6 +103,7 @@ impl EuphRoom { .clone() .room(self.vault().room().to_string()) .name(format!("{room}-{}", next_instance_id)) + .human(true) .username(self.config.username.clone()) .force_username(self.config.force_username) .password(self.config.password.clone()); From 0612d235d74b84e0521ffd969443df4e820f088f Mon Sep 17 00:00:00 2001 From: Joscha Date: Tue, 7 Mar 2023 14:21:28 +0100 Subject: [PATCH 044/266] Recognize links without scheme --- CHANGELOG.md | 1 + src/ui/euph/links.rs | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9bf818..b46fa15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Procedure when bumping the version number: - Respect colon-delimited emoji when calculating nick hue - Display colon-delimited emoji in nicks and messages - Non-export info is now printed to stderr instead of stdout +- Recognizes links without scheme (e. g. `euphoria.io` instead of `https://euphoria.io`) ### Fixed - Mentions not being stopped by `>` diff --git a/src/ui/euph/links.rs b/src/ui/euph/links.rs index 553e7ff..b1ebcb0 100644 --- a/src/ui/euph/links.rs +++ b/src/ui/euph/links.rs @@ -27,8 +27,9 @@ const NUMBER_KEYS: [char; 10] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0 impl LinksState { pub fn new(content: &str) -> Self { let links = LinkFinder::new() + .url_must_have_scheme(false) + .kinds(&[LinkKind::Url]) .links(content) - .filter(|l| *l.kind() == LinkKind::Url) .map(|l| l.as_str().to_string()) .collect(); @@ -76,11 +77,16 @@ impl LinksState { fn open_link_by_id(&self, id: usize) -> EventResult { if let Some(link) = self.links.get(id) { - if let Err(error) = open::that(link) { - return EventResult::ErrorOpeningLink { - link: link.to_string(), - error, - }; + // The `http://` or `https://` schema is necessary for open::that to + // successfully open the link in the browser. + let link = if link.starts_with("http://") || link.starts_with("https://") { + link.clone() + } else { + format!("https://{link}") + }; + + if let Err(error) = open::that(&link) { + return EventResult::ErrorOpeningLink { link, error }; } } EventResult::Handled From 1e90e76fba27585ceaf66a3fb3bc02a9cda9d921 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 12 Mar 2023 16:36:34 +0100 Subject: [PATCH 045/266] Fix rooms being stuck in "Connecting" state I haven't managed to reliably reproduce this bug, so I don't know if this actually fixes it. --- CHANGELOG.md | 3 +++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b46fa15..1fe6296 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,9 @@ Procedure when bumping the version number: ### Changed - Some key bindings in the rooms list +### Fixed +- Rooms being stuck in "Connecting" state + ## v0.3.0 - 2022-08-22 ### Added diff --git a/Cargo.lock b/Cargo.lock index 45fdbf9..4b04ccc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -320,7 +320,7 @@ dependencies = [ [[package]] name = "euphoxide" version = "0.3.1" -source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.3.1#1844cddcc01696ef4d04c957de8b84d6247d9332" +source = "git+https://github.com/Garmelon/euphoxide.git?rev=768a259f02f5743f7812904657a99f1f58b8e835#768a259f02f5743f7812904657a99f1f58b8e835" dependencies = [ "async-trait", "caseless", diff --git a/Cargo.toml b/Cargo.toml index f8bc8e5..629bd00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ features = ["rustls-tls-native-roots"] [dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -tag = "v0.3.1" +rev = "768a259f02f5743f7812904657a99f1f58b8e835" features = ["bot"] # [patch."https://github.com/Garmelon/euphoxide.git"] From 4e2b597f1efff9987e03bfce1c50499f6f63ecae Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 17 Mar 2023 18:27:01 +0100 Subject: [PATCH 046/266] Fix waiting rooms being sorted to bottom --- CHANGELOG.md | 1 + src/ui/rooms.rs | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fe6296..2ad3c38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Procedure when bumping the version number: - Display colon-delimited emoji in nicks and messages - Non-export info is now printed to stderr instead of stdout - Recognizes links without scheme (e. g. `euphoria.io` instead of `https://euphoria.io`) +- Rooms waiting for reconnect are no longer sorted to bottom in default sort order ### Fixed - Mentions not being stopped by `>` diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index 60cb9ee..eac0a75 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -317,10 +317,9 @@ impl Rooms { fn sort_rooms(&self, rooms: &mut [(&String, Option<&euph::State>, usize)]) { match self.order { - Order::Alphabet => rooms.sort_unstable_by_key(|(n, _, _)| *n), - Order::Importance => rooms.sort_unstable_by_key(|(n, s, u)| { - let no_instance = matches!(s, None | Some(euph::State::Disconnected)); - (no_instance, *u == 0, *n) + Order::Alphabet => rooms.sort_unstable_by_key(|(name, _, _)| *name), + Order::Importance => rooms.sort_unstable_by_key(|(name, state, unseen)| { + (state.is_none(), *unseen == 0, *name) }), } } From 3eb33f14e6677f878058aac55b6f22ccea90104e Mon Sep 17 00:00:00 2001 From: Joscha Date: Tue, 4 Apr 2023 23:26:28 +0200 Subject: [PATCH 047/266] Refine changelog --- CHANGELOG.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ad3c38..2bb7d3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,14 +15,13 @@ Procedure when bumping the version number: ## Unreleased ### Added -- `--verbose` flag -- `json-stream` room export format -- Option to export to stdout +- Emoji support - `flake.nix`, making cove available as a nix flake +- `json-stream` room export format +- Option to export to stdout via `--out -` +- `--verbose` flag ### Changed -- Respect colon-delimited emoji when calculating nick hue -- Display colon-delimited emoji in nicks and messages - Non-export info is now printed to stderr instead of stdout - Recognizes links without scheme (e. g. `euphoria.io` instead of `https://euphoria.io`) - Rooms waiting for reconnect are no longer sorted to bottom in default sort order From a487eeb85d0c4c1cb74349a9dd637aa5f95d205b Mon Sep 17 00:00:00 2001 From: Joscha Date: Tue, 4 Apr 2023 23:08:46 +0200 Subject: [PATCH 048/266] Update dependencies --- Cargo.lock | 441 ++++++++++++++++++++++++++++------------------------- Cargo.toml | 26 ++-- flake.lock | 18 +-- 3 files changed, 256 insertions(+), 229 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b04ccc..cf612ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,20 +23,60 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.69" +name = "anstream" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-wincon", + "concolor-override", + "concolor-query", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" + +[[package]] +name = "anstyle-parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-wincon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +dependencies = [ + "anstyle", + "windows-sys 0.45.0", +] + +[[package]] +name = "anyhow" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "async-trait" -version = "0.1.64" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] @@ -65,9 +105,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] @@ -114,39 +154,59 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.1.6" +version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" +checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" dependencies = [ - "bitflags", + "clap_builder", "clap_derive", - "clap_lex", - "is-terminal", "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", "strsim", - "termcolor", ] [[package]] name = "clap_derive" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] name = "clap_lex" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + +[[package]] +name = "concolor-override" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" dependencies = [ - "os_str_bytes", + "windows-sys 0.45.0", ] [[package]] @@ -171,9 +231,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cove" @@ -208,18 +268,18 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" dependencies = [ "libc", ] [[package]] name = "crossterm" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f67c7faacd4db07a939f55d66a983a5355358a1f17d32cc9a8d01d1266b9ce" +checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" dependencies = [ "bitflags", "crossterm_winapi", @@ -262,22 +322,22 @@ dependencies = [ [[package]] name = "directories" -version = "4.0.1" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +checksum = "74be3be809c18e089de43bdc504652bb2bc473fca8756131f8689db8cf079ba9" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.3.7" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" dependencies = [ "libc", "redox_users", - "winapi", + "windows-sys 0.45.0", ] [[package]] @@ -298,13 +358,13 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "errno" -version = "0.2.8" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.45.0", ] [[package]] @@ -351,9 +411,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] @@ -375,27 +435,27 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-sink" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", "futures-sink", @@ -407,9 +467,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -467,9 +527,9 @@ checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -494,9 +554,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -513,19 +573,20 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" dependencies = [ + "hermit-abi 0.3.1", "libc", "windows-sys 0.45.0", ] [[package]] name = "is-terminal" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" +checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", @@ -535,9 +596,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" @@ -550,9 +611,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.139" +version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "libsqlite3-sys" @@ -576,9 +637,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" [[package]] name = "lock_api" @@ -607,23 +668,14 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "mio" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.42.0", -] - -[[package]] -name = "nom8" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" -dependencies = [ - "memchr", + "windows-sys 0.45.0", ] [[package]] @@ -638,18 +690,17 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "open" -version = "3.2.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" +checksum = "075c5203b3a2b698bc72c6c10b1f6263182135751d5013ea66e8a4b3d0562a43" dependencies = [ "pathdiff", - "windows-sys 0.42.0", ] [[package]] @@ -658,12 +709,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" - [[package]] name = "parking_lot" version = "0.12.1" @@ -682,7 +727,7 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "windows-sys 0.45.0", ] @@ -723,44 +768,20 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -804,6 +825,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -811,15 +841,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", - "redox_syscall", + "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.7.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "aho-corasick", "memchr", @@ -828,18 +858,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "ring" @@ -873,9 +894,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.8" +version = "0.37.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" +checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" dependencies = [ "bitflags", "errno", @@ -920,9 +941,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "schannel" @@ -974,29 +995,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.152" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] name = "serde_json" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" dependencies = [ "itoa", "ryu", @@ -1055,9 +1076,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -1070,9 +1091,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -1092,9 +1113,20 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" dependencies = [ "proc-macro2", "quote", @@ -1103,45 +1135,35 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", ] [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] @@ -1188,14 +1210,13 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.25.0" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", "parking_lot", @@ -1203,18 +1224,18 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] @@ -1257,9 +1278,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" dependencies = [ "serde", "serde_spanned", @@ -1278,15 +1299,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.3" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6a7712b49e1775fb9a7b998de6635b299237f48b404dde71704f2e0e7f37e5" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" dependencies = [ "indexmap", - "nom8", "serde", "serde_spanned", "toml_datetime", + "winnow", ] [[package]] @@ -1329,15 +1350,15 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-linebreak" @@ -1393,6 +1414,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "vault" version = "0.1.0" @@ -1441,7 +1468,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -1463,7 +1490,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1521,15 +1548,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1562,9 +1580,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1577,42 +1595,51 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index 629bd00..c336af8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,29 +4,29 @@ version = "0.5.2" edition = "2021" [dependencies] -anyhow = "1.0.69" -async-trait = "0.1.64" -clap = { version = "4.1.4", features = ["derive", "deprecated"] } +anyhow = "1.0.70" +async-trait = "0.1.68" +clap = { version = "4.2.1", features = ["derive", "deprecated"] } cookie = "0.17.0" -crossterm = "0.26.0" -directories = "4.0.1" +crossterm = "0.26.1" +directories = "5.0.0" edit = "0.1.4" linkify = "0.9.0" log = { version = "0.4.17", features = ["std"] } -once_cell = "1.17.0" -open = "3.2.0" +once_cell = "1.17.1" +open = "4.0.1" parking_lot = "0.12.1" rusqlite = { version = "0.28.0", features = ["bundled", "time"] } -serde = { version = "1.0.152", features = ["derive"] } -serde_json = "1.0.93" -thiserror = "1.0.38" -tokio = { version = "1.25.0", features = ["full"] } -toml = "0.7.2" +serde = { version = "1.0.159", features = ["derive"] } +serde_json = "1.0.95" +thiserror = "1.0.40" +tokio = { version = "1.27.0", features = ["full"] } +toml = "0.7.3" unicode-segmentation = "1.10.1" unicode-width = "0.1.10" [dependencies.time] -version = "0.3.17" +version = "0.3.20" features = ["macros", "formatting", "parsing", "serde"] [dependencies.tokio-tungstenite] diff --git a/flake.lock b/flake.lock index ae07f3b..7196112 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "flake-utils": { "locked": { - "lastModified": 1676283394, - "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=", + "lastModified": 1678901627, + "narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=", "owner": "numtide", "repo": "flake-utils", - "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073", + "rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6", "type": "github" }, "original": { @@ -22,11 +22,11 @@ ] }, "locked": { - "lastModified": 1671096816, - "narHash": "sha256-ezQCsNgmpUHdZANDCILm3RvtO1xH8uujk/+EqNvzIOg=", + "lastModified": 1679567394, + "narHash": "sha256-ZvLuzPeARDLiQUt6zSZFGOs+HZmE+3g4QURc8mkBsfM=", "owner": "nix-community", "repo": "naersk", - "rev": "d998160d6a076cfe8f9741e56aeec7e267e3e114", + "rev": "88cd22380154a2c36799fe8098888f0f59861a15", "type": "github" }, "original": { @@ -37,11 +37,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1677950239, - "narHash": "sha256-TAX92aTbRyRD8dF6luUyYdSXPuULpntWQukknm2AliM=", + "lastModified": 1680643271, + "narHash": "sha256-m76rYcvqs+NzTyETfxh1o/9gKdBuJ/Hl+PI/kp73mDw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5aee86add3e576f6b1862d1e6c005e63c409f669", + "rev": "246567a3ad88e3119c2001e2fe78be233474cde0", "type": "github" }, "original": { From 9f9c3d998e73e83cc6dc8421f99b011e522068f8 Mon Sep 17 00:00:00 2001 From: Joscha Date: Tue, 4 Apr 2023 23:30:26 +0200 Subject: [PATCH 049/266] Bump version to 0.6.0 --- CHANGELOG.md | 2 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bb7d3d..826d211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ Procedure when bumping the version number: ## Unreleased +## v0.6.0 - 2023-04-04 + ### Added - Emoji support - `flake.nix`, making cove available as a nix flake diff --git a/Cargo.lock b/Cargo.lock index cf612ba..70a95c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -237,7 +237,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cove" -version = "0.5.2" +version = "0.6.0" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index c336af8..8721800 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cove" -version = "0.5.2" +version = "0.6.0" edition = "2021" [dependencies] From 847af34ceb71a97309e753febe86bfeb24a9e511 Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 5 Apr 2023 01:10:49 +0200 Subject: [PATCH 050/266] Make JSON exports faster --- CHANGELOG.md | 3 ++ src/export/json.rs | 26 ++++++------- src/vault/euph.rs | 95 ++++++++++++++++++++++++++-------------------- 3 files changed, 69 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 826d211..9b7c343 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ Procedure when bumping the version number: ## Unreleased +### Changed +- Improved JSON export performance + ## v0.6.0 - 2023-04-04 ### Added diff --git a/src/export/json.rs b/src/export/json.rs index 258b7bd..e72a0b8 100644 --- a/src/export/json.rs +++ b/src/export/json.rs @@ -8,14 +8,13 @@ pub async fn export(vault: &EuphRoomVault, file: &mut W) -> anyhow::Re write!(file, "[")?; let mut total = 0; - let mut offset = 0; + let mut last_msg_id = None; loop { - let messages = vault.chunk_at_offset(CHUNK_SIZE, offset).await?; - offset += messages.len(); - - if messages.is_empty() { - break; - } + let messages = vault.chunk_after(last_msg_id, CHUNK_SIZE).await?; + last_msg_id = Some(match messages.last() { + Some(last_msg) => last_msg.id, + None => break, // No more messages, export finished + }); for message in messages { if total == 0 { @@ -40,14 +39,13 @@ pub async fn export(vault: &EuphRoomVault, file: &mut W) -> anyhow::Re pub async fn export_stream(vault: &EuphRoomVault, file: &mut W) -> anyhow::Result<()> { let mut total = 0; - let mut offset = 0; + let mut last_msg_id = None; loop { - let messages = vault.chunk_at_offset(CHUNK_SIZE, offset).await?; - offset += messages.len(); - - if messages.is_empty() { - break; - } + let messages = vault.chunk_after(last_msg_id, CHUNK_SIZE).await?; + last_msg_id = Some(match messages.last() { + Some(last_msg) => last_msg.id, + None => break, // No more messages, export finished + }); for message in messages { serde_json::to_writer(&mut *file, &message)?; // Fancy reborrow! :D diff --git a/src/vault/euph.rs b/src/vault/euph.rs index ff32754..49f1501 100644 --- a/src/vault/euph.rs +++ b/src/vault/euph.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; use cookie::{Cookie, CookieJar}; use euphoxide::api::{Message, MessageId, SessionId, SessionView, Snowflake, Time, UserId}; use rusqlite::types::{FromSql, FromSqlError, ToSqlOutput, Value, ValueRef}; -use rusqlite::{named_params, params, Connection, OptionalExtension, ToSql, Transaction}; +use rusqlite::{named_params, params, Connection, OptionalExtension, Row, ToSql, Transaction}; use time::OffsetDateTime; use vault::Action; @@ -240,7 +240,7 @@ euph_room_vault_actions! { GetUnseenMsgsCount : unseen_msgs_count() -> usize; SetSeen : set_seen(id: MessageId, seen: bool) -> (); SetOlderSeen : set_older_seen(id: MessageId, seen: bool) -> (); - GetChunkAtOffset : chunk_at_offset(amount: usize, offset: usize) -> Vec; + GetChunkAfter : chunk_after(id: Option, amount: usize) -> Vec; } impl Action for Join { @@ -961,49 +961,62 @@ impl Action for SetOlderSeen { } } -impl Action for GetChunkAtOffset { +impl Action for GetChunkAfter { type Result = Vec; fn run(self, conn: &mut Connection) -> rusqlite::Result { - let mut query = conn.prepare( - " - SELECT - id, parent, previous_edit_id, time, content, encryption_key_id, edited, deleted, truncated, - user_id, name, server_id, server_era, session_id, is_staff, is_manager, client_address, real_client_address - FROM euph_msgs - WHERE room = ? - ORDER BY id ASC - LIMIT ? - OFFSET ? - ", - )?; + fn row2msg(row: &Row<'_>) -> rusqlite::Result { + Ok(Message { + id: MessageId(row.get::<_, WSnowflake>(0)?.0), + parent: row.get::<_, Option>(1)?.map(|s| MessageId(s.0)), + previous_edit_id: row.get::<_, Option>(2)?.map(|s| s.0), + time: row.get::<_, WTime>(3)?.0, + content: row.get(4)?, + encryption_key_id: row.get(5)?, + edited: row.get::<_, Option>(6)?.map(|t| t.0), + deleted: row.get::<_, Option>(7)?.map(|t| t.0), + truncated: row.get(8)?, + sender: SessionView { + id: UserId(row.get(9)?), + name: row.get(10)?, + server_id: row.get(11)?, + server_era: row.get(12)?, + session_id: SessionId(row.get(13)?), + is_staff: row.get(14)?, + is_manager: row.get(15)?, + client_address: row.get(16)?, + real_client_address: row.get(17)?, + }, + }) + } + + let messages = if let Some(id) = self.id { + conn.prepare(" + SELECT + id, parent, previous_edit_id, time, content, encryption_key_id, edited, deleted, truncated, + user_id, name, server_id, server_era, session_id, is_staff, is_manager, client_address, real_client_address + FROM euph_msgs + WHERE room = ? + AND id > ? + ORDER BY id ASC + LIMIT ? + ")? + .query_map(params![self.room, WSnowflake(id.0), self.amount], row2msg)? + .collect::>()? + } else { + conn.prepare(" + SELECT + id, parent, previous_edit_id, time, content, encryption_key_id, edited, deleted, truncated, + user_id, name, server_id, server_era, session_id, is_staff, is_manager, client_address, real_client_address + FROM euph_msgs + WHERE room = ? + ORDER BY id ASC + LIMIT ? + ")? + .query_map(params![self.room, self.amount], row2msg)? + .collect::>()? + }; - let messages = query - .query_map(params![self.room, self.amount, self.offset], |row| { - Ok(Message { - id: MessageId(row.get::<_, WSnowflake>(0)?.0), - parent: row.get::<_, Option>(1)?.map(|s| MessageId(s.0)), - previous_edit_id: row.get::<_, Option>(2)?.map(|s| s.0), - time: row.get::<_, WTime>(3)?.0, - content: row.get(4)?, - encryption_key_id: row.get(5)?, - edited: row.get::<_, Option>(6)?.map(|t| t.0), - deleted: row.get::<_, Option>(7)?.map(|t| t.0), - truncated: row.get(8)?, - sender: SessionView { - id: UserId(row.get(9)?), - name: row.get(10)?, - server_id: row.get(11)?, - server_era: row.get(12)?, - session_id: SessionId(row.get(13)?), - is_staff: row.get(14)?, - is_manager: row.get(15)?, - client_address: row.get(16)?, - real_client_address: row.get(17)?, - }, - }) - })? - .collect::>()?; Ok(messages) } } From 8c4a966451bd0321d7a0568fd9074f5e298c1e75 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 8 Apr 2023 20:09:18 +0200 Subject: [PATCH 051/266] Update euphoxide --- CHANGELOG.md | 3 +++ Cargo.lock | 2 +- Cargo.toml | 2 +- src/euph/room.rs | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b7c343..51e2088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ Procedure when bumping the version number: ### Changed - Improved JSON export performance +### Fixed +- Rooms reconnecting instead of showing error popups + ## v0.6.0 - 2023-04-04 ### Added diff --git a/Cargo.lock b/Cargo.lock index 70a95c3..f2a43b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -380,7 +380,7 @@ dependencies = [ [[package]] name = "euphoxide" version = "0.3.1" -source = "git+https://github.com/Garmelon/euphoxide.git?rev=768a259f02f5743f7812904657a99f1f58b8e835#768a259f02f5743f7812904657a99f1f58b8e835" +source = "git+https://github.com/Garmelon/euphoxide.git?rev=0f217a6279181b0731216760219e8ff0fa01e449#0f217a6279181b0731216760219e8ff0fa01e449" dependencies = [ "async-trait", "caseless", diff --git a/Cargo.toml b/Cargo.toml index 8721800..ec32221 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ features = ["rustls-tls-native-roots"] [dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -rev = "768a259f02f5743f7812904657a99f1f58b8e835" +rev = "0f217a6279181b0731216760219e8ff0fa01e449" features = ["bot"] # [patch."https://github.com/Garmelon/euphoxide.git"] diff --git a/src/euph/room.rs b/src/euph/room.rs index e38c2ed..bc5221a 100644 --- a/src/euph/room.rs +++ b/src/euph/room.rs @@ -316,7 +316,7 @@ impl Room { } pub fn logout(&self) -> Result<(), Error> { - self.conn_tx()?.send_only(Logout); + self.conn_tx()?.send_only(Logout {}); Ok(()) } } From 923e68c0b59d050ade0e396b0a88fb6bdb5d1659 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 29 Aug 2022 20:13:47 +0200 Subject: [PATCH 052/266] Always show rooms from config in rooms list --- CHANGELOG.md | 1 + src/ui/rooms.rs | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51e2088..c9d4acc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Procedure when bumping the version number: ### Changed - Improved JSON export performance +- Always show rooms from config file in room list ### Fixed - Rooms reconnecting instead of showing error popups diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index eac0a75..be59196 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -138,22 +138,30 @@ impl Rooms { } } - /// Remove rooms that are not running any more and can't be found in the db. - /// Insert rooms that are in the db but not yet in in the hash map. + /// Remove rooms that are not running any more and can't be found in the db + /// or config. Insert rooms that are in the db or config but not yet in in + /// the hash map. /// /// These kinds of rooms are either /// - failed connection attempts, or /// - rooms that were deleted from the db. async fn stabilize_rooms(&mut self) { + // Collect all rooms from the db and config file let rooms = logging_unwrap!(self.vault.euph().rooms().await); - let mut rooms_set = rooms.into_iter().collect::>(); + let mut rooms_set = rooms + .into_iter() + .chain(self.config.euph.rooms.keys().cloned()) + .collect::>(); // Prevent room that is currently being shown from being removed. This - // could otherwise happen when connecting to a room that doesn't exist. + // could otherwise happen after connecting to a room that doesn't exist. if let State::ShowRoom(name) = &self.state { rooms_set.insert(name.clone()); } + // Now `rooms_set` contains all rooms that must exist. Other rooms may + // also exist, for example rooms that are connecting for the first time. + self.euph_rooms .retain(|n, r| !r.stopped() || rooms_set.contains(n)); From 6089a94a2e891b99d947bd48db9f1a773dbd3fcb Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 10 Apr 2023 12:40:37 +0200 Subject: [PATCH 053/266] Update dependencies --- Cargo.lock | 124 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 95 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2a43b6..a08651c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -358,13 +358,13 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "errno" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -477,9 +477,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -573,25 +573,25 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" dependencies = [ "hermit-abi 0.3.1", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "is-terminal" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -894,16 +894,16 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.7" +version = "0.37.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" +checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1560,13 +1560,13 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -1575,7 +1575,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] @@ -1584,13 +1593,28 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] @@ -1599,42 +1623,84 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "winnow" version = "0.4.1" From 53250ccdcbb14c33314de245b57fb1a4011a49fc Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 10 Apr 2023 12:43:03 +0200 Subject: [PATCH 054/266] Bump version to 0.6.1 --- CHANGELOG.md | 2 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9d4acc..3024301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ Procedure when bumping the version number: ## Unreleased +## v0.6.1 - 2023-04-10 + ### Changed - Improved JSON export performance - Always show rooms from config file in room list diff --git a/Cargo.lock b/Cargo.lock index a08651c..9db6bb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -237,7 +237,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cove" -version = "0.6.0" +version = "0.6.1" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index ec32221..f0077c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cove" -version = "0.6.0" +version = "0.6.1" edition = "2021" [dependencies] From 3f63221594cd5b4021d3a08213c2a84d73609bec Mon Sep 17 00:00:00 2001 From: Joscha Date: Tue, 11 Apr 2023 23:10:49 +0200 Subject: [PATCH 055/266] Write "e.g." correctly --- CHANGELOG.md | 2 +- src/euph/room.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3024301..05f2a03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ Procedure when bumping the version number: ### Changed - Non-export info is now printed to stderr instead of stdout -- Recognizes links without scheme (e. g. `euphoria.io` instead of `https://euphoria.io`) +- Recognizes links without scheme (e.g. `euphoria.io` instead of `https://euphoria.io`) - Rooms waiting for reconnect are no longer sorted to bottom in default sort order ### Fixed diff --git a/src/euph/room.rs b/src/euph/room.rs index bc5221a..2e3c0c0 100644 --- a/src/euph/room.rs +++ b/src/euph/room.rs @@ -1,4 +1,4 @@ -// TODO Stop if room does not exist (e. g. 404) +// TODO Stop if room does not exist (e.g. 404) use std::convert::Infallible; use std::time::Duration; From 674534dfa4adf31b6f3c7dfb6f8f7003e83d61a7 Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 12 Apr 2023 19:37:18 +0200 Subject: [PATCH 056/266] Optimize dependencies in debug builds --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index f0077c7..89209ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,3 +55,6 @@ features = ["tokio"] # [patch."https://github.com/Garmelon/vault.git"] # vault = { path = "../vault/" } + +[profile.dev.package."*"] +opt-level = 3 From d2e3e2aef9a13d504336b1545aa8362e30fa9853 Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 14 Apr 2023 22:18:39 +0200 Subject: [PATCH 057/266] Remove flake-utils dependency See also: https://github.com/NixOS/nixpkgs/blob/4f399bd5c47c267a86ea998e35404f0a2661ee9b/flake.nix#L14 https://github.com/nix-community/home-manager/commit/17198cf5ae27af5b647c7dac58d935a7d0dbd189 --- CHANGELOG.md | 3 +++ flake.lock | 16 ---------------- flake.nix | 23 +++++++++++++---------- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05f2a03..d93ddc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ Procedure when bumping the version number: ## Unreleased +### Changed +- Simplified flake dependencies + ## v0.6.1 - 2023-04-10 ### Changed diff --git a/flake.lock b/flake.lock index 7196112..bc5ec53 100644 --- a/flake.lock +++ b/flake.lock @@ -1,20 +1,5 @@ { "nodes": { - "flake-utils": { - "locked": { - "lastModified": 1678901627, - "narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "naersk": { "inputs": { "nixpkgs": [ @@ -52,7 +37,6 @@ }, "root": { "inputs": { - "flake-utils": "flake-utils", "naersk": "naersk", "nixpkgs": "nixpkgs" } diff --git a/flake.nix b/flake.nix index 17b2241..68a8c1a 100644 --- a/flake.nix +++ b/flake.nix @@ -3,19 +3,22 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs"; - flake-utils.url = "github:numtide/flake-utils"; naersk.url = "github:nix-community/naersk"; naersk.inputs.nixpkgs.follows = "nixpkgs"; }; - outputs = { self, nixpkgs, flake-utils, naersk }: flake-utils.lib.eachDefaultSystem (system: - let - pkgs = import nixpkgs { inherit system; }; - naersk' = pkgs.callPackage naersk { }; - in - rec { - packages.default = naersk'.buildPackage { src = ./.; }; - } - ); + outputs = { self, nixpkgs, naersk }: + let forAllSystems = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed; + in { + packages = forAllSystems (system: + let + pkgs = import nixpkgs { inherit system; }; + naersk' = pkgs.callPackage naersk { }; + in + { + default = naersk'.buildPackage { src = ./.; }; + } + ); + }; } From b515ace90654fe038005c9b4449fc953c7f612b0 Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 14 Apr 2023 01:21:58 +0200 Subject: [PATCH 058/266] Add InfallibleExt util trait --- src/main.rs | 1 + src/util.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 src/util.rs diff --git a/src/main.rs b/src/main.rs index 945ba7b..116c75a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,7 @@ mod logger; mod macros; mod store; mod ui; +mod util; mod vault; use std::path::PathBuf; diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..b6e7c97 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,15 @@ +use std::convert::Infallible; + +pub trait InfallibleExt { + type Inner; + + fn infallible(self) -> Self::Inner; +} + +impl InfallibleExt for Result { + type Inner = T; + + fn infallible(self) -> T { + self.expect("infallible") + } +} From 059ff94aef6a6f3b1da677175a0ac053f60f623a Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 17 Feb 2023 20:07:08 +0100 Subject: [PATCH 059/266] Update toss --- Cargo.lock | 3 +- Cargo.toml | 2 +- src/euph/small_message.rs | 20 ++++++------- src/euph/util.rs | 10 +++---- src/logger.rs | 14 +++++----- src/main.rs | 2 +- src/ui.rs | 21 +++++++------- src/ui/chat.rs | 4 +-- src/ui/chat/blocks.rs | 2 +- src/ui/chat/tree.rs | 3 +- src/ui/chat/tree/layout.rs | 2 +- src/ui/chat/tree/widgets.rs | 45 +++++++++++++++++------------- src/ui/chat/tree/widgets/indent.rs | 7 ++--- src/ui/chat/tree/widgets/seen.rs | 7 +++-- src/ui/chat/tree/widgets/time.rs | 4 +-- src/ui/euph/account.rs | 8 +++--- src/ui/euph/auth.rs | 2 +- src/ui/euph/inspect.rs | 26 +++++++++-------- src/ui/euph/links.rs | 20 +++++-------- src/ui/euph/nick.rs | 5 ++-- src/ui/euph/nick_list.rs | 16 +++++------ src/ui/euph/popup.rs | 8 +++--- src/ui/euph/room.rs | 11 ++++---- src/ui/input.rs | 11 ++++---- src/ui/rooms.rs | 23 ++++++++------- src/ui/util.rs | 2 +- src/ui/widgets.rs | 2 +- src/ui/widgets/background.rs | 9 +++--- src/ui/widgets/border.rs | 9 +++--- src/ui/widgets/cursor.rs | 2 +- src/ui/widgets/editor.rs | 9 ++---- src/ui/widgets/empty.rs | 2 +- src/ui/widgets/float.rs | 2 +- src/ui/widgets/join.rs | 2 +- src/ui/widgets/layer.rs | 2 +- src/ui/widgets/list.rs | 2 +- src/ui/widgets/padding.rs | 2 +- src/ui/widgets/popup.rs | 15 +++++----- src/ui/widgets/resize.rs | 2 +- src/ui/widgets/rules.rs | 2 +- src/ui/widgets/text.rs | 4 +-- 41 files changed, 165 insertions(+), 179 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9db6bb3..8839b7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1313,8 +1313,9 @@ dependencies = [ [[package]] name = "toss" version = "0.1.0" -source = "git+https://github.com/Garmelon/toss.git?rev=0d59116012a51516a821991e2969b1cf4779770f#0d59116012a51516a821991e2969b1cf4779770f" +source = "git+https://github.com/Garmelon/toss.git?rev=59710c816269c434b97ece3ab1701d3fef2269cb#59710c816269c434b97ece3ab1701d3fef2269cb" dependencies = [ + "async-trait", "crossterm", "unicode-linebreak", "unicode-segmentation", diff --git a/Cargo.toml b/Cargo.toml index 89209ed..63742b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ features = ["bot"] [dependencies.toss] git = "https://github.com/Garmelon/toss.git" -rev = "0d59116012a51516a821991e2969b1cf4779770f" +rev = "59710c816269c434b97ece3ab1701d3fef2269cb" # [patch."https://github.com/Garmelon/toss.git"] # toss = { path = "../toss/" } diff --git a/src/euph/small_message.rs b/src/euph/small_message.rs index 4d2f4f4..2751058 100644 --- a/src/euph/small_message.rs +++ b/src/euph/small_message.rs @@ -1,9 +1,9 @@ use std::mem; -use crossterm::style::{ContentStyle, Stylize}; +use crossterm::style::Stylize; use euphoxide::api::{MessageId, Snowflake, Time}; use time::OffsetDateTime; -use toss::styled::Styled; +use toss::{Style, Styled}; use crate::store::Msg; use crate::ui::ChatMsg; @@ -35,7 +35,7 @@ enum Span { struct Highlighter<'a> { content: &'a str, - base_style: ContentStyle, + base_style: Style, exact: bool, span: Span, @@ -177,7 +177,7 @@ impl<'a> Highlighter<'a> { self.room_or_mention_possible = !char.is_alphanumeric(); } - fn highlight(content: &'a str, base_style: ContentStyle, exact: bool) -> Styled { + fn highlight(content: &'a str, base_style: Style, exact: bool) -> Styled { let mut this = Self { content: if exact { content } else { content.trim() }, base_style, @@ -198,7 +198,7 @@ impl<'a> Highlighter<'a> { } } -fn highlight_content(content: &str, base_style: ContentStyle, exact: bool) -> Styled { +fn highlight_content(content: &str, base_style: Style, exact: bool) -> Styled { Highlighter::highlight(content, base_style, exact) } @@ -216,13 +216,13 @@ fn as_me(content: &str) -> Option<&str> { content.strip_prefix("/me") } -fn style_me() -> ContentStyle { - ContentStyle::default().grey().italic() +fn style_me() -> Style { + Style::new().grey().italic() } fn styled_nick(nick: &str) -> Styled { Styled::new_plain("[") - .and_then(util::style_nick(nick, ContentStyle::default())) + .and_then(util::style_nick(nick, Style::new())) .then_plain("]") } @@ -232,7 +232,7 @@ fn styled_nick_me(nick: &str) -> Styled { } fn styled_content(content: &str) -> Styled { - highlight_content(content.trim(), ContentStyle::default(), false) + highlight_content(content.trim(), Style::new(), false) } fn styled_content_me(content: &str) -> Styled { @@ -244,7 +244,7 @@ fn styled_editor_content(content: &str) -> Styled { let style = if as_me(content).is_some() { style_me() } else { - ContentStyle::default() + Style::new() }; highlight_content(content, style, true) } diff --git a/src/euph/util.rs b/src/euph/util.rs index 4769c7b..fdf11a3 100644 --- a/src/euph/util.rs +++ b/src/euph/util.rs @@ -1,7 +1,7 @@ -use crossterm::style::{Color, ContentStyle, Stylize}; +use crossterm::style::{Color, Stylize}; use euphoxide::Emoji; use once_cell::sync::Lazy; -use toss::styled::Styled; +use toss::{Style, Styled}; pub static EMOJI: Lazy = Lazy::new(Emoji::load); @@ -42,15 +42,15 @@ pub fn nick_color(nick: &str) -> (u8, u8, u8) { hsl_to_rgb(hue, 1.0, 0.72) } -pub fn nick_style(nick: &str, base: ContentStyle) -> ContentStyle { +pub fn nick_style(nick: &str, base: Style) -> Style { let (r, g, b) = nick_color(nick); base.bold().with(Color::Rgb { r, g, b }) } -pub fn style_nick(nick: &str, base: ContentStyle) -> Styled { +pub fn style_nick(nick: &str, base: Style) -> Styled { Styled::new(EMOJI.replace(nick), nick_style(nick, base)) } -pub fn style_nick_exact(nick: &str, base: ContentStyle) -> Styled { +pub fn style_nick_exact(nick: &str, base: Style) -> Styled { Styled::new(nick, nick_style(nick, base)) } diff --git a/src/logger.rs b/src/logger.rs index f1d3cc0..731a000 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -3,12 +3,12 @@ use std::sync::Arc; use std::vec; use async_trait::async_trait; -use crossterm::style::{ContentStyle, Stylize}; +use crossterm::style::Stylize; use log::{Level, LevelFilter, Log}; use parking_lot::Mutex; use time::OffsetDateTime; use tokio::sync::mpsc; -use toss::styled::Styled; +use toss::{Style, Styled}; use crate::store::{Msg, MsgStore, Path, Tree}; use crate::ui::ChatMsg; @@ -48,11 +48,11 @@ impl ChatMsg for LogMsg { fn styled(&self) -> (Styled, Styled) { let nick_style = match self.level { - Level::Error => ContentStyle::default().bold().red(), - Level::Warn => ContentStyle::default().bold().yellow(), - Level::Info => ContentStyle::default().bold().green(), - Level::Debug => ContentStyle::default().bold().blue(), - Level::Trace => ContentStyle::default().bold().magenta(), + Level::Error => Style::new().bold().red(), + Level::Warn => Style::new().bold().yellow(), + Level::Info => Style::new().bold().green(), + Level::Debug => Style::new().bold().blue(), + Level::Trace => Style::new().bold().magenta(), }; let nick = Styled::new(format!("{}", self.level), nick_style); let content = Styled::new_plain(&self.content); diff --git a/src/main.rs b/src/main.rs index 116c75a..d5a402b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,7 +31,7 @@ use cookie::CookieJar; use directories::{BaseDirs, ProjectDirs}; use log::info; use tokio::sync::mpsc; -use toss::terminal::Terminal; +use toss::Terminal; use crate::config::Config; use crate::logger::Logger; diff --git a/src/ui.rs b/src/ui.rs index a9caaf9..208a8cd 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -14,7 +14,7 @@ use parking_lot::FairMutex; use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; use tokio::task; -use toss::terminal::Terminal; +use toss::Terminal; use crate::config::Config; use crate::logger::{LogMsg, Logger}; @@ -144,14 +144,7 @@ impl Ui { terminal.present()?; loop { - // 1. Measure grapheme widths if required - if terminal.measuring_required() { - let _guard = crossterm_lock.lock(); - terminal.measure_widths()?; - ok_or_return!(self.event_tx.send(UiEvent::GraphemeWidthsChanged), Ok(())); - } - - // 2. Handle events (in batches) + // 1. Handle events (in batches) let mut event = match event_rx.recv().await { Some(event) => event, None => return Ok(()), @@ -174,12 +167,18 @@ impl Ui { }; } - // 3. Render and present final state if redraw { + // 2. Draw and present resulting state terminal.autoresize()?; - terminal.frame().reset(); self.widget().await.render(terminal.frame()).await; terminal.present()?; + + // 3. Measure grapheme widths + if terminal.measuring_required() { + let _guard = crossterm_lock.lock(); + terminal.measure_widths()?; + ok_or_return!(self.event_tx.send(UiEvent::GraphemeWidthsChanged), Ok(())); + } } } } diff --git a/src/ui/chat.rs b/src/ui/chat.rs index 124d95d..475f7f0 100644 --- a/src/ui/chat.rs +++ b/src/ui/chat.rs @@ -11,9 +11,7 @@ use std::{fmt, io}; use async_trait::async_trait; use parking_lot::FairMutex; use time::OffsetDateTime; -use toss::frame::{Frame, Size}; -use toss::styled::Styled; -use toss::terminal::Terminal; +use toss::{Frame, Size, Styled, Terminal}; use crate::store::{Msg, MsgStore}; diff --git a/src/ui/chat/blocks.rs b/src/ui/chat/blocks.rs index 1389d43..240981c 100644 --- a/src/ui/chat/blocks.rs +++ b/src/ui/chat/blocks.rs @@ -1,7 +1,7 @@ use std::collections::{vec_deque, VecDeque}; use std::ops::Range; -use toss::frame::Frame; +use toss::Frame; use crate::macros::some_or_return; use crate::ui::widgets::BoxedWidget; diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs index ad8e6ab..0749ac4 100644 --- a/src/ui/chat/tree.rs +++ b/src/ui/chat/tree.rs @@ -12,8 +12,7 @@ use std::sync::Arc; use async_trait::async_trait; use parking_lot::FairMutex; use tokio::sync::Mutex; -use toss::frame::{Frame, Pos, Size}; -use toss::terminal::Terminal; +use toss::{Frame, Pos, Size, Terminal}; use crate::macros::logging_unwrap; use crate::store::{Msg, MsgStore}; diff --git a/src/ui/chat/tree/layout.rs b/src/ui/chat/tree/layout.rs index baccfbe..f474bf9 100644 --- a/src/ui/chat/tree/layout.rs +++ b/src/ui/chat/tree/layout.rs @@ -1,4 +1,4 @@ -use toss::frame::Frame; +use toss::Frame; use crate::store::{Msg, MsgStore, Path, Tree}; use crate::ui::chat::blocks::Block; diff --git a/src/ui/chat/tree/widgets.rs b/src/ui/chat/tree/widgets.rs index 8854dcd..919d98b 100644 --- a/src/ui/chat/tree/widgets.rs +++ b/src/ui/chat/tree/widgets.rs @@ -2,9 +2,8 @@ mod indent; mod seen; mod time; -use crossterm::style::{ContentStyle, Stylize}; -use toss::styled::Styled; -use toss::widthdb::WidthDb; +use crossterm::style::Stylize; +use toss::{Style, Styled, WidthDb}; use super::super::ChatMsg; use crate::store::Msg; @@ -19,36 +18,36 @@ use self::indent::Indent; pub const PLACEHOLDER: &str = "[...]"; -pub fn style_placeholder() -> ContentStyle { - ContentStyle::default().dark_grey() +pub fn style_placeholder() -> Style { + Style::new().dark_grey() } -fn style_time(highlighted: bool) -> ContentStyle { +fn style_time(highlighted: bool) -> Style { if highlighted { - ContentStyle::default().black().on_white() + Style::new().black().on_white() } else { - ContentStyle::default().grey() + Style::new().grey() } } -fn style_indent(highlighted: bool) -> ContentStyle { +fn style_indent(highlighted: bool) -> Style { if highlighted { - ContentStyle::default().black().on_white() + Style::new().black().on_white() } else { - ContentStyle::default().dark_grey() + Style::new().dark_grey() } } -fn style_info() -> ContentStyle { - ContentStyle::default().italic().dark_grey() +fn style_info() -> Style { + Style::new().italic().dark_grey() } -fn style_editor_highlight() -> ContentStyle { - ContentStyle::default().black().on_cyan() +fn style_editor_highlight() -> Style { + Style::new().black().on_cyan() } -fn style_pseudo_highlight() -> ContentStyle { - ContentStyle::default().black().on_yellow() +fn style_pseudo_highlight() -> Style { + Style::new().black().on_yellow() } pub fn msg( @@ -74,7 +73,9 @@ pub fn msg( ), Segment::new(Indent::new(indent, style_indent(highlighted))), Segment::new(Layer::new(vec![ - Indent::new(1, style_indent(false)).into(), + Padding::new(Indent::new(1, style_indent(false))) + .top(1) + .into(), Padding::new(Text::new(nick)).right(1).into(), ])), // TODO Minimum content width @@ -129,7 +130,9 @@ pub fn editor( ), Segment::new(Indent::new(indent, style_editor_highlight())), Segment::new(Layer::new(vec![ - Indent::new(1, style_indent(false)).into(), + Padding::new(Indent::new(1, style_indent(false))) + .top(1) + .into(), Padding::new(Text::new(nick)).right(1).into(), ])), Segment::new(editor).priority(1).expanding(true), @@ -151,7 +154,9 @@ pub fn pseudo(indent: usize, nick: &str, editor: &EditorState) -> Bo ), Segment::new(Indent::new(indent, style_pseudo_highlight())), Segment::new(Layer::new(vec![ - Indent::new(1, style_indent(false)).into(), + Padding::new(Indent::new(1, style_indent(false))) + .top(1) + .into(), Padding::new(Text::new(nick)).right(1).into(), ])), Segment::new(Text::new(content).wrap(true)).priority(1), diff --git a/src/ui/chat/tree/widgets/indent.rs b/src/ui/chat/tree/widgets/indent.rs index d512102..4e120c6 100644 --- a/src/ui/chat/tree/widgets/indent.rs +++ b/src/ui/chat/tree/widgets/indent.rs @@ -1,6 +1,5 @@ use async_trait::async_trait; -use crossterm::style::ContentStyle; -use toss::frame::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size, Style}; use crate::ui::widgets::Widget; @@ -9,11 +8,11 @@ pub const INDENT_WIDTH: usize = 2; pub struct Indent { level: usize, - style: ContentStyle, + style: Style, } impl Indent { - pub fn new(level: usize, style: ContentStyle) -> Self { + pub fn new(level: usize, style: Style) -> Self { Self { level, style } } } diff --git a/src/ui/chat/tree/widgets/seen.rs b/src/ui/chat/tree/widgets/seen.rs index 8197afd..d53271b 100644 --- a/src/ui/chat/tree/widgets/seen.rs +++ b/src/ui/chat/tree/widgets/seen.rs @@ -1,4 +1,5 @@ -use crossterm::style::{ContentStyle, Stylize}; +use crossterm::style::Stylize; +use toss::Style; use crate::ui::widgets::background::Background; use crate::ui::widgets::empty::Empty; @@ -8,8 +9,8 @@ use crate::ui::widgets::BoxedWidget; const UNSEEN: &str = "*"; const WIDTH: u16 = 1; -fn seen_style() -> ContentStyle { - ContentStyle::default().black().on_green() +fn seen_style() -> Style { + Style::new().black().on_green() } pub fn widget(seen: bool) -> BoxedWidget { diff --git a/src/ui/chat/tree/widgets/time.rs b/src/ui/chat/tree/widgets/time.rs index 0976197..0801126 100644 --- a/src/ui/chat/tree/widgets/time.rs +++ b/src/ui/chat/tree/widgets/time.rs @@ -1,7 +1,7 @@ -use crossterm::style::ContentStyle; use time::format_description::FormatItem; use time::macros::format_description; use time::OffsetDateTime; +use toss::Style; use crate::ui::widgets::background::Background; use crate::ui::widgets::empty::Empty; @@ -11,7 +11,7 @@ use crate::ui::widgets::BoxedWidget; const TIME_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day] [hour]:[minute]"); const TIME_WIDTH: u16 = 16; -pub fn widget(time: Option, style: ContentStyle) -> BoxedWidget { +pub fn widget(time: Option, style: Style) -> BoxedWidget { if let Some(time) = time { let text = time.format(TIME_FORMAT).expect("could not format time"); Background::new(Text::new((text, style))) diff --git a/src/ui/euph/account.rs b/src/ui/euph/account.rs index 3112719..c303375 100644 --- a/src/ui/euph/account.rs +++ b/src/ui/euph/account.rs @@ -1,7 +1,7 @@ -use crossterm::style::{ContentStyle, Stylize}; +use crossterm::style::Stylize; use euphoxide::api::PersonalAccountView; use euphoxide::conn; -use toss::terminal::Terminal; +use toss::{Style, Terminal}; use crate::euph::{self, Room}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; @@ -36,7 +36,7 @@ impl LoggedOut { } fn widget(&self) -> BoxedWidget { - let bold = ContentStyle::default().bold(); + let bold = Style::new().bold(); VJoin::new(vec![ Segment::new(Text::new(("Not logged in", bold.yellow()))), Segment::new(Empty::new().height(1)), @@ -64,7 +64,7 @@ pub struct LoggedIn(PersonalAccountView); impl LoggedIn { fn widget(&self) -> BoxedWidget { - let bold = ContentStyle::default().bold(); + let bold = Style::new().bold(); VJoin::new(vec![ Segment::new(Text::new(("Logged in", bold.green()))), Segment::new(Empty::new().height(1)), diff --git a/src/ui/euph/auth.rs b/src/ui/euph/auth.rs index b9b72b9..682d967 100644 --- a/src/ui/euph/auth.rs +++ b/src/ui/euph/auth.rs @@ -1,4 +1,4 @@ -use toss::terminal::Terminal; +use toss::Terminal; use crate::euph::Room; use crate::ui::input::{key, InputEvent, KeyBindingsList}; diff --git a/src/ui/euph/inspect.rs b/src/ui/euph/inspect.rs index 4f1f427..2d15c15 100644 --- a/src/ui/euph/inspect.rs +++ b/src/ui/euph/inspect.rs @@ -1,7 +1,7 @@ -use crossterm::style::{ContentStyle, Stylize}; +use crossterm::style::Stylize; use euphoxide::api::{Message, NickEvent, SessionView}; use euphoxide::conn::SessionInfo; -use toss::styled::Styled; +use toss::{Style, Styled}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::widgets::popup::Popup; @@ -11,31 +11,33 @@ use crate::ui::widgets::BoxedWidget; macro_rules! line { ( $text:ident, $name:expr, $val:expr ) => { $text = $text - .then($name, ContentStyle::default().cyan()) + .then($name, Style::new().cyan()) .then_plain(format!(" {}\n", $val)); }; ( $text:ident, $name:expr, $val:expr, debug ) => { $text = $text - .then($name, ContentStyle::default().cyan()) + .then($name, Style::new().cyan()) .then_plain(format!(" {:?}\n", $val)); }; ( $text:ident, $name:expr, $val:expr, optional ) => { if let Some(val) = $val { $text = $text - .then($name, ContentStyle::default().cyan()) + .then($name, Style::new().cyan()) .then_plain(format!(" {val}\n")); } else { $text = $text - .then($name, ContentStyle::default().cyan()) + .then($name, Style::new().cyan()) .then_plain(" ") - .then("none", ContentStyle::default().italic().grey()) + .then("none", Style::new().italic().grey()) .then_plain("\n"); } }; ( $text:ident, $name:expr, $val:expr, yes or no ) => { - $text = $text - .then($name, ContentStyle::default().cyan()) - .then_plain(if $val { " yes\n" } else { " no\n" }); + $text = $text.then($name, Style::new().cyan()).then_plain(if $val { + " yes\n" + } else { + " no\n" + }); }; } @@ -87,7 +89,7 @@ fn message_lines(mut text: Styled, msg: &Message) -> Styled { } pub fn session_widget(session: &SessionInfo) -> BoxedWidget { - let heading_style = ContentStyle::default().bold(); + let heading_style = Style::new().bold(); let text = match session { SessionInfo::Full(session) => { @@ -104,7 +106,7 @@ pub fn session_widget(session: &SessionInfo) -> BoxedWidget { } pub fn message_widget(msg: &Message) -> BoxedWidget { - let heading_style = ContentStyle::default().bold(); + let heading_style = Style::new().bold(); let mut text = Styled::new("Message", heading_style).then_plain("\n"); diff --git a/src/ui/euph/links.rs b/src/ui/euph/links.rs index b1ebcb0..9ccdc51 100644 --- a/src/ui/euph/links.rs +++ b/src/ui/euph/links.rs @@ -1,8 +1,8 @@ use std::io; -use crossterm::style::{ContentStyle, Stylize}; +use crossterm::style::Stylize; use linkify::{LinkFinder, LinkKind}; -use toss::styled::Styled; +use toss::{Style, Styled}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::widgets::list::ListState; @@ -40,24 +40,18 @@ impl LinksState { } pub fn widget(&self) -> BoxedWidget { - let style_selected = ContentStyle::default().black().on_white(); + let style_selected = Style::new().black().on_white(); let mut list = self.list.widget().focus(true); if self.links.is_empty() { - list.add_unsel(Text::new(( - "No links found", - ContentStyle::default().grey().italic(), - ))) + list.add_unsel(Text::new(("No links found", Style::new().grey().italic()))) } for (id, link) in self.links.iter().enumerate() { let (line_normal, line_selected) = if let Some(number_key) = NUMBER_KEYS.get(id) { ( - Styled::new( - format!("[{number_key}]"), - ContentStyle::default().dark_grey().bold(), - ) - .then_plain(" ") - .then_plain(link), + Styled::new(format!("[{number_key}]"), Style::new().dark_grey().bold()) + .then_plain(" ") + .then_plain(link), Styled::new(format!("[{number_key}]"), style_selected.bold()) .then(" ", style_selected) .then(link, style_selected), diff --git a/src/ui/euph/nick.rs b/src/ui/euph/nick.rs index e520fef..18e3d5d 100644 --- a/src/ui/euph/nick.rs +++ b/src/ui/euph/nick.rs @@ -1,6 +1,5 @@ -use crossterm::style::ContentStyle; use euphoxide::conn::Joined; -use toss::terminal::Terminal; +use toss::{Style, Terminal}; use crate::euph::{self, Room}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; @@ -17,7 +16,7 @@ pub fn new(joined: Joined) -> EditorState { pub fn widget(editor: &EditorState) -> BoxedWidget { let editor = editor .widget() - .highlight(|s| euph::style_nick_exact(s, ContentStyle::default())); + .highlight(|s| euph::style_nick_exact(s, Style::new())); Popup::new(Padding::new(editor).left(1)) .title("Choose nick") .inner_padding(false) diff --git a/src/ui/euph/nick_list.rs b/src/ui/euph/nick_list.rs index 926ca68..05ef864 100644 --- a/src/ui/euph/nick_list.rs +++ b/src/ui/euph/nick_list.rs @@ -1,10 +1,10 @@ use std::borrow::Cow; use std::iter; -use crossterm::style::{Color, ContentStyle, Stylize}; +use crossterm::style::{Color, Stylize}; use euphoxide::api::{NickEvent, SessionId, SessionType, SessionView, UserId}; use euphoxide::conn::{Joined, SessionInfo}; -use toss::styled::Styled; +use toss::{Style, Styled}; use crate::euph; use crate::ui::widgets::background::Background; @@ -98,7 +98,7 @@ fn render_section( return; } - let heading_style = ContentStyle::new().bold(); + let heading_style = Style::new().bold(); if !list.is_empty() { list.add_unsel(Empty::new()); @@ -117,16 +117,16 @@ fn render_section( fn render_row(list: &mut List, session: &HalfSession, own_session: &SessionView) { let (name, style, style_inv, perms_style_inv) = if session.name.is_empty() { let name = "lurk"; - let style = ContentStyle::default().grey(); - let style_inv = ContentStyle::default().black().on_grey(); + let style = Style::new().grey(); + let style_inv = Style::new().black().on_grey(); (Cow::Borrowed(name), style, style_inv, style_inv) } else { let name = &session.name as &str; let (r, g, b) = euph::nick_color(name); let color = Color::Rgb { r, g, b }; - let style = ContentStyle::default().bold().with(color); - let style_inv = ContentStyle::default().bold().black().on(color); - let perms_style_inv = ContentStyle::default().black().on(color); + let style = Style::new().bold().with(color); + let style_inv = Style::new().bold().black().on(color); + let perms_style_inv = Style::new().black().on(color); (euph::EMOJI.replace(name), style, style_inv, perms_style_inv) }; diff --git a/src/ui/euph/popup.rs b/src/ui/euph/popup.rs index 8bc8c6c..d5cbe76 100644 --- a/src/ui/euph/popup.rs +++ b/src/ui/euph/popup.rs @@ -1,5 +1,5 @@ -use crossterm::style::{ContentStyle, Stylize}; -use toss::styled::Styled; +use crossterm::style::Stylize; +use toss::{Style, Styled}; use crate::ui::widgets::float::Float; use crate::ui::widgets::popup::Popup; @@ -12,10 +12,10 @@ pub enum RoomPopup { impl RoomPopup { fn server_error_widget(description: &str, reason: &str) -> BoxedWidget { - let border_style = ContentStyle::default().red().bold(); + let border_style = Style::new().red().bold(); let text = Styled::new_plain(description) .then_plain("\n\n") - .then("Reason:", ContentStyle::default().bold()) + .then("Reason:", Style::new().bold()) .then_plain(" ") .then_plain(reason); Popup::new(Text::new(text)) diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 740d9f7..75d1f11 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -1,15 +1,14 @@ use std::collections::VecDeque; use std::sync::Arc; -use crossterm::style::{ContentStyle, Stylize}; +use crossterm::style::Stylize; use euphoxide::api::{Data, Message, MessageId, PacketType, SessionId}; use euphoxide::bot::instance::{Event, ServerConfig}; use euphoxide::conn::{self, Joined, Joining, SessionInfo}; use parking_lot::FairMutex; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::{mpsc, oneshot}; -use toss::styled::Styled; -use toss::terminal::Terminal; +use toss::{Style, Styled, Terminal}; use crate::config; use crate::euph; @@ -277,7 +276,7 @@ impl EuphRoom { } async fn status_widget(&self, state: Option<&euph::State>) -> BoxedWidget { - let room_style = ContentStyle::default().bold().blue(); + let room_style = Style::new().bold().blue(); let mut info = Styled::new(format!("&{}", self.name()), room_style); info = match state { @@ -296,7 +295,7 @@ impl EuphRoom { info.then_plain(", present without nick") } else { info.then_plain(", present as ") - .and_then(euph::style_nick(nick, ContentStyle::default())) + .and_then(euph::style_nick(nick, Style::new())) } } }; @@ -305,7 +304,7 @@ impl EuphRoom { if unseen > 0 { info = info .then_plain(" (") - .then(format!("{unseen}"), ContentStyle::default().bold().green()) + .then(format!("{unseen}"), Style::new().bold().green()) .then_plain(")"); } diff --git a/src/ui/input.rs b/src/ui/input.rs index d8cf209..4c3362e 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -1,8 +1,8 @@ use std::convert::Infallible; use crossterm::event::{Event, KeyCode, KeyModifiers}; -use crossterm::style::{ContentStyle, Stylize}; -use toss::styled::Styled; +use crossterm::style::Stylize; +use toss::{Style, Styled}; use super::widgets::background::Background; use super::widgets::border::Border; @@ -94,8 +94,8 @@ impl KeyBindingsList { Self(state.widget()) } - fn binding_style() -> ContentStyle { - ContentStyle::default().cyan() + fn binding_style() -> Style { + Style::new().cyan() } pub fn widget(self) -> BoxedWidget { @@ -124,8 +124,7 @@ impl KeyBindingsList { } pub fn heading(&mut self, name: &str) { - self.0 - .add_unsel(Text::new((name, ContentStyle::default().bold()))); + self.0.add_unsel(Text::new((name, Style::new().bold()))); } pub fn binding(&mut self, binding: &str, description: &str) { diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index be59196..c0e875c 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -2,14 +2,13 @@ use std::collections::{HashMap, HashSet}; use std::iter; use std::sync::{Arc, Mutex}; -use crossterm::style::{ContentStyle, Stylize}; +use crossterm::style::Stylize; use euphoxide::api::SessionType; use euphoxide::bot::instance::{Event, ServerConfig}; use euphoxide::conn::{self, Joined}; use parking_lot::FairMutex; use tokio::sync::mpsc; -use toss::styled::Styled; -use toss::terminal::Terminal; +use toss::{Style, Styled, Terminal}; use crate::config::{Config, RoomsSortOrder}; use crate::euph; @@ -199,7 +198,7 @@ impl Rooms { } fn new_room_widget(editor: &EditorState) -> BoxedWidget { - let room_style = ContentStyle::default().bold().blue(); + let room_style = Style::new().bold().blue(); let editor = editor.widget().highlight(|s| Styled::new(s, room_style)); Popup::new(HJoin::new(vec![ Segment::new(Text::new(("&", room_style))), @@ -210,8 +209,8 @@ impl Rooms { } fn delete_room_widget(name: &str, editor: &EditorState) -> BoxedWidget { - let warn_style = ContentStyle::default().bold().red(); - let room_style = ContentStyle::default().bold().blue(); + let warn_style = Style::new().bold().red(); + let room_style = Style::new().bold().blue(); let editor = editor.widget().highlight(|s| Styled::new(s, room_style)); let text = Styled::new_plain("Are you sure you want to delete ") .then("&", room_style) @@ -219,7 +218,7 @@ impl Rooms { .then_plain("?\n\n") .then_plain("This will delete the entire room history from your vault. ") .then_plain("To shrink your vault afterwards, run ") - .then("cove gc", ContentStyle::default().italic().grey()) + .then("cove gc", Style::new().italic().grey()) .then_plain(".\n\n") .then_plain("To confirm the deletion, ") .then_plain("enter the full name of the room and press enter:"); @@ -304,7 +303,7 @@ impl Rooms { } fn format_room_info(state: Option<&euph::State>, unseen: usize) -> Styled { - let unseen_style = ContentStyle::default().bold().green(); + let unseen_style = Style::new().bold().green(); let state = Self::format_room_state(state); let unseen = Self::format_unseen_msgs(unseen); @@ -336,7 +335,7 @@ impl Rooms { if self.euph_rooms.is_empty() { list.add_unsel(Text::new(( "Press F1 for key bindings", - ContentStyle::default().grey().italic(), + Style::new().grey().italic(), ))) } @@ -348,8 +347,8 @@ impl Rooms { } self.sort_rooms(&mut rooms); for (name, state, unseen) in rooms { - let room_style = ContentStyle::default().bold().blue(); - let room_sel_style = ContentStyle::default().bold().black().on_white(); + let room_style = Style::new().bold().blue(); + let room_sel_style = Style::new().bold().black().on_white(); let mut normal = Styled::new(format!("&{name}"), room_style); let mut selected = Styled::new(format!("&{name}"), room_sel_style); @@ -363,7 +362,7 @@ impl Rooms { } async fn rooms_widget(&self) -> BoxedWidget { - let heading_style = ContentStyle::default().bold(); + let heading_style = Style::new().bold(); let amount = self.euph_rooms.len(); let heading = Text::new(Styled::new("Rooms", heading_style).then_plain(format!(" ({amount})"))); diff --git a/src/ui/util.rs b/src/ui/util.rs index 583dcd5..2ba5241 100644 --- a/src/ui/util.rs +++ b/src/ui/util.rs @@ -2,7 +2,7 @@ use std::io; use std::sync::Arc; use parking_lot::FairMutex; -use toss::terminal::Terminal; +use toss::Terminal; use super::input::{key, InputEvent, KeyBindingsList}; use super::widgets::editor::EditorState; diff --git a/src/ui/widgets.rs b/src/ui/widgets.rs index 33c2c49..f5cb59c 100644 --- a/src/ui/widgets.rs +++ b/src/ui/widgets.rs @@ -19,7 +19,7 @@ pub mod rules; pub mod text; use async_trait::async_trait; -use toss::frame::{Frame, Size}; +use toss::{Frame, Size}; // TODO Add Error type and return Result-s (at least in Widget::render) diff --git a/src/ui/widgets/background.rs b/src/ui/widgets/background.rs index 4990bcf..f075a91 100644 --- a/src/ui/widgets/background.rs +++ b/src/ui/widgets/background.rs @@ -1,23 +1,22 @@ use async_trait::async_trait; -use crossterm::style::ContentStyle; -use toss::frame::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size, Style}; use super::{BoxedWidget, Widget}; pub struct Background { inner: BoxedWidget, - style: ContentStyle, + style: Style, } impl Background { pub fn new>(inner: W) -> Self { Self { inner: inner.into(), - style: ContentStyle::default(), + style: Style::new().opaque(), } } - pub fn style(mut self, style: ContentStyle) -> Self { + pub fn style(mut self, style: Style) -> Self { self.style = style; self } diff --git a/src/ui/widgets/border.rs b/src/ui/widgets/border.rs index fd32a9c..134312b 100644 --- a/src/ui/widgets/border.rs +++ b/src/ui/widgets/border.rs @@ -1,23 +1,22 @@ use async_trait::async_trait; -use crossterm::style::ContentStyle; -use toss::frame::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size, Style}; use super::{BoxedWidget, Widget}; pub struct Border { inner: BoxedWidget, - style: ContentStyle, + style: Style, } impl Border { pub fn new>(inner: W) -> Self { Self { inner: inner.into(), - style: ContentStyle::default(), + style: Style::new(), } } - pub fn style(mut self, style: ContentStyle) -> Self { + pub fn style(mut self, style: Style) -> Self { self.style = style; self } diff --git a/src/ui/widgets/cursor.rs b/src/ui/widgets/cursor.rs index 205c5c1..09856f4 100644 --- a/src/ui/widgets/cursor.rs +++ b/src/ui/widgets/cursor.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::frame::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size}; use super::{BoxedWidget, Widget}; diff --git a/src/ui/widgets/editor.rs b/src/ui/widgets/editor.rs index 54c8fd4..088373e 100644 --- a/src/ui/widgets/editor.rs +++ b/src/ui/widgets/editor.rs @@ -2,12 +2,9 @@ use std::sync::Arc; use std::{io, iter}; use async_trait::async_trait; -use crossterm::style::{ContentStyle, Stylize}; +use crossterm::style::Stylize; use parking_lot::{FairMutex, Mutex}; -use toss::frame::{Frame, Pos, Size}; -use toss::styled::Styled; -use toss::terminal::Terminal; -use toss::widthdb::WidthDb; +use toss::{Frame, Pos, Size, Style, Styled, Terminal, WidthDb}; use unicode_segmentation::UnicodeSegmentation; use crate::ui::util; @@ -461,7 +458,7 @@ impl Editor { } pub fn hidden(self) -> Self { - self.hidden_with_placeholder(("", ContentStyle::default().grey().italic())) + self.hidden_with_placeholder(("", Style::new().grey().italic())) } pub fn hidden_with_placeholder>(mut self, placeholder: S) -> Self { diff --git a/src/ui/widgets/empty.rs b/src/ui/widgets/empty.rs index 40ff3bf..790c8b1 100644 --- a/src/ui/widgets/empty.rs +++ b/src/ui/widgets/empty.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::frame::{Frame, Size}; +use toss::{Frame, Size}; use super::Widget; diff --git a/src/ui/widgets/float.rs b/src/ui/widgets/float.rs index 96f398c..6a82284 100644 --- a/src/ui/widgets/float.rs +++ b/src/ui/widgets/float.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::frame::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size}; use super::{BoxedWidget, Widget}; diff --git a/src/ui/widgets/join.rs b/src/ui/widgets/join.rs index 04d01c0..00d18fa 100644 --- a/src/ui/widgets/join.rs +++ b/src/ui/widgets/join.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::frame::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size}; use super::{BoxedWidget, Widget}; diff --git a/src/ui/widgets/layer.rs b/src/ui/widgets/layer.rs index 7c5e659..30807e6 100644 --- a/src/ui/widgets/layer.rs +++ b/src/ui/widgets/layer.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::frame::{Frame, Size}; +use toss::{Frame, Size}; use super::{BoxedWidget, Widget}; diff --git a/src/ui/widgets/list.rs b/src/ui/widgets/list.rs index 8df110c..0dcd0aa 100644 --- a/src/ui/widgets/list.rs +++ b/src/ui/widgets/list.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use async_trait::async_trait; use parking_lot::Mutex; -use toss::frame::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size}; use super::{BoxedWidget, Widget}; diff --git a/src/ui/widgets/padding.rs b/src/ui/widgets/padding.rs index 74a7e29..e2be11d 100644 --- a/src/ui/widgets/padding.rs +++ b/src/ui/widgets/padding.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::frame::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size}; use super::{BoxedWidget, Widget}; diff --git a/src/ui/widgets/popup.rs b/src/ui/widgets/popup.rs index 96ce7c2..b7ec0fe 100644 --- a/src/ui/widgets/popup.rs +++ b/src/ui/widgets/popup.rs @@ -1,5 +1,4 @@ -use crossterm::style::ContentStyle; -use toss::styled::Styled; +use toss::{Style, Styled}; use super::background::Background; use super::border::Border; @@ -13,8 +12,8 @@ pub struct Popup { inner: BoxedWidget, inner_padding: bool, title: Option, - border_style: ContentStyle, - bg_style: ContentStyle, + border_style: Style, + bg_style: Style, } impl Popup { @@ -23,8 +22,8 @@ impl Popup { inner: inner.into(), inner_padding: true, title: None, - border_style: ContentStyle::default(), - bg_style: ContentStyle::default(), + border_style: Style::new(), + bg_style: Style::new().opaque(), } } @@ -38,12 +37,12 @@ impl Popup { self } - pub fn border(mut self, style: ContentStyle) -> Self { + pub fn border(mut self, style: Style) -> Self { self.border_style = style; self } - pub fn background(mut self, style: ContentStyle) -> Self { + pub fn background(mut self, style: Style) -> Self { self.bg_style = style; self } diff --git a/src/ui/widgets/resize.rs b/src/ui/widgets/resize.rs index 15f5577..67c07ed 100644 --- a/src/ui/widgets/resize.rs +++ b/src/ui/widgets/resize.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::frame::{Frame, Size}; +use toss::{Frame, Size}; use super::{BoxedWidget, Widget}; diff --git a/src/ui/widgets/rules.rs b/src/ui/widgets/rules.rs index 9fcc5df..f6b01c1 100644 --- a/src/ui/widgets/rules.rs +++ b/src/ui/widgets/rules.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::frame::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size}; use super::Widget; diff --git a/src/ui/widgets/text.rs b/src/ui/widgets/text.rs index 5ab65d8..1a383f5 100644 --- a/src/ui/widgets/text.rs +++ b/src/ui/widgets/text.rs @@ -1,7 +1,5 @@ use async_trait::async_trait; -use toss::frame::{Frame, Pos, Size}; -use toss::styled::Styled; -use toss::widthdb::WidthDb; +use toss::{Frame, Pos, Size, Styled, WidthDb}; use super::Widget; From ff9a16d8a3e550b897257721f5c55d57ba420af7 Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 5 Apr 2023 23:08:53 +0200 Subject: [PATCH 060/266] Make Widget::size like toss::AsyncWidget::size --- Cargo.lock | 12 +++++ Cargo.toml | 1 + src/ui/chat.rs | 13 +++-- src/ui/chat/blocks.rs | 5 +- src/ui/chat/tree.rs | 11 +++-- src/ui/chat/tree/layout.rs | 77 ++++++++++++++++-------------- src/ui/chat/tree/widgets/indent.rs | 9 +++- src/ui/widgets.rs | 13 +++-- src/ui/widgets/background.rs | 11 +++-- src/ui/widgets/border.rs | 11 +++-- src/ui/widgets/cursor.rs | 11 +++-- src/ui/widgets/editor.rs | 11 +++-- src/ui/widgets/empty.rs | 9 +++- src/ui/widgets/float.rs | 16 +++++-- src/ui/widgets/join.rs | 50 ++++++++++++++----- src/ui/widgets/layer.rs | 11 +++-- src/ui/widgets/list.rs | 35 +++++++++----- src/ui/widgets/padding.rs | 11 +++-- src/ui/widgets/resize.rs | 11 +++-- src/ui/widgets/rules.rs | 16 +++++-- src/ui/widgets/text.rs | 10 ++-- 21 files changed, 244 insertions(+), 110 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8839b7c..d9a92db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,17 @@ version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +[[package]] +name = "async-recursion" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + [[package]] name = "async-trait" version = "0.1.68" @@ -240,6 +251,7 @@ name = "cove" version = "0.6.1" dependencies = [ "anyhow", + "async-recursion", "async-trait", "clap", "cookie", diff --git a/Cargo.toml b/Cargo.toml index 63742b3..ef096ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] anyhow = "1.0.70" +async-recursion = "1.0.4" async-trait = "0.1.68" clap = { version = "4.2.1", features = ["derive", "deprecated"] } cookie = "0.17.0" diff --git a/src/ui/chat.rs b/src/ui/chat.rs index 475f7f0..f9f0367 100644 --- a/src/ui/chat.rs +++ b/src/ui/chat.rs @@ -11,7 +11,7 @@ use std::{fmt, io}; use async_trait::async_trait; use parking_lot::FairMutex; use time::OffsetDateTime; -use toss::{Frame, Size, Styled, Terminal}; +use toss::{Frame, Size, Styled, Terminal, WidthDb}; use crate::store::{Msg, MsgStore}; @@ -139,14 +139,19 @@ pub enum Chat> { #[async_trait] impl Widget for Chat where - M: Msg + ChatMsg, + M: Msg + ChatMsg + Send + Sync, M::Id: Send + Sync, S: MsgStore + Send + Sync, S::Error: fmt::Display, { - fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Size { match self { - Self::Tree(tree) => tree.size(frame, max_width, max_height), + Self::Tree(tree) => tree.size(widthdb, max_width, max_height).await, } } diff --git a/src/ui/chat/blocks.rs b/src/ui/chat/blocks.rs index 240981c..bb596cb 100644 --- a/src/ui/chat/blocks.rs +++ b/src/ui/chat/blocks.rs @@ -20,11 +20,12 @@ pub struct Block { } impl Block { - pub fn new>(frame: &mut Frame, id: I, widget: W) -> Self { + pub async fn new>(frame: &mut Frame, id: I, widget: W) -> Self { // Interestingly, rust-analyzer fails to deduce the type of `widget` // here but rustc knows it's a `BoxedWidget`. let widget = widget.into(); - let size = widget.size(frame, Some(frame.size().width), None); + let max_width = frame.size().width; + let size = widget.size(frame.widthdb(), Some(max_width), None).await; let height = size.height.into(); Self { id, diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs index 0749ac4..49e1392 100644 --- a/src/ui/chat/tree.rs +++ b/src/ui/chat/tree.rs @@ -12,7 +12,7 @@ use std::sync::Arc; use async_trait::async_trait; use parking_lot::FairMutex; use tokio::sync::Mutex; -use toss::{Frame, Pos, Size, Terminal}; +use toss::{Frame, Pos, Size, Terminal, WidthDb}; use crate::macros::logging_unwrap; use crate::store::{Msg, MsgStore}; @@ -427,12 +427,17 @@ pub struct TreeView> { #[async_trait] impl Widget for TreeView where - M: Msg + ChatMsg, + M: Msg + ChatMsg + Send + Sync, M::Id: Send + Sync, S: MsgStore + Send + Sync, S::Error: fmt::Display, { - fn size(&self, _frame: &mut Frame, _max_width: Option, _max_height: Option) -> Size { + async fn size( + &self, + _widthdb: &mut WidthDb, + _max_width: Option, + _max_height: Option, + ) -> Size { Size::ZERO } diff --git a/src/ui/chat/tree/layout.rs b/src/ui/chat/tree/layout.rs index f474bf9..df5fa4a 100644 --- a/src/ui/chat/tree/layout.rs +++ b/src/ui/chat/tree/layout.rs @@ -1,3 +1,4 @@ +use async_recursion::async_recursion; use toss::Frame; use crate::store::{Msg, MsgStore, Path, Tree}; @@ -21,7 +22,12 @@ struct Context { focused: bool, } -impl> InnerTreeViewState { +impl InnerTreeViewState +where + M: Msg + ChatMsg + Send + Sync, + M::Id: Send + Sync, + S: MsgStore + Send + Sync, +{ async fn cursor_path(&self, cursor: &Cursor) -> Result, S::Error> { Ok(match cursor { Cursor::Msg(id) => self.store.path(id).await?, @@ -69,7 +75,7 @@ impl> InnerTreeViewState { .is_some() } - fn editor_block( + async fn editor_block( &self, context: &Context, frame: &mut Frame, @@ -78,20 +84,23 @@ impl> InnerTreeViewState { let (widget, cursor_row) = widgets::editor::(frame.widthdb(), indent, &context.nick, &self.editor); let cursor_row = cursor_row as i32; - Block::new(frame, BlockId::Cursor, widget).focus(cursor_row..cursor_row + 1) + Block::new(frame, BlockId::Cursor, widget) + .await + .focus(cursor_row..cursor_row + 1) } - fn pseudo_block( + async fn pseudo_block( &self, context: &Context, frame: &mut Frame, indent: usize, ) -> Block> { let widget = widgets::pseudo::(indent, &context.nick, &self.editor); - Block::new(frame, BlockId::Cursor, widget) + Block::new(frame, BlockId::Cursor, widget).await } - fn layout_subtree( + #[async_recursion] + async fn layout_subtree( &self, context: &Context, frame: &mut Frame, @@ -102,7 +111,7 @@ impl> InnerTreeViewState { ) { // Ghost cursor in front, for positioning according to last cursor line if self.last_cursor.refers_to(id) { - let block = Block::new(frame, BlockId::LastCursor, Empty::new()); + let block = Block::new(frame, BlockId::LastCursor, Empty::new()).await; blocks.blocks_mut().push_back(block); } @@ -121,43 +130,40 @@ impl> InnerTreeViewState { } else { widgets::msg_placeholder(highlighted, indent, folded_info) }; - let block = Block::new(frame, BlockId::Msg(id.clone()), widget); + let block = Block::new(frame, BlockId::Msg(id.clone()), widget).await; blocks.blocks_mut().push_back(block); // Children, recursively if !folded { if let Some(children) = tree.children(id) { for child in children { - self.layout_subtree(context, frame, tree, indent + 1, child, blocks); + self.layout_subtree(context, frame, tree, indent + 1, child, blocks) + .await; } } } // Trailing ghost cursor, for positioning according to last cursor line if self.last_cursor.refers_to_last_child_of(id) { - let block = Block::new(frame, BlockId::LastCursor, Empty::new()); + let block = Block::new(frame, BlockId::LastCursor, Empty::new()).await; blocks.blocks_mut().push_back(block); } // Trailing editor or pseudomessage if self.cursor.refers_to_last_child_of(id) { match self.cursor { - Cursor::Editor { .. } => { - blocks - .blocks_mut() - .push_back(self.editor_block(context, frame, indent + 1)) - } - Cursor::Pseudo { .. } => { - blocks - .blocks_mut() - .push_back(self.pseudo_block(context, frame, indent + 1)) - } + Cursor::Editor { .. } => blocks + .blocks_mut() + .push_back(self.editor_block(context, frame, indent + 1).await), + Cursor::Pseudo { .. } => blocks + .blocks_mut() + .push_back(self.pseudo_block(context, frame, indent + 1).await), _ => {} } } } - fn layout_tree( + async fn layout_tree( &self, context: &Context, frame: &mut Frame, @@ -165,32 +171,33 @@ impl> InnerTreeViewState { ) -> TreeBlocks { let root = Root::Tree(tree.root().clone()); let mut blocks = TreeBlocks::new(root.clone(), root); - self.layout_subtree(context, frame, &tree, 0, tree.root(), &mut blocks); + self.layout_subtree(context, frame, &tree, 0, tree.root(), &mut blocks) + .await; blocks } - fn layout_bottom(&self, context: &Context, frame: &mut Frame) -> TreeBlocks { + async fn layout_bottom(&self, context: &Context, frame: &mut Frame) -> TreeBlocks { let mut blocks = TreeBlocks::new(Root::Bottom, Root::Bottom); // Ghost cursor, for positioning according to last cursor line if let Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } = self.last_cursor { - let block = Block::new(frame, BlockId::LastCursor, Empty::new()); + let block = Block::new(frame, BlockId::LastCursor, Empty::new()).await; blocks.blocks_mut().push_back(block); } match self.cursor { Cursor::Bottom => { - let block = Block::new(frame, BlockId::Cursor, Empty::new()); + let block = Block::new(frame, BlockId::Cursor, Empty::new()).await; blocks.blocks_mut().push_back(block); } Cursor::Editor { parent: None, .. } => blocks .blocks_mut() - .push_back(self.editor_block(context, frame, 0)), + .push_back(self.editor_block(context, frame, 0).await), Cursor::Pseudo { parent: None, .. } => blocks .blocks_mut() - .push_back(self.pseudo_block(context, frame, 0)), + .push_back(self.pseudo_block(context, frame, 0).await), _ => {} } @@ -216,7 +223,7 @@ impl> InnerTreeViewState { None => break, }; let prev_tree = self.store.tree(&prev_root_id).await?; - blocks.prepend(self.layout_tree(context, frame, prev_tree)); + blocks.prepend(self.layout_tree(context, frame, prev_tree).await); } Ok(()) @@ -238,9 +245,9 @@ impl> InnerTreeViewState { }; if let Some(next_root_id) = next_root_id { let next_tree = self.store.tree(&next_root_id).await?; - blocks.append(self.layout_tree(context, frame, next_tree)); + blocks.append(self.layout_tree(context, frame, next_tree).await); } else { - blocks.append(self.layout_bottom(context, frame)); + blocks.append(self.layout_bottom(context, frame).await); } } @@ -281,7 +288,7 @@ impl> InnerTreeViewState { ) -> Result, S::Error> { Ok(match &self.last_cursor { Cursor::Bottom => { - let mut blocks = self.layout_bottom(context, frame); + let mut blocks = self.layout_bottom(context, frame).await; let bottom_line = frame.size().height as i32 - 1; blocks.blocks_mut().set_bottom_line(bottom_line); @@ -289,7 +296,7 @@ impl> InnerTreeViewState { blocks } Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } => { - let mut blocks = self.layout_bottom(context, frame); + let mut blocks = self.layout_bottom(context, frame).await; blocks .blocks_mut() @@ -306,7 +313,7 @@ impl> InnerTreeViewState { } => { let root = last_cursor_path.first(); let tree = self.store.tree(root).await?; - let mut blocks = self.layout_tree(context, frame, tree); + let mut blocks = self.layout_tree(context, frame, tree).await; blocks .blocks_mut() @@ -330,7 +337,7 @@ impl> InnerTreeViewState { Cursor::Bottom | Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } => { - let mut blocks = self.layout_bottom(context, frame); + let mut blocks = self.layout_bottom(context, frame).await; blocks.blocks_mut().set_bottom_line(bottom_line); @@ -345,7 +352,7 @@ impl> InnerTreeViewState { } => { let root = cursor_path.first(); let tree = self.store.tree(root).await?; - let mut blocks = self.layout_tree(context, frame, tree); + let mut blocks = self.layout_tree(context, frame, tree).await; let cursor_above_last = cursor_path < last_cursor_path; let cursor_line = if cursor_above_last { 0 } else { bottom_line }; diff --git a/src/ui/chat/tree/widgets/indent.rs b/src/ui/chat/tree/widgets/indent.rs index 4e120c6..a226f93 100644 --- a/src/ui/chat/tree/widgets/indent.rs +++ b/src/ui/chat/tree/widgets/indent.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::{Frame, Pos, Size, Style}; +use toss::{Frame, Pos, Size, Style, WidthDb}; use crate::ui::widgets::Widget; @@ -19,7 +19,12 @@ impl Indent { #[async_trait] impl Widget for Indent { - fn size(&self, _frame: &mut Frame, _max_width: Option, _max_height: Option) -> Size { + async fn size( + &self, + _widthdb: &mut WidthDb, + _max_width: Option, + _max_height: Option, + ) -> Size { Size::new((INDENT_WIDTH * self.level) as u16, 0) } diff --git a/src/ui/widgets.rs b/src/ui/widgets.rs index f5cb59c..20fb6d3 100644 --- a/src/ui/widgets.rs +++ b/src/ui/widgets.rs @@ -19,20 +19,25 @@ pub mod rules; pub mod text; use async_trait::async_trait; -use toss::{Frame, Size}; +use toss::{Frame, Size, WidthDb}; // TODO Add Error type and return Result-s (at least in Widget::render) #[async_trait] pub trait Widget { - fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size; + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Size; async fn render(self: Box, frame: &mut Frame); } -pub type BoxedWidget = Box; +pub type BoxedWidget = Box; -impl From for BoxedWidget { +impl From for BoxedWidget { fn from(widget: W) -> Self { Box::new(widget) } diff --git a/src/ui/widgets/background.rs b/src/ui/widgets/background.rs index f075a91..432e54d 100644 --- a/src/ui/widgets/background.rs +++ b/src/ui/widgets/background.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::{Frame, Pos, Size, Style}; +use toss::{Frame, Pos, Size, Style, WidthDb}; use super::{BoxedWidget, Widget}; @@ -24,8 +24,13 @@ impl Background { #[async_trait] impl Widget for Background { - fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { - self.inner.size(frame, max_width, max_height) + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Size { + self.inner.size(widthdb, max_width, max_height).await } async fn render(self: Box, frame: &mut Frame) { diff --git a/src/ui/widgets/border.rs b/src/ui/widgets/border.rs index 134312b..bfd76ea 100644 --- a/src/ui/widgets/border.rs +++ b/src/ui/widgets/border.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::{Frame, Pos, Size, Style}; +use toss::{Frame, Pos, Size, Style, WidthDb}; use super::{BoxedWidget, Widget}; @@ -24,10 +24,15 @@ impl Border { #[async_trait] impl Widget for Border { - fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Size { let max_width = max_width.map(|w| w.saturating_sub(2)); let max_height = max_height.map(|h| h.saturating_sub(2)); - let size = self.inner.size(frame, max_width, max_height); + let size = self.inner.size(widthdb, max_width, max_height).await; size + Size::new(2, 2) } diff --git a/src/ui/widgets/cursor.rs b/src/ui/widgets/cursor.rs index 09856f4..22ac5cc 100644 --- a/src/ui/widgets/cursor.rs +++ b/src/ui/widgets/cursor.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size, WidthDb}; use super::{BoxedWidget, Widget}; @@ -28,8 +28,13 @@ impl Cursor { #[async_trait] impl Widget for Cursor { - fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { - self.inner.size(frame, max_width, max_height) + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Size { + self.inner.size(widthdb, max_width, max_height).await } async fn render(self: Box, frame: &mut Frame) { diff --git a/src/ui/widgets/editor.rs b/src/ui/widgets/editor.rs index 088373e..e2b8955 100644 --- a/src/ui/widgets/editor.rs +++ b/src/ui/widgets/editor.rs @@ -493,9 +493,14 @@ impl Editor { #[async_trait] impl Widget for Editor { - fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Size { if let Some(placeholder) = &self.hidden { - let mut size = placeholder.size(frame, max_width, max_height); + let mut size = placeholder.size(widthdb, max_width, max_height).await; // Cursor needs to fit regardless of focus size.width = size.width.max(1); @@ -504,8 +509,6 @@ impl Widget for Editor { return size; } - let widthdb = frame.widthdb(); - let max_width = max_width.map(|w| w as usize).unwrap_or(usize::MAX).max(1); let max_text_width = max_width - 1; let indices = wrap(widthdb, self.text.text(), max_text_width); diff --git a/src/ui/widgets/empty.rs b/src/ui/widgets/empty.rs index 790c8b1..a5d98ea 100644 --- a/src/ui/widgets/empty.rs +++ b/src/ui/widgets/empty.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::{Frame, Size}; +use toss::{Frame, Size, WidthDb}; use super::Widget; @@ -31,7 +31,12 @@ impl Empty { #[async_trait] impl Widget for Empty { - fn size(&self, _frame: &mut Frame, _max_width: Option, _max_height: Option) -> Size { + async fn size( + &self, + _widthdb: &mut WidthDb, + _max_width: Option, + _max_height: Option, + ) -> Size { self.size } diff --git a/src/ui/widgets/float.rs b/src/ui/widgets/float.rs index 6a82284..a262cd6 100644 --- a/src/ui/widgets/float.rs +++ b/src/ui/widgets/float.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size, WidthDb}; use super::{BoxedWidget, Widget}; @@ -31,14 +31,22 @@ impl Float { #[async_trait] impl Widget for Float { - fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { - self.inner.size(frame, max_width, max_height) + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Size { + self.inner.size(widthdb, max_width, max_height).await } async fn render(self: Box, frame: &mut Frame) { let size = frame.size(); - let mut inner_size = self.inner.size(frame, Some(size.width), Some(size.height)); + let mut inner_size = self + .inner + .size(frame.widthdb(), Some(size.width), Some(size.height)) + .await; inner_size.width = inner_size.width.min(size.width); inner_size.height = inner_size.height.min(size.height); diff --git a/src/ui/widgets/join.rs b/src/ui/widgets/join.rs index 00d18fa..2aa4551 100644 --- a/src/ui/widgets/join.rs +++ b/src/ui/widgets/join.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size, WidthDb}; use super::{BoxedWidget, Widget}; @@ -54,9 +54,9 @@ impl SizedSegment { } } -fn sizes_horiz( +async fn sizes_horiz( segments: &[Segment], - frame: &mut Frame, + widthdb: &mut WidthDb, max_width: Option, max_height: Option, ) -> Vec { @@ -74,7 +74,8 @@ fn sizes_horiz( .map(|w| w.saturating_sub(total_width)); s.size = segments[s.idx] .widget - .size(frame, available_width, max_height); + .size(widthdb, available_width, max_height) + .await; if let Some(available_width) = available_width { s.size.width = s.size.width.min(available_width); } @@ -84,9 +85,9 @@ fn sizes_horiz( sized } -fn sizes_vert( +async fn sizes_vert( segments: &[Segment], - frame: &mut Frame, + widthdb: &mut WidthDb, max_width: Option, max_height: Option, ) -> Vec { @@ -104,7 +105,8 @@ fn sizes_vert( .map(|w| w.saturating_sub(total_height)); s.size = segments[s.idx] .widget - .size(frame, max_width, available_height); + .size(widthdb, max_width, available_height) + .await; if let Some(available_height) = available_height { s.size.height = s.size.height.min(available_height); } @@ -177,8 +179,13 @@ impl HJoin { #[async_trait] impl Widget for HJoin { - fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { - let sizes = sizes_horiz(&self.segments, frame, max_width, max_height); + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Size { + let sizes = sizes_horiz(&self.segments, widthdb, max_width, max_height).await; let width = sizes.iter().map(|s| s.size.width).sum::(); let height = sizes.iter().map(|s| s.size.height).max().unwrap_or(0); Size::new(width, height) @@ -187,7 +194,13 @@ impl Widget for HJoin { async fn render(self: Box, frame: &mut Frame) { let size = frame.size(); - let mut sizes = sizes_horiz(&self.segments, frame, Some(size.width), Some(size.height)); + let mut sizes = sizes_horiz( + &self.segments, + frame.widthdb(), + Some(size.width), + Some(size.height), + ) + .await; expand_horiz(&mut sizes, size.width); sizes.sort_by_key(|s| s.idx); @@ -215,8 +228,13 @@ impl VJoin { #[async_trait] impl Widget for VJoin { - fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { - let sizes = sizes_vert(&self.segments, frame, max_width, max_height); + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Size { + let sizes = sizes_vert(&self.segments, widthdb, max_width, max_height).await; let width = sizes.iter().map(|s| s.size.width).max().unwrap_or(0); let height = sizes.iter().map(|s| s.size.height).sum::(); Size::new(width, height) @@ -225,7 +243,13 @@ impl Widget for VJoin { async fn render(self: Box, frame: &mut Frame) { let size = frame.size(); - let mut sizes = sizes_vert(&self.segments, frame, Some(size.width), Some(size.height)); + let mut sizes = sizes_vert( + &self.segments, + frame.widthdb(), + Some(size.width), + Some(size.height), + ) + .await; expand_vert(&mut sizes, size.height); sizes.sort_by_key(|s| s.idx); diff --git a/src/ui/widgets/layer.rs b/src/ui/widgets/layer.rs index 30807e6..fe0d983 100644 --- a/src/ui/widgets/layer.rs +++ b/src/ui/widgets/layer.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::{Frame, Size}; +use toss::{Frame, Size, WidthDb}; use super::{BoxedWidget, Widget}; @@ -15,10 +15,15 @@ impl Layer { #[async_trait] impl Widget for Layer { - fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Size { let mut max_size = Size::ZERO; for layer in &self.layers { - let size = layer.size(frame, max_width, max_height); + let size = layer.size(widthdb, max_width, max_height).await; max_size.width = max_size.width.max(size.width); max_size.height = max_size.height.max(size.height); } diff --git a/src/ui/widgets/list.rs b/src/ui/widgets/list.rs index 0dcd0aa..fc148a0 100644 --- a/src/ui/widgets/list.rs +++ b/src/ui/widgets/list.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use async_trait::async_trait; use parking_lot::Mutex; -use toss::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size, WidthDb}; use super::{BoxedWidget, Widget}; @@ -266,14 +266,19 @@ impl Row { } } - fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Size { match self { - Self::Unselectable { normal } => normal.size(frame, max_width, max_height), + Self::Unselectable { normal } => normal.size(widthdb, max_width, max_height).await, Self::Selectable { normal, selected, .. } => { - let normal_size = normal.size(frame, max_width, max_height); - let selected_size = selected.size(frame, max_width, max_height); + let normal_size = normal.size(widthdb, max_width, max_height).await; + let selected_size = selected.size(widthdb, max_width, max_height).await; Size::new( normal_size.width.max(selected_size.width), normal_size.height.max(selected_size.height), @@ -327,14 +332,18 @@ impl List { } #[async_trait] -impl Widget for List { - fn size(&self, frame: &mut Frame, max_width: Option, _max_height: Option) -> Size { - let width = self - .rows - .iter() - .map(|r| r.size(frame, max_width, Some(1)).width) - .max() - .unwrap_or(0); +impl Widget for List { + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + _max_height: Option, + ) -> Size { + let mut width = 0; + for row in &self.rows { + let size = row.size(widthdb, max_width, Some(1)).await; + width = width.max(size.width); + } let height = self.rows.len(); Size::new(width, height as u16) } diff --git a/src/ui/widgets/padding.rs b/src/ui/widgets/padding.rs index e2be11d..e5b45cd 100644 --- a/src/ui/widgets/padding.rs +++ b/src/ui/widgets/padding.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size, WidthDb}; use super::{BoxedWidget, Widget}; @@ -66,14 +66,19 @@ impl Padding { #[async_trait] impl Widget for Padding { - fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Size { let horizontal = self.left + self.right; let vertical = self.top + self.bottom; let max_width = max_width.map(|w| w.saturating_sub(horizontal)); let max_height = max_height.map(|h| h.saturating_sub(vertical)); - let size = self.inner.size(frame, max_width, max_height); + let size = self.inner.size(widthdb, max_width, max_height).await; size + Size::new(horizontal, vertical) } diff --git a/src/ui/widgets/resize.rs b/src/ui/widgets/resize.rs index 67c07ed..b2edf22 100644 --- a/src/ui/widgets/resize.rs +++ b/src/ui/widgets/resize.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::{Frame, Size}; +use toss::{Frame, Size, WidthDb}; use super::{BoxedWidget, Widget}; @@ -45,7 +45,12 @@ impl Resize { #[async_trait] impl Widget for Resize { - fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Size { let max_width = match (max_width, self.max_width) { (None, None) => None, (Some(w), None) => Some(w), @@ -60,7 +65,7 @@ impl Widget for Resize { (Some(h), Some(sh)) => Some(h.min(sh)), }; - let size = self.inner.size(frame, max_width, max_height); + let size = self.inner.size(widthdb, max_width, max_height).await; let width = match self.min_width { Some(min_width) => size.width.max(min_width), diff --git a/src/ui/widgets/rules.rs b/src/ui/widgets/rules.rs index f6b01c1..eaff35f 100644 --- a/src/ui/widgets/rules.rs +++ b/src/ui/widgets/rules.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use toss::{Frame, Pos, Size}; +use toss::{Frame, Pos, Size, WidthDb}; use super::Widget; @@ -7,7 +7,12 @@ pub struct HRule; #[async_trait] impl Widget for HRule { - fn size(&self, _frame: &mut Frame, _max_width: Option, _max_height: Option) -> Size { + async fn size( + &self, + _widthdb: &mut WidthDb, + _max_width: Option, + _max_height: Option, + ) -> Size { Size::new(0, 1) } @@ -23,7 +28,12 @@ pub struct VRule; #[async_trait] impl Widget for VRule { - fn size(&self, _frame: &mut Frame, _max_width: Option, _max_height: Option) -> Size { + async fn size( + &self, + _widthdb: &mut WidthDb, + _max_width: Option, + _max_height: Option, + ) -> Size { Size::new(1, 0) } diff --git a/src/ui/widgets/text.rs b/src/ui/widgets/text.rs index 1a383f5..3ce1dbe 100644 --- a/src/ui/widgets/text.rs +++ b/src/ui/widgets/text.rs @@ -36,9 +36,13 @@ impl Text { #[async_trait] impl Widget for Text { - fn size(&self, frame: &mut Frame, max_width: Option, _max_height: Option) -> Size { - let lines = self.wrapped(frame.widthdb(), max_width); - let widthdb = frame.widthdb(); + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + _max_height: Option, + ) -> Size { + let lines = self.wrapped(widthdb, max_width); let min_width = lines .iter() .map(|l| widthdb.width(l.text().trim_end())) From 3f7ed630646186e033bdab8684889ae7ab75ae44 Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 5 Apr 2023 23:51:02 +0200 Subject: [PATCH 061/266] Add AsyncWidgetWrapper and WidgetWrapper --- src/ui.rs | 9 +++++-- src/ui/widgets.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index 208a8cd..b5e8873 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -32,6 +32,13 @@ use self::widgets::BoxedWidget; /// Time to spend batch processing events before redrawing the screen. const EVENT_PROCESSING_TIME: Duration = Duration::from_millis(1000 / 15); // 15 fps +/// Error for anything that can go wrong while rendering. +#[derive(Debug, thiserror::Error)] +pub enum UiError { + #[error("{0}")] + Io(#[from] io::Error), +} + pub enum UiEvent { GraphemeWidthsChanged, LogChanged, @@ -51,8 +58,6 @@ enum Mode { Log, } -// TODO Add Error for anything that can go wrong while rendering - pub struct Ui { event_tx: UnboundedSender, diff --git a/src/ui/widgets.rs b/src/ui/widgets.rs index 20fb6d3..6f137b8 100644 --- a/src/ui/widgets.rs +++ b/src/ui/widgets.rs @@ -19,7 +19,9 @@ pub mod rules; pub mod text; use async_trait::async_trait; -use toss::{Frame, Size, WidthDb}; +use toss::{AsyncWidget, Frame, Size, WidthDb}; + +use super::UiError; // TODO Add Error type and return Result-s (at least in Widget::render) @@ -42,3 +44,66 @@ impl From for BoxedWidget { Box::new(widget) } } + +/// Wrapper that implements [`Widget`] for an [`AsyncWidget`]. +pub struct AsyncWidgetWrapper { + inner: I, +} + +impl AsyncWidgetWrapper { + pub fn new(inner: I) -> Self { + Self { inner } + } +} + +#[async_trait] +impl Widget for AsyncWidgetWrapper +where + I: AsyncWidget + Send + Sync, +{ + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Size { + self.inner + .size(widthdb, max_width, max_height) + .await + .unwrap() + } + + async fn render(self: Box, frame: &mut Frame) { + self.inner.draw(frame).await.unwrap(); + } +} + +/// Wrapper that implements [`AsyncWidget`] for a [`Widget`]. +pub struct WidgetWrapper { + inner: BoxedWidget, +} + +impl WidgetWrapper { + pub fn new>(inner: W) -> Self { + Self { + inner: inner.into(), + } + } +} + +#[async_trait] +impl AsyncWidget for WidgetWrapper { + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Result { + Ok(self.inner.size(widthdb, max_width, max_height).await) + } + + async fn draw(self, frame: &mut Frame) -> Result<(), E> { + self.inner.render(frame).await; + Ok(()) + } +} From 07960142e09659a973c1485e7ea0bed2e4773568 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 26 Feb 2023 22:06:24 +0100 Subject: [PATCH 062/266] Add Popup AsyncWidget --- src/ui.rs | 1 + src/ui/widgets2.rs | 3 +++ src/ui/widgets2/popup.rs | 54 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 src/ui/widgets2.rs create mode 100644 src/ui/widgets2/popup.rs diff --git a/src/ui.rs b/src/ui.rs index b5e8873..33696cc 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -4,6 +4,7 @@ mod input; mod rooms; mod util; mod widgets; +mod widgets2; use std::convert::Infallible; use std::io; diff --git a/src/ui/widgets2.rs b/src/ui/widgets2.rs new file mode 100644 index 0000000..a12a5aa --- /dev/null +++ b/src/ui/widgets2.rs @@ -0,0 +1,3 @@ +mod popup; + +pub use self::popup::*; diff --git a/src/ui/widgets2/popup.rs b/src/ui/widgets2/popup.rs new file mode 100644 index 0000000..d8dae47 --- /dev/null +++ b/src/ui/widgets2/popup.rs @@ -0,0 +1,54 @@ +use async_trait::async_trait; +use toss::widgets::{Background, Border, Float, Layer2, Padding, Text}; +use toss::{AsyncWidget, Frame, Size, Style, Styled, WidgetExt, WidthDb}; + +type Body = Background>>; +type Title = Float>>>; + +pub struct Popup(Float, Title>>); + +impl Popup { + pub fn new>(inner: I, title: S) -> Self { + let title = Text::new(title) + .padding() + .with_horizontal(1) + // The background displaces the border without affecting the style + .background() + .with_style(Style::new()) + .padding() + .with_horizontal(2) + .float() + .with_top() + .with_left(); + + let body = inner.padding().with_horizontal(1).border().background(); + + Self(title.above(body).float().with_center()) + } + + pub fn with_border_style(mut self, style: Style) -> Self { + let border = &mut self.0.inner.first.inner; + border.style = style; + self + } +} + +#[async_trait] +impl AsyncWidget for Popup +where + E: Send, + I: AsyncWidget + Send + Sync, +{ + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Result { + self.0.size(widthdb, max_width, max_height).await + } + + async fn draw(self, frame: &mut Frame) -> Result<(), E> { + self.0.draw(frame).await + } +} From 267ef2bee91a55905eaf463b15a6cca557eed09c Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 12 Apr 2023 00:15:30 +0200 Subject: [PATCH 063/266] Add List AsyncWidget --- src/ui/widgets2.rs | 2 + src/ui/widgets2/list.rs | 328 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 330 insertions(+) create mode 100644 src/ui/widgets2/list.rs diff --git a/src/ui/widgets2.rs b/src/ui/widgets2.rs index a12a5aa..aed063a 100644 --- a/src/ui/widgets2.rs +++ b/src/ui/widgets2.rs @@ -1,3 +1,5 @@ +mod list; mod popup; +pub use self::list::*; pub use self::popup::*; diff --git a/src/ui/widgets2/list.rs b/src/ui/widgets2/list.rs new file mode 100644 index 0000000..5693a8a --- /dev/null +++ b/src/ui/widgets2/list.rs @@ -0,0 +1,328 @@ +use std::vec; + +use async_trait::async_trait; +use toss::{AsyncWidget, Frame, Pos, Size, WidthDb}; + +#[derive(Debug, Clone)] +struct Cursor { + /// Id of the element the cursor is pointing to. + /// + /// If the rows change (e.g. reorder) but there is still a row with this id, + /// the cursor is moved to this row. + id: Id, + + /// Index of the row the cursor is pointing to. + /// + /// If the rows change and there is no longer a row with the cursor's id, + /// the cursor is moved up or down to the next selectable row. This way, it + /// stays close to its previous position. + idx: usize, +} + +impl Cursor { + pub fn new(id: Id, idx: usize) -> Self { + Self { id, idx } + } +} + +#[derive(Debug)] +pub struct ListState { + /// Amount of lines that the list is scrolled, i.e. offset from the top. + offset: usize, + + /// A cursor within the list. + /// + /// Set to `None` if the list contains no selectable rows. + cursor: Option>, + + /// Height of the list when it was last rendered. + last_height: u16, + + /// Rows when the list was last rendered. + last_rows: Vec>, +} + +impl ListState { + pub fn new() -> Self { + Self { + offset: 0, + cursor: None, + last_height: 0, + last_rows: vec![], + } + } + + pub fn selected(&self) -> Option<&Id> { + self.cursor.as_ref().map(|cursor| &cursor.id) + } +} + +impl ListState { + fn first_selectable(&self) -> Option> { + self.last_rows + .iter() + .enumerate() + .find_map(|(i, row)| row.as_ref().map(|id| Cursor::new(id.clone(), i))) + } + + fn last_selectable(&self) -> Option> { + self.last_rows + .iter() + .enumerate() + .rev() + .find_map(|(i, row)| row.as_ref().map(|id| Cursor::new(id.clone(), i))) + } + + fn selectable_at_or_before_index(&self, i: usize) -> Option> { + self.last_rows + .iter() + .enumerate() + .take(i + 1) + .rev() + .find_map(|(i, row)| row.as_ref().map(|id| Cursor::new(id.clone(), i))) + } + + fn selectable_at_or_after_index(&self, i: usize) -> Option> { + self.last_rows + .iter() + .enumerate() + .skip(i) + .find_map(|(i, row)| row.as_ref().map(|id| Cursor::new(id.clone(), i))) + } + + fn selectable_before_index(&self, i: usize) -> Option> { + self.last_rows + .iter() + .enumerate() + .take(i) + .rev() + .find_map(|(i, row)| row.as_ref().map(|id| Cursor::new(id.clone(), i))) + } + + fn selectable_after_index(&self, i: usize) -> Option> { + self.last_rows + .iter() + .enumerate() + .skip(i + 1) + .find_map(|(i, row)| row.as_ref().map(|id| Cursor::new(id.clone(), i))) + } + + fn move_cursor_to_make_it_visible(&mut self) { + if let Some(cursor) = &self.cursor { + let first_visible_line_idx = self.offset; + let last_visible_line_idx = self + .offset + .saturating_add(self.last_height.into()) + .saturating_sub(1); + + let new_cursor = if cursor.idx < first_visible_line_idx { + self.selectable_at_or_after_index(first_visible_line_idx) + } else if cursor.idx > last_visible_line_idx { + self.selectable_at_or_before_index(last_visible_line_idx) + } else { + return; + }; + + if let Some(new_cursor) = new_cursor { + self.cursor = Some(new_cursor); + } + } + } + + fn scroll_so_cursor_is_visible(&mut self) { + if self.last_height == 0 { + // Cursor can't be visible because nothing is visible + return; + } + + if let Some(cursor) = &self.cursor { + // As long as height > 0, min <= max is true + let min = (cursor.idx + 1).saturating_sub(self.last_height.into()); + let max = cursor.idx; // Rows have a height of 1 + self.offset = self.offset.clamp(min, max); + } + } + + fn clamp_scrolling(&mut self) { + let min = 0; + let max = self.last_rows.len().saturating_sub(self.last_height.into()); + self.offset = self.offset.clamp(min, max); + } + + fn scroll_to(&mut self, new_offset: usize) { + self.offset = new_offset; + self.clamp_scrolling(); + self.move_cursor_to_make_it_visible(); + } + + fn move_cursor_to(&mut self, new_cursor: Cursor) { + self.cursor = Some(new_cursor); + self.scroll_so_cursor_is_visible(); + self.clamp_scrolling(); + } + + /// Scroll the list up by an amount of lines. + pub fn scroll_up(&mut self, lines: usize) { + self.scroll_to(self.offset.saturating_sub(lines)); + } + + /// Scroll the list down by an amount of lines. + pub fn scroll_down(&mut self, lines: usize) { + self.scroll_to(self.offset.saturating_add(lines)); + } + + /// Scroll so that the cursor is in the center of the widget, or at least as + /// close as possible. + pub fn center_cursor(&mut self) { + if let Some(cursor) = &self.cursor { + let height: usize = self.last_height.into(); + self.scroll_to(cursor.idx.saturating_sub(height / 2)); + } + } + + /// Move the cursor up to the next selectable row. + pub fn move_cursor_up(&mut self) { + if let Some(cursor) = &self.cursor { + if let Some(new_cursor) = self.selectable_before_index(cursor.idx) { + self.move_cursor_to(new_cursor); + } + } + } + + /// Move the cursor down to the next selectable row. + pub fn move_cursor_down(&mut self) { + if let Some(cursor) = &self.cursor { + if let Some(new_cursor) = self.selectable_after_index(cursor.idx) { + self.move_cursor_to(new_cursor); + } + } + } + + /// Move the cursor to the first selectable row. + pub fn move_cursor_to_top(&mut self) { + if let Some(new_cursor) = self.first_selectable() { + self.move_cursor_to(new_cursor); + } + } + + /// Move the cursor to the last selectable row. + pub fn move_cursor_to_bottom(&mut self) { + if let Some(new_cursor) = self.last_selectable() { + self.move_cursor_to(new_cursor); + } + } + + pub fn widget(&mut self) -> List<'_, Id, W> { + List { + state: self, + rows: vec![], + } + } +} + +impl ListState { + fn selectable_of_id(&self, id: &Id) -> Option> { + self.last_rows + .iter() + .enumerate() + .find_map(|(i, row)| match row { + Some(rid) if rid == id => Some(Cursor::new(rid.clone(), i)), + _ => None, + }) + } + + fn fix_cursor(&mut self) { + let new_cursor = if let Some(cursor) = &self.cursor { + self.selectable_of_id(&cursor.id) + .or_else(|| self.selectable_at_or_before_index(cursor.idx)) + .or_else(|| self.selectable_at_or_after_index(cursor.idx)) + } else { + self.first_selectable() + }; + + if let Some(new_cursor) = new_cursor { + self.move_cursor_to(new_cursor); + } else { + self.cursor = None; + } + } +} + +struct Row { + id: Option, + widget: W, +} + +pub struct List<'a, Id, W> { + state: &'a mut ListState, + rows: Vec>, +} + +impl List<'_, Id, W> { + pub fn state(&self) -> &ListState { + &self.state + } + + pub fn state_mut(&mut self) -> &mut ListState { + &mut self.state + } + + pub fn is_empty(&self) -> bool { + self.rows.is_empty() + } + + pub fn add_unsel(&mut self, widget: W) { + self.rows.push(Row { id: None, widget }); + } + + pub fn add_sel(&mut self, id: Id, widget: W) { + self.rows.push(Row { + id: Some(id), + widget, + }); + } +} + +#[async_trait] +impl AsyncWidget for List<'_, Id, W> +where + Id: Clone + Eq + Send + Sync, + W: AsyncWidget + Send + Sync, +{ + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + _max_height: Option, + ) -> Result { + let mut width = 0; + for row in &self.rows { + let size = row.widget.size(widthdb, max_width, Some(1)).await?; + width = width.max(size.width); + } + let height = self.rows.len().try_into().unwrap_or(u16::MAX); + Ok(Size::new(width, height)) + } + + async fn draw(self, frame: &mut Frame) -> Result<(), E> { + let size = frame.size(); + + self.state.last_rows = self.rows.iter().map(|row| row.id.clone()).collect(); + self.state.last_height = size.height; + self.state.fix_cursor(); + + for (y, row) in self + .rows + .into_iter() + .skip(self.state.offset) + .take(size.height.into()) + .enumerate() + { + frame.push(Pos::new(0, y as i32), Size::new(size.width, 1)); + row.widget.draw(frame).await?; + frame.pop(); + } + + Ok(()) + } +} From 8de5bf87af3940696bb71b85f4896ff7bd8daa0c Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 12 Apr 2023 18:44:24 +0200 Subject: [PATCH 064/266] Add util2 module for new widgets --- src/ui.rs | 1 + src/ui/util2.rs | 191 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 src/ui/util2.rs diff --git a/src/ui.rs b/src/ui.rs index 33696cc..c969e17 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -3,6 +3,7 @@ mod euph; mod input; mod rooms; mod util; +mod util2; mod widgets; mod widgets2; diff --git a/src/ui/util2.rs b/src/ui/util2.rs new file mode 100644 index 0000000..3f2b2d9 --- /dev/null +++ b/src/ui/util2.rs @@ -0,0 +1,191 @@ +use std::io; +use std::sync::Arc; + +use parking_lot::FairMutex; +use toss::widgets::EditorState; +use toss::Terminal; + +use super::input::{key, InputEvent, KeyBindingsList}; +use super::widgets2::ListState; + +pub fn prompt( + terminal: &mut Terminal, + crossterm_lock: &Arc>, + initial_text: &str, +) -> io::Result { + let content = { + let _guard = crossterm_lock.lock(); + terminal.suspend().expect("could not suspend"); + let content = edit::edit(initial_text); + terminal.unsuspend().expect("could not unsuspend"); + content + }; + + content +} + +////////// +// List // +////////// + +pub fn list_list_key_bindings(bindings: &mut KeyBindingsList) { + bindings.binding("j/k, ↓/↑", "move cursor up/down"); + bindings.binding("g, home", "move cursor to top"); + bindings.binding("G, end", "move cursor to bottom"); + bindings.binding("ctrl+y/e", "scroll up/down"); +} + +pub fn handle_list_input_event(list: &mut ListState, event: &InputEvent) -> bool { + match event { + key!('k') | key!(Up) => list.move_cursor_up(), + key!('j') | key!(Down) => list.move_cursor_down(), + key!('g') | key!(Home) => list.move_cursor_to_top(), + key!('G') | key!(End) => list.move_cursor_to_bottom(), + key!(Ctrl + 'y') => list.scroll_up(1), + key!(Ctrl + 'e') => list.scroll_down(1), + _ => return false, + } + + true +} + +//////////// +// Editor // +//////////// + +fn list_editor_editing_key_bindings( + bindings: &mut KeyBindingsList, + char_filter: impl Fn(char) -> bool, +) { + if char_filter('\n') { + bindings.binding("enter+", "insert newline"); + } + + bindings.binding("ctrl+h, backspace", "delete before cursor"); + bindings.binding("ctrl+d, delete", "delete after cursor"); + bindings.binding("ctrl+l", "clear editor contents"); +} + +fn list_editor_cursor_movement_key_bindings(bindings: &mut KeyBindingsList) { + bindings.binding("ctrl+b, ←", "move cursor left"); + bindings.binding("ctrl+f, →", "move cursor right"); + bindings.binding("alt+b, ctrl+←", "move cursor left a word"); + bindings.binding("alt+f, ctrl+→", "move cursor right a word"); + bindings.binding("ctrl+a, home", "move cursor to start of line"); + bindings.binding("ctrl+e, end", "move cursor to end of line"); + bindings.binding("↑/↓", "move cursor up/down"); +} + +pub fn list_editor_key_bindings( + bindings: &mut KeyBindingsList, + char_filter: impl Fn(char) -> bool, +) { + list_editor_editing_key_bindings(bindings, char_filter); + bindings.empty(); + list_editor_cursor_movement_key_bindings(bindings); +} + +pub fn handle_editor_input_event( + editor: &mut EditorState, + terminal: &mut Terminal, + event: &InputEvent, + char_filter: impl Fn(char) -> bool, +) -> bool { + match event { + // Enter with *any* modifier pressed - if ctrl and shift don't + // work, maybe alt does + key!(Enter) => return false, + InputEvent::Key(crate::ui::input::KeyEvent { + code: crossterm::event::KeyCode::Enter, + .. + }) if char_filter('\n') => editor.insert_char(terminal.widthdb(), '\n'), + + // Editing + key!(Char ch) if char_filter(*ch) => editor.insert_char(terminal.widthdb(), *ch), + key!(Paste str) => { + // It seems that when pasting, '\n' are converted into '\r' for some + // reason. I don't really know why, or at what point this happens. + // Vim converts any '\r' pasted via the terminal into '\n', so I + // decided to mirror that behaviour. + let str = str.replace('\r', "\n"); + if str.chars().all(char_filter) { + editor.insert_str(terminal.widthdb(), &str); + } else { + return false; + } + } + key!(Ctrl + 'h') | key!(Backspace) => editor.backspace(terminal.widthdb()), + key!(Ctrl + 'd') | key!(Delete) => editor.delete(), + key!(Ctrl + 'l') => editor.clear(), + // TODO Key bindings to delete words + + // Cursor movement + key!(Ctrl + 'b') | key!(Left) => editor.move_cursor_left(terminal.widthdb()), + key!(Ctrl + 'f') | key!(Right) => editor.move_cursor_right(terminal.widthdb()), + key!(Alt + 'b') | key!(Ctrl + Left) => editor.move_cursor_left_a_word(terminal.widthdb()), + key!(Alt + 'f') | key!(Ctrl + Right) => editor.move_cursor_right_a_word(terminal.widthdb()), + key!(Ctrl + 'a') | key!(Home) => editor.move_cursor_to_start_of_line(terminal.widthdb()), + key!(Ctrl + 'e') | key!(End) => editor.move_cursor_to_end_of_line(terminal.widthdb()), + key!(Up) => editor.move_cursor_up(terminal.widthdb()), + key!(Down) => editor.move_cursor_down(terminal.widthdb()), + + _ => return false, + } + + true +} + +fn edit_externally( + editor: &mut EditorState, + terminal: &mut Terminal, + crossterm_lock: &Arc>, +) -> io::Result<()> { + let text = prompt(terminal, crossterm_lock, editor.text())?; + + if text.trim().is_empty() { + // The user likely wanted to abort the edit and has deleted the + // entire text (bar whitespace left over by some editors). + return Ok(()); + } + + if let Some(text) = text.strip_suffix('\n') { + // Some editors like vim add a trailing newline that would look out of + // place in cove's editors. To intentionally add a trailing newline, + // simply add two in-editor. + editor.set_text(terminal.widthdb(), text.to_string()); + } else { + editor.set_text(terminal.widthdb(), text); + } + + Ok(()) +} + +pub fn list_editor_key_bindings_allowing_external_editing( + bindings: &mut KeyBindingsList, + char_filter: impl Fn(char) -> bool, +) { + list_editor_editing_key_bindings(bindings, char_filter); + bindings.binding("ctrl+x", "edit in external editor"); + bindings.empty(); + list_editor_cursor_movement_key_bindings(bindings); +} + +pub fn handle_editor_input_event_allowing_external_editing( + editor: &mut EditorState, + terminal: &mut Terminal, + crossterm_lock: &Arc>, + event: &InputEvent, + char_filter: impl Fn(char) -> bool, +) -> io::Result { + if let key!(Ctrl + 'x') = event { + edit_externally(editor, terminal, crossterm_lock)?; + Ok(true) + } else { + Ok(handle_editor_input_event( + editor, + terminal, + event, + char_filter, + )) + } +} From d5b6dd980233a40a09632a88af849a46750c704b Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 18 Feb 2023 21:20:40 +0100 Subject: [PATCH 065/266] Migrate topmost widget to AsyncWidget --- src/ui.rs | 55 ++++++++++++++++++++++++++----------------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index c969e17..4097ffc 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -16,7 +16,8 @@ use parking_lot::FairMutex; use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; use tokio::task; -use toss::Terminal; +use toss::widgets::BoxedAsync; +use toss::{Terminal, WidgetExt}; use crate::config::Config; use crate::logger::{LogMsg, Logger}; @@ -27,9 +28,8 @@ pub use self::chat::ChatMsg; use self::chat::ChatState; use self::input::{key, InputEvent, KeyBindingsList}; use self::rooms::Rooms; -use self::widgets::layer::Layer; use self::widgets::list::ListState; -use self::widgets::BoxedWidget; +use self::widgets::WidgetWrapper; /// Time to spend batch processing events before redrawing the screen. const EVENT_PROCESSING_TIME: Duration = Duration::from_millis(1000 / 15); // 15 fps @@ -143,20 +143,27 @@ impl Ui { terminal: &mut Terminal, mut event_rx: UnboundedReceiver, crossterm_lock: Arc>, - ) -> io::Result<()> { - // Initial render so we don't show a blank screen until the first event - terminal.autoresize()?; - terminal.frame().reset(); - self.widget().await.render(terminal.frame()).await; - terminal.present()?; + ) -> Result<(), UiError> { + let mut redraw = true; loop { - // 1. Handle events (in batches) + // Redraw if necessary + if redraw { + redraw = false; + terminal.present_async_widget(self.widget().await).await?; + + if terminal.measuring_required() { + let _guard = crossterm_lock.lock(); + terminal.measure_widths()?; + ok_or_return!(self.event_tx.send(UiEvent::GraphemeWidthsChanged), Ok(())); + } + } + + // Handle events (in batches) let mut event = match event_rx.recv().await { Some(event) => event, None => return Ok(()), }; - let mut redraw = false; let end_time = Instant::now() + EVENT_PROCESSING_TIME; loop { match self.handle_event(terminal, &crossterm_lock, event).await { @@ -173,33 +180,23 @@ impl Ui { Err(TryRecvError::Disconnected) => return Ok(()), }; } - - if redraw { - // 2. Draw and present resulting state - terminal.autoresize()?; - self.widget().await.render(terminal.frame()).await; - terminal.present()?; - - // 3. Measure grapheme widths - if terminal.measuring_required() { - let _guard = crossterm_lock.lock(); - terminal.measure_widths()?; - ok_or_return!(self.event_tx.send(UiEvent::GraphemeWidthsChanged), Ok(())); - } - } } } - async fn widget(&mut self) -> BoxedWidget { + async fn widget(&mut self) -> BoxedAsync<'_, UiError> { let widget = match self.mode { - Mode::Main => self.rooms.widget().await, - Mode::Log => self.log_chat.widget(String::new(), true).into(), + Mode::Main => WidgetWrapper::new(self.rooms.widget().await).boxed_async(), + Mode::Log => { + WidgetWrapper::new(self.log_chat.widget(String::new(), true)).boxed_async() + } }; if let Some(key_bindings_list) = &self.key_bindings_list { let mut bindings = KeyBindingsList::new(key_bindings_list); self.list_key_bindings(&mut bindings).await; - Layer::new(vec![widget, bindings.widget()]).into() + WidgetWrapper::new(bindings.widget()) + .above(widget) + .boxed_async() } else { widget } From adc70ad233262d50fad51648eac57d6a86f56054 Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 12 Apr 2023 00:49:39 +0200 Subject: [PATCH 066/266] Migrate key bindings list widget to AsyncWidget --- src/ui.rs | 20 ++++++-- src/ui/input.rs | 120 ++++++++++++++++++++++++++++-------------------- 2 files changed, 86 insertions(+), 54 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index 4097ffc..b5b751f 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -28,8 +28,8 @@ pub use self::chat::ChatMsg; use self::chat::ChatState; use self::input::{key, InputEvent, KeyBindingsList}; use self::rooms::Rooms; -use self::widgets::list::ListState; use self::widgets::WidgetWrapper; +use self::widgets2::ListState; /// Time to spend batch processing events before redrawing the screen. const EVENT_PROCESSING_TIME: Duration = Duration::from_millis(1000 / 15); // 15 fps @@ -184,6 +184,14 @@ impl Ui { } async fn widget(&mut self) -> BoxedAsync<'_, UiError> { + let key_bindings_list = if self.key_bindings_list.is_some() { + let mut bindings = KeyBindingsList::new(); + self.list_key_bindings(&mut bindings).await; + Some(bindings) + } else { + None + }; + let widget = match self.mode { Mode::Main => WidgetWrapper::new(self.rooms.widget().await).boxed_async(), Mode::Log => { @@ -191,10 +199,12 @@ impl Ui { } }; - if let Some(key_bindings_list) = &self.key_bindings_list { - let mut bindings = KeyBindingsList::new(key_bindings_list); - self.list_key_bindings(&mut bindings).await; - WidgetWrapper::new(bindings.widget()) + if let Some(key_bindings_list) = key_bindings_list { + // We checked whether this was Some earlier. + let list_state = self.key_bindings_list.as_mut().unwrap(); + + key_bindings_list + .widget(list_state) .above(widget) .boxed_async() } else { diff --git a/src/ui/input.rs b/src/ui/input.rs index 4c3362e..dd0a2dc 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -2,19 +2,11 @@ use std::convert::Infallible; use crossterm::event::{Event, KeyCode, KeyModifiers}; use crossterm::style::Stylize; -use toss::{Style, Styled}; +use toss::widgets::{BoxedAsync, Empty, Join2, Text}; +use toss::{Style, Styled, WidgetExt}; -use super::widgets::background::Background; -use super::widgets::border::Border; -use super::widgets::empty::Empty; -use super::widgets::float::Float; -use super::widgets::join::{HJoin, Segment}; -use super::widgets::layer::Layer; -use super::widgets::list::{List, ListState}; -use super::widgets::padding::Padding; -use super::widgets::resize::Resize; -use super::widgets::text::Text; -use super::widgets::BoxedWidget; +use super::widgets2::ListState; +use super::UiError; #[derive(Debug, Clone)] pub enum InputEvent { @@ -83,66 +75,96 @@ macro_rules! key { } pub(crate) use key; -/// Helper wrapper around a list widget for a more consistent key binding style. -pub struct KeyBindingsList(List); +enum Row { + Empty, + Heading(String), + Binding(String, String), + BindingContd(String), +} + +pub struct KeyBindingsList(Vec); impl KeyBindingsList { /// Width of the left column of key bindings. const BINDING_WIDTH: u16 = 20; - pub fn new(state: &ListState) -> Self { - Self(state.widget()) + pub fn new() -> Self { + Self(vec![]) } fn binding_style() -> Style { Style::new().cyan() } - pub fn widget(self) -> BoxedWidget { - let binding_style = Self::binding_style(); - Float::new(Layer::new(vec![ - Border::new(Background::new(Padding::new(self.0).horizontal(1))).into(), - Float::new( - Padding::new(Text::new( - Styled::new("jk/↓↑", binding_style) - .then_plain(" to scroll, ") - .then("esc", binding_style) - .then_plain(" to close"), - )) - .horizontal(1), + fn row_widget(row: Row) -> BoxedAsync<'static, UiError> { + match row { + Row::Empty => Empty::new().boxed_async(), + + Row::Heading(name) => Text::new((name, Style::new().bold())).boxed_async(), + + Row::Binding(binding, description) => Join2::horizontal( + Text::new((binding, Self::binding_style())) + .padding() + .with_right(1) + .resize() + .with_min_width(Self::BINDING_WIDTH) + .segment(), + Text::new(description).segment(), ) - .horizontal(0.5) - .into(), - ])) - .horizontal(0.5) - .vertical(0.5) - .into() + .boxed_async(), + + Row::BindingContd(description) => Join2::horizontal( + Empty::new().with_width(Self::BINDING_WIDTH).segment(), + Text::new(description).segment(), + ) + .boxed_async(), + } + } + + pub fn widget(self, list_state: &mut ListState) -> BoxedAsync<'_, UiError> { + let binding_style = Self::binding_style(); + + let hint_text = Styled::new("jk/↓↑", binding_style) + .then_plain(" to scroll, ") + .then("esc", binding_style) + .then_plain(" to close"); + + let hint = Text::new(hint_text) + .padding() + .with_horizontal(1) + .float() + .with_horizontal(0.5) + .with_vertical(0.0); + + let mut list = list_state.widget(); + for row in self.0 { + list.add_unsel(Self::row_widget(row)); + } + + list.padding() + .with_horizontal(1) + .border() + .below(hint) + .background() + .float() + .with_center() + .boxed_async() } pub fn empty(&mut self) { - self.0.add_unsel(Empty::new()); + self.0.push(Row::Empty); } pub fn heading(&mut self, name: &str) { - self.0.add_unsel(Text::new((name, Style::new().bold()))); + self.0.push(Row::Heading(name.to_string())); } pub fn binding(&mut self, binding: &str, description: &str) { - let widget = HJoin::new(vec![ - Segment::new( - Resize::new(Padding::new(Text::new((binding, Self::binding_style()))).right(1)) - .min_width(Self::BINDING_WIDTH), - ), - Segment::new(Text::new(description)), - ]); - self.0.add_unsel(widget); + self.0 + .push(Row::Binding(binding.to_string(), description.to_string())); } pub fn binding_ctd(&mut self, description: &str) { - let widget = HJoin::new(vec![ - Segment::new(Resize::new(Empty::new()).min_width(Self::BINDING_WIDTH)), - Segment::new(Text::new(description)), - ]); - self.0.add_unsel(widget); + self.0.push(Row::BindingContd(description.to_string())); } } From ead4fa7c8a6e2dccc73078bbed663753f0985d79 Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 12 Apr 2023 13:30:25 +0200 Subject: [PATCH 067/266] Migrate rooms list to AsyncWidget --- src/ui.rs | 2 +- src/ui/rooms.rs | 198 +++++++++++++++++++++++++++--------------------- 2 files changed, 114 insertions(+), 86 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index b5b751f..c0acdd8 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -193,7 +193,7 @@ impl Ui { }; let widget = match self.mode { - Mode::Main => WidgetWrapper::new(self.rooms.widget().await).boxed_async(), + Mode::Main => self.rooms.widget().await, Mode::Log => { WidgetWrapper::new(self.log_chat.widget(String::new(), true)).boxed_async() } diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index c0e875c..3be809f 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -8,7 +8,8 @@ use euphoxide::bot::instance::{Event, ServerConfig}; use euphoxide::conn::{self, Joined}; use parking_lot::FairMutex; use tokio::sync::mpsc; -use toss::{Style, Styled, Terminal}; +use toss::widgets::{BoxedAsync, EditorState, Empty, Join2, Text}; +use toss::{Style, Styled, Terminal, WidgetExt}; use crate::config::{Config, RoomsSortOrder}; use crate::euph; @@ -17,15 +18,9 @@ use crate::vault::Vault; use super::euph::room::EuphRoom; use super::input::{key, InputEvent, KeyBindingsList}; -use super::widgets::editor::EditorState; -use super::widgets::join::{HJoin, Segment, VJoin}; -use super::widgets::layer::Layer; -use super::widgets::list::{List, ListState}; -use super::widgets::popup::Popup; -use super::widgets::resize::Resize; -use super::widgets::text::Text; -use super::widgets::BoxedWidget; -use super::{util, UiEvent}; +use super::widgets::WidgetWrapper; +use super::widgets2::{List, ListState, Popup}; +use super::{util2, UiError, UiEvent}; enum State { ShowList, @@ -34,6 +29,7 @@ enum State { Delete(String, EditorState), } +#[derive(Clone, Copy)] enum Order { Alphabet, Importance, @@ -169,49 +165,59 @@ impl Rooms { } } - pub async fn widget(&mut self) -> BoxedWidget { + pub async fn widget(&mut self) -> BoxedAsync<'_, UiError> { match &self.state { State::ShowRoom(_) => {} _ => self.stabilize_rooms().await, } - match &self.state { - State::ShowList => self.rooms_widget().await, - State::ShowRoom(name) => { + match &mut self.state { + State::ShowList => { + Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order).await + } + + State::ShowRoom(name) => WidgetWrapper::new( self.euph_rooms .get_mut(name) .expect("room exists after stabilization") .widget() + .await, + ) + .boxed_async(), + + State::Connect(editor) => { + Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order) .await + .below(Self::new_room_widget(editor)) + .boxed_async() + } + + State::Delete(name, editor) => { + Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order) + .await + .below(Self::delete_room_widget(name, editor)) + .boxed_async() } - State::Connect(editor) => Layer::new(vec![ - self.rooms_widget().await, - Self::new_room_widget(editor), - ]) - .into(), - State::Delete(name, editor) => Layer::new(vec![ - self.rooms_widget().await, - Self::delete_room_widget(name, editor), - ]) - .into(), } } - fn new_room_widget(editor: &EditorState) -> BoxedWidget { + fn new_room_widget(editor: &mut EditorState) -> BoxedAsync<'_, UiError> { let room_style = Style::new().bold().blue(); - let editor = editor.widget().highlight(|s| Styled::new(s, room_style)); - Popup::new(HJoin::new(vec![ - Segment::new(Text::new(("&", room_style))), - Segment::new(editor).priority(0), - ])) - .title("Connect to") - .build() + + let inner = Join2::horizontal( + Text::new(("&", room_style)).segment().with_fixed(true), + editor + .widget() + .with_highlight(|s| Styled::new(s, room_style)) + .segment(), + ); + + Popup::new(inner, "Connect to").boxed_async() } - fn delete_room_widget(name: &str, editor: &EditorState) -> BoxedWidget { + fn delete_room_widget<'a>(name: &str, editor: &'a mut EditorState) -> BoxedAsync<'a, UiError> { let warn_style = Style::new().bold().red(); let room_style = Style::new().bold().blue(); - let editor = editor.widget().highlight(|s| Styled::new(s, room_style)); let text = Styled::new_plain("Are you sure you want to delete ") .then("&", room_style) .then(name, room_style) @@ -222,20 +228,32 @@ impl Rooms { .then_plain(".\n\n") .then_plain("To confirm the deletion, ") .then_plain("enter the full name of the room and press enter:"); - Popup::new(VJoin::new(vec![ - // The HJoin prevents the text from filling up the entire available + + let inner = Join2::vertical( + // The Join prevents the text from filling up the entire available // space if the editor is wider than the text. - Segment::new(HJoin::new(vec![Segment::new( - Resize::new(Text::new(text).wrap(true)).max_width(54), - )])), - Segment::new(HJoin::new(vec![ - Segment::new(Text::new(("&", room_style))), - Segment::new(editor).priority(0), - ])), - ])) - .title(("Delete room", warn_style)) - .border(warn_style) - .build() + Join2::horizontal( + Text::new(text) + .resize() + .with_max_width(54) + .segment() + .with_growing(false), + Empty::new().segment(), + ) + .segment(), + Join2::horizontal( + Text::new(("&", room_style)).segment().with_fixed(true), + editor + .widget() + .with_highlight(|s| Styled::new(s, room_style)) + .segment(), + ) + .segment(), + ); + + Popup::new(inner, "Delete room") + .with_border_style(warn_style) + .boxed_async() } fn format_pbln(joined: &Joined) -> String { @@ -322,8 +340,8 @@ impl Rooms { } } - fn sort_rooms(&self, rooms: &mut [(&String, Option<&euph::State>, usize)]) { - match self.order { + fn sort_rooms(rooms: &mut [(&String, Option<&euph::State>, usize)], order: Order) { + match order { Order::Alphabet => rooms.sort_unstable_by_key(|(name, _, _)| *name), Order::Importance => rooms.sort_unstable_by_key(|(name, state, unseen)| { (state.is_none(), *unseen == 0, *name) @@ -331,8 +349,12 @@ impl Rooms { } } - async fn render_rows(&self, list: &mut List) { - if self.euph_rooms.is_empty() { + async fn render_rows( + list: &mut List<'_, String, Text>, + euph_rooms: &HashMap, + order: Order, + ) { + if euph_rooms.is_empty() { list.add_unsel(Text::new(( "Press F1 for key bindings", Style::new().grey().italic(), @@ -340,37 +362,43 @@ impl Rooms { } let mut rooms = vec![]; - for (name, room) in &self.euph_rooms { + for (name, room) in euph_rooms { let state = room.room_state(); let unseen = room.unseen_msgs_count().await; rooms.push((name, state, unseen)); } - self.sort_rooms(&mut rooms); + Self::sort_rooms(&mut rooms, order); for (name, state, unseen) in rooms { - let room_style = Style::new().bold().blue(); - let room_sel_style = Style::new().bold().black().on_white(); + let style = if list.state().selected() == Some(name) { + Style::new().bold().black().on_white() + } else { + Style::new().bold().blue() + }; - let mut normal = Styled::new(format!("&{name}"), room_style); - let mut selected = Styled::new(format!("&{name}"), room_sel_style); + let text = Styled::new(format!("&{name}"), style) + .and_then(Self::format_room_info(state, unseen)); - let info = Self::format_room_info(state, unseen); - normal = normal.and_then(info.clone()); - selected = selected.and_then(info); - - list.add_sel(name.clone(), Text::new(normal), Text::new(selected)); + list.add_sel(name.clone(), Text::new(text)); } } - async fn rooms_widget(&self) -> BoxedWidget { + async fn rooms_widget<'a>( + list: &'a mut ListState, + euph_rooms: &HashMap, + order: Order, + ) -> BoxedAsync<'a, UiError> { let heading_style = Style::new().bold(); - let amount = self.euph_rooms.len(); - let heading = - Text::new(Styled::new("Rooms", heading_style).then_plain(format!(" ({amount})"))); + let heading_text = + Styled::new("Rooms", heading_style).then_plain(format!(" ({})", euph_rooms.len())); - let mut list = self.list.widget().focus(true); - self.render_rows(&mut list).await; + let mut list = list.widget(); + Self::render_rows(&mut list, euph_rooms, order).await; - VJoin::new(vec![Segment::new(heading), Segment::new(list).priority(0)]).into() + Join2::vertical( + Text::new(heading_text).segment().with_fixed(true), + list.segment(), + ) + .boxed_async() } fn room_char(c: char) -> bool { @@ -379,7 +407,7 @@ impl Rooms { fn list_showlist_key_bindings(bindings: &mut KeyBindingsList) { bindings.heading("Rooms"); - util::list_list_key_bindings(bindings); + util2::list_list_key_bindings(bindings); bindings.empty(); bindings.binding("enter", "enter selected room"); bindings.binding("c", "connect to selected room"); @@ -395,20 +423,20 @@ impl Rooms { } fn handle_showlist_input_event(&mut self, event: &InputEvent) -> bool { - if util::handle_list_input_event(&mut self.list, event) { + if util2::handle_list_input_event(&mut self.list, event) { return true; } match event { key!(Enter) => { - if let Some(name) = self.list.cursor() { - self.state = State::ShowRoom(name); + if let Some(name) = self.list.selected() { + self.state = State::ShowRoom(name.clone()); } return true; } key!('c') => { - if let Some(name) = self.list.cursor() { - self.connect_to_room(name); + if let Some(name) = self.list.selected() { + self.connect_to_room(name.clone()); } return true; } @@ -417,8 +445,8 @@ impl Rooms { return true; } key!('d') => { - if let Some(name) = self.list.cursor() { - self.disconnect_from_room(&name); + if let Some(name) = self.list.selected() { + self.disconnect_from_room(&name.clone()); } return true; } @@ -454,8 +482,8 @@ impl Rooms { return true; } key!('X') => { - if let Some(name) = self.list.cursor() { - self.state = State::Delete(name, EditorState::new()); + if let Some(name) = self.list.selected() { + self.state = State::Delete(name.clone(), EditorState::new()); } return true; } @@ -493,13 +521,13 @@ impl Rooms { bindings.heading("Rooms"); bindings.binding("esc", "abort"); bindings.binding("enter", "connect to room"); - util::list_editor_key_bindings(bindings, Self::room_char); + util2::list_editor_key_bindings(bindings, Self::room_char); } State::Delete(_, _) => { bindings.heading("Rooms"); bindings.binding("esc", "abort"); bindings.binding("enter", "delete room"); - util::list_editor_key_bindings(bindings, Self::room_char); + util2::list_editor_key_bindings(bindings, Self::room_char); } } } @@ -512,7 +540,7 @@ impl Rooms { ) -> bool { self.stabilize_rooms().await; - match &self.state { + match &mut self.state { State::ShowList => { if self.handle_showlist_input_event(event) { return true; @@ -539,7 +567,7 @@ impl Rooms { return true; } key!(Enter) => { - let name = ed.text(); + let name = ed.text().to_string(); if !name.is_empty() { self.connect_to_room(name.clone()); self.state = State::ShowRoom(name); @@ -547,7 +575,7 @@ impl Rooms { return true; } _ => { - if util::handle_editor_input_event(ed, terminal, event, Self::room_char) { + if util2::handle_editor_input_event(ed, terminal, event, Self::room_char) { return true; } } @@ -564,7 +592,7 @@ impl Rooms { return true; } _ => { - if util::handle_editor_input_event(editor, terminal, event, Self::room_char) { + if util2::handle_editor_input_event(editor, terminal, event, Self::room_char) { return true; } } From d8d3e64776acf3be7cafe73f8c23a3a6ad61c8b9 Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 12 Apr 2023 20:17:38 +0200 Subject: [PATCH 068/266] Migrate room to AsyncWidget --- src/ui/euph/room.rs | 152 +++++++++++++++++++++++++------------------- src/ui/rooms.rs | 8 +-- 2 files changed, 90 insertions(+), 70 deletions(-) diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 75d1f11..9f9786d 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -8,22 +8,18 @@ use euphoxide::conn::{self, Joined, Joining, SessionInfo}; use parking_lot::FairMutex; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::{mpsc, oneshot}; -use toss::{Style, Styled, Terminal}; +use toss::widgets::{BoxedAsync, Join2, Layer, Text}; +use toss::{AsyncWidget, Style, Styled, Terminal, WidgetExt}; use crate::config; use crate::euph; use crate::macros::logging_unwrap; use crate::ui::chat::{ChatState, Reaction}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::widgets::border::Border; -use crate::ui::widgets::editor::EditorState; -use crate::ui::widgets::join::{HJoin, Segment, VJoin}; -use crate::ui::widgets::layer::Layer; -use crate::ui::widgets::list::ListState; -use crate::ui::widgets::padding::Padding; -use crate::ui::widgets::text::Text; -use crate::ui::widgets::BoxedWidget; -use crate::ui::{util, UiEvent}; +use crate::ui::widgets::editor::EditorState as OldEditorState; +use crate::ui::widgets::list::ListState as OldListState; +use crate::ui::widgets::WidgetWrapper; +use crate::ui::{util, UiError, UiEvent}; use crate::vault::EuphRoomVault; use super::account::{self, AccountUiState}; @@ -31,7 +27,7 @@ use super::links::{self, LinksState}; use super::popup::RoomPopup; use super::{auth, inspect, nick, nick_list}; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Focus { Chat, NickList, @@ -40,14 +36,16 @@ enum Focus { #[allow(clippy::large_enum_variant)] enum State { Normal, - Auth(EditorState), - Nick(EditorState), + Auth(OldEditorState), + Nick(OldEditorState), Account(AccountUiState), Links(LinksState), InspectMessage(Message), InspectSession(SessionInfo), } +type EuphChatState = ChatState; + pub struct EuphRoom { server_config: ServerConfig, config: config::EuphRoom, @@ -59,10 +57,10 @@ pub struct EuphRoom { state: State, popups: VecDeque, - chat: ChatState, + chat: EuphChatState, last_msg_sent: Option>, - nick_list: ListState, + nick_list: OldListState, } impl EuphRoom { @@ -82,7 +80,7 @@ impl EuphRoom { popups: VecDeque::new(), chat: ChatState::new(vault), last_msg_sent: None, - nick_list: ListState::new(), + nick_list: OldListState::new(), } } @@ -205,77 +203,97 @@ impl EuphRoom { self.stabilize_state(); } - pub async fn widget(&mut self) -> BoxedWidget { + pub async fn widget(&mut self) -> BoxedAsync<'_, UiError> { self.stabilize().await; - let room_state = self.room_state(); + let room_state = self.room.as_ref().map(|room| room.state()); + let status_widget = self.status_widget(room_state).await; let chat = if let Some(euph::State::Connected(_, conn::State::Joined(joined))) = room_state { - self.widget_with_nick_list(room_state, joined).await + Self::widget_with_nick_list( + &mut self.chat, + status_widget, + &mut self.nick_list, + joined, + self.focus, + ) } else { - self.widget_without_nick_list(room_state).await + Self::widget_without_nick_list(&mut self.chat, status_widget) }; let mut layers = vec![chat]; match &self.state { State::Normal => {} - State::Auth(editor) => layers.push(auth::widget(editor)), - State::Nick(editor) => layers.push(nick::widget(editor)), - State::Account(account) => layers.push(account.widget()), - State::Links(links) => layers.push(links.widget()), - State::InspectMessage(message) => layers.push(inspect::message_widget(message)), - State::InspectSession(session) => layers.push(inspect::session_widget(session)), + State::Auth(editor) => { + layers.push(WidgetWrapper::new(auth::widget(editor)).boxed_async()) + } + State::Nick(editor) => { + layers.push(WidgetWrapper::new(nick::widget(editor)).boxed_async()) + } + State::Account(account) => { + layers.push(WidgetWrapper::new(account.widget()).boxed_async()) + } + State::Links(links) => layers.push(WidgetWrapper::new(links.widget()).boxed_async()), + State::InspectMessage(message) => { + layers.push(WidgetWrapper::new(inspect::message_widget(message)).boxed_async()) + } + State::InspectSession(session) => { + layers.push(WidgetWrapper::new(inspect::session_widget(session)).boxed_async()) + } } for popup in &self.popups { - layers.push(popup.widget()); + layers.push(WidgetWrapper::new(popup.widget()).boxed_async()); } - Layer::new(layers).into() + Layer::new(layers).boxed_async() } - async fn widget_without_nick_list(&self, state: Option<&euph::State>) -> BoxedWidget { - VJoin::new(vec![ - Segment::new(Border::new( - Padding::new(self.status_widget(state).await).horizontal(1), - )), - // TODO Use last known nick? - Segment::new(self.chat.widget(String::new(), true)).expanding(true), - ]) - .into() + fn widget_without_nick_list( + chat: &mut EuphChatState, + status_widget: impl AsyncWidget + Send + Sync + 'static, + ) -> BoxedAsync<'_, UiError> { + let chat_widget = WidgetWrapper::new(chat.widget(String::new(), true)); + + Join2::vertical( + status_widget.segment().with_fixed(true), + chat_widget.segment(), + ) + .boxed_async() } - async fn widget_with_nick_list( - &self, - state: Option<&euph::State>, + fn widget_with_nick_list<'a>( + chat: &'a mut EuphChatState, + status_widget: impl AsyncWidget + Send + Sync + 'static, + nick_list: &mut OldListState, joined: &Joined, - ) -> BoxedWidget { - HJoin::new(vec![ - Segment::new(VJoin::new(vec![ - Segment::new(Border::new( - Padding::new(self.status_widget(state).await).horizontal(1), - )), - Segment::new( - self.chat - .widget(joined.session.name.clone(), self.focus == Focus::Chat), - ) - .expanding(true), - ])) - .expanding(true), - Segment::new(Border::new( - Padding::new(nick_list::widget( - &self.nick_list, - joined, - self.focus == Focus::NickList, - )) - .right(1), - )), - ]) - .into() + focus: Focus, + ) -> BoxedAsync<'a, UiError> { + let nick_list_widget = WidgetWrapper::new(nick_list::widget( + nick_list, + joined, + focus == Focus::NickList, + )) + .padding() + .with_right(1) + .border(); + + let chat_widget = + WidgetWrapper::new(chat.widget(joined.session.name.clone(), focus == Focus::Chat)); + + Join2::horizontal( + Join2::vertical( + status_widget.segment().with_fixed(true), + chat_widget.segment(), + ) + .segment(), + nick_list_widget.segment().with_fixed(true), + ) + .boxed_async() } - async fn status_widget(&self, state: Option<&euph::State>) -> BoxedWidget { + async fn status_widget(&self, state: Option<&euph::State>) -> BoxedAsync<'static, UiError> { let room_style = Style::new().bold().blue(); let mut info = Styled::new(format!("&{}", self.name()), room_style); @@ -308,7 +326,11 @@ impl EuphRoom { .then_plain(")"); } - Text::new(info).into() + Text::new(info) + .padding() + .with_horizontal(1) + .border() + .boxed_async() } async fn list_chat_key_bindings(&self, bindings: &mut KeyBindingsList) { diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index 3be809f..348dd5a 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -18,7 +18,6 @@ use crate::vault::Vault; use super::euph::room::EuphRoom; use super::input::{key, InputEvent, KeyBindingsList}; -use super::widgets::WidgetWrapper; use super::widgets2::{List, ListState, Popup}; use super::{util2, UiError, UiEvent}; @@ -176,14 +175,13 @@ impl Rooms { Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order).await } - State::ShowRoom(name) => WidgetWrapper::new( + State::ShowRoom(name) => { self.euph_rooms .get_mut(name) .expect("room exists after stabilization") .widget() - .await, - ) - .boxed_async(), + .await + } State::Connect(editor) => { Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order) From c7cbd9856b42649ab2e9249d70f4c2126dd6f1e1 Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 13 Apr 2023 01:33:08 +0200 Subject: [PATCH 069/266] Migrate nick list to AsyncWidget --- src/ui/euph/nick_list.rs | 79 ++++++++++++++++++++++++---------------- src/ui/euph/room.rs | 32 +++++++--------- 2 files changed, 62 insertions(+), 49 deletions(-) diff --git a/src/ui/euph/nick_list.rs b/src/ui/euph/nick_list.rs index 05ef864..1396822 100644 --- a/src/ui/euph/nick_list.rs +++ b/src/ui/euph/nick_list.rs @@ -4,19 +4,21 @@ use std::iter; use crossterm::style::{Color, Stylize}; use euphoxide::api::{NickEvent, SessionId, SessionType, SessionView, UserId}; use euphoxide::conn::{Joined, SessionInfo}; -use toss::{Style, Styled}; +use toss::widgets::{BoxedAsync, Empty, Text}; +use toss::{Style, Styled, WidgetExt}; use crate::euph; -use crate::ui::widgets::background::Background; -use crate::ui::widgets::empty::Empty; -use crate::ui::widgets::list::{List, ListState}; -use crate::ui::widgets::text::Text; -use crate::ui::widgets::BoxedWidget; +use crate::ui::widgets2::{List, ListState}; +use crate::ui::UiError; -pub fn widget(state: &ListState, joined: &Joined, focused: bool) -> BoxedWidget { - let mut list = state.widget().focus(focused); - render_rows(&mut list, joined); - list.into() +pub fn widget<'a>( + list: &'a mut ListState, + joined: &Joined, + focused: bool, +) -> BoxedAsync<'a, UiError> { + let mut list = list.widget(); + render_rows(&mut list, joined, focused); + list.boxed_async() } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -57,7 +59,11 @@ impl HalfSession { } } -fn render_rows(list: &mut List, joined: &Joined) { +fn render_rows( + list: &mut List<'_, SessionId, BoxedAsync<'static, UiError>>, + joined: &Joined, + focused: bool, +) { let mut people = vec![]; let mut bots = vec![]; let mut lurkers = vec![]; @@ -82,17 +88,18 @@ fn render_rows(list: &mut List, joined: &Joined) { lurkers.sort_unstable(); nurkers.sort_unstable(); - render_section(list, "People", &people, &joined.session); - render_section(list, "Bots", &bots, &joined.session); - render_section(list, "Lurkers", &lurkers, &joined.session); - render_section(list, "Nurkers", &nurkers, &joined.session); + render_section(list, "People", &people, &joined.session, focused); + render_section(list, "Bots", &bots, &joined.session, focused); + render_section(list, "Lurkers", &lurkers, &joined.session, focused); + render_section(list, "Nurkers", &nurkers, &joined.session, focused); } fn render_section( - list: &mut List, + list: &mut List<'_, SessionId, BoxedAsync<'static, UiError>>, name: &str, sessions: &[HalfSession], own_session: &SessionView, + focused: bool, ) { if sessions.is_empty() { return; @@ -101,20 +108,25 @@ fn render_section( let heading_style = Style::new().bold(); if !list.is_empty() { - list.add_unsel(Empty::new()); + list.add_unsel(Empty::new().boxed_async()); } let row = Styled::new_plain(" ") .then(name, heading_style) .then_plain(format!(" ({})", sessions.len())); - list.add_unsel(Text::new(row)); + list.add_unsel(Text::new(row).boxed_async()); for session in sessions { - render_row(list, session, own_session); + render_row(list, session, own_session, focused); } } -fn render_row(list: &mut List, session: &HalfSession, own_session: &SessionView) { +fn render_row( + list: &mut List<'_, SessionId, BoxedAsync<'static, UiError>>, + session: &HalfSession, + own_session: &SessionView, + focused: bool, +) { let (name, style, style_inv, perms_style_inv) = if session.name.is_empty() { let name = "lurk"; let style = Style::new().grey(); @@ -146,15 +158,20 @@ fn render_row(list: &mut List, session: &HalfSession, own_session: &S " " }; - let normal = Styled::new_plain(owner) - .then(&name, style) - .then_plain(perms); - let selected = Styled::new_plain(owner) - .then(name, style_inv) - .then(perms, perms_style_inv); - list.add_sel( - session.session_id.clone(), - Text::new(normal), - Background::new(Text::new(selected)).style(style_inv), - ); + let widget = if focused && list.state().selected() == Some(&session.session_id) { + let text = Styled::new_plain(owner) + .then(name, style_inv) + .then(perms, perms_style_inv); + Text::new(text) + .background() + .with_style(style_inv) + .boxed_async() + } else { + let text = Styled::new_plain(owner) + .then(&name, style) + .then_plain(perms); + Text::new(text).boxed_async() + }; + + list.add_sel(session.session_id.clone(), widget); } diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 9f9786d..f82e51a 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -17,9 +17,9 @@ use crate::macros::logging_unwrap; use crate::ui::chat::{ChatState, Reaction}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::widgets::editor::EditorState as OldEditorState; -use crate::ui::widgets::list::ListState as OldListState; use crate::ui::widgets::WidgetWrapper; -use crate::ui::{util, UiError, UiEvent}; +use crate::ui::widgets2::ListState; +use crate::ui::{util2, UiError, UiEvent}; use crate::vault::EuphRoomVault; use super::account::{self, AccountUiState}; @@ -60,7 +60,7 @@ pub struct EuphRoom { chat: EuphChatState, last_msg_sent: Option>, - nick_list: OldListState, + nick_list: ListState, } impl EuphRoom { @@ -80,7 +80,7 @@ impl EuphRoom { popups: VecDeque::new(), chat: ChatState::new(vault), last_msg_sent: None, - nick_list: OldListState::new(), + nick_list: ListState::new(), } } @@ -266,18 +266,14 @@ impl EuphRoom { fn widget_with_nick_list<'a>( chat: &'a mut EuphChatState, status_widget: impl AsyncWidget + Send + Sync + 'static, - nick_list: &mut OldListState, + nick_list: &'a mut ListState, joined: &Joined, focus: Focus, ) -> BoxedAsync<'a, UiError> { - let nick_list_widget = WidgetWrapper::new(nick_list::widget( - nick_list, - joined, - focus == Focus::NickList, - )) - .padding() - .with_right(1) - .border(); + let nick_list_widget = nick_list::widget(nick_list, joined, focus == Focus::NickList) + .padding() + .with_right(1) + .border(); let chat_widget = WidgetWrapper::new(chat.widget(joined.session.name.clone(), focus == Focus::Chat)); @@ -512,24 +508,24 @@ impl EuphRoom { } fn list_nick_list_focus_key_bindings(&self, bindings: &mut KeyBindingsList) { - util::list_list_key_bindings(bindings); + util2::list_list_key_bindings(bindings); bindings.binding("i", "inspect session"); } fn handle_nick_list_focus_input_event(&mut self, event: &InputEvent) -> bool { - if util::handle_list_input_event(&mut self.nick_list, event) { + if util2::handle_list_input_event(&mut self.nick_list, event) { return true; } if let key!('i') = event { if let Some(euph::State::Connected(_, conn::State::Joined(joined))) = self.room_state() { - if let Some(id) = self.nick_list.cursor() { - if id == joined.session.session_id { + if let Some(id) = self.nick_list.selected() { + if *id == joined.session.session_id { self.state = State::InspectSession(SessionInfo::Full(joined.session.clone())); - } else if let Some(session) = joined.listing.get(&id) { + } else if let Some(session) = joined.listing.get(id) { self.state = State::InspectSession(session.clone()); } } From e358e2184ef204ea8bdd64c0ec2865cbf4a014ad Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 13 Apr 2023 01:43:12 +0200 Subject: [PATCH 070/266] Migrate nick popup to AsyncWidget --- src/ui/euph/nick.rs | 30 +++++++++++++----------------- src/ui/euph/room.rs | 10 ++++------ 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/ui/euph/nick.rs b/src/ui/euph/nick.rs index 18e3d5d..a00e862 100644 --- a/src/ui/euph/nick.rs +++ b/src/ui/euph/nick.rs @@ -1,26 +1,22 @@ use euphoxide::conn::Joined; -use toss::{Style, Terminal}; +use toss::widgets::{BoxedAsync, EditorState}; +use toss::{Style, Terminal, WidgetExt}; use crate::euph::{self, Room}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::util; -use crate::ui::widgets::editor::EditorState; -use crate::ui::widgets::padding::Padding; -use crate::ui::widgets::popup::Popup; -use crate::ui::widgets::BoxedWidget; +use crate::ui::widgets2::Popup; +use crate::ui::{util2, UiError}; pub fn new(joined: Joined) -> EditorState { EditorState::with_initial_text(joined.session.name) } -pub fn widget(editor: &EditorState) -> BoxedWidget { - let editor = editor +pub fn widget(editor: &mut EditorState) -> BoxedAsync<'_, UiError> { + let inner = editor .widget() - .highlight(|s| euph::style_nick_exact(s, Style::new())); - Popup::new(Padding::new(editor).left(1)) - .title("Choose nick") - .inner_padding(false) - .build() + .with_highlight(|s| euph::style_nick_exact(s, Style::new())); + + Popup::new(inner, "Choose nick").boxed_async() } fn nick_char(c: char) -> bool { @@ -30,7 +26,7 @@ fn nick_char(c: char) -> bool { pub fn list_key_bindings(bindings: &mut KeyBindingsList) { bindings.binding("esc", "abort"); bindings.binding("enter", "set nick"); - util::list_editor_key_bindings(bindings, nick_char); + util2::list_editor_key_bindings(bindings, nick_char); } pub enum EventResult { @@ -43,18 +39,18 @@ pub fn handle_input_event( terminal: &mut Terminal, event: &InputEvent, room: &Option, - editor: &EditorState, + editor: &mut EditorState, ) -> EventResult { match event { key!(Esc) => EventResult::ResetState, key!(Enter) => { if let Some(room) = &room { - let _ = room.nick(editor.text()); + let _ = room.nick(editor.text().to_string()); } EventResult::ResetState } _ => { - if util::handle_editor_input_event(editor, terminal, event, nick_char) { + if util2::handle_editor_input_event(editor, terminal, event, nick_char) { EventResult::Handled } else { EventResult::NotHandled diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index f82e51a..71f28f5 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -8,7 +8,7 @@ use euphoxide::conn::{self, Joined, Joining, SessionInfo}; use parking_lot::FairMutex; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::{mpsc, oneshot}; -use toss::widgets::{BoxedAsync, Join2, Layer, Text}; +use toss::widgets::{BoxedAsync, EditorState, Join2, Layer, Text}; use toss::{AsyncWidget, Style, Styled, Terminal, WidgetExt}; use crate::config; @@ -37,7 +37,7 @@ enum Focus { enum State { Normal, Auth(OldEditorState), - Nick(OldEditorState), + Nick(EditorState), Account(AccountUiState), Links(LinksState), InspectMessage(Message), @@ -223,14 +223,12 @@ impl EuphRoom { let mut layers = vec![chat]; - match &self.state { + match &mut self.state { State::Normal => {} State::Auth(editor) => { layers.push(WidgetWrapper::new(auth::widget(editor)).boxed_async()) } - State::Nick(editor) => { - layers.push(WidgetWrapper::new(nick::widget(editor)).boxed_async()) - } + State::Nick(editor) => layers.push(nick::widget(editor)), State::Account(account) => { layers.push(WidgetWrapper::new(account.widget()).boxed_async()) } From 03766802fde92603907b4a87a190ed219ba16299 Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 13 Apr 2023 01:51:52 +0200 Subject: [PATCH 071/266] Migrate auth popup to AsyncWidget --- src/ui/euph/auth.rs | 27 ++++++++++++++------------- src/ui/euph/room.rs | 7 ++----- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/ui/euph/auth.rs b/src/ui/euph/auth.rs index 682d967..f13cbc4 100644 --- a/src/ui/euph/auth.rs +++ b/src/ui/euph/auth.rs @@ -1,26 +1,27 @@ -use toss::Terminal; +use toss::widgets::{BoxedAsync, EditorState}; +use toss::{Terminal, WidgetExt}; use crate::euph::Room; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::util; -use crate::ui::widgets::editor::EditorState; -use crate::ui::widgets::popup::Popup; -use crate::ui::widgets::BoxedWidget; +use crate::ui::widgets2::Popup; +use crate::ui::{util2, UiError}; pub fn new() -> EditorState { EditorState::new() } -pub fn widget(editor: &EditorState) -> BoxedWidget { - Popup::new(editor.widget().hidden()) - .title("Enter password") - .build() +pub fn widget(editor: &mut EditorState) -> BoxedAsync<'_, UiError> { + Popup::new( + editor.widget().with_hidden_default_placeholder(), + "Enter password", + ) + .boxed_async() } pub fn list_key_bindings(bindings: &mut KeyBindingsList) { bindings.binding("esc", "abort"); bindings.binding("enter", "authenticate"); - util::list_editor_key_bindings(bindings, |_| true); + util2::list_editor_key_bindings(bindings, |_| true); } pub enum EventResult { @@ -33,18 +34,18 @@ pub fn handle_input_event( terminal: &mut Terminal, event: &InputEvent, room: &Option, - editor: &EditorState, + editor: &mut EditorState, ) -> EventResult { match event { key!(Esc) => EventResult::ResetState, key!(Enter) => { if let Some(room) = &room { - let _ = room.auth(editor.text()); + let _ = room.auth(editor.text().to_string()); } EventResult::ResetState } _ => { - if util::handle_editor_input_event(editor, terminal, event, |_| true) { + if util2::handle_editor_input_event(editor, terminal, event, |_| true) { EventResult::Handled } else { EventResult::NotHandled diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 71f28f5..12c2f0c 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -16,7 +16,6 @@ use crate::euph; use crate::macros::logging_unwrap; use crate::ui::chat::{ChatState, Reaction}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::widgets::editor::EditorState as OldEditorState; use crate::ui::widgets::WidgetWrapper; use crate::ui::widgets2::ListState; use crate::ui::{util2, UiError, UiEvent}; @@ -36,7 +35,7 @@ enum Focus { #[allow(clippy::large_enum_variant)] enum State { Normal, - Auth(OldEditorState), + Auth(EditorState), Nick(EditorState), Account(AccountUiState), Links(LinksState), @@ -225,9 +224,7 @@ impl EuphRoom { match &mut self.state { State::Normal => {} - State::Auth(editor) => { - layers.push(WidgetWrapper::new(auth::widget(editor)).boxed_async()) - } + State::Auth(editor) => layers.push(auth::widget(editor)), State::Nick(editor) => layers.push(nick::widget(editor)), State::Account(account) => { layers.push(WidgetWrapper::new(account.widget()).boxed_async()) From 91d8d7ba975b71682e36d3c094d45c906cfb46de Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 13 Apr 2023 02:08:10 +0200 Subject: [PATCH 072/266] Migrate account popup to AsyncWidget --- src/ui/euph/account.rs | 113 ++++++++++++++++++++++------------------- src/ui/euph/room.rs | 4 +- 2 files changed, 61 insertions(+), 56 deletions(-) diff --git a/src/ui/euph/account.rs b/src/ui/euph/account.rs index c303375..0937ba2 100644 --- a/src/ui/euph/account.rs +++ b/src/ui/euph/account.rs @@ -1,18 +1,13 @@ use crossterm::style::Stylize; use euphoxide::api::PersonalAccountView; use euphoxide::conn; -use toss::{Style, Terminal}; +use toss::widgets::{BoxedAsync, EditorState, Empty, Join3, Join4, Text}; +use toss::{Style, Terminal, WidgetExt}; use crate::euph::{self, Room}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::util; -use crate::ui::widgets::editor::EditorState; -use crate::ui::widgets::empty::Empty; -use crate::ui::widgets::join::{HJoin, Segment, VJoin}; -use crate::ui::widgets::popup::Popup; -use crate::ui::widgets::resize::Resize; -use crate::ui::widgets::text::Text; -use crate::ui::widgets::BoxedWidget; +use crate::ui::widgets2::Popup; +use crate::ui::{util2, UiError}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Focus { @@ -35,46 +30,55 @@ impl LoggedOut { } } - fn widget(&self) -> BoxedWidget { + fn widget(&mut self) -> BoxedAsync<'_, UiError> { let bold = Style::new().bold(); - VJoin::new(vec![ - Segment::new(Text::new(("Not logged in", bold.yellow()))), - Segment::new(Empty::new().height(1)), - Segment::new(HJoin::new(vec![ - Segment::new(Text::new(("Email address:", bold))), - Segment::new(Empty::new().width(1)), - Segment::new(self.email.widget().focus(self.focus == Focus::Email)), - ])), - Segment::new(HJoin::new(vec![ - Segment::new(Text::new(("Password:", bold))), - Segment::new(Empty::new().width(5 + 1)), - Segment::new( - self.password - .widget() - .focus(self.focus == Focus::Password) - .hidden(), - ), - ])), - ]) - .into() + Join4::vertical( + Text::new(("Not logged in", bold.yellow())).segment(), + Empty::new().with_height(1).segment(), + Join3::horizontal( + Text::new(("Email address:", bold)) + .segment() + .with_fixed(true), + Empty::new().with_width(1).segment().with_fixed(true), + self.email + .widget() + .with_focus(self.focus == Focus::Email) + .segment(), + ) + .segment(), + Join3::horizontal( + Text::new(("Password:", bold)).segment().with_fixed(true), + Empty::new().with_width(5 + 1).segment().with_fixed(true), + self.password + .widget() + .with_focus(self.focus == Focus::Password) + .with_hidden_default_placeholder() + .segment(), + ) + .segment(), + ) + .boxed_async() } } pub struct LoggedIn(PersonalAccountView); impl LoggedIn { - fn widget(&self) -> BoxedWidget { + fn widget(&self) -> BoxedAsync<'_, UiError> { let bold = Style::new().bold(); - VJoin::new(vec![ - Segment::new(Text::new(("Logged in", bold.green()))), - Segment::new(Empty::new().height(1)), - Segment::new(HJoin::new(vec![ - Segment::new(Text::new(("Email address:", bold))), - Segment::new(Empty::new().width(1)), - Segment::new(Text::new((&self.0.email,))), - ])), - ]) - .into() + Join3::vertical( + Text::new(("Logged in", bold.green())).segment(), + Empty::new().with_height(1).segment(), + Join3::horizontal( + Text::new(("Email address:", bold)) + .segment() + .with_fixed(true), + Empty::new().with_width(1).segment().with_fixed(true), + Text::new((&self.0.email,)).segment(), + ) + .segment(), + ) + .boxed_async() } } @@ -108,14 +112,15 @@ impl AccountUiState { } } - pub fn widget(&self) -> BoxedWidget { + pub fn widget(&mut self) -> BoxedAsync<'_, UiError> { let inner = match self { Self::LoggedOut(logged_out) => logged_out.widget(), Self::LoggedIn(logged_in) => logged_in.widget(), - }; - Popup::new(Resize::new(inner).min_width(40)) - .title("Account") - .build() + } + .resize() + .with_min_width(40); + + Popup::new(inner, "Account").boxed_async() } pub fn list_key_bindings(&self, bindings: &mut KeyBindingsList) { @@ -128,7 +133,7 @@ impl AccountUiState { Focus::Password => bindings.binding("enter", "log in"), } bindings.binding("tab", "switch focus"); - util::list_editor_key_bindings(bindings, |c| c != '\n'); + util2::list_editor_key_bindings(bindings, |c| c != '\n'); } Self::LoggedIn(_) => bindings.binding("L", "log out"), } @@ -161,8 +166,8 @@ impl AccountUiState { return EventResult::Handled; } - if util::handle_editor_input_event( - &logged_out.email, + if util2::handle_editor_input_event( + &mut logged_out.email, terminal, event, |c| c != '\n', @@ -175,14 +180,16 @@ impl AccountUiState { Focus::Password => { if let key!(Enter) = event { if let Some(room) = room { - let _ = - room.login(logged_out.email.text(), logged_out.password.text()); + let _ = room.login( + logged_out.email.text().to_string(), + logged_out.password.text().to_string(), + ); } return EventResult::Handled; } - if util::handle_editor_input_event( - &logged_out.password, + if util2::handle_editor_input_event( + &mut logged_out.password, terminal, event, |c| c != '\n', diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 12c2f0c..d976578 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -226,9 +226,7 @@ impl EuphRoom { State::Normal => {} State::Auth(editor) => layers.push(auth::widget(editor)), State::Nick(editor) => layers.push(nick::widget(editor)), - State::Account(account) => { - layers.push(WidgetWrapper::new(account.widget()).boxed_async()) - } + State::Account(account) => layers.push(account.widget()), State::Links(links) => layers.push(WidgetWrapper::new(links.widget()).boxed_async()), State::InspectMessage(message) => { layers.push(WidgetWrapper::new(inspect::message_widget(message)).boxed_async()) From d7d25a83902d3ebb675a456bc53e6ae81374f409 Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 13 Apr 2023 13:31:21 +0200 Subject: [PATCH 073/266] Migrate inspection popups to AsyncWidget --- src/ui/euph/inspect.rs | 16 ++++++++-------- src/ui/euph/room.rs | 8 ++------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/ui/euph/inspect.rs b/src/ui/euph/inspect.rs index 2d15c15..505062c 100644 --- a/src/ui/euph/inspect.rs +++ b/src/ui/euph/inspect.rs @@ -1,12 +1,12 @@ use crossterm::style::Stylize; use euphoxide::api::{Message, NickEvent, SessionView}; use euphoxide::conn::SessionInfo; -use toss::{Style, Styled}; +use toss::widgets::{BoxedAsync, Text}; +use toss::{Style, Styled, WidgetExt}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::widgets::popup::Popup; -use crate::ui::widgets::text::Text; -use crate::ui::widgets::BoxedWidget; +use crate::ui::widgets2::Popup; +use crate::ui::UiError; macro_rules! line { ( $text:ident, $name:expr, $val:expr ) => { @@ -88,7 +88,7 @@ fn message_lines(mut text: Styled, msg: &Message) -> Styled { text } -pub fn session_widget(session: &SessionInfo) -> BoxedWidget { +pub fn session_widget(session: &SessionInfo) -> BoxedAsync<'static, UiError> { let heading_style = Style::new().bold(); let text = match session { @@ -102,10 +102,10 @@ pub fn session_widget(session: &SessionInfo) -> BoxedWidget { } }; - Popup::new(Text::new(text)).title("Inspect session").build() + Popup::new(Text::new(text), "Inspect session").boxed_async() } -pub fn message_widget(msg: &Message) -> BoxedWidget { +pub fn message_widget(msg: &Message) -> BoxedAsync<'static, UiError> { let heading_style = Style::new().bold(); let mut text = Styled::new("Message", heading_style).then_plain("\n"); @@ -119,7 +119,7 @@ pub fn message_widget(msg: &Message) -> BoxedWidget { text = session_view_lines(text, &msg.sender); - Popup::new(Text::new(text)).title("Inspect message").build() + Popup::new(Text::new(text), "Inspect message").boxed_async() } pub fn list_key_bindings(bindings: &mut KeyBindingsList) { diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index d976578..b2c575f 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -228,12 +228,8 @@ impl EuphRoom { State::Nick(editor) => layers.push(nick::widget(editor)), State::Account(account) => layers.push(account.widget()), State::Links(links) => layers.push(WidgetWrapper::new(links.widget()).boxed_async()), - State::InspectMessage(message) => { - layers.push(WidgetWrapper::new(inspect::message_widget(message)).boxed_async()) - } - State::InspectSession(session) => { - layers.push(WidgetWrapper::new(inspect::session_widget(session)).boxed_async()) - } + State::InspectMessage(message) => layers.push(inspect::message_widget(message)), + State::InspectSession(session) => layers.push(inspect::session_widget(session)), } for popup in &self.popups { From bb4d0fe047a593982eeb98d85d63528f42e9865f Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 13 Apr 2023 13:46:06 +0200 Subject: [PATCH 074/266] Add blocks as basis for rendering --- src/ui.rs | 1 + src/ui/chat2.rs | 1 + src/ui/chat2/blocks.rs | 223 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 src/ui/chat2.rs create mode 100644 src/ui/chat2/blocks.rs diff --git a/src/ui.rs b/src/ui.rs index c0acdd8..14ad1fc 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,4 +1,5 @@ mod chat; +mod chat2; mod euph; mod input; mod rooms; diff --git a/src/ui/chat2.rs b/src/ui/chat2.rs new file mode 100644 index 0000000..25a94de --- /dev/null +++ b/src/ui/chat2.rs @@ -0,0 +1 @@ +mod blocks; diff --git a/src/ui/chat2/blocks.rs b/src/ui/chat2/blocks.rs new file mode 100644 index 0000000..0705cf8 --- /dev/null +++ b/src/ui/chat2/blocks.rs @@ -0,0 +1,223 @@ +//! Common rendering logic. + +use std::collections::{vec_deque, VecDeque}; + +use toss::widgets::Predrawn; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Range { + pub top: T, + pub bottom: T, +} + +impl Range { + pub fn new(top: T, bottom: T) -> Self { + Self { top, bottom } + } +} + +impl Range { + pub fn shifted(self, delta: i32) -> Self { + Self::new(self.top + delta, self.bottom + delta) + } + + pub fn with_top(self, top: i32) -> Self { + self.shifted(top - self.top) + } + + pub fn with_bottom(self, bottom: i32) -> Self { + self.shifted(bottom - self.bottom) + } +} + +pub struct Block { + id: Id, + widget: Predrawn, + focus: Range, + can_be_cursor: bool, +} + +impl Block { + pub fn new(id: Id, widget: Predrawn, can_be_cursor: bool) -> Self { + let height: i32 = widget.size().height.into(); + Self { + id, + widget, + focus: Range::new(0, height), + can_be_cursor, + } + } + + pub fn id(&self) -> &Id { + &self.id + } + + pub fn into_widget(self) -> Predrawn { + self.widget + } + + fn height(&self) -> i32 { + self.widget.size().height.into() + } + + pub fn set_focus(&mut self, focus: Range) { + assert!(0 <= focus.top); + assert!(focus.top <= focus.bottom); + assert!(focus.bottom <= self.height()); + self.focus = focus; + } + + pub fn focus(&self, range: Range) -> Range { + Range::new(range.top + self.focus.top, range.top + self.focus.bottom) + } + + pub fn can_be_cursor(&self) -> bool { + self.can_be_cursor + } +} + +pub struct Blocks { + blocks: VecDeque>, + range: Range, + end: Range, +} + +impl Blocks { + pub fn new(at: i32) -> Self { + Self { + blocks: VecDeque::new(), + range: Range::new(at, at), + end: Range::new(false, false), + } + } + + pub fn range(&self) -> Range { + self.range + } + + pub fn end(&self) -> Range { + self.end + } + + pub fn iter(&self) -> Iter<'_, Id> { + Iter { + iter: self.blocks.iter(), + range: self.range, + } + } + + pub fn into_iter(self) -> IntoIter { + IntoIter { + iter: self.blocks.into_iter(), + range: self.range, + } + } + + // TODO Remove index from search result + pub fn find_block(&self, id: &Id) -> Option<(Range, &Block)> + where + Id: Eq, + { + self.iter().find(|(_, block)| block.id == *id) + } + + pub fn push_top(&mut self, block: Block) { + assert!(!self.end.top); + self.range.top -= block.height(); + self.blocks.push_front(block); + } + + pub fn push_bottom(&mut self, block: Block) { + assert!(!self.end.bottom); + self.range.bottom += block.height(); + self.blocks.push_back(block); + } + + pub fn append_top(&mut self, other: Self) { + assert!(!self.end.top); + assert!(!other.end.bottom); + for block in other.blocks.into_iter().rev() { + self.push_top(block); + } + self.end.top = other.end.top; + } + + pub fn append_bottom(&mut self, other: Self) { + assert!(!self.end.bottom); + assert!(!other.end.top); + for block in other.blocks { + self.push_bottom(block); + } + self.end.bottom = other.end.bottom; + } + + pub fn end_top(&mut self) { + self.end.top = true; + } + + pub fn end_bottom(&mut self) { + self.end.bottom = true; + } + + pub fn shift(&mut self, delta: i32) { + self.range = self.range.shifted(delta); + } + + pub fn set_top(&mut self, top: i32) { + self.shift(top - self.range.top); + } + + pub fn set_bottom(&mut self, bottom: i32) { + self.shift(bottom - self.range.bottom); + } +} + +pub struct Iter<'a, Id> { + iter: vec_deque::Iter<'a, Block>, + range: Range, +} + +impl<'a, Id> Iterator for Iter<'a, Id> { + type Item = (Range, &'a Block); + + fn next(&mut self) -> Option { + let block = self.iter.next()?; + let range = Range::new(self.range.top, self.range.top + block.height()); + self.range.top = range.bottom; + Some((range, block)) + } +} + +impl DoubleEndedIterator for Iter<'_, Id> { + fn next_back(&mut self) -> Option { + let block = self.iter.next_back()?; + let range = Range::new(self.range.bottom - block.height(), self.range.bottom); + self.range.bottom = range.top; + Some((range, block)) + } +} + +pub struct IntoIter { + iter: vec_deque::IntoIter>, + range: Range, +} + +impl Iterator for IntoIter { + type Item = (Range, Block); + + fn next(&mut self) -> Option { + let block = self.iter.next()?; + let range = Range::new(self.range.top, self.range.top + block.height()); + self.range.top = range.bottom; + Some((range, block)) + } +} + +impl DoubleEndedIterator for IntoIter { + fn next_back(&mut self) -> Option { + let block = self.iter.next_back()?; + let range = Range::new(self.range.bottom - block.height(), self.range.bottom); + self.range.bottom = range.top; + Some((range, block)) + } +} From a18ee8e7c00afec5ebdfe985cfffd37c9337a676 Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 13 Apr 2023 13:46:21 +0200 Subject: [PATCH 075/266] Implement common renderer and scrolling logic --- src/ui/chat2.rs | 1 + src/ui/chat2/renderer.rs | 348 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 349 insertions(+) create mode 100644 src/ui/chat2/renderer.rs diff --git a/src/ui/chat2.rs b/src/ui/chat2.rs index 25a94de..2d34c51 100644 --- a/src/ui/chat2.rs +++ b/src/ui/chat2.rs @@ -1 +1,2 @@ mod blocks; +mod renderer; diff --git a/src/ui/chat2/renderer.rs b/src/ui/chat2/renderer.rs new file mode 100644 index 0000000..7318f43 --- /dev/null +++ b/src/ui/chat2/renderer.rs @@ -0,0 +1,348 @@ +use std::cmp::Ordering; + +use async_trait::async_trait; +use toss::Size; + +use super::blocks::{Blocks, Range}; + +#[async_trait] +pub trait Renderer { + type Error; + + fn size(&self) -> Size; + fn scrolloff(&self) -> i32; + + fn blocks(&self) -> &Blocks; + fn blocks_mut(&mut self) -> &mut Blocks; + fn into_blocks(self) -> Blocks; + + async fn expand_top(&mut self) -> Result<(), Self::Error>; + async fn expand_bottom(&mut self) -> Result<(), Self::Error>; +} + +/// A range of all the lines that are visible given the renderer's size. +pub fn visible_area(r: &R) -> Range +where + R: Renderer, +{ + let height: i32 = r.size().height.into(); + Range::new(0, height) +} + +/// The renderer's visible area, reduced by its scrolloff at the top and bottom. +fn scroll_area(r: &R) -> Range +where + R: Renderer, +{ + let range = visible_area(r); + let scrolloff = r.scrolloff(); + let top = range.top + scrolloff; + let bottom = top.max(range.bottom - scrolloff); + Range::new(top, bottom) +} + +/// Compute a delta that makes the object partially or fully overlap the area +/// when added to the object. This delta should be as close to zero as possible. +/// +/// If the object has a height of zero, it must be within the area or exactly on +/// its border to be considered overlapping. +/// +/// If the object has a nonzero height, at least one line of the object must be +/// within the area for the object to be considered overlapping. +fn overlap_delta(area: Range, object: Range) -> i32 { + assert!(object.top <= object.bottom, "object range not well-formed"); + assert!(area.top <= area.bottom, "area range not well-formed"); + + if object.top == object.bottom || area.top == area.bottom { + // Delta that moves the object.bottom to area.top. If this is positive, + // we need to move the object because it is too high. + let move_to_top = area.top - object.bottom; + + // Delta that moves the object.top to area.bottom. If this is negative, + // we need to move the object because it is too low. + let move_to_bottom = area.bottom - object.top; + + // move_to_top <= move_to_bottom because... + // + // Case 1: object.top == object.bottom + // Premise follows from rom area.top <= area.bottom + // + // Case 2: area.top == area.bottom + // Premise follows from object.top <= object.bottom + 0.clamp(move_to_top, move_to_bottom) + } else { + // Delta that moves object.bottom one line below area.top. If this is + // positive, we need to move the object because it is too high. + let move_to_top = (area.top + 1) - object.bottom; + + // Delta that moves object.top one line above area.bottom. If this is + // negative, we need to move the object because it is too low. + let move_to_bottom = (area.bottom - 1) - object.top; + + // move_to_top <= move_to_bottom because... + // + // We know that area.top < area.bottom and object.top < object.bottom, + // otherwise we'd be in the previous `if` branch. + // + // We get the largest value for move_to_top if area.top is largest and + // object.bottom is smallest. We get the smallest value for + // move_to_bottom if area.bottom is smallest and object.top is largest. + // + // This means that the worst case scenario is when area.top and + // area.bottom as well as object.top and object.bottom are closest + // together. In other words: + // + // area.top + 1 == area.bottom + // object.top + 1 == object.bottom + // + // Inserting that into our formulas for move_to_top and move_to_bottom, + // we get: + // + // move_to_top = (area.top + 1) - (object.top + 1) = area.top + object.top + // move_to_bottom = (area.top + 1 - 1) - object.top = area.top + object.top + 0.clamp(move_to_top, move_to_bottom) + } +} + +pub fn overlaps(area: Range, object: Range) -> bool { + overlap_delta(area, object) == 0 +} + +/// Move the object such that it overlaps the area. +fn overlap(area: Range, object: Range) -> Range { + object.shifted(overlap_delta(area, object)) +} + +/// Compute a delta that makes the object fully overlap the area when added to +/// the object. This delta should be as close to zero as possible. +/// +/// If the object is higher than the area, it should be moved such that +/// object.top == area.top. +fn full_overlap_delta(area: Range, object: Range) -> i32 { + assert!(object.top <= object.bottom, "object range not well-formed"); + assert!(area.top <= area.bottom, "area range not well-formed"); + + // Delta that moves object.top to area.top. If this is positive, we need to + // move the object because it is too high. + let move_to_top = area.top - object.top; + + // Delta that moves object.bottom to area.bottom. If this is negative, we + // need to move the object because it is too low. + let move_to_bottom = area.bottom - object.bottom; + + // If the object is higher than the area, move_to_top becomes larger than + // move_to_bottom. In that case, this function should return move_to_top. + 0.min(move_to_bottom).max(move_to_top) +} + +async fn expand_upwards_until(r: &mut R, top: i32) -> Result<(), R::Error> +where + R: Renderer, +{ + loop { + let blocks = r.blocks(); + if blocks.end().top || blocks.range().top <= top { + break; + } + + r.expand_top().await?; + } + + Ok(()) +} + +async fn expand_downwards_until(r: &mut R, bottom: i32) -> Result<(), R::Error> +where + R: Renderer, +{ + loop { + let blocks = r.blocks(); + if blocks.end().bottom || blocks.range().bottom >= bottom { + break; + } + + r.expand_bottom().await?; + } + + Ok(()) +} + +pub async fn expand_to_fill_visible_area(r: &mut R) -> Result<(), R::Error> +where + R: Renderer, +{ + let area = visible_area(r); + expand_upwards_until(r, area.top).await?; + expand_downwards_until(r, area.bottom).await?; + Ok(()) +} + +/// Expand blocks such that the screen is full for any offset where the +/// specified block is visible. +pub async fn expand_to_fill_screen_around_block(r: &mut R, id: &Id) -> Result<(), R::Error> +where + Id: Eq, + R: Renderer, +{ + let screen = visible_area(r); + let (block, _) = r.blocks().find_block(id).expect("no block with that id"); + + let top = overlap(block, screen.with_bottom(block.top)).top; + let bottom = overlap(block, screen.with_top(block.bottom)).bottom; + + expand_upwards_until(r, top).await?; + expand_downwards_until(r, bottom).await?; + + Ok(()) +} + +pub fn scroll_to_set_block_top(r: &mut R, id: &Id, top: i32) -> bool +where + Id: Eq, + R: Renderer, +{ + if let Some((range, _)) = r.blocks().find_block(id) { + let delta = top - range.top; + r.blocks_mut().shift(delta); + true + } else { + false + } +} + +pub fn scroll_so_block_is_centered(r: &mut R, id: &Id) +where + Id: Eq, + R: Renderer, +{ + let area = visible_area(r); + let (range, block) = r.blocks().find_block(id).expect("no block with that id"); + let focus = block.focus(range); + let focus_height = focus.bottom - focus.top; + let top = (area.top + area.bottom - focus_height) / 2; + r.blocks_mut().shift(top - range.top); +} + +pub fn scroll_blocks_fully_above_screen(r: &mut R) +where + R: Renderer, +{ + let area = visible_area(r); + let blocks = r.blocks_mut(); + let delta = area.top - blocks.range().bottom; + blocks.shift(delta); +} + +pub fn scroll_blocks_fully_below_screen(r: &mut R) +where + R: Renderer, +{ + let area = visible_area(r); + let blocks = r.blocks_mut(); + let delta = area.bottom - blocks.range().top; + blocks.shift(delta); +} + +pub fn scroll_so_block_focus_overlaps_scroll_area(r: &mut R, id: &Id) -> bool +where + Id: Eq, + R: Renderer, +{ + if let Some((range, block)) = r.blocks().find_block(id) { + let area = scroll_area(r); + let delta = overlap_delta(area, block.focus(range)); + r.blocks_mut().shift(delta); + true + } else { + false + } +} + +pub fn scroll_so_block_focus_fully_overlaps_scroll_area(r: &mut R, id: &Id) -> bool +where + Id: Eq, + R: Renderer, +{ + if let Some((range, block)) = r.blocks().find_block(id) { + let area = scroll_area(r); + let delta = full_overlap_delta(area, block.focus(range)); + r.blocks_mut().shift(delta); + true + } else { + false + } +} + +pub fn clamp_scroll_biased_upwards(r: &mut R) +where + R: Renderer, +{ + let area = visible_area(r); + let blocks = r.blocks().range(); + + // Delta that moves blocks.top to the top of the screen. If this is + // negative, we need to move the blocks because they're too low. + let move_to_top = blocks.top - area.top; + + // Delta that moves blocks.bottom to the bottom of the screen. If this is + // positive, we need to move the blocks because they're too high. + let move_to_bottom = blocks.bottom - area.bottom; + + // If the screen is higher, the blocks should rather be moved to the top + // than the bottom because of the upwards bias. + let delta = 0.max(move_to_bottom).min(move_to_top); + r.blocks_mut().shift(delta); +} + +pub fn clamp_scroll_biased_downwards(r: &mut R) +where + R: Renderer, +{ + let area = visible_area(r); + let blocks = r.blocks().range(); + + // Delta that moves blocks.top to the top of the screen. If this is + // negative, we need to move the blocks because they're too low. + let move_to_top = area.top - blocks.top; + + // Delta that moves blocks.bottom to the bottom of the screen. If this is + // positive, we need to move the blocks because they're too high. + let move_to_bottom = area.bottom - blocks.bottom; + + // If the screen is higher, the blocks should rather be moved to the bottom + // than the top because of the downwards bias. + let delta = 0.min(move_to_top).max(move_to_bottom); + r.blocks_mut().shift(delta); +} + +pub fn find_cursor_starting_at<'a, Id, R>(r: &'a R, id: &Id) -> Option<&'a Id> +where + Id: Eq, + R: Renderer, +{ + let area = scroll_area(r); + let (range, block) = r.blocks().find_block(id)?; + let delta = overlap_delta(area, block.focus(range)); + match delta.cmp(&0) { + Ordering::Equal => Some(block.id()), + + // Blocks must be scrolled downwards to become visible, meaning the + // cursor must be above the visible area. + Ordering::Greater => r + .blocks() + .iter() + .filter(|(_, block)| block.can_be_cursor()) + .find(|(range, block)| overlaps(area, block.focus(*range))) + .map(|(_, block)| block.id()), + + // Blocks must be scrolled upwards to become visible, meaning the cursor + // must be below the visible area. + Ordering::Less => r + .blocks() + .iter() + .rev() + .filter(|(_, block)| block.can_be_cursor()) + .find(|(range, block)| overlaps(area, block.focus(*range))) + .map(|(_, block)| block.id()), + } +} From 95068920f14caa3acefecb06e1e292c33436e1aa Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 13 Apr 2023 13:50:41 +0200 Subject: [PATCH 076/266] Implement common cursor movement logic --- src/ui/chat2.rs | 1 + src/ui/chat2/cursor.rs | 528 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 529 insertions(+) create mode 100644 src/ui/chat2/cursor.rs diff --git a/src/ui/chat2.rs b/src/ui/chat2.rs index 2d34c51..8b7b9ca 100644 --- a/src/ui/chat2.rs +++ b/src/ui/chat2.rs @@ -1,2 +1,3 @@ mod blocks; +mod cursor; mod renderer; diff --git a/src/ui/chat2/cursor.rs b/src/ui/chat2/cursor.rs new file mode 100644 index 0000000..561f4ed --- /dev/null +++ b/src/ui/chat2/cursor.rs @@ -0,0 +1,528 @@ +//! Common cursor movement logic. + +use std::collections::HashSet; +use std::hash::Hash; + +use crate::store::{Msg, MsgStore, Tree}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Cursor { + Bottom, + Msg(Id), + Editor { + coming_from: Option, + parent: Option, + }, + Pseudo { + coming_from: Option, + parent: Option, + }, +} + +impl Cursor { + fn find_parent(tree: &Tree, id: &mut Id) -> bool + where + M: Msg, + { + if let Some(parent) = tree.parent(id) { + *id = parent; + true + } else { + false + } + } + + /// Move to the previous sibling, or don't move if this is not possible. + /// + /// Always stays at the same level of indentation. + async fn find_prev_sibling( + store: &S, + tree: &mut Tree, + id: &mut Id, + ) -> Result + where + M: Msg, + S: MsgStore, + { + let moved = if let Some(prev_sibling) = tree.prev_sibling(id) { + *id = prev_sibling; + true + } else if tree.parent(id).is_none() { + // We're at the root of our tree, so we need to move to the root of + // the previous tree. + if let Some(prev_root_id) = store.prev_root_id(tree.root()).await? { + *tree = store.tree(&prev_root_id).await?; + *id = prev_root_id; + true + } else { + false + } + } else { + false + }; + Ok(moved) + } + + /// Move to the next sibling, or don't move if this is not possible. + /// + /// Always stays at the same level of indentation. + async fn find_next_sibling( + store: &S, + tree: &mut Tree, + id: &mut Id, + ) -> Result + where + M: Msg, + S: MsgStore, + { + let moved = if let Some(next_sibling) = tree.next_sibling(id) { + *id = next_sibling; + true + } else if tree.parent(id).is_none() { + // We're at the root of our tree, so we need to move to the root of + // the next tree. + if let Some(next_root_id) = store.next_root_id(tree.root()).await? { + *tree = store.tree(&next_root_id).await?; + *id = next_root_id; + true + } else { + false + } + } else { + false + }; + Ok(moved) + } + + fn find_first_child_in_tree(folded: &HashSet, tree: &Tree, id: &mut Id) -> bool + where + M: Msg, + { + if folded.contains(id) { + return false; + } + + if let Some(child) = tree.children(id).and_then(|c| c.first()) { + *id = child.clone(); + true + } else { + false + } + } + + fn find_last_child_in_tree(folded: &HashSet, tree: &Tree, id: &mut Id) -> bool + where + M: Msg, + { + if folded.contains(id) { + return false; + } + + if let Some(child) = tree.children(id).and_then(|c| c.last()) { + *id = child.clone(); + true + } else { + false + } + } + + /// Move to the message above, or don't move if this is not possible. + async fn find_above_msg_in_tree( + store: &S, + folded: &HashSet, + tree: &mut Tree, + id: &mut Id, + ) -> Result + where + M: Msg, + S: MsgStore, + { + // Move to previous sibling, then to its last child + // If not possible, move to parent + let moved = if Self::find_prev_sibling(store, tree, id).await? { + while Self::find_last_child_in_tree(folded, tree, id) {} + true + } else { + Self::find_parent(tree, id) + }; + Ok(moved) + } + + /// Move to the next message, or don't move if this is not possible. + async fn find_below_msg_in_tree( + store: &S, + folded: &HashSet, + tree: &mut Tree, + id: &mut Id, + ) -> Result + where + M: Msg, + S: MsgStore, + { + if Self::find_first_child_in_tree(folded, tree, id) { + return Ok(true); + } + + if Self::find_next_sibling(store, tree, id).await? { + return Ok(true); + } + + // Temporary id to avoid modifying the original one if no parent-sibling + // can be found. + let mut tmp_id = id.clone(); + while Self::find_parent(tree, &mut tmp_id) { + if Self::find_next_sibling(store, tree, &mut tmp_id).await? { + *id = tmp_id; + return Ok(true); + } + } + + Ok(false) + } + + pub async fn move_to_top(&mut self, store: &S) -> Result<(), S::Error> + where + M: Msg, + S: MsgStore, + { + if let Some(first_root_id) = store.first_root_id().await? { + *self = Self::Msg(first_root_id); + } + Ok(()) + } + + pub fn move_to_bottom(&mut self) { + *self = Self::Bottom; + } + + pub async fn move_to_older_msg(&mut self, store: &S) -> Result<(), S::Error> + where + M: Msg, + S: MsgStore, + { + match self { + Self::Msg(id) => { + if let Some(prev_id) = store.older_msg_id(id).await? { + *id = prev_id; + } + } + Self::Bottom | Self::Pseudo { .. } => { + if let Some(id) = store.newest_msg_id().await? { + *self = Self::Msg(id); + } + } + _ => {} + } + Ok(()) + } + + pub async fn move_to_newer_msg(&mut self, store: &S) -> Result<(), S::Error> + where + M: Msg, + S: MsgStore, + { + match self { + Self::Msg(id) => { + if let Some(prev_id) = store.newer_msg_id(id).await? { + *id = prev_id; + } else { + *self = Self::Bottom; + } + } + Self::Pseudo { .. } => { + *self = Self::Bottom; + } + _ => {} + } + Ok(()) + } + + pub async fn move_to_older_unseen_msg(&mut self, store: &S) -> Result<(), S::Error> + where + M: Msg, + S: MsgStore, + { + match self { + Self::Msg(id) => { + if let Some(prev_id) = store.older_unseen_msg_id(id).await? { + *id = prev_id; + } + } + Self::Bottom | Self::Pseudo { .. } => { + if let Some(id) = store.newest_unseen_msg_id().await? { + *self = Self::Msg(id); + } + } + _ => {} + } + Ok(()) + } + + pub async fn move_to_newer_unseen_msg(&mut self, store: &S) -> Result<(), S::Error> + where + M: Msg, + S: MsgStore, + { + match self { + Self::Msg(id) => { + if let Some(prev_id) = store.newer_unseen_msg_id(id).await? { + *id = prev_id; + } else { + *self = Self::Bottom; + } + } + Self::Pseudo { .. } => { + *self = Self::Bottom; + } + _ => {} + } + Ok(()) + } + + pub async fn move_to_parent(&mut self, store: &S) -> Result<(), S::Error> + where + M: Msg, + S: MsgStore, + { + match self { + Self::Editor { parent, .. } | Self::Pseudo { parent, .. } => { + if let Some(parent_id) = parent { + *self = Self::Msg(parent_id.clone()) + } + } + + Self::Msg(id) => { + let path = store.path(id).await?; + if let Some(parent_id) = path.parent_segments().last() { + *id = parent_id.clone(); + } + } + _ => {} + } + Ok(()) + } + + pub async fn move_to_root(&mut self, store: &S) -> Result<(), S::Error> + where + M: Msg, + S: MsgStore, + { + match self { + Self::Pseudo { + parent: Some(parent), + .. + } => { + let path = store.path(parent).await?; + *self = Self::Msg(path.first().clone()); + } + Self::Msg(id) => { + let path = store.path(id).await?; + *id = path.first().clone(); + } + _ => {} + } + Ok(()) + } + + pub async fn move_to_prev_sibling(&mut self, store: &S) -> Result<(), S::Error> + where + M: Msg, + S: MsgStore, + { + match self { + Self::Bottom | Self::Pseudo { parent: None, .. } => { + if let Some(last_root_id) = store.last_root_id().await? { + *self = Self::Msg(last_root_id); + } + } + Self::Msg(msg) => { + let path = store.path(msg).await?; + let mut tree = store.tree(path.first()).await?; + Self::find_prev_sibling(store, &mut tree, msg).await?; + } + Self::Editor { .. } => {} + Self::Pseudo { + parent: Some(parent), + .. + } => { + let path = store.path(parent).await?; + let tree = store.tree(path.first()).await?; + if let Some(children) = tree.children(parent) { + if let Some(last_child) = children.last() { + *self = Self::Msg(last_child.clone()); + } + } + } + } + Ok(()) + } + + pub async fn move_to_next_sibling(&mut self, store: &S) -> Result<(), S::Error> + where + M: Msg, + S: MsgStore, + { + match self { + Self::Msg(msg) => { + let path = store.path(msg).await?; + let mut tree = store.tree(path.first()).await?; + if !Self::find_next_sibling(store, &mut tree, msg).await? + && tree.parent(msg).is_none() + { + *self = Self::Bottom; + } + } + Self::Pseudo { parent: None, .. } => { + *self = Self::Bottom; + } + _ => {} + } + Ok(()) + } + + pub async fn move_up_in_tree( + &mut self, + store: &S, + folded: &HashSet, + ) -> Result<(), S::Error> + where + M: Msg, + S: MsgStore, + { + match self { + Self::Bottom | Self::Pseudo { parent: None, .. } => { + if let Some(last_root_id) = store.last_root_id().await? { + let tree = store.tree(&last_root_id).await?; + let mut id = last_root_id; + while Self::find_last_child_in_tree(folded, &tree, &mut id) {} + *self = Self::Msg(id); + } + } + Self::Msg(msg) => { + let path = store.path(msg).await?; + let mut tree = store.tree(path.first()).await?; + Self::find_above_msg_in_tree(store, folded, &mut tree, msg).await?; + } + Self::Editor { .. } => {} + Self::Pseudo { + parent: Some(parent), + .. + } => { + let tree = store.tree(parent).await?; + let mut id = parent.clone(); + while Self::find_last_child_in_tree(folded, &tree, &mut id) {} + *self = Self::Msg(id); + } + } + Ok(()) + } + + pub async fn move_down_in_tree( + &mut self, + store: &S, + folded: &HashSet, + ) -> Result<(), S::Error> + where + M: Msg, + S: MsgStore, + { + match self { + Self::Msg(msg) => { + let path = store.path(msg).await?; + let mut tree = store.tree(path.first()).await?; + if !Self::find_below_msg_in_tree(store, folded, &mut tree, msg).await? { + *self = Self::Bottom; + } + } + Self::Pseudo { parent: None, .. } => { + *self = Self::Bottom; + } + Self::Pseudo { + parent: Some(parent), + .. + } => { + let mut tree = store.tree(parent).await?; + let mut id = parent.clone(); + while Self::find_last_child_in_tree(folded, &tree, &mut id) {} + // Now we're at the previous message + if Self::find_below_msg_in_tree(store, folded, &mut tree, &mut id).await? { + *self = Self::Msg(id); + } else { + *self = Self::Bottom; + } + } + _ => {} + } + Ok(()) + } + + /// The outer `Option` shows whether a parent exists or not. The inner + /// `Option` shows if that parent has an id. + pub async fn parent_for_normal_tree_reply( + &self, + store: &S, + ) -> Result>, S::Error> + where + M: Msg, + S: MsgStore, + { + Ok(match self { + Self::Bottom => Some(None), + Self::Msg(id) => { + let path = store.path(id).await?; + let tree = store.tree(path.first()).await?; + + Some(Some(if tree.next_sibling(id).is_some() { + // A reply to a message that has further siblings should be + // a direct reply. An indirect reply might end up a lot + // further down in the current conversation. + id.clone() + } else if let Some(parent) = tree.parent(id) { + // A reply to a message without younger siblings should be + // an indirect reply so as not to create unnecessarily deep + // threads. In the case that our message has children, this + // might get a bit confusing. I'm not sure yet how well this + // "smart" reply actually works in practice. + parent + } else { + // When replying to a top-level message, it makes sense to + // avoid creating unnecessary new threads. + id.clone() + })) + } + _ => None, + }) + } + + /// The outer `Option` shows whether a parent exists or not. The inner + /// `Option` shows if that parent has an id. + pub async fn parent_for_alternate_tree_reply( + &self, + store: &S, + ) -> Result>, S::Error> + where + M: Msg, + S: MsgStore, + { + Ok(match self { + Self::Bottom => Some(None), + Self::Msg(id) => { + let path = store.path(id).await?; + let tree = store.tree(path.first()).await?; + + Some(Some(if tree.next_sibling(id).is_none() { + // The opposite of replying normally + id.clone() + } else if let Some(parent) = tree.parent(id) { + // The opposite of replying normally + parent + } else { + // The same as replying normally, still to avoid creating + // unnecessary new threads + id.clone() + })) + } + _ => None, + }) + } +} From ecc4995397f7c3e04af25ed3a9f71fb6b1a2bbe8 Mon Sep 17 00:00:00 2001 From: Joscha Date: Fri, 14 Apr 2023 13:45:34 +0200 Subject: [PATCH 077/266] Implement common widgets --- src/ui/chat2.rs | 1 + src/ui/chat2/widgets.rs | 129 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 src/ui/chat2/widgets.rs diff --git a/src/ui/chat2.rs b/src/ui/chat2.rs index 8b7b9ca..a1d3865 100644 --- a/src/ui/chat2.rs +++ b/src/ui/chat2.rs @@ -1,3 +1,4 @@ mod blocks; mod cursor; mod renderer; +mod widgets; diff --git a/src/ui/chat2/widgets.rs b/src/ui/chat2/widgets.rs new file mode 100644 index 0000000..0241232 --- /dev/null +++ b/src/ui/chat2/widgets.rs @@ -0,0 +1,129 @@ +use std::convert::Infallible; + +use async_trait::async_trait; +use crossterm::style::Stylize; +use time::format_description::FormatItem; +use time::macros::format_description; +use time::OffsetDateTime; +use toss::widgets::{BoxedAsync, Empty, Text}; +use toss::{AsyncWidget, Frame, Pos, Size, Style, WidgetExt, WidthDb}; + +use crate::util::InfallibleExt; + +pub const INDENT_STR: &str = "│ "; +pub const INDENT_WIDTH: usize = 2; + +pub struct Indent { + level: usize, + style: Style, +} + +impl Indent { + pub fn new(level: usize, style: Style) -> Self { + Self { level, style } + } +} + +#[async_trait] +impl AsyncWidget for Indent { + async fn size( + &self, + _widthdb: &mut WidthDb, + _max_width: Option, + _max_height: Option, + ) -> Result { + let width = (INDENT_WIDTH * self.level).try_into().unwrap_or(u16::MAX); + Ok(Size::new(width, 0)) + } + + async fn draw(self, frame: &mut Frame) -> Result<(), E> { + let size = frame.size(); + let indent_string = INDENT_STR.repeat(self.level); + + for y in 0..size.height { + frame.write(Pos::new(0, y.into()), (&indent_string, self.style)) + } + + Ok(()) + } +} + +const TIME_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day] [hour]:[minute]"); +const TIME_WIDTH: u16 = 16; + +pub struct Time(BoxedAsync<'static, Infallible>); + +impl Time { + pub fn new(time: Option, style: Style) -> Self { + let widget = if let Some(time) = time { + let text = time.format(TIME_FORMAT).expect("could not format time"); + Text::new((text, style)) + .background() + .with_style(style) + .boxed_async() + } else { + Empty::new() + .with_width(TIME_WIDTH) + .background() + .with_style(style) + .boxed_async() + }; + Self(widget) + } +} + +#[async_trait] +impl AsyncWidget for Time { + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Result { + Ok(self + .0 + .size(widthdb, max_width, max_height) + .await + .infallible()) + } + + async fn draw(self, frame: &mut Frame) -> Result<(), E> { + self.0.draw(frame).await.infallible(); + Ok(()) + } +} + +pub struct Seen(BoxedAsync<'static, Infallible>); + +impl Seen { + pub fn new(seen: bool) -> Self { + let widget = if seen { + Empty::new().with_width(1).boxed_async() + } else { + let style = Style::new().black().on_green(); + Text::new("*").background().with_style(style).boxed_async() + }; + Self(widget) + } +} + +#[async_trait] +impl AsyncWidget for Seen { + async fn size( + &self, + widthdb: &mut WidthDb, + max_width: Option, + max_height: Option, + ) -> Result { + Ok(self + .0 + .size(widthdb, max_width, max_height) + .await + .infallible()) + } + + async fn draw(self, frame: &mut Frame) -> Result<(), E> { + self.0.draw(frame).await.infallible(); + Ok(()) + } +} From f69d88bf4a59ebcb91b0c9a23956d818984486ce Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 13 Apr 2023 21:12:29 +0200 Subject: [PATCH 078/266] Migrate chat to AsyncWidget --- src/store.rs | 6 +- src/ui.rs | 2 + src/ui/chat2.rs | 139 ++++++++++ src/ui/chat2/tree.rs | 491 ++++++++++++++++++++++++++++++++++ src/ui/chat2/tree/renderer.rs | 463 ++++++++++++++++++++++++++++++++ src/ui/chat2/tree/scroll.rs | 68 +++++ src/ui/chat2/tree/widgets.rs | 181 +++++++++++++ src/ui/euph/room.rs | 21 +- 8 files changed, 1359 insertions(+), 12 deletions(-) create mode 100644 src/ui/chat2/tree.rs create mode 100644 src/ui/chat2/tree/renderer.rs create mode 100644 src/ui/chat2/tree/scroll.rs create mode 100644 src/ui/chat2/tree/widgets.rs diff --git a/src/store.rs b/src/store.rs index d762752..35e02a6 100644 --- a/src/store.rs +++ b/src/store.rs @@ -32,7 +32,11 @@ impl Path { } pub fn first(&self) -> &I { - self.0.first().expect("path is not empty") + self.0.first().expect("path is empty") + } + + pub fn into_first(self) -> I { + self.0.into_iter().next().expect("path is empty") } } diff --git a/src/ui.rs b/src/ui.rs index 14ad1fc..33bc4a0 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -38,6 +38,8 @@ const EVENT_PROCESSING_TIME: Duration = Duration::from_millis(1000 / 15); // 15 /// Error for anything that can go wrong while rendering. #[derive(Debug, thiserror::Error)] pub enum UiError { + #[error("{0}")] + Vault(#[from] vault::tokio::Error), #[error("{0}")] Io(#[from] io::Error), } diff --git a/src/ui/chat2.rs b/src/ui/chat2.rs index a1d3865..9ac7409 100644 --- a/src/ui/chat2.rs +++ b/src/ui/chat2.rs @@ -1,4 +1,143 @@ mod blocks; mod cursor; mod renderer; +mod tree; mod widgets; + +use std::io; +use std::sync::Arc; + +use parking_lot::FairMutex; +use toss::widgets::{BoxedAsync, EditorState}; +use toss::{Terminal, WidgetExt}; + +use crate::store::{Msg, MsgStore}; + +use self::cursor::Cursor; +use self::tree::TreeViewState; + +use super::input::{InputEvent, KeyBindingsList}; +use super::{ChatMsg, UiError}; + +pub enum Mode { + Tree, +} + +pub struct ChatState> { + store: S, + + cursor: Cursor, + editor: EditorState, + + mode: Mode, + tree: TreeViewState, +} + +impl + Clone> ChatState { + pub fn new(store: S) -> Self { + Self { + cursor: Cursor::Bottom, + editor: EditorState::new(), + + mode: Mode::Tree, + tree: TreeViewState::new(store.clone()), + + store, + } + } +} + +impl> ChatState { + pub fn store(&self) -> &S { + &self.store + } + + pub fn widget(&mut self, nick: String, focused: bool) -> BoxedAsync<'_, UiError> + where + M: ChatMsg + Send + Sync, + M::Id: Send + Sync, + S: Send + Sync, + S::Error: Send, + UiError: From, + { + match self.mode { + Mode::Tree => self + .tree + .widget(&mut self.cursor, &mut self.editor, nick, focused) + .boxed_async(), + } + } + + pub async fn list_key_bindings(&self, bindings: &mut KeyBindingsList, can_compose: bool) { + match self.mode { + Mode::Tree => self + .tree + .list_key_bindings(bindings, &self.cursor, can_compose), + } + todo!() + } + + pub async fn handle_input_event( + &mut self, + terminal: &mut Terminal, + crossterm_lock: &Arc>, + event: &InputEvent, + can_compose: bool, + ) -> Result, S::Error> + where + M: ChatMsg + Send + Sync, + M::Id: Send + Sync, + S: Send + Sync, + S::Error: Send, + { + match self.mode { + Mode::Tree => { + self.tree + .handle_input_event( + terminal, + crossterm_lock, + event, + &mut self.cursor, + &mut self.editor, + can_compose, + ) + .await + } + } + } + + pub fn cursor(&self) -> Option<&M::Id> { + match &self.cursor { + Cursor::Msg(id) => Some(id), + Cursor::Bottom | Cursor::Editor { .. } | Cursor::Pseudo { .. } => None, + } + } + + /// A [`Reaction::Composed`] message was sent successfully. + pub fn send_successful(&mut self, id: M::Id) { + if let Cursor::Pseudo { .. } = &self.cursor { + self.cursor = Cursor::Msg(id); + self.editor.clear(); + } + } + + /// A [`Reaction::Composed`] message failed to be sent. + pub fn send_failed(&mut self) { + if let Cursor::Pseudo { coming_from, .. } = &self.cursor { + self.cursor = match coming_from { + Some(id) => Cursor::Msg(id.clone()), + None => Cursor::Bottom, + }; + } + } +} + +pub enum Reaction { + NotHandled, + Handled, + Composed { + parent: Option, + content: String, + }, + ComposeError(io::Error), +} diff --git a/src/ui/chat2/tree.rs b/src/ui/chat2/tree.rs new file mode 100644 index 0000000..dc5a02b --- /dev/null +++ b/src/ui/chat2/tree.rs @@ -0,0 +1,491 @@ +//! Rendering messages as full trees. + +// TODO Focusing on sub-trees + +mod renderer; +mod scroll; +mod widgets; + +use std::collections::HashSet; +use std::sync::Arc; + +use async_trait::async_trait; +use parking_lot::FairMutex; +use toss::widgets::EditorState; +use toss::{AsyncWidget, Frame, Pos, Size, Terminal, WidthDb}; + +use crate::store::{Msg, MsgStore}; +use crate::ui::input::{key, InputEvent, KeyBindingsList}; +use crate::ui::{util2, ChatMsg, UiError}; +use crate::util::InfallibleExt; + +use self::renderer::{TreeContext, TreeRenderer}; + +use super::cursor::Cursor; +use super::Reaction; + +pub struct TreeViewState> { + store: S, + + last_size: Size, + last_nick: String, + last_cursor: Cursor, + last_cursor_top: i32, + last_visible_msgs: Vec, + + folded: HashSet, +} + +impl> TreeViewState { + pub fn new(store: S) -> Self { + Self { + store, + last_size: Size::ZERO, + last_nick: String::new(), + last_cursor: Cursor::Bottom, + last_cursor_top: 0, + last_visible_msgs: vec![], + folded: HashSet::new(), + } + } + + pub fn list_movement_key_bindings(&self, bindings: &mut KeyBindingsList) { + bindings.binding("j/k, ↓/↑", "move cursor up/down"); + bindings.binding("J/K, ctrl+↓/↑", "move cursor to prev/next sibling"); + bindings.binding("p/P", "move cursor to parent/root"); + bindings.binding("h/l, ←/→", "move cursor chronologically"); + bindings.binding("H/L, ctrl+←/→", "move cursor to prev/next unseen message"); + bindings.binding("g, home", "move cursor to top"); + bindings.binding("G, end", "move cursor to bottom"); + bindings.binding("ctrl+y/e", "scroll up/down a line"); + bindings.binding("ctrl+u/d", "scroll up/down half a screen"); + bindings.binding("ctrl+b/f, page up/down", "scroll up/down one screen"); + bindings.binding("z", "center cursor on screen"); + // TODO Bindings inspired by vim's ()/[]/{} bindings? + } + + async fn handle_movement_input_event( + &mut self, + frame: &mut Frame, + event: &InputEvent, + cursor: &mut Cursor, + editor: &mut EditorState, + ) -> Result + where + M: ChatMsg + Send + Sync, + M::Id: Send + Sync, + S: Send + Sync, + S::Error: Send, + { + let chat_height: i32 = (frame.size().height - 3).into(); + let widthdb = frame.widthdb(); + + match event { + key!('k') | key!(Up) => cursor.move_up_in_tree(&self.store, &self.folded).await?, + key!('j') | key!(Down) => cursor.move_down_in_tree(&self.store, &self.folded).await?, + key!('K') | key!(Ctrl + Up) => cursor.move_to_prev_sibling(&self.store).await?, + key!('J') | key!(Ctrl + Down) => cursor.move_to_next_sibling(&self.store).await?, + key!('p') => cursor.move_to_parent(&self.store).await?, + key!('P') => cursor.move_to_root(&self.store).await?, + key!('h') | key!(Left) => cursor.move_to_older_msg(&self.store).await?, + key!('l') | key!(Right) => cursor.move_to_newer_msg(&self.store).await?, + key!('H') | key!(Ctrl + Left) => cursor.move_to_older_unseen_msg(&self.store).await?, + key!('L') | key!(Ctrl + Right) => cursor.move_to_newer_unseen_msg(&self.store).await?, + key!('g') | key!(Home) => cursor.move_to_top(&self.store).await?, + key!('G') | key!(End) => cursor.move_to_bottom(), + key!(Ctrl + 'y') => self.scroll_by(cursor, editor, widthdb, 1).await?, + key!(Ctrl + 'e') => self.scroll_by(cursor, editor, widthdb, -1).await?, + key!(Ctrl + 'u') => { + let delta = chat_height / 2; + self.scroll_by(cursor, editor, widthdb, delta).await?; + } + key!(Ctrl + 'd') => { + let delta = -(chat_height / 2); + self.scroll_by(cursor, editor, widthdb, delta).await?; + } + key!(Ctrl + 'b') | key!(PageUp) => { + let delta = chat_height.saturating_sub(1); + self.scroll_by(cursor, editor, widthdb, delta).await?; + } + key!(Ctrl + 'f') | key!(PageDown) => { + let delta = -chat_height.saturating_sub(1); + self.scroll_by(cursor, editor, widthdb, delta).await?; + } + key!('z') => self.center_cursor(cursor, editor, widthdb).await?, + _ => return Ok(false), + } + + Ok(true) + } + + pub fn list_action_key_bindings(&self, bindings: &mut KeyBindingsList) { + bindings.binding("space", "fold current message's subtree"); + bindings.binding("s", "toggle current message's seen status"); + bindings.binding("S", "mark all visible messages as seen"); + bindings.binding("ctrl+s", "mark all older messages as seen"); + } + + async fn handle_action_input_event( + &mut self, + event: &InputEvent, + id: Option<&M::Id>, + ) -> Result { + match event { + key!(' ') => { + if let Some(id) = id { + if !self.folded.remove(id) { + self.folded.insert(id.clone()); + } + return Ok(true); + } + } + key!('s') => { + if let Some(id) = id { + if let Some(msg) = self.store.tree(id).await?.msg(id) { + self.store.set_seen(id, !msg.seen()).await?; + } + return Ok(true); + } + } + key!('S') => { + for id in &self.last_visible_msgs { + self.store.set_seen(id, true).await?; + } + return Ok(true); + } + key!(Ctrl + 's') => { + if let Some(id) = id { + self.store.set_older_seen(id, true).await?; + } else { + self.store + .set_older_seen(&M::last_possible_id(), true) + .await?; + } + return Ok(true); + } + _ => {} + } + Ok(false) + } + + pub fn list_edit_initiating_key_bindings(&self, bindings: &mut KeyBindingsList) { + bindings.binding("r", "reply to message (inline if possible, else directly)"); + bindings.binding("R", "reply to message (opposite of R)"); + bindings.binding("t", "start a new thread"); + } + + async fn handle_edit_initiating_input_event( + &mut self, + event: &InputEvent, + cursor: &mut Cursor, + id: Option, + ) -> Result { + match event { + key!('r') => { + if let Some(parent) = cursor.parent_for_normal_tree_reply(&self.store).await? { + *cursor = Cursor::Editor { + coming_from: id, + parent, + }; + } + } + key!('R') => { + if let Some(parent) = cursor.parent_for_alternate_tree_reply(&self.store).await? { + *cursor = Cursor::Editor { + coming_from: id, + parent, + }; + } + } + key!('t') | key!('T') => { + *cursor = Cursor::Editor { + coming_from: id, + parent: None, + }; + } + _ => return Ok(false), + } + + Ok(true) + } + + pub fn list_normal_key_bindings(&self, bindings: &mut KeyBindingsList, can_compose: bool) { + self.list_movement_key_bindings(bindings); + bindings.empty(); + self.list_action_key_bindings(bindings); + if can_compose { + bindings.empty(); + self.list_edit_initiating_key_bindings(bindings); + } + } + + async fn handle_normal_input_event( + &mut self, + frame: &mut Frame, + event: &InputEvent, + cursor: &mut Cursor, + editor: &mut EditorState, + can_compose: bool, + id: Option, + ) -> Result + where + M: ChatMsg + Send + Sync, + M::Id: Send + Sync, + S: Send + Sync, + S::Error: Send, + { + #[allow(clippy::if_same_then_else)] + Ok( + if self + .handle_movement_input_event(frame, event, cursor, editor) + .await? + { + true + } else if self.handle_action_input_event(event, id.as_ref()).await? { + true + } else if can_compose { + self.handle_edit_initiating_input_event(event, cursor, id) + .await? + } else { + false + }, + ) + } + + fn list_editor_key_bindings(&self, bindings: &mut KeyBindingsList) { + bindings.binding("esc", "close editor"); + bindings.binding("enter", "send message"); + util2::list_editor_key_bindings_allowing_external_editing(bindings, |_| true); + } + + #[allow(clippy::too_many_arguments)] + fn handle_editor_input_event( + &mut self, + terminal: &mut Terminal, + crossterm_lock: &Arc>, + event: &InputEvent, + cursor: &mut Cursor, + editor: &mut EditorState, + coming_from: Option, + parent: Option, + ) -> Reaction { + // TODO Tab-completion + + match event { + key!(Esc) => { + *cursor = coming_from.map(Cursor::Msg).unwrap_or(Cursor::Bottom); + return Reaction::Handled; + } + + key!(Enter) => { + let content = editor.text().to_string(); + if !content.trim().is_empty() { + *cursor = Cursor::Pseudo { + coming_from, + parent: parent.clone(), + }; + return Reaction::Composed { parent, content }; + } + } + + _ => { + let handled = util2::handle_editor_input_event_allowing_external_editing( + editor, + terminal, + crossterm_lock, + event, + |_| true, + ); + match handled { + Ok(true) => {} + Ok(false) => return Reaction::NotHandled, + Err(e) => return Reaction::ComposeError(e), + } + } + } + + Reaction::Handled + } + + pub fn list_key_bindings( + &self, + bindings: &mut KeyBindingsList, + cursor: &Cursor, + can_compose: bool, + ) { + bindings.heading("Chat"); + match cursor { + Cursor::Bottom | Cursor::Msg(_) => { + self.list_normal_key_bindings(bindings, can_compose); + } + Cursor::Editor { .. } => self.list_editor_key_bindings(bindings), + Cursor::Pseudo { .. } => { + self.list_normal_key_bindings(bindings, false); + } + } + } + + pub async fn handle_input_event( + &mut self, + terminal: &mut Terminal, + crossterm_lock: &Arc>, + event: &InputEvent, + cursor: &mut Cursor, + editor: &mut EditorState, + can_compose: bool, + ) -> Result, S::Error> + where + M: ChatMsg + Send + Sync, + M::Id: Send + Sync, + S: Send + Sync, + S::Error: Send, + { + Ok(match cursor { + Cursor::Bottom => { + if self + .handle_normal_input_event( + terminal.frame(), + event, + cursor, + editor, + can_compose, + None, + ) + .await? + { + Reaction::Handled + } else { + Reaction::NotHandled + } + } + Cursor::Msg(id) => { + let id = id.clone(); + if self + .handle_normal_input_event( + terminal.frame(), + event, + cursor, + editor, + can_compose, + Some(id), + ) + .await? + { + Reaction::Handled + } else { + Reaction::NotHandled + } + } + Cursor::Editor { + coming_from, + parent, + } => { + let coming_from = coming_from.clone(); + let parent = parent.clone(); + self.handle_editor_input_event( + terminal, + crossterm_lock, + event, + cursor, + editor, + coming_from, + parent, + ) + } + Cursor::Pseudo { .. } => { + if self + .handle_movement_input_event(terminal.frame(), event, cursor, editor) + .await? + { + Reaction::Handled + } else { + Reaction::NotHandled + } + } + }) + } + + pub fn widget<'a>( + &'a mut self, + cursor: &'a mut Cursor, + editor: &'a mut EditorState, + nick: String, + focused: bool, + ) -> TreeView<'a, M, S> { + TreeView { + state: self, + cursor, + editor, + nick, + focused, + } + } +} + +pub struct TreeView<'a, M: Msg, S: MsgStore> { + state: &'a mut TreeViewState, + + cursor: &'a mut Cursor, + editor: &'a mut EditorState, + + nick: String, + focused: bool, +} + +#[async_trait] +impl AsyncWidget for TreeView<'_, M, S> +where + M: Msg + ChatMsg + Send + Sync, + M::Id: Send + Sync, + S: MsgStore + Send + Sync, + S::Error: Send, + UiError: From, +{ + async fn size( + &self, + _widthdb: &mut WidthDb, + _max_width: Option, + _max_height: Option, + ) -> Result { + Ok(Size::ZERO) + } + + async fn draw(self, frame: &mut Frame) -> Result<(), UiError> { + let size = frame.size(); + + let context = TreeContext { + size, + nick: self.nick.clone(), + focused: self.focused, + last_cursor: self.state.last_cursor.clone(), + last_cursor_top: self.state.last_cursor_top, + }; + + let mut renderer = TreeRenderer::new( + context, + &self.state.store, + self.cursor, + self.editor, + frame.widthdb(), + ); + + renderer.prepare_blocks_for_drawing().await?; + + self.state.last_size = size; + self.state.last_nick = self.nick; + renderer.update_render_info( + &mut self.state.last_cursor, + &mut self.state.last_cursor_top, + &mut self.state.last_visible_msgs, + ); + + for (range, block) in renderer.into_visible_blocks() { + let widget = block.into_widget(); + frame.push(Pos::new(0, range.top), widget.size()); + widget.draw(frame).await.infallible(); + frame.pop(); + } + + Ok(()) + } +} diff --git a/src/ui/chat2/tree/renderer.rs b/src/ui/chat2/tree/renderer.rs new file mode 100644 index 0000000..0f58f4e --- /dev/null +++ b/src/ui/chat2/tree/renderer.rs @@ -0,0 +1,463 @@ +//! A [`BlockProvider`] for message trees. + +use std::convert::Infallible; + +use async_recursion::async_recursion; +use async_trait::async_trait; +use toss::widgets::{EditorState, Empty, Predrawn, Resize}; +use toss::{AsyncWidget, Size, WidthDb}; + +use crate::store::{Msg, MsgStore, Tree}; +use crate::ui::chat2::blocks::{Block, Blocks, Range}; +use crate::ui::chat2::cursor::Cursor; +use crate::ui::chat2::renderer::{self, overlaps, Renderer}; +use crate::ui::ChatMsg; +use crate::util::InfallibleExt; + +use super::widgets; + +/// When rendering messages as full trees, special ids and zero-height messages +/// are used for robust scrolling behaviour. +#[derive(PartialEq, Eq)] +pub enum TreeBlockId { + /// There is a zero-height block at the very bottom of the chat that has + /// this id. It is used for positioning [`Cursor::Bottom`]. + Bottom, + /// Normal messages have this id. It is used for positioning + /// [`Cursor::Msg`]. + Msg(Id), + /// After all children of a message, a zero-height block with this id is + /// rendered. It is used for positioning [`Cursor::Editor`] and + /// [`Cursor::Pseudo`]. + After(Id), +} + +impl TreeBlockId { + pub fn from_cursor(cursor: &Cursor) -> Self { + match cursor { + Cursor::Bottom + | Cursor::Editor { parent: None, .. } + | Cursor::Pseudo { parent: None, .. } => Self::Bottom, + + Cursor::Msg(id) => Self::Msg(id.clone()), + + Cursor::Editor { + parent: Some(id), .. + } + | Cursor::Pseudo { + parent: Some(id), .. + } => Self::After(id.clone()), + } + } + + pub fn any_id(&self) -> Option<&Id> { + match self { + Self::Bottom => None, + Self::Msg(id) | Self::After(id) => Some(id), + } + } + + pub fn msg_id(&self) -> Option<&Id> { + match self { + Self::Bottom | Self::After(_) => None, + Self::Msg(id) => Some(id), + } + } +} + +type TreeBlock = Block>; +type TreeBlocks = Blocks>; + +pub struct TreeContext { + pub size: Size, + pub nick: String, + pub focused: bool, + pub last_cursor: Cursor, + pub last_cursor_top: i32, +} + +pub struct TreeRenderer<'a, M: Msg, S: MsgStore> { + context: TreeContext, + + store: &'a S, + cursor: &'a mut Cursor, + editor: &'a mut EditorState, + widthdb: &'a mut WidthDb, + + /// Root id of the topmost tree in the blocks. When set to `None`, only the + /// bottom of the chat history has been rendered. + top_root_id: Option, + /// Root id of the bottommost tree in the blocks. When set to `None`, only + /// the bottom of the chat history has been rendered. + bottom_root_id: Option, + + blocks: TreeBlocks, +} + +impl<'a, M, S> TreeRenderer<'a, M, S> +where + M: Msg + ChatMsg + Send + Sync, + M::Id: Send + Sync, + S: MsgStore + Send + Sync, + S::Error: Send, +{ + /// You must call [`Self::prepare_blocks`] immediately after calling + /// this function. + pub fn new( + context: TreeContext, + store: &'a S, + cursor: &'a mut Cursor, + editor: &'a mut EditorState, + widthdb: &'a mut WidthDb, + ) -> Self { + Self { + context, + store, + cursor, + editor, + widthdb, + top_root_id: None, + bottom_root_id: None, + blocks: Blocks::new(0), + } + } + + async fn predraw(widget: W, size: Size, widthdb: &mut WidthDb) -> Predrawn + where + W: AsyncWidget + Send + Sync, + { + Predrawn::new_async(Resize::new(widget).with_max_width(size.width), widthdb) + .await + .infallible() + } + + async fn zero_height_block(&mut self, parent: Option<&M::Id>) -> TreeBlock { + let id = match parent { + Some(parent) => TreeBlockId::After(parent.clone()), + None => TreeBlockId::Bottom, + }; + + let widget = Self::predraw(Empty::new(), self.context.size, self.widthdb).await; + Block::new(id, widget, false) + } + + async fn editor_block(&mut self, indent: usize, parent: Option<&M::Id>) -> TreeBlock { + let id = match parent { + Some(parent) => TreeBlockId::After(parent.clone()), + None => TreeBlockId::Bottom, + }; + + // TODO Unhighlighted version when focusing on nick list + let widget = widgets::editor::(indent, &self.context.nick, self.editor); + let widget = Self::predraw(widget, self.context.size, self.widthdb).await; + let mut block = Block::new(id, widget, false); + + // Since the editor was rendered when the `Predrawn` was created, the + // last cursor pos is accurate now. + let cursor_line = self.editor.last_cursor_pos().y; + block.set_focus(Range::new(cursor_line, cursor_line)); + + block + } + + async fn pseudo_block(&mut self, indent: usize, parent: Option<&M::Id>) -> TreeBlock { + let id = match parent { + Some(parent) => TreeBlockId::After(parent.clone()), + None => TreeBlockId::Bottom, + }; + + // TODO Unhighlighted version when focusing on nick list + let widget = widgets::pseudo::(indent, &self.context.nick, self.editor); + let widget = Self::predraw(widget, self.context.size, self.widthdb).await; + Block::new(id, widget, false) + } + + async fn message_block(&mut self, indent: usize, msg: &M) -> TreeBlock { + let msg_id = msg.id(); + + let highlighted = match self.cursor { + Cursor::Msg(id) => *id == msg_id, + _ => false, + }; + + // TODO Amount of folded messages + let widget = widgets::msg(self.context.focused && highlighted, indent, msg, None); + let widget = Self::predraw(widget, self.context.size, self.widthdb).await; + Block::new(TreeBlockId::Msg(msg_id), widget, true) + } + + async fn message_placeholder_block( + &mut self, + indent: usize, + msg_id: &M::Id, + ) -> TreeBlock { + let highlighted = match self.cursor { + Cursor::Msg(id) => id == msg_id, + _ => false, + }; + + // TODO Amount of folded messages + let widget = widgets::msg_placeholder(self.context.focused && highlighted, indent, None); + let widget = Self::predraw(widget, self.context.size, self.widthdb).await; + Block::new(TreeBlockId::Msg(msg_id.clone()), widget, true) + } + + async fn layout_bottom(&mut self) -> TreeBlocks { + let mut blocks = Blocks::new(0); + + match self.cursor { + Cursor::Editor { parent: None, .. } => { + blocks.push_bottom(self.editor_block(0, None).await) + } + Cursor::Pseudo { parent: None, .. } => { + blocks.push_bottom(self.pseudo_block(0, None).await) + } + _ => blocks.push_bottom(self.zero_height_block(None).await), + } + + blocks + } + + #[async_recursion] + async fn layout_subtree( + &mut self, + tree: &Tree, + indent: usize, + msg_id: &M::Id, + blocks: &mut TreeBlocks, + ) { + // Message itself + let block = if let Some(msg) = tree.msg(msg_id) { + self.message_block(indent, msg).await + } else { + self.message_placeholder_block(indent, msg_id).await + }; + blocks.push_bottom(block); + + // Children, recursively + if let Some(children) = tree.children(msg_id) { + for child in children { + self.layout_subtree(tree, indent + 1, child, blocks).await; + } + } + + // After message (zero-height block, editor, or placeholder) + let block = match self.cursor { + Cursor::Editor { + parent: Some(id), .. + } if id == msg_id => self.editor_block(indent + 1, Some(msg_id)).await, + + Cursor::Pseudo { + parent: Some(id), .. + } if id == msg_id => self.pseudo_block(indent + 1, Some(msg_id)).await, + + _ => self.zero_height_block(Some(msg_id)).await, + }; + blocks.push_bottom(block); + } + + async fn layout_tree(&mut self, tree: Tree) -> TreeBlocks { + let mut blocks = Blocks::new(0); + self.layout_subtree(&tree, 0, tree.root(), &mut blocks) + .await; + blocks + } + + async fn root_id(&self, id: &TreeBlockId) -> Result, S::Error> { + let Some(id) = id.any_id() else { return Ok(None); }; + let path = self.store.path(id).await?; + Ok(Some(path.into_first())) + } + + async fn prepare_initial_tree(&mut self, root_id: &Option) -> Result<(), S::Error> { + self.top_root_id = root_id.clone(); + self.bottom_root_id = root_id.clone(); + + let blocks = if let Some(root_id) = root_id { + let tree = self.store.tree(root_id).await?; + self.layout_tree(tree).await + } else { + self.layout_bottom().await + }; + self.blocks.append_bottom(blocks); + + Ok(()) + } + + fn make_cursor_visible(&mut self) { + let cursor_id = TreeBlockId::from_cursor(self.cursor); + if *self.cursor == self.context.last_cursor { + // Cursor did not move, so we just need to ensure it overlaps the + // scroll area + renderer::scroll_so_block_focus_overlaps_scroll_area(self, &cursor_id); + } else { + // Cursor moved, so it should fully overlap the scroll area + renderer::scroll_so_block_focus_fully_overlaps_scroll_area(self, &cursor_id); + } + } + + pub async fn prepare_blocks_for_drawing(&mut self) -> Result<(), S::Error> { + let cursor_id = TreeBlockId::from_cursor(self.cursor); + let cursor_root_id = self.root_id(&cursor_id).await?; + + // Render cursor and blocks around it until screen is filled as long as + // the cursor is visible, regardless of how the screen is scrolled. + self.prepare_initial_tree(&cursor_root_id).await?; + renderer::expand_to_fill_screen_around_block(self, &cursor_id).await?; + + // Scroll based on last cursor position + let last_cursor_id = TreeBlockId::from_cursor(&self.context.last_cursor); + if !renderer::scroll_to_set_block_top(self, &last_cursor_id, self.context.last_cursor_top) { + // Since the last cursor is not within scrolling distance of our + // current cursor, we need to estimate whether the last cursor was + // above or below the current cursor. + let last_cursor_root_id = self.root_id(&cursor_id).await?; + if last_cursor_root_id <= cursor_root_id { + renderer::scroll_blocks_fully_below_screen(self); + } else { + renderer::scroll_blocks_fully_above_screen(self); + } + } + + // Fulfill scroll constraints + self.make_cursor_visible(); + renderer::clamp_scroll_biased_downwards(self); + + Ok(()) + } + + fn move_cursor_so_it_is_visible(&mut self) { + let cursor_id = TreeBlockId::from_cursor(self.cursor); + if matches!(cursor_id, TreeBlockId::Bottom | TreeBlockId::Msg(_)) { + match renderer::find_cursor_starting_at(self, &cursor_id) { + Some(TreeBlockId::Bottom) => *self.cursor = Cursor::Bottom, + Some(TreeBlockId::Msg(id)) => *self.cursor = Cursor::Msg(id.clone()), + _ => {} + } + } + } + + pub async fn scroll_by(&mut self, delta: i32) -> Result<(), S::Error> { + self.blocks.shift(delta); + renderer::expand_to_fill_visible_area(self).await?; + renderer::clamp_scroll_biased_downwards(self); + + self.move_cursor_so_it_is_visible(); + + self.make_cursor_visible(); + renderer::clamp_scroll_biased_downwards(self); + + Ok(()) + } + + pub fn center_cursor(&mut self) { + let cursor_id = TreeBlockId::from_cursor(self.cursor); + renderer::scroll_so_block_is_centered(self, &cursor_id); + + self.make_cursor_visible(); + renderer::clamp_scroll_biased_downwards(self); + } + + pub fn update_render_info( + &self, + last_cursor: &mut Cursor, + last_cursor_top: &mut i32, + last_visible_msgs: &mut Vec, + ) { + *last_cursor = self.cursor.clone(); + + let cursor_id = TreeBlockId::from_cursor(self.cursor); + let (range, _) = self.blocks.find_block(&cursor_id).unwrap(); + *last_cursor_top = range.top; + + let area = renderer::visible_area(self); + *last_visible_msgs = self + .blocks + .iter() + .filter(|(range, _)| overlaps(area, *range)) + .filter_map(|(_, block)| block.id().msg_id()) + .cloned() + .collect() + } + + pub fn into_visible_blocks( + self, + ) -> impl Iterator, Block>)> { + let area = renderer::visible_area(&self); + self.blocks + .into_iter() + .filter(move |(range, block)| overlaps(area, block.focus(*range))) + } +} + +#[async_trait] +impl Renderer> for TreeRenderer<'_, M, S> +where + M: Msg + ChatMsg + Send + Sync, + M::Id: Send + Sync, + S: MsgStore + Send + Sync, + S::Error: Send, +{ + type Error = S::Error; + + fn size(&self) -> Size { + self.context.size + } + + fn scrolloff(&self) -> i32 { + 2 // TODO Make configurable + } + + fn blocks(&self) -> &TreeBlocks { + &self.blocks + } + + fn blocks_mut(&mut self) -> &mut TreeBlocks { + &mut self.blocks + } + + fn into_blocks(self) -> TreeBlocks { + self.blocks + } + + async fn expand_top(&mut self) -> Result<(), Self::Error> { + let prev_root_id = if let Some(top_root_id) = &self.top_root_id { + self.store.prev_root_id(top_root_id).await? + } else { + self.store.last_root_id().await? + }; + + if let Some(prev_root_id) = prev_root_id { + let tree = self.store.tree(&prev_root_id).await?; + let blocks = self.layout_tree(tree).await; + self.blocks.append_top(blocks); + self.top_root_id = Some(prev_root_id); + } else { + self.blocks.end_top(); + } + + Ok(()) + } + + async fn expand_bottom(&mut self) -> Result<(), Self::Error> { + let Some(bottom_root_id) = &self.bottom_root_id else { + self.blocks.end_bottom(); + return Ok(()) + }; + + let next_root_id = self.store.next_root_id(bottom_root_id).await?; + if let Some(next_root_id) = next_root_id { + let tree = self.store.tree(&next_root_id).await?; + let blocks = self.layout_tree(tree).await; + self.blocks.append_bottom(blocks); + self.bottom_root_id = Some(next_root_id); + } else { + let blocks = self.layout_bottom().await; + self.blocks.append_bottom(blocks); + self.blocks.end_bottom(); + self.bottom_root_id = None; + }; + + Ok(()) + } +} diff --git a/src/ui/chat2/tree/scroll.rs b/src/ui/chat2/tree/scroll.rs new file mode 100644 index 0000000..40007f1 --- /dev/null +++ b/src/ui/chat2/tree/scroll.rs @@ -0,0 +1,68 @@ +use toss::widgets::EditorState; +use toss::WidthDb; + +use crate::store::{Msg, MsgStore}; +use crate::ui::chat2::cursor::Cursor; +use crate::ui::ChatMsg; + +use super::renderer::{TreeContext, TreeRenderer}; +use super::TreeViewState; + +impl TreeViewState +where + M: Msg + ChatMsg + Send + Sync, + M::Id: Send + Sync, + S: MsgStore + Send + Sync, + S::Error: Send, +{ + fn last_context(&self) -> TreeContext { + TreeContext { + size: self.last_size, + nick: self.last_nick.clone(), + focused: true, + last_cursor: self.last_cursor.clone(), + last_cursor_top: self.last_cursor_top, + } + } + + pub async fn scroll_by( + &mut self, + cursor: &mut Cursor, + editor: &mut EditorState, + widthdb: &mut WidthDb, + delta: i32, + ) -> Result<(), S::Error> { + let context = self.last_context(); + let mut renderer = TreeRenderer::new(context, &self.store, cursor, editor, widthdb); + renderer.prepare_blocks_for_drawing().await?; + + renderer.scroll_by(delta).await?; + + renderer.update_render_info( + &mut self.last_cursor, + &mut self.last_cursor_top, + &mut self.last_visible_msgs, + ); + Ok(()) + } + + pub async fn center_cursor( + &mut self, + cursor: &mut Cursor, + editor: &mut EditorState, + widthdb: &mut WidthDb, + ) -> Result<(), S::Error> { + let context = self.last_context(); + let mut renderer = TreeRenderer::new(context, &self.store, cursor, editor, widthdb); + renderer.prepare_blocks_for_drawing().await?; + + renderer.center_cursor(); + + renderer.update_render_info( + &mut self.last_cursor, + &mut self.last_cursor_top, + &mut self.last_visible_msgs, + ); + Ok(()) + } +} diff --git a/src/ui/chat2/tree/widgets.rs b/src/ui/chat2/tree/widgets.rs new file mode 100644 index 0000000..d0ff5f7 --- /dev/null +++ b/src/ui/chat2/tree/widgets.rs @@ -0,0 +1,181 @@ +use std::convert::Infallible; + +use crossterm::style::Stylize; +use toss::widgets::{BoxedAsync, EditorState, Join2, Join4, Join5, Text}; +use toss::{Style, Styled, WidgetExt}; + +use crate::store::Msg; +use crate::ui::chat2::widgets::{Indent, Seen, Time}; +use crate::ui::ChatMsg; + +pub const PLACEHOLDER: &str = "[...]"; + +pub fn style_placeholder() -> Style { + Style::new().dark_grey() +} + +fn style_time(highlighted: bool) -> Style { + if highlighted { + Style::new().black().on_white() + } else { + Style::new().grey() + } +} + +fn style_indent(highlighted: bool) -> Style { + if highlighted { + Style::new().black().on_white() + } else { + Style::new().dark_grey() + } +} + +fn style_info() -> Style { + Style::new().italic().dark_grey() +} + +fn style_editor_highlight() -> Style { + Style::new().black().on_cyan() +} + +fn style_pseudo_highlight() -> Style { + Style::new().black().on_yellow() +} + +pub fn msg( + highlighted: bool, + indent: usize, + msg: &M, + folded_info: Option, +) -> BoxedAsync<'static, Infallible> { + let (nick, mut content) = msg.styled(); + + if let Some(amount) = folded_info { + content = content + .then_plain("\n") + .then(format!("[{amount} more]"), style_info()); + } + + Join5::horizontal( + Seen::new(msg.seen()).segment().with_fixed(true), + Time::new(Some(msg.time()), style_time(highlighted)) + .padding() + .with_right(1) + .with_stretch(true) + .segment() + .with_fixed(true), + Indent::new(indent, style_indent(highlighted)) + .segment() + .with_fixed(true), + Join2::vertical( + Text::new(nick) + .padding() + .with_right(1) + .segment() + .with_fixed(true), + Indent::new(1, style_indent(false)).segment(), + ) + .segment() + .with_fixed(true), + // TODO Minimum content width + // TODO Minimizing and maximizing messages + Text::new(content).segment(), + ) + .boxed_async() +} + +pub fn msg_placeholder( + highlighted: bool, + indent: usize, + folded_info: Option, +) -> BoxedAsync<'static, Infallible> { + let mut content = Styled::new(PLACEHOLDER, style_placeholder()); + + if let Some(amount) = folded_info { + content = content + .then_plain("\n") + .then(format!("[{amount} more]"), style_info()); + } + + Join4::horizontal( + Seen::new(true).segment().with_fixed(true), + Time::new(None, style_time(highlighted)) + .padding() + .with_right(1) + .with_stretch(true) + .segment() + .with_fixed(true), + Indent::new(indent, style_indent(highlighted)) + .segment() + .with_fixed(true), + Text::new(content).segment(), + ) + .boxed_async() +} + +pub fn editor<'a, M: ChatMsg>( + indent: usize, + nick: &str, + editor: &'a mut EditorState, +) -> BoxedAsync<'a, Infallible> { + let (nick, content) = M::edit(nick, editor.text()); + let editor = editor.widget().with_highlight(|_| content); + + Join5::horizontal( + Seen::new(true).segment().with_fixed(true), + Time::new(None, style_editor_highlight()) + .padding() + .with_right(1) + .with_stretch(true) + .segment() + .with_fixed(true), + Indent::new(indent, style_editor_highlight()) + .segment() + .with_fixed(true), + Join2::vertical( + Text::new(nick) + .padding() + .with_right(1) + .segment() + .with_fixed(true), + Indent::new(1, style_indent(false)).segment(), + ) + .segment() + .with_fixed(true), + editor.segment(), + ) + .boxed_async() +} + +pub fn pseudo<'a, M: ChatMsg>( + indent: usize, + nick: &str, + editor: &'a mut EditorState, +) -> BoxedAsync<'a, Infallible> { + let (nick, content) = M::edit(nick, editor.text()); + + Join5::horizontal( + Seen::new(true).segment().with_fixed(true), + Time::new(None, style_pseudo_highlight()) + .padding() + .with_right(1) + .with_stretch(true) + .segment() + .with_fixed(true), + Indent::new(indent, style_pseudo_highlight()) + .segment() + .with_fixed(true), + Join2::vertical( + Text::new(nick) + .padding() + .with_right(1) + .segment() + .with_fixed(true), + Indent::new(1, style_indent(false)).segment(), + ) + .segment() + .with_fixed(true), + Text::new(content).segment(), + ) + .boxed_async() +} diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index b2c575f..c611362 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -14,7 +14,7 @@ use toss::{AsyncWidget, Style, Styled, Terminal, WidgetExt}; use crate::config; use crate::euph; use crate::macros::logging_unwrap; -use crate::ui::chat::{ChatState, Reaction}; +use crate::ui::chat2::{ChatState, Reaction}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::widgets::WidgetWrapper; use crate::ui::widgets2::ListState; @@ -150,12 +150,12 @@ impl EuphRoom { if let Some(id_rx) = &mut self.last_msg_sent { match id_rx.try_recv() { Ok(id) => { - self.chat.sent(Some(id)).await; + self.chat.send_successful(id); self.last_msg_sent = None; } Err(TryRecvError::Empty) => {} // Wait a bit longer Err(TryRecvError::Closed) => { - self.chat.sent(None).await; + self.chat.send_failed(); self.last_msg_sent = None; } } @@ -243,7 +243,7 @@ impl EuphRoom { chat: &mut EuphChatState, status_widget: impl AsyncWidget + Send + Sync + 'static, ) -> BoxedAsync<'_, UiError> { - let chat_widget = WidgetWrapper::new(chat.widget(String::new(), true)); + let chat_widget = chat.widget(String::new(), true); Join2::vertical( status_widget.segment().with_fixed(true), @@ -264,8 +264,7 @@ impl EuphRoom { .with_right(1) .border(); - let chat_widget = - WidgetWrapper::new(chat.widget(joined.session.name.clone(), focus == Focus::Chat)); + let chat_widget = chat.widget(joined.session.name.clone(), focus == Focus::Chat); Join2::horizontal( Join2::vertical( @@ -350,7 +349,7 @@ impl EuphRoom { if let Some(room) = &self.room { match room.send(parent, content) { Ok(id_rx) => self.last_msg_sent = Some(id_rx), - Err(_) => self.chat.sent(None).await, + Err(_) => self.chat.send_failed(), } return true; } @@ -437,16 +436,16 @@ impl EuphRoom { // Always applicable match event { key!('i') => { - if let Some(id) = self.chat.cursor().await { - if let Some(msg) = logging_unwrap!(self.vault().full_msg(id).await) { + if let Some(id) = self.chat.cursor() { + if let Some(msg) = logging_unwrap!(self.vault().full_msg(*id).await) { self.state = State::InspectMessage(msg); } } return true; } key!('I') => { - if let Some(id) = self.chat.cursor().await { - if let Some(msg) = logging_unwrap!(self.vault().msg(id).await) { + if let Some(id) = self.chat.cursor() { + if let Some(msg) = logging_unwrap!(self.vault().msg(*id).await) { self.state = State::Links(LinksState::new(&msg.content)); } } From 31c8453a83ea1d3200412aba1992f62ff2b8d349 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 17 Apr 2023 09:54:51 +0200 Subject: [PATCH 079/266] Migrate links popup to AsyncWidget --- src/ui/euph/links.rs | 48 ++++++++++++++++++++++++-------------------- src/ui/euph/room.rs | 2 +- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/ui/euph/links.rs b/src/ui/euph/links.rs index 9ccdc51..22bc67d 100644 --- a/src/ui/euph/links.rs +++ b/src/ui/euph/links.rs @@ -2,13 +2,12 @@ use std::io; use crossterm::style::Stylize; use linkify::{LinkFinder, LinkKind}; -use toss::{Style, Styled}; +use toss::widgets::{BoxedAsync, Text}; +use toss::{Style, Styled, WidgetExt}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::widgets::list::ListState; -use crate::ui::widgets::popup::Popup; -use crate::ui::widgets::text::Text; -use crate::ui::widgets::BoxedWidget; +use crate::ui::widgets2::{ListState, Popup}; +use crate::ui::UiError; pub struct LinksState { links: Vec, @@ -39,34 +38,39 @@ impl LinksState { } } - pub fn widget(&self) -> BoxedWidget { + pub fn widget(&mut self) -> BoxedAsync<'_, UiError> { let style_selected = Style::new().black().on_white(); - let mut list = self.list.widget().focus(true); + let mut list = self.list.widget(); + if self.links.is_empty() { list.add_unsel(Text::new(("No links found", Style::new().grey().italic()))) } + for (id, link) in self.links.iter().enumerate() { - let (line_normal, line_selected) = if let Some(number_key) = NUMBER_KEYS.get(id) { - ( - Styled::new(format!("[{number_key}]"), Style::new().dark_grey().bold()) - .then_plain(" ") - .then_plain(link), + #[allow(clippy::collapsible_else_if)] + let text = if list.state().selected() == Some(&id) { + if let Some(number_key) = NUMBER_KEYS.get(id) { Styled::new(format!("[{number_key}]"), style_selected.bold()) .then(" ", style_selected) - .then(link, style_selected), - ) + .then(link, style_selected) + } else { + Styled::new(format!(" {link}"), style_selected) + } } else { - ( - Styled::new_plain(format!(" {link}")), - Styled::new(format!(" {link}"), style_selected), - ) + if let Some(number_key) = NUMBER_KEYS.get(id) { + Styled::new(format!("[{number_key}]"), Style::new().dark_grey().bold()) + .then_plain(" ") + .then_plain(link) + } else { + Styled::new_plain(format!(" {link}")) + } }; - list.add_sel(id, Text::new(line_normal), Text::new(line_selected)); + list.add_sel(id, Text::new(text)); } - Popup::new(list).title("Links").build() + Popup::new(list, "Links").boxed_async() } fn open_link_by_id(&self, id: usize) -> EventResult { @@ -87,8 +91,8 @@ impl LinksState { } fn open_link(&self) -> EventResult { - if let Some(id) = self.list.cursor() { - self.open_link_by_id(id) + if let Some(id) = self.list.selected() { + self.open_link_by_id(*id) } else { EventResult::Handled } diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index c611362..17462fa 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -227,7 +227,7 @@ impl EuphRoom { State::Auth(editor) => layers.push(auth::widget(editor)), State::Nick(editor) => layers.push(nick::widget(editor)), State::Account(account) => layers.push(account.widget()), - State::Links(links) => layers.push(WidgetWrapper::new(links.widget()).boxed_async()), + State::Links(links) => layers.push(links.widget()), State::InspectMessage(message) => layers.push(inspect::message_widget(message)), State::InspectSession(session) => layers.push(inspect::session_widget(session)), } From b8da97aaa426f6c2053771e8a92b424459ce5a88 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 17 Apr 2023 09:57:50 +0200 Subject: [PATCH 080/266] Migrate room popups to AsyncWidget --- src/ui/euph/popup.rs | 26 +++++++++++--------------- src/ui/euph/room.rs | 3 +-- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/ui/euph/popup.rs b/src/ui/euph/popup.rs index d5cbe76..ebf507c 100644 --- a/src/ui/euph/popup.rs +++ b/src/ui/euph/popup.rs @@ -1,37 +1,33 @@ use crossterm::style::Stylize; -use toss::{Style, Styled}; +use toss::widgets::{BoxedAsync, Text}; +use toss::{Style, Styled, WidgetExt}; -use crate::ui::widgets::float::Float; -use crate::ui::widgets::popup::Popup; -use crate::ui::widgets::text::Text; -use crate::ui::widgets::BoxedWidget; +use crate::ui::widgets2::Popup; +use crate::ui::UiError; pub enum RoomPopup { Error { description: String, reason: String }, } impl RoomPopup { - fn server_error_widget(description: &str, reason: &str) -> BoxedWidget { + fn server_error_widget(description: &str, reason: &str) -> BoxedAsync<'static, UiError> { let border_style = Style::new().red().bold(); let text = Styled::new_plain(description) .then_plain("\n\n") .then("Reason:", Style::new().bold()) .then_plain(" ") .then_plain(reason); - Popup::new(Text::new(text)) - .title(("Error", border_style)) - .border(border_style) - .build() + Popup::new(Text::new(text), ("Error", border_style)) + .with_border_style(border_style) + .boxed_async() } - pub fn widget(&self) -> BoxedWidget { - let widget = match self { + pub fn widget(&self) -> BoxedAsync<'static, UiError> { + match self { Self::Error { description, reason, } => Self::server_error_widget(description, reason), - }; - - Float::new(widget).horizontal(0.5).vertical(0.5).into() + } } } diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 17462fa..1203acb 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -16,7 +16,6 @@ use crate::euph; use crate::macros::logging_unwrap; use crate::ui::chat2::{ChatState, Reaction}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::widgets::WidgetWrapper; use crate::ui::widgets2::ListState; use crate::ui::{util2, UiError, UiEvent}; use crate::vault::EuphRoomVault; @@ -233,7 +232,7 @@ impl EuphRoom { } for popup in &self.popups { - layers.push(WidgetWrapper::new(popup.widget()).boxed_async()); + layers.push(popup.widget()); } Layer::new(layers).boxed_async() From 6f0088e194a9fc7c02bf8ddc3e4267f1485cd60c Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 17 Apr 2023 10:02:05 +0200 Subject: [PATCH 081/266] Migrate F12 log to AsyncWidget --- src/ui.rs | 14 +++++++++----- src/ui/chat2.rs | 6 ++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index 33bc4a0..eae4f36 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -23,13 +23,13 @@ use toss::{Terminal, WidgetExt}; use crate::config::Config; use crate::logger::{LogMsg, Logger}; use crate::macros::{logging_unwrap, ok_or_return, some_or_return}; +use crate::util::InfallibleExt; use crate::vault::Vault; pub use self::chat::ChatMsg; -use self::chat::ChatState; +use self::chat2::ChatState; use self::input::{key, InputEvent, KeyBindingsList}; use self::rooms::Rooms; -use self::widgets::WidgetWrapper; use self::widgets2::ListState; /// Time to spend batch processing events before redrawing the screen. @@ -44,6 +44,12 @@ pub enum UiError { Io(#[from] io::Error), } +impl From for UiError { + fn from(value: Infallible) -> Self { + Err(value).infallible() + } +} + pub enum UiEvent { GraphemeWidthsChanged, LogChanged, @@ -197,9 +203,7 @@ impl Ui { let widget = match self.mode { Mode::Main => self.rooms.widget().await, - Mode::Log => { - WidgetWrapper::new(self.log_chat.widget(String::new(), true)).boxed_async() - } + Mode::Log => self.log_chat.widget(String::new(), true), }; if let Some(key_bindings_list) = key_bindings_list { diff --git a/src/ui/chat2.rs b/src/ui/chat2.rs index 9ac7409..7567de4 100644 --- a/src/ui/chat2.rs +++ b/src/ui/chat2.rs @@ -141,3 +141,9 @@ pub enum Reaction { }, ComposeError(io::Error), } + +impl Reaction { + pub fn handled(&self) -> bool { + !matches!(self, Self::NotHandled) + } +} From e2b75d2f527441f11cb17bcc8440ac544590b47c Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 17 Apr 2023 10:07:56 +0200 Subject: [PATCH 082/266] Move ChatMsg trait to chat2 --- src/ui.rs | 2 +- src/ui/chat2.rs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index eae4f36..654c819 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -26,7 +26,7 @@ use crate::macros::{logging_unwrap, ok_or_return, some_or_return}; use crate::util::InfallibleExt; use crate::vault::Vault; -pub use self::chat::ChatMsg; +pub use self::chat2::ChatMsg; use self::chat2::ChatState; use self::input::{key, InputEvent, KeyBindingsList}; use self::rooms::Rooms; diff --git a/src/ui/chat2.rs b/src/ui/chat2.rs index 7567de4..24b0185 100644 --- a/src/ui/chat2.rs +++ b/src/ui/chat2.rs @@ -8,8 +8,9 @@ use std::io; use std::sync::Arc; use parking_lot::FairMutex; +use time::OffsetDateTime; use toss::widgets::{BoxedAsync, EditorState}; -use toss::{Terminal, WidgetExt}; +use toss::{Styled, Terminal, WidgetExt}; use crate::store::{Msg, MsgStore}; @@ -17,7 +18,14 @@ use self::cursor::Cursor; use self::tree::TreeViewState; use super::input::{InputEvent, KeyBindingsList}; -use super::{ChatMsg, UiError}; +use super::UiError; + +pub trait ChatMsg { + fn time(&self) -> OffsetDateTime; + fn styled(&self) -> (Styled, Styled); + fn edit(nick: &str, content: &str) -> (Styled, Styled); + fn pseudo(nick: &str, content: &str) -> (Styled, Styled); +} pub enum Mode { Tree, From bc8c5968d6bebe1f0fd00599aa01cfa9404ffe49 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 17 Apr 2023 10:08:14 +0200 Subject: [PATCH 083/266] Remove old chat, widgets, util modules --- src/ui.rs | 3 - src/ui/chat.rs | 163 -------- src/ui/chat/blocks.rs | 171 -------- src/ui/chat/tree.rs | 458 --------------------- src/ui/chat/tree/cursor.rs | 498 ----------------------- src/ui/chat/tree/layout.rs | 612 ----------------------------- src/ui/chat/tree/tree_blocks.rs | 71 ---- src/ui/chat/tree/widgets.rs | 165 -------- src/ui/chat/tree/widgets/indent.rs | 41 -- src/ui/chat/tree/widgets/seen.rs | 25 -- src/ui/chat/tree/widgets/time.rs | 25 -- src/ui/util.rs | 166 -------- src/ui/widgets.rs | 109 ----- src/ui/widgets/background.rs | 46 --- src/ui/widgets/border.rs | 65 --- src/ui/widgets/cursor.rs | 44 --- src/ui/widgets/editor.rs | 566 -------------------------- src/ui/widgets/empty.rs | 44 --- src/ui/widgets/float.rs | 73 ---- src/ui/widgets/join.rs | 265 ------------- src/ui/widgets/layer.rs | 38 -- src/ui/widgets/list.rs | 395 ------------------- src/ui/widgets/padding.rs | 103 ----- src/ui/widgets/popup.rs | 74 ---- src/ui/widgets/resize.rs | 86 ---- src/ui/widgets/rules.rs | 46 --- src/ui/widgets/text.rs | 65 --- 27 files changed, 4417 deletions(-) delete mode 100644 src/ui/chat.rs delete mode 100644 src/ui/chat/blocks.rs delete mode 100644 src/ui/chat/tree.rs delete mode 100644 src/ui/chat/tree/cursor.rs delete mode 100644 src/ui/chat/tree/layout.rs delete mode 100644 src/ui/chat/tree/tree_blocks.rs delete mode 100644 src/ui/chat/tree/widgets.rs delete mode 100644 src/ui/chat/tree/widgets/indent.rs delete mode 100644 src/ui/chat/tree/widgets/seen.rs delete mode 100644 src/ui/chat/tree/widgets/time.rs delete mode 100644 src/ui/util.rs delete mode 100644 src/ui/widgets.rs delete mode 100644 src/ui/widgets/background.rs delete mode 100644 src/ui/widgets/border.rs delete mode 100644 src/ui/widgets/cursor.rs delete mode 100644 src/ui/widgets/editor.rs delete mode 100644 src/ui/widgets/empty.rs delete mode 100644 src/ui/widgets/float.rs delete mode 100644 src/ui/widgets/join.rs delete mode 100644 src/ui/widgets/layer.rs delete mode 100644 src/ui/widgets/list.rs delete mode 100644 src/ui/widgets/padding.rs delete mode 100644 src/ui/widgets/popup.rs delete mode 100644 src/ui/widgets/resize.rs delete mode 100644 src/ui/widgets/rules.rs delete mode 100644 src/ui/widgets/text.rs diff --git a/src/ui.rs b/src/ui.rs index 654c819..df3371c 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,11 +1,8 @@ -mod chat; mod chat2; mod euph; mod input; mod rooms; -mod util; mod util2; -mod widgets; mod widgets2; use std::convert::Infallible; diff --git a/src/ui/chat.rs b/src/ui/chat.rs deleted file mode 100644 index f9f0367..0000000 --- a/src/ui/chat.rs +++ /dev/null @@ -1,163 +0,0 @@ -// TODO Implement thread view -// TODO Implement flat (chronological?) view -// TODO Implement message search? - -mod blocks; -mod tree; - -use std::sync::Arc; -use std::{fmt, io}; - -use async_trait::async_trait; -use parking_lot::FairMutex; -use time::OffsetDateTime; -use toss::{Frame, Size, Styled, Terminal, WidthDb}; - -use crate::store::{Msg, MsgStore}; - -use self::tree::{TreeView, TreeViewState}; - -use super::input::{InputEvent, KeyBindingsList}; -use super::widgets::Widget; - -/////////// -// Trait // -/////////// - -pub trait ChatMsg { - fn time(&self) -> OffsetDateTime; - fn styled(&self) -> (Styled, Styled); - fn edit(nick: &str, content: &str) -> (Styled, Styled); - fn pseudo(nick: &str, content: &str) -> (Styled, Styled); -} - -/////////// -// State // -/////////// - -pub enum Mode { - Tree, - // Thread, - // Flat, -} - -pub struct ChatState> { - store: S, - mode: Mode, - tree: TreeViewState, - // thread: ThreadView, - // flat: FlatView, -} - -impl + Clone> ChatState { - pub fn new(store: S) -> Self { - Self { - mode: Mode::Tree, - tree: TreeViewState::new(store.clone()), - store, - } - } -} - -impl> ChatState { - pub fn store(&self) -> &S { - &self.store - } - - pub fn widget(&self, nick: String, focused: bool) -> Chat { - match self.mode { - Mode::Tree => Chat::Tree(self.tree.widget(nick, focused)), - } - } -} - -pub enum Reaction { - NotHandled, - Handled, - Composed { - parent: Option, - content: String, - }, - ComposeError(io::Error), -} - -impl Reaction { - pub fn handled(&self) -> bool { - !matches!(self, Self::NotHandled) - } -} - -impl> ChatState { - pub async fn list_key_bindings(&self, bindings: &mut KeyBindingsList, can_compose: bool) { - match self.mode { - Mode::Tree => self.tree.list_key_bindings(bindings, can_compose).await, - } - } - - pub async fn handle_input_event( - &mut self, - terminal: &mut Terminal, - crossterm_lock: &Arc>, - event: &InputEvent, - can_compose: bool, - ) -> Result, S::Error> { - match self.mode { - Mode::Tree => { - self.tree - .handle_input_event(terminal, crossterm_lock, event, can_compose) - .await - } - } - } - - pub async fn cursor(&self) -> Option { - match self.mode { - Mode::Tree => self.tree.cursor().await, - } - } - - /// A [`Reaction::Composed`] message was sent, either successfully or - /// unsuccessfully. - /// - /// If successful, include the message's id as an argument. If unsuccessful, - /// instead pass a `None`. - pub async fn sent(&mut self, id: Option) { - match self.mode { - Mode::Tree => self.tree.sent(id).await, - } - } -} - -//////////// -// Widget // -//////////// - -pub enum Chat> { - Tree(TreeView), -} - -#[async_trait] -impl Widget for Chat -where - M: Msg + ChatMsg + Send + Sync, - M::Id: Send + Sync, - S: MsgStore + Send + Sync, - S::Error: fmt::Display, -{ - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Size { - match self { - Self::Tree(tree) => tree.size(widthdb, max_width, max_height).await, - } - } - - async fn render(self: Box, frame: &mut Frame) { - match *self { - Self::Tree(tree) => Box::new(tree).render(frame).await, - } - } -} diff --git a/src/ui/chat/blocks.rs b/src/ui/chat/blocks.rs deleted file mode 100644 index bb596cb..0000000 --- a/src/ui/chat/blocks.rs +++ /dev/null @@ -1,171 +0,0 @@ -use std::collections::{vec_deque, VecDeque}; -use std::ops::Range; - -use toss::Frame; - -use crate::macros::some_or_return; -use crate::ui::widgets::BoxedWidget; - -pub struct Block { - pub id: I, - pub top_line: i32, - pub height: i32, - /// The lines of the block that should be made visible if the block is - /// focused on. By default, the focus encompasses the entire block. - /// - /// If not all of these lines can be made visible, the top of the range - /// should be preferred over the bottom. - pub focus: Range, - pub widget: BoxedWidget, -} - -impl Block { - pub async fn new>(frame: &mut Frame, id: I, widget: W) -> Self { - // Interestingly, rust-analyzer fails to deduce the type of `widget` - // here but rustc knows it's a `BoxedWidget`. - let widget = widget.into(); - let max_width = frame.size().width; - let size = widget.size(frame.widthdb(), Some(max_width), None).await; - let height = size.height.into(); - Self { - id, - top_line: 0, - height, - focus: 0..height, - widget, - } - } - - pub fn focus(mut self, focus: Range) -> Self { - self.focus = focus; - self - } -} - -pub struct Blocks { - pub blocks: VecDeque>, - /// The top line of the first block. Useful for prepending blocks, - /// especially to empty [`Blocks`]s. - pub top_line: i32, - /// The bottom line of the last block. Useful for appending blocks, - /// especially to empty [`Blocks`]s. - pub bottom_line: i32, -} - -impl Blocks { - pub fn new() -> Self { - Self::new_below(0) - } - - /// Create a new [`Blocks`] such that the first prepended line will be on - /// `line`. - pub fn new_below(line: i32) -> Self { - Self { - blocks: VecDeque::new(), - top_line: line + 1, - bottom_line: line, - } - } - - pub fn iter(&self) -> vec_deque::Iter<'_, Block> { - self.blocks.iter() - } - - pub fn offset(&mut self, delta: i32) { - self.top_line += delta; - self.bottom_line += delta; - for block in &mut self.blocks { - block.top_line += delta; - } - } - - pub fn push_front(&mut self, mut block: Block) { - self.top_line -= block.height; - block.top_line = self.top_line; - self.blocks.push_front(block); - } - - pub fn push_back(&mut self, mut block: Block) { - block.top_line = self.bottom_line + 1; - self.bottom_line += block.height; - self.blocks.push_back(block); - } - - pub fn prepend(&mut self, mut layout: Self) { - while let Some(block) = layout.blocks.pop_back() { - self.push_front(block); - } - } - - pub fn append(&mut self, mut layout: Self) { - while let Some(block) = layout.blocks.pop_front() { - self.push_back(block); - } - } - - pub fn set_top_line(&mut self, line: i32) { - self.top_line = line; - - if let Some(first_block) = self.blocks.front_mut() { - first_block.top_line = self.top_line; - } - - for i in 1..self.blocks.len() { - self.blocks[i].top_line = self.blocks[i - 1].top_line + self.blocks[i - 1].height; - } - - self.bottom_line = self - .blocks - .back() - .map(|b| b.top_line + b.height - 1) - .unwrap_or(self.top_line - 1); - } - - pub fn set_bottom_line(&mut self, line: i32) { - self.bottom_line = line; - - if let Some(last_block) = self.blocks.back_mut() { - last_block.top_line = self.bottom_line + 1 - last_block.height; - } - - for i in (1..self.blocks.len()).rev() { - self.blocks[i - 1].top_line = self.blocks[i].top_line - self.blocks[i - 1].height; - } - - self.top_line = self - .blocks - .front() - .map(|b| b.top_line) - .unwrap_or(self.bottom_line + 1) - } -} - -impl Blocks { - pub fn find(&self, id: &I) -> Option<&Block> { - self.blocks.iter().find(|b| b.id == *id) - } - - pub fn recalculate_offsets(&mut self, id: &I, top_line: i32) { - let idx = some_or_return!(self - .blocks - .iter() - .enumerate() - .find(|(_, b)| b.id == *id) - .map(|(i, _)| i)); - - self.blocks[idx].top_line = top_line; - - // Propagate changes to top - for i in (0..idx).rev() { - self.blocks[i].top_line = self.blocks[i + 1].top_line - self.blocks[i].height; - } - self.top_line = self.blocks.front().expect("blocks nonempty").top_line; - - // Propagate changes to bottom - for i in (idx + 1)..self.blocks.len() { - self.blocks[i].top_line = self.blocks[i - 1].top_line + self.blocks[i - 1].height; - } - let bottom = self.blocks.back().expect("blocks nonempty"); - self.bottom_line = bottom.top_line + bottom.height - 1; - } -} diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs deleted file mode 100644 index 49e1392..0000000 --- a/src/ui/chat/tree.rs +++ /dev/null @@ -1,458 +0,0 @@ -// TODO Focusing on sub-trees - -mod cursor; -mod layout; -mod tree_blocks; -mod widgets; - -use std::collections::HashSet; -use std::fmt; -use std::sync::Arc; - -use async_trait::async_trait; -use parking_lot::FairMutex; -use tokio::sync::Mutex; -use toss::{Frame, Pos, Size, Terminal, WidthDb}; - -use crate::macros::logging_unwrap; -use crate::store::{Msg, MsgStore}; -use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::util; -use crate::ui::widgets::editor::EditorState; -use crate::ui::widgets::Widget; - -use self::cursor::Cursor; - -use super::{ChatMsg, Reaction}; - -/////////// -// State // -/////////// - -enum Correction { - MakeCursorVisible, - MoveCursorToVisibleArea, - CenterCursor, -} - -struct InnerTreeViewState> { - store: S, - - last_cursor: Cursor, - last_cursor_line: i32, - last_visible_msgs: Vec, - - cursor: Cursor, - editor: EditorState, - - /// Scroll the view on the next render. Positive values scroll up and - /// negative values scroll down. - scroll: i32, - correction: Option, - - folded: HashSet, -} - -impl> InnerTreeViewState { - fn new(store: S) -> Self { - Self { - store, - last_cursor: Cursor::Bottom, - last_cursor_line: 0, - last_visible_msgs: vec![], - cursor: Cursor::Bottom, - editor: EditorState::new(), - scroll: 0, - correction: None, - folded: HashSet::new(), - } - } - - pub fn list_movement_key_bindings(&self, bindings: &mut KeyBindingsList) { - bindings.binding("j/k, ↓/↑", "move cursor up/down"); - bindings.binding("J/K, ctrl+↓/↑", "move cursor to prev/next sibling"); - bindings.binding("p/P", "move cursor to parent/root"); - bindings.binding("h/l, ←/→", "move cursor chronologically"); - bindings.binding("H/L, ctrl+←/→", "move cursor to prev/next unseen message"); - bindings.binding("g, home", "move cursor to top"); - bindings.binding("G, end", "move cursor to bottom"); - bindings.binding("ctrl+y/e", "scroll up/down a line"); - bindings.binding("ctrl+u/d", "scroll up/down half a screen"); - bindings.binding("ctrl+b/f, page up/down", "scroll up/down one screen"); - bindings.binding("z", "center cursor on screen"); - // TODO Bindings inspired by vim's ()/[]/{} bindings? - } - - async fn handle_movement_input_event( - &mut self, - frame: &mut Frame, - event: &InputEvent, - ) -> Result { - let chat_height = frame.size().height - 3; - - match event { - key!('k') | key!(Up) => self.move_cursor_up().await?, - key!('j') | key!(Down) => self.move_cursor_down().await?, - key!('K') | key!(Ctrl + Up) => self.move_cursor_up_sibling().await?, - key!('J') | key!(Ctrl + Down) => self.move_cursor_down_sibling().await?, - key!('p') => self.move_cursor_to_parent().await?, - key!('P') => self.move_cursor_to_root().await?, - key!('h') | key!(Left) => self.move_cursor_older().await?, - key!('l') | key!(Right) => self.move_cursor_newer().await?, - key!('H') | key!(Ctrl + Left) => self.move_cursor_older_unseen().await?, - key!('L') | key!(Ctrl + Right) => self.move_cursor_newer_unseen().await?, - key!('g') | key!(Home) => self.move_cursor_to_top().await?, - key!('G') | key!(End) => self.move_cursor_to_bottom().await, - key!(Ctrl + 'y') => self.scroll_up(1), - key!(Ctrl + 'e') => self.scroll_down(1), - key!(Ctrl + 'u') => self.scroll_up((chat_height / 2).into()), - key!(Ctrl + 'd') => self.scroll_down((chat_height / 2).into()), - key!(Ctrl + 'b') | key!(PageUp) => self.scroll_up(chat_height.saturating_sub(1).into()), - key!(Ctrl + 'f') | key!(PageDown) => { - self.scroll_down(chat_height.saturating_sub(1).into()) - } - key!('z') => self.center_cursor(), - _ => return Ok(false), - } - - Ok(true) - } - - pub fn list_action_key_bindings(&self, bindings: &mut KeyBindingsList) { - bindings.binding("space", "fold current message's subtree"); - bindings.binding("s", "toggle current message's seen status"); - bindings.binding("S", "mark all visible messages as seen"); - bindings.binding("ctrl+s", "mark all older messages as seen"); - } - - async fn handle_action_input_event( - &mut self, - event: &InputEvent, - id: Option<&M::Id>, - ) -> Result { - match event { - key!(' ') => { - if let Some(id) = id { - if !self.folded.remove(id) { - self.folded.insert(id.clone()); - } - return Ok(true); - } - } - key!('s') => { - if let Some(id) = id { - if let Some(msg) = self.store.tree(id).await?.msg(id) { - self.store.set_seen(id, !msg.seen()).await?; - } - return Ok(true); - } - } - key!('S') => { - for id in &self.last_visible_msgs { - self.store.set_seen(id, true).await?; - } - return Ok(true); - } - key!(Ctrl + 's') => { - if let Some(id) = id { - self.store.set_older_seen(id, true).await?; - } else { - self.store - .set_older_seen(&M::last_possible_id(), true) - .await?; - } - return Ok(true); - } - _ => {} - } - Ok(false) - } - - pub fn list_edit_initiating_key_bindings(&self, bindings: &mut KeyBindingsList) { - bindings.binding("r", "reply to message (inline if possible, else directly)"); - bindings.binding("R", "reply to message (opposite of R)"); - bindings.binding("t", "start a new thread"); - } - - async fn handle_edit_initiating_input_event( - &mut self, - event: &InputEvent, - id: Option, - ) -> Result { - match event { - key!('r') => { - if let Some(parent) = self.parent_for_normal_reply().await? { - self.cursor = Cursor::editor(id, parent); - self.correction = Some(Correction::MakeCursorVisible); - } - } - key!('R') => { - if let Some(parent) = self.parent_for_alternate_reply().await? { - self.cursor = Cursor::editor(id, parent); - self.correction = Some(Correction::MakeCursorVisible); - } - } - key!('t') | key!('T') => { - self.cursor = Cursor::editor(id, None); - self.correction = Some(Correction::MakeCursorVisible); - } - _ => return Ok(false), - } - - Ok(true) - } - - pub fn list_normal_key_bindings(&self, bindings: &mut KeyBindingsList, can_compose: bool) { - self.list_movement_key_bindings(bindings); - bindings.empty(); - self.list_action_key_bindings(bindings); - if can_compose { - bindings.empty(); - self.list_edit_initiating_key_bindings(bindings); - } - } - - async fn handle_normal_input_event( - &mut self, - frame: &mut Frame, - event: &InputEvent, - can_compose: bool, - id: Option, - ) -> Result { - #[allow(clippy::if_same_then_else)] - Ok(if self.handle_movement_input_event(frame, event).await? { - true - } else if self.handle_action_input_event(event, id.as_ref()).await? { - true - } else if can_compose { - self.handle_edit_initiating_input_event(event, id).await? - } else { - false - }) - } - - fn list_editor_key_bindings(&self, bindings: &mut KeyBindingsList) { - bindings.binding("esc", "close editor"); - bindings.binding("enter", "send message"); - util::list_editor_key_bindings_allowing_external_editing(bindings, |_| true); - } - - fn handle_editor_input_event( - &mut self, - terminal: &mut Terminal, - crossterm_lock: &Arc>, - event: &InputEvent, - coming_from: Option, - parent: Option, - ) -> Reaction { - // TODO Tab-completion - match event { - key!(Esc) => { - self.cursor = coming_from.map(Cursor::Msg).unwrap_or(Cursor::Bottom); - self.correction = Some(Correction::MakeCursorVisible); - return Reaction::Handled; - } - - key!(Enter) => { - let content = self.editor.text(); - if !content.trim().is_empty() { - self.cursor = Cursor::Pseudo { - coming_from, - parent: parent.clone(), - }; - return Reaction::Composed { parent, content }; - } - } - - _ => { - let handled = util::handle_editor_input_event_allowing_external_editing( - &self.editor, - terminal, - crossterm_lock, - event, - |_| true, - ); - match handled { - Ok(true) => {} - Ok(false) => return Reaction::NotHandled, - Err(e) => return Reaction::ComposeError(e), - } - } - } - - self.correction = Some(Correction::MakeCursorVisible); - Reaction::Handled - } - - pub fn list_key_bindings(&self, bindings: &mut KeyBindingsList, can_compose: bool) { - bindings.heading("Chat"); - match &self.cursor { - Cursor::Bottom | Cursor::Msg(_) => { - self.list_normal_key_bindings(bindings, can_compose); - } - Cursor::Editor { .. } => self.list_editor_key_bindings(bindings), - Cursor::Pseudo { .. } => { - self.list_normal_key_bindings(bindings, false); - } - } - } - - async fn handle_input_event( - &mut self, - terminal: &mut Terminal, - crossterm_lock: &Arc>, - event: &InputEvent, - can_compose: bool, - ) -> Result, S::Error> { - Ok(match &self.cursor { - Cursor::Bottom => { - if self - .handle_normal_input_event(terminal.frame(), event, can_compose, None) - .await? - { - Reaction::Handled - } else { - Reaction::NotHandled - } - } - Cursor::Msg(id) => { - let id = id.clone(); - if self - .handle_normal_input_event(terminal.frame(), event, can_compose, Some(id)) - .await? - { - Reaction::Handled - } else { - Reaction::NotHandled - } - } - Cursor::Editor { - coming_from, - parent, - } => self.handle_editor_input_event( - terminal, - crossterm_lock, - event, - coming_from.clone(), - parent.clone(), - ), - Cursor::Pseudo { .. } => { - if self - .handle_movement_input_event(terminal.frame(), event) - .await? - { - Reaction::Handled - } else { - Reaction::NotHandled - } - } - }) - } - - fn cursor(&self) -> Option { - match &self.cursor { - Cursor::Msg(id) => Some(id.clone()), - Cursor::Bottom | Cursor::Editor { .. } | Cursor::Pseudo { .. } => None, - } - } - - fn sent(&mut self, id: Option) { - if let Cursor::Pseudo { coming_from, .. } = &self.cursor { - if let Some(id) = id { - self.last_cursor = Cursor::Msg(id.clone()); - self.cursor = Cursor::Msg(id); - self.editor.clear(); - } else { - self.cursor = match coming_from { - Some(id) => Cursor::Msg(id.clone()), - None => Cursor::Bottom, - }; - }; - } - } -} - -pub struct TreeViewState>(Arc>>); - -impl> TreeViewState { - pub fn new(store: S) -> Self { - Self(Arc::new(Mutex::new(InnerTreeViewState::new(store)))) - } - - pub fn widget(&self, nick: String, focused: bool) -> TreeView { - TreeView { - inner: self.0.clone(), - nick, - focused, - } - } - - pub async fn list_key_bindings(&self, bindings: &mut KeyBindingsList, can_compose: bool) { - self.0.lock().await.list_key_bindings(bindings, can_compose); - } - - pub async fn handle_input_event( - &mut self, - terminal: &mut Terminal, - crossterm_lock: &Arc>, - event: &InputEvent, - can_compose: bool, - ) -> Result, S::Error> { - self.0 - .lock() - .await - .handle_input_event(terminal, crossterm_lock, event, can_compose) - .await - } - - pub async fn cursor(&self) -> Option { - self.0.lock().await.cursor() - } - - pub async fn sent(&mut self, id: Option) { - self.0.lock().await.sent(id) - } -} - -//////////// -// Widget // -//////////// - -pub struct TreeView> { - inner: Arc>>, - nick: String, - focused: bool, -} - -#[async_trait] -impl Widget for TreeView -where - M: Msg + ChatMsg + Send + Sync, - M::Id: Send + Sync, - S: MsgStore + Send + Sync, - S::Error: fmt::Display, -{ - async fn size( - &self, - _widthdb: &mut WidthDb, - _max_width: Option, - _max_height: Option, - ) -> Size { - Size::ZERO - } - - async fn render(self: Box, frame: &mut Frame) { - let mut guard = self.inner.lock().await; - let blocks = logging_unwrap!(guard.relayout(self.nick, self.focused, frame).await); - - let size = frame.size(); - for block in blocks.into_blocks().blocks { - frame.push( - Pos::new(0, block.top_line), - Size::new(size.width, block.height as u16), - ); - block.widget.render(frame).await; - frame.pop(); - } - } -} diff --git a/src/ui/chat/tree/cursor.rs b/src/ui/chat/tree/cursor.rs deleted file mode 100644 index e154708..0000000 --- a/src/ui/chat/tree/cursor.rs +++ /dev/null @@ -1,498 +0,0 @@ -//! Moving the cursor around. - -use std::collections::HashSet; - -use crate::store::{Msg, MsgStore, Tree}; - -use super::{Correction, InnerTreeViewState}; - -#[derive(Debug, Clone, Copy)] -pub enum Cursor { - Bottom, - Msg(I), - Editor { - coming_from: Option, - parent: Option, - }, - Pseudo { - coming_from: Option, - parent: Option, - }, -} - -impl Cursor { - pub fn editor(coming_from: Option, parent: Option) -> Self { - Self::Editor { - coming_from, - parent, - } - } -} - -impl Cursor { - pub fn refers_to(&self, id: &I) -> bool { - if let Self::Msg(own_id) = self { - own_id == id - } else { - false - } - } - - pub fn refers_to_last_child_of(&self, id: &I) -> bool { - if let Self::Editor { - parent: Some(parent), - .. - } - | Self::Pseudo { - parent: Some(parent), - .. - } = self - { - parent == id - } else { - false - } - } -} - -impl> InnerTreeViewState { - fn find_parent(tree: &Tree, id: &mut M::Id) -> bool { - if let Some(parent) = tree.parent(id) { - *id = parent; - true - } else { - false - } - } - - fn find_first_child(folded: &HashSet, tree: &Tree, id: &mut M::Id) -> bool { - if folded.contains(id) { - return false; - } - - if let Some(child) = tree.children(id).and_then(|c| c.first()) { - *id = child.clone(); - true - } else { - false - } - } - - fn find_last_child(folded: &HashSet, tree: &Tree, id: &mut M::Id) -> bool { - if folded.contains(id) { - return false; - } - - if let Some(child) = tree.children(id).and_then(|c| c.last()) { - *id = child.clone(); - true - } else { - false - } - } - - /// Move to the previous sibling, or don't move if this is not possible. - /// - /// Always stays at the same level of indentation. - async fn find_prev_sibling( - store: &S, - tree: &mut Tree, - id: &mut M::Id, - ) -> Result { - let moved = if let Some(prev_sibling) = tree.prev_sibling(id) { - *id = prev_sibling; - true - } else if tree.parent(id).is_none() { - // We're at the root of our tree, so we need to move to the root of - // the previous tree. - if let Some(prev_root_id) = store.prev_root_id(tree.root()).await? { - *tree = store.tree(&prev_root_id).await?; - *id = prev_root_id; - true - } else { - false - } - } else { - false - }; - Ok(moved) - } - - /// Move to the next sibling, or don't move if this is not possible. - /// - /// Always stays at the same level of indentation. - async fn find_next_sibling( - store: &S, - tree: &mut Tree, - id: &mut M::Id, - ) -> Result { - let moved = if let Some(next_sibling) = tree.next_sibling(id) { - *id = next_sibling; - true - } else if tree.parent(id).is_none() { - // We're at the root of our tree, so we need to move to the root of - // the next tree. - if let Some(next_root_id) = store.next_root_id(tree.root()).await? { - *tree = store.tree(&next_root_id).await?; - *id = next_root_id; - true - } else { - false - } - } else { - false - }; - Ok(moved) - } - - /// Move to the previous message, or don't move if this is not possible. - async fn find_prev_msg( - store: &S, - folded: &HashSet, - tree: &mut Tree, - id: &mut M::Id, - ) -> Result { - // Move to previous sibling, then to its last child - // If not possible, move to parent - let moved = if Self::find_prev_sibling(store, tree, id).await? { - while Self::find_last_child(folded, tree, id) {} - true - } else { - Self::find_parent(tree, id) - }; - Ok(moved) - } - - /// Move to the next message, or don't move if this is not possible. - async fn find_next_msg( - store: &S, - folded: &HashSet, - tree: &mut Tree, - id: &mut M::Id, - ) -> Result { - if Self::find_first_child(folded, tree, id) { - return Ok(true); - } - - if Self::find_next_sibling(store, tree, id).await? { - return Ok(true); - } - - // Temporary id to avoid modifying the original one if no parent-sibling - // can be found. - let mut tmp_id = id.clone(); - while Self::find_parent(tree, &mut tmp_id) { - if Self::find_next_sibling(store, tree, &mut tmp_id).await? { - *id = tmp_id; - return Ok(true); - } - } - - Ok(false) - } - - pub async fn move_cursor_up(&mut self) -> Result<(), S::Error> { - match &mut self.cursor { - Cursor::Bottom | Cursor::Pseudo { parent: None, .. } => { - if let Some(last_root_id) = self.store.last_root_id().await? { - let tree = self.store.tree(&last_root_id).await?; - let mut id = last_root_id; - while Self::find_last_child(&self.folded, &tree, &mut id) {} - self.cursor = Cursor::Msg(id); - } - } - Cursor::Msg(msg) => { - let path = self.store.path(msg).await?; - let mut tree = self.store.tree(path.first()).await?; - Self::find_prev_msg(&self.store, &self.folded, &mut tree, msg).await?; - } - Cursor::Editor { .. } => {} - Cursor::Pseudo { - parent: Some(parent), - .. - } => { - let tree = self.store.tree(parent).await?; - let mut id = parent.clone(); - while Self::find_last_child(&self.folded, &tree, &mut id) {} - self.cursor = Cursor::Msg(id); - } - } - self.correction = Some(Correction::MakeCursorVisible); - Ok(()) - } - - pub async fn move_cursor_down(&mut self) -> Result<(), S::Error> { - match &mut self.cursor { - Cursor::Msg(msg) => { - let path = self.store.path(msg).await?; - let mut tree = self.store.tree(path.first()).await?; - if !Self::find_next_msg(&self.store, &self.folded, &mut tree, msg).await? { - self.cursor = Cursor::Bottom; - } - } - Cursor::Pseudo { parent: None, .. } => { - self.cursor = Cursor::Bottom; - } - Cursor::Pseudo { - parent: Some(parent), - .. - } => { - let mut tree = self.store.tree(parent).await?; - let mut id = parent.clone(); - while Self::find_last_child(&self.folded, &tree, &mut id) {} - // Now we're at the previous message - if Self::find_next_msg(&self.store, &self.folded, &mut tree, &mut id).await? { - self.cursor = Cursor::Msg(id); - } else { - self.cursor = Cursor::Bottom; - } - } - _ => {} - } - self.correction = Some(Correction::MakeCursorVisible); - Ok(()) - } - - pub async fn move_cursor_up_sibling(&mut self) -> Result<(), S::Error> { - match &mut self.cursor { - Cursor::Bottom | Cursor::Pseudo { parent: None, .. } => { - if let Some(last_root_id) = self.store.last_root_id().await? { - self.cursor = Cursor::Msg(last_root_id); - } - } - Cursor::Msg(msg) => { - let path = self.store.path(msg).await?; - let mut tree = self.store.tree(path.first()).await?; - Self::find_prev_sibling(&self.store, &mut tree, msg).await?; - } - Cursor::Editor { .. } => {} - Cursor::Pseudo { - parent: Some(parent), - .. - } => { - let path = self.store.path(parent).await?; - let tree = self.store.tree(path.first()).await?; - if let Some(children) = tree.children(parent) { - if let Some(last_child) = children.last() { - self.cursor = Cursor::Msg(last_child.clone()); - } - } - } - } - self.correction = Some(Correction::MakeCursorVisible); - Ok(()) - } - - pub async fn move_cursor_down_sibling(&mut self) -> Result<(), S::Error> { - match &mut self.cursor { - Cursor::Msg(msg) => { - let path = self.store.path(msg).await?; - let mut tree = self.store.tree(path.first()).await?; - if !Self::find_next_sibling(&self.store, &mut tree, msg).await? - && tree.parent(msg).is_none() - { - self.cursor = Cursor::Bottom; - } - } - Cursor::Pseudo { parent: None, .. } => { - self.cursor = Cursor::Bottom; - } - _ => {} - } - self.correction = Some(Correction::MakeCursorVisible); - Ok(()) - } - - pub async fn move_cursor_to_parent(&mut self) -> Result<(), S::Error> { - match &mut self.cursor { - Cursor::Pseudo { - parent: Some(parent), - .. - } => self.cursor = Cursor::Msg(parent.clone()), - Cursor::Msg(id) => { - // Could also be done via retrieving the path, but it doesn't - // really matter here - let tree = self.store.tree(id).await?; - Self::find_parent(&tree, id); - } - _ => {} - } - self.correction = Some(Correction::MakeCursorVisible); - Ok(()) - } - - pub async fn move_cursor_to_root(&mut self) -> Result<(), S::Error> { - match &mut self.cursor { - Cursor::Pseudo { - parent: Some(parent), - .. - } => { - let path = self.store.path(parent).await?; - self.cursor = Cursor::Msg(path.first().clone()); - } - Cursor::Msg(msg) => { - let path = self.store.path(msg).await?; - *msg = path.first().clone(); - } - _ => {} - } - self.correction = Some(Correction::MakeCursorVisible); - Ok(()) - } - - pub async fn move_cursor_older(&mut self) -> Result<(), S::Error> { - match &mut self.cursor { - Cursor::Msg(id) => { - if let Some(prev_id) = self.store.older_msg_id(id).await? { - *id = prev_id; - } - } - Cursor::Bottom | Cursor::Pseudo { .. } => { - if let Some(id) = self.store.newest_msg_id().await? { - self.cursor = Cursor::Msg(id); - } - } - _ => {} - } - self.correction = Some(Correction::MakeCursorVisible); - Ok(()) - } - - pub async fn move_cursor_newer(&mut self) -> Result<(), S::Error> { - match &mut self.cursor { - Cursor::Msg(id) => { - if let Some(prev_id) = self.store.newer_msg_id(id).await? { - *id = prev_id; - } else { - self.cursor = Cursor::Bottom; - } - } - Cursor::Pseudo { .. } => { - self.cursor = Cursor::Bottom; - } - _ => {} - } - self.correction = Some(Correction::MakeCursorVisible); - Ok(()) - } - - pub async fn move_cursor_older_unseen(&mut self) -> Result<(), S::Error> { - match &mut self.cursor { - Cursor::Msg(id) => { - if let Some(prev_id) = self.store.older_unseen_msg_id(id).await? { - *id = prev_id; - } - } - Cursor::Bottom | Cursor::Pseudo { .. } => { - if let Some(id) = self.store.newest_unseen_msg_id().await? { - self.cursor = Cursor::Msg(id); - } - } - _ => {} - } - self.correction = Some(Correction::MakeCursorVisible); - Ok(()) - } - - pub async fn move_cursor_newer_unseen(&mut self) -> Result<(), S::Error> { - match &mut self.cursor { - Cursor::Msg(id) => { - if let Some(prev_id) = self.store.newer_unseen_msg_id(id).await? { - *id = prev_id; - } else { - self.cursor = Cursor::Bottom; - } - } - Cursor::Pseudo { .. } => { - self.cursor = Cursor::Bottom; - } - _ => {} - } - self.correction = Some(Correction::MakeCursorVisible); - Ok(()) - } - - pub async fn move_cursor_to_top(&mut self) -> Result<(), S::Error> { - if let Some(first_root_id) = self.store.first_root_id().await? { - self.cursor = Cursor::Msg(first_root_id); - self.correction = Some(Correction::MakeCursorVisible); - } - Ok(()) - } - - pub async fn move_cursor_to_bottom(&mut self) { - self.cursor = Cursor::Bottom; - // Not really necessary; only here for consistency with other methods - self.correction = Some(Correction::MakeCursorVisible); - } - - pub fn scroll_up(&mut self, amount: i32) { - self.scroll += amount; - self.correction = Some(Correction::MoveCursorToVisibleArea); - } - - pub fn scroll_down(&mut self, amount: i32) { - self.scroll -= amount; - self.correction = Some(Correction::MoveCursorToVisibleArea); - } - - pub fn center_cursor(&mut self) { - self.correction = Some(Correction::CenterCursor); - } - - /// The outer `Option` shows whether a parent exists or not. The inner - /// `Option` shows if that parent has an id. - pub async fn parent_for_normal_reply(&self) -> Result>, S::Error> { - Ok(match &self.cursor { - Cursor::Bottom => Some(None), - Cursor::Msg(id) => { - let path = self.store.path(id).await?; - let tree = self.store.tree(path.first()).await?; - - Some(Some(if tree.next_sibling(id).is_some() { - // A reply to a message that has further siblings should be a - // direct reply. An indirect reply might end up a lot further - // down in the current conversation. - id.clone() - } else if let Some(parent) = tree.parent(id) { - // A reply to a message without younger siblings should be - // an indirect reply so as not to create unnecessarily deep - // threads. In the case that our message has children, this - // might get a bit confusing. I'm not sure yet how well this - // "smart" reply actually works in practice. - parent - } else { - // When replying to a top-level message, it makes sense to avoid - // creating unnecessary new threads. - id.clone() - })) - } - _ => None, - }) - } - - /// The outer `Option` shows whether a parent exists or not. The inner - /// `Option` shows if that parent has an id. - pub async fn parent_for_alternate_reply(&self) -> Result>, S::Error> { - Ok(match &self.cursor { - Cursor::Bottom => Some(None), - Cursor::Msg(id) => { - let path = self.store.path(id).await?; - let tree = self.store.tree(path.first()).await?; - - Some(Some(if tree.next_sibling(id).is_none() { - // The opposite of replying normally - id.clone() - } else if let Some(parent) = tree.parent(id) { - // The opposite of replying normally - parent - } else { - // The same as replying normally, still to avoid creating - // unnecessary new threads - id.clone() - })) - } - _ => None, - }) - } -} diff --git a/src/ui/chat/tree/layout.rs b/src/ui/chat/tree/layout.rs deleted file mode 100644 index df5fa4a..0000000 --- a/src/ui/chat/tree/layout.rs +++ /dev/null @@ -1,612 +0,0 @@ -use async_recursion::async_recursion; -use toss::Frame; - -use crate::store::{Msg, MsgStore, Path, Tree}; -use crate::ui::chat::blocks::Block; -use crate::ui::widgets::empty::Empty; -use crate::ui::ChatMsg; - -use super::tree_blocks::{BlockId, Root, TreeBlocks}; -use super::{widgets, Correction, Cursor, InnerTreeViewState}; - -const SCROLLOFF: i32 = 2; -const MIN_CONTENT_HEIGHT: i32 = 10; - -fn scrolloff(height: i32) -> i32 { - let scrolloff = (height - MIN_CONTENT_HEIGHT).max(0) / 2; - scrolloff.min(SCROLLOFF) -} - -struct Context { - nick: String, - focused: bool, -} - -impl InnerTreeViewState -where - M: Msg + ChatMsg + Send + Sync, - M::Id: Send + Sync, - S: MsgStore + Send + Sync, -{ - async fn cursor_path(&self, cursor: &Cursor) -> Result, S::Error> { - Ok(match cursor { - Cursor::Msg(id) => self.store.path(id).await?, - Cursor::Bottom - | Cursor::Editor { parent: None, .. } - | Cursor::Pseudo { parent: None, .. } => Path::new(vec![M::last_possible_id()]), - Cursor::Editor { - parent: Some(parent), - .. - } - | Cursor::Pseudo { - parent: Some(parent), - .. - } => { - let mut path = self.store.path(parent).await?; - path.push(M::last_possible_id()); - path - } - }) - } - - fn make_path_visible(&mut self, path: &Path) { - for segment in path.parent_segments() { - self.folded.remove(segment); - } - } - - fn cursor_line(&self, blocks: &TreeBlocks) -> i32 { - if let Cursor::Bottom = self.cursor { - // The value doesn't matter as it will always be ignored. - 0 - } else { - blocks - .blocks() - .find(&BlockId::from_cursor(&self.cursor)) - .expect("no cursor found") - .top_line - } - } - - fn contains_cursor(&self, blocks: &TreeBlocks) -> bool { - blocks - .blocks() - .find(&BlockId::from_cursor(&self.cursor)) - .is_some() - } - - async fn editor_block( - &self, - context: &Context, - frame: &mut Frame, - indent: usize, - ) -> Block> { - let (widget, cursor_row) = - widgets::editor::(frame.widthdb(), indent, &context.nick, &self.editor); - let cursor_row = cursor_row as i32; - Block::new(frame, BlockId::Cursor, widget) - .await - .focus(cursor_row..cursor_row + 1) - } - - async fn pseudo_block( - &self, - context: &Context, - frame: &mut Frame, - indent: usize, - ) -> Block> { - let widget = widgets::pseudo::(indent, &context.nick, &self.editor); - Block::new(frame, BlockId::Cursor, widget).await - } - - #[async_recursion] - async fn layout_subtree( - &self, - context: &Context, - frame: &mut Frame, - tree: &Tree, - indent: usize, - id: &M::Id, - blocks: &mut TreeBlocks, - ) { - // Ghost cursor in front, for positioning according to last cursor line - if self.last_cursor.refers_to(id) { - let block = Block::new(frame, BlockId::LastCursor, Empty::new()).await; - blocks.blocks_mut().push_back(block); - } - - // Last part of message body if message is folded - let folded = self.folded.contains(id); - let folded_info = if folded { - Some(tree.subtree_size(id)).filter(|s| *s > 0) - } else { - None - }; - - // Main message body - let highlighted = context.focused && self.cursor.refers_to(id); - let widget = if let Some(msg) = tree.msg(id) { - widgets::msg(highlighted, indent, msg, folded_info) - } else { - widgets::msg_placeholder(highlighted, indent, folded_info) - }; - let block = Block::new(frame, BlockId::Msg(id.clone()), widget).await; - blocks.blocks_mut().push_back(block); - - // Children, recursively - if !folded { - if let Some(children) = tree.children(id) { - for child in children { - self.layout_subtree(context, frame, tree, indent + 1, child, blocks) - .await; - } - } - } - - // Trailing ghost cursor, for positioning according to last cursor line - if self.last_cursor.refers_to_last_child_of(id) { - let block = Block::new(frame, BlockId::LastCursor, Empty::new()).await; - blocks.blocks_mut().push_back(block); - } - - // Trailing editor or pseudomessage - if self.cursor.refers_to_last_child_of(id) { - match self.cursor { - Cursor::Editor { .. } => blocks - .blocks_mut() - .push_back(self.editor_block(context, frame, indent + 1).await), - Cursor::Pseudo { .. } => blocks - .blocks_mut() - .push_back(self.pseudo_block(context, frame, indent + 1).await), - _ => {} - } - } - } - - async fn layout_tree( - &self, - context: &Context, - frame: &mut Frame, - tree: Tree, - ) -> TreeBlocks { - let root = Root::Tree(tree.root().clone()); - let mut blocks = TreeBlocks::new(root.clone(), root); - self.layout_subtree(context, frame, &tree, 0, tree.root(), &mut blocks) - .await; - blocks - } - - async fn layout_bottom(&self, context: &Context, frame: &mut Frame) -> TreeBlocks { - let mut blocks = TreeBlocks::new(Root::Bottom, Root::Bottom); - - // Ghost cursor, for positioning according to last cursor line - if let Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } = - self.last_cursor - { - let block = Block::new(frame, BlockId::LastCursor, Empty::new()).await; - blocks.blocks_mut().push_back(block); - } - - match self.cursor { - Cursor::Bottom => { - let block = Block::new(frame, BlockId::Cursor, Empty::new()).await; - blocks.blocks_mut().push_back(block); - } - Cursor::Editor { parent: None, .. } => blocks - .blocks_mut() - .push_back(self.editor_block(context, frame, 0).await), - Cursor::Pseudo { parent: None, .. } => blocks - .blocks_mut() - .push_back(self.pseudo_block(context, frame, 0).await), - _ => {} - } - - blocks - } - - async fn expand_to_top( - &self, - context: &Context, - frame: &mut Frame, - blocks: &mut TreeBlocks, - ) -> Result<(), S::Error> { - let top_line = 0; - - while blocks.blocks().top_line > top_line { - let top_root = blocks.top_root(); - let prev_root_id = match top_root { - Root::Bottom => self.store.last_root_id().await?, - Root::Tree(root_id) => self.store.prev_root_id(root_id).await?, - }; - let prev_root_id = match prev_root_id { - Some(id) => id, - None => break, - }; - let prev_tree = self.store.tree(&prev_root_id).await?; - blocks.prepend(self.layout_tree(context, frame, prev_tree).await); - } - - Ok(()) - } - - async fn expand_to_bottom( - &self, - context: &Context, - frame: &mut Frame, - blocks: &mut TreeBlocks, - ) -> Result<(), S::Error> { - let bottom_line = frame.size().height as i32 - 1; - - while blocks.blocks().bottom_line < bottom_line { - let bottom_root = blocks.bottom_root(); - let next_root_id = match bottom_root { - Root::Bottom => break, - Root::Tree(root_id) => self.store.next_root_id(root_id).await?, - }; - if let Some(next_root_id) = next_root_id { - let next_tree = self.store.tree(&next_root_id).await?; - blocks.append(self.layout_tree(context, frame, next_tree).await); - } else { - blocks.append(self.layout_bottom(context, frame).await); - } - } - - Ok(()) - } - - async fn fill_screen_and_clamp_scrolling( - &self, - context: &Context, - frame: &mut Frame, - blocks: &mut TreeBlocks, - ) -> Result<(), S::Error> { - let top_line = 0; - let bottom_line = frame.size().height as i32 - 1; - - self.expand_to_top(context, frame, blocks).await?; - - if blocks.blocks().top_line > top_line { - blocks.blocks_mut().set_top_line(0); - } - - self.expand_to_bottom(context, frame, blocks).await?; - - if blocks.blocks().bottom_line < bottom_line { - blocks.blocks_mut().set_bottom_line(bottom_line); - } - - self.expand_to_top(context, frame, blocks).await?; - - Ok(()) - } - - async fn layout_last_cursor_seed( - &self, - context: &Context, - frame: &mut Frame, - last_cursor_path: &Path, - ) -> Result, S::Error> { - Ok(match &self.last_cursor { - Cursor::Bottom => { - let mut blocks = self.layout_bottom(context, frame).await; - - let bottom_line = frame.size().height as i32 - 1; - blocks.blocks_mut().set_bottom_line(bottom_line); - - blocks - } - Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } => { - let mut blocks = self.layout_bottom(context, frame).await; - - blocks - .blocks_mut() - .recalculate_offsets(&BlockId::LastCursor, self.last_cursor_line); - - blocks - } - Cursor::Msg(_) - | Cursor::Editor { - parent: Some(_), .. - } - | Cursor::Pseudo { - parent: Some(_), .. - } => { - let root = last_cursor_path.first(); - let tree = self.store.tree(root).await?; - let mut blocks = self.layout_tree(context, frame, tree).await; - - blocks - .blocks_mut() - .recalculate_offsets(&BlockId::LastCursor, self.last_cursor_line); - - blocks - } - }) - } - - async fn layout_cursor_seed( - &self, - context: &Context, - frame: &mut Frame, - last_cursor_path: &Path, - cursor_path: &Path, - ) -> Result, S::Error> { - let bottom_line = frame.size().height as i32 - 1; - - Ok(match &self.cursor { - Cursor::Bottom - | Cursor::Editor { parent: None, .. } - | Cursor::Pseudo { parent: None, .. } => { - let mut blocks = self.layout_bottom(context, frame).await; - - blocks.blocks_mut().set_bottom_line(bottom_line); - - blocks - } - Cursor::Msg(_) - | Cursor::Editor { - parent: Some(_), .. - } - | Cursor::Pseudo { - parent: Some(_), .. - } => { - let root = cursor_path.first(); - let tree = self.store.tree(root).await?; - let mut blocks = self.layout_tree(context, frame, tree).await; - - let cursor_above_last = cursor_path < last_cursor_path; - let cursor_line = if cursor_above_last { 0 } else { bottom_line }; - blocks - .blocks_mut() - .recalculate_offsets(&BlockId::from_cursor(&self.cursor), cursor_line); - - blocks - } - }) - } - - async fn layout_initial_seed( - &self, - context: &Context, - frame: &mut Frame, - last_cursor_path: &Path, - cursor_path: &Path, - ) -> Result, S::Error> { - if let Cursor::Bottom = self.cursor { - self.layout_cursor_seed(context, frame, last_cursor_path, cursor_path) - .await - } else { - self.layout_last_cursor_seed(context, frame, last_cursor_path) - .await - } - } - - fn scroll_so_cursor_is_visible(&self, frame: &Frame, blocks: &mut TreeBlocks) { - if matches!(self.cursor, Cursor::Bottom) { - return; // Cursor is locked to bottom - } - - let block = blocks - .blocks() - .find(&BlockId::from_cursor(&self.cursor)) - .expect("no cursor found"); - - let height = frame.size().height as i32; - let scrolloff = scrolloff(height); - - let min_line = -block.focus.start + scrolloff; - let max_line = height - block.focus.end - scrolloff; - - // If the message is higher than the available space, the top of the - // message should always be visible. I'm not using top_line.clamp(...) - // because the order of the min and max matters. - let top_line = block.top_line; - #[allow(clippy::manual_clamp)] - let new_top_line = top_line.min(max_line).max(min_line); - if new_top_line != top_line { - blocks.blocks_mut().offset(new_top_line - top_line); - } - } - - fn scroll_so_cursor_is_centered(&self, frame: &Frame, blocks: &mut TreeBlocks) { - if matches!(self.cursor, Cursor::Bottom) { - return; // Cursor is locked to bottom - } - - let block = blocks - .blocks() - .find(&BlockId::from_cursor(&self.cursor)) - .expect("no cursor found"); - - let height = frame.size().height as i32; - let scrolloff = scrolloff(height); - - let min_line = -block.focus.start + scrolloff; - let max_line = height - block.focus.end - scrolloff; - - // If the message is higher than the available space, the top of the - // message should always be visible. I'm not using top_line.clamp(...) - // because the order of the min and max matters. - let top_line = block.top_line; - let new_top_line = (height - block.height) / 2; - #[allow(clippy::manual_clamp)] - let new_top_line = new_top_line.min(max_line).max(min_line); - if new_top_line != top_line { - blocks.blocks_mut().offset(new_top_line - top_line); - } - } - - /// Try to obtain a [`Cursor::Msg`] pointing to the block. - fn msg_id(block: &Block>) -> Option { - match &block.id { - BlockId::Msg(id) => Some(id.clone()), - _ => None, - } - } - - fn visible(block: &Block>, first_line: i32, last_line: i32) -> bool { - (first_line + 1 - block.height..=last_line).contains(&block.top_line) - } - - fn move_cursor_so_it_is_visible( - &mut self, - frame: &Frame, - blocks: &TreeBlocks, - ) -> Option { - if !matches!(self.cursor, Cursor::Bottom | Cursor::Msg(_)) { - // In all other cases, there is no need to make the cursor visible - // since scrolling behaves differently enough. - return None; - } - - let height = frame.size().height as i32; - let scrolloff = scrolloff(height); - - let first_line = scrolloff; - let last_line = height - 1 - scrolloff; - - let new_cursor = if matches!(self.cursor, Cursor::Bottom) { - blocks - .blocks() - .iter() - .rev() - .filter(|b| Self::visible(b, first_line, last_line)) - .find_map(Self::msg_id) - } else { - let block = blocks - .blocks() - .find(&BlockId::from_cursor(&self.cursor)) - .expect("no cursor found"); - - if Self::visible(block, first_line, last_line) { - return None; - } else if block.top_line < first_line { - blocks - .blocks() - .iter() - .filter(|b| Self::visible(b, first_line, last_line)) - .find_map(Self::msg_id) - } else { - blocks - .blocks() - .iter() - .rev() - .filter(|b| Self::visible(b, first_line, last_line)) - .find_map(Self::msg_id) - } - }; - - if let Some(id) = new_cursor { - self.cursor = Cursor::Msg(id.clone()); - Some(id) - } else { - None - } - } - - fn visible_msgs(frame: &Frame, blocks: &TreeBlocks) -> Vec { - let height: i32 = frame.size().height.into(); - let first_line = 0; - let last_line = first_line + height - 1; - - let mut result = vec![]; - for block in blocks.blocks().iter() { - if Self::visible(block, first_line, last_line) { - if let BlockId::Msg(id) = &block.id { - result.push(id.clone()); - } - } - } - - result - } - - pub async fn relayout( - &mut self, - nick: String, - focused: bool, - frame: &mut Frame, - ) -> Result, S::Error> { - // The basic idea is this: - // - // First, layout a full screen of blocks around self.last_cursor, using - // self.last_cursor_line for offset positioning. At this point, any - // outstanding scrolling is performed as well. - // - // Then, check if self.cursor is somewhere in these blocks. If it is, we - // now know the position of our own cursor. If it is not, it has jumped - // too far away from self.last_cursor and we'll need to render a new - // full screen of blocks around self.cursor before proceeding, using the - // cursor paths to determine the position of self.cursor on the screen. - // - // Now that we have a more-or-less accurate screen position of - // self.cursor, we can perform the actual cursor logic, i.e. make the - // cursor visible or move it so it is visible. - // - // This entire process is complicated by the different kinds of cursors. - - let context = Context { nick, focused }; - - let last_cursor_path = self.cursor_path(&self.last_cursor).await?; - let cursor_path = self.cursor_path(&self.cursor).await?; - self.make_path_visible(&cursor_path); - - let mut blocks = self - .layout_initial_seed(&context, frame, &last_cursor_path, &cursor_path) - .await?; - blocks.blocks_mut().offset(self.scroll); - self.fill_screen_and_clamp_scrolling(&context, frame, &mut blocks) - .await?; - - if !self.contains_cursor(&blocks) { - blocks = self - .layout_cursor_seed(&context, frame, &last_cursor_path, &cursor_path) - .await?; - self.fill_screen_and_clamp_scrolling(&context, frame, &mut blocks) - .await?; - } - - match self.correction { - Some(Correction::MakeCursorVisible) => { - self.scroll_so_cursor_is_visible(frame, &mut blocks); - self.fill_screen_and_clamp_scrolling(&context, frame, &mut blocks) - .await?; - } - Some(Correction::MoveCursorToVisibleArea) => { - let new_cursor_msg_id = self.move_cursor_so_it_is_visible(frame, &blocks); - if let Some(cursor_msg_id) = new_cursor_msg_id { - // Moving the cursor invalidates our current blocks, so we sadly - // have to either perform an expensive operation or redraw the - // entire thing. I'm choosing the latter for now. - - self.last_cursor = self.cursor.clone(); - self.last_cursor_line = self.cursor_line(&blocks); - self.last_visible_msgs = Self::visible_msgs(frame, &blocks); - self.scroll = 0; - self.correction = None; - - let last_cursor_path = self.store.path(&cursor_msg_id).await?; - blocks = self - .layout_last_cursor_seed(&context, frame, &last_cursor_path) - .await?; - self.fill_screen_and_clamp_scrolling(&context, frame, &mut blocks) - .await?; - } - } - Some(Correction::CenterCursor) => { - self.scroll_so_cursor_is_centered(frame, &mut blocks); - self.fill_screen_and_clamp_scrolling(&context, frame, &mut blocks) - .await?; - } - None => {} - } - - self.last_cursor = self.cursor.clone(); - self.last_cursor_line = self.cursor_line(&blocks); - self.last_visible_msgs = Self::visible_msgs(frame, &blocks); - self.scroll = 0; - self.correction = None; - - Ok(blocks) - } -} diff --git a/src/ui/chat/tree/tree_blocks.rs b/src/ui/chat/tree/tree_blocks.rs deleted file mode 100644 index 69b98ec..0000000 --- a/src/ui/chat/tree/tree_blocks.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::ui::chat::blocks::Blocks; - -use super::Cursor; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum BlockId { - Msg(I), - Cursor, - LastCursor, -} - -impl BlockId { - pub fn from_cursor(cursor: &Cursor) -> Self { - match cursor { - Cursor::Msg(id) => Self::Msg(id.clone()), - _ => Self::Cursor, - } - } -} - -#[derive(Debug, Clone, Copy)] -pub enum Root { - Bottom, - Tree(I), -} - -pub struct TreeBlocks { - blocks: Blocks>, - top_root: Root, - bottom_root: Root, -} - -impl TreeBlocks { - pub fn new(top_root: Root, bottom_root: Root) -> Self { - Self { - blocks: Blocks::new(), - top_root, - bottom_root, - } - } - - pub fn blocks(&self) -> &Blocks> { - &self.blocks - } - - pub fn blocks_mut(&mut self) -> &mut Blocks> { - &mut self.blocks - } - - pub fn into_blocks(self) -> Blocks> { - self.blocks - } - - pub fn top_root(&self) -> &Root { - &self.top_root - } - - pub fn bottom_root(&self) -> &Root { - &self.bottom_root - } - - pub fn prepend(&mut self, other: Self) { - self.blocks.prepend(other.blocks); - self.top_root = other.top_root; - } - - pub fn append(&mut self, other: Self) { - self.blocks.append(other.blocks); - self.bottom_root = other.bottom_root; - } -} diff --git a/src/ui/chat/tree/widgets.rs b/src/ui/chat/tree/widgets.rs deleted file mode 100644 index 919d98b..0000000 --- a/src/ui/chat/tree/widgets.rs +++ /dev/null @@ -1,165 +0,0 @@ -mod indent; -mod seen; -mod time; - -use crossterm::style::Stylize; -use toss::{Style, Styled, WidthDb}; - -use super::super::ChatMsg; -use crate::store::Msg; -use crate::ui::widgets::editor::EditorState; -use crate::ui::widgets::join::{HJoin, Segment}; -use crate::ui::widgets::layer::Layer; -use crate::ui::widgets::padding::Padding; -use crate::ui::widgets::text::Text; -use crate::ui::widgets::BoxedWidget; - -use self::indent::Indent; - -pub const PLACEHOLDER: &str = "[...]"; - -pub fn style_placeholder() -> Style { - Style::new().dark_grey() -} - -fn style_time(highlighted: bool) -> Style { - if highlighted { - Style::new().black().on_white() - } else { - Style::new().grey() - } -} - -fn style_indent(highlighted: bool) -> Style { - if highlighted { - Style::new().black().on_white() - } else { - Style::new().dark_grey() - } -} - -fn style_info() -> Style { - Style::new().italic().dark_grey() -} - -fn style_editor_highlight() -> Style { - Style::new().black().on_cyan() -} - -fn style_pseudo_highlight() -> Style { - Style::new().black().on_yellow() -} - -pub fn msg( - highlighted: bool, - indent: usize, - msg: &M, - folded_info: Option, -) -> BoxedWidget { - let (nick, mut content) = msg.styled(); - - if let Some(amount) = folded_info { - content = content - .then_plain("\n") - .then(format!("[{amount} more]"), style_info()); - } - - HJoin::new(vec![ - Segment::new(seen::widget(msg.seen())), - Segment::new( - Padding::new(time::widget(Some(msg.time()), style_time(highlighted))) - .stretch(true) - .right(1), - ), - Segment::new(Indent::new(indent, style_indent(highlighted))), - Segment::new(Layer::new(vec![ - Padding::new(Indent::new(1, style_indent(false))) - .top(1) - .into(), - Padding::new(Text::new(nick)).right(1).into(), - ])), - // TODO Minimum content width - // TODO Minimizing and maximizing messages - Segment::new(Text::new(content).wrap(true)).priority(1), - ]) - .into() -} - -pub fn msg_placeholder( - highlighted: bool, - indent: usize, - folded_info: Option, -) -> BoxedWidget { - let mut content = Styled::new(PLACEHOLDER, style_placeholder()); - - if let Some(amount) = folded_info { - content = content - .then_plain("\n") - .then(format!("[{amount} more]"), style_info()); - } - - HJoin::new(vec![ - Segment::new(seen::widget(true)), - Segment::new( - Padding::new(time::widget(None, style_time(highlighted))) - .stretch(true) - .right(1), - ), - Segment::new(Indent::new(indent, style_indent(highlighted))), - Segment::new(Text::new(content)), - ]) - .into() -} - -pub fn editor( - widthdb: &mut WidthDb, - indent: usize, - nick: &str, - editor: &EditorState, -) -> (BoxedWidget, usize) { - let (nick, content) = M::edit(nick, &editor.text()); - let editor = editor.widget().highlight(|_| content); - let cursor_row = editor.cursor_row(widthdb); - - let widget = HJoin::new(vec![ - Segment::new(seen::widget(true)), - Segment::new( - Padding::new(time::widget(None, style_editor_highlight())) - .stretch(true) - .right(1), - ), - Segment::new(Indent::new(indent, style_editor_highlight())), - Segment::new(Layer::new(vec![ - Padding::new(Indent::new(1, style_indent(false))) - .top(1) - .into(), - Padding::new(Text::new(nick)).right(1).into(), - ])), - Segment::new(editor).priority(1).expanding(true), - ]) - .into(); - - (widget, cursor_row) -} - -pub fn pseudo(indent: usize, nick: &str, editor: &EditorState) -> BoxedWidget { - let (nick, content) = M::edit(nick, &editor.text()); - - HJoin::new(vec![ - Segment::new(seen::widget(true)), - Segment::new( - Padding::new(time::widget(None, style_pseudo_highlight())) - .stretch(true) - .right(1), - ), - Segment::new(Indent::new(indent, style_pseudo_highlight())), - Segment::new(Layer::new(vec![ - Padding::new(Indent::new(1, style_indent(false))) - .top(1) - .into(), - Padding::new(Text::new(nick)).right(1).into(), - ])), - Segment::new(Text::new(content).wrap(true)).priority(1), - ]) - .into() -} diff --git a/src/ui/chat/tree/widgets/indent.rs b/src/ui/chat/tree/widgets/indent.rs deleted file mode 100644 index a226f93..0000000 --- a/src/ui/chat/tree/widgets/indent.rs +++ /dev/null @@ -1,41 +0,0 @@ -use async_trait::async_trait; -use toss::{Frame, Pos, Size, Style, WidthDb}; - -use crate::ui::widgets::Widget; - -pub const INDENT: &str = "│ "; -pub const INDENT_WIDTH: usize = 2; - -pub struct Indent { - level: usize, - style: Style, -} - -impl Indent { - pub fn new(level: usize, style: Style) -> Self { - Self { level, style } - } -} - -#[async_trait] -impl Widget for Indent { - async fn size( - &self, - _widthdb: &mut WidthDb, - _max_width: Option, - _max_height: Option, - ) -> Size { - Size::new((INDENT_WIDTH * self.level) as u16, 0) - } - - async fn render(self: Box, frame: &mut Frame) { - let size = frame.size(); - - for y in 0..size.height { - frame.write( - Pos::new(0, y.into()), - (INDENT.repeat(self.level), self.style), - ) - } - } -} diff --git a/src/ui/chat/tree/widgets/seen.rs b/src/ui/chat/tree/widgets/seen.rs deleted file mode 100644 index d53271b..0000000 --- a/src/ui/chat/tree/widgets/seen.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crossterm::style::Stylize; -use toss::Style; - -use crate::ui::widgets::background::Background; -use crate::ui::widgets::empty::Empty; -use crate::ui::widgets::text::Text; -use crate::ui::widgets::BoxedWidget; - -const UNSEEN: &str = "*"; -const WIDTH: u16 = 1; - -fn seen_style() -> Style { - Style::new().black().on_green() -} - -pub fn widget(seen: bool) -> BoxedWidget { - if seen { - Empty::new().width(WIDTH).into() - } else { - let style = seen_style(); - Background::new(Text::new((UNSEEN, style))) - .style(style) - .into() - } -} diff --git a/src/ui/chat/tree/widgets/time.rs b/src/ui/chat/tree/widgets/time.rs deleted file mode 100644 index 0801126..0000000 --- a/src/ui/chat/tree/widgets/time.rs +++ /dev/null @@ -1,25 +0,0 @@ -use time::format_description::FormatItem; -use time::macros::format_description; -use time::OffsetDateTime; -use toss::Style; - -use crate::ui::widgets::background::Background; -use crate::ui::widgets::empty::Empty; -use crate::ui::widgets::text::Text; -use crate::ui::widgets::BoxedWidget; - -const TIME_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day] [hour]:[minute]"); -const TIME_WIDTH: u16 = 16; - -pub fn widget(time: Option, style: Style) -> BoxedWidget { - if let Some(time) = time { - let text = time.format(TIME_FORMAT).expect("could not format time"); - Background::new(Text::new((text, style))) - .style(style) - .into() - } else { - Background::new(Empty::new().width(TIME_WIDTH)) - .style(style) - .into() - } -} diff --git a/src/ui/util.rs b/src/ui/util.rs deleted file mode 100644 index 2ba5241..0000000 --- a/src/ui/util.rs +++ /dev/null @@ -1,166 +0,0 @@ -use std::io; -use std::sync::Arc; - -use parking_lot::FairMutex; -use toss::Terminal; - -use super::input::{key, InputEvent, KeyBindingsList}; -use super::widgets::editor::EditorState; -use super::widgets::list::ListState; - -pub fn prompt( - terminal: &mut Terminal, - crossterm_lock: &Arc>, - initial_text: &str, -) -> io::Result { - let content = { - let _guard = crossterm_lock.lock(); - terminal.suspend().expect("could not suspend"); - let content = edit::edit(initial_text); - terminal.unsuspend().expect("could not unsuspend"); - content - }; - - content -} - -////////// -// List // -////////// - -pub fn list_list_key_bindings(bindings: &mut KeyBindingsList) { - bindings.binding("j/k, ↓/↑", "move cursor up/down"); - bindings.binding("g, home", "move cursor to top"); - bindings.binding("G, end", "move cursor to bottom"); - bindings.binding("ctrl+y/e", "scroll up/down"); -} - -pub fn handle_list_input_event(list: &mut ListState, event: &InputEvent) -> bool { - match event { - key!('k') | key!(Up) => list.move_cursor_up(), - key!('j') | key!(Down) => list.move_cursor_down(), - key!('g') | key!(Home) => list.move_cursor_to_top(), - key!('G') | key!(End) => list.move_cursor_to_bottom(), - key!(Ctrl + 'y') => list.scroll_up(1), - key!(Ctrl + 'e') => list.scroll_down(1), - _ => return false, - } - - true -} - -//////////// -// Editor // -//////////// - -fn list_editor_editing_key_bindings( - bindings: &mut KeyBindingsList, - char_filter: impl Fn(char) -> bool, -) { - if char_filter('\n') { - bindings.binding("enter+", "insert newline"); - } - - bindings.binding("ctrl+h, backspace", "delete before cursor"); - bindings.binding("ctrl+d, delete", "delete after cursor"); - bindings.binding("ctrl+l", "clear editor contents"); -} - -fn list_editor_cursor_movement_key_bindings(bindings: &mut KeyBindingsList) { - bindings.binding("ctrl+b, ←", "move cursor left"); - bindings.binding("ctrl+f, →", "move cursor right"); - bindings.binding("alt+b, ctrl+←", "move cursor left a word"); - bindings.binding("alt+f, ctrl+→", "move cursor right a word"); - bindings.binding("ctrl+a, home", "move cursor to start of line"); - bindings.binding("ctrl+e, end", "move cursor to end of line"); - bindings.binding("↑/↓", "move cursor up/down"); -} - -pub fn list_editor_key_bindings( - bindings: &mut KeyBindingsList, - char_filter: impl Fn(char) -> bool, -) { - list_editor_editing_key_bindings(bindings, char_filter); - bindings.empty(); - list_editor_cursor_movement_key_bindings(bindings); -} - -pub fn handle_editor_input_event( - editor: &EditorState, - terminal: &mut Terminal, - event: &InputEvent, - char_filter: impl Fn(char) -> bool, -) -> bool { - match event { - // Enter with *any* modifier pressed - if ctrl and shift don't - // work, maybe alt does - key!(Enter) => return false, - InputEvent::Key(crate::ui::input::KeyEvent { - code: crossterm::event::KeyCode::Enter, - .. - }) if char_filter('\n') => editor.insert_char(terminal.widthdb(), '\n'), - - // Editing - key!(Char ch) if char_filter(*ch) => editor.insert_char(terminal.widthdb(), *ch), - key!(Paste str) => { - // It seems that when pasting, '\n' are converted into '\r' for some - // reason. I don't really know why, or at what point this happens. - // Vim converts any '\r' pasted via the terminal into '\n', so I - // decided to mirror that behaviour. - let str = str.replace('\r', "\n"); - if str.chars().all(char_filter) { - editor.insert_str(terminal.widthdb(), &str); - } else { - return false; - } - } - key!(Ctrl + 'h') | key!(Backspace) => editor.backspace(terminal.widthdb()), - key!(Ctrl + 'd') | key!(Delete) => editor.delete(), - key!(Ctrl + 'l') => editor.clear(), - // TODO Key bindings to delete words - - // Cursor movement - key!(Ctrl + 'b') | key!(Left) => editor.move_cursor_left(terminal.widthdb()), - key!(Ctrl + 'f') | key!(Right) => editor.move_cursor_right(terminal.widthdb()), - key!(Alt + 'b') | key!(Ctrl + Left) => editor.move_cursor_left_a_word(terminal.widthdb()), - key!(Alt + 'f') | key!(Ctrl + Right) => editor.move_cursor_right_a_word(terminal.widthdb()), - key!(Ctrl + 'a') | key!(Home) => editor.move_cursor_to_start_of_line(terminal.widthdb()), - key!(Ctrl + 'e') | key!(End) => editor.move_cursor_to_end_of_line(terminal.widthdb()), - key!(Up) => editor.move_cursor_up(terminal.widthdb()), - key!(Down) => editor.move_cursor_down(terminal.widthdb()), - - _ => return false, - } - - true -} - -pub fn list_editor_key_bindings_allowing_external_editing( - bindings: &mut KeyBindingsList, - char_filter: impl Fn(char) -> bool, -) { - list_editor_editing_key_bindings(bindings, char_filter); - bindings.binding("ctrl+x", "edit in external editor"); - bindings.empty(); - list_editor_cursor_movement_key_bindings(bindings); -} - -pub fn handle_editor_input_event_allowing_external_editing( - editor: &EditorState, - terminal: &mut Terminal, - crossterm_lock: &Arc>, - event: &InputEvent, - char_filter: impl Fn(char) -> bool, -) -> io::Result { - if let key!(Ctrl + 'x') = event { - editor.edit_externally(terminal, crossterm_lock)?; - Ok(true) - } else { - Ok(handle_editor_input_event( - editor, - terminal, - event, - char_filter, - )) - } -} diff --git a/src/ui/widgets.rs b/src/ui/widgets.rs deleted file mode 100644 index 6f137b8..0000000 --- a/src/ui/widgets.rs +++ /dev/null @@ -1,109 +0,0 @@ -// Since the widget module is effectively a library and will probably be moved -// to toss later, warnings about unused functions are mostly inaccurate. -// TODO Restrict this a bit more? -#![allow(dead_code)] - -pub mod background; -pub mod border; -pub mod cursor; -pub mod editor; -pub mod empty; -pub mod float; -pub mod join; -pub mod layer; -pub mod list; -pub mod padding; -pub mod popup; -pub mod resize; -pub mod rules; -pub mod text; - -use async_trait::async_trait; -use toss::{AsyncWidget, Frame, Size, WidthDb}; - -use super::UiError; - -// TODO Add Error type and return Result-s (at least in Widget::render) - -#[async_trait] -pub trait Widget { - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Size; - - async fn render(self: Box, frame: &mut Frame); -} - -pub type BoxedWidget = Box; - -impl From for BoxedWidget { - fn from(widget: W) -> Self { - Box::new(widget) - } -} - -/// Wrapper that implements [`Widget`] for an [`AsyncWidget`]. -pub struct AsyncWidgetWrapper { - inner: I, -} - -impl AsyncWidgetWrapper { - pub fn new(inner: I) -> Self { - Self { inner } - } -} - -#[async_trait] -impl Widget for AsyncWidgetWrapper -where - I: AsyncWidget + Send + Sync, -{ - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Size { - self.inner - .size(widthdb, max_width, max_height) - .await - .unwrap() - } - - async fn render(self: Box, frame: &mut Frame) { - self.inner.draw(frame).await.unwrap(); - } -} - -/// Wrapper that implements [`AsyncWidget`] for a [`Widget`]. -pub struct WidgetWrapper { - inner: BoxedWidget, -} - -impl WidgetWrapper { - pub fn new>(inner: W) -> Self { - Self { - inner: inner.into(), - } - } -} - -#[async_trait] -impl AsyncWidget for WidgetWrapper { - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Result { - Ok(self.inner.size(widthdb, max_width, max_height).await) - } - - async fn draw(self, frame: &mut Frame) -> Result<(), E> { - self.inner.render(frame).await; - Ok(()) - } -} diff --git a/src/ui/widgets/background.rs b/src/ui/widgets/background.rs deleted file mode 100644 index 432e54d..0000000 --- a/src/ui/widgets/background.rs +++ /dev/null @@ -1,46 +0,0 @@ -use async_trait::async_trait; -use toss::{Frame, Pos, Size, Style, WidthDb}; - -use super::{BoxedWidget, Widget}; - -pub struct Background { - inner: BoxedWidget, - style: Style, -} - -impl Background { - pub fn new>(inner: W) -> Self { - Self { - inner: inner.into(), - style: Style::new().opaque(), - } - } - - pub fn style(mut self, style: Style) -> Self { - self.style = style; - self - } -} - -#[async_trait] -impl Widget for Background { - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Size { - self.inner.size(widthdb, max_width, max_height).await - } - - async fn render(self: Box, frame: &mut Frame) { - let size = frame.size(); - for dy in 0..size.height { - for dx in 0..size.width { - frame.write(Pos::new(dx.into(), dy.into()), (" ", self.style)); - } - } - - self.inner.render(frame).await; - } -} diff --git a/src/ui/widgets/border.rs b/src/ui/widgets/border.rs deleted file mode 100644 index bfd76ea..0000000 --- a/src/ui/widgets/border.rs +++ /dev/null @@ -1,65 +0,0 @@ -use async_trait::async_trait; -use toss::{Frame, Pos, Size, Style, WidthDb}; - -use super::{BoxedWidget, Widget}; - -pub struct Border { - inner: BoxedWidget, - style: Style, -} - -impl Border { - pub fn new>(inner: W) -> Self { - Self { - inner: inner.into(), - style: Style::new(), - } - } - - pub fn style(mut self, style: Style) -> Self { - self.style = style; - self - } -} - -#[async_trait] -impl Widget for Border { - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Size { - let max_width = max_width.map(|w| w.saturating_sub(2)); - let max_height = max_height.map(|h| h.saturating_sub(2)); - let size = self.inner.size(widthdb, max_width, max_height).await; - size + Size::new(2, 2) - } - - async fn render(self: Box, frame: &mut Frame) { - let mut size = frame.size(); - size.width = size.width.max(2); - size.height = size.height.max(2); - - let right = size.width as i32 - 1; - let bottom = size.height as i32 - 1; - frame.write(Pos::new(0, 0), ("┌", self.style)); - frame.write(Pos::new(right, 0), ("┐", self.style)); - frame.write(Pos::new(0, bottom), ("└", self.style)); - frame.write(Pos::new(right, bottom), ("┘", self.style)); - - for y in 1..bottom { - frame.write(Pos::new(0, y), ("│", self.style)); - frame.write(Pos::new(right, y), ("│", self.style)); - } - - for x in 1..right { - frame.write(Pos::new(x, 0), ("─", self.style)); - frame.write(Pos::new(x, bottom), ("─", self.style)); - } - - frame.push(Pos::new(1, 1), size - Size::new(2, 2)); - self.inner.render(frame).await; - frame.pop(); - } -} diff --git a/src/ui/widgets/cursor.rs b/src/ui/widgets/cursor.rs deleted file mode 100644 index 22ac5cc..0000000 --- a/src/ui/widgets/cursor.rs +++ /dev/null @@ -1,44 +0,0 @@ -use async_trait::async_trait; -use toss::{Frame, Pos, Size, WidthDb}; - -use super::{BoxedWidget, Widget}; - -pub struct Cursor { - inner: BoxedWidget, - pos: Pos, -} - -impl Cursor { - pub fn new>(inner: W) -> Self { - Self { - inner: inner.into(), - pos: Pos::ZERO, - } - } - - pub fn at(mut self, pos: Pos) -> Self { - self.pos = pos; - self - } - - pub fn at_xy(self, x: i32, y: i32) -> Self { - self.at(Pos::new(x, y)) - } -} - -#[async_trait] -impl Widget for Cursor { - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Size { - self.inner.size(widthdb, max_width, max_height).await - } - - async fn render(self: Box, frame: &mut Frame) { - self.inner.render(frame).await; - frame.set_cursor(Some(self.pos)); - } -} diff --git a/src/ui/widgets/editor.rs b/src/ui/widgets/editor.rs deleted file mode 100644 index e2b8955..0000000 --- a/src/ui/widgets/editor.rs +++ /dev/null @@ -1,566 +0,0 @@ -use std::sync::Arc; -use std::{io, iter}; - -use async_trait::async_trait; -use crossterm::style::Stylize; -use parking_lot::{FairMutex, Mutex}; -use toss::{Frame, Pos, Size, Style, Styled, Terminal, WidthDb}; -use unicode_segmentation::UnicodeSegmentation; - -use crate::ui::util; - -use super::text::Text; -use super::Widget; - -/// Like [`WidthDb::wrap`] but includes a final break index if the text ends -/// with a newline. -fn wrap(widthdb: &mut WidthDb, text: &str, width: usize) -> Vec { - let mut breaks = widthdb.wrap(text, width); - if text.ends_with('\n') { - breaks.push(text.len()) - } - breaks -} - -/////////// -// State // -/////////// - -struct InnerEditorState { - text: String, - - /// Index of the cursor in the text. - /// - /// Must point to a valid grapheme boundary. - idx: usize, - - /// Column of the cursor on the screen just after it was last moved - /// horizontally. - col: usize, - - /// Width of the text when the editor was last rendered. - /// - /// Does not include additional column for cursor. - last_width: u16, -} - -impl InnerEditorState { - fn new(text: String) -> Self { - Self { - idx: text.len(), - col: 0, - last_width: u16::MAX, - text, - } - } - - /////////////////////////////// - // Grapheme helper functions // - /////////////////////////////// - - fn grapheme_boundaries(&self) -> Vec { - self.text - .grapheme_indices(true) - .map(|(i, _)| i) - .chain(iter::once(self.text.len())) - .collect() - } - - /// Ensure the cursor index lies on a grapheme boundary. If it doesn't, it - /// is moved to the next grapheme boundary. - /// - /// Can handle arbitrary cursor index. - fn move_cursor_to_grapheme_boundary(&mut self) { - for i in self.grapheme_boundaries() { - #[allow(clippy::comparison_chain)] - if i == self.idx { - // We're at a valid grapheme boundary already - return; - } else if i > self.idx { - // There was no valid grapheme boundary at our cursor index, so - // we'll take the next one we can get. - self.idx = i; - return; - } - } - - // The cursor was out of bounds, so move it to the last valid index. - self.idx = self.text.len(); - } - - /////////////////////////////// - // Line/col helper functions // - /////////////////////////////// - - /// Like [`Self::grapheme_boundaries`] but for lines. - /// - /// Note that the last line can have a length of 0 if the text ends with a - /// newline. - fn line_boundaries(&self) -> Vec { - let newlines = self - .text - .char_indices() - .filter(|(_, c)| *c == '\n') - .map(|(i, _)| i + 1); // utf-8 encodes '\n' as a single byte - iter::once(0) - .chain(newlines) - .chain(iter::once(self.text.len())) - .collect() - } - - /// Find the cursor's current line. - /// - /// Returns `(line_nr, start_idx, end_idx)`. - fn cursor_line(&self, boundaries: &[usize]) -> (usize, usize, usize) { - let mut result = (0, 0, 0); - for (i, (start, end)) in boundaries.iter().zip(boundaries.iter().skip(1)).enumerate() { - if self.idx >= *start { - result = (i, *start, *end); - } else { - break; - } - } - result - } - - fn cursor_col(&self, widthdb: &mut WidthDb, line_start: usize) -> usize { - widthdb.width(&self.text[line_start..self.idx]) - } - - fn line(&self, line: usize) -> (usize, usize) { - let boundaries = self.line_boundaries(); - boundaries - .iter() - .copied() - .zip(boundaries.iter().copied().skip(1)) - .nth(line) - .expect("line exists") - } - - fn move_cursor_to_line_col(&mut self, widthdb: &mut WidthDb, line: usize, col: usize) { - let (start, end) = self.line(line); - let line = &self.text[start..end]; - - let mut width = 0; - for (gi, g) in line.grapheme_indices(true) { - self.idx = start + gi; - if col > width { - width += widthdb.grapheme_width(g, width) as usize; - } else { - return; - } - } - - if !line.ends_with('\n') { - self.idx = end; - } - } - - fn record_cursor_col(&mut self, widthdb: &mut WidthDb) { - let boundaries = self.line_boundaries(); - let (_, start, _) = self.cursor_line(&boundaries); - self.col = self.cursor_col(widthdb, start); - } - - ///////////// - // Editing // - ///////////// - - fn clear(&mut self) { - self.text = String::new(); - self.idx = 0; - self.col = 0; - } - - fn set_text(&mut self, widthdb: &mut WidthDb, text: String) { - self.text = text; - self.move_cursor_to_grapheme_boundary(); - self.record_cursor_col(widthdb); - } - - /// Insert a character at the current cursor position and move the cursor - /// accordingly. - fn insert_char(&mut self, widthdb: &mut WidthDb, ch: char) { - self.text.insert(self.idx, ch); - self.idx += ch.len_utf8(); - self.record_cursor_col(widthdb); - } - - /// Insert a string at the current cursor position and move the cursor - /// accordingly. - fn insert_str(&mut self, widthdb: &mut WidthDb, str: &str) { - self.text.insert_str(self.idx, str); - self.idx += str.len(); - self.record_cursor_col(widthdb); - } - - /// Delete the grapheme before the cursor position. - fn backspace(&mut self, widthdb: &mut WidthDb) { - let boundaries = self.grapheme_boundaries(); - for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) { - if *end == self.idx { - self.text.replace_range(start..end, ""); - self.idx = *start; - self.record_cursor_col(widthdb); - break; - } - } - } - - /// Delete the grapheme after the cursor position. - fn delete(&mut self) { - let boundaries = self.grapheme_boundaries(); - for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) { - if *start == self.idx { - self.text.replace_range(start..end, ""); - break; - } - } - } - - ///////////////////// - // Cursor movement // - ///////////////////// - - fn move_cursor_left(&mut self, widthdb: &mut WidthDb) { - let boundaries = self.grapheme_boundaries(); - for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) { - if *end == self.idx { - self.idx = *start; - self.record_cursor_col(widthdb); - break; - } - } - } - - fn move_cursor_right(&mut self, widthdb: &mut WidthDb) { - let boundaries = self.grapheme_boundaries(); - for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) { - if *start == self.idx { - self.idx = *end; - self.record_cursor_col(widthdb); - break; - } - } - } - - fn move_cursor_left_a_word(&mut self, widthdb: &mut WidthDb) { - let boundaries = self.grapheme_boundaries(); - let mut encountered_word = false; - for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)).rev() { - if *end == self.idx { - let g = &self.text[*start..*end]; - let whitespace = g.chars().all(|c| c.is_whitespace()); - if encountered_word && whitespace { - break; - } else if !whitespace { - encountered_word = true; - } - self.idx = *start; - } - } - self.record_cursor_col(widthdb); - } - - fn move_cursor_right_a_word(&mut self, widthdb: &mut WidthDb) { - let boundaries = self.grapheme_boundaries(); - let mut encountered_word = false; - for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) { - if *start == self.idx { - let g = &self.text[*start..*end]; - let whitespace = g.chars().all(|c| c.is_whitespace()); - if encountered_word && whitespace { - break; - } else if !whitespace { - encountered_word = true; - } - self.idx = *end; - } - } - self.record_cursor_col(widthdb); - } - - fn move_cursor_to_start_of_line(&mut self, widthdb: &mut WidthDb) { - let boundaries = self.line_boundaries(); - let (line, _, _) = self.cursor_line(&boundaries); - self.move_cursor_to_line_col(widthdb, line, 0); - self.record_cursor_col(widthdb); - } - - fn move_cursor_to_end_of_line(&mut self, widthdb: &mut WidthDb) { - let boundaries = self.line_boundaries(); - let (line, _, _) = self.cursor_line(&boundaries); - self.move_cursor_to_line_col(widthdb, line, usize::MAX); - self.record_cursor_col(widthdb); - } - - fn move_cursor_up(&mut self, widthdb: &mut WidthDb) { - let boundaries = self.line_boundaries(); - let (line, _, _) = self.cursor_line(&boundaries); - if line > 0 { - self.move_cursor_to_line_col(widthdb, line - 1, self.col); - } - } - - fn move_cursor_down(&mut self, widthdb: &mut WidthDb) { - let boundaries = self.line_boundaries(); - - // There's always at least one line, and always at least two line - // boundaries at 0 and self.text.len(). - let amount_of_lines = boundaries.len() - 1; - - let (line, _, _) = self.cursor_line(&boundaries); - if line + 1 < amount_of_lines { - self.move_cursor_to_line_col(widthdb, line + 1, self.col); - } - } -} - -pub struct EditorState(Arc>); - -impl EditorState { - pub fn new() -> Self { - Self(Arc::new(Mutex::new(InnerEditorState::new(String::new())))) - } - - pub fn with_initial_text(text: String) -> Self { - Self(Arc::new(Mutex::new(InnerEditorState::new(text)))) - } - - pub fn widget(&self) -> Editor { - let guard = self.0.lock(); - let text = Styled::new_plain(guard.text.clone()); - let idx = guard.idx; - Editor { - state: self.0.clone(), - text, - idx, - focus: true, - hidden: None, - } - } - - pub fn text(&self) -> String { - self.0.lock().text.clone() - } - - pub fn clear(&self) { - self.0.lock().clear(); - } - - pub fn set_text(&self, widthdb: &mut WidthDb, text: String) { - self.0.lock().set_text(widthdb, text); - } - - pub fn insert_char(&self, widthdb: &mut WidthDb, ch: char) { - self.0.lock().insert_char(widthdb, ch); - } - - pub fn insert_str(&self, widthdb: &mut WidthDb, str: &str) { - self.0.lock().insert_str(widthdb, str); - } - - /// Delete the grapheme before the cursor position. - pub fn backspace(&self, widthdb: &mut WidthDb) { - self.0.lock().backspace(widthdb); - } - - /// Delete the grapheme after the cursor position. - pub fn delete(&self) { - self.0.lock().delete(); - } - - pub fn move_cursor_left(&self, widthdb: &mut WidthDb) { - self.0.lock().move_cursor_left(widthdb); - } - - pub fn move_cursor_right(&self, widthdb: &mut WidthDb) { - self.0.lock().move_cursor_right(widthdb); - } - - pub fn move_cursor_left_a_word(&self, widthdb: &mut WidthDb) { - self.0.lock().move_cursor_left_a_word(widthdb); - } - - pub fn move_cursor_right_a_word(&self, widthdb: &mut WidthDb) { - self.0.lock().move_cursor_right_a_word(widthdb); - } - - pub fn move_cursor_to_start_of_line(&self, widthdb: &mut WidthDb) { - self.0.lock().move_cursor_to_start_of_line(widthdb); - } - - pub fn move_cursor_to_end_of_line(&self, widthdb: &mut WidthDb) { - self.0.lock().move_cursor_to_end_of_line(widthdb); - } - - pub fn move_cursor_up(&self, widthdb: &mut WidthDb) { - self.0.lock().move_cursor_up(widthdb); - } - - pub fn move_cursor_down(&self, widthdb: &mut WidthDb) { - self.0.lock().move_cursor_down(widthdb); - } - - pub fn edit_externally( - &self, - terminal: &mut Terminal, - crossterm_lock: &Arc>, - ) -> io::Result<()> { - let mut guard = self.0.lock(); - let text = util::prompt(terminal, crossterm_lock, &guard.text)?; - - if text.trim().is_empty() { - // The user likely wanted to abort the edit and has deleted the - // entire text (bar whitespace left over by some editors). - return Ok(()); - } - - if let Some(text) = text.strip_suffix('\n') { - // Some editors like vim add a trailing newline that would look out - // of place in cove's editor. To intentionally add a trailing - // newline, simply add two in-editor. - guard.set_text(terminal.widthdb(), text.to_string()); - } else { - guard.set_text(terminal.widthdb(), text); - } - - Ok(()) - } -} - -//////////// -// Widget // -//////////// - -pub struct Editor { - state: Arc>, - text: Styled, - idx: usize, - focus: bool, - hidden: Option>, -} - -impl Editor { - pub fn highlight(mut self, f: F) -> Self - where - F: FnOnce(&str) -> Styled, - { - let new_text = f(self.text.text()); - assert_eq!(self.text.text(), new_text.text()); - self.text = new_text; - self - } - - pub fn focus(mut self, active: bool) -> Self { - self.focus = active; - self - } - - pub fn hidden(self) -> Self { - self.hidden_with_placeholder(("", Style::new().grey().italic())) - } - - pub fn hidden_with_placeholder>(mut self, placeholder: S) -> Self { - self.hidden = Some(Box::new(Text::new(placeholder))); - self - } - - fn wrapped_cursor(cursor_idx: usize, break_indices: &[usize]) -> (usize, usize) { - let mut row = 0; - let mut line_idx = cursor_idx; - - for break_idx in break_indices { - if cursor_idx < *break_idx { - break; - } else { - row += 1; - line_idx = cursor_idx - break_idx; - } - } - - (row, line_idx) - } - - pub fn cursor_row(&self, widthdb: &mut WidthDb) -> usize { - let width = self.state.lock().last_width; - let text_width = (width - 1) as usize; - let indices = wrap(widthdb, self.text.text(), text_width); - let (row, _) = Self::wrapped_cursor(self.idx, &indices); - row - } -} - -#[async_trait] -impl Widget for Editor { - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Size { - if let Some(placeholder) = &self.hidden { - let mut size = placeholder.size(widthdb, max_width, max_height).await; - - // Cursor needs to fit regardless of focus - size.width = size.width.max(1); - size.height = size.height.max(1); - - return size; - } - - let max_width = max_width.map(|w| w as usize).unwrap_or(usize::MAX).max(1); - let max_text_width = max_width - 1; - let indices = wrap(widthdb, self.text.text(), max_text_width); - let lines = self.text.clone().split_at_indices(&indices); - - let min_width = lines - .iter() - .map(|l| widthdb.width(l.text().trim_end())) - .max() - .unwrap_or(0) - + 1; - let min_height = lines.len(); - Size::new(min_width as u16, min_height as u16) - } - - async fn render(self: Box, frame: &mut Frame) { - if let Some(placeholder) = self.hidden { - if !self.text.text().is_empty() { - placeholder.render(frame).await; - } - if self.focus { - frame.set_cursor(Some(Pos::ZERO)); - } - return; - } - - let size = frame.size(); - let widthdb = frame.widthdb(); - - let width = size.width.max(1); - let text_width = (width - 1) as usize; - let indices = wrap(widthdb, self.text.text(), text_width); - let lines = self.text.split_at_indices(&indices); - - // Determine cursor position now while we still have the lines. - let cursor_pos = if self.focus { - let (cursor_row, cursor_line_idx) = Self::wrapped_cursor(self.idx, &indices); - let cursor_col = widthdb.width(lines[cursor_row].text().split_at(cursor_line_idx).0); - let cursor_col = cursor_col.min(text_width); - Some(Pos::new(cursor_col as i32, cursor_row as i32)) - } else { - None - }; - - for (i, line) in lines.into_iter().enumerate() { - frame.write(Pos::new(0, i as i32), line); - } - - if let Some(pos) = cursor_pos { - frame.set_cursor(Some(pos)); - } - - self.state.lock().last_width = width; - } -} diff --git a/src/ui/widgets/empty.rs b/src/ui/widgets/empty.rs deleted file mode 100644 index a5d98ea..0000000 --- a/src/ui/widgets/empty.rs +++ /dev/null @@ -1,44 +0,0 @@ -use async_trait::async_trait; -use toss::{Frame, Size, WidthDb}; - -use super::Widget; - -#[derive(Debug, Default, Clone, Copy)] -pub struct Empty { - size: Size, -} - -impl Empty { - pub fn new() -> Self { - Self { size: Size::ZERO } - } - - pub fn width(mut self, width: u16) -> Self { - self.size.width = width; - self - } - - pub fn height(mut self, height: u16) -> Self { - self.size.height = height; - self - } - - pub fn size(mut self, size: Size) -> Self { - self.size = size; - self - } -} - -#[async_trait] -impl Widget for Empty { - async fn size( - &self, - _widthdb: &mut WidthDb, - _max_width: Option, - _max_height: Option, - ) -> Size { - self.size - } - - async fn render(self: Box, _frame: &mut Frame) {} -} diff --git a/src/ui/widgets/float.rs b/src/ui/widgets/float.rs deleted file mode 100644 index a262cd6..0000000 --- a/src/ui/widgets/float.rs +++ /dev/null @@ -1,73 +0,0 @@ -use async_trait::async_trait; -use toss::{Frame, Pos, Size, WidthDb}; - -use super::{BoxedWidget, Widget}; - -pub struct Float { - inner: BoxedWidget, - horizontal: Option, - vertical: Option, -} - -impl Float { - pub fn new>(inner: W) -> Self { - Self { - inner: inner.into(), - horizontal: None, - vertical: None, - } - } - - pub fn horizontal(mut self, position: f32) -> Self { - self.horizontal = Some(position); - self - } - - pub fn vertical(mut self, position: f32) -> Self { - self.vertical = Some(position); - self - } -} - -#[async_trait] -impl Widget for Float { - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Size { - self.inner.size(widthdb, max_width, max_height).await - } - - async fn render(self: Box, frame: &mut Frame) { - let size = frame.size(); - - let mut inner_size = self - .inner - .size(frame.widthdb(), Some(size.width), Some(size.height)) - .await; - inner_size.width = inner_size.width.min(size.width); - inner_size.height = inner_size.height.min(size.height); - - let mut inner_pos = Pos::ZERO; - - if let Some(horizontal) = self.horizontal { - let available = (size.width - inner_size.width) as f32; - // Biased towards the left if horizontal lands exactly on the - // boundary between two cells - inner_pos.x = (horizontal * available).floor().min(available) as i32; - } - - if let Some(vertical) = self.vertical { - let available = (size.height - inner_size.height) as f32; - // Biased towards the top if vertical lands exactly on the boundary - // between two cells - inner_pos.y = (vertical * available).floor().min(available) as i32; - } - - frame.push(inner_pos, inner_size); - self.inner.render(frame).await; - frame.pop(); - } -} diff --git a/src/ui/widgets/join.rs b/src/ui/widgets/join.rs deleted file mode 100644 index 2aa4551..0000000 --- a/src/ui/widgets/join.rs +++ /dev/null @@ -1,265 +0,0 @@ -use async_trait::async_trait; -use toss::{Frame, Pos, Size, WidthDb}; - -use super::{BoxedWidget, Widget}; - -pub struct Segment { - widget: BoxedWidget, - expanding: bool, - priority: Option, -} - -impl Segment { - pub fn new>(widget: W) -> Self { - Self { - widget: widget.into(), - expanding: false, - priority: None, - } - } - - /// Expand this segment into the remaining space after all segment minimum - /// sizes have been determined. The remaining space is split up evenly. - pub fn expanding(mut self, active: bool) -> Self { - self.expanding = active; - self - } - - /// The size of segments with a priority is calculated in order of - /// increasing priority, using the remaining available space as maximum - /// space for the widget during size calculations. - /// - /// Widgets without priority are processed first without size restrictions. - pub fn priority(mut self, priority: u8) -> Self { - self.priority = Some(priority); - self - } -} - -struct SizedSegment { - idx: usize, - size: Size, - expanding: bool, - priority: Option, -} - -impl SizedSegment { - pub fn new(idx: usize, segment: &Segment) -> Self { - Self { - idx, - size: Size::ZERO, - expanding: segment.expanding, - priority: segment.priority, - } - } -} - -async fn sizes_horiz( - segments: &[Segment], - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, -) -> Vec { - let mut sized = segments - .iter() - .enumerate() - .map(|(i, s)| SizedSegment::new(i, s)) - .collect::>(); - sized.sort_by_key(|s| s.priority); - - let mut total_width = 0; - for s in &mut sized { - let available_width = max_width - .filter(|_| s.priority.is_some()) - .map(|w| w.saturating_sub(total_width)); - s.size = segments[s.idx] - .widget - .size(widthdb, available_width, max_height) - .await; - if let Some(available_width) = available_width { - s.size.width = s.size.width.min(available_width); - } - total_width += s.size.width; - } - - sized -} - -async fn sizes_vert( - segments: &[Segment], - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, -) -> Vec { - let mut sized = segments - .iter() - .enumerate() - .map(|(i, s)| SizedSegment::new(i, s)) - .collect::>(); - sized.sort_by_key(|s| s.priority); - - let mut total_height = 0; - for s in &mut sized { - let available_height = max_height - .filter(|_| s.priority.is_some()) - .map(|w| w.saturating_sub(total_height)); - s.size = segments[s.idx] - .widget - .size(widthdb, max_width, available_height) - .await; - if let Some(available_height) = available_height { - s.size.height = s.size.height.min(available_height); - } - total_height += s.size.height; - } - - sized -} - -fn expand_horiz(segments: &mut [SizedSegment], available_width: u16) { - if !segments.iter().any(|s| s.expanding) { - return; - } - - // Interestingly, rustc needs this type annotation while rust-analyzer - // manages to derive the correct type in an inlay hint. - let current_width = segments.iter().map(|s| s.size.width).sum::(); - if current_width < available_width { - let mut remaining_width = available_width - current_width; - while remaining_width > 0 { - for segment in segments.iter_mut() { - if segment.expanding { - if remaining_width > 0 { - segment.size.width += 1; - remaining_width -= 1; - } else { - break; - } - } - } - } - } -} - -fn expand_vert(segments: &mut [SizedSegment], available_height: u16) { - if !segments.iter().any(|s| s.expanding) { - return; - } - - // Interestingly, rustc needs this type annotation while rust-analyzer - // manages to derive the correct type in an inlay hint. - let current_height = segments.iter().map(|s| s.size.height).sum::(); - if current_height < available_height { - let mut remaining_height = available_height - current_height; - while remaining_height > 0 { - for segment in segments.iter_mut() { - if segment.expanding { - if remaining_height > 0 { - segment.size.height += 1; - remaining_height -= 1; - } else { - break; - } - } - } - } - } -} - -/// Place multiple widgets next to each other horizontally. -pub struct HJoin { - segments: Vec, -} - -impl HJoin { - pub fn new(segments: Vec) -> Self { - Self { segments } - } -} - -#[async_trait] -impl Widget for HJoin { - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Size { - let sizes = sizes_horiz(&self.segments, widthdb, max_width, max_height).await; - let width = sizes.iter().map(|s| s.size.width).sum::(); - let height = sizes.iter().map(|s| s.size.height).max().unwrap_or(0); - Size::new(width, height) - } - - async fn render(self: Box, frame: &mut Frame) { - let size = frame.size(); - - let mut sizes = sizes_horiz( - &self.segments, - frame.widthdb(), - Some(size.width), - Some(size.height), - ) - .await; - expand_horiz(&mut sizes, size.width); - - sizes.sort_by_key(|s| s.idx); - let mut x = 0; - for (segment, sized) in self.segments.into_iter().zip(sizes.into_iter()) { - frame.push(Pos::new(x, 0), Size::new(sized.size.width, size.height)); - segment.widget.render(frame).await; - frame.pop(); - - x += sized.size.width as i32; - } - } -} - -/// Place multiple widgets next to each other vertically. -pub struct VJoin { - segments: Vec, -} - -impl VJoin { - pub fn new(segments: Vec) -> Self { - Self { segments } - } -} - -#[async_trait] -impl Widget for VJoin { - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Size { - let sizes = sizes_vert(&self.segments, widthdb, max_width, max_height).await; - let width = sizes.iter().map(|s| s.size.width).max().unwrap_or(0); - let height = sizes.iter().map(|s| s.size.height).sum::(); - Size::new(width, height) - } - - async fn render(self: Box, frame: &mut Frame) { - let size = frame.size(); - - let mut sizes = sizes_vert( - &self.segments, - frame.widthdb(), - Some(size.width), - Some(size.height), - ) - .await; - expand_vert(&mut sizes, size.height); - - sizes.sort_by_key(|s| s.idx); - let mut y = 0; - for (segment, sized) in self.segments.into_iter().zip(sizes.into_iter()) { - frame.push(Pos::new(0, y), Size::new(size.width, sized.size.height)); - segment.widget.render(frame).await; - frame.pop(); - - y += sized.size.height as i32; - } - } -} diff --git a/src/ui/widgets/layer.rs b/src/ui/widgets/layer.rs deleted file mode 100644 index fe0d983..0000000 --- a/src/ui/widgets/layer.rs +++ /dev/null @@ -1,38 +0,0 @@ -use async_trait::async_trait; -use toss::{Frame, Size, WidthDb}; - -use super::{BoxedWidget, Widget}; - -pub struct Layer { - layers: Vec, -} - -impl Layer { - pub fn new(layers: Vec) -> Self { - Self { layers } - } -} - -#[async_trait] -impl Widget for Layer { - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Size { - let mut max_size = Size::ZERO; - for layer in &self.layers { - let size = layer.size(widthdb, max_width, max_height).await; - max_size.width = max_size.width.max(size.width); - max_size.height = max_size.height.max(size.height); - } - max_size - } - - async fn render(self: Box, frame: &mut Frame) { - for layer in self.layers { - layer.render(frame).await; - } - } -} diff --git a/src/ui/widgets/list.rs b/src/ui/widgets/list.rs deleted file mode 100644 index fc148a0..0000000 --- a/src/ui/widgets/list.rs +++ /dev/null @@ -1,395 +0,0 @@ -use std::sync::Arc; - -use async_trait::async_trait; -use parking_lot::Mutex; -use toss::{Frame, Pos, Size, WidthDb}; - -use super::{BoxedWidget, Widget}; - -/////////// -// State // -/////////// - -#[derive(Debug, Clone)] -struct Cursor { - /// Id of the element the cursor is pointing to. - /// - /// If the rows change (e.g. reorder) but there is still a row with this id, - /// the cursor is moved to this row. - id: Id, - /// Index of the row the cursor is pointing to. - /// - /// If the rows change and there is no longer a row with the cursor's id, - /// the cursor is moved up or down to the next selectable row. This way, it - /// stays close to its previous position. - idx: usize, -} - -impl Cursor { - pub fn new(id: Id, idx: usize) -> Self { - Self { id, idx } - } -} - -#[derive(Debug)] -struct InnerListState { - rows: Vec>, - - /// Offset of the first line visible on the screen. - offset: usize, - - cursor: Option>, - make_cursor_visible: bool, -} - -impl InnerListState { - fn new() -> Self { - Self { - rows: vec![], - offset: 0, - cursor: None, - make_cursor_visible: true, - } - } -} - -impl InnerListState { - fn first_selectable(&self) -> Option> { - self.rows - .iter() - .enumerate() - .find_map(|(i, r)| r.as_ref().map(|c| Cursor::new(c.clone(), i))) - } - - fn last_selectable(&self) -> Option> { - self.rows - .iter() - .enumerate() - .rev() - .find_map(|(i, r)| r.as_ref().map(|c| Cursor::new(c.clone(), i))) - } - - fn selectable_at_or_before_index(&self, i: usize) -> Option> { - self.rows - .iter() - .enumerate() - .take(i + 1) - .rev() - .find_map(|(i, r)| r.as_ref().map(|c| Cursor::new(c.clone(), i))) - } - - fn selectable_at_or_after_index(&self, i: usize) -> Option> { - self.rows - .iter() - .enumerate() - .skip(i) - .find_map(|(i, r)| r.as_ref().map(|c| Cursor::new(c.clone(), i))) - } - - fn selectable_before_index(&self, i: usize) -> Option> { - self.rows - .iter() - .enumerate() - .take(i) - .rev() - .find_map(|(i, r)| r.as_ref().map(|c| Cursor::new(c.clone(), i))) - } - - fn selectable_after_index(&self, i: usize) -> Option> { - self.rows - .iter() - .enumerate() - .skip(i + 1) - .find_map(|(i, r)| r.as_ref().map(|c| Cursor::new(c.clone(), i))) - } - - fn scroll_so_cursor_is_visible(&mut self, height: usize) { - if height == 0 { - // Cursor can't be visible because nothing is visible - return; - } - - if let Some(cursor) = &self.cursor { - // As long as height > 0, min <= max is true - let min = (cursor.idx + 1).saturating_sub(height); - let max = cursor.idx; - self.offset = self.offset.clamp(min, max); - } - } - - fn move_cursor_to_make_it_visible(&mut self, height: usize) { - if let Some(cursor) = &self.cursor { - let min_idx = self.offset; - let max_idx = self.offset.saturating_add(height).saturating_sub(1); - - let new_cursor = if cursor.idx < min_idx { - self.selectable_at_or_after_index(min_idx) - } else if cursor.idx > max_idx { - self.selectable_at_or_before_index(max_idx) - } else { - return; - }; - - if let Some(new_cursor) = new_cursor { - self.cursor = Some(new_cursor); - } - } - } - - fn clamp_scrolling(&mut self, height: usize) { - let min = 0; - let max = self.rows.len().saturating_sub(height); - self.offset = self.offset.clamp(min, max); - } -} - -impl InnerListState { - fn selectable_of_id(&self, id: &Id) -> Option> { - self.rows.iter().enumerate().find_map(|(i, r)| match r { - Some(rid) if rid == id => Some(Cursor::new(id.clone(), i)), - _ => None, - }) - } - - fn fix_cursor(&mut self) { - self.cursor = if let Some(cursor) = &self.cursor { - self.selectable_of_id(&cursor.id) - .or_else(|| self.selectable_at_or_before_index(cursor.idx)) - .or_else(|| self.selectable_at_or_after_index(cursor.idx)) - } else { - self.first_selectable() - } - } - - /// Bring the list into a state consistent with the current rows and height. - fn stabilize(&mut self, rows: &[Row], height: usize) { - self.rows = rows.iter().map(|r| r.id().cloned()).collect(); - - self.fix_cursor(); - if self.make_cursor_visible { - self.scroll_so_cursor_is_visible(height); - self.clamp_scrolling(height); - } else { - self.clamp_scrolling(height); - self.move_cursor_to_make_it_visible(height); - } - self.make_cursor_visible = true; - } -} - -pub struct ListState(Arc>>); - -impl ListState { - pub fn new() -> Self { - Self(Arc::new(Mutex::new(InnerListState::new()))) - } - - pub fn widget(&self) -> List { - List::new(self.0.clone()) - } - - pub fn scroll_up(&mut self, amount: usize) { - let mut guard = self.0.lock(); - guard.offset = guard.offset.saturating_sub(amount); - guard.make_cursor_visible = false; - } - - pub fn scroll_down(&mut self, amount: usize) { - let mut guard = self.0.lock(); - guard.offset = guard.offset.saturating_add(amount); - guard.make_cursor_visible = false; - } -} - -impl ListState { - pub fn cursor(&self) -> Option { - self.0.lock().cursor.as_ref().map(|c| c.id.clone()) - } - - pub fn move_cursor_up(&mut self) { - let mut guard = self.0.lock(); - if let Some(cursor) = &guard.cursor { - if let Some(new_cursor) = guard.selectable_before_index(cursor.idx) { - guard.cursor = Some(new_cursor); - } - } - guard.make_cursor_visible = true; - } - - pub fn move_cursor_down(&mut self) { - let mut guard = self.0.lock(); - if let Some(cursor) = &guard.cursor { - if let Some(new_cursor) = guard.selectable_after_index(cursor.idx) { - guard.cursor = Some(new_cursor); - } - } - guard.make_cursor_visible = true; - } - - pub fn move_cursor_to_top(&mut self) { - let mut guard = self.0.lock(); - if let Some(new_cursor) = guard.first_selectable() { - guard.cursor = Some(new_cursor); - } - guard.make_cursor_visible = true; - } - - pub fn move_cursor_to_bottom(&mut self) { - let mut guard = self.0.lock(); - if let Some(new_cursor) = guard.last_selectable() { - guard.cursor = Some(new_cursor); - } - guard.make_cursor_visible = true; - } -} - -//////////// -// Widget // -//////////// - -enum Row { - Unselectable { - normal: BoxedWidget, - }, - Selectable { - id: Id, - normal: BoxedWidget, - selected: BoxedWidget, - }, -} - -impl Row { - fn id(&self) -> Option<&Id> { - match self { - Self::Unselectable { .. } => None, - Self::Selectable { id, .. } => Some(id), - } - } - - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Size { - match self { - Self::Unselectable { normal } => normal.size(widthdb, max_width, max_height).await, - Self::Selectable { - normal, selected, .. - } => { - let normal_size = normal.size(widthdb, max_width, max_height).await; - let selected_size = selected.size(widthdb, max_width, max_height).await; - Size::new( - normal_size.width.max(selected_size.width), - normal_size.height.max(selected_size.height), - ) - } - } - } -} - -pub struct List { - state: Arc>>, - rows: Vec>, - focus: bool, -} - -impl List { - fn new(state: Arc>>) -> Self { - Self { - state, - rows: vec![], - focus: false, - } - } - - pub fn focus(mut self, focus: bool) -> Self { - self.focus = focus; - self - } - - pub fn is_empty(&self) -> bool { - self.rows.is_empty() - } - - pub fn add_unsel>(&mut self, normal: W) { - self.rows.push(Row::Unselectable { - normal: normal.into(), - }); - } - - pub fn add_sel(&mut self, id: Id, normal: W1, selected: W2) - where - W1: Into, - W2: Into, - { - self.rows.push(Row::Selectable { - id, - normal: normal.into(), - selected: selected.into(), - }); - } -} - -#[async_trait] -impl Widget for List { - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - _max_height: Option, - ) -> Size { - let mut width = 0; - for row in &self.rows { - let size = row.size(widthdb, max_width, Some(1)).await; - width = width.max(size.width); - } - let height = self.rows.len(); - Size::new(width, height as u16) - } - - async fn render(self: Box, frame: &mut Frame) { - let size = frame.size(); - - // Guard acquisition and dropping must be inside its own block or the - // compiler complains that "future created by async block is not - // `Send`", pointing to the function body. - // - // I assume this is because I'm using the parking lot mutex whose guard - // is not Send, and even though I was explicitly dropping it with - // drop(), rustc couldn't figure this out without some help. - let (offset, cursor) = { - let mut guard = self.state.lock(); - guard.stabilize(&self.rows, size.height.into()); - (guard.offset as i32, guard.cursor.clone()) - }; - - let row_size = Size::new(size.width, 1); - for (i, row) in self.rows.into_iter().enumerate() { - let dy = i as i32 - offset; - if dy < 0 || dy >= size.height as i32 { - continue; - } - - frame.push(Pos::new(0, dy), row_size); - match row { - Row::Unselectable { normal } => normal.render(frame).await, - Row::Selectable { - id, - normal, - selected, - } => { - let focusing = self.focus - && if let Some(cursor) = &cursor { - cursor.id == id - } else { - false - }; - let widget = if focusing { selected } else { normal }; - widget.render(frame).await; - } - } - frame.pop(); - } - } -} diff --git a/src/ui/widgets/padding.rs b/src/ui/widgets/padding.rs deleted file mode 100644 index e5b45cd..0000000 --- a/src/ui/widgets/padding.rs +++ /dev/null @@ -1,103 +0,0 @@ -use async_trait::async_trait; -use toss::{Frame, Pos, Size, WidthDb}; - -use super::{BoxedWidget, Widget}; - -pub struct Padding { - inner: BoxedWidget, - stretch: bool, - left: u16, - right: u16, - top: u16, - bottom: u16, -} - -impl Padding { - pub fn new>(inner: W) -> Self { - Self { - inner: inner.into(), - stretch: false, - left: 0, - right: 0, - top: 0, - bottom: 0, - } - } - - /// Whether the inner widget should be stretched to fill the additional - /// space. - pub fn stretch(mut self, active: bool) -> Self { - self.stretch = active; - self - } - - pub fn left(mut self, amount: u16) -> Self { - self.left = amount; - self - } - - pub fn right(mut self, amount: u16) -> Self { - self.right = amount; - self - } - - pub fn horizontal(self, amount: u16) -> Self { - self.left(amount).right(amount) - } - - pub fn top(mut self, amount: u16) -> Self { - self.top = amount; - self - } - - pub fn bottom(mut self, amount: u16) -> Self { - self.bottom = amount; - self - } - - pub fn vertical(self, amount: u16) -> Self { - self.top(amount).bottom(amount) - } - - pub fn all(self, amount: u16) -> Self { - self.horizontal(amount).vertical(amount) - } -} - -#[async_trait] -impl Widget for Padding { - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Size { - let horizontal = self.left + self.right; - let vertical = self.top + self.bottom; - - let max_width = max_width.map(|w| w.saturating_sub(horizontal)); - let max_height = max_height.map(|h| h.saturating_sub(vertical)); - - let size = self.inner.size(widthdb, max_width, max_height).await; - - size + Size::new(horizontal, vertical) - } - - async fn render(self: Box, frame: &mut Frame) { - let size = frame.size(); - - let inner_pos = Pos::new(self.left.into(), self.top.into()); - let inner_size = if self.stretch { - size - } else { - Size::new( - size.width.saturating_sub(self.left + self.right), - size.height.saturating_sub(self.top + self.bottom), - ) - }; - - frame.push(inner_pos, inner_size); - self.inner.render(frame).await; - frame.pop(); - } -} diff --git a/src/ui/widgets/popup.rs b/src/ui/widgets/popup.rs deleted file mode 100644 index b7ec0fe..0000000 --- a/src/ui/widgets/popup.rs +++ /dev/null @@ -1,74 +0,0 @@ -use toss::{Style, Styled}; - -use super::background::Background; -use super::border::Border; -use super::float::Float; -use super::layer::Layer; -use super::padding::Padding; -use super::text::Text; -use super::BoxedWidget; - -pub struct Popup { - inner: BoxedWidget, - inner_padding: bool, - title: Option, - border_style: Style, - bg_style: Style, -} - -impl Popup { - pub fn new>(inner: W) -> Self { - Self { - inner: inner.into(), - inner_padding: true, - title: None, - border_style: Style::new(), - bg_style: Style::new().opaque(), - } - } - - pub fn inner_padding(mut self, active: bool) -> Self { - self.inner_padding = active; - self - } - - pub fn title>(mut self, title: S) -> Self { - self.title = Some(title.into()); - self - } - - pub fn border(mut self, style: Style) -> Self { - self.border_style = style; - self - } - - pub fn background(mut self, style: Style) -> Self { - self.bg_style = style; - self - } - - pub fn build(self) -> BoxedWidget { - let inner = if self.inner_padding { - Padding::new(self.inner).horizontal(1).into() - } else { - self.inner - }; - let window = - Border::new(Background::new(inner).style(self.bg_style)).style(self.border_style); - - let widget: BoxedWidget = if let Some(title) = self.title { - let title = Float::new( - Padding::new( - Background::new(Padding::new(Text::new(title)).horizontal(1)) - .style(self.border_style), - ) - .horizontal(2), - ); - Layer::new(vec![window.into(), title.into()]).into() - } else { - window.into() - }; - - Float::new(widget).vertical(0.5).horizontal(0.5).into() - } -} diff --git a/src/ui/widgets/resize.rs b/src/ui/widgets/resize.rs deleted file mode 100644 index b2edf22..0000000 --- a/src/ui/widgets/resize.rs +++ /dev/null @@ -1,86 +0,0 @@ -use async_trait::async_trait; -use toss::{Frame, Size, WidthDb}; - -use super::{BoxedWidget, Widget}; - -pub struct Resize { - inner: BoxedWidget, - min_width: Option, - min_height: Option, - max_width: Option, - max_height: Option, -} - -impl Resize { - pub fn new>(inner: W) -> Self { - Self { - inner: inner.into(), - min_width: None, - min_height: None, - max_width: None, - max_height: None, - } - } - - pub fn min_width(mut self, amount: u16) -> Self { - self.min_width = Some(amount); - self - } - - pub fn max_width(mut self, amount: u16) -> Self { - self.max_width = Some(amount); - self - } - - pub fn min_height(mut self, amount: u16) -> Self { - self.min_height = Some(amount); - self - } - - pub fn max_height(mut self, amount: u16) -> Self { - self.max_height = Some(amount); - self - } -} - -#[async_trait] -impl Widget for Resize { - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - max_height: Option, - ) -> Size { - let max_width = match (max_width, self.max_width) { - (None, None) => None, - (Some(w), None) => Some(w), - (None, Some(sw)) => Some(sw), - (Some(w), Some(sw)) => Some(w.min(sw)), - }; - - let max_height = match (max_height, self.max_height) { - (None, None) => None, - (Some(h), None) => Some(h), - (None, Some(sh)) => Some(sh), - (Some(h), Some(sh)) => Some(h.min(sh)), - }; - - let size = self.inner.size(widthdb, max_width, max_height).await; - - let width = match self.min_width { - Some(min_width) => size.width.max(min_width), - None => size.width, - }; - - let height = match self.min_height { - Some(min_height) => size.height.max(min_height), - None => size.height, - }; - - Size::new(width, height) - } - - async fn render(self: Box, frame: &mut Frame) { - self.inner.render(frame).await; - } -} diff --git a/src/ui/widgets/rules.rs b/src/ui/widgets/rules.rs deleted file mode 100644 index eaff35f..0000000 --- a/src/ui/widgets/rules.rs +++ /dev/null @@ -1,46 +0,0 @@ -use async_trait::async_trait; -use toss::{Frame, Pos, Size, WidthDb}; - -use super::Widget; - -pub struct HRule; - -#[async_trait] -impl Widget for HRule { - async fn size( - &self, - _widthdb: &mut WidthDb, - _max_width: Option, - _max_height: Option, - ) -> Size { - Size::new(0, 1) - } - - async fn render(self: Box, frame: &mut Frame) { - let size = frame.size(); - for x in 0..size.width as i32 { - frame.write(Pos::new(x, 0), "─"); - } - } -} - -pub struct VRule; - -#[async_trait] -impl Widget for VRule { - async fn size( - &self, - _widthdb: &mut WidthDb, - _max_width: Option, - _max_height: Option, - ) -> Size { - Size::new(1, 0) - } - - async fn render(self: Box, frame: &mut Frame) { - let size = frame.size(); - for y in 0..size.height as i32 { - frame.write(Pos::new(0, y), "│"); - } - } -} diff --git a/src/ui/widgets/text.rs b/src/ui/widgets/text.rs deleted file mode 100644 index 3ce1dbe..0000000 --- a/src/ui/widgets/text.rs +++ /dev/null @@ -1,65 +0,0 @@ -use async_trait::async_trait; -use toss::{Frame, Pos, Size, Styled, WidthDb}; - -use super::Widget; - -pub struct Text { - styled: Styled, - wrap: bool, -} - -impl Text { - pub fn new>(styled: S) -> Self { - Self { - styled: styled.into(), - wrap: false, - } - } - - pub fn wrap(mut self, active: bool) -> Self { - // TODO Re-think and check what behaviour this setting should entail - self.wrap = active; - self - } - - fn wrapped(&self, widthdb: &mut WidthDb, max_width: Option) -> Vec { - let max_width = if self.wrap { - max_width.map(|w| w as usize).unwrap_or(usize::MAX) - } else { - usize::MAX - }; - - let indices = widthdb.wrap(self.styled.text(), max_width); - self.styled.clone().split_at_indices(&indices) - } -} - -#[async_trait] -impl Widget for Text { - async fn size( - &self, - widthdb: &mut WidthDb, - max_width: Option, - _max_height: Option, - ) -> Size { - let lines = self.wrapped(widthdb, max_width); - let min_width = lines - .iter() - .map(|l| widthdb.width(l.text().trim_end())) - .max() - .unwrap_or(0); - let min_height = lines.len(); - Size::new(min_width as u16, min_height as u16) - } - - async fn render(self: Box, frame: &mut Frame) { - let size = frame.size(); - for (i, line) in self - .wrapped(frame.widthdb(), Some(size.width)) - .into_iter() - .enumerate() - { - frame.write(Pos::new(0, i as i32), line); - } - } -} From 21bb87fd4550735a1ddff6474bf668249c092303 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 17 Apr 2023 10:14:01 +0200 Subject: [PATCH 084/266] Rename new modules to old module names --- src/ui.rs | 12 ++++++------ src/ui/{chat2.rs => chat.rs} | 0 src/ui/{chat2 => chat}/blocks.rs | 0 src/ui/{chat2 => chat}/cursor.rs | 0 src/ui/{chat2 => chat}/renderer.rs | 0 src/ui/{chat2 => chat}/tree.rs | 6 +++--- src/ui/{chat2 => chat}/tree/renderer.rs | 6 +++--- src/ui/{chat2 => chat}/tree/scroll.rs | 2 +- src/ui/{chat2 => chat}/tree/widgets.rs | 2 +- src/ui/{chat2 => chat}/widgets.rs | 0 src/ui/euph/account.rs | 10 +++++----- src/ui/euph/auth.rs | 8 ++++---- src/ui/euph/inspect.rs | 2 +- src/ui/euph/links.rs | 2 +- src/ui/euph/nick.rs | 8 ++++---- src/ui/euph/nick_list.rs | 2 +- src/ui/euph/popup.rs | 2 +- src/ui/euph/room.rs | 10 +++++----- src/ui/input.rs | 2 +- src/ui/rooms.rs | 16 ++++++++-------- src/ui/{util2.rs => util.rs} | 2 +- src/ui/{widgets2.rs => widgets.rs} | 0 src/ui/{widgets2 => widgets}/list.rs | 0 src/ui/{widgets2 => widgets}/popup.rs | 0 24 files changed, 46 insertions(+), 46 deletions(-) rename src/ui/{chat2.rs => chat.rs} (100%) rename src/ui/{chat2 => chat}/blocks.rs (100%) rename src/ui/{chat2 => chat}/cursor.rs (100%) rename src/ui/{chat2 => chat}/renderer.rs (100%) rename src/ui/{chat2 => chat}/tree.rs (98%) rename src/ui/{chat2 => chat}/tree/renderer.rs (99%) rename src/ui/{chat2 => chat}/tree/scroll.rs (97%) rename src/ui/{chat2 => chat}/tree/widgets.rs (98%) rename src/ui/{chat2 => chat}/widgets.rs (100%) rename src/ui/{util2.rs => util.rs} (99%) rename src/ui/{widgets2.rs => widgets.rs} (100%) rename src/ui/{widgets2 => widgets}/list.rs (100%) rename src/ui/{widgets2 => widgets}/popup.rs (100%) diff --git a/src/ui.rs b/src/ui.rs index df3371c..437f68c 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,9 +1,9 @@ -mod chat2; +mod chat; mod euph; mod input; mod rooms; -mod util2; -mod widgets2; +mod util; +mod widgets; use std::convert::Infallible; use std::io; @@ -23,11 +23,11 @@ use crate::macros::{logging_unwrap, ok_or_return, some_or_return}; use crate::util::InfallibleExt; use crate::vault::Vault; -pub use self::chat2::ChatMsg; -use self::chat2::ChatState; +pub use self::chat::ChatMsg; +use self::chat::ChatState; use self::input::{key, InputEvent, KeyBindingsList}; use self::rooms::Rooms; -use self::widgets2::ListState; +use self::widgets::ListState; /// Time to spend batch processing events before redrawing the screen. const EVENT_PROCESSING_TIME: Duration = Duration::from_millis(1000 / 15); // 15 fps diff --git a/src/ui/chat2.rs b/src/ui/chat.rs similarity index 100% rename from src/ui/chat2.rs rename to src/ui/chat.rs diff --git a/src/ui/chat2/blocks.rs b/src/ui/chat/blocks.rs similarity index 100% rename from src/ui/chat2/blocks.rs rename to src/ui/chat/blocks.rs diff --git a/src/ui/chat2/cursor.rs b/src/ui/chat/cursor.rs similarity index 100% rename from src/ui/chat2/cursor.rs rename to src/ui/chat/cursor.rs diff --git a/src/ui/chat2/renderer.rs b/src/ui/chat/renderer.rs similarity index 100% rename from src/ui/chat2/renderer.rs rename to src/ui/chat/renderer.rs diff --git a/src/ui/chat2/tree.rs b/src/ui/chat/tree.rs similarity index 98% rename from src/ui/chat2/tree.rs rename to src/ui/chat/tree.rs index dc5a02b..6b09fa2 100644 --- a/src/ui/chat2/tree.rs +++ b/src/ui/chat/tree.rs @@ -16,7 +16,7 @@ use toss::{AsyncWidget, Frame, Pos, Size, Terminal, WidthDb}; use crate::store::{Msg, MsgStore}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::{util2, ChatMsg, UiError}; +use crate::ui::{util, ChatMsg, UiError}; use crate::util::InfallibleExt; use self::renderer::{TreeContext, TreeRenderer}; @@ -255,7 +255,7 @@ impl> TreeViewState { fn list_editor_key_bindings(&self, bindings: &mut KeyBindingsList) { bindings.binding("esc", "close editor"); bindings.binding("enter", "send message"); - util2::list_editor_key_bindings_allowing_external_editing(bindings, |_| true); + util::list_editor_key_bindings_allowing_external_editing(bindings, |_| true); } #[allow(clippy::too_many_arguments)] @@ -289,7 +289,7 @@ impl> TreeViewState { } _ => { - let handled = util2::handle_editor_input_event_allowing_external_editing( + let handled = util::handle_editor_input_event_allowing_external_editing( editor, terminal, crossterm_lock, diff --git a/src/ui/chat2/tree/renderer.rs b/src/ui/chat/tree/renderer.rs similarity index 99% rename from src/ui/chat2/tree/renderer.rs rename to src/ui/chat/tree/renderer.rs index 0f58f4e..99be83a 100644 --- a/src/ui/chat2/tree/renderer.rs +++ b/src/ui/chat/tree/renderer.rs @@ -8,9 +8,9 @@ use toss::widgets::{EditorState, Empty, Predrawn, Resize}; use toss::{AsyncWidget, Size, WidthDb}; use crate::store::{Msg, MsgStore, Tree}; -use crate::ui::chat2::blocks::{Block, Blocks, Range}; -use crate::ui::chat2::cursor::Cursor; -use crate::ui::chat2::renderer::{self, overlaps, Renderer}; +use crate::ui::chat::blocks::{Block, Blocks, Range}; +use crate::ui::chat::cursor::Cursor; +use crate::ui::chat::renderer::{self, overlaps, Renderer}; use crate::ui::ChatMsg; use crate::util::InfallibleExt; diff --git a/src/ui/chat2/tree/scroll.rs b/src/ui/chat/tree/scroll.rs similarity index 97% rename from src/ui/chat2/tree/scroll.rs rename to src/ui/chat/tree/scroll.rs index 40007f1..29b5d1e 100644 --- a/src/ui/chat2/tree/scroll.rs +++ b/src/ui/chat/tree/scroll.rs @@ -2,7 +2,7 @@ use toss::widgets::EditorState; use toss::WidthDb; use crate::store::{Msg, MsgStore}; -use crate::ui::chat2::cursor::Cursor; +use crate::ui::chat::cursor::Cursor; use crate::ui::ChatMsg; use super::renderer::{TreeContext, TreeRenderer}; diff --git a/src/ui/chat2/tree/widgets.rs b/src/ui/chat/tree/widgets.rs similarity index 98% rename from src/ui/chat2/tree/widgets.rs rename to src/ui/chat/tree/widgets.rs index d0ff5f7..5a89b70 100644 --- a/src/ui/chat2/tree/widgets.rs +++ b/src/ui/chat/tree/widgets.rs @@ -5,7 +5,7 @@ use toss::widgets::{BoxedAsync, EditorState, Join2, Join4, Join5, Text}; use toss::{Style, Styled, WidgetExt}; use crate::store::Msg; -use crate::ui::chat2::widgets::{Indent, Seen, Time}; +use crate::ui::chat::widgets::{Indent, Seen, Time}; use crate::ui::ChatMsg; pub const PLACEHOLDER: &str = "[...]"; diff --git a/src/ui/chat2/widgets.rs b/src/ui/chat/widgets.rs similarity index 100% rename from src/ui/chat2/widgets.rs rename to src/ui/chat/widgets.rs diff --git a/src/ui/euph/account.rs b/src/ui/euph/account.rs index 0937ba2..9599cfc 100644 --- a/src/ui/euph/account.rs +++ b/src/ui/euph/account.rs @@ -6,8 +6,8 @@ use toss::{Style, Terminal, WidgetExt}; use crate::euph::{self, Room}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::widgets2::Popup; -use crate::ui::{util2, UiError}; +use crate::ui::widgets::Popup; +use crate::ui::{util, UiError}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Focus { @@ -133,7 +133,7 @@ impl AccountUiState { Focus::Password => bindings.binding("enter", "log in"), } bindings.binding("tab", "switch focus"); - util2::list_editor_key_bindings(bindings, |c| c != '\n'); + util::list_editor_key_bindings(bindings, |c| c != '\n'); } Self::LoggedIn(_) => bindings.binding("L", "log out"), } @@ -166,7 +166,7 @@ impl AccountUiState { return EventResult::Handled; } - if util2::handle_editor_input_event( + if util::handle_editor_input_event( &mut logged_out.email, terminal, event, @@ -188,7 +188,7 @@ impl AccountUiState { return EventResult::Handled; } - if util2::handle_editor_input_event( + if util::handle_editor_input_event( &mut logged_out.password, terminal, event, diff --git a/src/ui/euph/auth.rs b/src/ui/euph/auth.rs index f13cbc4..ca47660 100644 --- a/src/ui/euph/auth.rs +++ b/src/ui/euph/auth.rs @@ -3,8 +3,8 @@ use toss::{Terminal, WidgetExt}; use crate::euph::Room; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::widgets2::Popup; -use crate::ui::{util2, UiError}; +use crate::ui::widgets::Popup; +use crate::ui::{util, UiError}; pub fn new() -> EditorState { EditorState::new() @@ -21,7 +21,7 @@ pub fn widget(editor: &mut EditorState) -> BoxedAsync<'_, UiError> { pub fn list_key_bindings(bindings: &mut KeyBindingsList) { bindings.binding("esc", "abort"); bindings.binding("enter", "authenticate"); - util2::list_editor_key_bindings(bindings, |_| true); + util::list_editor_key_bindings(bindings, |_| true); } pub enum EventResult { @@ -45,7 +45,7 @@ pub fn handle_input_event( EventResult::ResetState } _ => { - if util2::handle_editor_input_event(editor, terminal, event, |_| true) { + if util::handle_editor_input_event(editor, terminal, event, |_| true) { EventResult::Handled } else { EventResult::NotHandled diff --git a/src/ui/euph/inspect.rs b/src/ui/euph/inspect.rs index 505062c..2b0e43f 100644 --- a/src/ui/euph/inspect.rs +++ b/src/ui/euph/inspect.rs @@ -5,7 +5,7 @@ use toss::widgets::{BoxedAsync, Text}; use toss::{Style, Styled, WidgetExt}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::widgets2::Popup; +use crate::ui::widgets::Popup; use crate::ui::UiError; macro_rules! line { diff --git a/src/ui/euph/links.rs b/src/ui/euph/links.rs index 22bc67d..6e279f8 100644 --- a/src/ui/euph/links.rs +++ b/src/ui/euph/links.rs @@ -6,7 +6,7 @@ use toss::widgets::{BoxedAsync, Text}; use toss::{Style, Styled, WidgetExt}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::widgets2::{ListState, Popup}; +use crate::ui::widgets::{ListState, Popup}; use crate::ui::UiError; pub struct LinksState { diff --git a/src/ui/euph/nick.rs b/src/ui/euph/nick.rs index a00e862..20f8576 100644 --- a/src/ui/euph/nick.rs +++ b/src/ui/euph/nick.rs @@ -4,8 +4,8 @@ use toss::{Style, Terminal, WidgetExt}; use crate::euph::{self, Room}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::widgets2::Popup; -use crate::ui::{util2, UiError}; +use crate::ui::widgets::Popup; +use crate::ui::{util, UiError}; pub fn new(joined: Joined) -> EditorState { EditorState::with_initial_text(joined.session.name) @@ -26,7 +26,7 @@ fn nick_char(c: char) -> bool { pub fn list_key_bindings(bindings: &mut KeyBindingsList) { bindings.binding("esc", "abort"); bindings.binding("enter", "set nick"); - util2::list_editor_key_bindings(bindings, nick_char); + util::list_editor_key_bindings(bindings, nick_char); } pub enum EventResult { @@ -50,7 +50,7 @@ pub fn handle_input_event( EventResult::ResetState } _ => { - if util2::handle_editor_input_event(editor, terminal, event, nick_char) { + if util::handle_editor_input_event(editor, terminal, event, nick_char) { EventResult::Handled } else { EventResult::NotHandled diff --git a/src/ui/euph/nick_list.rs b/src/ui/euph/nick_list.rs index 1396822..2d91602 100644 --- a/src/ui/euph/nick_list.rs +++ b/src/ui/euph/nick_list.rs @@ -8,7 +8,7 @@ use toss::widgets::{BoxedAsync, Empty, Text}; use toss::{Style, Styled, WidgetExt}; use crate::euph; -use crate::ui::widgets2::{List, ListState}; +use crate::ui::widgets::{List, ListState}; use crate::ui::UiError; pub fn widget<'a>( diff --git a/src/ui/euph/popup.rs b/src/ui/euph/popup.rs index ebf507c..4f6384c 100644 --- a/src/ui/euph/popup.rs +++ b/src/ui/euph/popup.rs @@ -2,7 +2,7 @@ use crossterm::style::Stylize; use toss::widgets::{BoxedAsync, Text}; use toss::{Style, Styled, WidgetExt}; -use crate::ui::widgets2::Popup; +use crate::ui::widgets::Popup; use crate::ui::UiError; pub enum RoomPopup { diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 1203acb..399433d 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -14,10 +14,10 @@ use toss::{AsyncWidget, Style, Styled, Terminal, WidgetExt}; use crate::config; use crate::euph; use crate::macros::logging_unwrap; -use crate::ui::chat2::{ChatState, Reaction}; +use crate::ui::chat::{ChatState, Reaction}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::widgets2::ListState; -use crate::ui::{util2, UiError, UiEvent}; +use crate::ui::widgets::ListState; +use crate::ui::{util, UiError, UiEvent}; use crate::vault::EuphRoomVault; use super::account::{self, AccountUiState}; @@ -495,13 +495,13 @@ impl EuphRoom { } fn list_nick_list_focus_key_bindings(&self, bindings: &mut KeyBindingsList) { - util2::list_list_key_bindings(bindings); + util::list_list_key_bindings(bindings); bindings.binding("i", "inspect session"); } fn handle_nick_list_focus_input_event(&mut self, event: &InputEvent) -> bool { - if util2::handle_list_input_event(&mut self.nick_list, event) { + if util::handle_list_input_event(&mut self.nick_list, event) { return true; } diff --git a/src/ui/input.rs b/src/ui/input.rs index dd0a2dc..ab3fcf2 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -5,7 +5,7 @@ use crossterm::style::Stylize; use toss::widgets::{BoxedAsync, Empty, Join2, Text}; use toss::{Style, Styled, WidgetExt}; -use super::widgets2::ListState; +use super::widgets::ListState; use super::UiError; #[derive(Debug, Clone)] diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index 348dd5a..e9bdf4b 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -18,8 +18,8 @@ use crate::vault::Vault; use super::euph::room::EuphRoom; use super::input::{key, InputEvent, KeyBindingsList}; -use super::widgets2::{List, ListState, Popup}; -use super::{util2, UiError, UiEvent}; +use super::widgets::{List, ListState, Popup}; +use super::{util, UiError, UiEvent}; enum State { ShowList, @@ -405,7 +405,7 @@ impl Rooms { fn list_showlist_key_bindings(bindings: &mut KeyBindingsList) { bindings.heading("Rooms"); - util2::list_list_key_bindings(bindings); + util::list_list_key_bindings(bindings); bindings.empty(); bindings.binding("enter", "enter selected room"); bindings.binding("c", "connect to selected room"); @@ -421,7 +421,7 @@ impl Rooms { } fn handle_showlist_input_event(&mut self, event: &InputEvent) -> bool { - if util2::handle_list_input_event(&mut self.list, event) { + if util::handle_list_input_event(&mut self.list, event) { return true; } @@ -519,13 +519,13 @@ impl Rooms { bindings.heading("Rooms"); bindings.binding("esc", "abort"); bindings.binding("enter", "connect to room"); - util2::list_editor_key_bindings(bindings, Self::room_char); + util::list_editor_key_bindings(bindings, Self::room_char); } State::Delete(_, _) => { bindings.heading("Rooms"); bindings.binding("esc", "abort"); bindings.binding("enter", "delete room"); - util2::list_editor_key_bindings(bindings, Self::room_char); + util::list_editor_key_bindings(bindings, Self::room_char); } } } @@ -573,7 +573,7 @@ impl Rooms { return true; } _ => { - if util2::handle_editor_input_event(ed, terminal, event, Self::room_char) { + if util::handle_editor_input_event(ed, terminal, event, Self::room_char) { return true; } } @@ -590,7 +590,7 @@ impl Rooms { return true; } _ => { - if util2::handle_editor_input_event(editor, terminal, event, Self::room_char) { + if util::handle_editor_input_event(editor, terminal, event, Self::room_char) { return true; } } diff --git a/src/ui/util2.rs b/src/ui/util.rs similarity index 99% rename from src/ui/util2.rs rename to src/ui/util.rs index 3f2b2d9..d80b704 100644 --- a/src/ui/util2.rs +++ b/src/ui/util.rs @@ -6,7 +6,7 @@ use toss::widgets::EditorState; use toss::Terminal; use super::input::{key, InputEvent, KeyBindingsList}; -use super::widgets2::ListState; +use super::widgets::ListState; pub fn prompt( terminal: &mut Terminal, diff --git a/src/ui/widgets2.rs b/src/ui/widgets.rs similarity index 100% rename from src/ui/widgets2.rs rename to src/ui/widgets.rs diff --git a/src/ui/widgets2/list.rs b/src/ui/widgets/list.rs similarity index 100% rename from src/ui/widgets2/list.rs rename to src/ui/widgets/list.rs diff --git a/src/ui/widgets2/popup.rs b/src/ui/widgets/popup.rs similarity index 100% rename from src/ui/widgets2/popup.rs rename to src/ui/widgets/popup.rs From 3f18b76c7deea08100642c200e9c316c35ee48f4 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 17 Apr 2023 10:25:07 +0200 Subject: [PATCH 085/266] Fix chat scrolling up after sending message --- src/ui/chat.rs | 1 + src/ui/chat/tree.rs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/ui/chat.rs b/src/ui/chat.rs index 24b0185..9e243e2 100644 --- a/src/ui/chat.rs +++ b/src/ui/chat.rs @@ -124,6 +124,7 @@ impl> ChatState { /// A [`Reaction::Composed`] message was sent successfully. pub fn send_successful(&mut self, id: M::Id) { if let Cursor::Pseudo { .. } = &self.cursor { + self.tree.send_successful(&id); self.cursor = Cursor::Msg(id); self.editor.clear(); } diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs index 6b09fa2..246e3c9 100644 --- a/src/ui/chat/tree.rs +++ b/src/ui/chat/tree.rs @@ -405,6 +405,12 @@ impl> TreeViewState { }) } + pub fn send_successful(&mut self, id: &M::Id) { + if let Cursor::Pseudo { .. } = self.last_cursor { + self.last_cursor = Cursor::Msg(id.clone()); + } + } + pub fn widget<'a>( &'a mut self, cursor: &'a mut Cursor, From 07b761e0f982395ac2ebca25de213aef540e5f17 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 17 Apr 2023 11:10:33 +0200 Subject: [PATCH 086/266] Fix list cursor being invisible until first redraw --- src/ui/euph/links.rs | 50 +++++++++++++++------------ src/ui/euph/nick_list.rs | 70 ++++++++++++++++++------------------- src/ui/input.rs | 10 +++--- src/ui/rooms.rs | 31 +++++++++-------- src/ui/widgets/list.rs | 75 +++++++++++++++++++++++----------------- 5 files changed, 129 insertions(+), 107 deletions(-) diff --git a/src/ui/euph/links.rs b/src/ui/euph/links.rs index 6e279f8..a5541e6 100644 --- a/src/ui/euph/links.rs +++ b/src/ui/euph/links.rs @@ -6,7 +6,7 @@ use toss::widgets::{BoxedAsync, Text}; use toss::{Style, Styled, WidgetExt}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; -use crate::ui::widgets::{ListState, Popup}; +use crate::ui::widgets::{ListBuilder, ListState, Popup}; use crate::ui::UiError; pub struct LinksState { @@ -41,36 +41,40 @@ impl LinksState { pub fn widget(&mut self) -> BoxedAsync<'_, UiError> { let style_selected = Style::new().black().on_white(); - let mut list = self.list.widget(); + let mut list_builder = ListBuilder::new(); if self.links.is_empty() { - list.add_unsel(Text::new(("No links found", Style::new().grey().italic()))) + list_builder.add_unsel(Text::new(("No links found", Style::new().grey().italic()))) } for (id, link) in self.links.iter().enumerate() { - #[allow(clippy::collapsible_else_if)] - let text = if list.state().selected() == Some(&id) { - if let Some(number_key) = NUMBER_KEYS.get(id) { - Styled::new(format!("[{number_key}]"), style_selected.bold()) - .then(" ", style_selected) - .then(link, style_selected) - } else { - Styled::new(format!(" {link}"), style_selected) - } + let link = link.clone(); + if let Some(&number_key) = NUMBER_KEYS.get(id) { + list_builder.add_sel(id, move |selected| { + let text = if selected { + Styled::new(format!("[{number_key}]"), style_selected.bold()) + .then(" ", style_selected) + .then(link, style_selected) + } else { + Styled::new(format!("[{number_key}]"), Style::new().dark_grey().bold()) + .then_plain(" ") + .then_plain(link) + }; + Text::new(text) + }); } else { - if let Some(number_key) = NUMBER_KEYS.get(id) { - Styled::new(format!("[{number_key}]"), Style::new().dark_grey().bold()) - .then_plain(" ") - .then_plain(link) - } else { - Styled::new_plain(format!(" {link}")) - } - }; - - list.add_sel(id, Text::new(text)); + list_builder.add_sel(id, move |selected| { + let text = if selected { + Styled::new(format!(" {link}"), style_selected) + } else { + Styled::new_plain(format!(" {link}")) + }; + Text::new(text) + }); + } } - Popup::new(list, "Links").boxed_async() + Popup::new(list_builder.build(&mut self.list), "Links").boxed_async() } fn open_link_by_id(&self, id: usize) -> EventResult { diff --git a/src/ui/euph/nick_list.rs b/src/ui/euph/nick_list.rs index 2d91602..0e13c84 100644 --- a/src/ui/euph/nick_list.rs +++ b/src/ui/euph/nick_list.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use std::iter; use crossterm::style::{Color, Stylize}; @@ -8,7 +7,7 @@ use toss::widgets::{BoxedAsync, Empty, Text}; use toss::{Style, Styled, WidgetExt}; use crate::euph; -use crate::ui::widgets::{List, ListState}; +use crate::ui::widgets::{ListBuilder, ListState}; use crate::ui::UiError; pub fn widget<'a>( @@ -16,9 +15,9 @@ pub fn widget<'a>( joined: &Joined, focused: bool, ) -> BoxedAsync<'a, UiError> { - let mut list = list.widget(); - render_rows(&mut list, joined, focused); - list.boxed_async() + let mut list_builder = ListBuilder::new(); + render_rows(&mut list_builder, joined, focused); + list_builder.build(list).boxed_async() } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -60,7 +59,7 @@ impl HalfSession { } fn render_rows( - list: &mut List<'_, SessionId, BoxedAsync<'static, UiError>>, + list_builder: &mut ListBuilder<'_, SessionId, BoxedAsync<'static, UiError>>, joined: &Joined, focused: bool, ) { @@ -88,14 +87,14 @@ fn render_rows( lurkers.sort_unstable(); nurkers.sort_unstable(); - render_section(list, "People", &people, &joined.session, focused); - render_section(list, "Bots", &bots, &joined.session, focused); - render_section(list, "Lurkers", &lurkers, &joined.session, focused); - render_section(list, "Nurkers", &nurkers, &joined.session, focused); + render_section(list_builder, "People", &people, &joined.session, focused); + render_section(list_builder, "Bots", &bots, &joined.session, focused); + render_section(list_builder, "Lurkers", &lurkers, &joined.session, focused); + render_section(list_builder, "Nurkers", &nurkers, &joined.session, focused); } fn render_section( - list: &mut List<'_, SessionId, BoxedAsync<'static, UiError>>, + list_builder: &mut ListBuilder<'_, SessionId, BoxedAsync<'static, UiError>>, name: &str, sessions: &[HalfSession], own_session: &SessionView, @@ -107,39 +106,40 @@ fn render_section( let heading_style = Style::new().bold(); - if !list.is_empty() { - list.add_unsel(Empty::new().boxed_async()); + if !list_builder.is_empty() { + list_builder.add_unsel(Empty::new().boxed_async()); } let row = Styled::new_plain(" ") .then(name, heading_style) .then_plain(format!(" ({})", sessions.len())); - list.add_unsel(Text::new(row).boxed_async()); + list_builder.add_unsel(Text::new(row).boxed_async()); for session in sessions { - render_row(list, session, own_session, focused); + render_row(list_builder, session, own_session, focused); } } fn render_row( - list: &mut List<'_, SessionId, BoxedAsync<'static, UiError>>, + list_builder: &mut ListBuilder<'_, SessionId, BoxedAsync<'static, UiError>>, session: &HalfSession, own_session: &SessionView, focused: bool, ) { let (name, style, style_inv, perms_style_inv) = if session.name.is_empty() { - let name = "lurk"; + let name = "lurk".to_string(); let style = Style::new().grey(); let style_inv = Style::new().black().on_grey(); - (Cow::Borrowed(name), style, style_inv, style_inv) + (name, style, style_inv, style_inv) } else { let name = &session.name as &str; let (r, g, b) = euph::nick_color(name); + let name = euph::EMOJI.replace(name).to_string(); let color = Color::Rgb { r, g, b }; let style = Style::new().bold().with(color); let style_inv = Style::new().bold().black().on(color); let perms_style_inv = Style::new().black().on(color); - (euph::EMOJI.replace(name), style, style_inv, perms_style_inv) + (name, style, style_inv, perms_style_inv) }; let perms = if session.is_staff { @@ -158,20 +158,20 @@ fn render_row( " " }; - let widget = if focused && list.state().selected() == Some(&session.session_id) { - let text = Styled::new_plain(owner) - .then(name, style_inv) - .then(perms, perms_style_inv); - Text::new(text) - .background() - .with_style(style_inv) - .boxed_async() - } else { - let text = Styled::new_plain(owner) - .then(&name, style) - .then_plain(perms); - Text::new(text).boxed_async() - }; - - list.add_sel(session.session_id.clone(), widget); + list_builder.add_sel(session.session_id.clone(), move |selected| { + if focused && selected { + let text = Styled::new_plain(owner) + .then(name, style_inv) + .then(perms, perms_style_inv); + Text::new(text) + .background() + .with_style(style_inv) + .boxed_async() + } else { + let text = Styled::new_plain(owner) + .then(&name, style) + .then_plain(perms); + Text::new(text).boxed_async() + } + }); } diff --git a/src/ui/input.rs b/src/ui/input.rs index ab3fcf2..802bfdf 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -5,7 +5,7 @@ use crossterm::style::Stylize; use toss::widgets::{BoxedAsync, Empty, Join2, Text}; use toss::{Style, Styled, WidgetExt}; -use super::widgets::ListState; +use super::widgets::{ListBuilder, ListState}; use super::UiError; #[derive(Debug, Clone)] @@ -136,12 +136,14 @@ impl KeyBindingsList { .with_horizontal(0.5) .with_vertical(0.0); - let mut list = list_state.widget(); + let mut list_builder = ListBuilder::new(); for row in self.0 { - list.add_unsel(Self::row_widget(row)); + list_builder.add_unsel(Self::row_widget(row)); } - list.padding() + list_builder + .build(list_state) + .padding() .with_horizontal(1) .border() .below(hint) diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index e9bdf4b..b8170a3 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -18,7 +18,7 @@ use crate::vault::Vault; use super::euph::room::EuphRoom; use super::input::{key, InputEvent, KeyBindingsList}; -use super::widgets::{List, ListState, Popup}; +use super::widgets::{ListBuilder, ListState, Popup}; use super::{util, UiError, UiEvent}; enum State { @@ -348,12 +348,12 @@ impl Rooms { } async fn render_rows( - list: &mut List<'_, String, Text>, + list_builder: &mut ListBuilder<'_, String, Text>, euph_rooms: &HashMap, order: Order, ) { if euph_rooms.is_empty() { - list.add_unsel(Text::new(( + list_builder.add_unsel(Text::new(( "Press F1 for key bindings", Style::new().grey().italic(), ))) @@ -367,16 +367,19 @@ impl Rooms { } Self::sort_rooms(&mut rooms, order); for (name, state, unseen) in rooms { - let style = if list.state().selected() == Some(name) { - Style::new().bold().black().on_white() - } else { - Style::new().bold().blue() - }; + let name = name.clone(); + let info = Self::format_room_info(state, unseen); + list_builder.add_sel(name.clone(), move |selected| { + let style = if selected { + Style::new().bold().black().on_white() + } else { + Style::new().bold().blue() + }; - let text = Styled::new(format!("&{name}"), style) - .and_then(Self::format_room_info(state, unseen)); + let text = Styled::new(format!("&{name}"), style).and_then(info); - list.add_sel(name.clone(), Text::new(text)); + Text::new(text) + }); } } @@ -389,12 +392,12 @@ impl Rooms { let heading_text = Styled::new("Rooms", heading_style).then_plain(format!(" ({})", euph_rooms.len())); - let mut list = list.widget(); - Self::render_rows(&mut list, euph_rooms, order).await; + let mut list_builder = ListBuilder::new(); + Self::render_rows(&mut list_builder, euph_rooms, order).await; Join2::vertical( Text::new(heading_text).segment().with_fixed(true), - list.segment(), + list_builder.build(list).segment(), ) .boxed_async() } diff --git a/src/ui/widgets/list.rs b/src/ui/widgets/list.rs index 5693a8a..d933eb8 100644 --- a/src/ui/widgets/list.rs +++ b/src/ui/widgets/list.rs @@ -211,13 +211,6 @@ impl ListState { self.move_cursor_to(new_cursor); } } - - pub fn widget(&mut self) -> List<'_, Id, W> { - List { - state: self, - rows: vec![], - } - } } impl ListState { @@ -248,39 +241,61 @@ impl ListState { } } -struct Row { +struct UnrenderedRow<'a, Id, W> { id: Option, - widget: W, + widget: Box W + 'a>, } -pub struct List<'a, Id, W> { - state: &'a mut ListState, - rows: Vec>, +pub struct ListBuilder<'a, Id, W> { + rows: Vec>, } -impl List<'_, Id, W> { - pub fn state(&self) -> &ListState { - &self.state - } - - pub fn state_mut(&mut self) -> &mut ListState { - &mut self.state +impl<'a, Id, W> ListBuilder<'a, Id, W> { + pub fn new() -> Self { + Self { rows: vec![] } } pub fn is_empty(&self) -> bool { self.rows.is_empty() } - pub fn add_unsel(&mut self, widget: W) { - self.rows.push(Row { id: None, widget }); - } - - pub fn add_sel(&mut self, id: Id, widget: W) { - self.rows.push(Row { - id: Some(id), - widget, + pub fn add_unsel(&mut self, widget: W) + where + W: 'a, + { + self.rows.push(UnrenderedRow { + id: None, + widget: Box::new(|_| widget), }); } + + pub fn add_sel(&mut self, id: Id, widget: impl FnOnce(bool) -> W + 'a) { + self.rows.push(UnrenderedRow { + id: Some(id), + widget: Box::new(widget), + }); + } + + pub fn build(self, state: &mut ListState) -> List<'_, Id, W> + where + Id: Clone + Eq, + { + state.last_rows = self.rows.iter().map(|row| row.id.clone()).collect(); + state.fix_cursor(); + + let selected = state.selected(); + let rows = self + .rows + .into_iter() + .map(|row| (row.widget)(row.id.as_ref() == selected)) + .collect(); + List { state, rows } + } +} + +pub struct List<'a, Id, W> { + state: &'a mut ListState, + rows: Vec, } #[async_trait] @@ -297,7 +312,7 @@ where ) -> Result { let mut width = 0; for row in &self.rows { - let size = row.widget.size(widthdb, max_width, Some(1)).await?; + let size = row.size(widthdb, max_width, Some(1)).await?; width = width.max(size.width); } let height = self.rows.len().try_into().unwrap_or(u16::MAX); @@ -307,9 +322,7 @@ where async fn draw(self, frame: &mut Frame) -> Result<(), E> { let size = frame.size(); - self.state.last_rows = self.rows.iter().map(|row| row.id.clone()).collect(); self.state.last_height = size.height; - self.state.fix_cursor(); for (y, row) in self .rows @@ -319,7 +332,7 @@ where .enumerate() { frame.push(Pos::new(0, y as i32), Size::new(size.width, 1)); - row.widget.draw(frame).await?; + row.draw(frame).await?; frame.pop(); } From 8182cc5d387ecc5922f72c5d2f1a8a09c96bc4d0 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 17 Apr 2023 16:53:34 +0200 Subject: [PATCH 087/266] Fix blocks never being higher than one line --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9a92db..5db2959 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1325,7 +1325,7 @@ dependencies = [ [[package]] name = "toss" version = "0.1.0" -source = "git+https://github.com/Garmelon/toss.git?rev=59710c816269c434b97ece3ab1701d3fef2269cb#59710c816269c434b97ece3ab1701d3fef2269cb" +source = "git+https://github.com/Garmelon/toss.git?rev=57788a9dd9688bdf3e59bf7366ba6276fc660715#57788a9dd9688bdf3e59bf7366ba6276fc660715" dependencies = [ "async-trait", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index ef096ec..8fe874b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ features = ["bot"] [dependencies.toss] git = "https://github.com/Garmelon/toss.git" -rev = "59710c816269c434b97ece3ab1701d3fef2269cb" +rev = "57788a9dd9688bdf3e59bf7366ba6276fc660715" # [patch."https://github.com/Garmelon/toss.git"] # toss = { path = "../toss/" } From a638caadcb10a13273449602b2cf2b6447e6845b Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 17 Apr 2023 18:59:16 +0200 Subject: [PATCH 088/266] Render messages with less async --- Cargo.lock | 12 ------ Cargo.toml | 1 - src/ui/chat/tree/renderer.rs | 77 +++++++++++++++--------------------- src/ui/chat/tree/widgets.rs | 18 ++++----- src/ui/chat/widgets.rs | 54 ++++++++++--------------- 5 files changed, 62 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5db2959..aaa024f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,17 +68,6 @@ version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" -[[package]] -name = "async-recursion" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.13", -] - [[package]] name = "async-trait" version = "0.1.68" @@ -251,7 +240,6 @@ name = "cove" version = "0.6.1" dependencies = [ "anyhow", - "async-recursion", "async-trait", "clap", "cookie", diff --git a/Cargo.toml b/Cargo.toml index 8fe874b..d67bdd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" [dependencies] anyhow = "1.0.70" -async-recursion = "1.0.4" async-trait = "0.1.68" clap = { version = "4.2.1", features = ["derive", "deprecated"] } cookie = "0.17.0" diff --git a/src/ui/chat/tree/renderer.rs b/src/ui/chat/tree/renderer.rs index 99be83a..74c1f1e 100644 --- a/src/ui/chat/tree/renderer.rs +++ b/src/ui/chat/tree/renderer.rs @@ -2,10 +2,9 @@ use std::convert::Infallible; -use async_recursion::async_recursion; use async_trait::async_trait; use toss::widgets::{EditorState, Empty, Predrawn, Resize}; -use toss::{AsyncWidget, Size, WidthDb}; +use toss::{Size, Widget, WidthDb}; use crate::store::{Msg, MsgStore, Tree}; use crate::ui::chat::blocks::{Block, Blocks, Range}; @@ -122,26 +121,24 @@ where } } - async fn predraw(widget: W, size: Size, widthdb: &mut WidthDb) -> Predrawn + fn predraw(widget: W, size: Size, widthdb: &mut WidthDb) -> Predrawn where - W: AsyncWidget + Send + Sync, + W: Widget, { - Predrawn::new_async(Resize::new(widget).with_max_width(size.width), widthdb) - .await - .infallible() + Predrawn::new(Resize::new(widget).with_max_width(size.width), widthdb).infallible() } - async fn zero_height_block(&mut self, parent: Option<&M::Id>) -> TreeBlock { + fn zero_height_block(&mut self, parent: Option<&M::Id>) -> TreeBlock { let id = match parent { Some(parent) => TreeBlockId::After(parent.clone()), None => TreeBlockId::Bottom, }; - let widget = Self::predraw(Empty::new(), self.context.size, self.widthdb).await; + let widget = Self::predraw(Empty::new(), self.context.size, self.widthdb); Block::new(id, widget, false) } - async fn editor_block(&mut self, indent: usize, parent: Option<&M::Id>) -> TreeBlock { + fn editor_block(&mut self, indent: usize, parent: Option<&M::Id>) -> TreeBlock { let id = match parent { Some(parent) => TreeBlockId::After(parent.clone()), None => TreeBlockId::Bottom, @@ -149,7 +146,7 @@ where // TODO Unhighlighted version when focusing on nick list let widget = widgets::editor::(indent, &self.context.nick, self.editor); - let widget = Self::predraw(widget, self.context.size, self.widthdb).await; + let widget = Self::predraw(widget, self.context.size, self.widthdb); let mut block = Block::new(id, widget, false); // Since the editor was rendered when the `Predrawn` was created, the @@ -160,7 +157,7 @@ where block } - async fn pseudo_block(&mut self, indent: usize, parent: Option<&M::Id>) -> TreeBlock { + fn pseudo_block(&mut self, indent: usize, parent: Option<&M::Id>) -> TreeBlock { let id = match parent { Some(parent) => TreeBlockId::After(parent.clone()), None => TreeBlockId::Bottom, @@ -168,11 +165,11 @@ where // TODO Unhighlighted version when focusing on nick list let widget = widgets::pseudo::(indent, &self.context.nick, self.editor); - let widget = Self::predraw(widget, self.context.size, self.widthdb).await; + let widget = Self::predraw(widget, self.context.size, self.widthdb); Block::new(id, widget, false) } - async fn message_block(&mut self, indent: usize, msg: &M) -> TreeBlock { + fn message_block(&mut self, indent: usize, msg: &M) -> TreeBlock { let msg_id = msg.id(); let highlighted = match self.cursor { @@ -182,15 +179,11 @@ where // TODO Amount of folded messages let widget = widgets::msg(self.context.focused && highlighted, indent, msg, None); - let widget = Self::predraw(widget, self.context.size, self.widthdb).await; + let widget = Self::predraw(widget, self.context.size, self.widthdb); Block::new(TreeBlockId::Msg(msg_id), widget, true) } - async fn message_placeholder_block( - &mut self, - indent: usize, - msg_id: &M::Id, - ) -> TreeBlock { + fn message_placeholder_block(&mut self, indent: usize, msg_id: &M::Id) -> TreeBlock { let highlighted = match self.cursor { Cursor::Msg(id) => id == msg_id, _ => false, @@ -198,28 +191,23 @@ where // TODO Amount of folded messages let widget = widgets::msg_placeholder(self.context.focused && highlighted, indent, None); - let widget = Self::predraw(widget, self.context.size, self.widthdb).await; + let widget = Self::predraw(widget, self.context.size, self.widthdb); Block::new(TreeBlockId::Msg(msg_id.clone()), widget, true) } - async fn layout_bottom(&mut self) -> TreeBlocks { + fn layout_bottom(&mut self) -> TreeBlocks { let mut blocks = Blocks::new(0); match self.cursor { - Cursor::Editor { parent: None, .. } => { - blocks.push_bottom(self.editor_block(0, None).await) - } - Cursor::Pseudo { parent: None, .. } => { - blocks.push_bottom(self.pseudo_block(0, None).await) - } - _ => blocks.push_bottom(self.zero_height_block(None).await), + Cursor::Editor { parent: None, .. } => blocks.push_bottom(self.editor_block(0, None)), + Cursor::Pseudo { parent: None, .. } => blocks.push_bottom(self.pseudo_block(0, None)), + _ => blocks.push_bottom(self.zero_height_block(None)), } blocks } - #[async_recursion] - async fn layout_subtree( + fn layout_subtree( &mut self, tree: &Tree, indent: usize, @@ -228,16 +216,16 @@ where ) { // Message itself let block = if let Some(msg) = tree.msg(msg_id) { - self.message_block(indent, msg).await + self.message_block(indent, msg) } else { - self.message_placeholder_block(indent, msg_id).await + self.message_placeholder_block(indent, msg_id) }; blocks.push_bottom(block); // Children, recursively if let Some(children) = tree.children(msg_id) { for child in children { - self.layout_subtree(tree, indent + 1, child, blocks).await; + self.layout_subtree(tree, indent + 1, child, blocks); } } @@ -245,21 +233,20 @@ where let block = match self.cursor { Cursor::Editor { parent: Some(id), .. - } if id == msg_id => self.editor_block(indent + 1, Some(msg_id)).await, + } if id == msg_id => self.editor_block(indent + 1, Some(msg_id)), Cursor::Pseudo { parent: Some(id), .. - } if id == msg_id => self.pseudo_block(indent + 1, Some(msg_id)).await, + } if id == msg_id => self.pseudo_block(indent + 1, Some(msg_id)), - _ => self.zero_height_block(Some(msg_id)).await, + _ => self.zero_height_block(Some(msg_id)), }; blocks.push_bottom(block); } - async fn layout_tree(&mut self, tree: Tree) -> TreeBlocks { + fn layout_tree(&mut self, tree: Tree) -> TreeBlocks { let mut blocks = Blocks::new(0); - self.layout_subtree(&tree, 0, tree.root(), &mut blocks) - .await; + self.layout_subtree(&tree, 0, tree.root(), &mut blocks); blocks } @@ -275,9 +262,9 @@ where let blocks = if let Some(root_id) = root_id { let tree = self.store.tree(root_id).await?; - self.layout_tree(tree).await + self.layout_tree(tree) } else { - self.layout_bottom().await + self.layout_bottom() }; self.blocks.append_bottom(blocks); @@ -429,7 +416,7 @@ where if let Some(prev_root_id) = prev_root_id { let tree = self.store.tree(&prev_root_id).await?; - let blocks = self.layout_tree(tree).await; + let blocks = self.layout_tree(tree); self.blocks.append_top(blocks); self.top_root_id = Some(prev_root_id); } else { @@ -448,11 +435,11 @@ where let next_root_id = self.store.next_root_id(bottom_root_id).await?; if let Some(next_root_id) = next_root_id { let tree = self.store.tree(&next_root_id).await?; - let blocks = self.layout_tree(tree).await; + let blocks = self.layout_tree(tree); self.blocks.append_bottom(blocks); self.bottom_root_id = Some(next_root_id); } else { - let blocks = self.layout_bottom().await; + let blocks = self.layout_bottom(); self.blocks.append_bottom(blocks); self.blocks.end_bottom(); self.bottom_root_id = None; diff --git a/src/ui/chat/tree/widgets.rs b/src/ui/chat/tree/widgets.rs index 5a89b70..ecdb7f4 100644 --- a/src/ui/chat/tree/widgets.rs +++ b/src/ui/chat/tree/widgets.rs @@ -1,7 +1,7 @@ use std::convert::Infallible; use crossterm::style::Stylize; -use toss::widgets::{BoxedAsync, EditorState, Join2, Join4, Join5, Text}; +use toss::widgets::{Boxed, EditorState, Join2, Join4, Join5, Text}; use toss::{Style, Styled, WidgetExt}; use crate::store::Msg; @@ -47,7 +47,7 @@ pub fn msg( indent: usize, msg: &M, folded_info: Option, -) -> BoxedAsync<'static, Infallible> { +) -> Boxed<'static, Infallible> { let (nick, mut content) = msg.styled(); if let Some(amount) = folded_info { @@ -81,14 +81,14 @@ pub fn msg( // TODO Minimizing and maximizing messages Text::new(content).segment(), ) - .boxed_async() + .boxed() } pub fn msg_placeholder( highlighted: bool, indent: usize, folded_info: Option, -) -> BoxedAsync<'static, Infallible> { +) -> Boxed<'static, Infallible> { let mut content = Styled::new(PLACEHOLDER, style_placeholder()); if let Some(amount) = folded_info { @@ -110,14 +110,14 @@ pub fn msg_placeholder( .with_fixed(true), Text::new(content).segment(), ) - .boxed_async() + .boxed() } pub fn editor<'a, M: ChatMsg>( indent: usize, nick: &str, editor: &'a mut EditorState, -) -> BoxedAsync<'a, Infallible> { +) -> Boxed<'a, Infallible> { let (nick, content) = M::edit(nick, editor.text()); let editor = editor.widget().with_highlight(|_| content); @@ -144,14 +144,14 @@ pub fn editor<'a, M: ChatMsg>( .with_fixed(true), editor.segment(), ) - .boxed_async() + .boxed() } pub fn pseudo<'a, M: ChatMsg>( indent: usize, nick: &str, editor: &'a mut EditorState, -) -> BoxedAsync<'a, Infallible> { +) -> Boxed<'a, Infallible> { let (nick, content) = M::edit(nick, editor.text()); Join5::horizontal( @@ -177,5 +177,5 @@ pub fn pseudo<'a, M: ChatMsg>( .with_fixed(true), Text::new(content).segment(), ) - .boxed_async() + .boxed() } diff --git a/src/ui/chat/widgets.rs b/src/ui/chat/widgets.rs index 0241232..5d35e9c 100644 --- a/src/ui/chat/widgets.rs +++ b/src/ui/chat/widgets.rs @@ -1,12 +1,11 @@ use std::convert::Infallible; -use async_trait::async_trait; use crossterm::style::Stylize; use time::format_description::FormatItem; use time::macros::format_description; use time::OffsetDateTime; -use toss::widgets::{BoxedAsync, Empty, Text}; -use toss::{AsyncWidget, Frame, Pos, Size, Style, WidgetExt, WidthDb}; +use toss::widgets::{Boxed, Empty, Text}; +use toss::{Frame, Pos, Size, Style, Widget, WidgetExt, WidthDb}; use crate::util::InfallibleExt; @@ -24,9 +23,8 @@ impl Indent { } } -#[async_trait] -impl AsyncWidget for Indent { - async fn size( +impl Widget for Indent { + fn size( &self, _widthdb: &mut WidthDb, _max_width: Option, @@ -36,7 +34,7 @@ impl AsyncWidget for Indent { Ok(Size::new(width, 0)) } - async fn draw(self, frame: &mut Frame) -> Result<(), E> { + fn draw(self, frame: &mut Frame) -> Result<(), E> { let size = frame.size(); let indent_string = INDENT_STR.repeat(self.level); @@ -51,7 +49,7 @@ impl AsyncWidget for Indent { const TIME_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day] [hour]:[minute]"); const TIME_WIDTH: u16 = 16; -pub struct Time(BoxedAsync<'static, Infallible>); +pub struct Time(Boxed<'static, Infallible>); impl Time { pub fn new(time: Option, style: Style) -> Self { @@ -60,70 +58,60 @@ impl Time { Text::new((text, style)) .background() .with_style(style) - .boxed_async() + .boxed() } else { Empty::new() .with_width(TIME_WIDTH) .background() .with_style(style) - .boxed_async() + .boxed() }; Self(widget) } } -#[async_trait] -impl AsyncWidget for Time { - async fn size( +impl Widget for Time { + fn size( &self, widthdb: &mut WidthDb, max_width: Option, max_height: Option, ) -> Result { - Ok(self - .0 - .size(widthdb, max_width, max_height) - .await - .infallible()) + Ok(self.0.size(widthdb, max_width, max_height).infallible()) } - async fn draw(self, frame: &mut Frame) -> Result<(), E> { - self.0.draw(frame).await.infallible(); + fn draw(self, frame: &mut Frame) -> Result<(), E> { + self.0.draw(frame).infallible(); Ok(()) } } -pub struct Seen(BoxedAsync<'static, Infallible>); +pub struct Seen(Boxed<'static, Infallible>); impl Seen { pub fn new(seen: bool) -> Self { let widget = if seen { - Empty::new().with_width(1).boxed_async() + Empty::new().with_width(1).boxed() } else { let style = Style::new().black().on_green(); - Text::new("*").background().with_style(style).boxed_async() + Text::new("*").background().with_style(style).boxed() }; Self(widget) } } -#[async_trait] -impl AsyncWidget for Seen { - async fn size( +impl Widget for Seen { + fn size( &self, widthdb: &mut WidthDb, max_width: Option, max_height: Option, ) -> Result { - Ok(self - .0 - .size(widthdb, max_width, max_height) - .await - .infallible()) + Ok(self.0.size(widthdb, max_width, max_height).infallible()) } - async fn draw(self, frame: &mut Frame) -> Result<(), E> { - self.0.draw(frame).await.infallible(); + fn draw(self, frame: &mut Frame) -> Result<(), E> { + self.0.draw(frame).infallible(); Ok(()) } } From ade7be594e2c51b6b164920d85bb48ebe8b171c3 Mon Sep 17 00:00:00 2001 From: Joscha Date: Mon, 17 Apr 2023 20:36:04 +0200 Subject: [PATCH 089/266] Update toss and remove more async --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/ui.rs | 1 + src/ui/chat/tree.rs | 4 ++-- src/ui/euph/account.rs | 18 ++++++++---------- src/ui/euph/auth.rs | 7 +++---- src/ui/euph/inspect.rs | 12 ++++++------ src/ui/euph/links.rs | 8 ++++---- src/ui/euph/nick.rs | 8 ++++---- src/ui/euph/nick_list.rs | 25 +++++++++++-------------- src/ui/euph/popup.rs | 13 ++++++------- src/ui/euph/room.rs | 39 ++++++++++++++++++++------------------- src/ui/input.rs | 17 ++++++++--------- src/ui/rooms.rs | 27 +++++++++++++++------------ src/ui/widgets/list.rs | 18 ++++++++---------- src/ui/widgets/popup.rs | 24 +++++++++++------------- 16 files changed, 109 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aaa024f..bd844fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1313,7 +1313,7 @@ dependencies = [ [[package]] name = "toss" version = "0.1.0" -source = "git+https://github.com/Garmelon/toss.git?rev=57788a9dd9688bdf3e59bf7366ba6276fc660715#57788a9dd9688bdf3e59bf7366ba6276fc660715" +source = "git+https://github.com/Garmelon/toss.git?rev=f414db40d526295c74cbcae6c3d194088da8f1d9#f414db40d526295c74cbcae6c3d194088da8f1d9" dependencies = [ "async-trait", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index d67bdd8..f0e55ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ features = ["bot"] [dependencies.toss] git = "https://github.com/Garmelon/toss.git" -rev = "57788a9dd9688bdf3e59bf7366ba6276fc660715" +rev = "f414db40d526295c74cbcae6c3d194088da8f1d9" # [patch."https://github.com/Garmelon/toss.git"] # toss = { path = "../toss/" } diff --git a/src/ui.rs b/src/ui.rs index 437f68c..05ecbaa 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -209,6 +209,7 @@ impl Ui { key_bindings_list .widget(list_state) + .desync() .above(widget) .boxed_async() } else { diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs index 246e3c9..ad7e51d 100644 --- a/src/ui/chat/tree.rs +++ b/src/ui/chat/tree.rs @@ -12,7 +12,7 @@ use std::sync::Arc; use async_trait::async_trait; use parking_lot::FairMutex; use toss::widgets::EditorState; -use toss::{AsyncWidget, Frame, Pos, Size, Terminal, WidthDb}; +use toss::{AsyncWidget, Frame, Pos, Size, Terminal, WidgetExt, WidthDb}; use crate::store::{Msg, MsgStore}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; @@ -488,7 +488,7 @@ where for (range, block) in renderer.into_visible_blocks() { let widget = block.into_widget(); frame.push(Pos::new(0, range.top), widget.size()); - widget.draw(frame).await.infallible(); + widget.desync().draw(frame).await.infallible(); frame.pop(); } diff --git a/src/ui/euph/account.rs b/src/ui/euph/account.rs index 9599cfc..c398662 100644 --- a/src/ui/euph/account.rs +++ b/src/ui/euph/account.rs @@ -1,8 +1,8 @@ use crossterm::style::Stylize; use euphoxide::api::PersonalAccountView; use euphoxide::conn; -use toss::widgets::{BoxedAsync, EditorState, Empty, Join3, Join4, Text}; -use toss::{Style, Terminal, WidgetExt}; +use toss::widgets::{EditorState, Empty, Join3, Join4, Text}; +use toss::{Style, Terminal, Widget, WidgetExt}; use crate::euph::{self, Room}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; @@ -30,7 +30,7 @@ impl LoggedOut { } } - fn widget(&mut self) -> BoxedAsync<'_, UiError> { + fn widget(&mut self) -> impl Widget + '_ { let bold = Style::new().bold(); Join4::vertical( Text::new(("Not logged in", bold.yellow())).segment(), @@ -57,14 +57,13 @@ impl LoggedOut { ) .segment(), ) - .boxed_async() } } pub struct LoggedIn(PersonalAccountView); impl LoggedIn { - fn widget(&self) -> BoxedAsync<'_, UiError> { + fn widget(&self) -> impl Widget { let bold = Style::new().bold(); Join3::vertical( Text::new(("Logged in", bold.green())).segment(), @@ -78,7 +77,6 @@ impl LoggedIn { ) .segment(), ) - .boxed_async() } } @@ -112,15 +110,15 @@ impl AccountUiState { } } - pub fn widget(&mut self) -> BoxedAsync<'_, UiError> { + pub fn widget(&mut self) -> impl Widget + '_ { let inner = match self { - Self::LoggedOut(logged_out) => logged_out.widget(), - Self::LoggedIn(logged_in) => logged_in.widget(), + Self::LoggedOut(logged_out) => logged_out.widget().first2(), + Self::LoggedIn(logged_in) => logged_in.widget().second2(), } .resize() .with_min_width(40); - Popup::new(inner, "Account").boxed_async() + Popup::new(inner, "Account") } pub fn list_key_bindings(&self, bindings: &mut KeyBindingsList) { diff --git a/src/ui/euph/auth.rs b/src/ui/euph/auth.rs index ca47660..8cc58f9 100644 --- a/src/ui/euph/auth.rs +++ b/src/ui/euph/auth.rs @@ -1,5 +1,5 @@ -use toss::widgets::{BoxedAsync, EditorState}; -use toss::{Terminal, WidgetExt}; +use toss::widgets::EditorState; +use toss::{Terminal, Widget}; use crate::euph::Room; use crate::ui::input::{key, InputEvent, KeyBindingsList}; @@ -10,12 +10,11 @@ pub fn new() -> EditorState { EditorState::new() } -pub fn widget(editor: &mut EditorState) -> BoxedAsync<'_, UiError> { +pub fn widget(editor: &mut EditorState) -> impl Widget + '_ { Popup::new( editor.widget().with_hidden_default_placeholder(), "Enter password", ) - .boxed_async() } pub fn list_key_bindings(bindings: &mut KeyBindingsList) { diff --git a/src/ui/euph/inspect.rs b/src/ui/euph/inspect.rs index 2b0e43f..7cbf054 100644 --- a/src/ui/euph/inspect.rs +++ b/src/ui/euph/inspect.rs @@ -1,8 +1,8 @@ use crossterm::style::Stylize; use euphoxide::api::{Message, NickEvent, SessionView}; use euphoxide::conn::SessionInfo; -use toss::widgets::{BoxedAsync, Text}; -use toss::{Style, Styled, WidgetExt}; +use toss::widgets::Text; +use toss::{Style, Styled, Widget}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::widgets::Popup; @@ -88,7 +88,7 @@ fn message_lines(mut text: Styled, msg: &Message) -> Styled { text } -pub fn session_widget(session: &SessionInfo) -> BoxedAsync<'static, UiError> { +pub fn session_widget(session: &SessionInfo) -> impl Widget { let heading_style = Style::new().bold(); let text = match session { @@ -102,10 +102,10 @@ pub fn session_widget(session: &SessionInfo) -> BoxedAsync<'static, UiError> { } }; - Popup::new(Text::new(text), "Inspect session").boxed_async() + Popup::new(Text::new(text), "Inspect session") } -pub fn message_widget(msg: &Message) -> BoxedAsync<'static, UiError> { +pub fn message_widget(msg: &Message) -> impl Widget { let heading_style = Style::new().bold(); let mut text = Styled::new("Message", heading_style).then_plain("\n"); @@ -119,7 +119,7 @@ pub fn message_widget(msg: &Message) -> BoxedAsync<'static, UiError> { text = session_view_lines(text, &msg.sender); - Popup::new(Text::new(text), "Inspect message").boxed_async() + Popup::new(Text::new(text), "Inspect message") } pub fn list_key_bindings(bindings: &mut KeyBindingsList) { diff --git a/src/ui/euph/links.rs b/src/ui/euph/links.rs index a5541e6..3cebf7d 100644 --- a/src/ui/euph/links.rs +++ b/src/ui/euph/links.rs @@ -2,8 +2,8 @@ use std::io; use crossterm::style::Stylize; use linkify::{LinkFinder, LinkKind}; -use toss::widgets::{BoxedAsync, Text}; -use toss::{Style, Styled, WidgetExt}; +use toss::widgets::Text; +use toss::{Style, Styled, Widget}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::widgets::{ListBuilder, ListState, Popup}; @@ -38,7 +38,7 @@ impl LinksState { } } - pub fn widget(&mut self) -> BoxedAsync<'_, UiError> { + pub fn widget(&mut self) -> impl Widget + '_ { let style_selected = Style::new().black().on_white(); let mut list_builder = ListBuilder::new(); @@ -74,7 +74,7 @@ impl LinksState { } } - Popup::new(list_builder.build(&mut self.list), "Links").boxed_async() + Popup::new(list_builder.build(&mut self.list), "Links") } fn open_link_by_id(&self, id: usize) -> EventResult { diff --git a/src/ui/euph/nick.rs b/src/ui/euph/nick.rs index 20f8576..c928297 100644 --- a/src/ui/euph/nick.rs +++ b/src/ui/euph/nick.rs @@ -1,6 +1,6 @@ use euphoxide::conn::Joined; -use toss::widgets::{BoxedAsync, EditorState}; -use toss::{Style, Terminal, WidgetExt}; +use toss::widgets::EditorState; +use toss::{Style, Terminal, Widget}; use crate::euph::{self, Room}; use crate::ui::input::{key, InputEvent, KeyBindingsList}; @@ -11,12 +11,12 @@ pub fn new(joined: Joined) -> EditorState { EditorState::with_initial_text(joined.session.name) } -pub fn widget(editor: &mut EditorState) -> BoxedAsync<'_, UiError> { +pub fn widget(editor: &mut EditorState) -> impl Widget + '_ { let inner = editor .widget() .with_highlight(|s| euph::style_nick_exact(s, Style::new())); - Popup::new(inner, "Choose nick").boxed_async() + Popup::new(inner, "Choose nick") } fn nick_char(c: char) -> bool { diff --git a/src/ui/euph/nick_list.rs b/src/ui/euph/nick_list.rs index 0e13c84..23160bd 100644 --- a/src/ui/euph/nick_list.rs +++ b/src/ui/euph/nick_list.rs @@ -3,8 +3,8 @@ use std::iter; use crossterm::style::{Color, Stylize}; use euphoxide::api::{NickEvent, SessionId, SessionType, SessionView, UserId}; use euphoxide::conn::{Joined, SessionInfo}; -use toss::widgets::{BoxedAsync, Empty, Text}; -use toss::{Style, Styled, WidgetExt}; +use toss::widgets::{Background, Text}; +use toss::{Style, Styled, Widget, WidgetExt}; use crate::euph; use crate::ui::widgets::{ListBuilder, ListState}; @@ -14,10 +14,10 @@ pub fn widget<'a>( list: &'a mut ListState, joined: &Joined, focused: bool, -) -> BoxedAsync<'a, UiError> { +) -> impl Widget + 'a { let mut list_builder = ListBuilder::new(); render_rows(&mut list_builder, joined, focused); - list_builder.build(list).boxed_async() + list_builder.build(list) } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -59,7 +59,7 @@ impl HalfSession { } fn render_rows( - list_builder: &mut ListBuilder<'_, SessionId, BoxedAsync<'static, UiError>>, + list_builder: &mut ListBuilder<'_, SessionId, Background>, joined: &Joined, focused: bool, ) { @@ -94,7 +94,7 @@ fn render_rows( } fn render_section( - list_builder: &mut ListBuilder<'_, SessionId, BoxedAsync<'static, UiError>>, + list_builder: &mut ListBuilder<'_, SessionId, Background>, name: &str, sessions: &[HalfSession], own_session: &SessionView, @@ -107,13 +107,13 @@ fn render_section( let heading_style = Style::new().bold(); if !list_builder.is_empty() { - list_builder.add_unsel(Empty::new().boxed_async()); + list_builder.add_unsel(Text::new("").background()); } let row = Styled::new_plain(" ") .then(name, heading_style) .then_plain(format!(" ({})", sessions.len())); - list_builder.add_unsel(Text::new(row).boxed_async()); + list_builder.add_unsel(Text::new(row).background()); for session in sessions { render_row(list_builder, session, own_session, focused); @@ -121,7 +121,7 @@ fn render_section( } fn render_row( - list_builder: &mut ListBuilder<'_, SessionId, BoxedAsync<'static, UiError>>, + list_builder: &mut ListBuilder<'_, SessionId, Background>, session: &HalfSession, own_session: &SessionView, focused: bool, @@ -163,15 +163,12 @@ fn render_row( let text = Styled::new_plain(owner) .then(name, style_inv) .then(perms, perms_style_inv); - Text::new(text) - .background() - .with_style(style_inv) - .boxed_async() + Text::new(text).background().with_style(style_inv) } else { let text = Styled::new_plain(owner) .then(&name, style) .then_plain(perms); - Text::new(text).boxed_async() + Text::new(text).background() } }); } diff --git a/src/ui/euph/popup.rs b/src/ui/euph/popup.rs index 4f6384c..2ab8278 100644 --- a/src/ui/euph/popup.rs +++ b/src/ui/euph/popup.rs @@ -1,6 +1,6 @@ use crossterm::style::Stylize; -use toss::widgets::{BoxedAsync, Text}; -use toss::{Style, Styled, WidgetExt}; +use toss::widgets::Text; +use toss::{Style, Styled, Widget}; use crate::ui::widgets::Popup; use crate::ui::UiError; @@ -10,19 +10,18 @@ pub enum RoomPopup { } impl RoomPopup { - fn server_error_widget(description: &str, reason: &str) -> BoxedAsync<'static, UiError> { + fn server_error_widget(description: &str, reason: &str) -> impl Widget { let border_style = Style::new().red().bold(); let text = Styled::new_plain(description) .then_plain("\n\n") .then("Reason:", Style::new().bold()) .then_plain(" ") .then_plain(reason); - Popup::new(Text::new(text), ("Error", border_style)) - .with_border_style(border_style) - .boxed_async() + + Popup::new(Text::new(text), ("Error", border_style)).with_border_style(border_style) } - pub fn widget(&self) -> BoxedAsync<'static, UiError> { + pub fn widget(&self) -> impl Widget { match self { Self::Error { description, diff --git a/src/ui/euph/room.rs b/src/ui/euph/room.rs index 399433d..a03e6e1 100644 --- a/src/ui/euph/room.rs +++ b/src/ui/euph/room.rs @@ -9,7 +9,7 @@ use parking_lot::FairMutex; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::{mpsc, oneshot}; use toss::widgets::{BoxedAsync, EditorState, Join2, Layer, Text}; -use toss::{AsyncWidget, Style, Styled, Terminal, WidgetExt}; +use toss::{Style, Styled, Terminal, Widget, WidgetExt}; use crate::config; use crate::euph; @@ -223,16 +223,20 @@ impl EuphRoom { match &mut self.state { State::Normal => {} - State::Auth(editor) => layers.push(auth::widget(editor)), - State::Nick(editor) => layers.push(nick::widget(editor)), - State::Account(account) => layers.push(account.widget()), - State::Links(links) => layers.push(links.widget()), - State::InspectMessage(message) => layers.push(inspect::message_widget(message)), - State::InspectSession(session) => layers.push(inspect::session_widget(session)), + State::Auth(editor) => layers.push(auth::widget(editor).desync().boxed_async()), + State::Nick(editor) => layers.push(nick::widget(editor).desync().boxed_async()), + State::Account(account) => layers.push(account.widget().desync().boxed_async()), + State::Links(links) => layers.push(links.widget().desync().boxed_async()), + State::InspectMessage(message) => { + layers.push(inspect::message_widget(message).desync().boxed_async()) + } + State::InspectSession(session) => { + layers.push(inspect::session_widget(session).desync().boxed_async()) + } } for popup in &self.popups { - layers.push(popup.widget()); + layers.push(popup.widget().desync().boxed_async()); } Layer::new(layers).boxed_async() @@ -240,12 +244,12 @@ impl EuphRoom { fn widget_without_nick_list( chat: &mut EuphChatState, - status_widget: impl AsyncWidget + Send + Sync + 'static, + status_widget: impl Widget + Send + Sync + 'static, ) -> BoxedAsync<'_, UiError> { let chat_widget = chat.widget(String::new(), true); Join2::vertical( - status_widget.segment().with_fixed(true), + status_widget.desync().segment().with_fixed(true), chat_widget.segment(), ) .boxed_async() @@ -253,7 +257,7 @@ impl EuphRoom { fn widget_with_nick_list<'a>( chat: &'a mut EuphChatState, - status_widget: impl AsyncWidget + Send + Sync + 'static, + status_widget: impl Widget + Send + Sync + 'static, nick_list: &'a mut ListState, joined: &Joined, focus: Focus, @@ -261,13 +265,14 @@ impl EuphRoom { let nick_list_widget = nick_list::widget(nick_list, joined, focus == Focus::NickList) .padding() .with_right(1) - .border(); + .border() + .desync(); let chat_widget = chat.widget(joined.session.name.clone(), focus == Focus::Chat); Join2::horizontal( Join2::vertical( - status_widget.segment().with_fixed(true), + status_widget.desync().segment().with_fixed(true), chat_widget.segment(), ) .segment(), @@ -276,7 +281,7 @@ impl EuphRoom { .boxed_async() } - async fn status_widget(&self, state: Option<&euph::State>) -> BoxedAsync<'static, UiError> { + async fn status_widget(&self, state: Option<&euph::State>) -> impl Widget { let room_style = Style::new().bold().blue(); let mut info = Styled::new(format!("&{}", self.name()), room_style); @@ -309,11 +314,7 @@ impl EuphRoom { .then_plain(")"); } - Text::new(info) - .padding() - .with_horizontal(1) - .border() - .boxed_async() + Text::new(info).padding().with_horizontal(1).border() } async fn list_chat_key_bindings(&self, bindings: &mut KeyBindingsList) { diff --git a/src/ui/input.rs b/src/ui/input.rs index 802bfdf..c91789b 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -2,8 +2,8 @@ use std::convert::Infallible; use crossterm::event::{Event, KeyCode, KeyModifiers}; use crossterm::style::Stylize; -use toss::widgets::{BoxedAsync, Empty, Join2, Text}; -use toss::{Style, Styled, WidgetExt}; +use toss::widgets::{Empty, Join2, Text}; +use toss::{Style, Styled, Widget, WidgetExt}; use super::widgets::{ListBuilder, ListState}; use super::UiError; @@ -96,11 +96,11 @@ impl KeyBindingsList { Style::new().cyan() } - fn row_widget(row: Row) -> BoxedAsync<'static, UiError> { + fn row_widget(row: Row) -> impl Widget { match row { - Row::Empty => Empty::new().boxed_async(), + Row::Empty => Text::new("").first3(), - Row::Heading(name) => Text::new((name, Style::new().bold())).boxed_async(), + Row::Heading(name) => Text::new((name, Style::new().bold())).first3(), Row::Binding(binding, description) => Join2::horizontal( Text::new((binding, Self::binding_style())) @@ -111,17 +111,17 @@ impl KeyBindingsList { .segment(), Text::new(description).segment(), ) - .boxed_async(), + .second3(), Row::BindingContd(description) => Join2::horizontal( Empty::new().with_width(Self::BINDING_WIDTH).segment(), Text::new(description).segment(), ) - .boxed_async(), + .third3(), } } - pub fn widget(self, list_state: &mut ListState) -> BoxedAsync<'_, UiError> { + pub fn widget(self, list_state: &mut ListState) -> impl Widget + '_ { let binding_style = Self::binding_style(); let hint_text = Styled::new("jk/↓↑", binding_style) @@ -150,7 +150,6 @@ impl KeyBindingsList { .background() .float() .with_center() - .boxed_async() } pub fn empty(&mut self) { diff --git a/src/ui/rooms.rs b/src/ui/rooms.rs index b8170a3..8a347e2 100644 --- a/src/ui/rooms.rs +++ b/src/ui/rooms.rs @@ -9,7 +9,7 @@ use euphoxide::conn::{self, Joined}; use parking_lot::FairMutex; use tokio::sync::mpsc; use toss::widgets::{BoxedAsync, EditorState, Empty, Join2, Text}; -use toss::{Style, Styled, Terminal, WidgetExt}; +use toss::{Style, Styled, Terminal, Widget, WidgetExt}; use crate::config::{Config, RoomsSortOrder}; use crate::euph; @@ -171,9 +171,10 @@ impl Rooms { } match &mut self.state { - State::ShowList => { - Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order).await - } + State::ShowList => Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order) + .await + .desync() + .boxed_async(), State::ShowRoom(name) => { self.euph_rooms @@ -187,6 +188,7 @@ impl Rooms { Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order) .await .below(Self::new_room_widget(editor)) + .desync() .boxed_async() } @@ -194,12 +196,13 @@ impl Rooms { Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order) .await .below(Self::delete_room_widget(name, editor)) + .desync() .boxed_async() } } } - fn new_room_widget(editor: &mut EditorState) -> BoxedAsync<'_, UiError> { + fn new_room_widget(editor: &mut EditorState) -> impl Widget + '_ { let room_style = Style::new().bold().blue(); let inner = Join2::horizontal( @@ -210,10 +213,13 @@ impl Rooms { .segment(), ); - Popup::new(inner, "Connect to").boxed_async() + Popup::new(inner, "Connect to") } - fn delete_room_widget<'a>(name: &str, editor: &'a mut EditorState) -> BoxedAsync<'a, UiError> { + fn delete_room_widget<'a>( + name: &str, + editor: &'a mut EditorState, + ) -> impl Widget + 'a { let warn_style = Style::new().bold().red(); let room_style = Style::new().bold().blue(); let text = Styled::new_plain("Are you sure you want to delete ") @@ -249,9 +255,7 @@ impl Rooms { .segment(), ); - Popup::new(inner, "Delete room") - .with_border_style(warn_style) - .boxed_async() + Popup::new(inner, "Delete room").with_border_style(warn_style) } fn format_pbln(joined: &Joined) -> String { @@ -387,7 +391,7 @@ impl Rooms { list: &'a mut ListState, euph_rooms: &HashMap, order: Order, - ) -> BoxedAsync<'a, UiError> { + ) -> impl Widget + 'a { let heading_style = Style::new().bold(); let heading_text = Styled::new("Rooms", heading_style).then_plain(format!(" ({})", euph_rooms.len())); @@ -399,7 +403,6 @@ impl Rooms { Text::new(heading_text).segment().with_fixed(true), list_builder.build(list).segment(), ) - .boxed_async() } fn room_char(c: char) -> bool { diff --git a/src/ui/widgets/list.rs b/src/ui/widgets/list.rs index d933eb8..88d08bd 100644 --- a/src/ui/widgets/list.rs +++ b/src/ui/widgets/list.rs @@ -1,7 +1,6 @@ use std::vec; -use async_trait::async_trait; -use toss::{AsyncWidget, Frame, Pos, Size, WidthDb}; +use toss::{Frame, Pos, Size, Widget, WidthDb}; #[derive(Debug, Clone)] struct Cursor { @@ -298,13 +297,12 @@ pub struct List<'a, Id, W> { rows: Vec, } -#[async_trait] -impl AsyncWidget for List<'_, Id, W> +impl Widget for List<'_, Id, W> where - Id: Clone + Eq + Send + Sync, - W: AsyncWidget + Send + Sync, + Id: Clone + Eq, + W: Widget, { - async fn size( + fn size( &self, widthdb: &mut WidthDb, max_width: Option, @@ -312,14 +310,14 @@ where ) -> Result { let mut width = 0; for row in &self.rows { - let size = row.size(widthdb, max_width, Some(1)).await?; + let size = row.size(widthdb, max_width, Some(1))?; width = width.max(size.width); } let height = self.rows.len().try_into().unwrap_or(u16::MAX); Ok(Size::new(width, height)) } - async fn draw(self, frame: &mut Frame) -> Result<(), E> { + fn draw(self, frame: &mut Frame) -> Result<(), E> { let size = frame.size(); self.state.last_height = size.height; @@ -332,7 +330,7 @@ where .enumerate() { frame.push(Pos::new(0, y as i32), Size::new(size.width, 1)); - row.draw(frame).await?; + row.draw(frame)?; frame.pop(); } diff --git a/src/ui/widgets/popup.rs b/src/ui/widgets/popup.rs index d8dae47..40b41cb 100644 --- a/src/ui/widgets/popup.rs +++ b/src/ui/widgets/popup.rs @@ -1,11 +1,10 @@ -use async_trait::async_trait; -use toss::widgets::{Background, Border, Float, Layer2, Padding, Text}; -use toss::{AsyncWidget, Frame, Size, Style, Styled, WidgetExt, WidthDb}; +use toss::widgets::{Background, Border, Desync, Float, Layer2, Padding, Text}; +use toss::{Frame, Size, Style, Styled, Widget, WidgetExt, WidthDb}; type Body = Background>>; type Title = Float>>>; -pub struct Popup(Float, Title>>); +pub struct Popup(Float, Desync>>); impl<I> Popup<I> { pub fn new<S: Into<Styled>>(inner: I, title: S) -> Self { @@ -19,7 +18,8 @@ impl<I> Popup<I> { .with_horizontal(2) .float() .with_top() - .with_left(); + .with_left() + .desync(); let body = inner.padding().with_horizontal(1).border().background(); @@ -33,22 +33,20 @@ impl<I> Popup<I> { } } -#[async_trait] -impl<E, I> AsyncWidget<E> for Popup<I> +impl<E, I> Widget<E> for Popup<I> where - E: Send, - I: AsyncWidget<E> + Send + Sync, + I: Widget<E>, { - async fn size( + fn size( &self, widthdb: &mut WidthDb, max_width: Option<u16>, max_height: Option<u16>, ) -> Result<Size, E> { - self.0.size(widthdb, max_width, max_height).await + self.0.size(widthdb, max_width, max_height) } - async fn draw(self, frame: &mut Frame) -> Result<(), E> { - self.0.draw(frame).await + fn draw(self, frame: &mut Frame) -> Result<(), E> { + self.0.draw(frame) } } From 164c02243da576dcc04f81b67bb5c451eb8450b7 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 18 Apr 2023 18:13:25 +0200 Subject: [PATCH 090/266] Fix scrolling for editor cursor --- src/ui/chat/tree/renderer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/chat/tree/renderer.rs b/src/ui/chat/tree/renderer.rs index 74c1f1e..a48a4ab 100644 --- a/src/ui/chat/tree/renderer.rs +++ b/src/ui/chat/tree/renderer.rs @@ -152,7 +152,7 @@ where // Since the editor was rendered when the `Predrawn` was created, the // last cursor pos is accurate now. let cursor_line = self.editor.last_cursor_pos().y; - block.set_focus(Range::new(cursor_line, cursor_line)); + block.set_focus(Range::new(cursor_line, cursor_line + 1)); block } From 318f7e2a7361f3ba5bb58ba29d17b5c976c294f5 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 19 Apr 2023 23:24:35 +0200 Subject: [PATCH 091/266] Update vault --- Cargo.lock | 30 ++++++---- Cargo.toml | 4 +- src/ui.rs | 2 +- src/vault.rs | 7 ++- src/vault/euph.rs | 146 +++++++++++++++++++++++++++------------------- 5 files changed, 112 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd844fd..540a3cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3" + [[package]] name = "block-buffer" version = "0.10.4" @@ -171,7 +177,7 @@ checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" dependencies = [ "anstream", "anstyle", - "bitflags", + "bitflags 1.3.2", "clap_lex", "strsim", ] @@ -281,7 +287,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crossterm_winapi", "libc", "mio", @@ -617,9 +623,9 @@ checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "libsqlite3-sys" -version = "0.25.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" dependencies = [ "cc", "pkg-config", @@ -822,7 +828,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -831,7 +837,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -879,11 +885,11 @@ dependencies = [ [[package]] name = "rusqlite" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" +checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" dependencies = [ - "bitflags", + "bitflags 2.1.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -898,7 +904,7 @@ version = "0.37.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", @@ -976,7 +982,7 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -1424,7 +1430,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "vault" version = "0.1.0" -source = "git+https://github.com/Garmelon/vault.git?tag=v0.1.0#028c72cac4e84bfbbf9fb03b15acb59989a31df9" +source = "git+https://github.com/Garmelon/vault.git?rev=b4cf23b7279770226725c895e482c8eda88c43a7#b4cf23b7279770226725c895e482c8eda88c43a7" dependencies = [ "rusqlite", "tokio", diff --git a/Cargo.toml b/Cargo.toml index f0e55ee..61abd7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ log = { version = "0.4.17", features = ["std"] } once_cell = "1.17.1" open = "4.0.1" parking_lot = "0.12.1" -rusqlite = { version = "0.28.0", features = ["bundled", "time"] } +rusqlite = { version = "0.29.0", features = ["bundled", "time"] } serde = { version = "1.0.159", features = ["derive"] } serde_json = "1.0.95" thiserror = "1.0.40" @@ -50,7 +50,7 @@ rev = "f414db40d526295c74cbcae6c3d194088da8f1d9" [dependencies.vault] git = "https://github.com/Garmelon/vault.git" -tag = "v0.1.0" +rev = "b4cf23b7279770226725c895e482c8eda88c43a7" features = ["tokio"] # [patch."https://github.com/Garmelon/vault.git"] diff --git a/src/ui.rs b/src/ui.rs index 05ecbaa..42235bf 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -36,7 +36,7 @@ const EVENT_PROCESSING_TIME: Duration = Duration::from_millis(1000 / 15); // 15 #[derive(Debug, thiserror::Error)] pub enum UiError { #[error("{0}")] - Vault(#[from] vault::tokio::Error), + Vault(#[from] vault::tokio::Error<rusqlite::Error>), #[error("{0}")] Io(#[from] io::Error), } diff --git a/src/vault.rs b/src/vault.rs index 4f49e45..921ad4d 100644 --- a/src/vault.rs +++ b/src/vault.rs @@ -20,9 +20,10 @@ pub struct Vault { struct GcAction; impl Action for GcAction { - type Result = (); + type Output = (); + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { conn.execute_batch("ANALYZE; VACUUM;") } } @@ -36,7 +37,7 @@ impl Vault { self.tokio_vault.stop().await; } - pub async fn gc(&self) -> vault::tokio::Result<()> { + pub async fn gc(&self) -> Result<(), vault::tokio::Error<rusqlite::Error>> { self.tokio_vault.execute(GcAction).await } diff --git a/src/vault/euph.rs b/src/vault/euph.rs index 49f1501..e9f363e 100644 --- a/src/vault/euph.rs +++ b/src/vault/euph.rs @@ -88,7 +88,7 @@ macro_rules! euph_vault_actions { impl EuphVault { $( - pub async fn $fn(&self, $( $arg: $arg_ty, )* ) -> vault::tokio::Result<$res> { + pub async fn $fn(&self, $( $arg: $arg_ty, )* ) -> Result<$res, vault::tokio::Error<rusqlite::Error>> { self.vault.tokio_vault.execute($struct { $( $arg, )* }).await } )* @@ -103,9 +103,10 @@ euph_vault_actions! { } impl Action for GetCookies { - type Result = CookieJar; + type Output = CookieJar; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let cookies = conn .prepare( " @@ -128,9 +129,10 @@ impl Action for GetCookies { } impl Action for SetCookies { - type Result = (); + type Output = (); + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let tx = conn.transaction()?; // Since euphoria sets all cookies on every response, we can just delete @@ -154,9 +156,10 @@ impl Action for SetCookies { } impl Action for GetRooms { - type Result = Vec<String>; + type Output = Vec<String>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { conn.prepare( " SELECT room @@ -201,7 +204,7 @@ macro_rules! euph_room_vault_actions { impl EuphRoomVault { $( - pub async fn $fn(&self, $( $arg: $arg_ty, )* ) -> vault::tokio::Result<$res> { + pub async fn $fn(&self, $( $arg: $arg_ty, )* ) -> Result<$res, vault::tokio::Error<rusqlite::Error>> { self.vault.vault.tokio_vault.execute($struct { room: self.room.clone(), $( $arg, )* @@ -244,9 +247,10 @@ euph_room_vault_actions! { } impl Action for Join { - type Result = (); + type Output = (); + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { conn.execute( " INSERT INTO euph_rooms (room, first_joined, last_joined) @@ -261,9 +265,10 @@ impl Action for Join { } impl Action for Delete { - type Result = (); + type Output = (); + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { conn.execute( " DELETE FROM euph_rooms @@ -431,9 +436,10 @@ fn add_span( } impl Action for AddMsg { - type Result = (); + type Output = (); + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let tx = conn.transaction()?; let end = self.msg.id; @@ -446,9 +452,10 @@ impl Action for AddMsg { } impl Action for AddMsgs { - type Result = (); + type Output = (); + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let tx = conn.transaction()?; if self.msgs.is_empty() { @@ -469,9 +476,10 @@ impl Action for AddMsgs { } impl Action for GetLastSpan { - type Result = Option<(Option<MessageId>, Option<MessageId>)>; + type Output = Option<(Option<MessageId>, Option<MessageId>)>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let span = conn .prepare( " @@ -494,9 +502,10 @@ impl Action for GetLastSpan { } impl Action for GetPath { - type Result = Path<MessageId>; + type Output = Path<MessageId>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let path = conn .prepare( " @@ -523,9 +532,10 @@ impl Action for GetPath { } impl Action for GetMsg { - type Result = Option<SmallMessage>; + type Output = Option<SmallMessage>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let msg = conn .query_row( " @@ -552,9 +562,10 @@ impl Action for GetMsg { } impl Action for GetFullMsg { - type Result = Option<Message>; + type Output = Option<Message>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let mut query = conn.prepare( " SELECT @@ -597,9 +608,10 @@ impl Action for GetFullMsg { } impl Action for GetTree { - type Result = Tree<SmallMessage>; + type Output = Tree<SmallMessage>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let msgs = conn .prepare( " @@ -635,9 +647,10 @@ impl Action for GetTree { } impl Action for GetFirstRootId { - type Result = Option<MessageId>; + type Output = Option<MessageId>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let root_id = conn .prepare( " @@ -657,9 +670,10 @@ impl Action for GetFirstRootId { } impl Action for GetLastRootId { - type Result = Option<MessageId>; + type Output = Option<MessageId>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let root_id = conn .prepare( " @@ -679,9 +693,10 @@ impl Action for GetLastRootId { } impl Action for GetPrevRootId { - type Result = Option<MessageId>; + type Output = Option<MessageId>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let root_id = conn .prepare( " @@ -702,9 +717,10 @@ impl Action for GetPrevRootId { } impl Action for GetNextRootId { - type Result = Option<MessageId>; + type Output = Option<MessageId>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let root_id = conn .prepare( " @@ -725,9 +741,10 @@ impl Action for GetNextRootId { } impl Action for GetOldestMsgId { - type Result = Option<MessageId>; + type Output = Option<MessageId>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let msg_id = conn .prepare( " @@ -747,9 +764,10 @@ impl Action for GetOldestMsgId { } impl Action for GetNewestMsgId { - type Result = Option<MessageId>; + type Output = Option<MessageId>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let msg_id = conn .prepare( " @@ -769,9 +787,10 @@ impl Action for GetNewestMsgId { } impl Action for GetOlderMsgId { - type Result = Option<MessageId>; + type Output = Option<MessageId>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let msg_id = conn .prepare( " @@ -791,9 +810,10 @@ impl Action for GetOlderMsgId { } } impl Action for GetNewerMsgId { - type Result = Option<MessageId>; + type Output = Option<MessageId>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let msg_id = conn .prepare( " @@ -814,9 +834,10 @@ impl Action for GetNewerMsgId { } impl Action for GetOldestUnseenMsgId { - type Result = Option<MessageId>; + type Output = Option<MessageId>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let msg_id = conn .prepare( " @@ -837,9 +858,10 @@ impl Action for GetOldestUnseenMsgId { } impl Action for GetNewestUnseenMsgId { - type Result = Option<MessageId>; + type Output = Option<MessageId>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let msg_id = conn .prepare( " @@ -860,9 +882,10 @@ impl Action for GetNewestUnseenMsgId { } impl Action for GetOlderUnseenMsgId { - type Result = Option<MessageId>; + type Output = Option<MessageId>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let msg_id = conn .prepare( " @@ -884,9 +907,10 @@ impl Action for GetOlderUnseenMsgId { } impl Action for GetNewerUnseenMsgId { - type Result = Option<MessageId>; + type Output = Option<MessageId>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let msg_id = conn .prepare( " @@ -908,9 +932,10 @@ impl Action for GetNewerUnseenMsgId { } impl Action for GetUnseenMsgsCount { - type Result = usize; + type Output = usize; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { let amount = conn .prepare( " @@ -927,9 +952,10 @@ impl Action for GetUnseenMsgsCount { } impl Action for SetSeen { - type Result = (); + type Output = (); + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { conn.execute( " UPDATE euph_msgs @@ -944,9 +970,10 @@ impl Action for SetSeen { } impl Action for SetOlderSeen { - type Result = (); + type Output = (); + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { conn.execute( " UPDATE euph_msgs @@ -962,9 +989,10 @@ impl Action for SetOlderSeen { } impl Action for GetChunkAfter { - type Result = Vec<Message>; + type Output = Vec<Message>; + type Error = rusqlite::Error; - fn run(self, conn: &mut Connection) -> rusqlite::Result<Self::Result> { + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { fn row2msg(row: &Row<'_>) -> rusqlite::Result<Message> { Ok(Message { id: MessageId(row.get::<_, WSnowflake>(0)?.0), @@ -1023,7 +1051,7 @@ impl Action for GetChunkAfter { #[async_trait] impl MsgStore<SmallMessage> for EuphRoomVault { - type Error = vault::tokio::Error; + type Error = vault::tokio::Error<rusqlite::Error>; async fn path(&self, id: &MessageId) -> Result<Path<MessageId>, Self::Error> { self.path(*id).await From 3fb774e93ea6abd5c98f84105ee706a3de2f4f07 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 20 Apr 2023 20:48:11 +0200 Subject: [PATCH 092/266] Remove stray crash --- src/ui/chat.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ui/chat.rs b/src/ui/chat.rs index 9e243e2..a8acdea 100644 --- a/src/ui/chat.rs +++ b/src/ui/chat.rs @@ -82,7 +82,6 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> { .tree .list_key_bindings(bindings, &self.cursor, can_compose), } - todo!() } pub async fn handle_input_event( From 027bf489b70bd6c886f559fc8675603961915d4c Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 20 Apr 2023 20:52:41 +0200 Subject: [PATCH 093/266] Fix key binding listing spacing --- src/ui/input.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ui/input.rs b/src/ui/input.rs index c91789b..85b5f1f 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -86,7 +86,7 @@ pub struct KeyBindingsList(Vec<Row>); impl KeyBindingsList { /// Width of the left column of key bindings. - const BINDING_WIDTH: u16 = 20; + const BINDING_WIDTH: u16 = 24; pub fn new() -> Self { Self(vec![]) @@ -108,13 +108,17 @@ impl KeyBindingsList { .with_right(1) .resize() .with_min_width(Self::BINDING_WIDTH) - .segment(), + .segment() + .with_fixed(true), Text::new(description).segment(), ) .second3(), Row::BindingContd(description) => Join2::horizontal( - Empty::new().with_width(Self::BINDING_WIDTH).segment(), + Empty::new() + .with_width(Self::BINDING_WIDTH) + .segment() + .with_fixed(true), Text::new(description).segment(), ) .third3(), From 502ebab132572f5649113dcb07f81055ecda91ef Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 21 Apr 2023 01:28:55 +0200 Subject: [PATCH 094/266] Fix scroll offset estimation --- src/ui/chat/tree/renderer.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ui/chat/tree/renderer.rs b/src/ui/chat/tree/renderer.rs index a48a4ab..5e1a876 100644 --- a/src/ui/chat/tree/renderer.rs +++ b/src/ui/chat/tree/renderer.rs @@ -283,6 +283,14 @@ where } } + fn root_id_is_above_root_id(first: Option<M::Id>, second: Option<M::Id>) -> bool { + match (first, second) { + (Some(_), None) => true, + (Some(a), Some(b)) => a < b, + _ => false, + } + } + pub async fn prepare_blocks_for_drawing(&mut self) -> Result<(), S::Error> { let cursor_id = TreeBlockId::from_cursor(self.cursor); let cursor_root_id = self.root_id(&cursor_id).await?; @@ -298,8 +306,8 @@ where // Since the last cursor is not within scrolling distance of our // current cursor, we need to estimate whether the last cursor was // above or below the current cursor. - let last_cursor_root_id = self.root_id(&cursor_id).await?; - if last_cursor_root_id <= cursor_root_id { + let last_cursor_root_id = self.root_id(&last_cursor_id).await?; + if Self::root_id_is_above_root_id(last_cursor_root_id, cursor_root_id) { renderer::scroll_blocks_fully_below_screen(self); } else { renderer::scroll_blocks_fully_above_screen(self); From babdd10fba992fcbd4b11e1e0260aa50da7420de Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 21 Apr 2023 02:15:17 +0200 Subject: [PATCH 095/266] Fix docstrings --- src/ui/chat/tree/renderer.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ui/chat/tree/renderer.rs b/src/ui/chat/tree/renderer.rs index 5e1a876..b7d358b 100644 --- a/src/ui/chat/tree/renderer.rs +++ b/src/ui/chat/tree/renderer.rs @@ -1,4 +1,4 @@ -//! A [`BlockProvider`] for message trees. +//! A [`Renderer`] for message trees. use std::convert::Infallible; @@ -100,8 +100,8 @@ where S: MsgStore<M> + Send + Sync, S::Error: Send, { - /// You must call [`Self::prepare_blocks`] immediately after calling - /// this function. + /// You must call [`Self::prepare_blocks_for_drawing`] immediately after + /// calling this function. pub fn new( context: TreeContext<M::Id>, store: &'a S, From 288a5f97dd71215c388bae2d9f2638a3d7bc78b4 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 19 Apr 2023 23:43:03 +0200 Subject: [PATCH 096/266] Set up workspace --- Cargo.toml | 60 ++-------------------- cove/Cargo.toml | 57 ++++++++++++++++++++ {src => cove/src}/config.rs | 0 {src => cove/src}/euph.rs | 0 {src => cove/src}/euph/room.rs | 0 {src => cove/src}/euph/small_message.rs | 0 {src => cove/src}/euph/util.rs | 0 {src => cove/src}/export.rs | 0 {src => cove/src}/export/json.rs | 0 {src => cove/src}/export/text.rs | 0 {src => cove/src}/logger.rs | 0 {src => cove/src}/macros.rs | 0 {src => cove/src}/main.rs | 0 {src => cove/src}/store.rs | 0 {src => cove/src}/ui.rs | 0 {src => cove/src}/ui/chat.rs | 0 {src => cove/src}/ui/chat/blocks.rs | 0 {src => cove/src}/ui/chat/cursor.rs | 0 {src => cove/src}/ui/chat/renderer.rs | 0 {src => cove/src}/ui/chat/tree.rs | 0 {src => cove/src}/ui/chat/tree/renderer.rs | 0 {src => cove/src}/ui/chat/tree/scroll.rs | 0 {src => cove/src}/ui/chat/tree/widgets.rs | 0 {src => cove/src}/ui/chat/widgets.rs | 0 {src => cove/src}/ui/euph.rs | 0 {src => cove/src}/ui/euph/account.rs | 0 {src => cove/src}/ui/euph/auth.rs | 0 {src => cove/src}/ui/euph/inspect.rs | 0 {src => cove/src}/ui/euph/links.rs | 0 {src => cove/src}/ui/euph/nick.rs | 0 {src => cove/src}/ui/euph/nick_list.rs | 0 {src => cove/src}/ui/euph/popup.rs | 0 {src => cove/src}/ui/euph/room.rs | 0 {src => cove/src}/ui/input.rs | 0 {src => cove/src}/ui/rooms.rs | 0 {src => cove/src}/ui/util.rs | 0 {src => cove/src}/ui/widgets.rs | 0 {src => cove/src}/ui/widgets/list.rs | 0 {src => cove/src}/ui/widgets/popup.rs | 0 {src => cove/src}/util.rs | 0 {src => cove/src}/vault.rs | 0 {src => cove/src}/vault/euph.rs | 0 {src => cove/src}/vault/migrate.rs | 0 {src => cove/src}/vault/prepare.rs | 0 flake.nix | 7 ++- 45 files changed, 68 insertions(+), 56 deletions(-) create mode 100644 cove/Cargo.toml rename {src => cove/src}/config.rs (100%) rename {src => cove/src}/euph.rs (100%) rename {src => cove/src}/euph/room.rs (100%) rename {src => cove/src}/euph/small_message.rs (100%) rename {src => cove/src}/euph/util.rs (100%) rename {src => cove/src}/export.rs (100%) rename {src => cove/src}/export/json.rs (100%) rename {src => cove/src}/export/text.rs (100%) rename {src => cove/src}/logger.rs (100%) rename {src => cove/src}/macros.rs (100%) rename {src => cove/src}/main.rs (100%) rename {src => cove/src}/store.rs (100%) rename {src => cove/src}/ui.rs (100%) rename {src => cove/src}/ui/chat.rs (100%) rename {src => cove/src}/ui/chat/blocks.rs (100%) rename {src => cove/src}/ui/chat/cursor.rs (100%) rename {src => cove/src}/ui/chat/renderer.rs (100%) rename {src => cove/src}/ui/chat/tree.rs (100%) rename {src => cove/src}/ui/chat/tree/renderer.rs (100%) rename {src => cove/src}/ui/chat/tree/scroll.rs (100%) rename {src => cove/src}/ui/chat/tree/widgets.rs (100%) rename {src => cove/src}/ui/chat/widgets.rs (100%) rename {src => cove/src}/ui/euph.rs (100%) rename {src => cove/src}/ui/euph/account.rs (100%) rename {src => cove/src}/ui/euph/auth.rs (100%) rename {src => cove/src}/ui/euph/inspect.rs (100%) rename {src => cove/src}/ui/euph/links.rs (100%) rename {src => cove/src}/ui/euph/nick.rs (100%) rename {src => cove/src}/ui/euph/nick_list.rs (100%) rename {src => cove/src}/ui/euph/popup.rs (100%) rename {src => cove/src}/ui/euph/room.rs (100%) rename {src => cove/src}/ui/input.rs (100%) rename {src => cove/src}/ui/rooms.rs (100%) rename {src => cove/src}/ui/util.rs (100%) rename {src => cove/src}/ui/widgets.rs (100%) rename {src => cove/src}/ui/widgets/list.rs (100%) rename {src => cove/src}/ui/widgets/popup.rs (100%) rename {src => cove/src}/util.rs (100%) rename {src => cove/src}/vault.rs (100%) rename {src => cove/src}/vault/euph.rs (100%) rename {src => cove/src}/vault/migrate.rs (100%) rename {src => cove/src}/vault/prepare.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 61abd7f..e47762f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,60 +1,10 @@ -[package] -name = "cove" +[workspace] +resolver = "2" +members = ["cove"] + +[workspace.package] version = "0.6.1" edition = "2021" -[dependencies] -anyhow = "1.0.70" -async-trait = "0.1.68" -clap = { version = "4.2.1", features = ["derive", "deprecated"] } -cookie = "0.17.0" -crossterm = "0.26.1" -directories = "5.0.0" -edit = "0.1.4" -linkify = "0.9.0" -log = { version = "0.4.17", features = ["std"] } -once_cell = "1.17.1" -open = "4.0.1" -parking_lot = "0.12.1" -rusqlite = { version = "0.29.0", features = ["bundled", "time"] } -serde = { version = "1.0.159", features = ["derive"] } -serde_json = "1.0.95" -thiserror = "1.0.40" -tokio = { version = "1.27.0", features = ["full"] } -toml = "0.7.3" -unicode-segmentation = "1.10.1" -unicode-width = "0.1.10" - -[dependencies.time] -version = "0.3.20" -features = ["macros", "formatting", "parsing", "serde"] - -[dependencies.tokio-tungstenite] -version = "0.18.0" -features = ["rustls-tls-native-roots"] - -[dependencies.euphoxide] -git = "https://github.com/Garmelon/euphoxide.git" -rev = "0f217a6279181b0731216760219e8ff0fa01e449" -features = ["bot"] - -# [patch."https://github.com/Garmelon/euphoxide.git"] -# euphoxide = { path = "../euphoxide/" } - -[dependencies.toss] -git = "https://github.com/Garmelon/toss.git" -rev = "f414db40d526295c74cbcae6c3d194088da8f1d9" - -# [patch."https://github.com/Garmelon/toss.git"] -# toss = { path = "../toss/" } - -[dependencies.vault] -git = "https://github.com/Garmelon/vault.git" -rev = "b4cf23b7279770226725c895e482c8eda88c43a7" -features = ["tokio"] - -# [patch."https://github.com/Garmelon/vault.git"] -# vault = { path = "../vault/" } - [profile.dev.package."*"] opt-level = 3 diff --git a/cove/Cargo.toml b/cove/Cargo.toml new file mode 100644 index 0000000..5821137 --- /dev/null +++ b/cove/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "cove" +version = { workspace = true } +edition = { workspace = true } + +[dependencies] +anyhow = "1.0.70" +async-trait = "0.1.68" +clap = { version = "4.2.1", features = ["derive", "deprecated"] } +cookie = "0.17.0" +crossterm = "0.26.1" +directories = "5.0.0" +edit = "0.1.4" +linkify = "0.9.0" +log = { version = "0.4.17", features = ["std"] } +once_cell = "1.17.1" +open = "4.0.1" +parking_lot = "0.12.1" +rusqlite = { version = "0.29.0", features = ["bundled", "time"] } +serde = { version = "1.0.159", features = ["derive"] } +serde_json = "1.0.95" +thiserror = "1.0.40" +tokio = { version = "1.27.0", features = ["full"] } +toml = "0.7.3" +unicode-segmentation = "1.10.1" +unicode-width = "0.1.10" + +[dependencies.time] +version = "0.3.20" +features = ["macros", "formatting", "parsing", "serde"] + +[dependencies.tokio-tungstenite] +version = "0.18.0" +features = ["rustls-tls-native-roots"] + +[dependencies.euphoxide] +git = "https://github.com/Garmelon/euphoxide.git" +rev = "0f217a6279181b0731216760219e8ff0fa01e449" +features = ["bot"] + +# [patch."https://github.com/Garmelon/euphoxide.git"] +# euphoxide = { path = "../euphoxide/" } + +[dependencies.toss] +git = "https://github.com/Garmelon/toss.git" +rev = "f414db40d526295c74cbcae6c3d194088da8f1d9" + +# [patch."https://github.com/Garmelon/toss.git"] +# toss = { path = "../toss/" } + +[dependencies.vault] +git = "https://github.com/Garmelon/vault.git" +rev = "b4cf23b7279770226725c895e482c8eda88c43a7" +features = ["tokio"] + +# [patch."https://github.com/Garmelon/vault.git"] +# vault = { path = "../vault/" } diff --git a/src/config.rs b/cove/src/config.rs similarity index 100% rename from src/config.rs rename to cove/src/config.rs diff --git a/src/euph.rs b/cove/src/euph.rs similarity index 100% rename from src/euph.rs rename to cove/src/euph.rs diff --git a/src/euph/room.rs b/cove/src/euph/room.rs similarity index 100% rename from src/euph/room.rs rename to cove/src/euph/room.rs diff --git a/src/euph/small_message.rs b/cove/src/euph/small_message.rs similarity index 100% rename from src/euph/small_message.rs rename to cove/src/euph/small_message.rs diff --git a/src/euph/util.rs b/cove/src/euph/util.rs similarity index 100% rename from src/euph/util.rs rename to cove/src/euph/util.rs diff --git a/src/export.rs b/cove/src/export.rs similarity index 100% rename from src/export.rs rename to cove/src/export.rs diff --git a/src/export/json.rs b/cove/src/export/json.rs similarity index 100% rename from src/export/json.rs rename to cove/src/export/json.rs diff --git a/src/export/text.rs b/cove/src/export/text.rs similarity index 100% rename from src/export/text.rs rename to cove/src/export/text.rs diff --git a/src/logger.rs b/cove/src/logger.rs similarity index 100% rename from src/logger.rs rename to cove/src/logger.rs diff --git a/src/macros.rs b/cove/src/macros.rs similarity index 100% rename from src/macros.rs rename to cove/src/macros.rs diff --git a/src/main.rs b/cove/src/main.rs similarity index 100% rename from src/main.rs rename to cove/src/main.rs diff --git a/src/store.rs b/cove/src/store.rs similarity index 100% rename from src/store.rs rename to cove/src/store.rs diff --git a/src/ui.rs b/cove/src/ui.rs similarity index 100% rename from src/ui.rs rename to cove/src/ui.rs diff --git a/src/ui/chat.rs b/cove/src/ui/chat.rs similarity index 100% rename from src/ui/chat.rs rename to cove/src/ui/chat.rs diff --git a/src/ui/chat/blocks.rs b/cove/src/ui/chat/blocks.rs similarity index 100% rename from src/ui/chat/blocks.rs rename to cove/src/ui/chat/blocks.rs diff --git a/src/ui/chat/cursor.rs b/cove/src/ui/chat/cursor.rs similarity index 100% rename from src/ui/chat/cursor.rs rename to cove/src/ui/chat/cursor.rs diff --git a/src/ui/chat/renderer.rs b/cove/src/ui/chat/renderer.rs similarity index 100% rename from src/ui/chat/renderer.rs rename to cove/src/ui/chat/renderer.rs diff --git a/src/ui/chat/tree.rs b/cove/src/ui/chat/tree.rs similarity index 100% rename from src/ui/chat/tree.rs rename to cove/src/ui/chat/tree.rs diff --git a/src/ui/chat/tree/renderer.rs b/cove/src/ui/chat/tree/renderer.rs similarity index 100% rename from src/ui/chat/tree/renderer.rs rename to cove/src/ui/chat/tree/renderer.rs diff --git a/src/ui/chat/tree/scroll.rs b/cove/src/ui/chat/tree/scroll.rs similarity index 100% rename from src/ui/chat/tree/scroll.rs rename to cove/src/ui/chat/tree/scroll.rs diff --git a/src/ui/chat/tree/widgets.rs b/cove/src/ui/chat/tree/widgets.rs similarity index 100% rename from src/ui/chat/tree/widgets.rs rename to cove/src/ui/chat/tree/widgets.rs diff --git a/src/ui/chat/widgets.rs b/cove/src/ui/chat/widgets.rs similarity index 100% rename from src/ui/chat/widgets.rs rename to cove/src/ui/chat/widgets.rs diff --git a/src/ui/euph.rs b/cove/src/ui/euph.rs similarity index 100% rename from src/ui/euph.rs rename to cove/src/ui/euph.rs diff --git a/src/ui/euph/account.rs b/cove/src/ui/euph/account.rs similarity index 100% rename from src/ui/euph/account.rs rename to cove/src/ui/euph/account.rs diff --git a/src/ui/euph/auth.rs b/cove/src/ui/euph/auth.rs similarity index 100% rename from src/ui/euph/auth.rs rename to cove/src/ui/euph/auth.rs diff --git a/src/ui/euph/inspect.rs b/cove/src/ui/euph/inspect.rs similarity index 100% rename from src/ui/euph/inspect.rs rename to cove/src/ui/euph/inspect.rs diff --git a/src/ui/euph/links.rs b/cove/src/ui/euph/links.rs similarity index 100% rename from src/ui/euph/links.rs rename to cove/src/ui/euph/links.rs diff --git a/src/ui/euph/nick.rs b/cove/src/ui/euph/nick.rs similarity index 100% rename from src/ui/euph/nick.rs rename to cove/src/ui/euph/nick.rs diff --git a/src/ui/euph/nick_list.rs b/cove/src/ui/euph/nick_list.rs similarity index 100% rename from src/ui/euph/nick_list.rs rename to cove/src/ui/euph/nick_list.rs diff --git a/src/ui/euph/popup.rs b/cove/src/ui/euph/popup.rs similarity index 100% rename from src/ui/euph/popup.rs rename to cove/src/ui/euph/popup.rs diff --git a/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs similarity index 100% rename from src/ui/euph/room.rs rename to cove/src/ui/euph/room.rs diff --git a/src/ui/input.rs b/cove/src/ui/input.rs similarity index 100% rename from src/ui/input.rs rename to cove/src/ui/input.rs diff --git a/src/ui/rooms.rs b/cove/src/ui/rooms.rs similarity index 100% rename from src/ui/rooms.rs rename to cove/src/ui/rooms.rs diff --git a/src/ui/util.rs b/cove/src/ui/util.rs similarity index 100% rename from src/ui/util.rs rename to cove/src/ui/util.rs diff --git a/src/ui/widgets.rs b/cove/src/ui/widgets.rs similarity index 100% rename from src/ui/widgets.rs rename to cove/src/ui/widgets.rs diff --git a/src/ui/widgets/list.rs b/cove/src/ui/widgets/list.rs similarity index 100% rename from src/ui/widgets/list.rs rename to cove/src/ui/widgets/list.rs diff --git a/src/ui/widgets/popup.rs b/cove/src/ui/widgets/popup.rs similarity index 100% rename from src/ui/widgets/popup.rs rename to cove/src/ui/widgets/popup.rs diff --git a/src/util.rs b/cove/src/util.rs similarity index 100% rename from src/util.rs rename to cove/src/util.rs diff --git a/src/vault.rs b/cove/src/vault.rs similarity index 100% rename from src/vault.rs rename to cove/src/vault.rs diff --git a/src/vault/euph.rs b/cove/src/vault/euph.rs similarity index 100% rename from src/vault/euph.rs rename to cove/src/vault/euph.rs diff --git a/src/vault/migrate.rs b/cove/src/vault/migrate.rs similarity index 100% rename from src/vault/migrate.rs rename to cove/src/vault/migrate.rs diff --git a/src/vault/prepare.rs b/cove/src/vault/prepare.rs similarity index 100% rename from src/vault/prepare.rs rename to cove/src/vault/prepare.rs diff --git a/flake.nix b/flake.nix index 68a8c1a..707e335 100644 --- a/flake.nix +++ b/flake.nix @@ -15,9 +15,14 @@ let pkgs = import nixpkgs { inherit system; }; naersk' = pkgs.callPackage naersk { }; + cargoToml = pkgs.lib.importTOML ./Cargo.toml; in { - default = naersk'.buildPackage { src = ./.; }; + default = naersk'.buildPackage { + name = "cove"; + version = cargoToml.workspace.package.version; + root = ./.; + }; } ); }; From 5b5370d2dffd59de2c67dd4f0c2a229ff37c4556 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 20 Apr 2023 14:11:32 +0200 Subject: [PATCH 097/266] Extract config into cove-config crate --- Cargo.lock | 11 +++++++++-- Cargo.toml | 2 +- cove-config/Cargo.toml | 8 ++++++++ cove/src/config.rs => cove-config/src/lib.rs | 15 ++++++++++++--- cove/Cargo.toml | 4 ++-- cove/src/main.rs | 3 +-- cove/src/ui.rs | 2 +- cove/src/ui/euph/room.rs | 5 ++--- cove/src/ui/rooms.rs | 2 +- 9 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 cove-config/Cargo.toml rename cove/src/config.rs => cove-config/src/lib.rs (81%) diff --git a/Cargo.lock b/Cargo.lock index 540a3cd..d81e93c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,6 +249,7 @@ dependencies = [ "async-trait", "clap", "cookie", + "cove-config", "crossterm", "directories", "edit", @@ -259,19 +260,25 @@ dependencies = [ "open", "parking_lot", "rusqlite", - "serde", "serde_json", "thiserror", "time", "tokio", "tokio-tungstenite", - "toml", "toss", "unicode-segmentation", "unicode-width", "vault", ] +[[package]] +name = "cove-config" +version = "0.6.1" +dependencies = [ + "serde", + "toml", +] + [[package]] name = "cpufeatures" version = "0.2.6" diff --git a/Cargo.toml b/Cargo.toml index e47762f..2873003 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["cove"] +members = ["cove", "cove-*"] [workspace.package] version = "0.6.1" diff --git a/cove-config/Cargo.toml b/cove-config/Cargo.toml new file mode 100644 index 0000000..2e21781 --- /dev/null +++ b/cove-config/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "cove-config" +version = { workspace = true } +edition = { workspace = true } + +[dependencies] +serde = { version = "1.0.159", features = ["derive"] } +toml = "0.7.3" diff --git a/cove/src/config.rs b/cove-config/src/lib.rs similarity index 81% rename from cove/src/config.rs rename to cove-config/src/lib.rs index 2a8d13f..2beb8db 100644 --- a/cove/src/config.rs +++ b/cove-config/src/lib.rs @@ -1,11 +1,20 @@ +#![forbid(unsafe_code)] +// Rustc lint groups +#![warn(future_incompatible)] +#![warn(rust_2018_idioms)] +#![warn(unused)] +// Rustc lints +#![warn(noop_method_call)] +#![warn(single_use_lifetimes)] +// Clippy lints +#![warn(clippy::use_self)] + use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; use serde::Deserialize; -use crate::macros::ok_or_return; - #[derive(Debug, Clone, Copy, Default, Deserialize)] #[serde(rename_all = "snake_case")] pub enum RoomsSortOrder { @@ -45,7 +54,7 @@ pub struct Config { impl Config { pub fn load(path: &Path) -> Self { - let content = ok_or_return!(fs::read_to_string(path), Self::default()); + let Ok(content) = fs::read_to_string(path) else { return Self::default(); }; match toml::from_str(&content) { Ok(config) => config, Err(err) => { diff --git a/cove/Cargo.toml b/cove/Cargo.toml index 5821137..123b7c9 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -4,6 +4,8 @@ version = { workspace = true } edition = { workspace = true } [dependencies] +cove-config = { path = "../cove-config" } + anyhow = "1.0.70" async-trait = "0.1.68" clap = { version = "4.2.1", features = ["derive", "deprecated"] } @@ -17,11 +19,9 @@ once_cell = "1.17.1" open = "4.0.1" parking_lot = "0.12.1" rusqlite = { version = "0.29.0", features = ["bundled", "time"] } -serde = { version = "1.0.159", features = ["derive"] } serde_json = "1.0.95" thiserror = "1.0.40" tokio = { version = "1.27.0", features = ["full"] } -toml = "0.7.3" unicode-segmentation = "1.10.1" unicode-width = "0.1.10" diff --git a/cove/src/main.rs b/cove/src/main.rs index d5a402b..494d710 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -14,7 +14,6 @@ // TODO Time zones other than UTC // TODO Fix password room auth -mod config; mod euph; mod export; mod logger; @@ -28,12 +27,12 @@ use std::path::PathBuf; use clap::Parser; use cookie::CookieJar; +use cove_config::Config; use directories::{BaseDirs, ProjectDirs}; use log::info; use tokio::sync::mpsc; use toss::Terminal; -use crate::config::Config; use crate::logger::Logger; use crate::ui::Ui; use crate::vault::Vault; diff --git a/cove/src/ui.rs b/cove/src/ui.rs index 42235bf..1e9d55c 100644 --- a/cove/src/ui.rs +++ b/cove/src/ui.rs @@ -10,6 +10,7 @@ use std::io; use std::sync::{Arc, Weak}; use std::time::{Duration, Instant}; +use cove_config::Config; use parking_lot::FairMutex; use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; @@ -17,7 +18,6 @@ use tokio::task; use toss::widgets::BoxedAsync; use toss::{Terminal, WidgetExt}; -use crate::config::Config; use crate::logger::{LogMsg, Logger}; use crate::macros::{logging_unwrap, ok_or_return, some_or_return}; use crate::util::InfallibleExt; diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index a03e6e1..8dc6ed0 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -11,7 +11,6 @@ use tokio::sync::{mpsc, oneshot}; use toss::widgets::{BoxedAsync, EditorState, Join2, Layer, Text}; use toss::{Style, Styled, Terminal, Widget, WidgetExt}; -use crate::config; use crate::euph; use crate::macros::logging_unwrap; use crate::ui::chat::{ChatState, Reaction}; @@ -46,7 +45,7 @@ type EuphChatState = ChatState<euph::SmallMessage, EuphRoomVault>; pub struct EuphRoom { server_config: ServerConfig, - config: config::EuphRoom, + config: cove_config::EuphRoom, ui_event_tx: mpsc::UnboundedSender<UiEvent>, room: Option<euph::Room>, @@ -64,7 +63,7 @@ pub struct EuphRoom { impl EuphRoom { pub fn new( server_config: ServerConfig, - config: config::EuphRoom, + config: cove_config::EuphRoom, vault: EuphRoomVault, ui_event_tx: mpsc::UnboundedSender<UiEvent>, ) -> Self { diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index 8a347e2..b401372 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet}; use std::iter; use std::sync::{Arc, Mutex}; +use cove_config::{Config, RoomsSortOrder}; use crossterm::style::Stylize; use euphoxide::api::SessionType; use euphoxide::bot::instance::{Event, ServerConfig}; @@ -11,7 +12,6 @@ use tokio::sync::mpsc; use toss::widgets::{BoxedAsync, EditorState, Empty, Join2, Text}; use toss::{Style, Styled, Terminal, Widget, WidgetExt}; -use crate::config::{Config, RoomsSortOrder}; use crate::euph; use crate::macros::logging_unwrap; use crate::vault::Vault; From dfb2ef5371092c68e092099fd6779246a6200169 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 20 Apr 2023 14:14:55 +0200 Subject: [PATCH 098/266] Set up cove-macro proc macro crate --- Cargo.lock | 23 ++++++++++++++++------- cove-macro/Cargo.toml | 12 ++++++++++++ cove-macro/src/lib.rs | 10 ++++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 cove-macro/Cargo.toml create mode 100644 cove-macro/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d81e93c..01e8775 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,7 +76,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -191,7 +191,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -279,6 +279,15 @@ dependencies = [ "toml", ] +[[package]] +name = "cove-macro" +version = "0.6.1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + [[package]] name = "cpufeatures" version = "0.2.6" @@ -1023,7 +1032,7 @@ checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -1137,9 +1146,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", @@ -1176,7 +1185,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -1248,7 +1257,7 @@ checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] diff --git a/cove-macro/Cargo.toml b/cove-macro/Cargo.toml new file mode 100644 index 0000000..66ec539 --- /dev/null +++ b/cove-macro/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cove-macro" +version = { workspace = true } +edition = { workspace = true } + +[dependencies] +proc-macro2 = "1.0.56" +quote = "1.0.26" +syn = "2.0.15" + +[lib] +proc-macro = true diff --git a/cove-macro/src/lib.rs b/cove-macro/src/lib.rs new file mode 100644 index 0000000..f561415 --- /dev/null +++ b/cove-macro/src/lib.rs @@ -0,0 +1,10 @@ +#![forbid(unsafe_code)] +// Rustc lint groups +#![warn(future_incompatible)] +#![warn(rust_2018_idioms)] +#![warn(unused)] +// Rustc lints +#![warn(noop_method_call)] +#![warn(single_use_lifetimes)] +// Clippy lints +#![warn(clippy::use_self)] From cedeeff10b6b02f377a21bc5912adc748dc5b55d Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 20 Apr 2023 14:16:47 +0200 Subject: [PATCH 099/266] Add Document trait for config doc generation --- cove-config/src/doc.rs | 190 +++++++++++++++++++++++++++++++++++++++++ cove-config/src/lib.rs | 2 + 2 files changed, 192 insertions(+) create mode 100644 cove-config/src/doc.rs diff --git a/cove-config/src/doc.rs b/cove-config/src/doc.rs new file mode 100644 index 0000000..dc232f4 --- /dev/null +++ b/cove-config/src/doc.rs @@ -0,0 +1,190 @@ +use std::collections::HashMap; +use std::path::PathBuf; + +#[derive(Clone, Default)] +pub struct ValueInfo { + pub required: Option<bool>, + pub r#type: Option<String>, + pub values: Option<Vec<String>>, + pub default: Option<String>, +} + +impl ValueInfo { + fn as_markdown(&self) -> String { + let mut lines = vec![]; + + if let Some(required) = self.required { + let yesno = if required { "yes" } else { "no" }; + lines.push(format!("**Required:** {yesno}")); + } + + if let Some(r#type) = &self.r#type { + lines.push(format!("**Type:** {type}")); + } + + if let Some(values) = &self.values { + let values = values.join(", "); + lines.push(format!("**Values:** {values}")); + } + + if let Some(default) = &self.default { + lines.push(format!("**Default:** {default}")); + } + + lines.join(" \n") + } +} + +#[derive(Clone, Default)] +pub struct StructInfo { + pub fields: HashMap<String, Box<Doc>>, +} + +#[derive(Clone, Default)] +pub struct WrapInfo { + pub inner: Option<Box<Doc>>, + pub metavar: Option<String>, +} + +#[derive(Clone, Default)] +pub struct Doc { + pub description: Option<String>, + + pub value_info: ValueInfo, + pub struct_info: StructInfo, + pub wrap_info: WrapInfo, +} + +struct Entry { + path: String, + description: String, + value_info: ValueInfo, +} + +impl Entry { + fn new(description: String, value_info: ValueInfo) -> Self { + Self { + path: String::new(), + description, + value_info, + } + } + + fn with_parent(mut self, segment: String) -> Self { + if self.path.is_empty() { + self.path = segment; + } else { + self.path = format!("{segment}.{}", self.path); + } + self + } +} + +impl Doc { + fn entries(&self) -> Vec<Entry> { + let mut entries = vec![]; + + if let Some(description) = &self.description { + entries.push(Entry::new(description.clone(), self.value_info.clone())); + } + + for (segment, field) in &self.struct_info.fields { + entries.extend( + field + .entries() + .into_iter() + .map(|entry| entry.with_parent(segment.clone())), + ); + } + + if let Some(inner) = &self.wrap_info.inner { + let segment = match &self.wrap_info.metavar { + Some(metavar) => format!("<{metavar}>"), + None => "<...>".to_string(), + }; + entries.extend( + inner + .entries() + .into_iter() + .map(|entry| entry.with_parent(segment.clone())), + ); + } + + entries + } + + pub fn format_as_markdown(&self) -> String { + // Print entries in alphabetical order to make generated documentation + // format more stable. + let mut entries = self.entries(); + entries.sort_unstable_by(|a, b| a.path.cmp(&b.path)); + + let mut result = String::new(); + + result.push_str("# Configuration options\n\n"); + result.push_str("Cove's config file uses the [TOML](https://toml.io/) format.\n"); + + for entry in entries { + result.push_str(&format!("\n## `{}`\n", entry.path)); + + let value_info = entry.value_info.as_markdown(); + if !value_info.is_empty() { + result.push_str(&format!("\n{value_info}\n")); + } + + if !entry.description.is_empty() { + result.push_str(&format!("\n{}\n", entry.description)); + } + } + + result + } +} + +pub trait Document { + fn doc() -> Doc; +} + +impl Document for String { + fn doc() -> Doc { + let mut doc = Doc::default(); + doc.value_info.required = Some(true); + doc.value_info.r#type = Some("string".to_string()); + doc + } +} + +impl Document for bool { + fn doc() -> Doc { + let mut doc = Doc::default(); + doc.value_info.required = Some(true); + doc.value_info.r#type = Some("boolean".to_string()); + doc + } +} + +impl Document for PathBuf { + fn doc() -> Doc { + let mut doc = Doc::default(); + doc.value_info.required = Some(true); + doc.value_info.r#type = Some("path".to_string()); + doc + } +} + +impl<I: Document> Document for Option<I> { + fn doc() -> Doc { + let mut doc = I::doc(); + assert_eq!(doc.value_info.required, Some(true)); + doc.value_info.required = Some(false); + doc + } +} + +impl<I: Document> Document for HashMap<String, I> { + fn doc() -> Doc { + let mut doc = Doc::default(); + doc.wrap_info.inner = Some(Box::new(I::doc())); + doc + } +} diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 2beb8db..561bd13 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -9,6 +9,8 @@ // Clippy lints #![warn(clippy::use_self)] +pub mod doc; + use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; From f2c3011888757a0d5bd6baa68df98be6687c5b8d Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 20 Apr 2023 20:35:11 +0200 Subject: [PATCH 100/266] Implement Document derive proc macro --- cove-macro/src/lib.rs | 136 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/cove-macro/src/lib.rs b/cove-macro/src/lib.rs index f561415..5e7145d 100644 --- a/cove-macro/src/lib.rs +++ b/cove-macro/src/lib.rs @@ -8,3 +8,139 @@ #![warn(single_use_lifetimes)] // Clippy lints #![warn(clippy::use_self)] + +use proc_macro2::TokenStream; +use quote::quote; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::{ + parse_macro_input, Data, DeriveInput, Expr, ExprLit, Field, Lit, LitStr, MetaNameValue, Token, +}; + +fn strlit(expr: &Expr) -> Option<&LitStr> { + match expr { + Expr::Lit(ExprLit { + lit: Lit::Str(lit), .. + }) => Some(lit), + _ => None, + } +} + +/// Given a struct field, this finds all attributes like `#[doc = "bla"]`, +/// unindents, concatenates and returns them. +fn docstring(field: &Field) -> syn::Result<String> { + let mut lines = vec![]; + + for attr in field + .attrs + .iter() + .filter(|attr| attr.path().is_ident("doc")) + { + if let Some(lit) = strlit(&attr.meta.require_name_value()?.value) { + let value = lit.value(); + let value = value + .strip_prefix(' ') + .map(|value| value.to_string()) + .unwrap_or(value); + lines.push(value); + } + } + + Ok(lines.join("\n")) +} + +/// Given a struct field, this finds all key-value pairs of the form +/// `#[document(key = value, ...)]`. +fn document_attributes(field: &Field) -> syn::Result<Vec<MetaNameValue>> { + let mut attrs = vec![]; + + for attr in field + .attrs + .iter() + .filter(|attr| attr.path().is_ident("document")) + { + let args = + attr.parse_args_with(Punctuated::<MetaNameValue, Token![,]>::parse_terminated)?; + attrs.extend(args); + } + + Ok(attrs) +} + +fn field_doc(field: &Field) -> syn::Result<Option<TokenStream>> { + let Some(ident) = field.ident.as_ref() else { return Ok(None); }; + let ident = ident.to_string(); + let ty = &field.ty; + + let mut setters = vec![]; + + let docstring = docstring(field)?; + if !docstring.is_empty() { + setters.push(quote! { + doc.description = Some(#docstring.to_string()); + }); + } + + for attr in document_attributes(field)? { + let value = attr.value; + if attr.path.is_ident("default") { + setters.push(quote! { doc.value_info.default = Some(#value.to_string()); }); + } else if attr.path.is_ident("metavar") { + setters.push(quote! { doc.wrap_info.metavar = Some(#value.to_string()); }); + } else { + return Err(syn::Error::new(attr.path.span(), "unknown argument name")); + } + } + + Ok(Some(quote! { + fields.insert( + #ident.to_string(), + { + let mut doc = <#ty as Document>::doc(); + #( #setters )* + Box::new(doc) + } + ); + })) +} + +fn derive_document_impl(input: DeriveInput) -> syn::Result<TokenStream> { + let Data::Struct(data) = input.data else { + return Err(syn::Error::new(input.span(), "Must be a struct")); + }; + + let mut fields = Vec::new(); + for field in data.fields.iter() { + if let Some(field) = field_doc(field)? { + fields.push(field); + } + } + + let ident = input.ident; + let tokens = quote!( + impl crate::doc::Document for #ident { + fn doc() -> crate::doc::Doc { + use ::std::{boxed::Box, collections::HashMap}; + use crate::doc::{Doc, Document}; + + let mut fields = HashMap::new(); + #( #fields )* + + let mut doc = Doc::default(); + doc.struct_info.fields = fields; + doc + } + } + ); + + Ok(tokens) +} + +#[proc_macro_derive(Document, attributes(document))] +pub fn derive_document(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + match derive_document_impl(input) { + Ok(tokens) => tokens.into(), + Err(err) => err.into_compile_error().into(), + } +} From 3d91d447c762d8f5cdee5463dcd1a9794ed03849 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 20 Apr 2023 20:35:27 +0200 Subject: [PATCH 101/266] Document config --- Cargo.lock | 1 + cove-config/Cargo.toml | 2 ++ cove-config/src/lib.rs | 73 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01e8775..9ef0ddb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -275,6 +275,7 @@ dependencies = [ name = "cove-config" version = "0.6.1" dependencies = [ + "cove-macro", "serde", "toml", ] diff --git a/cove-config/Cargo.toml b/cove-config/Cargo.toml index 2e21781..11fcc5b 100644 --- a/cove-config/Cargo.toml +++ b/cove-config/Cargo.toml @@ -4,5 +4,7 @@ version = { workspace = true } edition = { workspace = true } [dependencies] +cove-macro = { path = "../cove-macro" } + serde = { version = "1.0.159", features = ["derive"] } toml = "0.7.3" diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 561bd13..04b75bb 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -15,6 +15,8 @@ use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; +use cove_macro::Document; +use doc::{Doc, Document}; use serde::Deserialize; #[derive(Debug, Clone, Copy, Default, Deserialize)] @@ -25,31 +27,94 @@ pub enum RoomsSortOrder { Importance, } -#[derive(Debug, Clone, Default, Deserialize)] +impl Document for RoomsSortOrder { + fn doc() -> Doc { + let mut doc = String::doc(); + doc.value_info.values = Some(vec![ + // TODO Generate by serializing + "`alphabet`".to_string(), + "`importance`".to_string(), + ]); + doc + } +} + +// TODO Mark favourite rooms via printable ascii characters +#[derive(Debug, Clone, Default, Deserialize, Document)] pub struct EuphRoom { - // TODO Mark favourite rooms via printable ascii characters + /// Whether to automatically join this room on startup. #[serde(default)] + #[document(default = "`false`")] pub autojoin: bool, + + /// If set, cove will set this username upon joining if there is no username + /// associated with the current session. pub username: Option<String>, + + /// If `euph.rooms.<room>.username` is set, this will force cove to set the + /// username even if there is already a different username associated with + /// the current session. #[serde(default)] + #[document(default = "`false`")] pub force_username: bool, + + /// If set, cove will try once to use this password to authenticate, should + /// the room be password-protected. pub password: Option<String>, } -#[derive(Debug, Default, Deserialize)] +#[derive(Debug, Default, Deserialize, Document)] pub struct Euph { + #[document(metavar = "room")] pub rooms: HashMap<String, EuphRoom>, } -#[derive(Debug, Default, Deserialize)] +#[derive(Debug, Default, Deserialize, Document)] pub struct Config { + /// The directory that cove stores its data in when not running in ephemeral + /// mode. + /// + /// Relative paths are interpreted relative to the user's home directory. + /// + /// See also the `--data-dir` command line option. + #[document(default = "platform-dependent")] pub data_dir: Option<PathBuf>, + + /// Whether to start in ephemeral mode. + /// + /// In ephemeral mode, cove doesn't store any data. It completely ignores + /// any options related to the data dir. + /// + /// See also the `--ephemeral` command line option. #[serde(default)] + #[document(default = "`false`")] pub ephemeral: bool, + + /// Whether to start in offline mode. + /// + /// In offline mode, cove won't automatically join rooms marked via the + /// `autojoin` option on startup. You can still join those rooms manually by + /// pressing `a` in the rooms list. + /// + /// See also the `--offline` command line option. #[serde(default)] + #[document(default = "`false`")] pub offline: bool, + + /// Initial sort order of rooms list. + /// + /// `alphabet` sorts rooms in alphabetic order. + /// + /// `importance` sorts rooms by the following criteria (in descending order + /// of priority): + /// + /// 1. connected rooms before unconnected rooms + /// 2. rooms with unread messages before rooms without + /// 3. alphabetic order #[serde(default)] + #[document(default = "`alphabet`")] pub rooms_sort_order: RoomsSortOrder, + // TODO Invoke external notification command? pub euph: Euph, } From cc7dd29af40a8aff92ea3482d881e1798c31ff5f Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 20 Apr 2023 20:35:35 +0200 Subject: [PATCH 102/266] Print config documentation --- CHANGELOG.md | 3 ++ cove/src/main.rs | 134 ++++++++++++++++++++++++++++++----------------- 2 files changed, 89 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d93ddc8..881ebae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ Procedure when bumping the version number: ## Unreleased +### Added +- `help-config` CLI command + ### Changed - Simplified flake dependencies diff --git a/cove/src/main.rs b/cove/src/main.rs index 494d710..72526b5 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -27,6 +27,7 @@ use std::path::PathBuf; use clap::Parser; use cookie::CookieJar; +use cove_config::doc::Document; use cove_config::Config; use directories::{BaseDirs, ProjectDirs}; use log::info; @@ -47,6 +48,8 @@ enum Command { Gc, /// Clear euphoria session cookies. ClearCookies, + /// Print config documentation as markdown. + HelpConfig, } impl Default for Command { @@ -91,75 +94,69 @@ struct Args { command: Option<Command>, } -fn set_data_dir(config: &mut Config, args_data_dir: Option<PathBuf>) { - if let Some(data_dir) = args_data_dir { +fn config_path(args: &Args, dirs: &ProjectDirs) -> PathBuf { + args.config + .clone() + .unwrap_or_else(|| dirs.config_dir().join("config.toml")) +} + +fn data_dir(config: &Config, dirs: &ProjectDirs) -> PathBuf { + config + .data_dir + .clone() + .unwrap_or_else(|| dirs.data_dir().to_path_buf()) +} + +fn update_config_with_args(config: &mut Config, args: &Args) { + if let Some(data_dir) = args.data_dir.clone() { // The data dir specified via args_data_dir is relative to the current // directory and needs no resolving. config.data_dir = Some(data_dir); } else if let Some(data_dir) = &config.data_dir { // Resolve the data dir specified in the config file relative to the // user's home directory, if possible. - if let Some(base_dirs) = BaseDirs::new() { - config.data_dir = Some(base_dirs.home_dir().join(data_dir)); - } + let base_dirs = BaseDirs::new().expect("failed to find home directory"); + config.data_dir = Some(base_dirs.home_dir().join(data_dir)); } + + config.ephemeral |= args.ephemeral; + config.offline |= args.offline; } -fn set_ephemeral(config: &mut Config, args_ephemeral: bool) { - if args_ephemeral { - config.ephemeral = true; - } -} - -fn set_offline(config: &mut Config, args_offline: bool) { - if args_offline { - config.offline = true; +fn open_vault(config: &Config, dirs: &ProjectDirs) -> rusqlite::Result<Vault> { + if config.ephemeral { + vault::launch_in_memory() + } else { + let data_dir = data_dir(config, dirs); + eprintln!("Data dir: {}", data_dir.to_string_lossy()); + vault::launch(&data_dir.join("vault.db")) } } #[tokio::main] async fn main() -> anyhow::Result<()> { let args = Args::parse(); - let (logger, logger_guard, logger_rx) = Logger::init(args.verbose); - let dirs = ProjectDirs::from("de", "plugh", "cove").expect("unable to determine directories"); - let config_path = args - .config - .unwrap_or_else(|| dirs.config_dir().join("config.toml")); + let (logger, logger_guard, logger_rx) = Logger::init(args.verbose); + let dirs = ProjectDirs::from("de", "plugh", "cove").expect("failed to find config directory"); + + // Locate config + let config_path = config_path(&args, &dirs); eprintln!("Config file: {}", config_path.to_string_lossy()); + + // Load config let mut config = Config::load(&config_path); - set_data_dir(&mut config, args.data_dir); - set_ephemeral(&mut config, args.ephemeral); - set_offline(&mut config, args.offline); + update_config_with_args(&mut config, &args); let config = Box::leak(Box::new(config)); - let vault = if config.ephemeral { - vault::launch_in_memory()? - } else { - let data_dir = config - .data_dir - .clone() - .unwrap_or_else(|| dirs.data_dir().to_path_buf()); - eprintln!("Data dir: {}", data_dir.to_string_lossy()); - vault::launch(&data_dir.join("vault.db"))? - }; - match args.command.unwrap_or_default() { - Command::Run => run(logger, logger_rx, config, &vault, args.measure_widths).await?, - Command::Export(args) => export::export(&vault.euph(), args).await?, - Command::Gc => { - eprintln!("Cleaning up and compacting vault"); - eprintln!("This may take a while..."); - vault.gc().await?; - } - Command::ClearCookies => { - eprintln!("Clearing cookies"); - vault.euph().set_cookies(CookieJar::new()).await?; - } + Command::Run => run(logger, logger_rx, config, &dirs, args.measure_widths).await?, + Command::Export(args) => export(config, &dirs, args).await?, + Command::Gc => gc(config, &dirs).await?, + Command::ClearCookies => clear_cookies(config, &dirs).await?, + Command::HelpConfig => help_config(), } - vault.close().await; - // Print all logged errors. This should always happen, even if cove panics, // because the errors may be key in diagnosing what happened. Because of // this, it is not implemented via a normal function call. @@ -173,7 +170,7 @@ async fn run( logger: Logger, logger_rx: mpsc::UnboundedReceiver<()>, config: &'static Config, - vault: &Vault, + dirs: &ProjectDirs, measure_widths: bool, ) -> anyhow::Result<()> { info!( @@ -182,10 +179,51 @@ async fn run( env!("CARGO_PKG_VERSION") ); + let vault = open_vault(config, dirs)?; + let mut terminal = Terminal::new()?; terminal.set_measuring(measure_widths); Ui::run(config, &mut terminal, vault.clone(), logger, logger_rx).await?; - drop(terminal); // So other things can print again + drop(terminal); + vault.close().await; Ok(()) } + +async fn export( + config: &'static Config, + dirs: &ProjectDirs, + args: export::Args, +) -> anyhow::Result<()> { + let vault = open_vault(config, dirs)?; + + export::export(&vault.euph(), args).await?; + + vault.close().await; + Ok(()) +} + +async fn gc(config: &'static Config, dirs: &ProjectDirs) -> anyhow::Result<()> { + let vault = open_vault(config, dirs)?; + + eprintln!("Cleaning up and compacting vault"); + eprintln!("This may take a while..."); + vault.gc().await?; + + vault.close().await; + Ok(()) +} + +async fn clear_cookies(config: &'static Config, dirs: &ProjectDirs) -> anyhow::Result<()> { + let vault = open_vault(config, dirs)?; + + eprintln!("Clearing cookies"); + vault.euph().set_cookies(CookieJar::new()).await?; + + vault.close().await; + Ok(()) +} + +fn help_config() { + print!("{}", Config::doc().format_as_markdown()); +} From 39026a217dd02030de61614d44b5a175b7329653 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Mon, 24 Apr 2023 14:03:40 +0200 Subject: [PATCH 103/266] Use auto-generated config documentation --- CHANGELOG.md | 9 +++-- CONFIG.md | 95 +++++++++++++++++++++++++++++++++++++++++++++ README.md | 107 +++++---------------------------------------------- 3 files changed, 110 insertions(+), 101 deletions(-) create mode 100644 CONFIG.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 881ebae..8c6b37f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,11 @@ Procedure when bumping the version number: 1. Update dependencies in a separate commit 2. Set version number in `Cargo.toml` 3. Add new section in this changelog -4. Commit with message `Bump version to X.Y.Z` -5. Create tag named `vX.Y.Z` -6. Fast-forward branch `latest` -7. Push `master`, `latest` and the new tag +4. Run `cargo run help-config > CONFIG.md` +5. Commit with message `Bump version to X.Y.Z` +6. Create tag named `vX.Y.Z` +7. Fast-forward branch `latest` +8. Push `master`, `latest` and the new tag ## Unreleased diff --git a/CONFIG.md b/CONFIG.md new file mode 100644 index 0000000..4f2a456 --- /dev/null +++ b/CONFIG.md @@ -0,0 +1,95 @@ +# Configuration options + +Cove's config file uses the [TOML](https://toml.io/) format. + +## `data_dir` + +**Required:** no +**Type:** path +**Default:** platform-dependent + +The directory that cove stores its data in when not running in ephemeral +mode. + +Relative paths are interpreted relative to the user's home directory. + +See also the `--data-dir` command line option. + +## `ephemeral` + +**Required:** yes +**Type:** boolean +**Default:** `false` + +Whether to start in ephemeral mode. + +In ephemeral mode, cove doesn't store any data. It completely ignores +any options related to the data dir. + +See also the `--ephemeral` command line option. + +## `euph.rooms.<room>.autojoin` + +**Required:** yes +**Type:** boolean +**Default:** `false` + +Whether to automatically join this room on startup. + +## `euph.rooms.<room>.force_username` + +**Required:** yes +**Type:** boolean +**Default:** `false` + +If `euph.rooms.<room>.username` is set, this will force cove to set the +username even if there is already a different username associated with +the current session. + +## `euph.rooms.<room>.password` + +**Required:** no +**Type:** string + +If set, cove will try once to use this password to authenticate, should +the room be password-protected. + +## `euph.rooms.<room>.username` + +**Required:** no +**Type:** string + +If set, cove will set this username upon joining if there is no username +associated with the current session. + +## `offline` + +**Required:** yes +**Type:** boolean +**Default:** `false` + +Whether to start in offline mode. + +In offline mode, cove won't automatically join rooms marked via the +`autojoin` option on startup. You can still join those rooms manually by +pressing `a` in the rooms list. + +See also the `--offline` command line option. + +## `rooms_sort_order` + +**Required:** yes +**Type:** string +**Values:** `alphabet`, `importance` +**Default:** `alphabet` + +Initial sort order of rooms list. + +`alphabet` sorts rooms in alphabetic order. + +`importance` sorts rooms by the following criteria (in descending order +of priority): + +1. connected rooms before unconnected rooms +2. rooms with unread messages before rooms without +3. alphabetic order diff --git a/README.md b/README.md index 4659f61..e5ee2c8 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ real-time chat platform. ![A very meta screenshot](screenshot.png) -It runs on Linux, Windows and macOS. +It runs on Linux, Windows, and macOS. ## Using cove @@ -18,6 +18,15 @@ things in) won't automatically shrink. If it takes up too much space, try running `cove gc` and waiting for it to finish. This isn't done automatically because it can take quite a while. +## Configuring cove + +A complete list of config options is available in the [CONFIG.md](CONFIG.md) +file or via `cove help-config`. + +When launched, cove prints the location it is loading its config file from. To +configure cove, create a config file at that location. This location can be +changed via the `--config` command line option. + ## Installation At this point, cove is not available via any package manager. @@ -75,99 +84,3 @@ in the full version you want to install: ```bash $ cargo install --force --git https://github.com/Garmelon/cove --tag v0.1.0 ``` - -## Config file - -Cove's config file uses the [TOML](https://toml.io/) format. - -When launched, cove prints the location it is loading its config file from. To -configure cove, create a config file at that location. This location can be -changed via the `--config` command line option. - -The following is a complete list of available options. If a command line option -with the same purpose exists, it takes precedence over the option specified in -the config file. - -### `data_dir` - -**Type:** String (representing path) -**Default:** Platform dependent - -The directory that cove stores its data in when not running in ephemeral mode. - -Relative paths are interpreted relative to the user's home directory. - -See also the `--data-dir` command line option. - -### `ephemeral` - -**Type:** Boolean -**Default:** `false` - -Whether to start in ephemeral mode. - -In ephemeral mode, cove doesn't store any data. It completely ignores any -options related to the data dir. - -See also the `--ephemeral` command line option. - -### `offline` - -**Type:** Boolean -**Default:** `false` - -Whether to start in offline mode. - -In offline mode, cove won't automatically join rooms marked via the `autojoin` -option on startup. You can still join those rooms manually by pressing `a` in -the rooms list. - -See also the `--offline` command line option. - -### `rooms_sort_order` - -**Type:** String, one of `alphabetic`, `importance` -**Default:** `alphabetic` - -Initial sort order of rooms list. - -`alphabetic` sorts rooms in alphabetic order. - -`importance` sorts rooms by the following criteria (in descending order of -priority): - -1. connected rooms before unconnected rooms -2. rooms with unread messages before rooms without -3. alphabetic order - -### `euph.rooms.<room>.autojoin` - -**Type:** Boolean -**Default:** `false` - -Whether to automatically join this room on startup. - -### `euph.rooms.<room>.username` - -**Type:** String -**Default:** Not set - -If set, cove will set this username upon joining if there is no username -associated with the current session. - -### `euph.rooms.<room>.force_username` - -**Type:** Boolean -**Default:** `false` - -If `euph.rooms.<room>.username` is set, this will force cove to set the username -even if there is already a different username associated with the current -session. - -### `euph.rooms.<room>.password` - -**Type:** String -**Default:** Not set - -If set, cove will try once to use this password to authenticate, should the room -be password-protected. From f7f200a608c0b568f6941416ef0ece7b668d5d60 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Mon, 24 Apr 2023 18:21:46 +0200 Subject: [PATCH 104/266] Add measure_widths config option --- CHANGELOG.md | 1 + cove-config/src/lib.rs | 12 ++++++++++++ cove/src/main.rs | 6 +++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c6b37f..d476ae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Procedure when bumping the version number: ### Added - `help-config` CLI command +- `measure_widths` config option ### Changed - Simplified flake dependencies diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 04b75bb..1f17988 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -90,6 +90,18 @@ pub struct Config { #[document(default = "`false`")] pub ephemeral: bool, + /// Whether to measure the width of characters as displayed by the terminal + /// emulator instead of guessing the width. + /// + /// Enabling this makes rendering a bit slower but more accurate. The screen + /// might also flash when encountering new characters (or, more accurately, + /// graphemes). + /// + /// See also the `--measure-graphemes` command line option. + #[serde(default)] + #[document(default = "`false`")] + pub measure_widths: bool, + /// Whether to start in offline mode. /// /// In offline mode, cove won't automatically join rooms marked via the diff --git a/cove/src/main.rs b/cove/src/main.rs index 72526b5..0c99ee4 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -120,6 +120,7 @@ fn update_config_with_args(config: &mut Config, args: &Args) { } config.ephemeral |= args.ephemeral; + config.measure_widths |= args.measure_widths; config.offline |= args.offline; } @@ -150,7 +151,7 @@ async fn main() -> anyhow::Result<()> { let config = Box::leak(Box::new(config)); match args.command.unwrap_or_default() { - Command::Run => run(logger, logger_rx, config, &dirs, args.measure_widths).await?, + Command::Run => run(logger, logger_rx, config, &dirs).await?, Command::Export(args) => export(config, &dirs, args).await?, Command::Gc => gc(config, &dirs).await?, Command::ClearCookies => clear_cookies(config, &dirs).await?, @@ -171,7 +172,6 @@ async fn run( logger_rx: mpsc::UnboundedReceiver<()>, config: &'static Config, dirs: &ProjectDirs, - measure_widths: bool, ) -> anyhow::Result<()> { info!( "Welcome to {} {}", @@ -182,7 +182,7 @@ async fn run( let vault = open_vault(config, dirs)?; let mut terminal = Terminal::new()?; - terminal.set_measuring(measure_widths); + terminal.set_measuring(config.measure_widths); Ui::run(config, &mut terminal, vault.clone(), logger, logger_rx).await?; drop(terminal); From abedc5f1942fefa94a22d50af6b0f936d9f6ae0d Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 26 Apr 2023 14:58:01 +0200 Subject: [PATCH 105/266] Create cove-input crate --- Cargo.lock | 4 ++++ cove-input/Cargo.toml | 6 ++++++ cove-input/src/lib.rs | 1 + 3 files changed, 11 insertions(+) create mode 100644 cove-input/Cargo.toml create mode 100644 cove-input/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 9ef0ddb..ffd8e6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,6 +280,10 @@ dependencies = [ "toml", ] +[[package]] +name = "cove-input" +version = "0.6.1" + [[package]] name = "cove-macro" version = "0.6.1" diff --git a/cove-input/Cargo.toml b/cove-input/Cargo.toml new file mode 100644 index 0000000..346ce18 --- /dev/null +++ b/cove-input/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "cove-input" +version = { workspace = true } +edition = { workspace = true } + +[dependencies] diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/cove-input/src/lib.rs @@ -0,0 +1 @@ + From 3fbb9127a60ad0ecbc884bf5834568f27d853de8 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 26 Apr 2023 15:00:41 +0200 Subject: [PATCH 106/266] Model and (de-)serialize key bindings --- Cargo.lock | 44 +++++++++ Cargo.toml | 6 ++ cove-config/Cargo.toml | 3 +- cove-input/Cargo.toml | 4 + cove-input/src/keys.rs | 198 +++++++++++++++++++++++++++++++++++++++++ cove-input/src/lib.rs | 2 + cove/Cargo.toml | 5 +- 7 files changed, 259 insertions(+), 3 deletions(-) create mode 100644 cove-input/src/keys.rs diff --git a/Cargo.lock b/Cargo.lock index ffd8e6e..ecdc77d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -283,6 +283,12 @@ dependencies = [ [[package]] name = "cove-input" version = "0.6.1" +dependencies = [ + "crossterm", + "serde", + "serde_either", + "thiserror", +] [[package]] name = "cove-macro" @@ -705,6 +711,15 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.15.0" @@ -736,6 +751,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1029,6 +1053,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.159" @@ -1040,6 +1074,16 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "serde_either" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "689643f4e7826ffcd227d2cc166bfdf5869750191ffe9fd593531e6ba351f2fb" +dependencies = [ + "serde", + "serde-value", +] + [[package]] name = "serde_json" version = "1.0.95" diff --git a/Cargo.toml b/Cargo.toml index 2873003..33e3c2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,11 @@ members = ["cove", "cove-*"] version = "0.6.1" edition = "2021" +[workspace.dependencies] +crossterm = "0.26.1" +serde = { version = "1.0.159", features = ["derive"] } +serde_either = "0.2.1" +thiserror = "1.0.40" + [profile.dev.package."*"] opt-level = 3 diff --git a/cove-config/Cargo.toml b/cove-config/Cargo.toml index 11fcc5b..65c8d55 100644 --- a/cove-config/Cargo.toml +++ b/cove-config/Cargo.toml @@ -6,5 +6,6 @@ edition = { workspace = true } [dependencies] cove-macro = { path = "../cove-macro" } -serde = { version = "1.0.159", features = ["derive"] } +serde = { workspace = true } + toml = "0.7.3" diff --git a/cove-input/Cargo.toml b/cove-input/Cargo.toml index 346ce18..2c3c243 100644 --- a/cove-input/Cargo.toml +++ b/cove-input/Cargo.toml @@ -4,3 +4,7 @@ version = { workspace = true } edition = { workspace = true } [dependencies] +crossterm = { workspace = true } +serde = { workspace = true } +serde_either = { workspace = true } +thiserror = { workspace = true } diff --git a/cove-input/src/keys.rs b/cove-input/src/keys.rs new file mode 100644 index 0000000..ddedcd9 --- /dev/null +++ b/cove-input/src/keys.rs @@ -0,0 +1,198 @@ +use std::fmt; +use std::num::ParseIntError; +use std::str::FromStr; + +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use serde::{de::Error, Deserialize, Deserializer}; +use serde::{Serialize, Serializer}; +use serde_either::SingleOrVec; + +#[derive(Debug, thiserror::Error)] +pub enum ParseKeysError { + #[error("no key code specified")] + NoKeyCode, + #[error("unknown key code: {0:?}")] + UnknownKeyCode(String), + #[error("invalid function key number: {0}")] + InvalidFNumber(#[from] ParseIntError), + #[error("unknown modifier: {0:?}")] + UnknownModifier(String), + #[error("modifier {0} conflicts with previous modifier")] + ConflictingModifier(String), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct KeyPress { + pub code: KeyCode, + pub shift: bool, + pub ctrl: bool, + pub alt: bool, + pub any: bool, +} + +impl KeyPress { + fn parse_key_code(code: &str) -> Result<Self, ParseKeysError> { + let code = match code { + "backspace" => KeyCode::Backspace, + "enter" => KeyCode::Enter, + "left" => KeyCode::Left, + "right" => KeyCode::Right, + "up" => KeyCode::Up, + "down" => KeyCode::Down, + "home" => KeyCode::Home, + "end" => KeyCode::End, + "pageup" => KeyCode::PageUp, + "pagedown" => KeyCode::PageDown, + "tab" => KeyCode::Tab, + "backtab" => KeyCode::BackTab, + "delete" => KeyCode::Delete, + "insert" => KeyCode::Insert, + "esc" => KeyCode::Esc, + c if c.starts_with('F') => KeyCode::F(c.strip_prefix('F').unwrap().parse()?), + c if c.chars().count() == 1 => KeyCode::Char(c.chars().next().unwrap()), + "" => return Err(ParseKeysError::NoKeyCode), + c => return Err(ParseKeysError::UnknownKeyCode(c.to_string())), + }; + Ok(Self { + code, + shift: false, + ctrl: false, + alt: false, + any: false, + }) + } + + fn display_key_code(code: KeyCode) -> String { + match code { + KeyCode::Backspace => "backspace".to_string(), + KeyCode::Enter => "enter".to_string(), + KeyCode::Left => "left".to_string(), + KeyCode::Right => "right".to_string(), + KeyCode::Up => "up".to_string(), + KeyCode::Down => "down".to_string(), + KeyCode::Home => "home".to_string(), + KeyCode::End => "end".to_string(), + KeyCode::PageUp => "pageup".to_string(), + KeyCode::PageDown => "pagedown".to_string(), + KeyCode::Tab => "tab".to_string(), + KeyCode::BackTab => "backtab".to_string(), + KeyCode::Delete => "delete".to_string(), + KeyCode::Insert => "insert".to_string(), + KeyCode::Esc => "esc".to_string(), + KeyCode::F(n) => format!("F{n}"), + KeyCode::Char(c) => c.to_string(), + _ => "unknown".to_string(), + } + } + + fn parse_modifier(&mut self, modifier: &str) -> Result<(), ParseKeysError> { + match modifier { + m if self.any => return Err(ParseKeysError::ConflictingModifier(m.to_string())), + "shift" if !self.shift => self.shift = true, + "ctrl" if !self.ctrl => self.ctrl = true, + "alt" if !self.alt => self.alt = true, + "any" if !self.shift && !self.ctrl && !self.alt => self.any = true, + m @ ("shift" | "ctrl" | "alt" | "any") => { + return Err(ParseKeysError::ConflictingModifier(m.to_string())) + } + m => return Err(ParseKeysError::UnknownModifier(m.to_string())), + } + Ok(()) + } + + pub fn matches(&self, event: KeyEvent) -> bool { + if event.code != self.code { + return false; + } + + if self.any { + return true; + } + + let shift = event.modifiers.contains(KeyModifiers::SHIFT); + let ctrl = event.modifiers.contains(KeyModifiers::CONTROL); + let alt = event.modifiers.contains(KeyModifiers::ALT); + self.shift == shift && self.ctrl == ctrl && self.alt == alt + } +} + +impl FromStr for KeyPress { + type Err = ParseKeysError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let mut parts = s.split('+'); + let code = parts.next_back().ok_or(ParseKeysError::NoKeyCode)?; + + let mut keys = KeyPress::parse_key_code(code)?; + for modifier in parts { + keys.parse_modifier(modifier)?; + } + + Ok(keys) + } +} + +impl fmt::Display for KeyPress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let code = Self::display_key_code(self.code); + + let mut segments = vec![]; + if self.shift { + segments.push("Shift"); + } + if self.ctrl { + segments.push("Ctrl"); + } + if self.alt { + segments.push("Alt"); + } + if self.any { + segments.push("Any"); + } + segments.push(&code); + + segments.join("+").fmt(f) + } +} + +impl Serialize for KeyPress { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + format!("{self}").serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for KeyPress { + fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + String::deserialize(deserializer)? + .parse() + .map_err(|e| D::Error::custom(format!("{e}"))) + } +} + +#[derive(Debug, Clone)] +pub struct KeyBinding(Vec<KeyPress>); + +impl KeyBinding { + pub fn matches(&self, event: KeyEvent) -> bool { + self.0.iter().any(|kp| kp.matches(event)) + } +} + +impl Serialize for KeyBinding { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + if self.0.len() == 1 { + self.0[0].serialize(serializer) + } else { + self.0.serialize(serializer) + } + } +} + +impl<'de> Deserialize<'de> for KeyBinding { + fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + Ok(match SingleOrVec::<KeyPress>::deserialize(deserializer)? { + SingleOrVec::Single(key) => Self(vec![key]), + SingleOrVec::Vec(keys) => Self(keys), + }) + } +} diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index 8b13789..1da1a6f 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -1 +1,3 @@ +mod keys; +pub use keys::*; diff --git a/cove/Cargo.toml b/cove/Cargo.toml index 123b7c9..a15d8af 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -6,11 +6,13 @@ edition = { workspace = true } [dependencies] cove-config = { path = "../cove-config" } +crossterm = { workspace = true } +thiserror = { workspace = true } + anyhow = "1.0.70" async-trait = "0.1.68" clap = { version = "4.2.1", features = ["derive", "deprecated"] } cookie = "0.17.0" -crossterm = "0.26.1" directories = "5.0.0" edit = "0.1.4" linkify = "0.9.0" @@ -20,7 +22,6 @@ open = "4.0.1" parking_lot = "0.12.1" rusqlite = { version = "0.29.0", features = ["bundled", "time"] } serde_json = "1.0.95" -thiserror = "1.0.40" tokio = { version = "1.27.0", features = ["full"] } unicode-segmentation = "1.10.1" unicode-width = "0.1.10" From 072290511b2861006dbaaf071240a972e44fe6a9 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 26 Apr 2023 15:01:50 +0200 Subject: [PATCH 107/266] Query key bindings against input event --- cove-input/src/input.rs | 65 +++++++++++++++++++++++++++++++++++++++++ cove-input/src/lib.rs | 2 ++ 2 files changed, 67 insertions(+) create mode 100644 cove-input/src/input.rs diff --git a/cove-input/src/input.rs b/cove-input/src/input.rs new file mode 100644 index 0000000..5e424ce --- /dev/null +++ b/cove-input/src/input.rs @@ -0,0 +1,65 @@ +use crossterm::event::KeyEvent; + +use crate::KeyBinding; + +pub enum Entry { + Space, + Category(String), + Binding(KeyBinding, String), +} + +pub struct Input { + event: Option<KeyEvent>, + + record_entries: bool, + entries: Vec<Entry>, +} + +impl Input { + pub fn new_from_event(event: KeyEvent) -> Self { + Self { + event: Some(event), + record_entries: false, + entries: vec![], + } + } + + pub fn new_recording() -> Self { + Self { + event: None, + record_entries: true, + entries: vec![], + } + } + + pub fn space<S: ToString>(&mut self) { + if self.record_entries { + self.entries.push(Entry::Space); + } + } + + pub fn category<S: ToString>(&mut self, name: S) { + if self.record_entries { + self.entries.push(Entry::Category(name.to_string())); + } + } + + pub fn matches<S: ToString>(&mut self, binding: &KeyBinding, description: S) -> bool { + let matches = if let Some(event) = self.event { + binding.matches(event) + } else { + false + }; + + if self.record_entries { + self.entries + .push(Entry::Binding(binding.clone(), description.to_string())); + } + + matches + } + + pub fn entries(&self) -> &[Entry] { + &self.entries + } +} diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index 1da1a6f..c89a689 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -1,3 +1,5 @@ +mod input; mod keys; +pub use input::*; pub use keys::*; From 17acdd0575fee29c1146afeb8142e9d75db2c686 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 26 Apr 2023 15:24:12 +0200 Subject: [PATCH 108/266] Add Group trait --- cove-input/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index c89a689..d3963ee 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -3,3 +3,9 @@ mod keys; pub use input::*; pub use keys::*; + +pub trait Group { + type Action; + + fn action(&self, input: &mut Input) -> Option<Self::Action>; +} From a1acc26027925b99fc287ce775a8dff55cea07b6 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 26 Apr 2023 15:26:36 +0200 Subject: [PATCH 109/266] Extract Document derive macro to submodule --- cove-macro/src/document.rs | 124 +++++++++++++++++++++++++++++++++++ cove-macro/src/lib.rs | 129 +------------------------------------ 2 files changed, 127 insertions(+), 126 deletions(-) create mode 100644 cove-macro/src/document.rs diff --git a/cove-macro/src/document.rs b/cove-macro/src/document.rs new file mode 100644 index 0000000..c2841a9 --- /dev/null +++ b/cove-macro/src/document.rs @@ -0,0 +1,124 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::{Data, DeriveInput, Expr, ExprLit, Field, Lit, LitStr, MetaNameValue, Token}; + +fn strlit(expr: &Expr) -> Option<&LitStr> { + match expr { + Expr::Lit(ExprLit { + lit: Lit::Str(lit), .. + }) => Some(lit), + _ => None, + } +} + +/// Given a struct field, this finds all attributes like `#[doc = "bla"]`, +/// unindents, concatenates and returns them. +fn docstring(field: &Field) -> syn::Result<String> { + let mut lines = vec![]; + + for attr in field + .attrs + .iter() + .filter(|attr| attr.path().is_ident("doc")) + { + if let Some(lit) = strlit(&attr.meta.require_name_value()?.value) { + let value = lit.value(); + let value = value + .strip_prefix(' ') + .map(|value| value.to_string()) + .unwrap_or(value); + lines.push(value); + } + } + + Ok(lines.join("\n")) +} + +/// Given a struct field, this finds all key-value pairs of the form +/// `#[document(key = value, ...)]`. +fn document_attributes(field: &Field) -> syn::Result<Vec<MetaNameValue>> { + let mut attrs = vec![]; + + for attr in field + .attrs + .iter() + .filter(|attr| attr.path().is_ident("document")) + { + let args = + attr.parse_args_with(Punctuated::<MetaNameValue, Token![,]>::parse_terminated)?; + attrs.extend(args); + } + + Ok(attrs) +} + +fn field_doc(field: &Field) -> syn::Result<Option<TokenStream>> { + let Some(ident) = field.ident.as_ref() else { return Ok(None); }; + let ident = ident.to_string(); + let ty = &field.ty; + + let mut setters = vec![]; + + let docstring = docstring(field)?; + if !docstring.is_empty() { + setters.push(quote! { + doc.description = Some(#docstring.to_string()); + }); + } + + for attr in document_attributes(field)? { + let value = attr.value; + if attr.path.is_ident("default") { + setters.push(quote! { doc.value_info.default = Some(#value.to_string()); }); + } else if attr.path.is_ident("metavar") { + setters.push(quote! { doc.wrap_info.metavar = Some(#value.to_string()); }); + } else { + return Err(syn::Error::new(attr.path.span(), "unknown argument name")); + } + } + + Ok(Some(quote! { + fields.insert( + #ident.to_string(), + { + let mut doc = <#ty as Document>::doc(); + #( #setters )* + Box::new(doc) + } + ); + })) +} + +pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { + let Data::Struct(data) = input.data else { + return Err(syn::Error::new(input.span(), "Must be a struct")); + }; + + let mut fields = Vec::new(); + for field in data.fields.iter() { + if let Some(field) = field_doc(field)? { + fields.push(field); + } + } + + let ident = input.ident; + let tokens = quote!( + impl crate::doc::Document for #ident { + fn doc() -> crate::doc::Doc { + use ::std::{boxed::Box, collections::HashMap}; + use crate::doc::{Doc, Document}; + + let mut fields = HashMap::new(); + #( #fields )* + + let mut doc = Doc::default(); + doc.struct_info.fields = fields; + doc + } + } + ); + + Ok(tokens) +} diff --git a/cove-macro/src/lib.rs b/cove-macro/src/lib.rs index 5e7145d..b05190e 100644 --- a/cove-macro/src/lib.rs +++ b/cove-macro/src/lib.rs @@ -9,137 +9,14 @@ // Clippy lints #![warn(clippy::use_self)] -use proc_macro2::TokenStream; -use quote::quote; -use syn::punctuated::Punctuated; -use syn::spanned::Spanned; -use syn::{ - parse_macro_input, Data, DeriveInput, Expr, ExprLit, Field, Lit, LitStr, MetaNameValue, Token, -}; +use syn::{parse_macro_input, DeriveInput}; -fn strlit(expr: &Expr) -> Option<&LitStr> { - match expr { - Expr::Lit(ExprLit { - lit: Lit::Str(lit), .. - }) => Some(lit), - _ => None, - } -} - -/// Given a struct field, this finds all attributes like `#[doc = "bla"]`, -/// unindents, concatenates and returns them. -fn docstring(field: &Field) -> syn::Result<String> { - let mut lines = vec![]; - - for attr in field - .attrs - .iter() - .filter(|attr| attr.path().is_ident("doc")) - { - if let Some(lit) = strlit(&attr.meta.require_name_value()?.value) { - let value = lit.value(); - let value = value - .strip_prefix(' ') - .map(|value| value.to_string()) - .unwrap_or(value); - lines.push(value); - } - } - - Ok(lines.join("\n")) -} - -/// Given a struct field, this finds all key-value pairs of the form -/// `#[document(key = value, ...)]`. -fn document_attributes(field: &Field) -> syn::Result<Vec<MetaNameValue>> { - let mut attrs = vec![]; - - for attr in field - .attrs - .iter() - .filter(|attr| attr.path().is_ident("document")) - { - let args = - attr.parse_args_with(Punctuated::<MetaNameValue, Token![,]>::parse_terminated)?; - attrs.extend(args); - } - - Ok(attrs) -} - -fn field_doc(field: &Field) -> syn::Result<Option<TokenStream>> { - let Some(ident) = field.ident.as_ref() else { return Ok(None); }; - let ident = ident.to_string(); - let ty = &field.ty; - - let mut setters = vec![]; - - let docstring = docstring(field)?; - if !docstring.is_empty() { - setters.push(quote! { - doc.description = Some(#docstring.to_string()); - }); - } - - for attr in document_attributes(field)? { - let value = attr.value; - if attr.path.is_ident("default") { - setters.push(quote! { doc.value_info.default = Some(#value.to_string()); }); - } else if attr.path.is_ident("metavar") { - setters.push(quote! { doc.wrap_info.metavar = Some(#value.to_string()); }); - } else { - return Err(syn::Error::new(attr.path.span(), "unknown argument name")); - } - } - - Ok(Some(quote! { - fields.insert( - #ident.to_string(), - { - let mut doc = <#ty as Document>::doc(); - #( #setters )* - Box::new(doc) - } - ); - })) -} - -fn derive_document_impl(input: DeriveInput) -> syn::Result<TokenStream> { - let Data::Struct(data) = input.data else { - return Err(syn::Error::new(input.span(), "Must be a struct")); - }; - - let mut fields = Vec::new(); - for field in data.fields.iter() { - if let Some(field) = field_doc(field)? { - fields.push(field); - } - } - - let ident = input.ident; - let tokens = quote!( - impl crate::doc::Document for #ident { - fn doc() -> crate::doc::Doc { - use ::std::{boxed::Box, collections::HashMap}; - use crate::doc::{Doc, Document}; - - let mut fields = HashMap::new(); - #( #fields )* - - let mut doc = Doc::default(); - doc.struct_info.fields = fields; - doc - } - } - ); - - Ok(tokens) -} +mod document; #[proc_macro_derive(Document, attributes(document))] pub fn derive_document(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); - match derive_document_impl(input) { + match document::derive_impl(input) { Ok(tokens) => tokens.into(), Err(err) => err.into_compile_error().into(), } From 1276a82e549fe939d66dd501df325c3364630007 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 26 Apr 2023 22:21:19 +0200 Subject: [PATCH 110/266] Implement Group derive proc macro --- Cargo.lock | 7 +++++ cove-macro/Cargo.toml | 1 + cove-macro/src/document.rs | 34 ++------------------ cove-macro/src/group.rs | 63 ++++++++++++++++++++++++++++++++++++++ cove-macro/src/lib.rs | 11 +++++++ cove-macro/src/util.rs | 33 ++++++++++++++++++++ 6 files changed, 117 insertions(+), 32 deletions(-) create mode 100644 cove-macro/src/group.rs create mode 100644 cove-macro/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index ecdc77d..bf3763b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,6 +136,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "case" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6c0e7b807d60291f42f33f58480c0bfafe28ed08286446f45e463728cf9c1c" + [[package]] name = "caseless" version = "0.2.1" @@ -294,6 +300,7 @@ dependencies = [ name = "cove-macro" version = "0.6.1" dependencies = [ + "case", "proc-macro2", "quote", "syn 2.0.15", diff --git a/cove-macro/Cargo.toml b/cove-macro/Cargo.toml index 66ec539..731111a 100644 --- a/cove-macro/Cargo.toml +++ b/cove-macro/Cargo.toml @@ -4,6 +4,7 @@ version = { workspace = true } edition = { workspace = true } [dependencies] +case = "1.0.0" proc-macro2 = "1.0.56" quote = "1.0.26" syn = "2.0.15" diff --git a/cove-macro/src/document.rs b/cove-macro/src/document.rs index c2841a9..e610977 100644 --- a/cove-macro/src/document.rs +++ b/cove-macro/src/document.rs @@ -2,39 +2,9 @@ use proc_macro2::TokenStream; use quote::quote; use syn::punctuated::Punctuated; use syn::spanned::Spanned; -use syn::{Data, DeriveInput, Expr, ExprLit, Field, Lit, LitStr, MetaNameValue, Token}; +use syn::{Data, DeriveInput, Field, MetaNameValue, Token}; -fn strlit(expr: &Expr) -> Option<&LitStr> { - match expr { - Expr::Lit(ExprLit { - lit: Lit::Str(lit), .. - }) => Some(lit), - _ => None, - } -} - -/// Given a struct field, this finds all attributes like `#[doc = "bla"]`, -/// unindents, concatenates and returns them. -fn docstring(field: &Field) -> syn::Result<String> { - let mut lines = vec![]; - - for attr in field - .attrs - .iter() - .filter(|attr| attr.path().is_ident("doc")) - { - if let Some(lit) = strlit(&attr.meta.require_name_value()?.value) { - let value = lit.value(); - let value = value - .strip_prefix(' ') - .map(|value| value.to_string()) - .unwrap_or(value); - lines.push(value); - } - } - - Ok(lines.join("\n")) -} +use crate::util::docstring; /// Given a struct field, this finds all key-value pairs of the form /// `#[document(key = value, ...)]`. diff --git a/cove-macro/src/group.rs b/cove-macro/src/group.rs new file mode 100644 index 0000000..3e0995c --- /dev/null +++ b/cove-macro/src/group.rs @@ -0,0 +1,63 @@ +use case::CaseExt; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::spanned::Spanned; +use syn::{Data, DeriveInput}; + +use crate::util; + +fn decapitalize(s: &str) -> String { + let mut chars = s.chars(); + if let Some(char) = chars.next() { + char.to_lowercase().chain(chars).collect() + } else { + String::new() + } +} + +pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { + let Data::Struct(data) = input.data else { + return Err(syn::Error::new(input.span(), "Must be a struct")); + }; + + let struct_ident = input.ident; + let enum_ident = format_ident!("{}Action", struct_ident); + + let mut enum_variants = vec![]; + let mut match_cases = vec![]; + for field in &data.fields { + if let Some(field_ident) = &field.ident { + let docstring = util::docstring(field)?; + let variant_ident = format_ident!("{}", field_ident.to_string().to_camel()); + + enum_variants.push(quote! { + #[doc = #docstring] + #variant_ident, + }); + + let description = decapitalize(&docstring); + let description = description.strip_suffix('.').unwrap_or(&description); + match_cases.push(quote!{ + () if input.matches(&self.#field_ident, #description) => Some(Self::Action::#variant_ident), + }); + } + } + + Ok(quote! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum #enum_ident { + #( #enum_variants )* + } + + impl crate::Group for #struct_ident { + type Action = #enum_ident; + + fn action(&self, input: &mut crate::Input) -> Option<Self::Action> { + match () { + #( #match_cases )* + () => None, + } + } + } + }) +} diff --git a/cove-macro/src/lib.rs b/cove-macro/src/lib.rs index b05190e..f064a73 100644 --- a/cove-macro/src/lib.rs +++ b/cove-macro/src/lib.rs @@ -12,6 +12,8 @@ use syn::{parse_macro_input, DeriveInput}; mod document; +mod group; +mod util; #[proc_macro_derive(Document, attributes(document))] pub fn derive_document(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -21,3 +23,12 @@ pub fn derive_document(input: proc_macro::TokenStream) -> proc_macro::TokenStrea Err(err) => err.into_compile_error().into(), } } + +#[proc_macro_derive(Group)] +pub fn derive_group(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + match group::derive_impl(input) { + Ok(tokens) => tokens.into(), + Err(err) => err.into_compile_error().into(), + } +} diff --git a/cove-macro/src/util.rs b/cove-macro/src/util.rs new file mode 100644 index 0000000..c733c69 --- /dev/null +++ b/cove-macro/src/util.rs @@ -0,0 +1,33 @@ +use syn::{Expr, ExprLit, Field, Lit, LitStr}; + +pub fn strlit(expr: &Expr) -> Option<&LitStr> { + match expr { + Expr::Lit(ExprLit { + lit: Lit::Str(lit), .. + }) => Some(lit), + _ => None, + } +} + +/// Given a struct field, this finds all attributes like `#[doc = "bla"]`, +/// unindents, concatenates and returns them. +pub fn docstring(field: &Field) -> syn::Result<String> { + let mut lines = vec![]; + + for attr in field + .attrs + .iter() + .filter(|attr| attr.path().is_ident("doc")) + { + if let Some(lit) = strlit(&attr.meta.require_name_value()?.value) { + let value = lit.value(); + let value = value + .strip_prefix(' ') + .map(|value| value.to_string()) + .unwrap_or(value); + lines.push(value); + } + } + + Ok(lines.join("\n")) +} From 478e3e767cd7a6fe3450e12c3921de76cdc04704 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 26 Apr 2023 15:24:23 +0200 Subject: [PATCH 111/266] Add some key binding groups --- Cargo.lock | 1 + cove-input/Cargo.toml | 2 + cove-input/src/groups.rs | 95 ++++++++++++++++++++++++++++++++++++++++ cove-input/src/lib.rs | 2 + 4 files changed, 100 insertions(+) create mode 100644 cove-input/src/groups.rs diff --git a/Cargo.lock b/Cargo.lock index bf3763b..a9e3df1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -290,6 +290,7 @@ dependencies = [ name = "cove-input" version = "0.6.1" dependencies = [ + "cove-macro", "crossterm", "serde", "serde_either", diff --git a/cove-input/Cargo.toml b/cove-input/Cargo.toml index 2c3c243..61e178f 100644 --- a/cove-input/Cargo.toml +++ b/cove-input/Cargo.toml @@ -4,6 +4,8 @@ version = { workspace = true } edition = { workspace = true } [dependencies] +cove-macro = { path = "../cove-macro" } + crossterm = { workspace = true } serde = { workspace = true } serde_either = { workspace = true } diff --git a/cove-input/src/groups.rs b/cove-input/src/groups.rs new file mode 100644 index 0000000..bf13abe --- /dev/null +++ b/cove-input/src/groups.rs @@ -0,0 +1,95 @@ +use cove_macro::Group; + +use crate::KeyBinding; + +#[derive(Debug, Group)] +pub struct General { + /// Quit cove. + pub exit: KeyBinding, + /// Abort/close. + pub abort: KeyBinding, + /// Confirm. + pub confirm: KeyBinding, + /// Show this help. + pub help: KeyBinding, + /// Show log. + pub log: KeyBinding, +} + +#[derive(Debug, Group)] +pub struct Scroll { + /// Scroll up one line. + pub up_line: KeyBinding, + /// Scroll down one line. + pub down_line: KeyBinding, + /// Scroll up half a screen. + pub up_half: KeyBinding, + /// Scroll down half a screen. + pub down_half: KeyBinding, + /// Scroll up a full screen. + pub up_full: KeyBinding, + /// Scroll down a full screen. + pub down_full: KeyBinding, +} + +#[derive(Debug, Group)] +pub struct Cursor { + /// Move cursor up. + pub up: KeyBinding, + /// Move cursor down. + pub down: KeyBinding, + /// Move cursor to top. + pub to_top: KeyBinding, + /// Move cursor to bottom. + pub to_bottom: KeyBinding, + /// Center cursor. + pub center: KeyBinding, +} + +#[derive(Debug, Group)] +pub struct TreeCursor { + /// Move cursor to above sibling. + pub to_above_sibling: KeyBinding, + /// Move cursor to below sibling. + pub to_below_sibling: KeyBinding, + /// Move cursor to parent. + pub to_parent: KeyBinding, + /// Move cursor to root. + pub to_root: KeyBinding, + /// Move cursor to previous message. + pub to_prev_message: KeyBinding, + /// Move cursor to next message. + pub to_next_message: KeyBinding, +} + +#[derive(Debug, Group)] +pub struct EditorCursor { + /// Move cursor left. + pub left: KeyBinding, + /// Move cursor right. + pub right: KeyBinding, + /// Move cursor left a word. + pub left_word: KeyBinding, + /// Move cursor right a word. + pub right_word: KeyBinding, + /// Move cursor to start of line. + pub start: KeyBinding, + /// Move cursor to end of line. + pub end: KeyBinding, + /// Move cursor up. + pub up: KeyBinding, + /// Move cursor down. + pub down: KeyBinding, +} + +#[derive(Debug, Group)] +pub struct EditorOp { + /// Insert newline. + pub newline: KeyBinding, + /// Delete before cursor. + pub backspace: KeyBinding, + /// Delete after cursor. + pub delete: KeyBinding, + /// Clear editor contents. + pub clear: KeyBinding, +} diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index d3963ee..1ee29d4 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -1,6 +1,8 @@ +mod groups; mod input; mod keys; +pub use groups::*; pub use input::*; pub use keys::*; From 5a0efd69e487d2fad2fa3673058fc17a73ac8517 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 27 Apr 2023 15:27:23 +0200 Subject: [PATCH 112/266] Extract euph config into submodule --- cove-config/src/doc.rs | 4 +++ cove-config/src/euph.rs | 55 +++++++++++++++++++++++++++++++++++++++++ cove-config/src/lib.rs | 55 +++-------------------------------------- 3 files changed, 62 insertions(+), 52 deletions(-) create mode 100644 cove-config/src/euph.rs diff --git a/cove-config/src/doc.rs b/cove-config/src/doc.rs index dc232f4..4a344a0 100644 --- a/cove-config/src/doc.rs +++ b/cove-config/src/doc.rs @@ -1,6 +1,10 @@ +//! Auto-generate markdown documentation. + use std::collections::HashMap; use std::path::PathBuf; +pub use cove_macro::Document; + #[derive(Clone, Default)] pub struct ValueInfo { pub required: Option<bool>, diff --git a/cove-config/src/euph.rs b/cove-config/src/euph.rs new file mode 100644 index 0000000..2c50c24 --- /dev/null +++ b/cove-config/src/euph.rs @@ -0,0 +1,55 @@ +use std::collections::HashMap; + +use serde::Deserialize; + +use crate::doc::{Doc, Document}; + +#[derive(Debug, Clone, Copy, Default, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum RoomsSortOrder { + #[default] + Alphabet, + Importance, +} + +impl Document for RoomsSortOrder { + fn doc() -> Doc { + let mut doc = String::doc(); + doc.value_info.values = Some(vec![ + // TODO Generate by serializing + "`alphabet`".to_string(), + "`importance`".to_string(), + ]); + doc + } +} + +// TODO Mark favourite rooms via printable ascii characters +#[derive(Debug, Clone, Default, Deserialize, Document)] +pub struct EuphRoom { + /// Whether to automatically join this room on startup. + #[serde(default)] + #[document(default = "`false`")] + pub autojoin: bool, + + /// If set, cove will set this username upon joining if there is no username + /// associated with the current session. + pub username: Option<String>, + + /// If `euph.rooms.<room>.username` is set, this will force cove to set the + /// username even if there is already a different username associated with + /// the current session. + #[serde(default)] + #[document(default = "`false`")] + pub force_username: bool, + + /// If set, cove will try once to use this password to authenticate, should + /// the room be password-protected. + pub password: Option<String>, +} + +#[derive(Debug, Default, Deserialize, Document)] +pub struct Euph { + #[document(metavar = "room")] + pub rooms: HashMap<String, EuphRoom>, +} diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 1f17988..9b81c28 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -10,64 +10,15 @@ #![warn(clippy::use_self)] pub mod doc; +mod euph; -use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; -use cove_macro::Document; -use doc::{Doc, Document}; +use doc::Document; use serde::Deserialize; -#[derive(Debug, Clone, Copy, Default, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum RoomsSortOrder { - #[default] - Alphabet, - Importance, -} - -impl Document for RoomsSortOrder { - fn doc() -> Doc { - let mut doc = String::doc(); - doc.value_info.values = Some(vec![ - // TODO Generate by serializing - "`alphabet`".to_string(), - "`importance`".to_string(), - ]); - doc - } -} - -// TODO Mark favourite rooms via printable ascii characters -#[derive(Debug, Clone, Default, Deserialize, Document)] -pub struct EuphRoom { - /// Whether to automatically join this room on startup. - #[serde(default)] - #[document(default = "`false`")] - pub autojoin: bool, - - /// If set, cove will set this username upon joining if there is no username - /// associated with the current session. - pub username: Option<String>, - - /// If `euph.rooms.<room>.username` is set, this will force cove to set the - /// username even if there is already a different username associated with - /// the current session. - #[serde(default)] - #[document(default = "`false`")] - pub force_username: bool, - - /// If set, cove will try once to use this password to authenticate, should - /// the room be password-protected. - pub password: Option<String>, -} - -#[derive(Debug, Default, Deserialize, Document)] -pub struct Euph { - #[document(metavar = "room")] - pub rooms: HashMap<String, EuphRoom>, -} +pub use crate::euph::*; #[derive(Debug, Default, Deserialize, Document)] pub struct Config { From e1c3a463b25e59b2825d2c5fb37cab97934e0d48 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 27 Apr 2023 15:31:49 +0200 Subject: [PATCH 113/266] Move key binding groups to config crate --- Cargo.lock | 1 + cove-config/Cargo.toml | 1 + .../src/groups.rs => cove-config/src/keys.rs | 4 +--- cove-config/src/lib.rs | 2 ++ cove-input/src/keys.rs | 24 +++++++++++++++++++ cove-input/src/lib.rs | 4 ++-- cove-macro/src/group.rs | 4 ++-- 7 files changed, 33 insertions(+), 7 deletions(-) rename cove-input/src/groups.rs => cove-config/src/keys.rs (97%) diff --git a/Cargo.lock b/Cargo.lock index a9e3df1..346690d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -281,6 +281,7 @@ dependencies = [ name = "cove-config" version = "0.6.1" dependencies = [ + "cove-input", "cove-macro", "serde", "toml", diff --git a/cove-config/Cargo.toml b/cove-config/Cargo.toml index 65c8d55..252e228 100644 --- a/cove-config/Cargo.toml +++ b/cove-config/Cargo.toml @@ -4,6 +4,7 @@ version = { workspace = true } edition = { workspace = true } [dependencies] +cove-input = { path = "../cove-input" } cove-macro = { path = "../cove-macro" } serde = { workspace = true } diff --git a/cove-input/src/groups.rs b/cove-config/src/keys.rs similarity index 97% rename from cove-input/src/groups.rs rename to cove-config/src/keys.rs index bf13abe..1823335 100644 --- a/cove-input/src/groups.rs +++ b/cove-config/src/keys.rs @@ -1,6 +1,4 @@ -use cove_macro::Group; - -use crate::KeyBinding; +use cove_input::{Group, KeyBinding}; #[derive(Debug, Group)] pub struct General { diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 9b81c28..e90f05a 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -11,6 +11,7 @@ pub mod doc; mod euph; +mod keys; use std::fs; use std::path::{Path, PathBuf}; @@ -19,6 +20,7 @@ use doc::Document; use serde::Deserialize; pub use crate::euph::*; +pub use crate::keys::*; #[derive(Debug, Default, Deserialize, Document)] pub struct Config { diff --git a/cove-input/src/keys.rs b/cove-input/src/keys.rs index ddedcd9..da7de81 100644 --- a/cove-input/src/keys.rs +++ b/cove-input/src/keys.rs @@ -173,11 +173,35 @@ impl<'de> Deserialize<'de> for KeyPress { pub struct KeyBinding(Vec<KeyPress>); impl KeyBinding { + pub fn new() -> Self { + Self(vec![]) + } + + pub fn with_key(self, key: &str) -> Result<Self, ParseKeysError> { + self.with_keys([key]) + } + + pub fn with_keys<'a, I>(mut self, keys: I) -> Result<Self, ParseKeysError> + where + I: IntoIterator<Item = &'a str>, + { + for key in keys { + self.0.push(key.parse()?); + } + Ok(self) + } + pub fn matches(&self, event: KeyEvent) -> bool { self.0.iter().any(|kp| kp.matches(event)) } } +impl Default for KeyBinding { + fn default() -> Self { + Self::new() + } +} + impl Serialize for KeyBinding { fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { if self.0.len() == 1 { diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index 1ee29d4..52dc17d 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -1,8 +1,8 @@ -mod groups; mod input; mod keys; -pub use groups::*; +pub use cove_macro::Group; + pub use input::*; pub use keys::*; diff --git a/cove-macro/src/group.rs b/cove-macro/src/group.rs index 3e0995c..f273ad8 100644 --- a/cove-macro/src/group.rs +++ b/cove-macro/src/group.rs @@ -49,10 +49,10 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { #( #enum_variants )* } - impl crate::Group for #struct_ident { + impl ::cove_input::Group for #struct_ident { type Action = #enum_ident; - fn action(&self, input: &mut crate::Input) -> Option<Self::Action> { + fn action(&self, input: &mut ::cove_input::Input) -> Option<Self::Action> { match () { #( #match_cases )* () => None, From c53e3c262e132765262c550974a4daf70dae5089 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 27 Apr 2023 18:13:43 +0200 Subject: [PATCH 114/266] Include key bindings in config --- cove-config/src/doc.rs | 10 ++ cove-config/src/keys.rs | 311 +++++++++++++++++++++++++++++++++++++--- cove-config/src/lib.rs | 3 + 3 files changed, 305 insertions(+), 19 deletions(-) diff --git a/cove-config/src/doc.rs b/cove-config/src/doc.rs index 4a344a0..9253364 100644 --- a/cove-config/src/doc.rs +++ b/cove-config/src/doc.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::path::PathBuf; +use cove_input::KeyBinding; pub use cove_macro::Document; #[derive(Clone, Default)] @@ -192,3 +193,12 @@ impl<I: Document> Document for HashMap<String, I> { doc } } + +impl Document for KeyBinding { + fn doc() -> Doc { + let mut doc = Doc::default(); + doc.value_info.required = Some(true); + doc.value_info.r#type = Some("key binding".to_string()); + doc + } +} diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index 1823335..5e2e45e 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -1,93 +1,366 @@ use cove_input::{Group, KeyBinding}; +use serde::Deserialize; -#[derive(Debug, Group)] +use crate::doc::Document; + +macro_rules! default_bindings { + ( $( + pub mod $mod:ident { $( + pub fn $name:ident => [ $($key:expr),* ]; + )* } + )*) => { + mod default { $( + pub mod $mod { $( + pub fn $name() -> ::cove_input::KeyBinding { + ::cove_input::KeyBinding::new().with_keys([ $($key),* ]).unwrap() + } + )* } + )* } + }; +} + +default_bindings! { + pub mod general { + pub fn exit => ["ctrl+c"]; + pub fn abort => ["esc"]; + pub fn confirm => ["enter"]; + pub fn help => ["f1", "?"]; + pub fn log => ["f12"]; + } + + pub mod scroll { + pub fn up_line => ["ctrl+y"]; + pub fn down_line => ["ctrl+e"]; + pub fn up_half => ["ctrl+u"]; + pub fn down_half => ["ctrl+d"]; + pub fn up_full => ["ctrl+b"]; + pub fn down_full => ["ctrl+f"]; + } + + pub mod cursor { + pub fn up => ["k", "up"]; + pub fn down => ["j", "down"]; + pub fn to_top => ["g", "home"]; + pub fn to_bottom => ["G", "end"]; + pub fn center => ["z"]; + } + + pub mod tree_cursor { + pub fn to_above_sibling => ["K", "ctrl+up"]; + pub fn to_below_sibling => ["J", "ctrl+down"]; + pub fn to_parent => ["p"]; + pub fn to_root => ["P"]; + pub fn to_prev_message => ["h", "left"]; + pub fn to_next_message => ["l", "right"]; + pub fn to_prev_unseen_message => ["H", "ctrl+left"]; + pub fn to_next_unseen_message => ["L", "ctrl+right"]; + } + + pub mod tree_op { + pub fn reply => ["r"]; + pub fn reply_alternate => ["R"]; + pub fn new_thread => ["t"]; + pub fn fold_tree => [" "]; + pub fn toggle_seen => ["s"]; + pub fn mark_visible_seen => ["S"]; + pub fn mark_older_seen => ["ctrl+s"]; + } + + pub mod editor_cursor { + pub fn left => []; + pub fn right => []; + pub fn left_word => []; + pub fn right_word => []; + pub fn start => []; + pub fn end => []; + pub fn up => []; + pub fn down => []; + } + + pub mod editor_op { + pub fn newline => []; + pub fn backspace => []; + pub fn delete => []; + pub fn clear => []; + } + +} + +#[derive(Debug, Deserialize, Document, Group)] pub struct General { /// Quit cove. + #[serde(default = "default::general::exit")] pub exit: KeyBinding, /// Abort/close. + #[serde(default = "default::general::abort")] pub abort: KeyBinding, /// Confirm. + #[serde(default = "default::general::confirm")] pub confirm: KeyBinding, /// Show this help. + #[serde(default = "default::general::help")] pub help: KeyBinding, /// Show log. + #[serde(default = "default::general::log")] pub log: KeyBinding, } -#[derive(Debug, Group)] +impl Default for General { + fn default() -> Self { + Self { + exit: default::general::exit(), + abort: default::general::abort(), + confirm: default::general::confirm(), + help: default::general::help(), + log: default::general::log(), + } + } +} + +#[derive(Debug, Deserialize, Document, Group)] pub struct Scroll { /// Scroll up one line. + #[serde(default = "default::scroll::up_line")] pub up_line: KeyBinding, /// Scroll down one line. + #[serde(default = "default::scroll::down_line")] pub down_line: KeyBinding, /// Scroll up half a screen. + #[serde(default = "default::scroll::up_half")] pub up_half: KeyBinding, /// Scroll down half a screen. + #[serde(default = "default::scroll::down_half")] pub down_half: KeyBinding, /// Scroll up a full screen. + #[serde(default = "default::scroll::up_full")] pub up_full: KeyBinding, /// Scroll down a full screen. + #[serde(default = "default::scroll::down_full")] pub down_full: KeyBinding, } -#[derive(Debug, Group)] +impl Default for Scroll { + fn default() -> Self { + Self { + up_line: default::scroll::up_line(), + down_line: default::scroll::down_line(), + up_half: default::scroll::up_half(), + down_half: default::scroll::down_half(), + up_full: default::scroll::up_full(), + down_full: default::scroll::down_full(), + } + } +} + +#[derive(Debug, Deserialize, Document, Group)] pub struct Cursor { /// Move cursor up. + #[serde(default = "default::cursor::up")] pub up: KeyBinding, /// Move cursor down. + #[serde(default = "default::cursor::down")] pub down: KeyBinding, /// Move cursor to top. + #[serde(default = "default::cursor::to_top")] pub to_top: KeyBinding, /// Move cursor to bottom. + #[serde(default = "default::cursor::to_bottom")] pub to_bottom: KeyBinding, /// Center cursor. + #[serde(default = "default::cursor::center")] pub center: KeyBinding, } -#[derive(Debug, Group)] -pub struct TreeCursor { - /// Move cursor to above sibling. - pub to_above_sibling: KeyBinding, - /// Move cursor to below sibling. - pub to_below_sibling: KeyBinding, - /// Move cursor to parent. - pub to_parent: KeyBinding, - /// Move cursor to root. - pub to_root: KeyBinding, - /// Move cursor to previous message. - pub to_prev_message: KeyBinding, - /// Move cursor to next message. - pub to_next_message: KeyBinding, +impl Default for Cursor { + fn default() -> Self { + Self { + up: default::cursor::up(), + down: default::cursor::down(), + to_top: default::cursor::to_top(), + to_bottom: default::cursor::to_bottom(), + center: default::cursor::center(), + } + } } -#[derive(Debug, Group)] +#[derive(Debug, Deserialize, Document, Group)] pub struct EditorCursor { /// Move cursor left. + #[serde(default = "default::editor_cursor::left")] pub left: KeyBinding, /// Move cursor right. + #[serde(default = "default::editor_cursor::right")] pub right: KeyBinding, /// Move cursor left a word. + #[serde(default = "default::editor_cursor::left_word")] pub left_word: KeyBinding, /// Move cursor right a word. + #[serde(default = "default::editor_cursor::right_word")] pub right_word: KeyBinding, /// Move cursor to start of line. + #[serde(default = "default::editor_cursor::start")] pub start: KeyBinding, /// Move cursor to end of line. + #[serde(default = "default::editor_cursor::end")] pub end: KeyBinding, /// Move cursor up. + #[serde(default = "default::editor_cursor::up")] pub up: KeyBinding, /// Move cursor down. + #[serde(default = "default::editor_cursor::down")] pub down: KeyBinding, } -#[derive(Debug, Group)] +impl Default for EditorCursor { + fn default() -> Self { + Self { + left: default::editor_cursor::left(), + right: default::editor_cursor::right(), + left_word: default::editor_cursor::left_word(), + right_word: default::editor_cursor::right_word(), + start: default::editor_cursor::start(), + end: default::editor_cursor::end(), + up: default::editor_cursor::up(), + down: default::editor_cursor::down(), + } + } +} + +#[derive(Debug, Deserialize, Document, Group)] pub struct EditorOp { /// Insert newline. + #[serde(default = "default::editor_op::newline")] pub newline: KeyBinding, /// Delete before cursor. + #[serde(default = "default::editor_op::backspace")] pub backspace: KeyBinding, /// Delete after cursor. + #[serde(default = "default::editor_op::delete")] pub delete: KeyBinding, /// Clear editor contents. + #[serde(default = "default::editor_op::clear")] pub clear: KeyBinding, } + +impl Default for EditorOp { + fn default() -> Self { + Self { + newline: default::editor_op::newline(), + backspace: default::editor_op::backspace(), + delete: default::editor_op::delete(), + clear: default::editor_op::clear(), + } + } +} + +#[derive(Debug, Default, Deserialize, Document)] +pub struct Editor { + #[serde(default)] + pub cursor: EditorCursor, + #[serde(default)] + pub action: EditorOp, +} + +#[derive(Debug, Deserialize, Document, Group)] +pub struct TreeCursor { + /// Move cursor to above sibling. + #[serde(default = "default::tree_cursor::to_above_sibling")] + pub to_above_sibling: KeyBinding, + /// Move cursor to below sibling. + #[serde(default = "default::tree_cursor::to_below_sibling")] + pub to_below_sibling: KeyBinding, + /// Move cursor to parent. + #[serde(default = "default::tree_cursor::to_parent")] + pub to_parent: KeyBinding, + /// Move cursor to root. + #[serde(default = "default::tree_cursor::to_root")] + pub to_root: KeyBinding, + /// Move cursor to previous message. + #[serde(default = "default::tree_cursor::to_prev_message")] + pub to_prev_message: KeyBinding, + /// Move cursor to next message. + #[serde(default = "default::tree_cursor::to_next_message")] + pub to_next_message: KeyBinding, + /// Move cursor to previous unseen message. + #[serde(default = "default::tree_cursor::to_prev_unseen_message")] + pub to_prev_unseen_message: KeyBinding, + /// Move cursor to next unseen message. + #[serde(default = "default::tree_cursor::to_next_unseen_message")] + pub to_next_unseen_message: KeyBinding, +} + +impl Default for TreeCursor { + fn default() -> Self { + Self { + to_above_sibling: default::tree_cursor::to_above_sibling(), + to_below_sibling: default::tree_cursor::to_below_sibling(), + to_parent: default::tree_cursor::to_parent(), + to_root: default::tree_cursor::to_root(), + to_prev_message: default::tree_cursor::to_prev_message(), + to_next_message: default::tree_cursor::to_next_message(), + to_prev_unseen_message: default::tree_cursor::to_prev_unseen_message(), + to_next_unseen_message: default::tree_cursor::to_next_unseen_message(), + } + } +} + +#[derive(Debug, Deserialize, Document, Group)] +pub struct TreeOp { + /// Reply to message (inline if possible). + #[serde(default = "default::tree_op::reply")] + pub reply: KeyBinding, + /// Reply to message, opposite of normal reply. + #[serde(default = "default::tree_op::reply_alternate")] + pub reply_alternate: KeyBinding, + /// Start a new thread. + #[serde(default = "default::tree_op::new_thread")] + pub new_thread: KeyBinding, + /// Fold current message's subtree. + #[serde(default = "default::tree_op::fold_tree")] + pub fold_tree: KeyBinding, + /// Toggle current message's seen status. + #[serde(default = "default::tree_op::toggle_seen")] + pub toggle_seen: KeyBinding, + /// Mark all visible messages as seen. + #[serde(default = "default::tree_op::mark_visible_seen")] + pub mark_visible_seen: KeyBinding, + /// Mark all older messages as seen. + #[serde(default = "default::tree_op::mark_older_seen")] + pub mark_older_seen: KeyBinding, +} + +impl Default for TreeOp { + fn default() -> Self { + Self { + reply: default::tree_op::reply(), + reply_alternate: default::tree_op::reply_alternate(), + new_thread: default::tree_op::new_thread(), + fold_tree: default::tree_op::fold_tree(), + toggle_seen: default::tree_op::toggle_seen(), + mark_visible_seen: default::tree_op::mark_visible_seen(), + mark_older_seen: default::tree_op::mark_older_seen(), + } + } +} + +#[derive(Debug, Default, Deserialize, Document)] +pub struct Tree { + #[serde(default)] + pub cursor: TreeCursor, + #[serde(default)] + pub action: TreeOp, +} + +#[derive(Debug, Default, Deserialize, Document)] +pub struct Keys { + #[serde(default)] + pub general: General, + #[serde(default)] + pub scroll: Scroll, + #[serde(default)] + pub cursor: Cursor, + #[serde(default)] + pub editor: Editor, + #[serde(default)] + pub tree: Tree, +} diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index e90f05a..ef9c731 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -82,6 +82,9 @@ pub struct Config { // TODO Invoke external notification command? pub euph: Euph, + + #[serde(default)] + pub keys: Keys, } impl Config { From 9f24cb2de133042d1be08597df355a88395a52e4 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 27 Apr 2023 18:27:53 +0200 Subject: [PATCH 115/266] Fix function key parsing --- cove-input/src/keys.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cove-input/src/keys.rs b/cove-input/src/keys.rs index da7de81..ebd4aa3 100644 --- a/cove-input/src/keys.rs +++ b/cove-input/src/keys.rs @@ -48,8 +48,8 @@ impl KeyPress { "delete" => KeyCode::Delete, "insert" => KeyCode::Insert, "esc" => KeyCode::Esc, - c if c.starts_with('F') => KeyCode::F(c.strip_prefix('F').unwrap().parse()?), c if c.chars().count() == 1 => KeyCode::Char(c.chars().next().unwrap()), + c if c.starts_with('f') => KeyCode::F(c.strip_prefix('f').unwrap().parse()?), "" => return Err(ParseKeysError::NoKeyCode), c => return Err(ParseKeysError::UnknownKeyCode(c.to_string())), }; @@ -79,7 +79,7 @@ impl KeyPress { KeyCode::Delete => "delete".to_string(), KeyCode::Insert => "insert".to_string(), KeyCode::Esc => "esc".to_string(), - KeyCode::F(n) => format!("F{n}"), + KeyCode::F(n) => format!("f{n}"), KeyCode::Char(c) => c.to_string(), _ => "unknown".to_string(), } From d29441bf024a2c7742385a5f875ddf05584152b8 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 27 Apr 2023 20:07:25 +0200 Subject: [PATCH 116/266] Move todo to proper location --- cove-config/src/lib.rs | 1 - cove/src/main.rs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index ef9c731..13d6ead 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -80,7 +80,6 @@ pub struct Config { #[document(default = "`alphabet`")] pub rooms_sort_order: RoomsSortOrder, - // TODO Invoke external notification command? pub euph: Euph, #[serde(default)] diff --git a/cove/src/main.rs b/cove/src/main.rs index 0c99ee4..2bd8271 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -13,6 +13,7 @@ // TODO Remove unnecessary Debug impls and compare compile times // TODO Time zones other than UTC // TODO Fix password room auth +// TODO Invoke external notification command? mod euph; mod export; From 458025b8bf185d4b95da3c7462ad0dade8fb1fd7 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 27 Apr 2023 20:11:16 +0200 Subject: [PATCH 117/266] Use serde's default annotation for Document --- cove-config/src/doc.rs | 11 ++- cove-config/src/euph.rs | 2 - cove-config/src/keys.rs | 15 ++++ cove-config/src/lib.rs | 3 + cove-macro/src/document.rs | 173 ++++++++++++++++++++++++------------- cove-macro/src/util.rs | 55 +++++++++++- cove/src/main.rs | 2 +- 7 files changed, 194 insertions(+), 67 deletions(-) diff --git a/cove-config/src/doc.rs b/cove-config/src/doc.rs index 9253364..da27b89 100644 --- a/cove-config/src/doc.rs +++ b/cove-config/src/doc.rs @@ -5,6 +5,15 @@ use std::path::PathBuf; use cove_input::KeyBinding; pub use cove_macro::Document; +use serde::Serialize; + +pub(crate) fn toml_value_as_markdown<T: Serialize>(value: &T) -> String { + let mut result = String::new(); + value + .serialize(toml::ser::ValueSerializer::new(&mut result)) + .expect("not a valid toml value"); + format!("`{result}`") +} #[derive(Clone, Default)] pub struct ValueInfo { @@ -118,7 +127,7 @@ impl Doc { entries } - pub fn format_as_markdown(&self) -> String { + pub fn as_markdown(&self) -> String { // Print entries in alphabetical order to make generated documentation // format more stable. let mut entries = self.entries(); diff --git a/cove-config/src/euph.rs b/cove-config/src/euph.rs index 2c50c24..d2fa84a 100644 --- a/cove-config/src/euph.rs +++ b/cove-config/src/euph.rs @@ -29,7 +29,6 @@ impl Document for RoomsSortOrder { pub struct EuphRoom { /// Whether to automatically join this room on startup. #[serde(default)] - #[document(default = "`false`")] pub autojoin: bool, /// If set, cove will set this username upon joining if there is no username @@ -40,7 +39,6 @@ pub struct EuphRoom { /// username even if there is already a different username associated with /// the current session. #[serde(default)] - #[document(default = "`false`")] pub force_username: bool, /// If set, cove will try once to use this password to authenticate, should diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index 5e2e45e..77029dc 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -256,8 +256,11 @@ impl Default for EditorOp { #[derive(Debug, Default, Deserialize, Document)] pub struct Editor { #[serde(default)] + #[document(no_default)] pub cursor: EditorCursor, + #[serde(default)] + #[document(no_default)] pub action: EditorOp, } @@ -346,21 +349,33 @@ impl Default for TreeOp { #[derive(Debug, Default, Deserialize, Document)] pub struct Tree { #[serde(default)] + #[document(no_default)] pub cursor: TreeCursor, + #[serde(default)] + #[document(no_default)] pub action: TreeOp, } #[derive(Debug, Default, Deserialize, Document)] pub struct Keys { #[serde(default)] + #[document(no_default)] pub general: General, + #[serde(default)] + #[document(no_default)] pub scroll: Scroll, + #[serde(default)] + #[document(no_default)] pub cursor: Cursor, + #[serde(default)] + #[document(no_default)] pub editor: Editor, + #[serde(default)] + #[document(no_default)] pub tree: Tree, } diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 13d6ead..6d00391 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -80,9 +80,12 @@ pub struct Config { #[document(default = "`alphabet`")] pub rooms_sort_order: RoomsSortOrder, + #[serde(default)] + #[document(no_default)] pub euph: Euph, #[serde(default)] + #[document(no_default)] pub keys: Keys, } diff --git a/cove-macro/src/document.rs b/cove-macro/src/document.rs index e610977..e6845d2 100644 --- a/cove-macro/src/document.rs +++ b/cove-macro/src/document.rs @@ -1,89 +1,142 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::punctuated::Punctuated; use syn::spanned::Spanned; -use syn::{Data, DeriveInput, Field, MetaNameValue, Token}; +use syn::{Data, DeriveInput, ExprPath, Field, LitStr, Type}; -use crate::util::docstring; +use crate::util::{self, docstring}; -/// Given a struct field, this finds all key-value pairs of the form -/// `#[document(key = value, ...)]`. -fn document_attributes(field: &Field) -> syn::Result<Vec<MetaNameValue>> { - let mut attrs = vec![]; - - for attr in field - .attrs - .iter() - .filter(|attr| attr.path().is_ident("document")) - { - let args = - attr.parse_args_with(Punctuated::<MetaNameValue, Token![,]>::parse_terminated)?; - attrs.extend(args); - } - - Ok(attrs) +enum SerdeDefault { + Default(Type), + Path(ExprPath), } -fn field_doc(field: &Field) -> syn::Result<Option<TokenStream>> { - let Some(ident) = field.ident.as_ref() else { return Ok(None); }; - let ident = ident.to_string(); - let ty = &field.ty; +#[derive(Default)] +struct FieldInfo { + description: Option<String>, + metavar: Option<LitStr>, + default: Option<LitStr>, + serde_default: Option<SerdeDefault>, + no_default: bool, +} - let mut setters = vec![]; - - let docstring = docstring(field)?; - if !docstring.is_empty() { - setters.push(quote! { - doc.description = Some(#docstring.to_string()); - }); - } - - for attr in document_attributes(field)? { - let value = attr.value; - if attr.path.is_ident("default") { - setters.push(quote! { doc.value_info.default = Some(#value.to_string()); }); - } else if attr.path.is_ident("metavar") { - setters.push(quote! { doc.wrap_info.metavar = Some(#value.to_string()); }); - } else { - return Err(syn::Error::new(attr.path.span(), "unknown argument name")); +impl FieldInfo { + fn initialize_from_field(&mut self, field: &Field) -> syn::Result<()> { + let docstring = docstring(field)?; + if !docstring.is_empty() { + self.description = Some(docstring); } + + for arg in util::attribute_arguments(field, "document")? { + if arg.path.is_ident("metavar") { + // Parse `#[document(metavar = "bla")]` + if let Some(metavar) = arg.value.and_then(util::into_litstr) { + self.metavar = Some(metavar); + } else { + util::bail(arg.path.span(), "must be of the form `key = \"value\"`")?; + } + } else if arg.path.is_ident("default") { + // Parse `#[document(default = "bla")]` + if let Some(value) = arg.value.and_then(util::into_litstr) { + self.default = Some(value); + } else { + util::bail(arg.path.span(), "must be of the form `key = \"value\"`")?; + } + } else if arg.path.is_ident("no_default") { + // Parse #[document(no_default)] + if arg.value.is_some() { + util::bail(arg.path.span(), "must not have a value")?; + } + self.no_default = true; + } else { + util::bail(arg.path.span(), "unknown argument name")?; + } + } + + // Find `#[serde(default)]` or `#[serde(default = "bla")]`. + for arg in util::attribute_arguments(field, "serde")? { + if arg.path.is_ident("default") { + if let Some(value) = arg.value { + if let Some(path) = util::into_litstr(value) { + self.serde_default = Some(SerdeDefault::Path(path.parse()?)); + } + } else { + self.serde_default = Some(SerdeDefault::Default(field.ty.clone())); + } + } + } + + Ok(()) } - Ok(Some(quote! { - fields.insert( - #ident.to_string(), - { - let mut doc = <#ty as Document>::doc(); - #( #setters )* - Box::new(doc) - } - ); - })) + fn from_field(field: &Field) -> syn::Result<Self> { + let mut result = Self::default(); + result.initialize_from_field(field)?; + Ok(result) + } } pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { let Data::Struct(data) = input.data else { - return Err(syn::Error::new(input.span(), "Must be a struct")); + return Err(syn::Error::new(input.span(), "must be a struct")); }; - let mut fields = Vec::new(); - for field in data.fields.iter() { - if let Some(field) = field_doc(field)? { - fields.push(field); + let mut fields = vec![]; + for field in data.fields { + let Some(ident) = field.ident.as_ref() else { + return util::bail(field.span(), "must not be a tuple struct"); + }; + let ident = ident.to_string(); + + let info = FieldInfo::from_field(&field)?; + + let mut setters = vec![]; + if let Some(description) = info.description { + setters.push(quote! { + doc.description = Some(#description.to_string()); + }); } + if let Some(metavar) = info.metavar { + setters.push(quote! { + doc.wrap_info.metavar = Some(#metavar.to_string()); + }); + } + if info.no_default { + } else if let Some(default) = info.default { + setters.push(quote! { + doc.value_info.default = Some(#default.to_string()); + }); + } else if let Some(serde_default) = info.serde_default { + setters.push(match serde_default { + SerdeDefault::Default(ty) => quote! { + doc.value_info.default = Some(crate::doc::toml_value_as_markdown(&<#ty as Default>::default())); + }, + SerdeDefault::Path(path) => quote! { + doc.value_info.default = Some(crate::doc::toml_value_as_markdown(&#path())); + }, + }); + } + + let ty = field.ty; + fields.push(quote! { + fields.insert( + #ident.to_string(), + { + let mut doc = <#ty as crate::doc::Document>::doc(); + #( #setters )* + ::std::boxed::Box::new(doc) + } + ); + }); } let ident = input.ident; let tokens = quote!( impl crate::doc::Document for #ident { fn doc() -> crate::doc::Doc { - use ::std::{boxed::Box, collections::HashMap}; - use crate::doc::{Doc, Document}; - - let mut fields = HashMap::new(); + let mut fields = ::std::collections::HashMap::new(); #( #fields )* - let mut doc = Doc::default(); + let mut doc = crate::doc::Doc::default(); doc.struct_info.fields = fields; doc } diff --git a/cove-macro/src/util.rs b/cove-macro/src/util.rs index c733c69..cf21159 100644 --- a/cove-macro/src/util.rs +++ b/cove-macro/src/util.rs @@ -1,6 +1,22 @@ -use syn::{Expr, ExprLit, Field, Lit, LitStr}; +use proc_macro2::Span; +use syn::parse::Parse; +use syn::punctuated::Punctuated; +use syn::{Expr, ExprLit, Field, Lit, LitStr, Path, Token}; -pub fn strlit(expr: &Expr) -> Option<&LitStr> { +pub fn bail<T>(span: Span, message: &str) -> syn::Result<T> { + Err(syn::Error::new(span, message)) +} + +pub fn litstr(expr: &Expr) -> Option<&LitStr> { + match expr { + Expr::Lit(ExprLit { + lit: Lit::Str(lit), .. + }) => Some(lit), + _ => None, + } +} + +pub fn into_litstr(expr: Expr) -> Option<LitStr> { match expr { Expr::Lit(ExprLit { lit: Lit::Str(lit), .. @@ -19,7 +35,7 @@ pub fn docstring(field: &Field) -> syn::Result<String> { .iter() .filter(|attr| attr.path().is_ident("doc")) { - if let Some(lit) = strlit(&attr.meta.require_name_value()?.value) { + if let Some(lit) = litstr(&attr.meta.require_name_value()?.value) { let value = lit.value(); let value = value .strip_prefix(' ') @@ -31,3 +47,36 @@ pub fn docstring(field: &Field) -> syn::Result<String> { Ok(lines.join("\n")) } + +pub struct AttributeArgument { + pub path: Path, + pub value: Option<Expr>, +} + +impl Parse for AttributeArgument { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> { + let path = Path::parse(input)?; + let value = if input.peek(Token![=]) { + input.parse::<Token![=]>()?; + Some(Expr::parse(input)?) + } else { + None + }; + Ok(Self { path, value }) + } +} + +/// Given a struct field, this finds all arguments of the form `#[path(key)]` +/// and `#[path(key = value)]`. Multiple arguments may be specified in a single +/// annotation, e.g. `#[foo(bar, baz = true)]`. +pub fn attribute_arguments(field: &Field, path: &str) -> syn::Result<Vec<AttributeArgument>> { + let mut attrs = vec![]; + + for attr in field.attrs.iter().filter(|attr| attr.path().is_ident(path)) { + let args = + attr.parse_args_with(Punctuated::<AttributeArgument, Token![,]>::parse_terminated)?; + attrs.extend(args); + } + + Ok(attrs) +} diff --git a/cove/src/main.rs b/cove/src/main.rs index 2bd8271..e7bc101 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -226,5 +226,5 @@ async fn clear_cookies(config: &'static Config, dirs: &ProjectDirs) -> anyhow::R } fn help_config() { - print!("{}", Config::doc().format_as_markdown()); + print!("{}", Config::doc().as_markdown()); } From 6f85995379286b583daa3bcc8c469b29b8b32f83 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 27 Apr 2023 20:34:46 +0200 Subject: [PATCH 118/266] Derive Document for simple enums --- cove-config/src/doc.rs | 2 +- cove-config/src/euph.rs | 18 +++--------------- cove-macro/src/document.rs | 39 +++++++++++++++++++++++++++++++------- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/cove-config/src/doc.rs b/cove-config/src/doc.rs index da27b89..2149b77 100644 --- a/cove-config/src/doc.rs +++ b/cove-config/src/doc.rs @@ -7,7 +7,7 @@ use cove_input::KeyBinding; pub use cove_macro::Document; use serde::Serialize; -pub(crate) fn toml_value_as_markdown<T: Serialize>(value: &T) -> String { +pub fn toml_value_as_markdown<T: Serialize>(value: &T) -> String { let mut result = String::new(); value .serialize(toml::ser::ValueSerializer::new(&mut result)) diff --git a/cove-config/src/euph.rs b/cove-config/src/euph.rs index d2fa84a..0584933 100644 --- a/cove-config/src/euph.rs +++ b/cove-config/src/euph.rs @@ -1,10 +1,10 @@ use std::collections::HashMap; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; -use crate::doc::{Doc, Document}; +use crate::doc::Document; -#[derive(Debug, Clone, Copy, Default, Deserialize)] +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, Document)] #[serde(rename_all = "snake_case")] pub enum RoomsSortOrder { #[default] @@ -12,18 +12,6 @@ pub enum RoomsSortOrder { Importance, } -impl Document for RoomsSortOrder { - fn doc() -> Doc { - let mut doc = String::doc(); - doc.value_info.values = Some(vec![ - // TODO Generate by serializing - "`alphabet`".to_string(), - "`importance`".to_string(), - ]); - doc - } -} - // TODO Mark favourite rooms via printable ascii characters #[derive(Debug, Clone, Default, Deserialize, Document)] pub struct EuphRoom { diff --git a/cove-macro/src/document.rs b/cove-macro/src/document.rs index e6845d2..75a0cfc 100644 --- a/cove-macro/src/document.rs +++ b/cove-macro/src/document.rs @@ -1,7 +1,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::spanned::Spanned; -use syn::{Data, DeriveInput, ExprPath, Field, LitStr, Type}; +use syn::{Data, DataEnum, DataStruct, DeriveInput, ExprPath, Field, Ident, LitStr, Type}; use crate::util::{self, docstring}; @@ -75,11 +75,7 @@ impl FieldInfo { } } -pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { - let Data::Struct(data) = input.data else { - return Err(syn::Error::new(input.span(), "must be a struct")); - }; - +fn from_struct(ident: Ident, data: DataStruct) -> syn::Result<TokenStream> { let mut fields = vec![]; for field in data.fields { let Some(ident) = field.ident.as_ref() else { @@ -129,7 +125,6 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { }); } - let ident = input.ident; let tokens = quote!( impl crate::doc::Document for #ident { fn doc() -> crate::doc::Doc { @@ -145,3 +140,33 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { Ok(tokens) } + +fn from_enum(ident: Ident, data: DataEnum) -> syn::Result<TokenStream> { + let mut values = vec![]; + for variant in data.variants { + let ident = variant.ident; + values.push(quote! { + crate::doc::toml_value_as_markdown(&Self::#ident) + }); + } + + let tokens = quote!( + impl crate::doc::Document for #ident { + fn doc() -> crate::doc::Doc { + let mut doc = <String as crate::doc::Document>::doc(); + doc.value_info.values = Some(vec![ #( #values ),* ]); + doc + } + } + ); + + Ok(tokens) +} + +pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { + match input.data { + Data::Struct(data) => from_struct(input.ident, data), + Data::Enum(data) => from_enum(input.ident, data), + Data::Union(_) => util::bail(input.span(), "must be an enum or a struct"), + } +} From 15177a529a5bd8f0aaff45a625c8bc10e8f3f00e Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 27 Apr 2023 20:36:00 +0200 Subject: [PATCH 119/266] Clean up macro code a bit --- cove-macro/src/document.rs | 4 ++-- cove-macro/src/group.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cove-macro/src/document.rs b/cove-macro/src/document.rs index 75a0cfc..cf75553 100644 --- a/cove-macro/src/document.rs +++ b/cove-macro/src/document.rs @@ -3,7 +3,7 @@ use quote::quote; use syn::spanned::Spanned; use syn::{Data, DataEnum, DataStruct, DeriveInput, ExprPath, Field, Ident, LitStr, Type}; -use crate::util::{self, docstring}; +use crate::util; enum SerdeDefault { Default(Type), @@ -21,7 +21,7 @@ struct FieldInfo { impl FieldInfo { fn initialize_from_field(&mut self, field: &Field) -> syn::Result<()> { - let docstring = docstring(field)?; + let docstring = util::docstring(field)?; if !docstring.is_empty() { self.description = Some(docstring); } diff --git a/cove-macro/src/group.rs b/cove-macro/src/group.rs index f273ad8..1864774 100644 --- a/cove-macro/src/group.rs +++ b/cove-macro/src/group.rs @@ -17,7 +17,7 @@ fn decapitalize(s: &str) -> String { pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { let Data::Struct(data) = input.data else { - return Err(syn::Error::new(input.span(), "Must be a struct")); + return util::bail(input.span(), "Must be a struct"); }; let struct_ident = input.ident; From 6c99f7a53a1cf317f201053daff19c8fc4f39574 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 27 Apr 2023 20:38:09 +0200 Subject: [PATCH 120/266] Rename Group::Action to Event --- cove-config/src/keys.rs | 60 ++++++++++++++++++++--------------------- cove-input/src/lib.rs | 4 +-- cove-macro/src/group.rs | 8 +++--- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index 77029dc..261c33e 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -56,7 +56,7 @@ default_bindings! { pub fn to_next_unseen_message => ["L", "ctrl+right"]; } - pub mod tree_op { + pub mod tree_action { pub fn reply => ["r"]; pub fn reply_alternate => ["R"]; pub fn new_thread => ["t"]; @@ -77,7 +77,7 @@ default_bindings! { pub fn down => []; } - pub mod editor_op { + pub mod editor_action { pub fn newline => []; pub fn backspace => []; pub fn delete => []; @@ -227,28 +227,28 @@ impl Default for EditorCursor { } #[derive(Debug, Deserialize, Document, Group)] -pub struct EditorOp { +pub struct EditorAction { /// Insert newline. - #[serde(default = "default::editor_op::newline")] + #[serde(default = "default::editor_action::newline")] pub newline: KeyBinding, /// Delete before cursor. - #[serde(default = "default::editor_op::backspace")] + #[serde(default = "default::editor_action::backspace")] pub backspace: KeyBinding, /// Delete after cursor. - #[serde(default = "default::editor_op::delete")] + #[serde(default = "default::editor_action::delete")] pub delete: KeyBinding, /// Clear editor contents. - #[serde(default = "default::editor_op::clear")] + #[serde(default = "default::editor_action::clear")] pub clear: KeyBinding, } -impl Default for EditorOp { +impl Default for EditorAction { fn default() -> Self { Self { - newline: default::editor_op::newline(), - backspace: default::editor_op::backspace(), - delete: default::editor_op::delete(), - clear: default::editor_op::clear(), + newline: default::editor_action::newline(), + backspace: default::editor_action::backspace(), + delete: default::editor_action::delete(), + clear: default::editor_action::clear(), } } } @@ -261,7 +261,7 @@ pub struct Editor { #[serde(default)] #[document(no_default)] - pub action: EditorOp, + pub action: EditorAction, } #[derive(Debug, Deserialize, Document, Group)] @@ -308,40 +308,40 @@ impl Default for TreeCursor { } #[derive(Debug, Deserialize, Document, Group)] -pub struct TreeOp { +pub struct TreeAction { /// Reply to message (inline if possible). - #[serde(default = "default::tree_op::reply")] + #[serde(default = "default::tree_action::reply")] pub reply: KeyBinding, /// Reply to message, opposite of normal reply. - #[serde(default = "default::tree_op::reply_alternate")] + #[serde(default = "default::tree_action::reply_alternate")] pub reply_alternate: KeyBinding, /// Start a new thread. - #[serde(default = "default::tree_op::new_thread")] + #[serde(default = "default::tree_action::new_thread")] pub new_thread: KeyBinding, /// Fold current message's subtree. - #[serde(default = "default::tree_op::fold_tree")] + #[serde(default = "default::tree_action::fold_tree")] pub fold_tree: KeyBinding, /// Toggle current message's seen status. - #[serde(default = "default::tree_op::toggle_seen")] + #[serde(default = "default::tree_action::toggle_seen")] pub toggle_seen: KeyBinding, /// Mark all visible messages as seen. - #[serde(default = "default::tree_op::mark_visible_seen")] + #[serde(default = "default::tree_action::mark_visible_seen")] pub mark_visible_seen: KeyBinding, /// Mark all older messages as seen. - #[serde(default = "default::tree_op::mark_older_seen")] + #[serde(default = "default::tree_action::mark_older_seen")] pub mark_older_seen: KeyBinding, } -impl Default for TreeOp { +impl Default for TreeAction { fn default() -> Self { Self { - reply: default::tree_op::reply(), - reply_alternate: default::tree_op::reply_alternate(), - new_thread: default::tree_op::new_thread(), - fold_tree: default::tree_op::fold_tree(), - toggle_seen: default::tree_op::toggle_seen(), - mark_visible_seen: default::tree_op::mark_visible_seen(), - mark_older_seen: default::tree_op::mark_older_seen(), + reply: default::tree_action::reply(), + reply_alternate: default::tree_action::reply_alternate(), + new_thread: default::tree_action::new_thread(), + fold_tree: default::tree_action::fold_tree(), + toggle_seen: default::tree_action::toggle_seen(), + mark_visible_seen: default::tree_action::mark_visible_seen(), + mark_older_seen: default::tree_action::mark_older_seen(), } } } @@ -354,7 +354,7 @@ pub struct Tree { #[serde(default)] #[document(no_default)] - pub action: TreeOp, + pub action: TreeAction, } #[derive(Debug, Default, Deserialize, Document)] diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index 52dc17d..7c0f86d 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -7,7 +7,7 @@ pub use input::*; pub use keys::*; pub trait Group { - type Action; + type Event; - fn action(&self, input: &mut Input) -> Option<Self::Action>; + fn event(&self, input: &mut Input) -> Option<Self::Event>; } diff --git a/cove-macro/src/group.rs b/cove-macro/src/group.rs index 1864774..23100d3 100644 --- a/cove-macro/src/group.rs +++ b/cove-macro/src/group.rs @@ -21,7 +21,7 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { }; let struct_ident = input.ident; - let enum_ident = format_ident!("{}Action", struct_ident); + let enum_ident = format_ident!("{}Event", struct_ident); let mut enum_variants = vec![]; let mut match_cases = vec![]; @@ -38,7 +38,7 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { let description = decapitalize(&docstring); let description = description.strip_suffix('.').unwrap_or(&description); match_cases.push(quote!{ - () if input.matches(&self.#field_ident, #description) => Some(Self::Action::#variant_ident), + () if input.matches(&self.#field_ident, #description) => Some(Self::Event::#variant_ident), }); } } @@ -50,9 +50,9 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { } impl ::cove_input::Group for #struct_ident { - type Action = #enum_ident; + type Event = #enum_ident; - fn action(&self, input: &mut ::cove_input::Input) -> Option<Self::Action> { + fn event(&self, input: &mut ::cove_input::Input) -> Option<Self::Event> { match () { #( #match_cases )* () => None, From fdc46aa3b8def03d680a957395c2a1adb06470c7 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 27 Apr 2023 20:55:26 +0200 Subject: [PATCH 121/266] Rename Group to KeyGroup --- cove-config/src/keys.rs | 16 ++++++++-------- cove-input/src/lib.rs | 5 +++-- cove-macro/src/{group.rs => key_group.rs} | 2 +- cove-macro/src/lib.rs | 6 +++--- 4 files changed, 15 insertions(+), 14 deletions(-) rename cove-macro/src/{group.rs => key_group.rs} (96%) diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index 261c33e..b27a6c8 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -1,4 +1,4 @@ -use cove_input::{Group, KeyBinding}; +use cove_input::{KeyBinding, KeyGroup}; use serde::Deserialize; use crate::doc::Document; @@ -86,7 +86,7 @@ default_bindings! { } -#[derive(Debug, Deserialize, Document, Group)] +#[derive(Debug, Deserialize, Document, KeyGroup)] pub struct General { /// Quit cove. #[serde(default = "default::general::exit")] @@ -117,7 +117,7 @@ impl Default for General { } } -#[derive(Debug, Deserialize, Document, Group)] +#[derive(Debug, Deserialize, Document, KeyGroup)] pub struct Scroll { /// Scroll up one line. #[serde(default = "default::scroll::up_line")] @@ -152,7 +152,7 @@ impl Default for Scroll { } } -#[derive(Debug, Deserialize, Document, Group)] +#[derive(Debug, Deserialize, Document, KeyGroup)] pub struct Cursor { /// Move cursor up. #[serde(default = "default::cursor::up")] @@ -183,7 +183,7 @@ impl Default for Cursor { } } -#[derive(Debug, Deserialize, Document, Group)] +#[derive(Debug, Deserialize, Document, KeyGroup)] pub struct EditorCursor { /// Move cursor left. #[serde(default = "default::editor_cursor::left")] @@ -226,7 +226,7 @@ impl Default for EditorCursor { } } -#[derive(Debug, Deserialize, Document, Group)] +#[derive(Debug, Deserialize, Document, KeyGroup)] pub struct EditorAction { /// Insert newline. #[serde(default = "default::editor_action::newline")] @@ -264,7 +264,7 @@ pub struct Editor { pub action: EditorAction, } -#[derive(Debug, Deserialize, Document, Group)] +#[derive(Debug, Deserialize, Document, KeyGroup)] pub struct TreeCursor { /// Move cursor to above sibling. #[serde(default = "default::tree_cursor::to_above_sibling")] @@ -307,7 +307,7 @@ impl Default for TreeCursor { } } -#[derive(Debug, Deserialize, Document, Group)] +#[derive(Debug, Deserialize, Document, KeyGroup)] pub struct TreeAction { /// Reply to message (inline if possible). #[serde(default = "default::tree_action::reply")] diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index 7c0f86d..4129adf 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -1,12 +1,13 @@ mod input; mod keys; -pub use cove_macro::Group; +pub use cove_macro::KeyGroup; pub use input::*; pub use keys::*; -pub trait Group { +/// A group of related key bindings. +pub trait KeyGroup { type Event; fn event(&self, input: &mut Input) -> Option<Self::Event>; diff --git a/cove-macro/src/group.rs b/cove-macro/src/key_group.rs similarity index 96% rename from cove-macro/src/group.rs rename to cove-macro/src/key_group.rs index 23100d3..83cec49 100644 --- a/cove-macro/src/group.rs +++ b/cove-macro/src/key_group.rs @@ -49,7 +49,7 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { #( #enum_variants )* } - impl ::cove_input::Group for #struct_ident { + impl ::cove_input::KeyGroup for #struct_ident { type Event = #enum_ident; fn event(&self, input: &mut ::cove_input::Input) -> Option<Self::Event> { diff --git a/cove-macro/src/lib.rs b/cove-macro/src/lib.rs index f064a73..82ef61a 100644 --- a/cove-macro/src/lib.rs +++ b/cove-macro/src/lib.rs @@ -12,7 +12,7 @@ use syn::{parse_macro_input, DeriveInput}; mod document; -mod group; +mod key_group; mod util; #[proc_macro_derive(Document, attributes(document))] @@ -24,10 +24,10 @@ pub fn derive_document(input: proc_macro::TokenStream) -> proc_macro::TokenStrea } } -#[proc_macro_derive(Group)] +#[proc_macro_derive(KeyGroup)] pub fn derive_group(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); - match group::derive_impl(input) { + match key_group::derive_impl(input) { Ok(tokens) => tokens.into(), Err(err) => err.into_compile_error().into(), } From 6ce2afbc9fff90aa395beb9179014818f3744ac9 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 27 Apr 2023 21:10:34 +0200 Subject: [PATCH 122/266] Rename Input to InputEvent and add paste events --- cove-input/src/event.rs | 100 ++++++++++++++++++++++++++++++++++++ cove-input/src/input.rs | 65 ----------------------- cove-input/src/lib.rs | 6 +-- cove-macro/src/key_group.rs | 4 +- 4 files changed, 105 insertions(+), 70 deletions(-) create mode 100644 cove-input/src/event.rs delete mode 100644 cove-input/src/input.rs diff --git a/cove-input/src/event.rs b/cove-input/src/event.rs new file mode 100644 index 0000000..0a92b83 --- /dev/null +++ b/cove-input/src/event.rs @@ -0,0 +1,100 @@ +use crossterm::event::KeyEvent; + +use crate::KeyBinding; + +enum Mode { + Record, + Key(KeyEvent), + Paste(String), +} + +impl Mode { + fn from_crossterm_event(event: crossterm::event::Event) -> Option<Self> { + use crossterm::event::Event::*; + match event { + Key(event) => Some(Self::Key(event)), + Paste(string) => Some(Self::Paste(string)), + _ => None, + } + } +} + +pub enum Entry { + Space, + Category(String), + Binding(KeyBinding, String), +} + +pub struct InputEvent { + mode: Mode, + entries: Vec<Entry>, +} + +impl InputEvent { + pub fn new_recording() -> Self { + Self { + mode: Mode::Record, + entries: vec![], + } + } + + pub fn from_crossterm_event(event: crossterm::event::Event) -> Option<Self> { + Some(Self { + mode: Mode::from_crossterm_event(event)?, + entries: vec![], + }) + } + + fn recording(&self) -> bool { + matches!(self.mode, Mode::Record) + } + + pub fn space<S: ToString>(&mut self) { + if self.recording() { + self.entries.push(Entry::Space); + } + } + + pub fn category<S: ToString>(&mut self, name: S) { + if self.recording() { + self.entries.push(Entry::Category(name.to_string())); + } + } + + pub fn key_event(&self) -> Option<KeyEvent> { + if let Mode::Key(event) = &self.mode { + Some(*event) + } else { + None + } + } + + pub fn paste_event(&self) -> Option<&str> { + if let Mode::Paste(string) = &self.mode { + Some(string) + } else { + None + } + } + + pub fn matches_key_binding<S: ToString>( + &mut self, + binding: &KeyBinding, + description: S, + ) -> bool { + if self.recording() { + self.entries + .push(Entry::Binding(binding.clone(), description.to_string())); + } + + if let Some(event) = self.key_event() { + binding.matches(event) + } else { + false + } + } + + pub fn entries(&self) -> &[Entry] { + &self.entries + } +} diff --git a/cove-input/src/input.rs b/cove-input/src/input.rs deleted file mode 100644 index 5e424ce..0000000 --- a/cove-input/src/input.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crossterm::event::KeyEvent; - -use crate::KeyBinding; - -pub enum Entry { - Space, - Category(String), - Binding(KeyBinding, String), -} - -pub struct Input { - event: Option<KeyEvent>, - - record_entries: bool, - entries: Vec<Entry>, -} - -impl Input { - pub fn new_from_event(event: KeyEvent) -> Self { - Self { - event: Some(event), - record_entries: false, - entries: vec![], - } - } - - pub fn new_recording() -> Self { - Self { - event: None, - record_entries: true, - entries: vec![], - } - } - - pub fn space<S: ToString>(&mut self) { - if self.record_entries { - self.entries.push(Entry::Space); - } - } - - pub fn category<S: ToString>(&mut self, name: S) { - if self.record_entries { - self.entries.push(Entry::Category(name.to_string())); - } - } - - pub fn matches<S: ToString>(&mut self, binding: &KeyBinding, description: S) -> bool { - let matches = if let Some(event) = self.event { - binding.matches(event) - } else { - false - }; - - if self.record_entries { - self.entries - .push(Entry::Binding(binding.clone(), description.to_string())); - } - - matches - } - - pub fn entries(&self) -> &[Entry] { - &self.entries - } -} diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index 4129adf..901f48e 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -1,14 +1,14 @@ -mod input; +mod event; mod keys; pub use cove_macro::KeyGroup; -pub use input::*; +pub use event::*; pub use keys::*; /// A group of related key bindings. pub trait KeyGroup { type Event; - fn event(&self, input: &mut Input) -> Option<Self::Event>; + fn match_input_event(&self, event: &mut InputEvent) -> Option<Self::Event>; } diff --git a/cove-macro/src/key_group.rs b/cove-macro/src/key_group.rs index 83cec49..3876956 100644 --- a/cove-macro/src/key_group.rs +++ b/cove-macro/src/key_group.rs @@ -38,7 +38,7 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { let description = decapitalize(&docstring); let description = description.strip_suffix('.').unwrap_or(&description); match_cases.push(quote!{ - () if input.matches(&self.#field_ident, #description) => Some(Self::Event::#variant_ident), + () if event.matches_key_binding(&self.#field_ident, #description) => Some(Self::Event::#variant_ident), }); } } @@ -52,7 +52,7 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { impl ::cove_input::KeyGroup for #struct_ident { type Event = #enum_ident; - fn event(&self, input: &mut ::cove_input::Input) -> Option<Self::Event> { + fn match_input_event(&self, event: &mut ::cove_input::InputEvent) -> Option<Self::Event> { match () { #( #match_cases )* () => None, From 64a7e7f5182761882316f8515f028c202e49d4ad Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 27 Apr 2023 21:37:48 +0200 Subject: [PATCH 123/266] Simplify KeyGroup The trait will only be used for documenting the key bindings in the F1 menu from now on. The InputEvent will be directly match-eable against KeyBinding-s, which should suffice for input event handling. --- cove-input/src/lib.rs | 4 +--- cove-macro/src/key_group.rs | 42 +++++++++++-------------------------- 2 files changed, 13 insertions(+), 33 deletions(-) diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index 901f48e..3221d8c 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -8,7 +8,5 @@ pub use keys::*; /// A group of related key bindings. pub trait KeyGroup { - type Event; - - fn match_input_event(&self, event: &mut InputEvent) -> Option<Self::Event>; + fn bindings(&self) -> Vec<(&KeyBinding, &'static str)>; } diff --git a/cove-macro/src/key_group.rs b/cove-macro/src/key_group.rs index 3876956..757e3f0 100644 --- a/cove-macro/src/key_group.rs +++ b/cove-macro/src/key_group.rs @@ -1,6 +1,5 @@ -use case::CaseExt; use proc_macro2::TokenStream; -use quote::{format_ident, quote}; +use quote::quote; use syn::spanned::Spanned; use syn::{Data, DeriveInput}; @@ -17,46 +16,29 @@ fn decapitalize(s: &str) -> String { pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { let Data::Struct(data) = input.data else { - return util::bail(input.span(), "Must be a struct"); + return util::bail(input.span(), "must be a struct"); }; - let struct_ident = input.ident; - let enum_ident = format_ident!("{}Event", struct_ident); - - let mut enum_variants = vec![]; - let mut match_cases = vec![]; + let mut bindings = vec![]; for field in &data.fields { if let Some(field_ident) = &field.ident { let docstring = util::docstring(field)?; - let variant_ident = format_ident!("{}", field_ident.to_string().to_camel()); - - enum_variants.push(quote! { - #[doc = #docstring] - #variant_ident, - }); - let description = decapitalize(&docstring); let description = description.strip_suffix('.').unwrap_or(&description); - match_cases.push(quote!{ - () if event.matches_key_binding(&self.#field_ident, #description) => Some(Self::Event::#variant_ident), + + bindings.push(quote! { + (&self.#field_ident, #description) }); } } + let ident = input.ident; Ok(quote! { - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub enum #enum_ident { - #( #enum_variants )* - } - - impl ::cove_input::KeyGroup for #struct_ident { - type Event = #enum_ident; - - fn match_input_event(&self, event: &mut ::cove_input::InputEvent) -> Option<Self::Event> { - match () { - #( #match_cases )* - () => None, - } + impl ::cove_input::KeyGroup for #ident { + fn bindings(&self) -> Vec<(&::cove_input::KeyBinding, &'static str)> { + vec![ + #( #bindings, )* + ] } } }) From 36c5831b9b54dfd45e064f1ace6a408fd8526d73 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 27 Apr 2023 21:44:53 +0200 Subject: [PATCH 124/266] Simplify InputEvent Now that I've decided the F1 menu should always show all key bindings, there is no need for the InputEvent to be so complex. --- cove-input/src/event.rs | 100 ---------------------------------------- cove-input/src/lib.rs | 43 +++++++++++++++-- 2 files changed, 40 insertions(+), 103 deletions(-) delete mode 100644 cove-input/src/event.rs diff --git a/cove-input/src/event.rs b/cove-input/src/event.rs deleted file mode 100644 index 0a92b83..0000000 --- a/cove-input/src/event.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crossterm::event::KeyEvent; - -use crate::KeyBinding; - -enum Mode { - Record, - Key(KeyEvent), - Paste(String), -} - -impl Mode { - fn from_crossterm_event(event: crossterm::event::Event) -> Option<Self> { - use crossterm::event::Event::*; - match event { - Key(event) => Some(Self::Key(event)), - Paste(string) => Some(Self::Paste(string)), - _ => None, - } - } -} - -pub enum Entry { - Space, - Category(String), - Binding(KeyBinding, String), -} - -pub struct InputEvent { - mode: Mode, - entries: Vec<Entry>, -} - -impl InputEvent { - pub fn new_recording() -> Self { - Self { - mode: Mode::Record, - entries: vec![], - } - } - - pub fn from_crossterm_event(event: crossterm::event::Event) -> Option<Self> { - Some(Self { - mode: Mode::from_crossterm_event(event)?, - entries: vec![], - }) - } - - fn recording(&self) -> bool { - matches!(self.mode, Mode::Record) - } - - pub fn space<S: ToString>(&mut self) { - if self.recording() { - self.entries.push(Entry::Space); - } - } - - pub fn category<S: ToString>(&mut self, name: S) { - if self.recording() { - self.entries.push(Entry::Category(name.to_string())); - } - } - - pub fn key_event(&self) -> Option<KeyEvent> { - if let Mode::Key(event) = &self.mode { - Some(*event) - } else { - None - } - } - - pub fn paste_event(&self) -> Option<&str> { - if let Mode::Paste(string) = &self.mode { - Some(string) - } else { - None - } - } - - pub fn matches_key_binding<S: ToString>( - &mut self, - binding: &KeyBinding, - description: S, - ) -> bool { - if self.recording() { - self.entries - .push(Entry::Binding(binding.clone(), description.to_string())); - } - - if let Some(event) = self.key_event() { - binding.matches(event) - } else { - false - } - } - - pub fn entries(&self) -> &[Entry] { - &self.entries - } -} diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index 3221d8c..ef0a0aa 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -1,12 +1,49 @@ -mod event; mod keys; pub use cove_macro::KeyGroup; +use crossterm::event::KeyEvent; -pub use event::*; -pub use keys::*; +pub use crate::keys::*; /// A group of related key bindings. pub trait KeyGroup { fn bindings(&self) -> Vec<(&KeyBinding, &'static str)>; } + +#[derive(Debug, Clone)] +pub enum InputEvent { + Key(KeyEvent), + Paste(String), +} + +impl InputEvent { + pub fn from_crossterm_event(event: crossterm::event::Event) -> Option<Self> { + use crossterm::event::Event::*; + match event { + Key(event) => Some(Self::Key(event)), + Paste(string) => Some(Self::Paste(string)), + _ => None, + } + } + + pub fn key_event(&self) -> Option<KeyEvent> { + match self { + Self::Key(event) => Some(*event), + _ => None, + } + } + + pub fn paste_event(&self) -> Option<&str> { + match self { + Self::Paste(string) => Some(string), + _ => None, + } + } + + pub fn matches<S: ToString>(&self, binding: &KeyBinding) -> bool { + match self.key_event() { + Some(event) => binding.matches(event), + None => false, + } + } +} From ff33454b9ad1704cb3900002fe6d602c3f458a63 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 28 Apr 2023 13:55:34 +0200 Subject: [PATCH 125/266] Document key binding format --- cove-config/src/doc.rs | 44 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/cove-config/src/doc.rs b/cove-config/src/doc.rs index 2149b77..ff47443 100644 --- a/cove-config/src/doc.rs +++ b/cove-config/src/doc.rs @@ -7,6 +7,45 @@ use cove_input::KeyBinding; pub use cove_macro::Document; use serde::Serialize; +const MARKDOWN_INTRODUCTION: &str = r#"# Config file format + +Cove's config file uses the [TOML](https://toml.io/) format. + +## Key binding format + +Key bindings are specified as strings or lists of strings. Each string specifies +a main key and zero or more modifier keys. The modifier keys (if any) are listed +first, followed by the main key. They are separated by the `+` character and +**no** whitespace. + +Examples of key bindings: +- `"ctrl+c"` +- `"X"` (not `"shift+x"`) +- `" "` (space bar) +- `["g", "home"]` +- `["K", "ctrl+up"]` +- `["f1", "?"]` +- `"ctrl+alt+f3"` +- `["enter", "any+enter"]` (matches `enter` regardless of modifiers) + +Available modifiers: +- `ctrl` +- `shift` +- `alt` +- `any` (matches as long as at least one modifier is pressed) + +Available main keys: +- Any single character that can be typed +- `enter`, `esc` +- `tab`, `backtab` +- `backspace`, `delete`, `insert` +- `left`, `right`, `up`, `down` +- `home`, `end`, `pageup`, `pagedown` +- `f1`, `f2`, ... + +## Config options +"#; + pub fn toml_value_as_markdown<T: Serialize>(value: &T) -> String { let mut result = String::new(); value @@ -135,11 +174,10 @@ impl Doc { let mut result = String::new(); - result.push_str("# Configuration options\n\n"); - result.push_str("Cove's config file uses the [TOML](https://toml.io/) format.\n"); + result.push_str(MARKDOWN_INTRODUCTION); for entry in entries { - result.push_str(&format!("\n## `{}`\n", entry.path)); + result.push_str(&format!("\n### `{}`\n", entry.path)); let value_info = entry.value_info.as_markdown(); if !value_info.is_empty() { From 51b1953207d67d07901c55213dc7ef4ef701d66e Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 28 Apr 2023 21:27:12 +0200 Subject: [PATCH 126/266] Fix "any" matching no modifiers --- cove-input/src/keys.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cove-input/src/keys.rs b/cove-input/src/keys.rs index ebd4aa3..4725a46 100644 --- a/cove-input/src/keys.rs +++ b/cove-input/src/keys.rs @@ -105,7 +105,7 @@ impl KeyPress { return false; } - if self.any { + if self.any && !event.modifiers.is_empty() { return true; } From 1ce31b6677ab0cf47b5eeaf4adcfddd91ac77598 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 28 Apr 2023 14:33:11 +0200 Subject: [PATCH 127/266] Always show all key bindings in F1 menu --- Cargo.lock | 1 + cove-config/src/keys.rs | 45 ++++++++++---------- cove-input/src/keys.rs | 4 ++ cove/Cargo.toml | 5 ++- cove/src/ui.rs | 48 ++++++++------------- cove/src/ui/key_bindings.rs | 84 +++++++++++++++++++++++++++++++++++++ 6 files changed, 132 insertions(+), 55 deletions(-) create mode 100644 cove/src/ui/key_bindings.rs diff --git a/Cargo.lock b/Cargo.lock index 346690d..9f2999a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,6 +256,7 @@ dependencies = [ "clap", "cookie", "cove-config", + "cove-input", "crossterm", "directories", "edit", diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index b27a6c8..3b1930b 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -154,16 +154,16 @@ impl Default for Scroll { #[derive(Debug, Deserialize, Document, KeyGroup)] pub struct Cursor { - /// Move cursor up. + /// Move up. #[serde(default = "default::cursor::up")] pub up: KeyBinding, - /// Move cursor down. + /// Move down. #[serde(default = "default::cursor::down")] pub down: KeyBinding, - /// Move cursor to top. + /// Move to top. #[serde(default = "default::cursor::to_top")] pub to_top: KeyBinding, - /// Move cursor to bottom. + /// Move to bottom. #[serde(default = "default::cursor::to_bottom")] pub to_bottom: KeyBinding, /// Center cursor. @@ -185,28 +185,28 @@ impl Default for Cursor { #[derive(Debug, Deserialize, Document, KeyGroup)] pub struct EditorCursor { - /// Move cursor left. + /// Move left. #[serde(default = "default::editor_cursor::left")] pub left: KeyBinding, - /// Move cursor right. + /// Move right. #[serde(default = "default::editor_cursor::right")] pub right: KeyBinding, - /// Move cursor left a word. + /// Move left a word. #[serde(default = "default::editor_cursor::left_word")] pub left_word: KeyBinding, - /// Move cursor right a word. + /// Move right a word. #[serde(default = "default::editor_cursor::right_word")] pub right_word: KeyBinding, - /// Move cursor to start of line. + /// Move to start of line. #[serde(default = "default::editor_cursor::start")] pub start: KeyBinding, - /// Move cursor to end of line. + /// Move to end of line. #[serde(default = "default::editor_cursor::end")] pub end: KeyBinding, - /// Move cursor up. + /// Move up. #[serde(default = "default::editor_cursor::up")] pub up: KeyBinding, - /// Move cursor down. + /// Move down. #[serde(default = "default::editor_cursor::down")] pub down: KeyBinding, } @@ -266,30 +266,31 @@ pub struct Editor { #[derive(Debug, Deserialize, Document, KeyGroup)] pub struct TreeCursor { - /// Move cursor to above sibling. + /// Move to above sibling. #[serde(default = "default::tree_cursor::to_above_sibling")] pub to_above_sibling: KeyBinding, - /// Move cursor to below sibling. + /// Move to below sibling. #[serde(default = "default::tree_cursor::to_below_sibling")] pub to_below_sibling: KeyBinding, - /// Move cursor to parent. + /// Move to parent. #[serde(default = "default::tree_cursor::to_parent")] pub to_parent: KeyBinding, - /// Move cursor to root. + /// Move to root. #[serde(default = "default::tree_cursor::to_root")] pub to_root: KeyBinding, - /// Move cursor to previous message. + /// Move to previous message. #[serde(default = "default::tree_cursor::to_prev_message")] pub to_prev_message: KeyBinding, - /// Move cursor to next message. + /// Move to next message. #[serde(default = "default::tree_cursor::to_next_message")] pub to_next_message: KeyBinding, - /// Move cursor to previous unseen message. + /// Move to previous unseen message. #[serde(default = "default::tree_cursor::to_prev_unseen_message")] pub to_prev_unseen_message: KeyBinding, - /// Move cursor to next unseen message. + /// Move to next unseen message. #[serde(default = "default::tree_cursor::to_next_unseen_message")] pub to_next_unseen_message: KeyBinding, + // TODO Bindings inspired by vim's ()/[]/{} bindings? } impl Default for TreeCursor { @@ -309,10 +310,10 @@ impl Default for TreeCursor { #[derive(Debug, Deserialize, Document, KeyGroup)] pub struct TreeAction { - /// Reply to message (inline if possible). + /// Reply to message, inline if possible. #[serde(default = "default::tree_action::reply")] pub reply: KeyBinding, - /// Reply to message, opposite of normal reply. + /// Reply opposite to normal reply. #[serde(default = "default::tree_action::reply_alternate")] pub reply_alternate: KeyBinding, /// Start a new thread. diff --git a/cove-input/src/keys.rs b/cove-input/src/keys.rs index 4725a46..7cba2e8 100644 --- a/cove-input/src/keys.rs +++ b/cove-input/src/keys.rs @@ -177,6 +177,10 @@ impl KeyBinding { Self(vec![]) } + pub fn keys(&self) -> &[KeyPress] { + &self.0 + } + pub fn with_key(self, key: &str) -> Result<Self, ParseKeysError> { self.with_keys([key]) } diff --git a/cove/Cargo.toml b/cove/Cargo.toml index a15d8af..077d62e 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -5,6 +5,7 @@ edition = { workspace = true } [dependencies] cove-config = { path = "../cove-config" } +cove-input = { path = "../cove-input" } crossterm = { workspace = true } thiserror = { workspace = true } @@ -46,8 +47,8 @@ features = ["bot"] git = "https://github.com/Garmelon/toss.git" rev = "f414db40d526295c74cbcae6c3d194088da8f1d9" -# [patch."https://github.com/Garmelon/toss.git"] -# toss = { path = "../toss/" } +[patch."https://github.com/Garmelon/toss.git"] +toss = { path = "../../toss/" } [dependencies.vault] git = "https://github.com/Garmelon/vault.git" diff --git a/cove/src/ui.rs b/cove/src/ui.rs index 1e9d55c..4443fe9 100644 --- a/cove/src/ui.rs +++ b/cove/src/ui.rs @@ -1,6 +1,7 @@ mod chat; mod euph; mod input; +mod key_bindings; mod rooms; mod util; mod widgets; @@ -67,13 +68,16 @@ enum Mode { } pub struct Ui { + config: &'static Config, event_tx: UnboundedSender<UiEvent>, mode: Mode, rooms: Rooms, log_chat: ChatState<LogMsg, Logger>, - key_bindings_list: Option<ListState<Infallible>>, + + key_bindings_visible: bool, + key_bindings_list: ListState<Infallible>, } impl Ui { @@ -106,11 +110,13 @@ impl Ui { // On the other hand, if the crossterm_event_task stops for any reason, // the rest of the UI is also shut down and the client stops. let mut ui = Self { + config, event_tx: event_tx.clone(), mode: Mode::Main, rooms: Rooms::new(config, vault, event_tx.clone()).await, log_chat: ChatState::new(logger), - key_bindings_list: None, + key_bindings_visible: false, + key_bindings_list: ListState::new(), }; tokio::select! { e = ui.run_main(terminal, event_rx, crossterm_lock) => e?, @@ -190,39 +196,19 @@ impl Ui { } async fn widget(&mut self) -> BoxedAsync<'_, UiError> { - let key_bindings_list = if self.key_bindings_list.is_some() { - let mut bindings = KeyBindingsList::new(); - self.list_key_bindings(&mut bindings).await; - Some(bindings) - } else { - None - }; - let widget = match self.mode { Mode::Main => self.rooms.widget().await, Mode::Log => self.log_chat.widget(String::new(), true), }; - if let Some(key_bindings_list) = key_bindings_list { - // We checked whether this was Some earlier. - let list_state = self.key_bindings_list.as_mut().unwrap(); - - key_bindings_list - .widget(list_state) - .desync() - .above(widget) - .boxed_async() + if self.key_bindings_visible { + let popup = key_bindings::widget(&mut self.key_bindings_list, self.config); + popup.desync().above(widget).boxed_async() } else { widget } } - fn show_key_bindings(&mut self) { - if self.key_bindings_list.is_none() { - self.key_bindings_list = Some(ListState::new()) - } - } - async fn list_key_bindings(&self, bindings: &mut KeyBindingsList) { bindings.binding("ctrl+c", "quit cove"); bindings.binding("F1, ?", "show this menu"); @@ -275,11 +261,11 @@ impl Ui { } // Key bindings list overrides any other bindings if visible - if let Some(key_bindings_list) = &mut self.key_bindings_list { + if self.key_bindings_visible { match event { - key!(Esc) | key!(F 1) | key!('?') => self.key_bindings_list = None, - key!('k') | key!(Up) => key_bindings_list.scroll_up(1), - key!('j') | key!(Down) => key_bindings_list.scroll_down(1), + key!(Esc) | key!(F 1) | key!('?') => self.key_bindings_visible = false, + key!('k') | key!(Up) => self.key_bindings_list.scroll_up(1), + key!('j') | key!(Down) => self.key_bindings_list.scroll_down(1), _ => return EventHandleResult::Continue, } return EventHandleResult::Redraw; @@ -287,7 +273,7 @@ impl Ui { match event { key!(F 1) => { - self.key_bindings_list = Some(ListState::new()); + self.key_bindings_visible = true; return EventHandleResult::Redraw; } key!(F 12) => { @@ -321,7 +307,7 @@ impl Ui { // text editor. if !handled { if let key!('?') = event { - self.show_key_bindings(); + self.key_bindings_visible = true; handled = true; } } diff --git a/cove/src/ui/key_bindings.rs b/cove/src/ui/key_bindings.rs new file mode 100644 index 0000000..86d01b1 --- /dev/null +++ b/cove/src/ui/key_bindings.rs @@ -0,0 +1,84 @@ +//! A scrollable popup showing the current key bindings. + +use std::convert::Infallible; + +use cove_config::Config; +use cove_input::{KeyBinding, KeyGroup}; +use crossterm::style::Stylize; +use toss::widgets::{Either2, Join2, Padding, Text}; +use toss::{Style, Styled, Widget, WidgetExt}; + +use super::widgets::{ListBuilder, ListState, Popup}; +use super::UiError; + +type Line = Either2<Text, Join2<Padding<Text>, Text>>; +type Builder = ListBuilder<'static, Infallible, Line>; + +fn render_empty(builder: &mut Builder) { + builder.add_unsel(Text::new("").first2()); +} + +fn render_title(builder: &mut Builder, title: &str) { + let style = Style::new().bold(); + builder.add_unsel(Text::new(Styled::new(title, style)).first2()); +} + +fn render_binding(builder: &mut Builder, binding: &KeyBinding, description: &str) { + let style = Style::new().cyan(); + let mut keys = Styled::default(); + for key in binding.keys() { + if !keys.text().is_empty() { + keys = keys.then_plain(", "); + } + keys = keys.then(key.to_string(), style); + } + + builder.add_unsel( + Join2::horizontal( + Text::new(description) + .with_wrap(false) + .padding() + .with_right(2) + .with_stretch(true) + .segment(), + Text::new(keys).with_wrap(false).segment().with_fixed(true), + ) + .second2(), + ) +} + +fn render_group<G: KeyGroup>(builder: &mut Builder, group: &G) { + for (binding, description) in group.bindings() { + render_binding(builder, binding, description); + } +} + +pub fn widget<'a>( + list: &'a mut ListState<Infallible>, + config: &Config, +) -> impl Widget<UiError> + 'a { + let mut list_builder = ListBuilder::new(); + + render_title(&mut list_builder, "General"); + render_group(&mut list_builder, &config.keys.general); + render_empty(&mut list_builder); + render_title(&mut list_builder, "Scrolling"); + render_group(&mut list_builder, &config.keys.scroll); + render_empty(&mut list_builder); + render_title(&mut list_builder, "Cursor movement"); + render_group(&mut list_builder, &config.keys.cursor); + render_empty(&mut list_builder); + render_title(&mut list_builder, "Editor cursor movement"); + render_group(&mut list_builder, &config.keys.editor.cursor); + render_empty(&mut list_builder); + render_title(&mut list_builder, "Editor actions"); + render_group(&mut list_builder, &config.keys.editor.action); + render_empty(&mut list_builder); + render_title(&mut list_builder, "Tree cursor movement"); + render_group(&mut list_builder, &config.keys.tree.cursor); + render_empty(&mut list_builder); + render_title(&mut list_builder, "Tree actions"); + render_group(&mut list_builder, &config.keys.tree.action); + + Popup::new(list_builder.build(list), "Key bindings") +} From 593443f10ec928587f55844cbe020adeba1ab847 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 28 Apr 2023 14:43:00 +0200 Subject: [PATCH 128/266] Fix F1 menu panicking when opened --- Cargo.lock | 2 +- cove/Cargo.toml | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f2999a..bac6134 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1394,7 +1394,7 @@ dependencies = [ [[package]] name = "toss" version = "0.1.0" -source = "git+https://github.com/Garmelon/toss.git?rev=f414db40d526295c74cbcae6c3d194088da8f1d9#f414db40d526295c74cbcae6c3d194088da8f1d9" +source = "git+https://github.com/Garmelon/toss.git?rev=8bfb4b2dc345c3e0ffdb89bdb34f2996487a35cb#8bfb4b2dc345c3e0ffdb89bdb34f2996487a35cb" dependencies = [ "async-trait", "crossterm", diff --git a/cove/Cargo.toml b/cove/Cargo.toml index 077d62e..6db9df3 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -40,20 +40,11 @@ git = "https://github.com/Garmelon/euphoxide.git" rev = "0f217a6279181b0731216760219e8ff0fa01e449" features = ["bot"] -# [patch."https://github.com/Garmelon/euphoxide.git"] -# euphoxide = { path = "../euphoxide/" } - [dependencies.toss] git = "https://github.com/Garmelon/toss.git" -rev = "f414db40d526295c74cbcae6c3d194088da8f1d9" - -[patch."https://github.com/Garmelon/toss.git"] -toss = { path = "../../toss/" } +rev = "8bfb4b2dc345c3e0ffdb89bdb34f2996487a35cb" [dependencies.vault] git = "https://github.com/Garmelon/vault.git" rev = "b4cf23b7279770226725c895e482c8eda88c43a7" features = ["tokio"] - -# [patch."https://github.com/Garmelon/vault.git"] -# vault = { path = "../vault/" } From 6b05a2a06db57bf336d876b82841bc119133e550 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 28 Apr 2023 15:18:11 +0200 Subject: [PATCH 129/266] Fix modifiers being capitalized --- cove-input/src/keys.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cove-input/src/keys.rs b/cove-input/src/keys.rs index 7cba2e8..07eff8d 100644 --- a/cove-input/src/keys.rs +++ b/cove-input/src/keys.rs @@ -138,16 +138,16 @@ impl fmt::Display for KeyPress { let mut segments = vec![]; if self.shift { - segments.push("Shift"); + segments.push("shift"); } if self.ctrl { - segments.push("Ctrl"); + segments.push("ctrl"); } if self.alt { - segments.push("Alt"); + segments.push("alt"); } if self.any { - segments.push("Any"); + segments.push("any"); } segments.push(&code); From e5960b8eda23b59a75540d9060176f0185508ba8 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 28 Apr 2023 15:51:19 +0200 Subject: [PATCH 130/266] Include more things in InputEvent --- Cargo.lock | 2 ++ Cargo.toml | 5 +++++ cove-input/Cargo.toml | 2 ++ cove-input/src/lib.rs | 41 ++++++++++++++++++++++++----------------- cove/Cargo.toml | 7 ++----- 5 files changed, 35 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bac6134..71de4e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -294,9 +294,11 @@ version = "0.6.1" dependencies = [ "cove-macro", "crossterm", + "parking_lot", "serde", "serde_either", "thiserror", + "toss", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 33e3c2c..df60d72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,14 @@ edition = "2021" [workspace.dependencies] crossterm = "0.26.1" +parking_lot = "0.12.1" serde = { version = "1.0.159", features = ["derive"] } serde_either = "0.2.1" thiserror = "1.0.40" +[workspace.dependencies.toss] +git = "https://github.com/Garmelon/toss.git" +rev = "8bfb4b2dc345c3e0ffdb89bdb34f2996487a35cb" + [profile.dev.package."*"] opt-level = 3 diff --git a/cove-input/Cargo.toml b/cove-input/Cargo.toml index 61e178f..63567d6 100644 --- a/cove-input/Cargo.toml +++ b/cove-input/Cargo.toml @@ -7,6 +7,8 @@ edition = { workspace = true } cove-macro = { path = "../cove-macro" } crossterm = { workspace = true } +parking_lot = {workspace = true} serde = { workspace = true } serde_either = { workspace = true } thiserror = { workspace = true } +toss = {workspace = true} diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index ef0a0aa..62063e6 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -1,7 +1,11 @@ mod keys; +use std::sync::Arc; + pub use cove_macro::KeyGroup; -use crossterm::event::KeyEvent; +use crossterm::event::{Event, KeyEvent}; +use parking_lot::FairMutex; +use toss::Terminal; pub use crate::keys::*; @@ -10,37 +14,40 @@ pub trait KeyGroup { fn bindings(&self) -> Vec<(&KeyBinding, &'static str)>; } -#[derive(Debug, Clone)] -pub enum InputEvent { - Key(KeyEvent), - Paste(String), +pub struct InputEvent<'a> { + event: crossterm::event::Event, + terminal: &'a mut Terminal, + crossterm_lock: Arc<FairMutex<()>>, } -impl InputEvent { - pub fn from_crossterm_event(event: crossterm::event::Event) -> Option<Self> { - use crossterm::event::Event::*; - match event { - Key(event) => Some(Self::Key(event)), - Paste(string) => Some(Self::Paste(string)), - _ => None, +impl<'a> InputEvent<'a> { + pub fn new( + event: Event, + terminal: &'a mut Terminal, + crossterm_lock: Arc<FairMutex<()>>, + ) -> Self { + Self { + event, + terminal, + crossterm_lock, } } pub fn key_event(&self) -> Option<KeyEvent> { - match self { - Self::Key(event) => Some(*event), + match &self.event { + Event::Key(event) => Some(*event), _ => None, } } pub fn paste_event(&self) -> Option<&str> { - match self { - Self::Paste(string) => Some(string), + match &self.event { + Event::Paste(string) => Some(string), _ => None, } } - pub fn matches<S: ToString>(&self, binding: &KeyBinding) -> bool { + pub fn matches(&self, binding: &KeyBinding) -> bool { match self.key_event() { Some(event) => binding.matches(event), None => false, diff --git a/cove/Cargo.toml b/cove/Cargo.toml index 6db9df3..2cc76d3 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -8,7 +8,9 @@ cove-config = { path = "../cove-config" } cove-input = { path = "../cove-input" } crossterm = { workspace = true } +parking_lot = { workspace = true } thiserror = { workspace = true } +toss = { workspace = true } anyhow = "1.0.70" async-trait = "0.1.68" @@ -20,7 +22,6 @@ linkify = "0.9.0" log = { version = "0.4.17", features = ["std"] } once_cell = "1.17.1" open = "4.0.1" -parking_lot = "0.12.1" rusqlite = { version = "0.29.0", features = ["bundled", "time"] } serde_json = "1.0.95" tokio = { version = "1.27.0", features = ["full"] } @@ -40,10 +41,6 @@ git = "https://github.com/Garmelon/euphoxide.git" rev = "0f217a6279181b0731216760219e8ff0fa01e449" features = ["bot"] -[dependencies.toss] -git = "https://github.com/Garmelon/toss.git" -rev = "8bfb4b2dc345c3e0ffdb89bdb34f2996487a35cb" - [dependencies.vault] git = "https://github.com/Garmelon/vault.git" rev = "b4cf23b7279770226725c895e482c8eda88c43a7" From c0a01b7ad44b9842d6f04cd4220b2a5016a1125c Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 28 Apr 2023 15:51:38 +0200 Subject: [PATCH 131/266] Move cursor centering bind to scroll group --- cove-config/src/keys.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index 3b1930b..64e112a 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -35,6 +35,7 @@ default_bindings! { pub fn down_half => ["ctrl+d"]; pub fn up_full => ["ctrl+b"]; pub fn down_full => ["ctrl+f"]; + pub fn center_cursor => ["z"]; } pub mod cursor { @@ -42,7 +43,6 @@ default_bindings! { pub fn down => ["j", "down"]; pub fn to_top => ["g", "home"]; pub fn to_bottom => ["G", "end"]; - pub fn center => ["z"]; } pub mod tree_cursor { @@ -137,6 +137,9 @@ pub struct Scroll { /// Scroll down a full screen. #[serde(default = "default::scroll::down_full")] pub down_full: KeyBinding, + /// Center cursor. + #[serde(default = "default::scroll::center_cursor")] + pub center_cursor: KeyBinding, } impl Default for Scroll { @@ -148,6 +151,7 @@ impl Default for Scroll { down_half: default::scroll::down_half(), up_full: default::scroll::up_full(), down_full: default::scroll::down_full(), + center_cursor: default::scroll::center_cursor(), } } } @@ -166,9 +170,6 @@ pub struct Cursor { /// Move to bottom. #[serde(default = "default::cursor::to_bottom")] pub to_bottom: KeyBinding, - /// Center cursor. - #[serde(default = "default::cursor::center")] - pub center: KeyBinding, } impl Default for Cursor { @@ -178,7 +179,6 @@ impl Default for Cursor { down: default::cursor::down(), to_top: default::cursor::to_top(), to_bottom: default::cursor::to_bottom(), - center: default::cursor::center(), } } } From 202969c7a9ed0fbd17d5c44d51cf5687b88a0cc4 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 28 Apr 2023 22:32:59 +0200 Subject: [PATCH 132/266] Add and update key bindings --- cove-config/src/keys.rs | 227 +++++++++++++++++++++++++++++------- cove-macro/src/lib.rs | 1 + cove/src/ui/key_bindings.rs | 3 + 3 files changed, 186 insertions(+), 45 deletions(-) diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index 64e112a..0eed8a7 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -24,7 +24,8 @@ default_bindings! { pub fn exit => ["ctrl+c"]; pub fn abort => ["esc"]; pub fn confirm => ["enter"]; - pub fn help => ["f1", "?"]; + pub fn focus => ["tab"]; + pub fn help => ["f1"]; pub fn log => ["f12"]; } @@ -33,8 +34,8 @@ default_bindings! { pub fn down_line => ["ctrl+e"]; pub fn up_half => ["ctrl+u"]; pub fn down_half => ["ctrl+d"]; - pub fn up_full => ["ctrl+b"]; - pub fn down_full => ["ctrl+f"]; + pub fn up_full => ["ctrl+b", "pageup"]; + pub fn down_full => ["ctrl+f", "pagedown"]; pub fn center_cursor => ["z"]; } @@ -45,15 +46,53 @@ default_bindings! { pub fn to_bottom => ["G", "end"]; } + pub mod editor_cursor { + pub fn left => ["ctrl+b","left"]; + pub fn right => ["ctrl+f", "right"]; + pub fn left_word => ["alt+b", "ctrl+left"]; + pub fn right_word => ["alt+f", "ctrl+right"]; + pub fn start => ["ctrl+a", "home"]; + pub fn end => ["ctrl+e", "end"]; + pub fn up => ["up"]; + pub fn down => ["down"]; + } + + pub mod editor_action { + pub fn backspace => ["ctrl+h", "backspace"]; + pub fn delete => ["ctrl+d", "delete"]; + pub fn clear => ["ctrl+l"]; + pub fn external => ["ctrl+x", "alt+e"]; + } + + pub mod rooms_action { + pub fn connect => ["c"]; + pub fn connect_all => ["C"]; + pub fn disconnect => ["d"]; + pub fn disconnect_all => ["D"]; + pub fn connect_autojoin => ["a"]; + pub fn disconnect_non_autojoin => ["A"]; + pub fn new => ["n"]; + pub fn delete => ["X"]; + pub fn change_sort_order => ["s"]; + } + + pub mod room_action { + pub fn authenticate => ["a"]; + pub fn nick => ["n"]; + pub fn more_messages => ["m"]; + pub fn account => ["A"]; + pub fn present => ["ctrl+p"]; + } + pub mod tree_cursor { pub fn to_above_sibling => ["K", "ctrl+up"]; pub fn to_below_sibling => ["J", "ctrl+down"]; pub fn to_parent => ["p"]; pub fn to_root => ["P"]; - pub fn to_prev_message => ["h", "left"]; - pub fn to_next_message => ["l", "right"]; - pub fn to_prev_unseen_message => ["H", "ctrl+left"]; - pub fn to_next_unseen_message => ["L", "ctrl+right"]; + pub fn to_older_message => ["h", "left"]; + pub fn to_newer_message => ["l", "right"]; + pub fn to_older_unseen_message => ["H", "ctrl+left"]; + pub fn to_newer_unseen_message => ["L", "ctrl+right"]; } pub mod tree_action { @@ -64,24 +103,8 @@ default_bindings! { pub fn toggle_seen => ["s"]; pub fn mark_visible_seen => ["S"]; pub fn mark_older_seen => ["ctrl+s"]; - } - - pub mod editor_cursor { - pub fn left => []; - pub fn right => []; - pub fn left_word => []; - pub fn right_word => []; - pub fn start => []; - pub fn end => []; - pub fn up => []; - pub fn down => []; - } - - pub mod editor_action { - pub fn newline => []; - pub fn backspace => []; - pub fn delete => []; - pub fn clear => []; + pub fn info => ["i"]; + pub fn links => ["I"]; } } @@ -97,6 +120,10 @@ pub struct General { /// Confirm. #[serde(default = "default::general::confirm")] pub confirm: KeyBinding, + /// Advance focus. + // TODO Mention examples where this is used + #[serde(default = "default::general::focus")] + pub focus: KeyBinding, /// Show this help. #[serde(default = "default::general::help")] pub help: KeyBinding, @@ -111,6 +138,7 @@ impl Default for General { exit: default::general::exit(), abort: default::general::abort(), confirm: default::general::confirm(), + focus: default::general::focus(), help: default::general::help(), log: default::general::log(), } @@ -228,9 +256,6 @@ impl Default for EditorCursor { #[derive(Debug, Deserialize, Document, KeyGroup)] pub struct EditorAction { - /// Insert newline. - #[serde(default = "default::editor_action::newline")] - pub newline: KeyBinding, /// Delete before cursor. #[serde(default = "default::editor_action::backspace")] pub backspace: KeyBinding, @@ -240,15 +265,18 @@ pub struct EditorAction { /// Clear editor contents. #[serde(default = "default::editor_action::clear")] pub clear: KeyBinding, + /// Edit in external editor. + #[serde(default = "default::editor_action::external")] + pub external: KeyBinding, } impl Default for EditorAction { fn default() -> Self { Self { - newline: default::editor_action::newline(), backspace: default::editor_action::backspace(), delete: default::editor_action::delete(), clear: default::editor_action::clear(), + external: default::editor_action::external(), } } } @@ -264,6 +292,98 @@ pub struct Editor { pub action: EditorAction, } +#[derive(Debug, Deserialize, Document, KeyGroup)] +pub struct RoomsAction { + /// Connect to selected room. + #[serde(default = "default::rooms_action::connect")] + pub connect: KeyBinding, + /// Connect to all rooms. + #[serde(default = "default::rooms_action::connect_all")] + pub connect_all: KeyBinding, + /// Disconnect from selected room. + #[serde(default = "default::rooms_action::disconnect")] + pub disconnect: KeyBinding, + /// Disconnect from all rooms. + #[serde(default = "default::rooms_action::disconnect_all")] + pub disconnect_all: KeyBinding, + /// Connect to all autojoin rooms. + #[serde(default = "default::rooms_action::connect_autojoin")] + pub connect_autojoin: KeyBinding, + /// Disconnect from all non-autojoin rooms. + #[serde(default = "default::rooms_action::disconnect_non_autojoin")] + pub disconnect_non_autojoin: KeyBinding, + /// Connect to new room. + #[serde(default = "default::rooms_action::new")] + pub new: KeyBinding, + /// Delete room. + #[serde(default = "default::rooms_action::delete")] + pub delete: KeyBinding, + /// Change sort order. + #[serde(default = "default::rooms_action::change_sort_order")] + pub change_sort_order: KeyBinding, +} + +impl Default for RoomsAction { + fn default() -> Self { + Self { + connect: default::rooms_action::connect(), + connect_all: default::rooms_action::connect_all(), + disconnect: default::rooms_action::disconnect(), + disconnect_all: default::rooms_action::disconnect_all(), + connect_autojoin: default::rooms_action::connect_autojoin(), + disconnect_non_autojoin: default::rooms_action::disconnect_non_autojoin(), + new: default::rooms_action::new(), + delete: default::rooms_action::delete(), + change_sort_order: default::rooms_action::change_sort_order(), + } + } +} + +#[derive(Debug, Default, Deserialize, Document)] +pub struct Rooms { + #[serde(default)] + #[document(no_default)] + pub action: RoomsAction, +} + +#[derive(Debug, Deserialize, Document, KeyGroup)] +pub struct RoomAction { + /// Authenticate. + #[serde(default = "default::room_action::authenticate")] + pub authenticate: KeyBinding, + /// Change nick. + #[serde(default = "default::room_action::nick")] + pub nick: KeyBinding, + /// Download more messages. + #[serde(default = "default::room_action::more_messages")] + pub more_messages: KeyBinding, + /// Manage account. + #[serde(default = "default::room_action::account")] + pub account: KeyBinding, + /// Open room's plugh.de/present page. + #[serde(default = "default::room_action::present")] + pub present: KeyBinding, +} + +impl Default for RoomAction { + fn default() -> Self { + Self { + authenticate: default::room_action::authenticate(), + account: default::room_action::account(), + nick: default::room_action::nick(), + more_messages: default::room_action::more_messages(), + present: default::room_action::present(), + } + } +} + +#[derive(Debug, Default, Deserialize, Document)] +pub struct Room { + #[serde(default)] + #[document(no_default)] + pub action: RoomAction, +} + #[derive(Debug, Deserialize, Document, KeyGroup)] pub struct TreeCursor { /// Move to above sibling. @@ -278,18 +398,18 @@ pub struct TreeCursor { /// Move to root. #[serde(default = "default::tree_cursor::to_root")] pub to_root: KeyBinding, - /// Move to previous message. - #[serde(default = "default::tree_cursor::to_prev_message")] - pub to_prev_message: KeyBinding, - /// Move to next message. - #[serde(default = "default::tree_cursor::to_next_message")] - pub to_next_message: KeyBinding, - /// Move to previous unseen message. - #[serde(default = "default::tree_cursor::to_prev_unseen_message")] - pub to_prev_unseen_message: KeyBinding, - /// Move to next unseen message. - #[serde(default = "default::tree_cursor::to_next_unseen_message")] - pub to_next_unseen_message: KeyBinding, + /// Move to older message. + #[serde(default = "default::tree_cursor::to_older_message")] + pub to_older_message: KeyBinding, + /// Move to newer message. + #[serde(default = "default::tree_cursor::to_newer_message")] + pub to_newer_message: KeyBinding, + /// Move to older unseen message. + #[serde(default = "default::tree_cursor::to_older_unseen_message")] + pub to_older_unseen_message: KeyBinding, + /// Move to newer unseen message. + #[serde(default = "default::tree_cursor::to_newer_unseen_message")] + pub to_newer_unseen_message: KeyBinding, // TODO Bindings inspired by vim's ()/[]/{} bindings? } @@ -300,14 +420,15 @@ impl Default for TreeCursor { to_below_sibling: default::tree_cursor::to_below_sibling(), to_parent: default::tree_cursor::to_parent(), to_root: default::tree_cursor::to_root(), - to_prev_message: default::tree_cursor::to_prev_message(), - to_next_message: default::tree_cursor::to_next_message(), - to_prev_unseen_message: default::tree_cursor::to_prev_unseen_message(), - to_next_unseen_message: default::tree_cursor::to_next_unseen_message(), + to_older_message: default::tree_cursor::to_older_message(), + to_newer_message: default::tree_cursor::to_newer_message(), + to_older_unseen_message: default::tree_cursor::to_older_unseen_message(), + to_newer_unseen_message: default::tree_cursor::to_newer_unseen_message(), } } } +// TODO Split up in "message", "nicklist", "room"? #[derive(Debug, Deserialize, Document, KeyGroup)] pub struct TreeAction { /// Reply to message, inline if possible. @@ -331,6 +452,12 @@ pub struct TreeAction { /// Mark all older messages as seen. #[serde(default = "default::tree_action::mark_older_seen")] pub mark_older_seen: KeyBinding, + /// Inspect selected element. + #[serde(default = "default::tree_action::info")] + pub inspect: KeyBinding, + /// List links found in message. + #[serde(default = "default::tree_action::links")] + pub links: KeyBinding, } impl Default for TreeAction { @@ -343,6 +470,8 @@ impl Default for TreeAction { toggle_seen: default::tree_action::toggle_seen(), mark_visible_seen: default::tree_action::mark_visible_seen(), mark_older_seen: default::tree_action::mark_older_seen(), + inspect: default::tree_action::info(), + links: default::tree_action::links(), } } } @@ -376,6 +505,14 @@ pub struct Keys { #[document(no_default)] pub editor: Editor, + #[serde(default)] + #[document(no_default)] + pub rooms: Rooms, + + #[serde(default)] + #[document(no_default)] + pub room: Room, + #[serde(default)] #[document(no_default)] pub tree: Tree, diff --git a/cove-macro/src/lib.rs b/cove-macro/src/lib.rs index 82ef61a..9ce748d 100644 --- a/cove-macro/src/lib.rs +++ b/cove-macro/src/lib.rs @@ -24,6 +24,7 @@ pub fn derive_document(input: proc_macro::TokenStream) -> proc_macro::TokenStrea } } +// TODO Derive Default as well #[proc_macro_derive(KeyGroup)] pub fn derive_group(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); diff --git a/cove/src/ui/key_bindings.rs b/cove/src/ui/key_bindings.rs index 86d01b1..c2a1249 100644 --- a/cove/src/ui/key_bindings.rs +++ b/cove/src/ui/key_bindings.rs @@ -74,6 +74,9 @@ pub fn widget<'a>( render_title(&mut list_builder, "Editor actions"); render_group(&mut list_builder, &config.keys.editor.action); render_empty(&mut list_builder); + render_title(&mut list_builder, "Room list actions"); + render_group(&mut list_builder, &config.keys.rooms.action); + render_empty(&mut list_builder); render_title(&mut list_builder, "Tree cursor movement"); render_group(&mut list_builder, &config.keys.tree.cursor); render_empty(&mut list_builder); From 9bc6931fae84769c2fba35ec06b48d3dfea0d8c2 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 00:24:33 +0200 Subject: [PATCH 133/266] Migrate input handling to new bindings --- Cargo.lock | 2 +- cove-input/Cargo.toml | 6 +- cove-input/src/lib.rs | 21 +- cove/Cargo.toml | 1 - cove/src/ui.rs | 96 +++----- cove/src/ui/chat.rs | 26 +-- cove/src/ui/chat/tree.rs | 441 +++++++++++++++++------------------- cove/src/ui/euph/account.rs | 77 +++---- cove/src/ui/euph/auth.rs | 55 ++--- cove/src/ui/euph/inspect.rs | 23 +- cove/src/ui/euph/links.rs | 92 ++++---- cove/src/ui/euph/nick.rs | 59 ++--- cove/src/ui/euph/popup.rs | 9 + cove/src/ui/euph/room.rs | 324 +++++++------------------- cove/src/ui/input.rs | 175 -------------- cove/src/ui/rooms.rs | 231 +++++++------------ cove/src/ui/util.rs | 316 +++++++++++++------------- cove/src/ui/widgets/list.rs | 16 ++ 18 files changed, 748 insertions(+), 1222 deletions(-) delete mode 100644 cove/src/ui/input.rs diff --git a/Cargo.lock b/Cargo.lock index 71de4e0..c59459d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,7 +259,6 @@ dependencies = [ "cove-input", "crossterm", "directories", - "edit", "euphoxide", "linkify", "log", @@ -294,6 +293,7 @@ version = "0.6.1" dependencies = [ "cove-macro", "crossterm", + "edit", "parking_lot", "serde", "serde_either", diff --git a/cove-input/Cargo.toml b/cove-input/Cargo.toml index 63567d6..f3dcc64 100644 --- a/cove-input/Cargo.toml +++ b/cove-input/Cargo.toml @@ -7,8 +7,10 @@ edition = { workspace = true } cove-macro = { path = "../cove-macro" } crossterm = { workspace = true } -parking_lot = {workspace = true} +parking_lot = { workspace = true } serde = { workspace = true } serde_either = { workspace = true } thiserror = { workspace = true } -toss = {workspace = true} +toss = { workspace = true } + +edit = "0.1.4" diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index 62063e6..fe578fd 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -1,11 +1,12 @@ mod keys; +use std::io; use std::sync::Arc; pub use cove_macro::KeyGroup; use crossterm::event::{Event, KeyEvent}; use parking_lot::FairMutex; -use toss::Terminal; +use toss::{Frame, Terminal, WidthDb}; pub use crate::keys::*; @@ -53,4 +54,22 @@ impl<'a> InputEvent<'a> { None => false, } } + + pub fn frame(&mut self) -> &mut Frame { + self.terminal.frame() + } + + pub fn widthdb(&mut self) -> &mut WidthDb { + self.terminal.widthdb() + } + + pub fn prompt(&mut self, initial_text: &str) -> io::Result<String> { + let guard = self.crossterm_lock.lock(); + self.terminal.suspend().expect("failed to suspend"); + let content = edit::edit(initial_text); + self.terminal.unsuspend().expect("fauled to unsuspend"); + drop(guard); + + content + } } diff --git a/cove/Cargo.toml b/cove/Cargo.toml index 2cc76d3..0d1a1f9 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -17,7 +17,6 @@ async-trait = "0.1.68" clap = { version = "4.2.1", features = ["derive", "deprecated"] } cookie = "0.17.0" directories = "5.0.0" -edit = "0.1.4" linkify = "0.9.0" log = { version = "0.4.17", features = ["std"] } once_cell = "1.17.1" diff --git a/cove/src/ui.rs b/cove/src/ui.rs index 4443fe9..7ac483e 100644 --- a/cove/src/ui.rs +++ b/cove/src/ui.rs @@ -1,6 +1,5 @@ mod chat; mod euph; -mod input; mod key_bindings; mod rooms; mod util; @@ -12,6 +11,7 @@ use std::sync::{Arc, Weak}; use std::time::{Duration, Instant}; use cove_config::Config; +use cove_input::InputEvent; use parking_lot::FairMutex; use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; @@ -26,7 +26,6 @@ use crate::vault::Vault; pub use self::chat::ChatMsg; use self::chat::ChatState; -use self::input::{key, InputEvent, KeyBindingsList}; use self::rooms::Rooms; use self::widgets::ListState; @@ -209,17 +208,6 @@ impl Ui { } } - async fn list_key_bindings(&self, bindings: &mut KeyBindingsList) { - bindings.binding("ctrl+c", "quit cove"); - bindings.binding("F1, ?", "show this menu"); - bindings.binding("F12", "toggle log"); - bindings.empty(); - match self.mode { - Mode::Main => self.rooms.list_key_bindings(bindings).await, - Mode::Log => self.log_chat.list_key_bindings(bindings, false).await, - } - } - async fn handle_event( &mut self, terminal: &mut Terminal, @@ -232,7 +220,7 @@ impl Ui { UiEvent::LogChanged => EventHandleResult::Continue, UiEvent::Term(crossterm::event::Event::Resize(_, _)) => EventHandleResult::Redraw, UiEvent::Term(event) => { - self.handle_term_event(terminal, crossterm_lock, event) + self.handle_term_event(terminal, crossterm_lock.clone(), event) .await } UiEvent::Euph(event) => { @@ -248,74 +236,60 @@ impl Ui { async fn handle_term_event( &mut self, terminal: &mut Terminal, - crossterm_lock: &Arc<FairMutex<()>>, + crossterm_lock: Arc<FairMutex<()>>, event: crossterm::event::Event, ) -> EventHandleResult { - let event = some_or_return!(InputEvent::from_event(event), EventHandleResult::Continue); + let mut event = InputEvent::new(event, terminal, crossterm_lock); + let keys = &self.config.keys; - if let key!(Ctrl + 'c') = event { - // Exit unconditionally on ctrl+c. Previously, shift+q would also - // unconditionally exit, but that interfered with typing text in - // inline editors. + if event.matches(&keys.general.exit) { return EventHandleResult::Stop; } // Key bindings list overrides any other bindings if visible if self.key_bindings_visible { - match event { - key!(Esc) | key!(F 1) | key!('?') => self.key_bindings_visible = false, - key!('k') | key!(Up) => self.key_bindings_list.scroll_up(1), - key!('j') | key!(Down) => self.key_bindings_list.scroll_down(1), - _ => return EventHandleResult::Continue, + if event.matches(&keys.general.abort) { + self.key_bindings_visible = false; + return EventHandleResult::Redraw; } + if util::handle_list_input_event(&mut self.key_bindings_list, &event, keys) { + return EventHandleResult::Redraw; + } + // ... and does not let anything below the popup receive events + return EventHandleResult::Continue; + } + + // Other general bindings that override any other bindings + if event.matches(&keys.general.help) { + self.key_bindings_visible = true; + return EventHandleResult::Redraw; + } + if event.matches(&keys.general.log) { + self.mode = match self.mode { + Mode::Main => Mode::Log, + Mode::Log => Mode::Main, + }; return EventHandleResult::Redraw; } - match event { - key!(F 1) => { - self.key_bindings_visible = true; - return EventHandleResult::Redraw; - } - key!(F 12) => { - self.mode = match self.mode { - Mode::Main => Mode::Log, - Mode::Log => Mode::Main, - }; - return EventHandleResult::Redraw; - } - _ => {} - } - - let mut handled = match self.mode { + match self.mode { Mode::Main => { - self.rooms - .handle_input_event(terminal, crossterm_lock, &event) - .await + if self.rooms.handle_input_event(&mut event, keys).await { + return EventHandleResult::Redraw; + } } Mode::Log => { let reaction = self .log_chat - .handle_input_event(terminal, crossterm_lock, &event, false) + .handle_input_event(&mut event, keys, false) .await; let reaction = logging_unwrap!(reaction); - reaction.handled() - } - }; - - // Pressing '?' should only open the key bindings list if it doesn't - // interfere with any part of the main UI, such as entering text in a - // text editor. - if !handled { - if let key!('?') = event { - self.key_bindings_visible = true; - handled = true; + if reaction.handled() { + return EventHandleResult::Redraw; + } } } - if handled { - EventHandleResult::Redraw - } else { - EventHandleResult::Continue - } + EventHandleResult::Continue } } diff --git a/cove/src/ui/chat.rs b/cove/src/ui/chat.rs index a8acdea..24ea82e 100644 --- a/cove/src/ui/chat.rs +++ b/cove/src/ui/chat.rs @@ -4,20 +4,17 @@ mod renderer; mod tree; mod widgets; -use std::io; -use std::sync::Arc; - -use parking_lot::FairMutex; +use cove_config::Keys; +use cove_input::InputEvent; use time::OffsetDateTime; use toss::widgets::{BoxedAsync, EditorState}; -use toss::{Styled, Terminal, WidgetExt}; +use toss::{Styled, WidgetExt}; use crate::store::{Msg, MsgStore}; use self::cursor::Cursor; use self::tree::TreeViewState; -use super::input::{InputEvent, KeyBindingsList}; use super::UiError; pub trait ChatMsg { @@ -76,19 +73,10 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> { } } - pub async fn list_key_bindings(&self, bindings: &mut KeyBindingsList, can_compose: bool) { - match self.mode { - Mode::Tree => self - .tree - .list_key_bindings(bindings, &self.cursor, can_compose), - } - } - pub async fn handle_input_event( &mut self, - terminal: &mut Terminal, - crossterm_lock: &Arc<FairMutex<()>>, - event: &InputEvent, + event: &mut InputEvent<'_>, + keys: &Keys, can_compose: bool, ) -> Result<Reaction<M>, S::Error> where @@ -101,9 +89,8 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> { Mode::Tree => { self.tree .handle_input_event( - terminal, - crossterm_lock, event, + keys, &mut self.cursor, &mut self.editor, can_compose, @@ -147,7 +134,6 @@ pub enum Reaction<M: Msg> { parent: Option<M::Id>, content: String, }, - ComposeError(io::Error), } impl<M: Msg> Reaction<M> { diff --git a/cove/src/ui/chat/tree.rs b/cove/src/ui/chat/tree.rs index ad7e51d..297429b 100644 --- a/cove/src/ui/chat/tree.rs +++ b/cove/src/ui/chat/tree.rs @@ -7,15 +7,14 @@ mod scroll; mod widgets; use std::collections::HashSet; -use std::sync::Arc; use async_trait::async_trait; -use parking_lot::FairMutex; +use cove_config::Keys; +use cove_input::InputEvent; use toss::widgets::EditorState; -use toss::{AsyncWidget, Frame, Pos, Size, Terminal, WidgetExt, WidthDb}; +use toss::{AsyncWidget, Frame, Pos, Size, WidgetExt, WidthDb}; use crate::store::{Msg, MsgStore}; -use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::{util, ChatMsg, UiError}; use crate::util::InfallibleExt; @@ -49,25 +48,10 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> { } } - pub fn list_movement_key_bindings(&self, bindings: &mut KeyBindingsList) { - bindings.binding("j/k, ↓/↑", "move cursor up/down"); - bindings.binding("J/K, ctrl+↓/↑", "move cursor to prev/next sibling"); - bindings.binding("p/P", "move cursor to parent/root"); - bindings.binding("h/l, ←/→", "move cursor chronologically"); - bindings.binding("H/L, ctrl+←/→", "move cursor to prev/next unseen message"); - bindings.binding("g, home", "move cursor to top"); - bindings.binding("G, end", "move cursor to bottom"); - bindings.binding("ctrl+y/e", "scroll up/down a line"); - bindings.binding("ctrl+u/d", "scroll up/down half a screen"); - bindings.binding("ctrl+b/f, page up/down", "scroll up/down one screen"); - bindings.binding("z", "center cursor on screen"); - // TODO Bindings inspired by vim's ()/[]/{} bindings? - } - async fn handle_movement_input_event( &mut self, - frame: &mut Frame, - event: &InputEvent, + event: &mut InputEvent<'_>, + keys: &Keys, cursor: &mut Cursor<M::Id>, editor: &mut EditorState, ) -> Result<bool, S::Error> @@ -77,152 +61,188 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> { S: Send + Sync, S::Error: Send, { - let chat_height: i32 = (frame.size().height - 3).into(); - let widthdb = frame.widthdb(); + let chat_height: i32 = (event.frame().size().height - 3).into(); - match event { - key!('k') | key!(Up) => cursor.move_up_in_tree(&self.store, &self.folded).await?, - key!('j') | key!(Down) => cursor.move_down_in_tree(&self.store, &self.folded).await?, - key!('K') | key!(Ctrl + Up) => cursor.move_to_prev_sibling(&self.store).await?, - key!('J') | key!(Ctrl + Down) => cursor.move_to_next_sibling(&self.store).await?, - key!('p') => cursor.move_to_parent(&self.store).await?, - key!('P') => cursor.move_to_root(&self.store).await?, - key!('h') | key!(Left) => cursor.move_to_older_msg(&self.store).await?, - key!('l') | key!(Right) => cursor.move_to_newer_msg(&self.store).await?, - key!('H') | key!(Ctrl + Left) => cursor.move_to_older_unseen_msg(&self.store).await?, - key!('L') | key!(Ctrl + Right) => cursor.move_to_newer_unseen_msg(&self.store).await?, - key!('g') | key!(Home) => cursor.move_to_top(&self.store).await?, - key!('G') | key!(End) => cursor.move_to_bottom(), - key!(Ctrl + 'y') => self.scroll_by(cursor, editor, widthdb, 1).await?, - key!(Ctrl + 'e') => self.scroll_by(cursor, editor, widthdb, -1).await?, - key!(Ctrl + 'u') => { - let delta = chat_height / 2; - self.scroll_by(cursor, editor, widthdb, delta).await?; - } - key!(Ctrl + 'd') => { - let delta = -(chat_height / 2); - self.scroll_by(cursor, editor, widthdb, delta).await?; - } - key!(Ctrl + 'b') | key!(PageUp) => { - let delta = chat_height.saturating_sub(1); - self.scroll_by(cursor, editor, widthdb, delta).await?; - } - key!(Ctrl + 'f') | key!(PageDown) => { - let delta = -chat_height.saturating_sub(1); - self.scroll_by(cursor, editor, widthdb, delta).await?; - } - key!('z') => self.center_cursor(cursor, editor, widthdb).await?, - _ => return Ok(false), + // Basic cursor movement + if event.matches(&keys.cursor.up) { + cursor.move_up_in_tree(&self.store, &self.folded).await?; + return Ok(true); + } + if event.matches(&keys.cursor.down) { + cursor.move_down_in_tree(&self.store, &self.folded).await?; + return Ok(true); + } + if event.matches(&keys.cursor.to_top) { + cursor.move_to_top(&self.store).await?; + return Ok(true); + } + if event.matches(&keys.cursor.to_bottom) { + cursor.move_to_bottom(); + return Ok(true); } - Ok(true) - } + // Tree cursor movement + if event.matches(&keys.tree.cursor.to_above_sibling) { + cursor.move_to_prev_sibling(&self.store).await?; + return Ok(true); + } + if event.matches(&keys.tree.cursor.to_below_sibling) { + cursor.move_to_next_sibling(&self.store).await?; + return Ok(true); + } + if event.matches(&keys.tree.cursor.to_parent) { + cursor.move_to_parent(&self.store).await?; + return Ok(true); + } + if event.matches(&keys.tree.cursor.to_root) { + cursor.move_to_root(&self.store).await?; + return Ok(true); + } + if event.matches(&keys.tree.cursor.to_older_message) { + cursor.move_to_older_msg(&self.store).await?; + return Ok(true); + } + if event.matches(&keys.tree.cursor.to_newer_message) { + cursor.move_to_newer_msg(&self.store).await?; + return Ok(true); + } + if event.matches(&keys.tree.cursor.to_older_unseen_message) { + cursor.move_to_older_unseen_msg(&self.store).await?; + return Ok(true); + } + if event.matches(&keys.tree.cursor.to_newer_unseen_message) { + cursor.move_to_newer_unseen_msg(&self.store).await?; + return Ok(true); + } - pub fn list_action_key_bindings(&self, bindings: &mut KeyBindingsList) { - bindings.binding("space", "fold current message's subtree"); - bindings.binding("s", "toggle current message's seen status"); - bindings.binding("S", "mark all visible messages as seen"); - bindings.binding("ctrl+s", "mark all older messages as seen"); + // Scrolling + if event.matches(&keys.scroll.up_line) { + self.scroll_by(cursor, editor, event.widthdb(), 1).await?; + return Ok(true); + } + if event.matches(&keys.scroll.down_line) { + self.scroll_by(cursor, editor, event.widthdb(), -1).await?; + return Ok(true); + } + if event.matches(&keys.scroll.up_half) { + let delta = chat_height / 2; + self.scroll_by(cursor, editor, event.widthdb(), delta) + .await?; + return Ok(true); + } + if event.matches(&keys.scroll.down_half) { + let delta = -(chat_height / 2); + self.scroll_by(cursor, editor, event.widthdb(), delta) + .await?; + return Ok(true); + } + if event.matches(&keys.scroll.up_full) { + let delta = chat_height.saturating_sub(1); + self.scroll_by(cursor, editor, event.widthdb(), delta) + .await?; + return Ok(true); + } + if event.matches(&keys.scroll.down_full) { + let delta = -chat_height.saturating_sub(1); + self.scroll_by(cursor, editor, event.widthdb(), delta) + .await?; + return Ok(true); + } + if event.matches(&keys.scroll.center_cursor) { + self.center_cursor(cursor, editor, event.widthdb()).await?; + return Ok(true); + } + + Ok(false) } async fn handle_action_input_event( &mut self, - event: &InputEvent, + event: &mut InputEvent<'_>, + keys: &Keys, id: Option<&M::Id>, ) -> Result<bool, S::Error> { - match event { - key!(' ') => { - if let Some(id) = id { - if !self.folded.remove(id) { - self.folded.insert(id.clone()); - } - return Ok(true); + if event.matches(&keys.tree.action.fold_tree) { + if let Some(id) = id { + if !self.folded.remove(id) { + self.folded.insert(id.clone()); } } - key!('s') => { - if let Some(id) = id { - if let Some(msg) = self.store.tree(id).await?.msg(id) { - self.store.set_seen(id, !msg.seen()).await?; - } - return Ok(true); - } - } - key!('S') => { - for id in &self.last_visible_msgs { - self.store.set_seen(id, true).await?; - } - return Ok(true); - } - key!(Ctrl + 's') => { - if let Some(id) = id { - self.store.set_older_seen(id, true).await?; - } else { - self.store - .set_older_seen(&M::last_possible_id(), true) - .await?; - } - return Ok(true); - } - _ => {} + return Ok(true); } - Ok(false) - } - pub fn list_edit_initiating_key_bindings(&self, bindings: &mut KeyBindingsList) { - bindings.binding("r", "reply to message (inline if possible, else directly)"); - bindings.binding("R", "reply to message (opposite of R)"); - bindings.binding("t", "start a new thread"); + if event.matches(&keys.tree.action.toggle_seen) { + if let Some(id) = id { + if let Some(msg) = self.store.tree(id).await?.msg(id) { + self.store.set_seen(id, !msg.seen()).await?; + } + } + return Ok(true); + } + + if event.matches(&keys.tree.action.mark_visible_seen) { + for id in &self.last_visible_msgs { + self.store.set_seen(id, true).await?; + } + return Ok(true); + } + + if event.matches(&keys.tree.action.mark_older_seen) { + if let Some(id) = id { + self.store.set_older_seen(id, true).await?; + } else { + self.store + .set_older_seen(&M::last_possible_id(), true) + .await?; + } + return Ok(true); + } + + Ok(false) } async fn handle_edit_initiating_input_event( &mut self, - event: &InputEvent, + event: &mut InputEvent<'_>, + keys: &Keys, cursor: &mut Cursor<M::Id>, id: Option<M::Id>, ) -> Result<bool, S::Error> { - match event { - key!('r') => { - if let Some(parent) = cursor.parent_for_normal_tree_reply(&self.store).await? { - *cursor = Cursor::Editor { - coming_from: id, - parent, - }; - } - } - key!('R') => { - if let Some(parent) = cursor.parent_for_alternate_tree_reply(&self.store).await? { - *cursor = Cursor::Editor { - coming_from: id, - parent, - }; - } - } - key!('t') | key!('T') => { + if event.matches(&keys.tree.action.reply) { + if let Some(parent) = cursor.parent_for_normal_tree_reply(&self.store).await? { *cursor = Cursor::Editor { coming_from: id, - parent: None, + parent, }; } - _ => return Ok(false), + return Ok(true); } - Ok(true) - } - - pub fn list_normal_key_bindings(&self, bindings: &mut KeyBindingsList, can_compose: bool) { - self.list_movement_key_bindings(bindings); - bindings.empty(); - self.list_action_key_bindings(bindings); - if can_compose { - bindings.empty(); - self.list_edit_initiating_key_bindings(bindings); + if event.matches(&keys.tree.action.reply_alternate) { + if let Some(parent) = cursor.parent_for_alternate_tree_reply(&self.store).await? { + *cursor = Cursor::Editor { + coming_from: id, + parent, + }; + } + return Ok(true); } + + if event.matches(&keys.tree.action.new_thread) { + *cursor = Cursor::Editor { + coming_from: id, + parent: None, + }; + return Ok(true); + } + + Ok(false) } async fn handle_normal_input_event( &mut self, - frame: &mut Frame, - event: &InputEvent, + event: &mut InputEvent<'_>, + keys: &Keys, cursor: &mut Cursor<M::Id>, editor: &mut EditorState, can_compose: bool, @@ -234,102 +254,73 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> { S: Send + Sync, S::Error: Send, { - #[allow(clippy::if_same_then_else)] - Ok( - if self - .handle_movement_input_event(frame, event, cursor, editor) + if self + .handle_movement_input_event(event, keys, cursor, editor) + .await? + { + return Ok(true); + } + + if self + .handle_action_input_event(event, keys, id.as_ref()) + .await? + { + return Ok(true); + } + + if can_compose + && self + .handle_edit_initiating_input_event(event, keys, cursor, id) .await? - { - true - } else if self.handle_action_input_event(event, id.as_ref()).await? { - true - } else if can_compose { - self.handle_edit_initiating_input_event(event, cursor, id) - .await? - } else { - false - }, - ) + { + return Ok(true); + } + + Ok(false) } - fn list_editor_key_bindings(&self, bindings: &mut KeyBindingsList) { - bindings.binding("esc", "close editor"); - bindings.binding("enter", "send message"); - util::list_editor_key_bindings_allowing_external_editing(bindings, |_| true); - } - - #[allow(clippy::too_many_arguments)] fn handle_editor_input_event( &mut self, - terminal: &mut Terminal, - crossterm_lock: &Arc<FairMutex<()>>, - event: &InputEvent, + event: &mut InputEvent<'_>, + keys: &Keys, cursor: &mut Cursor<M::Id>, editor: &mut EditorState, coming_from: Option<M::Id>, parent: Option<M::Id>, ) -> Reaction<M> { - // TODO Tab-completion + // Abort edit + if event.matches(&keys.general.abort) { + *cursor = coming_from.map(Cursor::Msg).unwrap_or(Cursor::Bottom); + return Reaction::Handled; + } - match event { - key!(Esc) => { - *cursor = coming_from.map(Cursor::Msg).unwrap_or(Cursor::Bottom); + // Send message + if event.matches(&keys.general.confirm) { + let content = editor.text().to_string(); + if content.trim().is_empty() { return Reaction::Handled; } - - key!(Enter) => { - let content = editor.text().to_string(); - if !content.trim().is_empty() { - *cursor = Cursor::Pseudo { - coming_from, - parent: parent.clone(), - }; - return Reaction::Composed { parent, content }; - } - } - - _ => { - let handled = util::handle_editor_input_event_allowing_external_editing( - editor, - terminal, - crossterm_lock, - event, - |_| true, - ); - match handled { - Ok(true) => {} - Ok(false) => return Reaction::NotHandled, - Err(e) => return Reaction::ComposeError(e), - } - } + *cursor = Cursor::Pseudo { + coming_from, + parent: parent.clone(), + }; + return Reaction::Composed { parent, content }; } - Reaction::Handled - } + // TODO Tab-completion - pub fn list_key_bindings( - &self, - bindings: &mut KeyBindingsList, - cursor: &Cursor<M::Id>, - can_compose: bool, - ) { - bindings.heading("Chat"); - match cursor { - Cursor::Bottom | Cursor::Msg(_) => { - self.list_normal_key_bindings(bindings, can_compose); - } - Cursor::Editor { .. } => self.list_editor_key_bindings(bindings), - Cursor::Pseudo { .. } => { - self.list_normal_key_bindings(bindings, false); - } + // Editing + if util::handle_editor_input_event(editor, event, keys, |_| true) { + return Reaction::Handled; } + + Reaction::NotHandled } pub async fn handle_input_event( &mut self, - terminal: &mut Terminal, - crossterm_lock: &Arc<FairMutex<()>>, - event: &InputEvent, + event: &mut InputEvent<'_>, + keys: &Keys, cursor: &mut Cursor<M::Id>, editor: &mut EditorState, can_compose: bool, @@ -343,14 +334,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> { Ok(match cursor { Cursor::Bottom => { if self - .handle_normal_input_event( - terminal.frame(), - event, - cursor, - editor, - can_compose, - None, - ) + .handle_normal_input_event(event, keys, cursor, editor, can_compose, None) .await? { Reaction::Handled @@ -361,14 +345,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> { Cursor::Msg(id) => { let id = id.clone(); if self - .handle_normal_input_event( - terminal.frame(), - event, - cursor, - editor, - can_compose, - Some(id), - ) + .handle_normal_input_event(event, keys, cursor, editor, can_compose, Some(id)) .await? { Reaction::Handled @@ -382,19 +359,11 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> { } => { let coming_from = coming_from.clone(); let parent = parent.clone(); - self.handle_editor_input_event( - terminal, - crossterm_lock, - event, - cursor, - editor, - coming_from, - parent, - ) + self.handle_editor_input_event(event, keys, cursor, editor, coming_from, parent) } Cursor::Pseudo { .. } => { if self - .handle_movement_input_event(terminal.frame(), event, cursor, editor) + .handle_movement_input_event(event, keys, cursor, editor) .await? { Reaction::Handled diff --git a/cove/src/ui/euph/account.rs b/cove/src/ui/euph/account.rs index c398662..359e9d5 100644 --- a/cove/src/ui/euph/account.rs +++ b/cove/src/ui/euph/account.rs @@ -1,14 +1,17 @@ +use cove_config::Keys; +use cove_input::InputEvent; use crossterm::style::Stylize; use euphoxide::api::PersonalAccountView; use euphoxide::conn; -use toss::widgets::{EditorState, Empty, Join3, Join4, Text}; -use toss::{Style, Terminal, Widget, WidgetExt}; +use toss::widgets::{EditorState, Empty, Join3, Join4, Join5, Text}; +use toss::{Style, Widget, WidgetExt}; use crate::euph::{self, Room}; -use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::widgets::Popup; use crate::ui::{util, UiError}; +use super::popup::PopupResult; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Focus { Email, @@ -65,7 +68,7 @@ pub struct LoggedIn(PersonalAccountView); impl LoggedIn { fn widget(&self) -> impl Widget<UiError> { let bold = Style::new().bold(); - Join3::vertical( + Join5::vertical( Text::new(("Logged in", bold.green())).segment(), Empty::new().with_height(1).segment(), Join3::horizontal( @@ -76,6 +79,8 @@ impl LoggedIn { Text::new((&self.0.email,)).segment(), ) .segment(), + Empty::new().with_height(1).segment(), + Text::new(("Log out", Style::new().black().on_white())).segment(), ) } } @@ -85,12 +90,6 @@ pub enum AccountUiState { LoggedIn(LoggedIn), } -pub enum EventResult { - NotHandled, - Handled, - ResetState, -} - impl AccountUiState { pub fn new() -> Self { Self::LoggedOut(LoggedOut::new()) @@ -121,94 +120,74 @@ impl AccountUiState { Popup::new(inner, "Account") } - pub fn list_key_bindings(&self, bindings: &mut KeyBindingsList) { - bindings.binding("esc", "close account ui"); - - match self { - Self::LoggedOut(logged_out) => { - match logged_out.focus { - Focus::Email => bindings.binding("enter", "focus on password"), - Focus::Password => bindings.binding("enter", "log in"), - } - bindings.binding("tab", "switch focus"); - util::list_editor_key_bindings(bindings, |c| c != '\n'); - } - Self::LoggedIn(_) => bindings.binding("L", "log out"), - } - } - pub fn handle_input_event( &mut self, - terminal: &mut Terminal, - event: &InputEvent, + event: &mut InputEvent<'_>, + keys: &Keys, room: &Option<Room>, - ) -> EventResult { - if let key!(Esc) = event { - return EventResult::ResetState; + ) -> PopupResult { + if event.matches(&keys.general.abort) { + return PopupResult::Close; } match self { Self::LoggedOut(logged_out) => { - if let key!(Tab) = event { + if event.matches(&keys.general.focus) { logged_out.focus = match logged_out.focus { Focus::Email => Focus::Password, Focus::Password => Focus::Email, }; - return EventResult::Handled; + return PopupResult::Handled; } match logged_out.focus { Focus::Email => { - if let key!(Enter) = event { + if event.matches(&keys.general.confirm) { logged_out.focus = Focus::Password; - return EventResult::Handled; + return PopupResult::Handled; } if util::handle_editor_input_event( &mut logged_out.email, - terminal, event, + keys, |c| c != '\n', ) { - EventResult::Handled - } else { - EventResult::NotHandled + return PopupResult::Handled; } } Focus::Password => { - if let key!(Enter) = event { + if event.matches(&keys.general.confirm) { if let Some(room) = room { let _ = room.login( logged_out.email.text().to_string(), logged_out.password.text().to_string(), ); } - return EventResult::Handled; + return PopupResult::Handled; } if util::handle_editor_input_event( &mut logged_out.password, - terminal, event, + keys, |c| c != '\n', ) { - EventResult::Handled - } else { - EventResult::NotHandled + return PopupResult::Handled; } } } } Self::LoggedIn(_) => { - if let key!('L') = event { + if event.matches(&keys.general.confirm) { if let Some(room) = room { let _ = room.logout(); } - EventResult::Handled - } else { - EventResult::NotHandled + return PopupResult::Handled; } } } + + PopupResult::NotHandled } } diff --git a/cove/src/ui/euph/auth.rs b/cove/src/ui/euph/auth.rs index 8cc58f9..b938ff1 100644 --- a/cove/src/ui/euph/auth.rs +++ b/cove/src/ui/euph/auth.rs @@ -1,11 +1,14 @@ +use cove_config::Keys; +use cove_input::InputEvent; use toss::widgets::EditorState; -use toss::{Terminal, Widget}; +use toss::Widget; use crate::euph::Room; -use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::widgets::Popup; use crate::ui::{util, UiError}; +use super::popup::PopupResult; + pub fn new() -> EditorState { EditorState::new() } @@ -17,38 +20,26 @@ pub fn widget(editor: &mut EditorState) -> impl Widget<UiError> + '_ { ) } -pub fn list_key_bindings(bindings: &mut KeyBindingsList) { - bindings.binding("esc", "abort"); - bindings.binding("enter", "authenticate"); - util::list_editor_key_bindings(bindings, |_| true); -} - -pub enum EventResult { - NotHandled, - Handled, - ResetState, -} - pub fn handle_input_event( - terminal: &mut Terminal, - event: &InputEvent, + event: &mut InputEvent<'_>, + keys: &Keys, room: &Option<Room>, editor: &mut EditorState, -) -> EventResult { - match event { - key!(Esc) => EventResult::ResetState, - key!(Enter) => { - if let Some(room) = &room { - let _ = room.auth(editor.text().to_string()); - } - EventResult::ResetState - } - _ => { - if util::handle_editor_input_event(editor, terminal, event, |_| true) { - EventResult::Handled - } else { - EventResult::NotHandled - } - } +) -> PopupResult { + if event.matches(&keys.general.abort) { + return PopupResult::Close; } + + if event.matches(&keys.general.confirm) { + if let Some(room) = &room { + let _ = room.auth(editor.text().to_string()); + } + return PopupResult::Close; + } + + if util::handle_editor_input_event(editor, event, keys, |_| true) { + return PopupResult::Handled; + } + + PopupResult::NotHandled } diff --git a/cove/src/ui/euph/inspect.rs b/cove/src/ui/euph/inspect.rs index 7cbf054..25620a2 100644 --- a/cove/src/ui/euph/inspect.rs +++ b/cove/src/ui/euph/inspect.rs @@ -1,13 +1,16 @@ +use cove_config::Keys; +use cove_input::InputEvent; use crossterm::style::Stylize; use euphoxide::api::{Message, NickEvent, SessionView}; use euphoxide::conn::SessionInfo; use toss::widgets::Text; use toss::{Style, Styled, Widget}; -use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::widgets::Popup; use crate::ui::UiError; +use super::popup::PopupResult; + macro_rules! line { ( $text:ident, $name:expr, $val:expr ) => { $text = $text @@ -122,18 +125,10 @@ pub fn message_widget(msg: &Message) -> impl Widget<UiError> { Popup::new(Text::new(text), "Inspect message") } -pub fn list_key_bindings(bindings: &mut KeyBindingsList) { - bindings.binding("esc", "close"); -} - -pub enum EventResult { - NotHandled, - Close, -} - -pub fn handle_input_event(event: &InputEvent) -> EventResult { - match event { - key!(Esc) => EventResult::Close, - _ => EventResult::NotHandled, +pub fn handle_input_event(event: &mut InputEvent<'_>, keys: &Keys) -> PopupResult { + if event.matches(&keys.general.abort) { + return PopupResult::Close; } + + PopupResult::NotHandled } diff --git a/cove/src/ui/euph/links.rs b/cove/src/ui/euph/links.rs index 3cebf7d..00d9d7d 100644 --- a/cove/src/ui/euph/links.rs +++ b/cove/src/ui/euph/links.rs @@ -1,26 +1,21 @@ -use std::io; - +use cove_config::Keys; +use cove_input::InputEvent; +use crossterm::event::KeyCode; use crossterm::style::Stylize; use linkify::{LinkFinder, LinkKind}; use toss::widgets::Text; use toss::{Style, Styled, Widget}; -use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::widgets::{ListBuilder, ListState, Popup}; -use crate::ui::UiError; +use crate::ui::{util, UiError}; + +use super::popup::PopupResult; pub struct LinksState { links: Vec<String>, list: ListState<usize>, } -pub enum EventResult { - NotHandled, - Handled, - Close, - ErrorOpeningLink { link: String, error: io::Error }, -} - const NUMBER_KEYS: [char; 10] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']; impl LinksState { @@ -77,7 +72,7 @@ impl LinksState { Popup::new(list_builder.build(&mut self.list), "Links") } - fn open_link_by_id(&self, id: usize) -> EventResult { + fn open_link_by_id(&self, id: usize) -> PopupResult { if let Some(link) = self.links.get(id) { // The `http://` or `https://` schema is necessary for open::that to // successfully open the link in the browser. @@ -88,53 +83,52 @@ impl LinksState { }; if let Err(error) = open::that(&link) { - return EventResult::ErrorOpeningLink { link, error }; + return PopupResult::ErrorOpeningLink { link, error }; } } - EventResult::Handled + PopupResult::Handled } - fn open_link(&self) -> EventResult { + fn open_link(&self) -> PopupResult { if let Some(id) = self.list.selected() { self.open_link_by_id(*id) } else { - EventResult::Handled + PopupResult::Handled } } - pub fn list_key_bindings(&self, bindings: &mut KeyBindingsList) { - bindings.binding("esc", "close links popup"); - bindings.binding("j/k, ↓/↑", "move cursor up/down"); - bindings.binding("g, home", "move cursor to top"); - bindings.binding("G, end", "move cursor to bottom"); - bindings.binding("ctrl+y/e", "scroll up/down"); - bindings.empty(); - bindings.binding("enter", "open selected link"); - bindings.binding("1,2,...", "open link by position"); - } - - pub fn handle_input_event(&mut self, event: &InputEvent) -> EventResult { - match event { - key!(Esc) => return EventResult::Close, - key!('k') | key!(Up) => self.list.move_cursor_up(), - key!('j') | key!(Down) => self.list.move_cursor_down(), - key!('g') | key!(Home) => self.list.move_cursor_to_top(), - key!('G') | key!(End) => self.list.move_cursor_to_bottom(), - key!(Ctrl + 'y') => self.list.scroll_up(1), - key!(Ctrl + 'e') => self.list.scroll_down(1), - key!(Enter) => return self.open_link(), - key!('1') => return self.open_link_by_id(0), - key!('2') => return self.open_link_by_id(1), - key!('3') => return self.open_link_by_id(2), - key!('4') => return self.open_link_by_id(3), - key!('5') => return self.open_link_by_id(4), - key!('6') => return self.open_link_by_id(5), - key!('7') => return self.open_link_by_id(6), - key!('8') => return self.open_link_by_id(7), - key!('9') => return self.open_link_by_id(8), - key!('0') => return self.open_link_by_id(9), - _ => return EventResult::NotHandled, + pub fn handle_input_event(&mut self, event: &mut InputEvent<'_>, keys: &Keys) -> PopupResult { + if event.matches(&keys.general.abort) { + return PopupResult::Close; } - EventResult::Handled + + if event.matches(&keys.general.confirm) { + return self.open_link(); + } + + if util::handle_list_input_event(&mut self.list, event, keys) { + return PopupResult::Handled; + } + + // TODO Mention that this is possible in the UI + if let Some(key_event) = event.key_event() { + if key_event.modifiers.is_empty() { + match key_event.code { + KeyCode::Char('1') => return self.open_link_by_id(0), + KeyCode::Char('2') => return self.open_link_by_id(1), + KeyCode::Char('3') => return self.open_link_by_id(2), + KeyCode::Char('4') => return self.open_link_by_id(3), + KeyCode::Char('5') => return self.open_link_by_id(4), + KeyCode::Char('6') => return self.open_link_by_id(5), + KeyCode::Char('7') => return self.open_link_by_id(6), + KeyCode::Char('8') => return self.open_link_by_id(7), + KeyCode::Char('9') => return self.open_link_by_id(8), + KeyCode::Char('0') => return self.open_link_by_id(9), + _ => {} + } + } + } + + PopupResult::NotHandled } } diff --git a/cove/src/ui/euph/nick.rs b/cove/src/ui/euph/nick.rs index c928297..0bb1062 100644 --- a/cove/src/ui/euph/nick.rs +++ b/cove/src/ui/euph/nick.rs @@ -1,12 +1,15 @@ +use cove_config::Keys; +use cove_input::InputEvent; use euphoxide::conn::Joined; use toss::widgets::EditorState; -use toss::{Style, Terminal, Widget}; +use toss::{Style, Widget}; use crate::euph::{self, Room}; -use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::widgets::Popup; use crate::ui::{util, UiError}; +use super::popup::PopupResult; + pub fn new(joined: Joined) -> EditorState { EditorState::with_initial_text(joined.session.name) } @@ -19,42 +22,26 @@ pub fn widget(editor: &mut EditorState) -> impl Widget<UiError> + '_ { Popup::new(inner, "Choose nick") } -fn nick_char(c: char) -> bool { - c != '\n' -} - -pub fn list_key_bindings(bindings: &mut KeyBindingsList) { - bindings.binding("esc", "abort"); - bindings.binding("enter", "set nick"); - util::list_editor_key_bindings(bindings, nick_char); -} - -pub enum EventResult { - NotHandled, - Handled, - ResetState, -} - pub fn handle_input_event( - terminal: &mut Terminal, - event: &InputEvent, + event: &mut InputEvent<'_>, + keys: &Keys, room: &Option<Room>, editor: &mut EditorState, -) -> EventResult { - match event { - key!(Esc) => EventResult::ResetState, - key!(Enter) => { - if let Some(room) = &room { - let _ = room.nick(editor.text().to_string()); - } - EventResult::ResetState - } - _ => { - if util::handle_editor_input_event(editor, terminal, event, nick_char) { - EventResult::Handled - } else { - EventResult::NotHandled - } - } +) -> PopupResult { + if event.matches(&keys.general.abort) { + return PopupResult::Close; } + + if event.matches(&keys.general.confirm) { + if let Some(room) = &room { + let _ = room.nick(editor.text().to_string()); + } + return PopupResult::Close; + } + + if util::handle_editor_input_event(editor, event, keys, |c| c != '\n') { + return PopupResult::Handled; + } + + PopupResult::NotHandled } diff --git a/cove/src/ui/euph/popup.rs b/cove/src/ui/euph/popup.rs index 2ab8278..f70e999 100644 --- a/cove/src/ui/euph/popup.rs +++ b/cove/src/ui/euph/popup.rs @@ -1,3 +1,5 @@ +use std::io; + use crossterm::style::Stylize; use toss::widgets::Text; use toss::{Style, Styled, Widget}; @@ -30,3 +32,10 @@ impl RoomPopup { } } } + +pub enum PopupResult { + NotHandled, + Handled, + Close, + ErrorOpeningLink { link: String, error: io::Error }, +} diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index 8dc6ed0..f539a0e 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -1,27 +1,26 @@ use std::collections::VecDeque; -use std::sync::Arc; +use cove_config::Keys; +use cove_input::InputEvent; use crossterm::style::Stylize; use euphoxide::api::{Data, Message, MessageId, PacketType, SessionId}; use euphoxide::bot::instance::{Event, ServerConfig}; use euphoxide::conn::{self, Joined, Joining, SessionInfo}; -use parking_lot::FairMutex; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::{mpsc, oneshot}; use toss::widgets::{BoxedAsync, EditorState, Join2, Layer, Text}; -use toss::{Style, Styled, Terminal, Widget, WidgetExt}; +use toss::{Style, Styled, Widget, WidgetExt}; use crate::euph; use crate::macros::logging_unwrap; use crate::ui::chat::{ChatState, Reaction}; -use crate::ui::input::{key, InputEvent, KeyBindingsList}; use crate::ui::widgets::ListState; use crate::ui::{util, UiError, UiEvent}; use crate::vault::EuphRoomVault; -use super::account::{self, AccountUiState}; -use super::links::{self, LinksState}; -use super::popup::RoomPopup; +use super::account::AccountUiState; +use super::links::LinksState; +use super::popup::{PopupResult, RoomPopup}; use super::{auth, inspect, nick, nick_list}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -316,29 +315,13 @@ impl EuphRoom { Text::new(info).padding().with_horizontal(1).border() } - async fn list_chat_key_bindings(&self, bindings: &mut KeyBindingsList) { - let can_compose = matches!( - self.room_state(), - Some(euph::State::Connected(_, conn::State::Joined(_))) - ); - self.chat.list_key_bindings(bindings, can_compose).await; - } - - async fn handle_chat_input_event( - &mut self, - terminal: &mut Terminal, - crossterm_lock: &Arc<FairMutex<()>>, - event: &InputEvent, - ) -> bool { + async fn handle_chat_input_event(&mut self, event: &mut InputEvent<'_>, keys: &Keys) -> bool { let can_compose = matches!( self.room_state(), Some(euph::State::Connected(_, conn::State::Joined(_))) ); - let reaction = self - .chat - .handle_input_event(terminal, crossterm_lock, event, can_compose) - .await; + let reaction = self.chat.handle_input_event(event, keys, can_compose).await; let reaction = logging_unwrap!(reaction); match reaction { @@ -353,19 +336,12 @@ impl EuphRoom { return true; } } - Reaction::ComposeError(e) => { - self.popups.push_front(RoomPopup::Error { - description: "Failed to use external editor".to_string(), - reason: format!("{e}"), - }); - return true; - } } false } - fn list_room_key_bindings(&self, bindings: &mut KeyBindingsList) { + async fn handle_room_input_event(&mut self, event: &mut InputEvent<'_>, keys: &Keys) -> bool { match self.room_state() { // Authenticating Some(euph::State::Connected( @@ -374,138 +350,95 @@ impl EuphRoom { bounce: Some(_), .. }), )) => { - bindings.binding("a", "authenticate"); - } - - // Connected - Some(euph::State::Connected(_, conn::State::Joined(_))) => { - bindings.binding("n", "change nick"); - bindings.binding("m", "download more messages"); - bindings.binding("A", "show account ui"); - } - - // Otherwise - _ => {} - } - - // Inspecting messages - bindings.binding("i", "inspect message"); - bindings.binding("I", "show message links"); - bindings.binding("ctrl+p", "open room's plugh.de/present page"); - } - - async fn handle_room_input_event(&mut self, event: &InputEvent) -> bool { - match self.room_state() { - // Authenticating - Some(euph::State::Connected( - _, - conn::State::Joining(Joining { - bounce: Some(_), .. - }), - )) => { - if let key!('a') = event { + if event.matches(&keys.room.action.authenticate) { self.state = State::Auth(auth::new()); return true; } } // Joined - Some(euph::State::Connected(_, conn::State::Joined(joined))) => match event { - key!('n') | key!('N') => { + Some(euph::State::Connected(_, conn::State::Joined(joined))) => { + if event.matches(&keys.room.action.nick) { self.state = State::Nick(nick::new(joined.clone())); return true; } - key!('m') => { + if event.matches(&keys.room.action.more_messages) { if let Some(room) = &self.room { let _ = room.log(); } return true; } - key!('A') => { + if event.matches(&keys.room.action.account) { self.state = State::Account(AccountUiState::new()); return true; } - _ => {} - }, + } // Otherwise _ => {} } // Always applicable - match event { - key!('i') => { - if let Some(id) = self.chat.cursor() { - if let Some(msg) = logging_unwrap!(self.vault().full_msg(*id).await) { - self.state = State::InspectMessage(msg); - } - } - return true; + if event.matches(&keys.room.action.present) { + let link = format!("https://plugh.de/present/{}/", self.name()); + if let Err(error) = open::that(&link) { + self.popups.push_front(RoomPopup::Error { + description: format!("Failed to open link: {link}"), + reason: format!("{error}"), + }); } - key!('I') => { - if let Some(id) = self.chat.cursor() { - if let Some(msg) = logging_unwrap!(self.vault().msg(*id).await) { - self.state = State::Links(LinksState::new(&msg.content)); - } - } - return true; - } - key!(Ctrl + 'p') => { - let link = format!("https://plugh.de/present/{}/", self.name()); - if let Err(error) = open::that(&link) { - self.popups.push_front(RoomPopup::Error { - description: format!("Failed to open link: {link}"), - reason: format!("{error}"), - }); - } - return true; - } - _ => {} + return true; } false } - async fn list_chat_focus_key_bindings(&self, bindings: &mut KeyBindingsList) { - self.list_room_key_bindings(bindings); - bindings.empty(); - self.list_chat_key_bindings(bindings).await; - } - async fn handle_chat_focus_input_event( &mut self, - terminal: &mut Terminal, - crossterm_lock: &Arc<FairMutex<()>>, - event: &InputEvent, + event: &mut InputEvent<'_>, + keys: &Keys, ) -> bool { // We need to handle chat input first, otherwise the other // key bindings will shadow characters in the editor. - if self - .handle_chat_input_event(terminal, crossterm_lock, event) - .await - { + if self.handle_chat_input_event(event, keys).await { return true; } - if self.handle_room_input_event(event).await { + if self.handle_room_input_event(event, keys).await { + return true; + } + + if event.matches(&keys.tree.action.inspect) { + if let Some(id) = self.chat.cursor() { + if let Some(msg) = logging_unwrap!(self.vault().full_msg(*id).await) { + self.state = State::InspectMessage(msg); + } + } + return true; + } + + if event.matches(&keys.tree.action.links) { + if let Some(id) = self.chat.cursor() { + if let Some(msg) = logging_unwrap!(self.vault().msg(*id).await) { + self.state = State::Links(LinksState::new(&msg.content)); + } + } return true; } false } - fn list_nick_list_focus_key_bindings(&self, bindings: &mut KeyBindingsList) { - util::list_list_key_bindings(bindings); - - bindings.binding("i", "inspect session"); - } - - fn handle_nick_list_focus_input_event(&mut self, event: &InputEvent) -> bool { - if util::handle_list_input_event(&mut self.nick_list, event) { + fn handle_nick_list_focus_input_event( + &mut self, + event: &mut InputEvent<'_>, + keys: &Keys, + ) -> bool { + if util::handle_list_input_event(&mut self.nick_list, event, keys) { return true; } - if let key!('i') = event { + if event.matches(&keys.tree.action.inspect) { if let Some(euph::State::Connected(_, conn::State::Joined(joined))) = self.room_state() { if let Some(id) = self.nick_list.selected() { @@ -523,58 +456,27 @@ impl EuphRoom { false } - pub async fn list_normal_key_bindings(&self, bindings: &mut KeyBindingsList) { - // Handled in rooms list, not here - bindings.binding("esc", "leave room"); - + async fn handle_normal_input_event(&mut self, event: &mut InputEvent<'_>, keys: &Keys) -> bool { match self.focus { Focus::Chat => { - if let Some(euph::State::Connected(_, conn::State::Joined(_))) = self.room_state() { - bindings.binding("tab", "focus on nick list"); - } - - self.list_chat_focus_key_bindings(bindings).await; - } - Focus::NickList => { - bindings.binding("tab, esc", "focus on chat"); - bindings.empty(); - bindings.heading("Nick list"); - self.list_nick_list_focus_key_bindings(bindings); - } - } - } - - async fn handle_normal_input_event( - &mut self, - terminal: &mut Terminal, - crossterm_lock: &Arc<FairMutex<()>>, - event: &InputEvent, - ) -> bool { - match self.focus { - Focus::Chat => { - // Needs to be handled first or the tab key may be shadowed - // during editing. - if self - .handle_chat_focus_input_event(terminal, crossterm_lock, event) - .await - { + if self.handle_chat_focus_input_event(event, keys).await { return true; } if let Some(euph::State::Connected(_, conn::State::Joined(_))) = self.room_state() { - if let key!(Tab) = event { + if event.matches(&keys.general.focus) { self.focus = Focus::NickList; return true; } } } Focus::NickList => { - if let key!(Tab) | key!(Esc) = event { + if event.matches(&keys.general.abort) || event.matches(&keys.general.focus) { self.focus = Focus::Chat; return true; } - if self.handle_nick_list_focus_input_event(event) { + if self.handle_nick_list_focus_input_event(event, keys) { return true; } } @@ -583,100 +485,40 @@ impl EuphRoom { false } - pub async fn list_key_bindings(&self, bindings: &mut KeyBindingsList) { - bindings.heading("Room"); - + pub async fn handle_input_event(&mut self, event: &mut InputEvent<'_>, keys: &Keys) -> bool { if !self.popups.is_empty() { - bindings.binding("esc", "close popup"); - return; - } - - match &self.state { - State::Normal => self.list_normal_key_bindings(bindings).await, - State::Auth(_) => auth::list_key_bindings(bindings), - State::Nick(_) => nick::list_key_bindings(bindings), - State::Account(account) => account.list_key_bindings(bindings), - State::Links(links) => links.list_key_bindings(bindings), - State::InspectMessage(_) | State::InspectSession(_) => { - inspect::list_key_bindings(bindings) - } - } - } - - pub async fn handle_input_event( - &mut self, - terminal: &mut Terminal, - crossterm_lock: &Arc<FairMutex<()>>, - event: &InputEvent, - ) -> bool { - if !self.popups.is_empty() { - if matches!(event, key!(Esc)) { + if event.matches(&keys.general.abort) { self.popups.pop_back(); return true; } + // Prevent event from reaching anything below the popup return false; } - // TODO Use a common EventResult - - match &mut self.state { - State::Normal => { - self.handle_normal_input_event(terminal, crossterm_lock, event) - .await - } - State::Auth(editor) => { - match auth::handle_input_event(terminal, event, &self.room, editor) { - auth::EventResult::NotHandled => false, - auth::EventResult::Handled => true, - auth::EventResult::ResetState => { - self.state = State::Normal; - true - } - } - } - State::Nick(editor) => { - match nick::handle_input_event(terminal, event, &self.room, editor) { - nick::EventResult::NotHandled => false, - nick::EventResult::Handled => true, - nick::EventResult::ResetState => { - self.state = State::Normal; - true - } - } - } - State::Account(account) => { - match account.handle_input_event(terminal, event, &self.room) { - account::EventResult::NotHandled => false, - account::EventResult::Handled => true, - account::EventResult::ResetState => { - self.state = State::Normal; - true - } - } - } - State::Links(links) => match links.handle_input_event(event) { - links::EventResult::NotHandled => false, - links::EventResult::Handled => true, - links::EventResult::Close => { - self.state = State::Normal; - true - } - links::EventResult::ErrorOpeningLink { link, error } => { - self.popups.push_front(RoomPopup::Error { - description: format!("Failed to open link: {link}"), - reason: format!("{error}"), - }); - true - } - }, + let result = match &mut self.state { + State::Normal => return self.handle_normal_input_event(event, keys).await, + State::Auth(editor) => auth::handle_input_event(event, keys, &self.room, editor), + State::Nick(editor) => nick::handle_input_event(event, keys, &self.room, editor), + State::Account(account) => account.handle_input_event(event, keys, &self.room), + State::Links(links) => links.handle_input_event(event, keys), State::InspectMessage(_) | State::InspectSession(_) => { - match inspect::handle_input_event(event) { - inspect::EventResult::NotHandled => false, - inspect::EventResult::Close => { - self.state = State::Normal; - true - } - } + inspect::handle_input_event(event, keys) + } + }; + + match result { + PopupResult::NotHandled => false, + PopupResult::Handled => true, + PopupResult::Close => { + self.state = State::Normal; + true + } + PopupResult::ErrorOpeningLink { link, error } => { + self.popups.push_front(RoomPopup::Error { + description: format!("Failed to open link: {link}"), + reason: format!("{error}"), + }); + true } } } diff --git a/cove/src/ui/input.rs b/cove/src/ui/input.rs deleted file mode 100644 index 85b5f1f..0000000 --- a/cove/src/ui/input.rs +++ /dev/null @@ -1,175 +0,0 @@ -use std::convert::Infallible; - -use crossterm::event::{Event, KeyCode, KeyModifiers}; -use crossterm::style::Stylize; -use toss::widgets::{Empty, Join2, Text}; -use toss::{Style, Styled, Widget, WidgetExt}; - -use super::widgets::{ListBuilder, ListState}; -use super::UiError; - -#[derive(Debug, Clone)] -pub enum InputEvent { - Key(KeyEvent), - Paste(String), -} - -impl InputEvent { - pub fn from_event(event: Event) -> Option<Self> { - match event { - crossterm::event::Event::Key(key) => Some(Self::Key(key.into())), - crossterm::event::Event::Paste(text) => Some(Self::Paste(text)), - _ => None, - } - } -} - -/// A key event data type that is a bit easier to pattern match on than -/// [`crossterm::event::KeyEvent`]. -#[derive(Debug, Clone, Copy)] -pub struct KeyEvent { - pub code: KeyCode, - pub shift: bool, - pub ctrl: bool, - pub alt: bool, -} - -impl From<crossterm::event::KeyEvent> for KeyEvent { - fn from(event: crossterm::event::KeyEvent) -> Self { - Self { - code: event.code, - shift: event.modifiers.contains(KeyModifiers::SHIFT), - ctrl: event.modifiers.contains(KeyModifiers::CONTROL), - alt: event.modifiers.contains(KeyModifiers::ALT), - } - } -} - -#[rustfmt::skip] -#[allow(unused_macro_rules)] -macro_rules! key { - // key!(Paste text) - ( Paste $text:ident ) => { crate::ui::input::InputEvent::Paste($text) }; - - // key!('a') - ( $key:literal ) => { crate::ui::input::InputEvent::Key(crate::ui::input::KeyEvent { code: crossterm::event::KeyCode::Char($key), shift: _, ctrl: false, alt: false, }) }; - ( Ctrl + $key:literal ) => { crate::ui::input::InputEvent::Key(crate::ui::input::KeyEvent { code: crossterm::event::KeyCode::Char($key), shift: _, ctrl: true, alt: false, }) }; - ( Alt + $key:literal ) => { crate::ui::input::InputEvent::Key(crate::ui::input::KeyEvent { code: crossterm::event::KeyCode::Char($key), shift: _, ctrl: false, alt: true, }) }; - - // key!(Char c) - ( Char $key:pat ) => { crate::ui::input::InputEvent::Key(crate::ui::input::KeyEvent { code: crossterm::event::KeyCode::Char($key), shift: _, ctrl: false, alt: false, }) }; - ( Ctrl + Char $key:pat ) => { crate::ui::input::InputEvent::Key(crate::ui::input::KeyEvent { code: crossterm::event::KeyCode::Char($key), shift: _, ctrl: true, alt: false, }) }; - ( Alt + Char $key:pat ) => { crate::ui::input::InputEvent::Key(crate::ui::input::KeyEvent { code: crossterm::event::KeyCode::Char($key), shift: _, ctrl: false, alt: true, }) }; - - // key!(F n) - ( F $key:pat ) => { crate::ui::input::InputEvent::Key(crate::ui::input::KeyEvent { code: crossterm::event::KeyCode::F($key), shift: false, ctrl: false, alt: false, }) }; - ( Shift + F $key:pat ) => { crate::ui::input::InputEvent::Key(crate::ui::input::KeyEvent { code: crossterm::event::KeyCode::F($key), shift: true, ctrl: false, alt: false, }) }; - ( Ctrl + F $key:pat ) => { crate::ui::input::InputEvent::Key(crate::ui::input::KeyEvent { code: crossterm::event::KeyCode::F($key), shift: false, ctrl: true, alt: false, }) }; - ( Alt + F $key:pat ) => { crate::ui::input::InputEvent::Key(crate::ui::input::KeyEvent { code: crossterm::event::KeyCode::F($key), shift: false, ctrl: false, alt: true, }) }; - - // key!(other) - ( $key:ident ) => { crate::ui::input::InputEvent::Key(crate::ui::input::KeyEvent { code: crossterm::event::KeyCode::$key, shift: false, ctrl: false, alt: false, }) }; - ( Shift + $key:ident ) => { crate::ui::input::InputEvent::Key(crate::ui::input::KeyEvent { code: crossterm::event::KeyCode::$key, shift: true, ctrl: false, alt: false, }) }; - ( Ctrl + $key:ident ) => { crate::ui::input::InputEvent::Key(crate::ui::input::KeyEvent { code: crossterm::event::KeyCode::$key, shift: false, ctrl: true, alt: false, }) }; - ( Alt + $key:ident ) => { crate::ui::input::InputEvent::Key(crate::ui::input::KeyEvent { code: crossterm::event::KeyCode::$key, shift: false, ctrl: false, alt: true, }) }; -} -pub(crate) use key; - -enum Row { - Empty, - Heading(String), - Binding(String, String), - BindingContd(String), -} - -pub struct KeyBindingsList(Vec<Row>); - -impl KeyBindingsList { - /// Width of the left column of key bindings. - const BINDING_WIDTH: u16 = 24; - - pub fn new() -> Self { - Self(vec![]) - } - - fn binding_style() -> Style { - Style::new().cyan() - } - - fn row_widget(row: Row) -> impl Widget<UiError> { - match row { - Row::Empty => Text::new("").first3(), - - Row::Heading(name) => Text::new((name, Style::new().bold())).first3(), - - Row::Binding(binding, description) => Join2::horizontal( - Text::new((binding, Self::binding_style())) - .padding() - .with_right(1) - .resize() - .with_min_width(Self::BINDING_WIDTH) - .segment() - .with_fixed(true), - Text::new(description).segment(), - ) - .second3(), - - Row::BindingContd(description) => Join2::horizontal( - Empty::new() - .with_width(Self::BINDING_WIDTH) - .segment() - .with_fixed(true), - Text::new(description).segment(), - ) - .third3(), - } - } - - pub fn widget(self, list_state: &mut ListState<Infallible>) -> impl Widget<UiError> + '_ { - let binding_style = Self::binding_style(); - - let hint_text = Styled::new("jk/↓↑", binding_style) - .then_plain(" to scroll, ") - .then("esc", binding_style) - .then_plain(" to close"); - - let hint = Text::new(hint_text) - .padding() - .with_horizontal(1) - .float() - .with_horizontal(0.5) - .with_vertical(0.0); - - let mut list_builder = ListBuilder::new(); - for row in self.0 { - list_builder.add_unsel(Self::row_widget(row)); - } - - list_builder - .build(list_state) - .padding() - .with_horizontal(1) - .border() - .below(hint) - .background() - .float() - .with_center() - } - - pub fn empty(&mut self) { - self.0.push(Row::Empty); - } - - pub fn heading(&mut self, name: &str) { - self.0.push(Row::Heading(name.to_string())); - } - - pub fn binding(&mut self, binding: &str, description: &str) { - self.0 - .push(Row::Binding(binding.to_string(), description.to_string())); - } - - pub fn binding_ctd(&mut self, description: &str) { - self.0.push(Row::BindingContd(description.to_string())); - } -} diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index b401372..32f3941 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -2,22 +2,21 @@ use std::collections::{HashMap, HashSet}; use std::iter; use std::sync::{Arc, Mutex}; -use cove_config::{Config, RoomsSortOrder}; +use cove_config::{Config, Keys, RoomsSortOrder}; +use cove_input::InputEvent; use crossterm::style::Stylize; use euphoxide::api::SessionType; use euphoxide::bot::instance::{Event, ServerConfig}; use euphoxide::conn::{self, Joined}; -use parking_lot::FairMutex; use tokio::sync::mpsc; use toss::widgets::{BoxedAsync, EditorState, Empty, Join2, Text}; -use toss::{Style, Styled, Terminal, Widget, WidgetExt}; +use toss::{Style, Styled, Widget, WidgetExt}; use crate::euph; use crate::macros::logging_unwrap; use crate::vault::Vault; use super::euph::room::EuphRoom; -use super::input::{key, InputEvent, KeyBindingsList}; use super::widgets::{ListBuilder, ListState, Popup}; use super::{util, UiError, UiEvent}; @@ -357,6 +356,7 @@ impl Rooms { order: Order, ) { if euph_rooms.is_empty() { + // TODO Use configured key binding list_builder.add_unsel(Text::new(( "Press F1 for key bindings", Style::new().grey().italic(), @@ -409,198 +409,137 @@ impl Rooms { c.is_ascii_alphanumeric() || c == '_' } - fn list_showlist_key_bindings(bindings: &mut KeyBindingsList) { - bindings.heading("Rooms"); - util::list_list_key_bindings(bindings); - bindings.empty(); - bindings.binding("enter", "enter selected room"); - bindings.binding("c", "connect to selected room"); - bindings.binding("C", "connect to all rooms"); - bindings.binding("d", "disconnect from selected room"); - bindings.binding("D", "disconnect from all rooms"); - bindings.binding("a", "connect to all autojoin room"); - bindings.binding("A", "disconnect from all non-autojoin rooms"); - bindings.binding("n", "connect to new room"); - bindings.binding("X", "delete room"); - bindings.empty(); - bindings.binding("s", "change sort order"); - } - - fn handle_showlist_input_event(&mut self, event: &InputEvent) -> bool { - if util::handle_list_input_event(&mut self.list, event) { + fn handle_showlist_input_event(&mut self, event: &mut InputEvent<'_>, keys: &Keys) -> bool { + // Open room + if event.matches(&keys.general.confirm) { + if let Some(name) = self.list.selected() { + self.state = State::ShowRoom(name.clone()); + } return true; } - match event { - key!(Enter) => { - if let Some(name) = self.list.selected() { - self.state = State::ShowRoom(name.clone()); - } - return true; + // Move cursor and scroll + if util::handle_list_input_event(&mut self.list, event, keys) { + return true; + } + + // Room actions + if event.matches(&keys.rooms.action.connect) { + if let Some(name) = self.list.selected() { + self.connect_to_room(name.clone()); } - key!('c') => { - if let Some(name) = self.list.selected() { + return true; + } + if event.matches(&keys.rooms.action.connect_all) { + self.connect_to_all_rooms(); + return true; + } + if event.matches(&keys.rooms.action.disconnect) { + if let Some(name) = self.list.selected() { + self.disconnect_from_room(&name.clone()); + } + return true; + } + if event.matches(&keys.rooms.action.disconnect_all) { + self.disconnect_from_all_rooms(); + return true; + } + if event.matches(&keys.rooms.action.connect_autojoin) { + for (name, options) in &self.config.euph.rooms { + if options.autojoin { self.connect_to_room(name.clone()); } - return true; } - key!('C') => { - self.connect_to_all_rooms(); - return true; - } - key!('d') => { - if let Some(name) = self.list.selected() { - self.disconnect_from_room(&name.clone()); + return true; + } + if event.matches(&keys.rooms.action.disconnect_non_autojoin) { + for (name, room) in &mut self.euph_rooms { + let autojoin = self + .config + .euph + .rooms + .get(name) + .map(|r| r.autojoin) + .unwrap_or(false); + if !autojoin { + room.disconnect(); } - return true; } - key!('D') => { - self.disconnect_from_all_rooms(); - return true; + return true; + } + if event.matches(&keys.rooms.action.new) { + self.state = State::Connect(EditorState::new()); + return true; + } + if event.matches(&keys.rooms.action.delete) { + if let Some(name) = self.list.selected() { + self.state = State::Delete(name.clone(), EditorState::new()); } - key!('a') => { - for (name, options) in &self.config.euph.rooms { - if options.autojoin { - self.connect_to_room(name.clone()); - } - } - return true; - } - key!('A') => { - for (name, room) in &mut self.euph_rooms { - let autojoin = self - .config - .euph - .rooms - .get(name) - .map(|r| r.autojoin) - .unwrap_or(false); - if !autojoin { - room.disconnect(); - } - } - return true; - } - key!('n') => { - self.state = State::Connect(EditorState::new()); - return true; - } - key!('X') => { - if let Some(name) = self.list.selected() { - self.state = State::Delete(name.clone(), EditorState::new()); - } - return true; - } - key!('s') => { - self.order = match self.order { - Order::Alphabet => Order::Importance, - Order::Importance => Order::Alphabet, - }; - return true; - } - _ => {} + return true; + } + if event.matches(&keys.rooms.action.change_sort_order) { + self.order = match self.order { + Order::Alphabet => Order::Importance, + Order::Importance => Order::Alphabet, + }; + return true; } false } - pub async fn list_key_bindings(&self, bindings: &mut KeyBindingsList) { - match &self.state { - State::ShowList => Self::list_showlist_key_bindings(bindings), - State::ShowRoom(name) => { - // Key bindings for leaving the room are a part of the room's - // list_key_bindings function since they may be shadowed by the - // nick selector or message editor. - if let Some(room) = self.euph_rooms.get(name) { - room.list_key_bindings(bindings).await; - } else { - // There should always be a room here already but I don't - // really want to panic in case it is not. If I show a - // message like this, it'll hopefully be reported if - // somebody ever encounters it. - bindings.binding_ctd("oops, this text should never be visible") - } - } - State::Connect(_) => { - bindings.heading("Rooms"); - bindings.binding("esc", "abort"); - bindings.binding("enter", "connect to room"); - util::list_editor_key_bindings(bindings, Self::room_char); - } - State::Delete(_, _) => { - bindings.heading("Rooms"); - bindings.binding("esc", "abort"); - bindings.binding("enter", "delete room"); - util::list_editor_key_bindings(bindings, Self::room_char); - } - } - } - - pub async fn handle_input_event( - &mut self, - terminal: &mut Terminal, - crossterm_lock: &Arc<FairMutex<()>>, - event: &InputEvent, - ) -> bool { + pub async fn handle_input_event(&mut self, event: &mut InputEvent<'_>, keys: &Keys) -> bool { self.stabilize_rooms().await; match &mut self.state { State::ShowList => { - if self.handle_showlist_input_event(event) { + if self.handle_showlist_input_event(event, keys) { return true; } } State::ShowRoom(name) => { if let Some(room) = self.euph_rooms.get_mut(name) { - if room - .handle_input_event(terminal, crossterm_lock, event) - .await - { + if room.handle_input_event(event, keys).await { return true; } - - if let key!(Esc) = event { + if event.matches(&keys.general.abort) { self.state = State::ShowList; return true; } } } - State::Connect(ed) => match event { - key!(Esc) => { + State::Connect(editor) => { + if event.matches(&keys.general.abort) { self.state = State::ShowList; return true; } - key!(Enter) => { - let name = ed.text().to_string(); + if event.matches(&keys.general.confirm) { + let name = editor.text().to_string(); if !name.is_empty() { self.connect_to_room(name.clone()); self.state = State::ShowRoom(name); } return true; } - _ => { - if util::handle_editor_input_event(ed, terminal, event, Self::room_char) { - return true; - } + if util::handle_editor_input_event(editor, event, keys, Self::room_char) { + return true; } - }, - State::Delete(name, editor) => match event { - key!(Esc) => { + } + State::Delete(name, editor) => { + if event.matches(&keys.general.abort) { self.state = State::ShowList; return true; } - key!(Enter) if editor.text() == *name => { + if event.matches(&keys.general.confirm) { self.euph_rooms.remove(name); logging_unwrap!(self.vault.euph().room(name.clone()).delete().await); self.state = State::ShowList; return true; } - _ => { - if util::handle_editor_input_event(editor, terminal, event, Self::room_char) { - return true; - } + if util::handle_editor_input_event(editor, event, keys, Self::room_char) { + return true; } - }, + } } false diff --git a/cove/src/ui/util.rs b/cove/src/ui/util.rs index d80b704..fa434fe 100644 --- a/cove/src/ui/util.rs +++ b/cove/src/ui/util.rs @@ -1,191 +1,191 @@ -use std::io; -use std::sync::Arc; - -use parking_lot::FairMutex; +use cove_config::Keys; +use cove_input::InputEvent; +use crossterm::event::{KeyCode, KeyModifiers}; use toss::widgets::EditorState; -use toss::Terminal; -use super::input::{key, InputEvent, KeyBindingsList}; use super::widgets::ListState; -pub fn prompt( - terminal: &mut Terminal, - crossterm_lock: &Arc<FairMutex<()>>, - initial_text: &str, -) -> io::Result<String> { - let content = { - let _guard = crossterm_lock.lock(); - terminal.suspend().expect("could not suspend"); - let content = edit::edit(initial_text); - terminal.unsuspend().expect("could not unsuspend"); - content - }; - - content -} - ////////// // List // ////////// -pub fn list_list_key_bindings(bindings: &mut KeyBindingsList) { - bindings.binding("j/k, ↓/↑", "move cursor up/down"); - bindings.binding("g, home", "move cursor to top"); - bindings.binding("G, end", "move cursor to bottom"); - bindings.binding("ctrl+y/e", "scroll up/down"); -} - -pub fn handle_list_input_event<Id: Clone>(list: &mut ListState<Id>, event: &InputEvent) -> bool { - match event { - key!('k') | key!(Up) => list.move_cursor_up(), - key!('j') | key!(Down) => list.move_cursor_down(), - key!('g') | key!(Home) => list.move_cursor_to_top(), - key!('G') | key!(End) => list.move_cursor_to_bottom(), - key!(Ctrl + 'y') => list.scroll_up(1), - key!(Ctrl + 'e') => list.scroll_down(1), - _ => return false, +pub fn handle_list_input_event<Id: Clone>( + list: &mut ListState<Id>, + event: &InputEvent<'_>, + keys: &Keys, +) -> bool { + // Cursor movement + if event.matches(&keys.cursor.up) { + list.move_cursor_up(); + return true; + } + if event.matches(&keys.cursor.down) { + list.move_cursor_down(); + return true; + } + if event.matches(&keys.cursor.to_top) { + list.move_cursor_to_top(); + return true; + } + if event.matches(&keys.cursor.to_bottom) { + list.move_cursor_to_bottom(); + return true; } - true + // Scrolling + if event.matches(&keys.scroll.up_line) { + list.scroll_up(1); + return true; + } + if event.matches(&keys.scroll.down_line) { + list.scroll_down(1); + return true; + } + if event.matches(&keys.scroll.up_half) { + list.scroll_up_half(); + return true; + } + if event.matches(&keys.scroll.down_half) { + list.scroll_down_half(); + return true; + } + if event.matches(&keys.scroll.up_full) { + list.scroll_up_full(); + return true; + } + if event.matches(&keys.scroll.down_full) { + list.scroll_down_full(); + return true; + } + if event.matches(&keys.scroll.center_cursor) { + list.center_cursor(); + return true; + } + + false } //////////// // Editor // //////////// -fn list_editor_editing_key_bindings( - bindings: &mut KeyBindingsList, - char_filter: impl Fn(char) -> bool, -) { - if char_filter('\n') { - bindings.binding("enter+<any modifier>", "insert newline"); - } - - bindings.binding("ctrl+h, backspace", "delete before cursor"); - bindings.binding("ctrl+d, delete", "delete after cursor"); - bindings.binding("ctrl+l", "clear editor contents"); -} - -fn list_editor_cursor_movement_key_bindings(bindings: &mut KeyBindingsList) { - bindings.binding("ctrl+b, ←", "move cursor left"); - bindings.binding("ctrl+f, →", "move cursor right"); - bindings.binding("alt+b, ctrl+←", "move cursor left a word"); - bindings.binding("alt+f, ctrl+→", "move cursor right a word"); - bindings.binding("ctrl+a, home", "move cursor to start of line"); - bindings.binding("ctrl+e, end", "move cursor to end of line"); - bindings.binding("↑/↓", "move cursor up/down"); -} - -pub fn list_editor_key_bindings( - bindings: &mut KeyBindingsList, - char_filter: impl Fn(char) -> bool, -) { - list_editor_editing_key_bindings(bindings, char_filter); - bindings.empty(); - list_editor_cursor_movement_key_bindings(bindings); -} - -pub fn handle_editor_input_event( - editor: &mut EditorState, - terminal: &mut Terminal, - event: &InputEvent, - char_filter: impl Fn(char) -> bool, -) -> bool { - match event { - // Enter with *any* modifier pressed - if ctrl and shift don't - // work, maybe alt does - key!(Enter) => return false, - InputEvent::Key(crate::ui::input::KeyEvent { - code: crossterm::event::KeyCode::Enter, - .. - }) if char_filter('\n') => editor.insert_char(terminal.widthdb(), '\n'), - - // Editing - key!(Char ch) if char_filter(*ch) => editor.insert_char(terminal.widthdb(), *ch), - key!(Paste str) => { - // It seems that when pasting, '\n' are converted into '\r' for some - // reason. I don't really know why, or at what point this happens. - // Vim converts any '\r' pasted via the terminal into '\n', so I - // decided to mirror that behaviour. - let str = str.replace('\r', "\n"); - if str.chars().all(char_filter) { - editor.insert_str(terminal.widthdb(), &str); - } else { - return false; - } - } - key!(Ctrl + 'h') | key!(Backspace) => editor.backspace(terminal.widthdb()), - key!(Ctrl + 'd') | key!(Delete) => editor.delete(), - key!(Ctrl + 'l') => editor.clear(), - // TODO Key bindings to delete words - - // Cursor movement - key!(Ctrl + 'b') | key!(Left) => editor.move_cursor_left(terminal.widthdb()), - key!(Ctrl + 'f') | key!(Right) => editor.move_cursor_right(terminal.widthdb()), - key!(Alt + 'b') | key!(Ctrl + Left) => editor.move_cursor_left_a_word(terminal.widthdb()), - key!(Alt + 'f') | key!(Ctrl + Right) => editor.move_cursor_right_a_word(terminal.widthdb()), - key!(Ctrl + 'a') | key!(Home) => editor.move_cursor_to_start_of_line(terminal.widthdb()), - key!(Ctrl + 'e') | key!(End) => editor.move_cursor_to_end_of_line(terminal.widthdb()), - key!(Up) => editor.move_cursor_up(terminal.widthdb()), - key!(Down) => editor.move_cursor_down(terminal.widthdb()), - - _ => return false, - } - - true -} - fn edit_externally( editor: &mut EditorState, - terminal: &mut Terminal, - crossterm_lock: &Arc<FairMutex<()>>, -) -> io::Result<()> { - let text = prompt(terminal, crossterm_lock, editor.text())?; + event: &mut InputEvent<'_>, + char_filter: impl Fn(char) -> bool, +) { + let Ok(text) = event.prompt(editor.text()) else { + // Something went wrong during editing, let's abort the edit. + return; + }; if text.trim().is_empty() { // The user likely wanted to abort the edit and has deleted the // entire text (bar whitespace left over by some editors). - return Ok(()); + return; } - if let Some(text) = text.strip_suffix('\n') { - // Some editors like vim add a trailing newline that would look out of - // place in cove's editors. To intentionally add a trailing newline, - // simply add two in-editor. - editor.set_text(terminal.widthdb(), text.to_string()); - } else { - editor.set_text(terminal.widthdb(), text); - } + let text = text + .strip_suffix('\n') + .unwrap_or(&text) + .chars() + .filter(|c| char_filter(*c)) + .collect::<String>(); - Ok(()) + editor.set_text(event.widthdb(), text); } -pub fn list_editor_key_bindings_allowing_external_editing( - bindings: &mut KeyBindingsList, - char_filter: impl Fn(char) -> bool, -) { - list_editor_editing_key_bindings(bindings, char_filter); - bindings.binding("ctrl+x", "edit in external editor"); - bindings.empty(); - list_editor_cursor_movement_key_bindings(bindings); +fn char_modifier(modifiers: KeyModifiers) -> bool { + modifiers == KeyModifiers::NONE || modifiers == KeyModifiers::SHIFT } -pub fn handle_editor_input_event_allowing_external_editing( +pub fn handle_editor_input_event( editor: &mut EditorState, - terminal: &mut Terminal, - crossterm_lock: &Arc<FairMutex<()>>, - event: &InputEvent, + event: &mut InputEvent<'_>, + keys: &Keys, char_filter: impl Fn(char) -> bool, -) -> io::Result<bool> { - if let key!(Ctrl + 'x') = event { - edit_externally(editor, terminal, crossterm_lock)?; - Ok(true) - } else { - Ok(handle_editor_input_event( - editor, - terminal, - event, - char_filter, - )) +) -> bool { + // Cursor movement + if event.matches(&keys.editor.cursor.left) { + editor.move_cursor_left(event.widthdb()); + return true; } + if event.matches(&keys.editor.cursor.right) { + editor.move_cursor_right(event.widthdb()); + return true; + } + if event.matches(&keys.editor.cursor.left_word) { + editor.move_cursor_left_a_word(event.widthdb()); + return true; + } + if event.matches(&keys.editor.cursor.right_word) { + editor.move_cursor_right_a_word(event.widthdb()); + return true; + } + if event.matches(&keys.editor.cursor.start) { + editor.move_cursor_to_start_of_line(event.widthdb()); + return true; + } + if event.matches(&keys.editor.cursor.end) { + editor.move_cursor_to_end_of_line(event.widthdb()); + return true; + } + if event.matches(&keys.editor.cursor.up) { + editor.move_cursor_up(event.widthdb()); + return true; + } + if event.matches(&keys.editor.cursor.down) { + editor.move_cursor_down(event.widthdb()); + return true; + } + + // Editing + if event.matches(&keys.editor.action.backspace) { + editor.backspace(event.widthdb()); + return true; + } + if event.matches(&keys.editor.action.delete) { + editor.delete(); + return true; + } + if event.matches(&keys.editor.action.clear) { + editor.clear(); + return true; + } + if event.matches(&keys.editor.action.external) { + edit_externally(editor, event, char_filter); + return true; + } + + // Inserting individual characters + if let Some(key_event) = event.key_event() { + match key_event.code { + KeyCode::Enter if char_filter('\n') => { + editor.insert_char(event.widthdb(), '\n'); + return true; + } + KeyCode::Char(c) if char_modifier(key_event.modifiers) && char_filter(c) => { + editor.insert_char(event.widthdb(), c); + return true; + } + _ => {} + } + } + + // Pasting text + if let Some(text) = event.paste_event() { + // It seems that when pasting, '\n' are converted into '\r' for some + // reason. I don't really know why, or at what point this happens. Vim + // converts any '\r' pasted via the terminal into '\n', so I decided to + // mirror that behaviour. + let text = text + .chars() + .map(|c| if c == '\r' { '\n' } else { c }) + .filter(|c| char_filter(*c)) + .collect::<String>(); + editor.insert_str(event.widthdb(), &text); + return true; + } + + false } diff --git a/cove/src/ui/widgets/list.rs b/cove/src/ui/widgets/list.rs index 88d08bd..bb27540 100644 --- a/cove/src/ui/widgets/list.rs +++ b/cove/src/ui/widgets/list.rs @@ -170,6 +170,22 @@ impl<Id: Clone> ListState<Id> { self.scroll_to(self.offset.saturating_add(lines)); } + pub fn scroll_up_half(&mut self) { + self.scroll_up((self.last_height / 2).into()); + } + + pub fn scroll_down_half(&mut self) { + self.scroll_down((self.last_height / 2).into()); + } + + pub fn scroll_up_full(&mut self) { + self.scroll_up(self.last_height.saturating_sub(1).into()); + } + + pub fn scroll_down_full(&mut self) { + self.scroll_down(self.last_height.saturating_sub(1).into()); + } + /// Scroll so that the cursor is in the center of the widget, or at least as /// close as possible. pub fn center_cursor(&mut self) { From 591807dd55231971ef44f8027798b6f428a4f540 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 00:52:40 +0200 Subject: [PATCH 134/266] Fix handling of shift for KeyCode::Char --- cove-config/src/doc.rs | 12 ++++++------ cove-input/src/keys.rs | 23 ++++++++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/cove-config/src/doc.rs b/cove-config/src/doc.rs index ff47443..3c34224 100644 --- a/cove-config/src/doc.rs +++ b/cove-config/src/doc.rs @@ -28,12 +28,6 @@ Examples of key bindings: - `"ctrl+alt+f3"` - `["enter", "any+enter"]` (matches `enter` regardless of modifiers) -Available modifiers: -- `ctrl` -- `shift` -- `alt` -- `any` (matches as long as at least one modifier is pressed) - Available main keys: - Any single character that can be typed - `enter`, `esc` @@ -43,6 +37,12 @@ Available main keys: - `home`, `end`, `pageup`, `pagedown` - `f1`, `f2`, ... +Available modifiers: +- `shift` (must not be used with single characters) +- `ctrl` +- `alt` +- `any` (matches as long as at least one modifier is pressed) + ## Config options "#; diff --git a/cove-input/src/keys.rs b/cove-input/src/keys.rs index 07eff8d..3fe74c9 100644 --- a/cove-input/src/keys.rs +++ b/cove-input/src/keys.rs @@ -85,10 +85,14 @@ impl KeyPress { } } - fn parse_modifier(&mut self, modifier: &str) -> Result<(), ParseKeysError> { + fn parse_modifier( + &mut self, + modifier: &str, + shift_allowed: bool, + ) -> Result<(), ParseKeysError> { match modifier { m if self.any => return Err(ParseKeysError::ConflictingModifier(m.to_string())), - "shift" if !self.shift => self.shift = true, + "shift" if shift_allowed && !self.shift => self.shift = true, "ctrl" if !self.ctrl => self.ctrl = true, "alt" if !self.alt => self.alt = true, "any" if !self.shift && !self.ctrl && !self.alt => self.any = true, @@ -109,10 +113,14 @@ impl KeyPress { return true; } - let shift = event.modifiers.contains(KeyModifiers::SHIFT); - let ctrl = event.modifiers.contains(KeyModifiers::CONTROL); - let alt = event.modifiers.contains(KeyModifiers::ALT); - self.shift == shift && self.ctrl == ctrl && self.alt == alt + let ctrl = event.modifiers.contains(KeyModifiers::CONTROL) == self.ctrl; + let alt = event.modifiers.contains(KeyModifiers::ALT) == self.alt; + if matches!(self.code, KeyCode::Char(_)) { + ctrl && alt + } else { + let shift = event.modifiers.contains(KeyModifiers::SHIFT) == self.shift; + shift && ctrl && alt + } } } @@ -124,8 +132,9 @@ impl FromStr for KeyPress { let code = parts.next_back().ok_or(ParseKeysError::NoKeyCode)?; let mut keys = KeyPress::parse_key_code(code)?; + let shift_allowed = !matches!(keys.code, KeyCode::Char(_)); for modifier in parts { - keys.parse_modifier(modifier)?; + keys.parse_modifier(modifier, shift_allowed)?; } Ok(keys) From 14e17730dce9451a92c11a881a2889d4a0923916 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 00:55:22 +0200 Subject: [PATCH 135/266] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d476ae7..c26f824 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Procedure when bumping the version number: ### Added - `help-config` CLI command +- `keys.*` config options - `measure_widths` config option ### Changed From c04f6a8cb46312a260fffcd0f52f9865565ad14c Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 01:04:52 +0200 Subject: [PATCH 136/266] Scroll key binding list with cursor keys --- cove/src/ui.rs | 2 +- cove/src/ui/key_bindings.rs | 31 +++++++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/cove/src/ui.rs b/cove/src/ui.rs index 7ac483e..fc58bf4 100644 --- a/cove/src/ui.rs +++ b/cove/src/ui.rs @@ -252,7 +252,7 @@ impl Ui { self.key_bindings_visible = false; return EventHandleResult::Redraw; } - if util::handle_list_input_event(&mut self.key_bindings_list, &event, keys) { + if key_bindings::handle_input_event(&mut self.key_bindings_list, &mut event, keys) { return EventHandleResult::Redraw; } // ... and does not let anything below the popup receive events diff --git a/cove/src/ui/key_bindings.rs b/cove/src/ui/key_bindings.rs index c2a1249..40cc2aa 100644 --- a/cove/src/ui/key_bindings.rs +++ b/cove/src/ui/key_bindings.rs @@ -2,14 +2,14 @@ use std::convert::Infallible; -use cove_config::Config; -use cove_input::{KeyBinding, KeyGroup}; +use cove_config::{Config, Keys}; +use cove_input::{InputEvent, KeyBinding, KeyGroup}; use crossterm::style::Stylize; use toss::widgets::{Either2, Join2, Padding, Text}; use toss::{Style, Styled, Widget, WidgetExt}; use super::widgets::{ListBuilder, ListState, Popup}; -use super::UiError; +use super::{util, UiError}; type Line = Either2<Text, Join2<Padding<Text>, Text>>; type Builder = ListBuilder<'static, Infallible, Line>; @@ -19,7 +19,7 @@ fn render_empty(builder: &mut Builder) { } fn render_title(builder: &mut Builder, title: &str) { - let style = Style::new().bold(); + let style = Style::new().bold().magenta(); builder.add_unsel(Text::new(Styled::new(title, style)).first2()); } @@ -85,3 +85,26 @@ pub fn widget<'a>( Popup::new(list_builder.build(list), "Key bindings") } + +pub fn handle_input_event( + list: &mut ListState<Infallible>, + event: &mut InputEvent<'_>, + keys: &Keys, +) -> bool { + // To make scrolling with the mouse wheel work as expected + if event.matches(&keys.cursor.up) { + list.scroll_up(1); + return true; + } + if event.matches(&keys.cursor.down) { + list.scroll_down(1); + return true; + } + + // List movement must come later, or it shadows the cursor movement keys + if util::handle_list_input_event(list, event, keys) { + return true; + } + + false +} From 98cb1f2cbc71ad3666000e24865ff0b2a20ca988 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 01:25:20 +0200 Subject: [PATCH 137/266] Fix hidden editor sometimes crashing --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c59459d..ab7cc05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1396,7 +1396,7 @@ dependencies = [ [[package]] name = "toss" version = "0.1.0" -source = "git+https://github.com/Garmelon/toss.git?rev=8bfb4b2dc345c3e0ffdb89bdb34f2996487a35cb#8bfb4b2dc345c3e0ffdb89bdb34f2996487a35cb" +source = "git+https://github.com/Garmelon/toss.git?rev=f005ec10fe1b6034c50f3a4ef24dd44d3e6d5593#f005ec10fe1b6034c50f3a4ef24dd44d3e6d5593" dependencies = [ "async-trait", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index df60d72..9fe5f0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ thiserror = "1.0.40" [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" -rev = "8bfb4b2dc345c3e0ffdb89bdb34f2996487a35cb" +rev = "f005ec10fe1b6034c50f3a4ef24dd44d3e6d5593" [profile.dev.package."*"] opt-level = 3 From f3efff68f5de217aac403b5c4deb43ca36d8b70d Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 01:29:44 +0200 Subject: [PATCH 138/266] Allow closing log with abort key --- cove/src/ui.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/cove/src/ui.rs b/cove/src/ui.rs index fc58bf4..790d0c3 100644 --- a/cove/src/ui.rs +++ b/cove/src/ui.rs @@ -248,7 +248,7 @@ impl Ui { // Key bindings list overrides any other bindings if visible if self.key_bindings_visible { - if event.matches(&keys.general.abort) { + if event.matches(&keys.general.abort) || event.matches(&keys.general.help) { self.key_bindings_visible = false; return EventHandleResult::Redraw; } @@ -259,26 +259,28 @@ impl Ui { return EventHandleResult::Continue; } - // Other general bindings that override any other bindings if event.matches(&keys.general.help) { self.key_bindings_visible = true; return EventHandleResult::Redraw; } - if event.matches(&keys.general.log) { - self.mode = match self.mode { - Mode::Main => Mode::Log, - Mode::Log => Mode::Main, - }; - return EventHandleResult::Redraw; - } match self.mode { Mode::Main => { + if event.matches(&keys.general.log) { + self.mode = Mode::Log; + return EventHandleResult::Redraw; + } + if self.rooms.handle_input_event(&mut event, keys).await { return EventHandleResult::Redraw; } } Mode::Log => { + if event.matches(&keys.general.abort) || event.matches(&keys.general.log) { + self.mode = Mode::Main; + return EventHandleResult::Redraw; + } + let reaction = self .log_chat .handle_input_event(&mut event, keys, false) From 03c7fb567cc2115eb21ece5b5544bb333c97e247 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 01:50:33 +0200 Subject: [PATCH 139/266] Auto-derive Default for KeyGroups --- cove-config/src/keys.rs | 123 ------------------------------------ cove-macro/src/document.rs | 31 ++------- cove-macro/src/key_group.rs | 21 +++++- cove-macro/src/util.rs | 39 +++++++++++- 4 files changed, 63 insertions(+), 151 deletions(-) diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index 0eed8a7..616aa2e 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -132,19 +132,6 @@ pub struct General { pub log: KeyBinding, } -impl Default for General { - fn default() -> Self { - Self { - exit: default::general::exit(), - abort: default::general::abort(), - confirm: default::general::confirm(), - focus: default::general::focus(), - help: default::general::help(), - log: default::general::log(), - } - } -} - #[derive(Debug, Deserialize, Document, KeyGroup)] pub struct Scroll { /// Scroll up one line. @@ -170,20 +157,6 @@ pub struct Scroll { pub center_cursor: KeyBinding, } -impl Default for Scroll { - fn default() -> Self { - Self { - up_line: default::scroll::up_line(), - down_line: default::scroll::down_line(), - up_half: default::scroll::up_half(), - down_half: default::scroll::down_half(), - up_full: default::scroll::up_full(), - down_full: default::scroll::down_full(), - center_cursor: default::scroll::center_cursor(), - } - } -} - #[derive(Debug, Deserialize, Document, KeyGroup)] pub struct Cursor { /// Move up. @@ -200,17 +173,6 @@ pub struct Cursor { pub to_bottom: KeyBinding, } -impl Default for Cursor { - fn default() -> Self { - Self { - up: default::cursor::up(), - down: default::cursor::down(), - to_top: default::cursor::to_top(), - to_bottom: default::cursor::to_bottom(), - } - } -} - #[derive(Debug, Deserialize, Document, KeyGroup)] pub struct EditorCursor { /// Move left. @@ -239,21 +201,6 @@ pub struct EditorCursor { pub down: KeyBinding, } -impl Default for EditorCursor { - fn default() -> Self { - Self { - left: default::editor_cursor::left(), - right: default::editor_cursor::right(), - left_word: default::editor_cursor::left_word(), - right_word: default::editor_cursor::right_word(), - start: default::editor_cursor::start(), - end: default::editor_cursor::end(), - up: default::editor_cursor::up(), - down: default::editor_cursor::down(), - } - } -} - #[derive(Debug, Deserialize, Document, KeyGroup)] pub struct EditorAction { /// Delete before cursor. @@ -270,17 +217,6 @@ pub struct EditorAction { pub external: KeyBinding, } -impl Default for EditorAction { - fn default() -> Self { - Self { - backspace: default::editor_action::backspace(), - delete: default::editor_action::delete(), - clear: default::editor_action::clear(), - external: default::editor_action::external(), - } - } -} - #[derive(Debug, Default, Deserialize, Document)] pub struct Editor { #[serde(default)] @@ -323,22 +259,6 @@ pub struct RoomsAction { pub change_sort_order: KeyBinding, } -impl Default for RoomsAction { - fn default() -> Self { - Self { - connect: default::rooms_action::connect(), - connect_all: default::rooms_action::connect_all(), - disconnect: default::rooms_action::disconnect(), - disconnect_all: default::rooms_action::disconnect_all(), - connect_autojoin: default::rooms_action::connect_autojoin(), - disconnect_non_autojoin: default::rooms_action::disconnect_non_autojoin(), - new: default::rooms_action::new(), - delete: default::rooms_action::delete(), - change_sort_order: default::rooms_action::change_sort_order(), - } - } -} - #[derive(Debug, Default, Deserialize, Document)] pub struct Rooms { #[serde(default)] @@ -365,18 +285,6 @@ pub struct RoomAction { pub present: KeyBinding, } -impl Default for RoomAction { - fn default() -> Self { - Self { - authenticate: default::room_action::authenticate(), - account: default::room_action::account(), - nick: default::room_action::nick(), - more_messages: default::room_action::more_messages(), - present: default::room_action::present(), - } - } -} - #[derive(Debug, Default, Deserialize, Document)] pub struct Room { #[serde(default)] @@ -413,21 +321,6 @@ pub struct TreeCursor { // TODO Bindings inspired by vim's ()/[]/{} bindings? } -impl Default for TreeCursor { - fn default() -> Self { - Self { - to_above_sibling: default::tree_cursor::to_above_sibling(), - to_below_sibling: default::tree_cursor::to_below_sibling(), - to_parent: default::tree_cursor::to_parent(), - to_root: default::tree_cursor::to_root(), - to_older_message: default::tree_cursor::to_older_message(), - to_newer_message: default::tree_cursor::to_newer_message(), - to_older_unseen_message: default::tree_cursor::to_older_unseen_message(), - to_newer_unseen_message: default::tree_cursor::to_newer_unseen_message(), - } - } -} - // TODO Split up in "message", "nicklist", "room"? #[derive(Debug, Deserialize, Document, KeyGroup)] pub struct TreeAction { @@ -460,22 +353,6 @@ pub struct TreeAction { pub links: KeyBinding, } -impl Default for TreeAction { - fn default() -> Self { - Self { - reply: default::tree_action::reply(), - reply_alternate: default::tree_action::reply_alternate(), - new_thread: default::tree_action::new_thread(), - fold_tree: default::tree_action::fold_tree(), - toggle_seen: default::tree_action::toggle_seen(), - mark_visible_seen: default::tree_action::mark_visible_seen(), - mark_older_seen: default::tree_action::mark_older_seen(), - inspect: default::tree_action::info(), - links: default::tree_action::links(), - } - } -} - #[derive(Debug, Default, Deserialize, Document)] pub struct Tree { #[serde(default)] diff --git a/cove-macro/src/document.rs b/cove-macro/src/document.rs index cf75553..ddf13c8 100644 --- a/cove-macro/src/document.rs +++ b/cove-macro/src/document.rs @@ -1,14 +1,9 @@ use proc_macro2::TokenStream; use quote::quote; use syn::spanned::Spanned; -use syn::{Data, DataEnum, DataStruct, DeriveInput, ExprPath, Field, Ident, LitStr, Type}; +use syn::{Data, DataEnum, DataStruct, DeriveInput, Field, Ident, LitStr}; -use crate::util; - -enum SerdeDefault { - Default(Type), - Path(ExprPath), -} +use crate::util::{self, SerdeDefault}; #[derive(Default)] struct FieldInfo { @@ -53,17 +48,7 @@ impl FieldInfo { } // Find `#[serde(default)]` or `#[serde(default = "bla")]`. - for arg in util::attribute_arguments(field, "serde")? { - if arg.path.is_ident("default") { - if let Some(value) = arg.value { - if let Some(path) = util::into_litstr(value) { - self.serde_default = Some(SerdeDefault::Path(path.parse()?)); - } - } else { - self.serde_default = Some(SerdeDefault::Default(field.ty.clone())); - } - } - } + self.serde_default = util::serde_default(field)?; Ok(()) } @@ -102,13 +87,9 @@ fn from_struct(ident: Ident, data: DataStruct) -> syn::Result<TokenStream> { doc.value_info.default = Some(#default.to_string()); }); } else if let Some(serde_default) = info.serde_default { - setters.push(match serde_default { - SerdeDefault::Default(ty) => quote! { - doc.value_info.default = Some(crate::doc::toml_value_as_markdown(&<#ty as Default>::default())); - }, - SerdeDefault::Path(path) => quote! { - doc.value_info.default = Some(crate::doc::toml_value_as_markdown(&#path())); - }, + let value = serde_default.value(); + setters.push(quote! { + doc.value_info.default = Some(crate::doc::toml_value_as_markdown(&#value)); }); } diff --git a/cove-macro/src/key_group.rs b/cove-macro/src/key_group.rs index 757e3f0..254f660 100644 --- a/cove-macro/src/key_group.rs +++ b/cove-macro/src/key_group.rs @@ -3,7 +3,7 @@ use quote::quote; use syn::spanned::Spanned; use syn::{Data, DeriveInput}; -use crate::util; +use crate::util::{self, bail}; fn decapitalize(s: &str) -> String { let mut chars = s.chars(); @@ -20,15 +20,26 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { }; let mut bindings = vec![]; + let mut defaults = vec![]; for field in &data.fields { if let Some(field_ident) = &field.ident { let docstring = util::docstring(field)?; let description = decapitalize(&docstring); let description = description.strip_suffix('.').unwrap_or(&description); + let default = util::serde_default(field)?; + let Some(default) = default else { + return bail(field_ident.span(), "must have serde default"); + }; + let default_value = default.value(); + bindings.push(quote! { (&self.#field_ident, #description) }); + + defaults.push(quote! { + #field_ident: #default_value, + }); } } @@ -41,5 +52,13 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { ] } } + + impl Default for #ident { + fn default() -> Self { + Self { + #( #defaults )* + } + } + } }) } diff --git a/cove-macro/src/util.rs b/cove-macro/src/util.rs index cf21159..e56174a 100644 --- a/cove-macro/src/util.rs +++ b/cove-macro/src/util.rs @@ -1,7 +1,8 @@ -use proc_macro2::Span; +use proc_macro2::{Span, TokenStream}; +use quote::quote; use syn::parse::Parse; use syn::punctuated::Punctuated; -use syn::{Expr, ExprLit, Field, Lit, LitStr, Path, Token}; +use syn::{Expr, ExprLit, ExprPath, Field, Lit, LitStr, Path, Token, Type}; pub fn bail<T>(span: Span, message: &str) -> syn::Result<T> { Err(syn::Error::new(span, message)) @@ -80,3 +81,37 @@ pub fn attribute_arguments(field: &Field, path: &str) -> syn::Result<Vec<Attribu Ok(attrs) } + +pub enum SerdeDefault { + Default(Type), + Path(ExprPath), +} + +impl SerdeDefault { + pub fn value(&self) -> TokenStream { + match self { + Self::Default(ty) => quote! { + <#ty as Default>::default() + }, + Self::Path(path) => quote! { + #path() + }, + } + } +} + +/// Find `#[serde(default)]` or `#[serde(default = "bla")]`. +pub fn serde_default(field: &Field) -> syn::Result<Option<SerdeDefault>> { + for arg in attribute_arguments(field, "serde")? { + if arg.path.is_ident("default") { + if let Some(value) = arg.value { + if let Some(path) = into_litstr(value) { + return Ok(Some(SerdeDefault::Path(path.parse()?))); + } + } else { + return Ok(Some(SerdeDefault::Default(field.ty.clone()))); + } + } + } + Ok(None) +} From b6fdc3b7e836a5aa92318ce1d0b9f454aec5c09a Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 15:04:24 +0200 Subject: [PATCH 140/266] Parse and show space key as "space" --- cove-config/src/doc.rs | 5 ++--- cove-input/src/keys.rs | 50 +++++++++++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/cove-config/src/doc.rs b/cove-config/src/doc.rs index 3c34224..062a5bd 100644 --- a/cove-config/src/doc.rs +++ b/cove-config/src/doc.rs @@ -21,7 +21,7 @@ first, followed by the main key. They are separated by the `+` character and Examples of key bindings: - `"ctrl+c"` - `"X"` (not `"shift+x"`) -- `" "` (space bar) +- `"space"` or `" "` (both space bar) - `["g", "home"]` - `["K", "ctrl+up"]` - `["f1", "?"]` @@ -30,8 +30,7 @@ Examples of key bindings: Available main keys: - Any single character that can be typed -- `enter`, `esc` -- `tab`, `backtab` +- `esc`, `enter`, `space`, `tab`, `backtab` - `backspace`, `delete`, `insert` - `left`, `right`, `up`, `down` - `home`, `end`, `pageup`, `pagedown` diff --git a/cove-input/src/keys.rs b/cove-input/src/keys.rs index 3fe74c9..4ede713 100644 --- a/cove-input/src/keys.rs +++ b/cove-input/src/keys.rs @@ -21,6 +21,14 @@ pub enum ParseKeysError { ConflictingModifier(String), } +fn conflicts_with_shift(code: KeyCode) -> bool { + match code { + KeyCode::Char(' ') => false, + KeyCode::Char(_) => true, + _ => false, + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct KeyPress { pub code: KeyCode, @@ -33,23 +41,29 @@ pub struct KeyPress { impl KeyPress { fn parse_key_code(code: &str) -> Result<Self, ParseKeysError> { let code = match code { - "backspace" => KeyCode::Backspace, + "esc" => KeyCode::Esc, "enter" => KeyCode::Enter, + "space" => KeyCode::Char(' '), + "tab" => KeyCode::Tab, + "backtab" => KeyCode::BackTab, + + "backspace" => KeyCode::Backspace, + "delete" => KeyCode::Delete, + "insert" => KeyCode::Insert, + "left" => KeyCode::Left, "right" => KeyCode::Right, "up" => KeyCode::Up, "down" => KeyCode::Down, + "home" => KeyCode::Home, "end" => KeyCode::End, "pageup" => KeyCode::PageUp, "pagedown" => KeyCode::PageDown, - "tab" => KeyCode::Tab, - "backtab" => KeyCode::BackTab, - "delete" => KeyCode::Delete, - "insert" => KeyCode::Insert, - "esc" => KeyCode::Esc, + c if c.chars().count() == 1 => KeyCode::Char(c.chars().next().unwrap()), c if c.starts_with('f') => KeyCode::F(c.strip_prefix('f').unwrap().parse()?), + "" => return Err(ParseKeysError::NoKeyCode), c => return Err(ParseKeysError::UnknownKeyCode(c.to_string())), }; @@ -64,23 +78,29 @@ impl KeyPress { fn display_key_code(code: KeyCode) -> String { match code { - KeyCode::Backspace => "backspace".to_string(), + KeyCode::Esc => "esc".to_string(), KeyCode::Enter => "enter".to_string(), + KeyCode::Char(' ') => "space".to_string(), + KeyCode::Tab => "tab".to_string(), + KeyCode::BackTab => "backtab".to_string(), + + KeyCode::Backspace => "backspace".to_string(), + KeyCode::Delete => "delete".to_string(), + KeyCode::Insert => "insert".to_string(), + KeyCode::Left => "left".to_string(), KeyCode::Right => "right".to_string(), KeyCode::Up => "up".to_string(), KeyCode::Down => "down".to_string(), + KeyCode::Home => "home".to_string(), KeyCode::End => "end".to_string(), KeyCode::PageUp => "pageup".to_string(), KeyCode::PageDown => "pagedown".to_string(), - KeyCode::Tab => "tab".to_string(), - KeyCode::BackTab => "backtab".to_string(), - KeyCode::Delete => "delete".to_string(), - KeyCode::Insert => "insert".to_string(), - KeyCode::Esc => "esc".to_string(), - KeyCode::F(n) => format!("f{n}"), + KeyCode::Char(c) => c.to_string(), + KeyCode::F(n) => format!("f{n}"), + _ => "unknown".to_string(), } } @@ -115,7 +135,7 @@ impl KeyPress { let ctrl = event.modifiers.contains(KeyModifiers::CONTROL) == self.ctrl; let alt = event.modifiers.contains(KeyModifiers::ALT) == self.alt; - if matches!(self.code, KeyCode::Char(_)) { + if conflicts_with_shift(self.code) { ctrl && alt } else { let shift = event.modifiers.contains(KeyModifiers::SHIFT) == self.shift; @@ -132,7 +152,7 @@ impl FromStr for KeyPress { let code = parts.next_back().ok_or(ParseKeysError::NoKeyCode)?; let mut keys = KeyPress::parse_key_code(code)?; - let shift_allowed = !matches!(keys.code, KeyCode::Char(_)); + let shift_allowed = !conflicts_with_shift(keys.code); for modifier in parts { keys.parse_modifier(modifier, shift_allowed)?; } From dd427b7792ef2b971b95c7c40780beec7e4dd7b6 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 15:07:16 +0200 Subject: [PATCH 141/266] Show unbound bindings as "unbound" --- cove/src/ui/key_bindings.rs | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/cove/src/ui/key_bindings.rs b/cove/src/ui/key_bindings.rs index 40cc2aa..a4de6da 100644 --- a/cove/src/ui/key_bindings.rs +++ b/cove/src/ui/key_bindings.rs @@ -14,6 +14,24 @@ use super::{util, UiError}; type Line = Either2<Text, Join2<Padding<Text>, Text>>; type Builder = ListBuilder<'static, Infallible, Line>; +pub fn format_binding(binding: &KeyBinding) -> Styled { + let style = Style::new().cyan(); + let mut keys = Styled::default(); + + for key in binding.keys() { + if !keys.text().is_empty() { + keys = keys.then_plain(", "); + } + keys = keys.then(key.to_string(), style); + } + + if keys.text().is_empty() { + keys = keys.then("unbound", style); + } + + keys +} + fn render_empty(builder: &mut Builder) { builder.add_unsel(Text::new("").first2()); } @@ -24,15 +42,6 @@ fn render_title(builder: &mut Builder, title: &str) { } fn render_binding(builder: &mut Builder, binding: &KeyBinding, description: &str) { - let style = Style::new().cyan(); - let mut keys = Styled::default(); - for key in binding.keys() { - if !keys.text().is_empty() { - keys = keys.then_plain(", "); - } - keys = keys.then(key.to_string(), style); - } - builder.add_unsel( Join2::horizontal( Text::new(description) @@ -41,7 +50,10 @@ fn render_binding(builder: &mut Builder, binding: &KeyBinding, description: &str .with_right(2) .with_stretch(true) .segment(), - Text::new(keys).with_wrap(false).segment().with_fixed(true), + Text::new(format_binding(binding)) + .with_wrap(false) + .segment() + .with_fixed(true), ) .second2(), ) From 1b831f1b2904e67be1f3c9cf148beffe33df0910 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 15:33:09 +0200 Subject: [PATCH 142/266] Fix messages not rendering as folded --- cove/src/ui/chat/tree.rs | 1 + cove/src/ui/chat/tree/renderer.rs | 45 +++++++++++++++++++++++-------- cove/src/ui/chat/tree/scroll.rs | 6 +++-- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/cove/src/ui/chat/tree.rs b/cove/src/ui/chat/tree.rs index 297429b..f2f230a 100644 --- a/cove/src/ui/chat/tree.rs +++ b/cove/src/ui/chat/tree.rs @@ -439,6 +439,7 @@ where let mut renderer = TreeRenderer::new( context, &self.state.store, + &self.state.folded, self.cursor, self.editor, frame.widthdb(), diff --git a/cove/src/ui/chat/tree/renderer.rs b/cove/src/ui/chat/tree/renderer.rs index b7d358b..07edab0 100644 --- a/cove/src/ui/chat/tree/renderer.rs +++ b/cove/src/ui/chat/tree/renderer.rs @@ -1,5 +1,6 @@ //! A [`Renderer`] for message trees. +use std::collections::HashSet; use std::convert::Infallible; use async_trait::async_trait; @@ -79,6 +80,7 @@ pub struct TreeRenderer<'a, M: Msg, S: MsgStore<M>> { context: TreeContext<M::Id>, store: &'a S, + folded: &'a HashSet<M::Id>, cursor: &'a mut Cursor<M::Id>, editor: &'a mut EditorState, widthdb: &'a mut WidthDb, @@ -105,6 +107,7 @@ where pub fn new( context: TreeContext<M::Id>, store: &'a S, + folded: &'a HashSet<M::Id>, cursor: &'a mut Cursor<M::Id>, editor: &'a mut EditorState, widthdb: &'a mut WidthDb, @@ -112,6 +115,7 @@ where Self { context, store, + folded, cursor, editor, widthdb, @@ -169,28 +173,38 @@ where Block::new(id, widget, false) } - fn message_block(&mut self, indent: usize, msg: &M) -> TreeBlock<M::Id> { + fn message_block( + &mut self, + indent: usize, + msg: &M, + folded_info: Option<usize>, + ) -> TreeBlock<M::Id> { let msg_id = msg.id(); let highlighted = match self.cursor { Cursor::Msg(id) => *id == msg_id, _ => false, }; + let highlighted = highlighted && self.context.focused; - // TODO Amount of folded messages - let widget = widgets::msg(self.context.focused && highlighted, indent, msg, None); + let widget = widgets::msg(highlighted, indent, msg, folded_info); let widget = Self::predraw(widget, self.context.size, self.widthdb); Block::new(TreeBlockId::Msg(msg_id), widget, true) } - fn message_placeholder_block(&mut self, indent: usize, msg_id: &M::Id) -> TreeBlock<M::Id> { + fn message_placeholder_block( + &mut self, + indent: usize, + msg_id: &M::Id, + folded_info: Option<usize>, + ) -> TreeBlock<M::Id> { let highlighted = match self.cursor { Cursor::Msg(id) => id == msg_id, _ => false, }; + let highlighted = highlighted && self.context.focused; - // TODO Amount of folded messages - let widget = widgets::msg_placeholder(self.context.focused && highlighted, indent, None); + let widget = widgets::msg_placeholder(highlighted, indent, folded_info); let widget = Self::predraw(widget, self.context.size, self.widthdb); Block::new(TreeBlockId::Msg(msg_id.clone()), widget, true) } @@ -214,18 +228,27 @@ where msg_id: &M::Id, blocks: &mut TreeBlocks<M::Id>, ) { + let folded = self.folded.contains(msg_id); + let folded_info = if folded { + Some(tree.subtree_size(msg_id)).filter(|s| *s > 0) + } else { + None + }; + // Message itself let block = if let Some(msg) = tree.msg(msg_id) { - self.message_block(indent, msg) + self.message_block(indent, msg, folded_info) } else { - self.message_placeholder_block(indent, msg_id) + self.message_placeholder_block(indent, msg_id, folded_info) }; blocks.push_bottom(block); // Children, recursively - if let Some(children) = tree.children(msg_id) { - for child in children { - self.layout_subtree(tree, indent + 1, child, blocks); + if !folded { + if let Some(children) = tree.children(msg_id) { + for child in children { + self.layout_subtree(tree, indent + 1, child, blocks); + } } } diff --git a/cove/src/ui/chat/tree/scroll.rs b/cove/src/ui/chat/tree/scroll.rs index 29b5d1e..8f565b3 100644 --- a/cove/src/ui/chat/tree/scroll.rs +++ b/cove/src/ui/chat/tree/scroll.rs @@ -33,7 +33,8 @@ where delta: i32, ) -> Result<(), S::Error> { let context = self.last_context(); - let mut renderer = TreeRenderer::new(context, &self.store, cursor, editor, widthdb); + let mut renderer = + TreeRenderer::new(context, &self.store, &self.folded, cursor, editor, widthdb); renderer.prepare_blocks_for_drawing().await?; renderer.scroll_by(delta).await?; @@ -53,7 +54,8 @@ where widthdb: &mut WidthDb, ) -> Result<(), S::Error> { let context = self.last_context(); - let mut renderer = TreeRenderer::new(context, &self.store, cursor, editor, widthdb); + let mut renderer = + TreeRenderer::new(context, &self.store, &self.folded, cursor, editor, widthdb); renderer.prepare_blocks_for_drawing().await?; renderer.center_cursor(); From 01c2934fd58a5415030f690955c7d3883293e803 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 15:41:25 +0200 Subject: [PATCH 143/266] Use actual key binding for empty room list hint --- cove/src/ui/rooms.rs | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index 32f3941..e93b8ba 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -18,7 +18,7 @@ use crate::vault::Vault; use super::euph::room::EuphRoom; use super::widgets::{ListBuilder, ListState, Popup}; -use super::{util, UiError, UiEvent}; +use super::{key_bindings, util, UiError, UiEvent}; enum State { ShowList, @@ -170,10 +170,12 @@ impl Rooms { } match &mut self.state { - State::ShowList => Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order) - .await - .desync() - .boxed_async(), + State::ShowList => { + Self::rooms_widget(self.config, &mut self.list, self.order, &self.euph_rooms) + .await + .desync() + .boxed_async() + } State::ShowRoom(name) => { self.euph_rooms @@ -184,7 +186,7 @@ impl Rooms { } State::Connect(editor) => { - Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order) + Self::rooms_widget(self.config, &mut self.list, self.order, &self.euph_rooms) .await .below(Self::new_room_widget(editor)) .desync() @@ -192,7 +194,7 @@ impl Rooms { } State::Delete(name, editor) => { - Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order) + Self::rooms_widget(self.config, &mut self.list, self.order, &self.euph_rooms) .await .below(Self::delete_room_widget(name, editor)) .desync() @@ -351,16 +353,18 @@ impl Rooms { } async fn render_rows( + config: &Config, list_builder: &mut ListBuilder<'_, String, Text>, - euph_rooms: &HashMap<String, EuphRoom>, order: Order, + euph_rooms: &HashMap<String, EuphRoom>, ) { if euph_rooms.is_empty() { - // TODO Use configured key binding - list_builder.add_unsel(Text::new(( - "Press F1 for key bindings", - Style::new().grey().italic(), - ))) + let style = Style::new().grey().italic(); + list_builder.add_unsel(Text::new( + Styled::new("Press ", style) + .and_then(key_bindings::format_binding(&config.keys.general.help)) + .then(" for key bindings", style), + )); } let mut rooms = vec![]; @@ -388,16 +392,17 @@ impl Rooms { } async fn rooms_widget<'a>( + config: &Config, list: &'a mut ListState<String>, - euph_rooms: &HashMap<String, EuphRoom>, order: Order, + euph_rooms: &HashMap<String, EuphRoom>, ) -> impl Widget<UiError> + 'a { let heading_style = Style::new().bold(); let heading_text = Styled::new("Rooms", heading_style).then_plain(format!(" ({})", euph_rooms.len())); let mut list_builder = ListBuilder::new(); - Self::render_rows(&mut list_builder, euph_rooms, order).await; + Self::render_rows(config, &mut list_builder, order, euph_rooms).await; Join2::vertical( Text::new(heading_text).segment().with_fixed(true), From 325c5e6e5c7c08f9eec79d2979f802188a6a07f3 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 15:46:05 +0200 Subject: [PATCH 144/266] Remove resolved todos --- cove-config/src/keys.rs | 1 - cove-macro/src/lib.rs | 1 - cove/src/main.rs | 1 - cove/src/ui/chat/blocks.rs | 1 - 4 files changed, 4 deletions(-) diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index 616aa2e..b9372d5 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -321,7 +321,6 @@ pub struct TreeCursor { // TODO Bindings inspired by vim's ()/[]/{} bindings? } -// TODO Split up in "message", "nicklist", "room"? #[derive(Debug, Deserialize, Document, KeyGroup)] pub struct TreeAction { /// Reply to message, inline if possible. diff --git a/cove-macro/src/lib.rs b/cove-macro/src/lib.rs index 9ce748d..82ef61a 100644 --- a/cove-macro/src/lib.rs +++ b/cove-macro/src/lib.rs @@ -24,7 +24,6 @@ pub fn derive_document(input: proc_macro::TokenStream) -> proc_macro::TokenStrea } } -// TODO Derive Default as well #[proc_macro_derive(KeyGroup)] pub fn derive_group(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); diff --git a/cove/src/main.rs b/cove/src/main.rs index e7bc101..eb71d85 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -12,7 +12,6 @@ // TODO Enable warn(unreachable_pub)? // TODO Remove unnecessary Debug impls and compare compile times // TODO Time zones other than UTC -// TODO Fix password room auth // TODO Invoke external notification command? mod euph; diff --git a/cove/src/ui/chat/blocks.rs b/cove/src/ui/chat/blocks.rs index 0705cf8..2a9eb0a 100644 --- a/cove/src/ui/chat/blocks.rs +++ b/cove/src/ui/chat/blocks.rs @@ -113,7 +113,6 @@ impl<Id> Blocks<Id> { } } - // TODO Remove index from search result pub fn find_block(&self, id: &Id) -> Option<(Range<i32>, &Block<Id>)> where Id: Eq, From e4e321c589cc5a4666f626a5397c0935836efbaa Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 16:00:36 +0200 Subject: [PATCH 145/266] Remove some_or_remove, ok_or_remove macros --- cove/src/euph/room.rs | 4 ++-- cove/src/macros.rs | 32 -------------------------------- cove/src/ui.rs | 20 ++++++++++++++------ 3 files changed, 16 insertions(+), 40 deletions(-) diff --git a/cove/src/euph/room.rs b/cove/src/euph/room.rs index 2e3c0c0..7432a37 100644 --- a/cove/src/euph/room.rs +++ b/cove/src/euph/room.rs @@ -14,7 +14,7 @@ use log::{debug, error, info, warn}; use tokio::select; use tokio::sync::oneshot; -use crate::macros::{logging_unwrap, ok_or_return}; +use crate::macros::logging_unwrap; use crate::vault::EuphRoomVault; const LOG_INTERVAL: Duration = Duration::from_secs(10); @@ -209,7 +209,7 @@ impl Room { async fn on_packet(&mut self, packet: ParsedPacket) { let room_name = &self.instance.config().room; - let data = ok_or_return!(&packet.content); + let Ok(data) = &packet.content else { return; }; match data { Data::BounceEvent(_) => {} Data::DisconnectEvent(_) => {} diff --git a/cove/src/macros.rs b/cove/src/macros.rs index 3e03b07..a20cec9 100644 --- a/cove/src/macros.rs +++ b/cove/src/macros.rs @@ -1,35 +1,3 @@ -macro_rules! some_or_return { - ($e:expr) => { - match $e { - Some(result) => result, - None => return, - } - }; - ($e:expr, $ret:expr) => { - match $e { - Some(result) => result, - None => return $ret, - } - }; -} -pub(crate) use some_or_return; - -macro_rules! ok_or_return { - ($e:expr) => { - match $e { - Ok(result) => result, - Err(_) => return, - } - }; - ($e:expr, $ret:expr) => { - match $e { - Ok(result) => result, - Err(_) => return $ret, - } - }; -} -pub(crate) use ok_or_return; - // TODO Get rid of this macro as much as possible macro_rules! logging_unwrap { ($e:expr) => { diff --git a/cove/src/ui.rs b/cove/src/ui.rs index 790d0c3..0dd5f93 100644 --- a/cove/src/ui.rs +++ b/cove/src/ui.rs @@ -20,7 +20,7 @@ use toss::widgets::BoxedAsync; use toss::{Terminal, WidgetExt}; use crate::logger::{LogMsg, Logger}; -use crate::macros::{logging_unwrap, ok_or_return, some_or_return}; +use crate::macros::logging_unwrap; use crate::util::InfallibleExt; use crate::vault::Vault; @@ -130,11 +130,13 @@ impl Ui { lock: Weak<FairMutex<()>>, ) -> crossterm::Result<()> { loop { - let lock = some_or_return!(lock.upgrade(), Ok(())); + let Some(lock) = lock.upgrade() else { return Ok(()); }; let _guard = lock.lock(); if crossterm::event::poll(Self::POLL_DURATION)? { let event = crossterm::event::read()?; - ok_or_return!(tx.send(UiEvent::Term(event)), Ok(())); + if tx.send(UiEvent::Term(event)).is_err() { + return Ok(()); + } } } } @@ -144,8 +146,12 @@ impl Ui { event_tx: &UnboundedSender<UiEvent>, ) { loop { - some_or_return!(logger_rx.recv().await); - ok_or_return!(event_tx.send(UiEvent::LogChanged)); + if logger_rx.recv().await.is_none() { + return; + } + if event_tx.send(UiEvent::LogChanged).is_err() { + return; + } } } @@ -166,7 +172,9 @@ impl Ui { if terminal.measuring_required() { let _guard = crossterm_lock.lock(); terminal.measure_widths()?; - ok_or_return!(self.event_tx.send(UiEvent::GraphemeWidthsChanged), Ok(())); + if self.event_tx.send(UiEvent::GraphemeWidthsChanged).is_err() { + return Ok(()); + } } } From 5e553898877c6252186fd529bbf9cdb9e339bdcd Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 16:18:04 +0200 Subject: [PATCH 146/266] Make changelog more detailed --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c26f824..985adf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,11 +16,20 @@ Procedure when bumping the version number: ## Unreleased ### Added -- `help-config` CLI command +- Auto-generated config documentation + - in [CONFIG.md](CONFIG.md) + - via `help-config` CLI command - `keys.*` config options - `measure_widths` config option ### Changed +- Overhauled widget system and extracted generic widgets to [toss](https://github.com/Garmelon/toss) +- Overhauled config system to support auto-generating documentation +- Overhauled key binding system to make key bindings configurable +- Redesigned F1 popup. It can now be toggled with F1 like the F12 log +- The F12 log can now be closed with escape +- Some more small UI fixes and adjustments to the new key binding system +- Split up project into sub-crates - Simplified flake dependencies ## v0.6.1 - 2023-04-10 From 48279e879ae15520c02e81c5909bf89f5a302150 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 29 Apr 2023 16:31:23 +0200 Subject: [PATCH 147/266] Show key binding hint in link popup --- cove/src/ui/euph/links.rs | 32 +++++++++++++++++++++++++------- cove/src/ui/euph/room.rs | 19 +++++++++++-------- cove/src/ui/rooms.rs | 2 ++ 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/cove/src/ui/euph/links.rs b/cove/src/ui/euph/links.rs index 00d9d7d..8e3f535 100644 --- a/cove/src/ui/euph/links.rs +++ b/cove/src/ui/euph/links.rs @@ -1,17 +1,18 @@ -use cove_config::Keys; +use cove_config::{Config, Keys}; use cove_input::InputEvent; use crossterm::event::KeyCode; use crossterm::style::Stylize; use linkify::{LinkFinder, LinkKind}; -use toss::widgets::Text; -use toss::{Style, Styled, Widget}; +use toss::widgets::{Join2, Text}; +use toss::{Style, Styled, Widget, WidgetExt}; use crate::ui::widgets::{ListBuilder, ListState, Popup}; -use crate::ui::{util, UiError}; +use crate::ui::{key_bindings, util, UiError}; use super::popup::PopupResult; pub struct LinksState { + config: &'static Config, links: Vec<String>, list: ListState<usize>, } @@ -19,7 +20,7 @@ pub struct LinksState { const NUMBER_KEYS: [char; 10] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']; impl LinksState { - pub fn new(content: &str) -> Self { + pub fn new(config: &'static Config, content: &str) -> Self { let links = LinkFinder::new() .url_must_have_scheme(false) .kinds(&[LinkKind::Url]) @@ -28,6 +29,7 @@ impl LinksState { .collect(); Self { + config, links, list: ListState::new(), } @@ -69,7 +71,24 @@ impl LinksState { } } - Popup::new(list_builder.build(&mut self.list), "Links") + let hint_style = Style::new().grey().italic(); + let hint = Styled::new("Open links with ", hint_style) + .and_then(key_bindings::format_binding( + &self.config.keys.general.confirm, + )) + .then(" or the number keys.", hint_style); + + Popup::new( + Join2::vertical( + list_builder.build(&mut self.list).segment(), + Text::new(hint) + .padding() + .with_top(1) + .segment() + .with_fixed(true), + ), + "Links", + ) } fn open_link_by_id(&self, id: usize) -> PopupResult { @@ -110,7 +129,6 @@ impl LinksState { return PopupResult::Handled; } - // TODO Mention that this is possible in the UI if let Some(key_event) = event.key_event() { if key_event.modifiers.is_empty() { match key_event.code { diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index f539a0e..5f26304 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -1,6 +1,6 @@ use std::collections::VecDeque; -use cove_config::Keys; +use cove_config::{Config, Keys}; use cove_input::InputEvent; use crossterm::style::Stylize; use euphoxide::api::{Data, Message, MessageId, PacketType, SessionId}; @@ -43,8 +43,9 @@ enum State { type EuphChatState = ChatState<euph::SmallMessage, EuphRoomVault>; pub struct EuphRoom { + config: &'static Config, server_config: ServerConfig, - config: cove_config::EuphRoom, + room_config: cove_config::EuphRoom, ui_event_tx: mpsc::UnboundedSender<UiEvent>, room: Option<euph::Room>, @@ -61,14 +62,16 @@ pub struct EuphRoom { impl EuphRoom { pub fn new( + config: &'static Config, server_config: ServerConfig, - config: cove_config::EuphRoom, + room_config: cove_config::EuphRoom, vault: EuphRoomVault, ui_event_tx: mpsc::UnboundedSender<UiEvent>, ) -> Self { Self { - server_config, config, + server_config, + room_config, ui_event_tx, room: None, focus: Focus::Chat, @@ -97,9 +100,9 @@ impl EuphRoom { .room(self.vault().room().to_string()) .name(format!("{room}-{}", next_instance_id)) .human(true) - .username(self.config.username.clone()) - .force_username(self.config.force_username) - .password(self.config.password.clone()); + .username(self.room_config.username.clone()) + .force_username(self.room_config.force_username) + .password(self.room_config.password.clone()); *next_instance_id = next_instance_id.wrapping_add(1); let tx = self.ui_event_tx.clone(); @@ -420,7 +423,7 @@ impl EuphRoom { if event.matches(&keys.tree.action.links) { if let Some(id) = self.chat.cursor() { if let Some(msg) = logging_unwrap!(self.vault().msg(*id).await) { - self.state = State::Links(LinksState::new(&msg.content)); + self.state = State::Links(LinksState::new(self.config, &msg.content)); } } return true; diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index e93b8ba..c692d46 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -93,6 +93,7 @@ impl Rooms { fn get_or_insert_room(&mut self, name: String) -> &mut EuphRoom { self.euph_rooms.entry(name.clone()).or_insert_with(|| { EuphRoom::new( + self.config, self.euph_server_config.clone(), self.config.euph_room(&name), self.vault.euph().room(name), @@ -104,6 +105,7 @@ impl Rooms { fn connect_to_room(&mut self, name: String) { let room = self.euph_rooms.entry(name.clone()).or_insert_with(|| { EuphRoom::new( + self.config, self.euph_server_config.clone(), self.config.euph_room(&name), self.vault.euph().room(name), From 101d36cd45ba2fa5216f94a75ee07f41f4dbc11d Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 30 Apr 2023 22:12:21 +0200 Subject: [PATCH 148/266] Provide list of key groups in config crate This also fixes the f1 menu not displaying the room group. --- cove-config/src/keys.rs | 27 ++++++++++++++++++++++- cove-input/src/lib.rs | 26 +++++++++++++++++++++- cove-macro/src/document.rs | 4 ++-- cove-macro/src/key_group.rs | 17 +++++++++++--- cove-macro/src/util.rs | 25 ++++++++++----------- cove/src/ui/key_bindings.rs | 44 ++++++++++++------------------------- 6 files changed, 93 insertions(+), 50 deletions(-) diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index b9372d5..3ecbb5b 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -1,4 +1,4 @@ -use cove_input::{KeyBinding, KeyGroup}; +use cove_input::{KeyBinding, KeyGroup, KeyGroupInfo}; use serde::Deserialize; use crate::doc::Document; @@ -110,6 +110,7 @@ default_bindings! { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// General. pub struct General { /// Quit cove. #[serde(default = "default::general::exit")] @@ -133,6 +134,7 @@ pub struct General { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Scrolling. pub struct Scroll { /// Scroll up one line. #[serde(default = "default::scroll::up_line")] @@ -158,6 +160,7 @@ pub struct Scroll { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Cursor movement. pub struct Cursor { /// Move up. #[serde(default = "default::cursor::up")] @@ -174,6 +177,7 @@ pub struct Cursor { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Editor cursor movement. pub struct EditorCursor { /// Move left. #[serde(default = "default::editor_cursor::left")] @@ -202,6 +206,7 @@ pub struct EditorCursor { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Editor actions. pub struct EditorAction { /// Delete before cursor. #[serde(default = "default::editor_action::backspace")] @@ -229,6 +234,7 @@ pub struct Editor { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Room list actions. pub struct RoomsAction { /// Connect to selected room. #[serde(default = "default::rooms_action::connect")] @@ -267,6 +273,7 @@ pub struct Rooms { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Room actions. pub struct RoomAction { /// Authenticate. #[serde(default = "default::room_action::authenticate")] @@ -293,6 +300,7 @@ pub struct Room { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Tree cursor movement. pub struct TreeCursor { /// Move to above sibling. #[serde(default = "default::tree_cursor::to_above_sibling")] @@ -322,6 +330,7 @@ pub struct TreeCursor { } #[derive(Debug, Deserialize, Document, KeyGroup)] +/// Tree actions. pub struct TreeAction { /// Reply to message, inline if possible. #[serde(default = "default::tree_action::reply")] @@ -393,3 +402,19 @@ pub struct Keys { #[document(no_default)] pub tree: Tree, } + +impl Keys { + pub fn groups(&self) -> Vec<KeyGroupInfo<'_>> { + vec![ + KeyGroupInfo::new("general", &self.general), + KeyGroupInfo::new("scroll", &self.scroll), + KeyGroupInfo::new("cursor", &self.cursor), + KeyGroupInfo::new("editor.cursor", &self.editor.cursor), + KeyGroupInfo::new("editor.action", &self.editor.action), + KeyGroupInfo::new("rooms.action", &self.rooms.action), + KeyGroupInfo::new("room.action", &self.room.action), + KeyGroupInfo::new("tree.cursor", &self.tree.cursor), + KeyGroupInfo::new("tree.action", &self.tree.action), + ] + } +} diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index fe578fd..b42fdcb 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -10,9 +10,33 @@ use toss::{Frame, Terminal, WidthDb}; pub use crate::keys::*; +pub struct KeyBindingInfo<'a> { + pub name: &'static str, + pub binding: &'a KeyBinding, + pub description: &'static str, +} + /// A group of related key bindings. pub trait KeyGroup { - fn bindings(&self) -> Vec<(&KeyBinding, &'static str)>; + const DESCRIPTION: &'static str; + + fn bindings(&self) -> Vec<KeyBindingInfo<'_>>; +} + +pub struct KeyGroupInfo<'a> { + pub name: &'static str, + pub description: &'static str, + pub bindings: Vec<KeyBindingInfo<'a>>, +} + +impl<'a> KeyGroupInfo<'a> { + pub fn new<G: KeyGroup>(name: &'static str, group: &'a G) -> Self { + Self { + name, + description: G::DESCRIPTION, + bindings: group.bindings(), + } + } } pub struct InputEvent<'a> { diff --git a/cove-macro/src/document.rs b/cove-macro/src/document.rs index ddf13c8..e8e248e 100644 --- a/cove-macro/src/document.rs +++ b/cove-macro/src/document.rs @@ -16,12 +16,12 @@ struct FieldInfo { impl FieldInfo { fn initialize_from_field(&mut self, field: &Field) -> syn::Result<()> { - let docstring = util::docstring(field)?; + let docstring = util::docstring(&field.attrs)?; if !docstring.is_empty() { self.description = Some(docstring); } - for arg in util::attribute_arguments(field, "document")? { + for arg in util::attribute_arguments(&field.attrs, "document")? { if arg.path.is_ident("metavar") { // Parse `#[document(metavar = "bla")]` if let Some(metavar) = arg.value.and_then(util::into_litstr) { diff --git a/cove-macro/src/key_group.rs b/cove-macro/src/key_group.rs index 254f660..bc7bdea 100644 --- a/cove-macro/src/key_group.rs +++ b/cove-macro/src/key_group.rs @@ -19,11 +19,16 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { return util::bail(input.span(), "must be a struct"); }; + let docstring = util::docstring(&input.attrs)?; + let description = docstring.strip_suffix('.').unwrap_or(&docstring); + let mut bindings = vec![]; let mut defaults = vec![]; for field in &data.fields { if let Some(field_ident) = &field.ident { - let docstring = util::docstring(field)?; + let field_name = field_ident.to_string(); + + let docstring = util::docstring(&field.attrs)?; let description = decapitalize(&docstring); let description = description.strip_suffix('.').unwrap_or(&description); @@ -34,7 +39,11 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { let default_value = default.value(); bindings.push(quote! { - (&self.#field_ident, #description) + ::cove_input::KeyBindingInfo { + name: #field_name, + binding: &self.#field_ident, + description: #description + } }); defaults.push(quote! { @@ -46,7 +55,9 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { let ident = input.ident; Ok(quote! { impl ::cove_input::KeyGroup for #ident { - fn bindings(&self) -> Vec<(&::cove_input::KeyBinding, &'static str)> { + const DESCRIPTION: &'static str = #description; + + fn bindings(&self) -> Vec<::cove_input::KeyBindingInfo<'_>> { vec![ #( #bindings, )* ] diff --git a/cove-macro/src/util.rs b/cove-macro/src/util.rs index e56174a..b7bf62a 100644 --- a/cove-macro/src/util.rs +++ b/cove-macro/src/util.rs @@ -2,7 +2,7 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::parse::Parse; use syn::punctuated::Punctuated; -use syn::{Expr, ExprLit, ExprPath, Field, Lit, LitStr, Path, Token, Type}; +use syn::{Attribute, Expr, ExprLit, ExprPath, Field, Lit, LitStr, Path, Token, Type}; pub fn bail<T>(span: Span, message: &str) -> syn::Result<T> { Err(syn::Error::new(span, message)) @@ -28,14 +28,10 @@ pub fn into_litstr(expr: Expr) -> Option<LitStr> { /// Given a struct field, this finds all attributes like `#[doc = "bla"]`, /// unindents, concatenates and returns them. -pub fn docstring(field: &Field) -> syn::Result<String> { +pub fn docstring(attributes: &[Attribute]) -> syn::Result<String> { let mut lines = vec![]; - for attr in field - .attrs - .iter() - .filter(|attr| attr.path().is_ident("doc")) - { + for attr in attributes.iter().filter(|attr| attr.path().is_ident("doc")) { if let Some(lit) = litstr(&attr.meta.require_name_value()?.value) { let value = lit.value(); let value = value @@ -70,16 +66,19 @@ impl Parse for AttributeArgument { /// Given a struct field, this finds all arguments of the form `#[path(key)]` /// and `#[path(key = value)]`. Multiple arguments may be specified in a single /// annotation, e.g. `#[foo(bar, baz = true)]`. -pub fn attribute_arguments(field: &Field, path: &str) -> syn::Result<Vec<AttributeArgument>> { - let mut attrs = vec![]; +pub fn attribute_arguments( + attributes: &[Attribute], + path: &str, +) -> syn::Result<Vec<AttributeArgument>> { + let mut attr_args = vec![]; - for attr in field.attrs.iter().filter(|attr| attr.path().is_ident(path)) { + for attr in attributes.iter().filter(|attr| attr.path().is_ident(path)) { let args = attr.parse_args_with(Punctuated::<AttributeArgument, Token![,]>::parse_terminated)?; - attrs.extend(args); + attr_args.extend(args); } - Ok(attrs) + Ok(attr_args) } pub enum SerdeDefault { @@ -102,7 +101,7 @@ impl SerdeDefault { /// Find `#[serde(default)]` or `#[serde(default = "bla")]`. pub fn serde_default(field: &Field) -> syn::Result<Option<SerdeDefault>> { - for arg in attribute_arguments(field, "serde")? { + for arg in attribute_arguments(&field.attrs, "serde")? { if arg.path.is_ident("default") { if let Some(value) = arg.value { if let Some(path) = into_litstr(value) { diff --git a/cove/src/ui/key_bindings.rs b/cove/src/ui/key_bindings.rs index a4de6da..679c929 100644 --- a/cove/src/ui/key_bindings.rs +++ b/cove/src/ui/key_bindings.rs @@ -3,7 +3,7 @@ use std::convert::Infallible; use cove_config::{Config, Keys}; -use cove_input::{InputEvent, KeyBinding, KeyGroup}; +use cove_input::{InputEvent, KeyBinding, KeyBindingInfo, KeyGroupInfo}; use crossterm::style::Stylize; use toss::widgets::{Either2, Join2, Padding, Text}; use toss::{Style, Styled, Widget, WidgetExt}; @@ -41,16 +41,16 @@ fn render_title(builder: &mut Builder, title: &str) { builder.add_unsel(Text::new(Styled::new(title, style)).first2()); } -fn render_binding(builder: &mut Builder, binding: &KeyBinding, description: &str) { +fn render_binding_info(builder: &mut Builder, binding_info: KeyBindingInfo<'_>) { builder.add_unsel( Join2::horizontal( - Text::new(description) + Text::new(binding_info.description) .with_wrap(false) .padding() .with_right(2) .with_stretch(true) .segment(), - Text::new(format_binding(binding)) + Text::new(format_binding(binding_info.binding)) .with_wrap(false) .segment() .with_fixed(true), @@ -59,9 +59,10 @@ fn render_binding(builder: &mut Builder, binding: &KeyBinding, description: &str ) } -fn render_group<G: KeyGroup>(builder: &mut Builder, group: &G) { - for (binding, description) in group.bindings() { - render_binding(builder, binding, description); +fn render_group_info(builder: &mut Builder, group_info: KeyGroupInfo<'_>) { + render_title(builder, group_info.description); + for binding_info in group_info.bindings { + render_binding_info(builder, binding_info); } } @@ -71,29 +72,12 @@ pub fn widget<'a>( ) -> impl Widget<UiError> + 'a { let mut list_builder = ListBuilder::new(); - render_title(&mut list_builder, "General"); - render_group(&mut list_builder, &config.keys.general); - render_empty(&mut list_builder); - render_title(&mut list_builder, "Scrolling"); - render_group(&mut list_builder, &config.keys.scroll); - render_empty(&mut list_builder); - render_title(&mut list_builder, "Cursor movement"); - render_group(&mut list_builder, &config.keys.cursor); - render_empty(&mut list_builder); - render_title(&mut list_builder, "Editor cursor movement"); - render_group(&mut list_builder, &config.keys.editor.cursor); - render_empty(&mut list_builder); - render_title(&mut list_builder, "Editor actions"); - render_group(&mut list_builder, &config.keys.editor.action); - render_empty(&mut list_builder); - render_title(&mut list_builder, "Room list actions"); - render_group(&mut list_builder, &config.keys.rooms.action); - render_empty(&mut list_builder); - render_title(&mut list_builder, "Tree cursor movement"); - render_group(&mut list_builder, &config.keys.tree.cursor); - render_empty(&mut list_builder); - render_title(&mut list_builder, "Tree actions"); - render_group(&mut list_builder, &config.keys.tree.action); + for group_info in config.keys.groups() { + if !list_builder.is_empty() { + render_empty(&mut list_builder); + } + render_group_info(&mut list_builder, group_info); + } Popup::new(list_builder.build(list), "Key bindings") } From 2afda17d4b13e828b31a0a15fb44a1837e2a80cf Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 30 Apr 2023 22:51:28 +0200 Subject: [PATCH 149/266] Improve config loading error handling --- Cargo.lock | 1 + cove-config/Cargo.toml | 1 + cove-config/src/lib.rs | 26 ++++++++++++++++---------- cove/src/main.rs | 2 +- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab7cc05..234a1cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,6 +284,7 @@ dependencies = [ "cove-input", "cove-macro", "serde", + "thiserror", "toml", ] diff --git a/cove-config/Cargo.toml b/cove-config/Cargo.toml index 252e228..a5bb70b 100644 --- a/cove-config/Cargo.toml +++ b/cove-config/Cargo.toml @@ -8,5 +8,6 @@ cove-input = { path = "../cove-input" } cove-macro = { path = "../cove-macro" } serde = { workspace = true } +thiserror = { workspace = true } toml = "0.7.3" diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 6d00391..d2a773a 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -13,8 +13,9 @@ pub mod doc; mod euph; mod keys; -use std::fs; +use std::io::ErrorKind; use std::path::{Path, PathBuf}; +use std::{fs, io}; use doc::Document; use serde::Deserialize; @@ -22,6 +23,14 @@ use serde::Deserialize; pub use crate::euph::*; pub use crate::keys::*; +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("failed to read config file")] + Io(#[from] io::Error), + #[error("failed to parse config file")] + Toml(#[from] toml::de::Error), +} + #[derive(Debug, Default, Deserialize, Document)] pub struct Config { /// The directory that cove stores its data in when not running in ephemeral @@ -90,15 +99,12 @@ pub struct Config { } impl Config { - pub fn load(path: &Path) -> Self { - let Ok(content) = fs::read_to_string(path) else { return Self::default(); }; - match toml::from_str(&content) { - Ok(config) => config, - Err(err) => { - eprintln!("Error loading config file: {err}"); - Self::default() - } - } + pub fn load(path: &Path) -> Result<Self, Error> { + Ok(match fs::read_to_string(path) { + Ok(content) => toml::from_str(&content)?, + Err(err) if err.kind() == ErrorKind::NotFound => Self::default(), + Err(err) => Err(err)?, + }) } pub fn euph_room(&self, name: &str) -> EuphRoom { diff --git a/cove/src/main.rs b/cove/src/main.rs index eb71d85..e9dc920 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -146,7 +146,7 @@ async fn main() -> anyhow::Result<()> { eprintln!("Config file: {}", config_path.to_string_lossy()); // Load config - let mut config = Config::load(&config_path); + let mut config = Config::load(&config_path)?; update_config_with_args(&mut config, &args); let config = Box::leak(Box::new(config)); From e6585286e33d3eba1f5c46dca01d8cd0be20a1af Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 3 May 2023 00:02:09 +0200 Subject: [PATCH 150/266] Fix crash when cursor moves inside folded subtree --- cove/src/ui/chat/renderer.rs | 4 +++- cove/src/ui/chat/tree.rs | 2 +- cove/src/ui/chat/tree/renderer.rs | 32 +++++++++++++++++++++++++------ cove/src/ui/chat/tree/scroll.rs | 20 +++++++++++++++---- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/cove/src/ui/chat/renderer.rs b/cove/src/ui/chat/renderer.rs index 7318f43..1edde46 100644 --- a/cove/src/ui/chat/renderer.rs +++ b/cove/src/ui/chat/renderer.rs @@ -178,7 +178,7 @@ where } /// Expand blocks such that the screen is full for any offset where the -/// specified block is visible. +/// specified block is visible. The block must exist. pub async fn expand_to_fill_screen_around_block<Id, R>(r: &mut R, id: &Id) -> Result<(), R::Error> where Id: Eq, @@ -196,6 +196,8 @@ where Ok(()) } +/// Scroll so that the top of the block is at the specified value. Returns +/// `true` if successful, or `false` if the block could not be found. pub fn scroll_to_set_block_top<Id, R>(r: &mut R, id: &Id, top: i32) -> bool where Id: Eq, diff --git a/cove/src/ui/chat/tree.rs b/cove/src/ui/chat/tree.rs index f2f230a..772363f 100644 --- a/cove/src/ui/chat/tree.rs +++ b/cove/src/ui/chat/tree.rs @@ -439,7 +439,7 @@ where let mut renderer = TreeRenderer::new( context, &self.state.store, - &self.state.folded, + &mut self.state.folded, self.cursor, self.editor, frame.widthdb(), diff --git a/cove/src/ui/chat/tree/renderer.rs b/cove/src/ui/chat/tree/renderer.rs index 07edab0..6932123 100644 --- a/cove/src/ui/chat/tree/renderer.rs +++ b/cove/src/ui/chat/tree/renderer.rs @@ -80,7 +80,7 @@ pub struct TreeRenderer<'a, M: Msg, S: MsgStore<M>> { context: TreeContext<M::Id>, store: &'a S, - folded: &'a HashSet<M::Id>, + folded: &'a mut HashSet<M::Id>, cursor: &'a mut Cursor<M::Id>, editor: &'a mut EditorState, widthdb: &'a mut WidthDb, @@ -107,7 +107,7 @@ where pub fn new( context: TreeContext<M::Id>, store: &'a S, - folded: &'a HashSet<M::Id>, + folded: &'a mut HashSet<M::Id>, cursor: &'a mut Cursor<M::Id>, editor: &'a mut EditorState, widthdb: &'a mut WidthDb, @@ -279,12 +279,30 @@ where Ok(Some(path.into_first())) } - async fn prepare_initial_tree(&mut self, root_id: &Option<M::Id>) -> Result<(), S::Error> { + /// Render the tree containing the cursor to the blocks and set the top and + /// bottom root id accordingly. This function will always render a block + /// that has the cusor id. + async fn prepare_initial_tree( + &mut self, + cursor_id: &TreeBlockId<M::Id>, + root_id: &Option<M::Id>, + ) -> Result<(), S::Error> { self.top_root_id = root_id.clone(); self.bottom_root_id = root_id.clone(); let blocks = if let Some(root_id) = root_id { let tree = self.store.tree(root_id).await?; + + // To ensure the cursor block will be rendered, all its parents must + // be unfolded. + if let TreeBlockId::Msg(id) | TreeBlockId::After(id) = cursor_id { + let mut id = id.clone(); + while let Some(parent_id) = tree.parent(&id) { + self.folded.remove(&parent_id); + id = parent_id; + } + } + self.layout_tree(tree) } else { self.layout_bottom() @@ -318,9 +336,11 @@ where let cursor_id = TreeBlockId::from_cursor(self.cursor); let cursor_root_id = self.root_id(&cursor_id).await?; - // Render cursor and blocks around it until screen is filled as long as - // the cursor is visible, regardless of how the screen is scrolled. - self.prepare_initial_tree(&cursor_root_id).await?; + // Render cursor and blocks around it so that the screen will always be + // filled as long as the cursor is visible, regardless of how the screen + // is scrolled. + self.prepare_initial_tree(&cursor_id, &cursor_root_id) + .await?; renderer::expand_to_fill_screen_around_block(self, &cursor_id).await?; // Scroll based on last cursor position diff --git a/cove/src/ui/chat/tree/scroll.rs b/cove/src/ui/chat/tree/scroll.rs index 8f565b3..482c7ca 100644 --- a/cove/src/ui/chat/tree/scroll.rs +++ b/cove/src/ui/chat/tree/scroll.rs @@ -33,8 +33,14 @@ where delta: i32, ) -> Result<(), S::Error> { let context = self.last_context(); - let mut renderer = - TreeRenderer::new(context, &self.store, &self.folded, cursor, editor, widthdb); + let mut renderer = TreeRenderer::new( + context, + &self.store, + &mut self.folded, + cursor, + editor, + widthdb, + ); renderer.prepare_blocks_for_drawing().await?; renderer.scroll_by(delta).await?; @@ -54,8 +60,14 @@ where widthdb: &mut WidthDb, ) -> Result<(), S::Error> { let context = self.last_context(); - let mut renderer = - TreeRenderer::new(context, &self.store, &self.folded, cursor, editor, widthdb); + let mut renderer = TreeRenderer::new( + context, + &self.store, + &mut self.folded, + cursor, + editor, + widthdb, + ); renderer.prepare_blocks_for_drawing().await?; renderer.center_cursor(); From dc0de4354f404dc198cf30abe1f1515861f2bb21 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 10 May 2023 19:43:51 +0200 Subject: [PATCH 151/266] Reduce tearing when redrawing --- CHANGELOG.md | 1 + Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 985adf4..f472457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Procedure when bumping the version number: - Redesigned F1 popup. It can now be toggled with F1 like the F12 log - The F12 log can now be closed with escape - Some more small UI fixes and adjustments to the new key binding system +- Reduced tearing when redrawing screen - Split up project into sub-crates - Simplified flake dependencies diff --git a/Cargo.lock b/Cargo.lock index 234a1cd..b273d5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1397,7 +1397,7 @@ dependencies = [ [[package]] name = "toss" version = "0.1.0" -source = "git+https://github.com/Garmelon/toss.git?rev=f005ec10fe1b6034c50f3a4ef24dd44d3e6d5593#f005ec10fe1b6034c50f3a4ef24dd44d3e6d5593" +source = "git+https://github.com/Garmelon/toss.git?rev=6eb853e3136dd320272bd67e2957774869c21e4b#6eb853e3136dd320272bd67e2957774869c21e4b" dependencies = [ "async-trait", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 9fe5f0d..4ff3ca3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ thiserror = "1.0.40" [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" -rev = "f005ec10fe1b6034c50f3a4ef24dd44d3e6d5593" +rev = "6eb853e3136dd320272bd67e2957774869c21e4b" [profile.dev.package."*"] opt-level = 3 From 3b4e41ea4e03690596dbc0d6af48fd9b95577b89 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 14 May 2023 16:47:10 +0200 Subject: [PATCH 152/266] Add example config to config documentation --- cove-config/src/doc.rs | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/cove-config/src/doc.rs b/cove-config/src/doc.rs index 062a5bd..3221eb5 100644 --- a/cove-config/src/doc.rs +++ b/cove-config/src/doc.rs @@ -11,7 +11,40 @@ const MARKDOWN_INTRODUCTION: &str = r#"# Config file format Cove's config file uses the [TOML](https://toml.io/) format. -## Key binding format +Here is an example config that changes a few different options: + +```toml +measure_widths = true +rooms_sort_order = "importance" + +[euph.rooms.welcome] +autojoin = true + +[euph.rooms.test] +username = "badingle" +force_username = true + +[euph.rooms.private] +password = "foobar" + +[keys] +general.abort = ["esc", "ctrl+c"] +general.exit = "ctrl+q" +tree.action.fold_tree = "f" +``` + +If you want to configure lots of rooms, TOML lets you write this in a more +compact way: + +```toml +[euph.rooms] +foo = { autojoin = true } +bar = { autojoin = true } +baz = { autojoin = true } +private = { autojoin = true, password = "foobar" } +``` + +## Key bindings Key bindings are specified as strings or lists of strings. Each string specifies a main key and zero or more modifier keys. The modifier keys (if any) are listed @@ -42,7 +75,7 @@ Available modifiers: - `alt` - `any` (matches as long as at least one modifier is pressed) -## Config options +## Available options "#; pub fn toml_value_as_markdown<T: Serialize>(value: &T) -> String { From 876619454ec32990d0a37af76ecf2111d161a98a Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 14 May 2023 16:48:26 +0200 Subject: [PATCH 153/266] Update dependencies --- Cargo.lock | 264 ++++++++++++++++++++++-------------------- Cargo.toml | 4 +- cove-macro/Cargo.toml | 4 +- cove/Cargo.toml | 18 +-- cove/src/euph/room.rs | 6 +- 5 files changed, 156 insertions(+), 140 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b273d5c..e2bbbce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,58 +15,67 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] [[package]] name = "anstream" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" dependencies = [ "anstyle", "anstyle-parse", + "anstyle-query", "anstyle-wincon", - "concolor-override", - "concolor-query", + "colorchoice", "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "0.3.5" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" [[package]] name = "anstyle-parse" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" dependencies = [ "utf8parse", ] [[package]] -name = "anstyle-wincon" -version = "0.2.0" +name = "anstyle-query" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "async-trait" @@ -76,7 +85,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] @@ -105,9 +114,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3" +checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813" [[package]] name = "block-buffer" @@ -120,9 +129,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" [[package]] name = "byteorder" @@ -166,9 +175,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.2.1" +version = "4.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" +checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938" dependencies = [ "clap_builder", "clap_derive", @@ -177,9 +186,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.1" +version = "4.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd" dependencies = [ "anstream", "anstyle", @@ -197,7 +206,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] @@ -207,19 +216,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" [[package]] -name = "concolor-override" +name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" - -[[package]] -name = "concolor-query" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" -dependencies = [ - "windows-sys 0.45.0", -] +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "cookie" @@ -309,14 +309,14 @@ dependencies = [ "case", "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] name = "cpufeatures" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] @@ -368,22 +368,23 @@ dependencies = [ [[package]] name = "directories" -version = "5.0.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74be3be809c18e089de43bdc504652bb2bc473fca8756131f8689db8cf079ba9" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", + "option-ext", "redox_users", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -425,8 +426,8 @@ dependencies = [ [[package]] name = "euphoxide" -version = "0.3.1" -source = "git+https://github.com/Garmelon/euphoxide.git?rev=0f217a6279181b0731216760219e8ff0fa01e449#0f217a6279181b0731216760219e8ff0fa01e449" +version = "0.4.0" +source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.4.0#fa6c8cdce9dd7e5f38e333e35ca975cfcdd60cd2" dependencies = [ "async-trait", "caseless", @@ -628,6 +629,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + [[package]] name = "is-terminal" version = "0.4.7" @@ -640,6 +650,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + [[package]] name = "itoa" version = "1.0.6" @@ -648,18 +668,18 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "68c16e1bfd491478ab155fd8b4896b86f9ede344949b641e61501e07c2b8b4d5" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.141" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libsqlite3-sys" @@ -683,9 +703,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.3.1" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" [[package]] name = "lock_api" @@ -751,10 +771,11 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "open" -version = "4.0.1" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "075c5203b3a2b698bc72c6c10b1f6263182135751d5013ea66e8a4b3d0562a43" +checksum = "d16814a067484415fda653868c9be0ac5f2abd2ef5d951082a5f2fe1b3662944" dependencies = [ + "is-wsl", "pathdiff", ] @@ -764,6 +785,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-float" version = "2.10.0" @@ -822,9 +849,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "ppv-lite86" @@ -843,9 +870,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -911,9 +938,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.3" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ "aho-corasick", "memchr", @@ -922,9 +949,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" [[package]] name = "ring" @@ -947,7 +974,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" dependencies = [ - "bitflags 2.1.0", + "bitflags 2.2.1", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -958,9 +985,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.11" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ "bitflags 1.3.2", "errno", @@ -1036,9 +1063,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.8.2" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "ca2855b3715770894e67cbfa3df957790aa0c9edc3bf06efa1a84d77fa0839d1" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -1049,9 +1076,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" dependencies = [ "core-foundation-sys", "libc", @@ -1059,9 +1086,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.159" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] @@ -1078,13 +1105,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] @@ -1099,9 +1126,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -1197,20 +1224,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" dependencies = [ "proc-macro2", "quote", @@ -1247,14 +1263,14 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] name = "time" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ "itoa", "serde", @@ -1264,15 +1280,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" dependencies = [ "time-core", ] @@ -1294,9 +1310,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.27.0" +version = "1.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" dependencies = [ "autocfg", "bytes", @@ -1308,18 +1324,18 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] @@ -1335,9 +1351,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", @@ -1397,7 +1413,7 @@ dependencies = [ [[package]] name = "toss" version = "0.1.0" -source = "git+https://github.com/Garmelon/toss.git?rev=6eb853e3136dd320272bd67e2957774869c21e4b#6eb853e3136dd320272bd67e2957774869c21e4b" +source = "git+https://github.com/Garmelon/toss.git?tag=v0.1.0#87723840df5ff75666d7270e62b943621197ce62" dependencies = [ "async-trait", "crossterm", @@ -1507,8 +1523,8 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "vault" -version = "0.1.0" -source = "git+https://github.com/Garmelon/vault.git?rev=b4cf23b7279770226725c895e482c8eda88c43a7#b4cf23b7279770226725c895e482c8eda88c43a7" +version = "0.2.0" +source = "git+https://github.com/Garmelon/vault.git?tag=v0.2.0#6fd284fed71ece886db9b8aab659f454ba4858b6" dependencies = [ "rusqlite", "tokio", @@ -1534,9 +1550,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "5b6cb788c4e39112fbe1822277ef6fb3c55cd86b95cb3d3c4c1c9597e4ac74b4" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1544,24 +1560,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "35e522ed4105a9d626d885b35d62501b30d9666283a5c8be12c14a8bdafe7822" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "358a79a0cb89d21db8120cbfb91392335913e4890665b1a7981d9e956903b434" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1569,28 +1585,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "a901d592cafaa4d711bc324edfaff879ac700b19c3dfd60058d2b445be2691eb" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "16b5f940c7edfdc6d12126d98c9ef4d1b3d470011c47c76a6581df47ad9ba721" dependencies = [ "js-sys", "wasm-bindgen", @@ -1788,9 +1804,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.1" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 4ff3ca3..beacc35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,13 @@ edition = "2021" [workspace.dependencies] crossterm = "0.26.1" parking_lot = "0.12.1" -serde = { version = "1.0.159", features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"] } serde_either = "0.2.1" thiserror = "1.0.40" [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" -rev = "6eb853e3136dd320272bd67e2957774869c21e4b" +tag = "v0.1.0" [profile.dev.package."*"] opt-level = 3 diff --git a/cove-macro/Cargo.toml b/cove-macro/Cargo.toml index 731111a..840b729 100644 --- a/cove-macro/Cargo.toml +++ b/cove-macro/Cargo.toml @@ -6,8 +6,8 @@ edition = { workspace = true } [dependencies] case = "1.0.0" proc-macro2 = "1.0.56" -quote = "1.0.26" -syn = "2.0.15" +quote = "1.0.27" +syn = "2.0.16" [lib] proc-macro = true diff --git a/cove/Cargo.toml b/cove/Cargo.toml index 0d1a1f9..ff50048 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -12,23 +12,23 @@ parking_lot = { workspace = true } thiserror = { workspace = true } toss = { workspace = true } -anyhow = "1.0.70" +anyhow = "1.0.71" async-trait = "0.1.68" -clap = { version = "4.2.1", features = ["derive", "deprecated"] } +clap = { version = "4.2.7", features = ["derive", "deprecated"] } cookie = "0.17.0" -directories = "5.0.0" +directories = "5.0.1" linkify = "0.9.0" log = { version = "0.4.17", features = ["std"] } once_cell = "1.17.1" -open = "4.0.1" +open = "4.1.0" rusqlite = { version = "0.29.0", features = ["bundled", "time"] } -serde_json = "1.0.95" -tokio = { version = "1.27.0", features = ["full"] } +serde_json = "1.0.96" +tokio = { version = "1.28.1", features = ["full"] } unicode-segmentation = "1.10.1" unicode-width = "0.1.10" [dependencies.time] -version = "0.3.20" +version = "0.3.21" features = ["macros", "formatting", "parsing", "serde"] [dependencies.tokio-tungstenite] @@ -37,10 +37,10 @@ features = ["rustls-tls-native-roots"] [dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -rev = "0f217a6279181b0731216760219e8ff0fa01e449" +tag = "v0.4.0" features = ["bot"] [dependencies.vault] git = "https://github.com/Garmelon/vault.git" -rev = "b4cf23b7279770226725c895e482c8eda88c43a7" +tag = "v0.2.0" features = ["tokio"] diff --git a/cove/src/euph/room.rs b/cove/src/euph/room.rs index 7432a37..7937180 100644 --- a/cove/src/euph/room.rs +++ b/cove/src/euph/room.rs @@ -8,7 +8,7 @@ use euphoxide::api::{ Auth, AuthOption, Data, Log, Login, Logout, MessageId, Nick, Send, SendEvent, SendReply, Time, UserId, }; -use euphoxide::bot::instance::{Event, Instance, InstanceConfig, Snapshot}; +use euphoxide::bot::instance::{ConnSnapshot, Event, Instance, InstanceConfig}; use euphoxide::conn::{self, ConnTx}; use log::{debug, error, info, warn}; use tokio::select; @@ -106,7 +106,7 @@ impl Room { self.last_msg_id = None; self.log_request_canary = None; } - Event::Connected(_, Snapshot { conn_tx, state }) => { + Event::Connected(_, ConnSnapshot { conn_tx, state }) => { if !self.ephemeral { let (tx, rx) = oneshot::channel(); self.log_request_canary = Some(tx); @@ -127,7 +127,7 @@ impl Room { let cookies = cookies.lock().unwrap().clone(); logging_unwrap!(self.vault.vault().set_cookies(cookies).await); } - Event::Packet(_, packet, Snapshot { conn_tx, state }) => { + Event::Packet(_, packet, ConnSnapshot { conn_tx, state }) => { self.state = State::Connected(conn_tx, state); self.on_packet(packet).await; } From 02bfd3ed3dbc5a86cd9cbb4c3861247877c87c89 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 14 May 2023 16:52:04 +0200 Subject: [PATCH 154/266] Bump version to 0.7.0 --- CHANGELOG.md | 2 + CONFIG.md | 581 ++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.lock | 8 +- Cargo.toml | 2 +- 4 files changed, 578 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f472457..580a811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Procedure when bumping the version number: ## Unreleased +## v0.7.0 - 2023-05-14 + ### Added - Auto-generated config documentation - in [CONFIG.md](CONFIG.md) diff --git a/CONFIG.md b/CONFIG.md index 4f2a456..a3f25ac 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -1,8 +1,74 @@ -# Configuration options +# Config file format Cove's config file uses the [TOML](https://toml.io/) format. -## `data_dir` +Here is an example config that changes a few different options: + +```toml +measure_widths = true +rooms_sort_order = "importance" + +[euph.rooms.welcome] +autojoin = true + +[euph.rooms.test] +username = "badingle" +force_username = true + +[euph.rooms.private] +password = "foobar" + +[keys] +general.abort = ["esc", "ctrl+c"] +general.exit = "ctrl+q" +tree.action.fold_tree = "f" +``` + +If you want to configure lots of rooms, TOML lets you write this in a more +compact way: + +```toml +[euph.rooms] +foo = { autojoin = true } +bar = { autojoin = true } +baz = { autojoin = true } +private = { autojoin = true, password = "foobar" } +``` + +## Key bindings + +Key bindings are specified as strings or lists of strings. Each string specifies +a main key and zero or more modifier keys. The modifier keys (if any) are listed +first, followed by the main key. They are separated by the `+` character and +**no** whitespace. + +Examples of key bindings: +- `"ctrl+c"` +- `"X"` (not `"shift+x"`) +- `"space"` or `" "` (both space bar) +- `["g", "home"]` +- `["K", "ctrl+up"]` +- `["f1", "?"]` +- `"ctrl+alt+f3"` +- `["enter", "any+enter"]` (matches `enter` regardless of modifiers) + +Available main keys: +- Any single character that can be typed +- `esc`, `enter`, `space`, `tab`, `backtab` +- `backspace`, `delete`, `insert` +- `left`, `right`, `up`, `down` +- `home`, `end`, `pageup`, `pagedown` +- `f1`, `f2`, ... + +Available modifiers: +- `shift` (must not be used with single characters) +- `ctrl` +- `alt` +- `any` (matches as long as at least one modifier is pressed) + +## Available options + +### `data_dir` **Required:** no **Type:** path @@ -15,7 +81,7 @@ Relative paths are interpreted relative to the user's home directory. See also the `--data-dir` command line option. -## `ephemeral` +### `ephemeral` **Required:** yes **Type:** boolean @@ -28,7 +94,7 @@ any options related to the data dir. See also the `--ephemeral` command line option. -## `euph.rooms.<room>.autojoin` +### `euph.rooms.<room>.autojoin` **Required:** yes **Type:** boolean @@ -36,7 +102,7 @@ See also the `--ephemeral` command line option. Whether to automatically join this room on startup. -## `euph.rooms.<room>.force_username` +### `euph.rooms.<room>.force_username` **Required:** yes **Type:** boolean @@ -46,7 +112,7 @@ If `euph.rooms.<room>.username` is set, this will force cove to set the username even if there is already a different username associated with the current session. -## `euph.rooms.<room>.password` +### `euph.rooms.<room>.password` **Required:** no **Type:** string @@ -54,7 +120,7 @@ the current session. If set, cove will try once to use this password to authenticate, should the room be password-protected. -## `euph.rooms.<room>.username` +### `euph.rooms.<room>.username` **Required:** no **Type:** string @@ -62,7 +128,502 @@ the room be password-protected. If set, cove will set this username upon joining if there is no username associated with the current session. -## `offline` +### `keys.cursor.down` + +**Required:** yes +**Type:** key binding +**Default:** `["j", "down"]` + +Move down. + +### `keys.cursor.to_bottom` + +**Required:** yes +**Type:** key binding +**Default:** `["G", "end"]` + +Move to bottom. + +### `keys.cursor.to_top` + +**Required:** yes +**Type:** key binding +**Default:** `["g", "home"]` + +Move to top. + +### `keys.cursor.up` + +**Required:** yes +**Type:** key binding +**Default:** `["k", "up"]` + +Move up. + +### `keys.editor.action.backspace` + +**Required:** yes +**Type:** key binding +**Default:** `["ctrl+h", "backspace"]` + +Delete before cursor. + +### `keys.editor.action.clear` + +**Required:** yes +**Type:** key binding +**Default:** `"ctrl+l"` + +Clear editor contents. + +### `keys.editor.action.delete` + +**Required:** yes +**Type:** key binding +**Default:** `["ctrl+d", "delete"]` + +Delete after cursor. + +### `keys.editor.action.external` + +**Required:** yes +**Type:** key binding +**Default:** `["ctrl+x", "alt+e"]` + +Edit in external editor. + +### `keys.editor.cursor.down` + +**Required:** yes +**Type:** key binding +**Default:** `"down"` + +Move down. + +### `keys.editor.cursor.end` + +**Required:** yes +**Type:** key binding +**Default:** `["ctrl+e", "end"]` + +Move to end of line. + +### `keys.editor.cursor.left` + +**Required:** yes +**Type:** key binding +**Default:** `["ctrl+b", "left"]` + +Move left. + +### `keys.editor.cursor.left_word` + +**Required:** yes +**Type:** key binding +**Default:** `["alt+b", "ctrl+left"]` + +Move left a word. + +### `keys.editor.cursor.right` + +**Required:** yes +**Type:** key binding +**Default:** `["ctrl+f", "right"]` + +Move right. + +### `keys.editor.cursor.right_word` + +**Required:** yes +**Type:** key binding +**Default:** `["alt+f", "ctrl+right"]` + +Move right a word. + +### `keys.editor.cursor.start` + +**Required:** yes +**Type:** key binding +**Default:** `["ctrl+a", "home"]` + +Move to start of line. + +### `keys.editor.cursor.up` + +**Required:** yes +**Type:** key binding +**Default:** `"up"` + +Move up. + +### `keys.general.abort` + +**Required:** yes +**Type:** key binding +**Default:** `"esc"` + +Abort/close. + +### `keys.general.confirm` + +**Required:** yes +**Type:** key binding +**Default:** `"enter"` + +Confirm. + +### `keys.general.exit` + +**Required:** yes +**Type:** key binding +**Default:** `"ctrl+c"` + +Quit cove. + +### `keys.general.focus` + +**Required:** yes +**Type:** key binding +**Default:** `"tab"` + +Advance focus. + +### `keys.general.help` + +**Required:** yes +**Type:** key binding +**Default:** `"f1"` + +Show this help. + +### `keys.general.log` + +**Required:** yes +**Type:** key binding +**Default:** `"f12"` + +Show log. + +### `keys.room.action.account` + +**Required:** yes +**Type:** key binding +**Default:** `"A"` + +Manage account. + +### `keys.room.action.authenticate` + +**Required:** yes +**Type:** key binding +**Default:** `"a"` + +Authenticate. + +### `keys.room.action.more_messages` + +**Required:** yes +**Type:** key binding +**Default:** `"m"` + +Download more messages. + +### `keys.room.action.nick` + +**Required:** yes +**Type:** key binding +**Default:** `"n"` + +Change nick. + +### `keys.room.action.present` + +**Required:** yes +**Type:** key binding +**Default:** `"ctrl+p"` + +Open room's plugh.de/present page. + +### `keys.rooms.action.change_sort_order` + +**Required:** yes +**Type:** key binding +**Default:** `"s"` + +Change sort order. + +### `keys.rooms.action.connect` + +**Required:** yes +**Type:** key binding +**Default:** `"c"` + +Connect to selected room. + +### `keys.rooms.action.connect_all` + +**Required:** yes +**Type:** key binding +**Default:** `"C"` + +Connect to all rooms. + +### `keys.rooms.action.connect_autojoin` + +**Required:** yes +**Type:** key binding +**Default:** `"a"` + +Connect to all autojoin rooms. + +### `keys.rooms.action.delete` + +**Required:** yes +**Type:** key binding +**Default:** `"X"` + +Delete room. + +### `keys.rooms.action.disconnect` + +**Required:** yes +**Type:** key binding +**Default:** `"d"` + +Disconnect from selected room. + +### `keys.rooms.action.disconnect_all` + +**Required:** yes +**Type:** key binding +**Default:** `"D"` + +Disconnect from all rooms. + +### `keys.rooms.action.disconnect_non_autojoin` + +**Required:** yes +**Type:** key binding +**Default:** `"A"` + +Disconnect from all non-autojoin rooms. + +### `keys.rooms.action.new` + +**Required:** yes +**Type:** key binding +**Default:** `"n"` + +Connect to new room. + +### `keys.scroll.center_cursor` + +**Required:** yes +**Type:** key binding +**Default:** `"z"` + +Center cursor. + +### `keys.scroll.down_full` + +**Required:** yes +**Type:** key binding +**Default:** `["ctrl+f", "pagedown"]` + +Scroll down a full screen. + +### `keys.scroll.down_half` + +**Required:** yes +**Type:** key binding +**Default:** `"ctrl+d"` + +Scroll down half a screen. + +### `keys.scroll.down_line` + +**Required:** yes +**Type:** key binding +**Default:** `"ctrl+e"` + +Scroll down one line. + +### `keys.scroll.up_full` + +**Required:** yes +**Type:** key binding +**Default:** `["ctrl+b", "pageup"]` + +Scroll up a full screen. + +### `keys.scroll.up_half` + +**Required:** yes +**Type:** key binding +**Default:** `"ctrl+u"` + +Scroll up half a screen. + +### `keys.scroll.up_line` + +**Required:** yes +**Type:** key binding +**Default:** `"ctrl+y"` + +Scroll up one line. + +### `keys.tree.action.fold_tree` + +**Required:** yes +**Type:** key binding +**Default:** `"space"` + +Fold current message's subtree. + +### `keys.tree.action.inspect` + +**Required:** yes +**Type:** key binding +**Default:** `"i"` + +Inspect selected element. + +### `keys.tree.action.links` + +**Required:** yes +**Type:** key binding +**Default:** `"I"` + +List links found in message. + +### `keys.tree.action.mark_older_seen` + +**Required:** yes +**Type:** key binding +**Default:** `"ctrl+s"` + +Mark all older messages as seen. + +### `keys.tree.action.mark_visible_seen` + +**Required:** yes +**Type:** key binding +**Default:** `"S"` + +Mark all visible messages as seen. + +### `keys.tree.action.new_thread` + +**Required:** yes +**Type:** key binding +**Default:** `"t"` + +Start a new thread. + +### `keys.tree.action.reply` + +**Required:** yes +**Type:** key binding +**Default:** `"r"` + +Reply to message, inline if possible. + +### `keys.tree.action.reply_alternate` + +**Required:** yes +**Type:** key binding +**Default:** `"R"` + +Reply opposite to normal reply. + +### `keys.tree.action.toggle_seen` + +**Required:** yes +**Type:** key binding +**Default:** `"s"` + +Toggle current message's seen status. + +### `keys.tree.cursor.to_above_sibling` + +**Required:** yes +**Type:** key binding +**Default:** `["K", "ctrl+up"]` + +Move to above sibling. + +### `keys.tree.cursor.to_below_sibling` + +**Required:** yes +**Type:** key binding +**Default:** `["J", "ctrl+down"]` + +Move to below sibling. + +### `keys.tree.cursor.to_newer_message` + +**Required:** yes +**Type:** key binding +**Default:** `["l", "right"]` + +Move to newer message. + +### `keys.tree.cursor.to_newer_unseen_message` + +**Required:** yes +**Type:** key binding +**Default:** `["L", "ctrl+right"]` + +Move to newer unseen message. + +### `keys.tree.cursor.to_older_message` + +**Required:** yes +**Type:** key binding +**Default:** `["h", "left"]` + +Move to older message. + +### `keys.tree.cursor.to_older_unseen_message` + +**Required:** yes +**Type:** key binding +**Default:** `["H", "ctrl+left"]` + +Move to older unseen message. + +### `keys.tree.cursor.to_parent` + +**Required:** yes +**Type:** key binding +**Default:** `"p"` + +Move to parent. + +### `keys.tree.cursor.to_root` + +**Required:** yes +**Type:** key binding +**Default:** `"P"` + +Move to root. + +### `measure_widths` + +**Required:** yes +**Type:** boolean +**Default:** `false` + +Whether to measure the width of characters as displayed by the terminal +emulator instead of guessing the width. + +Enabling this makes rendering a bit slower but more accurate. The screen +might also flash when encountering new characters (or, more accurately, +graphemes). + +See also the `--measure-graphemes` command line option. + +### `offline` **Required:** yes **Type:** boolean @@ -76,11 +637,11 @@ pressing `a` in the rooms list. See also the `--offline` command line option. -## `rooms_sort_order` +### `rooms_sort_order` **Required:** yes **Type:** string -**Values:** `alphabet`, `importance` +**Values:** `"alphabet"`, `"importance"` **Default:** `alphabet` Initial sort order of rooms list. diff --git a/Cargo.lock b/Cargo.lock index e2bbbce..783365c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,7 +249,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cove" -version = "0.6.1" +version = "0.7.0" dependencies = [ "anyhow", "async-trait", @@ -279,7 +279,7 @@ dependencies = [ [[package]] name = "cove-config" -version = "0.6.1" +version = "0.7.0" dependencies = [ "cove-input", "cove-macro", @@ -290,7 +290,7 @@ dependencies = [ [[package]] name = "cove-input" -version = "0.6.1" +version = "0.7.0" dependencies = [ "cove-macro", "crossterm", @@ -304,7 +304,7 @@ dependencies = [ [[package]] name = "cove-macro" -version = "0.6.1" +version = "0.7.0" dependencies = [ "case", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index beacc35..8f5be76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["cove", "cove-*"] [workspace.package] -version = "0.6.1" +version = "0.7.0" edition = "2021" [workspace.dependencies] From bd874e6212350d74aac0bf95abdaf94d26c9ccf7 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 31 Aug 2023 13:27:11 +0200 Subject: [PATCH 155/266] Run cargo fmt --- cove/src/euph/room.rs | 4 +++- cove/src/ui.rs | 4 +++- cove/src/ui/chat/tree/renderer.rs | 6 ++++-- cove/src/ui/rooms.rs | 4 +++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cove/src/euph/room.rs b/cove/src/euph/room.rs index 7937180..2b7d861 100644 --- a/cove/src/euph/room.rs +++ b/cove/src/euph/room.rs @@ -209,7 +209,9 @@ impl Room { async fn on_packet(&mut self, packet: ParsedPacket) { let room_name = &self.instance.config().room; - let Ok(data) = &packet.content else { return; }; + let Ok(data) = &packet.content else { + return; + }; match data { Data::BounceEvent(_) => {} Data::DisconnectEvent(_) => {} diff --git a/cove/src/ui.rs b/cove/src/ui.rs index 0dd5f93..d16596a 100644 --- a/cove/src/ui.rs +++ b/cove/src/ui.rs @@ -130,7 +130,9 @@ impl Ui { lock: Weak<FairMutex<()>>, ) -> crossterm::Result<()> { loop { - let Some(lock) = lock.upgrade() else { return Ok(()); }; + let Some(lock) = lock.upgrade() else { + return Ok(()); + }; let _guard = lock.lock(); if crossterm::event::poll(Self::POLL_DURATION)? { let event = crossterm::event::read()?; diff --git a/cove/src/ui/chat/tree/renderer.rs b/cove/src/ui/chat/tree/renderer.rs index 6932123..e6753c7 100644 --- a/cove/src/ui/chat/tree/renderer.rs +++ b/cove/src/ui/chat/tree/renderer.rs @@ -274,7 +274,9 @@ where } async fn root_id(&self, id: &TreeBlockId<M::Id>) -> Result<Option<M::Id>, S::Error> { - let Some(id) = id.any_id() else { return Ok(None); }; + let Some(id) = id.any_id() else { + return Ok(None); + }; let path = self.store.path(id).await?; Ok(Some(path.into_first())) } @@ -480,7 +482,7 @@ where async fn expand_bottom(&mut self) -> Result<(), Self::Error> { let Some(bottom_root_id) = &self.bottom_root_id else { self.blocks.end_bottom(); - return Ok(()) + return Ok(()); }; let next_root_id = self.store.next_root_id(bottom_root_id).await?; diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index c692d46..3dfc7e1 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -554,7 +554,9 @@ impl Rooms { pub async fn handle_euph_event(&mut self, event: Event) -> bool { let room_name = event.config().room.clone(); - let Some(room) = self.euph_rooms.get_mut(&room_name) else { return false; }; + let Some(room) = self.euph_rooms.get_mut(&room_name) else { + return false; + }; let handled = room.handle_event(event).await; From 2e039a855c0bfbd79654dd3734b31c8000e57073 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 31 Aug 2023 13:46:43 +0200 Subject: [PATCH 156/266] Update dependencies --- CHANGELOG.md | 3 + Cargo.lock | 781 +++++++++++++++++++++-------------------- Cargo.toml | 8 +- cove-config/Cargo.toml | 2 +- cove-macro/Cargo.toml | 6 +- cove/Cargo.toml | 22 +- cove/src/ui.rs | 2 +- 7 files changed, 432 insertions(+), 392 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 580a811..9743feb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Procedure when bumping the version number: ## Unreleased +### Changed +- Updated dependencies + ## v0.7.0 - 2023-05-14 ### Added diff --git a/Cargo.lock b/Cargo.lock index 783365c..ffe1652 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,51 +3,71 @@ version = 3 [[package]] -name = "ahash" -version = "0.7.6" +name = "addr2line" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ - "getrandom", + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", "once_cell", "version_check", ] [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] [[package]] -name = "anstream" -version = "0.3.2" +name = "allocator-api2" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "anstream" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] @@ -58,30 +78,30 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", @@ -94,6 +114,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.13.1" @@ -102,9 +137,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" [[package]] name = "bitflags" @@ -114,9 +149,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.2.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "block-buffer" @@ -129,9 +164,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.2" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byteorder" @@ -163,9 +198,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -175,9 +213,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.2.7" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938" +checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27" dependencies = [ "clap_builder", "clap_derive", @@ -186,22 +224,21 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.7" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd" +checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d" dependencies = [ "anstream", "anstyle", - "bitflags 1.3.2", "clap_lex", "strsim", ] [[package]] name = "clap_derive" -version = "4.2.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a" dependencies = [ "heck", "proc-macro2", @@ -211,9 +248,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "colorchoice" @@ -270,7 +307,7 @@ dependencies = [ "thiserror", "time", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.20.0", "toss", "unicode-segmentation", "unicode-width", @@ -314,20 +351,20 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] [[package]] name = "crossterm" -version = "0.26.1" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", "crossterm_winapi", "libc", "mio", @@ -339,9 +376,9 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] @@ -357,10 +394,25 @@ dependencies = [ ] [[package]] -name = "digest" -version = "0.10.6" +name = "data-encoding" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +dependencies = [ + "serde", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -384,7 +436,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -399,19 +451,25 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -440,7 +498,7 @@ dependencies = [ "time", "tokio", "tokio-stream", - "tokio-tungstenite", + "tokio-tungstenite 0.18.0", "unicode-normalization", ] @@ -458,12 +516,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "fnv" @@ -473,9 +528,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -524,9 +579,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -534,19 +589,26 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "gimli" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ "ahash", + "allocator-api2", ] [[package]] name = "hashlink" -version = "0.8.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ "hashbrown", ] @@ -559,18 +621,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "http" @@ -591,9 +644,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -601,34 +654,14 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ - "autocfg", + "equivalent", "hashbrown", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" -dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "is-docker" version = "0.2.0" @@ -638,18 +671,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "is-terminal" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" -dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "is-wsl" version = "0.4.0" @@ -662,24 +683,24 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.62" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68c16e1bfd491478ab155fd8b4896b86f9ede344949b641e61501e07c2b8b4d5" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.144" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libsqlite3-sys" @@ -694,24 +715,24 @@ dependencies = [ [[package]] name = "linkify" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96dd5884008358112bc66093362197c7248ece00d46624e2cf71e50029f8cff5" +checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780" dependencies = [ "memchr", ] [[package]] name = "linux-raw-sys" -version = "0.3.7" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -719,63 +740,79 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] [[package]] -name = "once_cell" -version = "1.17.1" +name = "object" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "open" -version = "4.1.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16814a067484415fda653868c9be0ac5f2abd2ef5d951082a5f2fe1b3662944" +checksum = "cfabf1927dce4d6fdf563d63328a0a506101ced3ec780ca2135747336c98cef8" dependencies = [ "is-wsl", + "libc", "pathdiff", ] @@ -812,15 +849,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets", ] [[package]] @@ -831,15 +868,15 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -861,18 +898,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.27" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -938,9 +975,21 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.1" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" dependencies = [ "aho-corasick", "memchr", @@ -949,9 +998,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "ring" @@ -974,7 +1023,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" dependencies = [ - "bitflags 2.2.1", + "bitflags 2.4.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -984,24 +1033,29 @@ dependencies = [ ] [[package]] -name = "rustix" -version = "0.37.19" +name = "rustc-demangle" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "rustls" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" dependencies = [ "log", "ring", @@ -1010,10 +1064,22 @@ dependencies = [ ] [[package]] -name = "rustls-native-certs" -version = "0.6.2" +name = "rustls" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -1023,33 +1089,43 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.0", + "base64 0.21.3", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +dependencies = [ + "ring", + "untrusted", ] [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" @@ -1063,9 +1139,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.0" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2855b3715770894e67cbfa3df957790aa0c9edc3bf06efa1a84d77fa0839d1" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -1076,9 +1152,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -1086,9 +1162,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.163" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] @@ -1105,9 +1181,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", @@ -1126,9 +1202,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", @@ -1137,9 +1213,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" dependencies = [ "serde", ] @@ -1157,9 +1233,9 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", @@ -1187,27 +1263,27 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" -version = "0.4.9" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" dependencies = [ "libc", - "winapi", + "windows-sys", ] [[package]] @@ -1224,9 +1300,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.16" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -1235,31 +1311,31 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.5.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.3.5", "rustix", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", @@ -1268,10 +1344,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" dependencies = [ + "deranged", "itoa", "serde", "time-core", @@ -1286,9 +1363,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" dependencies = [ "time-core", ] @@ -1310,11 +1387,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.1" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", "mio", @@ -1324,7 +1401,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1344,11 +1421,21 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls", + "rustls 0.20.9", "tokio", "webpki", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.7", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -1368,19 +1455,34 @@ checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" dependencies = [ "futures-util", "log", - "rustls", + "rustls 0.20.9", "rustls-native-certs", "tokio", - "tokio-rustls", - "tungstenite", + "tokio-rustls 0.23.4", + "tungstenite 0.18.0", "webpki", ] [[package]] -name = "toml" -version = "0.7.3" +name = "tokio-tungstenite" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +checksum = "2b2dbec703c26b00d74844519606ef15d09a7d6857860f84ad223dec002ddea2" +dependencies = [ + "futures-util", + "log", + "rustls 0.21.7", + "rustls-native-certs", + "tokio", + "tokio-rustls 0.24.1", + "tungstenite 0.20.0", +] + +[[package]] +name = "toml" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" dependencies = [ "serde", "serde_spanned", @@ -1390,18 +1492,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.8" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ "indexmap", "serde", @@ -1412,8 +1514,8 @@ dependencies = [ [[package]] name = "toss" -version = "0.1.0" -source = "git+https://github.com/Garmelon/toss.git?tag=v0.1.0#87723840df5ff75666d7270e62b943621197ce62" +version = "0.2.0" +source = "git+https://github.com/Garmelon/toss.git?tag=v0.2.0#2c7888fa413c9b12bec7d55a73051aa96d59386f" dependencies = [ "async-trait", "crossterm", @@ -1435,7 +1537,7 @@ dependencies = [ "httparse", "log", "rand", - "rustls", + "rustls 0.20.9", "sha1", "thiserror", "url", @@ -1443,6 +1545,26 @@ dependencies = [ "webpki", ] +[[package]] +name = "tungstenite" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e862a1c4128df0112ab625f55cd5c934bcb4312ba80b39ae4b4835a3fd58e649" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "rustls 0.21.7", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "typenum" version = "1.16.0" @@ -1457,19 +1579,15 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-linebreak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137" -dependencies = [ - "hashbrown", - "regex", -] +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" @@ -1500,9 +1618,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -1550,9 +1668,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.85" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b6cb788c4e39112fbe1822277ef6fb3c55cd86b95cb3d3c4c1c9597e4ac74b4" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1560,9 +1678,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.85" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e522ed4105a9d626d885b35d62501b30d9666283a5c8be12c14a8bdafe7822" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", @@ -1575,9 +1693,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.85" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "358a79a0cb89d21db8120cbfb91392335913e4890665b1a7981d9e956903b434" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1585,9 +1703,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.85" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", @@ -1598,15 +1716,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.85" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a901d592cafaa4d711bc324edfaff879ac700b19c3dfd60058d2b445be2691eb" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.62" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b5f940c7edfdc6d12126d98c9ef4d1b3d470011c47c76a6581df47ad9ba721" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -1614,9 +1732,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e" dependencies = [ "ring", "untrusted", @@ -1655,158 +1773,77 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.4.6" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 8f5be76..f62cc91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,15 +7,15 @@ version = "0.7.0" edition = "2021" [workspace.dependencies] -crossterm = "0.26.1" +crossterm = "0.27.0" parking_lot = "0.12.1" -serde = { version = "1.0.163", features = ["derive"] } +serde = { version = "1.0.188", features = ["derive"] } serde_either = "0.2.1" -thiserror = "1.0.40" +thiserror = "1.0.47" [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" -tag = "v0.1.0" +tag = "v0.2.0" [profile.dev.package."*"] opt-level = 3 diff --git a/cove-config/Cargo.toml b/cove-config/Cargo.toml index a5bb70b..37ef5bd 100644 --- a/cove-config/Cargo.toml +++ b/cove-config/Cargo.toml @@ -10,4 +10,4 @@ cove-macro = { path = "../cove-macro" } serde = { workspace = true } thiserror = { workspace = true } -toml = "0.7.3" +toml = "0.7.6" diff --git a/cove-macro/Cargo.toml b/cove-macro/Cargo.toml index 840b729..07541a9 100644 --- a/cove-macro/Cargo.toml +++ b/cove-macro/Cargo.toml @@ -5,9 +5,9 @@ edition = { workspace = true } [dependencies] case = "1.0.0" -proc-macro2 = "1.0.56" -quote = "1.0.27" -syn = "2.0.16" +proc-macro2 = "1.0.66" +quote = "1.0.33" +syn = "2.0.29" [lib] proc-macro = true diff --git a/cove/Cargo.toml b/cove/Cargo.toml index ff50048..ca556a2 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -12,27 +12,27 @@ parking_lot = { workspace = true } thiserror = { workspace = true } toss = { workspace = true } -anyhow = "1.0.71" -async-trait = "0.1.68" -clap = { version = "4.2.7", features = ["derive", "deprecated"] } +anyhow = "1.0.75" +async-trait = "0.1.73" +clap = { version = "4.4.1", features = ["derive", "deprecated"] } cookie = "0.17.0" directories = "5.0.1" -linkify = "0.9.0" -log = { version = "0.4.17", features = ["std"] } -once_cell = "1.17.1" -open = "4.1.0" +linkify = "0.10.0" +log = { version = "0.4.20", features = ["std"] } +once_cell = "1.18.0" +open = "5.0.0" rusqlite = { version = "0.29.0", features = ["bundled", "time"] } -serde_json = "1.0.96" -tokio = { version = "1.28.1", features = ["full"] } +serde_json = "1.0.105" +tokio = { version = "1.32.0", features = ["full"] } unicode-segmentation = "1.10.1" unicode-width = "0.1.10" [dependencies.time] -version = "0.3.21" +version = "0.3.28" features = ["macros", "formatting", "parsing", "serde"] [dependencies.tokio-tungstenite] -version = "0.18.0" +version = "0.20.0" features = ["rustls-tls-native-roots"] [dependencies.euphoxide] diff --git a/cove/src/ui.rs b/cove/src/ui.rs index d16596a..cb85876 100644 --- a/cove/src/ui.rs +++ b/cove/src/ui.rs @@ -128,7 +128,7 @@ impl Ui { fn poll_crossterm_events( tx: UnboundedSender<UiEvent>, lock: Weak<FairMutex<()>>, - ) -> crossterm::Result<()> { + ) -> io::Result<()> { loop { let Some(lock) = lock.upgrade() else { return Ok(()); From 2983733744afa5874904d42f5af138d1fcb447b9 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 31 Aug 2023 13:55:30 +0200 Subject: [PATCH 157/266] Bump version to 0.7.1 --- CHANGELOG.md | 2 ++ Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9743feb..47d75a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Procedure when bumping the version number: ## Unreleased +## v0.7.1 - 2023-08-31 + ### Changed - Updated dependencies diff --git a/Cargo.lock b/Cargo.lock index ffe1652..b029739 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,7 +286,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cove" -version = "0.7.0" +version = "0.7.1" dependencies = [ "anyhow", "async-trait", @@ -316,7 +316,7 @@ dependencies = [ [[package]] name = "cove-config" -version = "0.7.0" +version = "0.7.1" dependencies = [ "cove-input", "cove-macro", @@ -327,7 +327,7 @@ dependencies = [ [[package]] name = "cove-input" -version = "0.7.0" +version = "0.7.1" dependencies = [ "cove-macro", "crossterm", @@ -341,7 +341,7 @@ dependencies = [ [[package]] name = "cove-macro" -version = "0.7.0" +version = "0.7.1" dependencies = [ "case", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index f62cc91..b9cff62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["cove", "cove-*"] [workspace.package] -version = "0.7.0" +version = "0.7.1" edition = "2021" [workspace.dependencies] From 39a4f29a2a4d07c9e4672898acf25d38de834b57 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 28 Dec 2023 20:05:18 +0100 Subject: [PATCH 158/266] Update dependencies --- Cargo.lock | 817 +++++++++++++++++++---------------------- Cargo.toml | 4 +- cove-config/Cargo.toml | 2 +- cove-input/Cargo.toml | 2 +- cove-macro/Cargo.toml | 4 +- cove/Cargo.toml | 28 +- 6 files changed, 406 insertions(+), 451 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b029739..1ed64bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,20 +19,21 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -45,9 +46,9 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "anstream" -version = "0.5.0" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ "anstyle", "anstyle-parse", @@ -59,49 +60,49 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "2.1.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "c9d19de80eff169429ac1e9f48fffb163916b448a44e8e046186232046d9e1f9" [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" dependencies = [ "proc-macro2", "quote", @@ -131,15 +132,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bitflags" @@ -149,9 +144,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "block-buffer" @@ -162,23 +157,17 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bumpalo" -version = "3.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" - [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "case" @@ -213,20 +202,19 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.4.1" +version = "4.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27" +checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.4.1" +version = "4.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d" +checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" dependencies = [ "anstream", "anstyle", @@ -236,9 +224,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.0" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", @@ -248,9 +236,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "colorchoice" @@ -260,9 +248,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "cookie" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +checksum = "3cd91cf61412820176e137621345ee43b3f4423e589e7ae4e50d601d93e35ef8" dependencies = [ "time", "version_check", @@ -270,9 +258,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -280,9 +268,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cove" @@ -307,7 +295,7 @@ dependencies = [ "thiserror", "time", "tokio", - "tokio-tungstenite 0.20.0", + "tokio-tungstenite", "toss", "unicode-segmentation", "unicode-width", @@ -351,9 +339,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -364,7 +352,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "crossterm_winapi", "libc", "mio", @@ -395,16 +383,17 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" dependencies = [ + "powerfmt", "serde", ] @@ -436,14 +425,14 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "edit" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c562aa71f7bc691fde4c6bf5f93ae5a5298b617c2eb44c76c87832299a17fbb4" +checksum = "f364860e764787163c8c8f58231003839be31276e821e2ad2092ddf496b1aa09" dependencies = [ "tempfile", "which", @@ -463,29 +452,18 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.3" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] name = "euphoxide" -version = "0.4.0" -source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.4.0#fa6c8cdce9dd7e5f38e333e35ca975cfcdd60cd2" +version = "0.5.0" +source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.5.0#276ff685127c4c392a2ab001f80f7a053e58746b" dependencies = [ "async-trait", "caseless", @@ -498,15 +476,15 @@ dependencies = [ "time", "tokio", "tokio-stream", - "tokio-tungstenite 0.18.0", + "tokio-tungstenite", "unicode-normalization", ] [[package]] name = "fallible-iterator" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fallible-streaming-iterator" @@ -516,9 +494,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fnv" @@ -528,36 +506,36 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-sink", @@ -579,9 +557,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", @@ -590,15 +568,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ "ahash", "allocator-api2", @@ -621,15 +599,24 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] [[package]] name = "http" -version = "0.2.9" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" dependencies = [ "bytes", "fnv", @@ -644,9 +631,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -654,9 +641,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown", @@ -683,30 +670,32 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" - -[[package]] -name = "js-sys" -version = "0.3.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" -dependencies = [ - "wasm-bindgen", -] +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall", +] [[package]] name = "libsqlite3-sys" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" dependencies = [ "cc", "pkg-config", @@ -724,15 +713,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -746,9 +735,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.6.2" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "miniz_oxide" @@ -761,21 +750,21 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -792,24 +781,24 @@ dependencies = [ [[package]] name = "object" -version = "0.32.0" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "open" -version = "5.0.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfabf1927dce4d6fdf563d63328a0a506101ced3ec780ca2135747336c98cef8" +checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349" dependencies = [ "is-wsl", "libc", @@ -830,9 +819,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-float" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" dependencies = [ "num-traits", ] @@ -849,15 +838,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -868,9 +857,9 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" @@ -886,9 +875,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" @@ -898,9 +893,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" dependencies = [ "unicode-ident", ] @@ -946,38 +941,29 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "redox_syscall 0.2.16", + "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.9.4" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -987,9 +973,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.7" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -998,32 +984,31 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "ring" -version = "0.16.20" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", + "getrandom", "libc", - "once_cell", "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys 0.48.0", ] [[package]] name = "rusqlite" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" +checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -1040,85 +1025,84 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.10" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.20.9" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" -dependencies = [ - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "rustls" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "fe6b63262c9fcac8659abfaa96cac103d28166d3ff3eaf8f412e19f3ae9e5a48" dependencies = [ "log", "ring", + "rustls-pki-types", "rustls-webpki", - "sct", + "subtle", + "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", "rustls-pemfile", + "rustls-pki-types", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" dependencies = [ - "base64 0.21.3", + "base64", + "rustls-pki-types", ] [[package]] -name = "rustls-webpki" -version = "0.101.4" +name = "rustls-pki-types" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +checksum = "9e9d979b3ce68192e42760c7810125eb6cf2ea10efae545a156063e61f314e2a" + +[[package]] +name = "rustls-webpki" +version = "0.102.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de2635c8bc2b88d367767c5de8ea1d8db9af3f6219eba28442242d9ab81d1b89" dependencies = [ "ring", + "rustls-pki-types", "untrusted", ] [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1127,16 +1111,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "security-framework" version = "2.9.2" @@ -1162,9 +1136,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -1181,9 +1155,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -1202,9 +1176,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1213,18 +1187,18 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -1272,25 +1246,25 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "strsim" @@ -1299,10 +1273,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] -name = "syn" -version = "2.0.29" +name = "subtle" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "2.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" dependencies = [ "proc-macro2", "quote", @@ -1311,31 +1291,31 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.0" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.3.5", + "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" dependencies = [ "proc-macro2", "quote", @@ -1344,12 +1324,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -1357,15 +1338,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" dependencies = [ "time-core", ] @@ -1387,9 +1368,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -1401,14 +1382,14 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", @@ -1417,22 +1398,12 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.20.9", - "tokio", - "webpki", -] - -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.7", + "rustls", + "rustls-pki-types", "tokio", ] @@ -1449,40 +1420,25 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.18.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" dependencies = [ "futures-util", "log", - "rustls 0.20.9", + "rustls", "rustls-native-certs", + "rustls-pki-types", "tokio", - "tokio-rustls 0.23.4", - "tungstenite 0.18.0", - "webpki", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2dbec703c26b00d74844519606ef15d09a7d6857860f84ad223dec002ddea2" -dependencies = [ - "futures-util", - "log", - "rustls 0.21.7", - "rustls-native-certs", - "tokio", - "tokio-rustls 0.24.1", - "tungstenite 0.20.0", + "tokio-rustls", + "tungstenite", ] [[package]] name = "toml" -version = "0.7.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" dependencies = [ "serde", "serde_spanned", @@ -1492,18 +1448,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ "indexmap", "serde", @@ -1526,30 +1482,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.18.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" -dependencies = [ - "base64 0.13.1", - "byteorder", - "bytes", - "http", - "httparse", - "log", - "rand", - "rustls 0.20.9", - "sha1", - "thiserror", - "url", - "utf-8", - "webpki", -] - -[[package]] -name = "tungstenite" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e862a1c4128df0112ab625f55cd5c934bcb4312ba80b39ae4b4835a3fd58e649" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" dependencies = [ "byteorder", "bytes", @@ -1558,7 +1493,8 @@ dependencies = [ "httparse", "log", "rand", - "rustls 0.21.7", + "rustls", + "rustls-pki-types", "sha1", "thiserror", "url", @@ -1567,21 +1503,21 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-linebreak" @@ -1606,21 +1542,21 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -1641,8 +1577,8 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "vault" -version = "0.2.0" -source = "git+https://github.com/Garmelon/vault.git?tag=v0.2.0#6fd284fed71ece886db9b8aab659f454ba4858b6" +version = "0.3.0" +source = "git+https://github.com/Garmelon/vault.git?tag=v0.3.0#6640f601f3b4eef4ed7201e9fc197cbac3228dad" dependencies = [ "rusqlite", "tokio", @@ -1666,89 +1602,16 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" - -[[package]] -name = "web-sys" -version = "0.3.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "which" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] @@ -1779,7 +1642,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -1788,13 +1660,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -1803,36 +1690,72 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -1840,10 +1763,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] -name = "winnow" -version = "0.5.15" +name = "windows_x86_64_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winnow" +version = "0.5.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c" dependencies = [ "memchr", ] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index b9cff62..b9284b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,9 +9,9 @@ edition = "2021" [workspace.dependencies] crossterm = "0.27.0" parking_lot = "0.12.1" -serde = { version = "1.0.188", features = ["derive"] } +serde = { version = "1.0.193", features = ["derive"] } serde_either = "0.2.1" -thiserror = "1.0.47" +thiserror = "1.0.52" [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" diff --git a/cove-config/Cargo.toml b/cove-config/Cargo.toml index 37ef5bd..c05257d 100644 --- a/cove-config/Cargo.toml +++ b/cove-config/Cargo.toml @@ -10,4 +10,4 @@ cove-macro = { path = "../cove-macro" } serde = { workspace = true } thiserror = { workspace = true } -toml = "0.7.6" +toml = "0.8.8" diff --git a/cove-input/Cargo.toml b/cove-input/Cargo.toml index f3dcc64..dd6d23d 100644 --- a/cove-input/Cargo.toml +++ b/cove-input/Cargo.toml @@ -13,4 +13,4 @@ serde_either = { workspace = true } thiserror = { workspace = true } toss = { workspace = true } -edit = "0.1.4" +edit = "0.1.5" diff --git a/cove-macro/Cargo.toml b/cove-macro/Cargo.toml index 07541a9..637722e 100644 --- a/cove-macro/Cargo.toml +++ b/cove-macro/Cargo.toml @@ -5,9 +5,9 @@ edition = { workspace = true } [dependencies] case = "1.0.0" -proc-macro2 = "1.0.66" +proc-macro2 = "1.0.71" quote = "1.0.33" -syn = "2.0.29" +syn = "2.0.43" [lib] proc-macro = true diff --git a/cove/Cargo.toml b/cove/Cargo.toml index ca556a2..9687080 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -12,35 +12,35 @@ parking_lot = { workspace = true } thiserror = { workspace = true } toss = { workspace = true } -anyhow = "1.0.75" -async-trait = "0.1.73" -clap = { version = "4.4.1", features = ["derive", "deprecated"] } -cookie = "0.17.0" +anyhow = "1.0.77" +async-trait = "0.1.75" +clap = { version = "4.4.12", features = ["derive", "deprecated"] } +cookie = "0.18.0" directories = "5.0.1" linkify = "0.10.0" log = { version = "0.4.20", features = ["std"] } -once_cell = "1.18.0" -open = "5.0.0" -rusqlite = { version = "0.29.0", features = ["bundled", "time"] } -serde_json = "1.0.105" -tokio = { version = "1.32.0", features = ["full"] } +once_cell = "1.19.0" +open = "5.0.1" +rusqlite = { version = "0.30.0", features = ["bundled", "time"] } +serde_json = "1.0.108" +tokio = { version = "1.35.1", features = ["full"] } unicode-segmentation = "1.10.1" -unicode-width = "0.1.10" +unicode-width = "0.1.11" [dependencies.time] -version = "0.3.28" +version = "0.3.31" features = ["macros", "formatting", "parsing", "serde"] [dependencies.tokio-tungstenite] -version = "0.20.0" +version = "0.21.0" features = ["rustls-tls-native-roots"] [dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -tag = "v0.4.0" +tag = "v0.5.0" features = ["bot"] [dependencies.vault] git = "https://github.com/Garmelon/vault.git" -tag = "v0.2.0" +tag = "v0.3.0" features = ["tokio"] From c6a1dd863212357db7a8899b063f47a8b8a8bd54 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 29 Dec 2023 00:51:47 +0100 Subject: [PATCH 159/266] Migrate vault identify rooms by their name and domain --- cove/src/vault/migrate.rs | 150 +++++++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 3 deletions(-) diff --git a/cove/src/vault/migrate.rs b/cove/src/vault/migrate.rs index e5d16da..85b9309 100644 --- a/cove/src/vault/migrate.rs +++ b/cove/src/vault/migrate.rs @@ -1,10 +1,14 @@ use rusqlite::Transaction; use vault::Migration; -pub const MIGRATIONS: [Migration; 2] = [m1, m2]; +pub const MIGRATIONS: [Migration; 3] = [m1, m2, m3]; + +fn eprint_status(nr: usize, total: usize) { + eprintln!("Migrating vault from {} to {} (out of {total})", nr, nr + 1); +} fn m1(tx: &mut Transaction<'_>, nr: usize, total: usize) -> rusqlite::Result<()> { - eprintln!("Migrating vault from {} to {} (out of {total})", nr, nr + 1); + eprint_status(nr, total); tx.execute_batch( " CREATE TABLE euph_rooms ( @@ -67,7 +71,7 @@ fn m1(tx: &mut Transaction<'_>, nr: usize, total: usize) -> rusqlite::Result<()> } fn m2(tx: &mut Transaction<'_>, nr: usize, total: usize) -> rusqlite::Result<()> { - eprintln!("Migrating vault from {} to {} (out of {total})", nr, nr + 1); + eprint_status(nr, total); tx.execute_batch( " ALTER TABLE euph_msgs @@ -78,3 +82,143 @@ fn m2(tx: &mut Transaction<'_>, nr: usize, total: usize) -> rusqlite::Result<()> ", ) } + +fn m3(tx: &mut Transaction<'_>, nr: usize, total: usize) -> rusqlite::Result<()> { + eprint_status(nr, total); + println!(" This migration might take quite a while."); + println!(" Aborting it will not corrupt your vault."); + + // Rooms should be identified not just via their name but also their domain. + // The domain should be required but there should be no default value. + // + // To accomplish this, we need to recreate and repopulate all euph related + // tables because SQLite's ALTER TABLE is not powerful enough. + + eprintln!(" Preparing tables..."); + tx.execute_batch( + " + DROP INDEX euph_idx_msgs_room_id_parent; + DROP INDEX euph_idx_msgs_room_parent_id; + DROP INDEX euph_idx_msgs_room_id_seen; + + ALTER TABLE euph_rooms RENAME TO old_euph_rooms; + ALTER TABLE euph_msgs RENAME TO old_euph_msgs; + ALTER TABLE euph_spans RENAME TO old_euph_spans; + ALTER TABLE euph_cookies RENAME TO old_euph_cookies; + + CREATE TABLE euph_rooms ( + domain TEXT NOT NULL, + room TEXT NOT NULL, + first_joined INT NOT NULL, + last_joined INT NOT NULL, + + PRIMARY KEY (domain, room) + ) STRICT; + + CREATE TABLE euph_msgs ( + domain TEXT NOT NULL, + room TEXT NOT NULL, + seen INT NOT NULL, + + -- Message + id INT NOT NULL, + parent INT, + previous_edit_id INT, + time INT NOT NULL, + content TEXT NOT NULL, + encryption_key_id TEXT, + edited INT, + deleted INT, + truncated INT NOT NULL, + + -- SessionView + user_id TEXT NOT NULL, + name TEXT, + server_id TEXT NOT NULL, + server_era TEXT NOT NULL, + session_id TEXT NOT NULL, + is_staff INT NOT NULL, + is_manager INT NOT NULL, + client_address TEXT, + real_client_address TEXT, + + PRIMARY KEY (domain, room, id), + FOREIGN KEY (domain, room) REFERENCES euph_rooms (domain, room) + ON DELETE CASCADE + ) STRICT; + + CREATE TABLE euph_spans ( + domain TEXT NOT NULL, + room TEXT NOT NULL, + start INT, + end INT, + + UNIQUE (room, domain, start, end), + FOREIGN KEY (domain, room) REFERENCES euph_rooms (domain, room) + ON DELETE CASCADE, + CHECK (start IS NULL OR end IS NOT NULL) + ) STRICT; + + CREATE TABLE euph_cookies ( + domain TEXT NOT NULL, + cookie TEXT NOT NULL + ) STRICT; + ", + )?; + + eprintln!(" Migrating data..."); + tx.execute_batch( + " + INSERT INTO euph_rooms (domain, room, first_joined, last_joined) + SELECT 'euphoria.io', room, first_joined, last_joined + FROM old_euph_rooms; + + INSERT INTO euph_msgs ( + domain, room, seen, + id, parent, previous_edit_id, time, content, encryption_key_id, edited, deleted, truncated, + user_id, name, server_id, server_era, session_id, is_staff, is_manager, client_address, real_client_address + ) + SELECT + 'euphoria.io', room, seen, + id, parent, previous_edit_id, time, content, encryption_key_id, edited, deleted, truncated, + user_id, name, server_id, server_era, session_id, is_staff, is_manager, client_address, real_client_address + FROM old_euph_msgs; + + INSERT INTO euph_spans (domain, room, start, end) + SELECT 'euphoria.io', room, start, end + FROM old_euph_spans; + + INSERT INTO euph_cookies (domain, cookie) + SELECT 'euphoria.io', cookie + FROM old_euph_cookies; + ", + )?; + + eprintln!(" Recreating indices..."); + tx.execute_batch( + " + CREATE INDEX euph_idx_msgs_domain_room_id_parent + ON euph_msgs (domain, room, id, parent); + + CREATE INDEX euph_idx_msgs_domain_room_parent_id + ON euph_msgs (domain, room, parent, id); + + CREATE INDEX euph_idx_msgs_domain_room_id_seen + ON euph_msgs (domain, room, id, seen); + ", + )?; + + eprintln!(" Cleaning up loose ends..."); + tx.execute_batch( + " + DROP TABLE old_euph_rooms; + DROP TABLE old_euph_msgs; + DROP TABLE old_euph_spans; + DROP TABLE old_euph_cookies; + + ANALYZE; + ", + )?; + + Ok(()) +} From 076c8f1a72c9d62b7c5019b02986e27756318dda Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 29 Dec 2023 00:51:57 +0100 Subject: [PATCH 160/266] Include domain in temporary tables --- cove/src/vault.rs | 2 -- cove/src/vault/prepare.rs | 57 +++++++++++++++++++++++---------------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/cove/src/vault.rs b/cove/src/vault.rs index 921ad4d..7a7e4ba 100644 --- a/cove/src/vault.rs +++ b/cove/src/vault.rs @@ -50,8 +50,6 @@ fn launch_from_connection(conn: Connection, ephemeral: bool) -> rusqlite::Result conn.pragma_update(None, "foreign_keys", true)?; conn.pragma_update(None, "trusted_schema", false)?; - eprintln!("Opening vault"); - let tokio_vault = TokioVault::launch_and_prepare(conn, &migrate::MIGRATIONS, prepare::prepare)?; Ok(Vault { tokio_vault, diff --git a/cove/src/vault/prepare.rs b/cove/src/vault/prepare.rs index c990e26..8bbcb2b 100644 --- a/cove/src/vault/prepare.rs +++ b/cove/src/vault/prepare.rs @@ -1,28 +1,32 @@ use rusqlite::Connection; pub fn prepare(conn: &mut Connection) -> rusqlite::Result<()> { + eprintln!("Preparing vault"); + // Cache ids of tree roots. conn.execute_batch( " CREATE TEMPORARY TABLE euph_trees ( + domain TEXT NOT NULL, room TEXT NOT NULL, id INT NOT NULL, - PRIMARY KEY (room, id) + PRIMARY KEY (domain, room, id) ) STRICT; - INSERT INTO euph_trees (room, id) - SELECT room, id + INSERT INTO euph_trees (domain, room, id) + SELECT domain, room, id FROM euph_msgs WHERE parent IS NULL UNION - SELECT room, parent + SELECT domain, room, parent FROM euph_msgs WHERE parent IS NOT NULL AND NOT EXISTS( SELECT * FROM euph_msgs AS parents - WHERE parents.room = euph_msgs.room + WHERE parents.domain = euph_msgs.domain + AND parents.room = euph_msgs.room AND parents.id = euph_msgs.parent ); @@ -30,15 +34,16 @@ pub fn prepare(conn: &mut Connection) -> rusqlite::Result<()> { AFTER DELETE ON main.euph_rooms BEGIN DELETE FROM euph_trees - WHERE room = old.room; + WHERE domain = old.domain + AND room = old.room; END; CREATE TEMPORARY TRIGGER et_insert_msg_without_parent AFTER INSERT ON main.euph_msgs WHEN new.parent IS NULL BEGIN - INSERT OR IGNORE INTO euph_trees (room, id) - VALUES (new.room, new.id); + INSERT OR IGNORE INTO euph_trees (domain, room, id) + VALUES (new.domain, new.room, new.id); END; CREATE TEMPORARY TRIGGER et_insert_msg_with_parent @@ -46,16 +51,18 @@ pub fn prepare(conn: &mut Connection) -> rusqlite::Result<()> { WHEN new.parent IS NOT NULL BEGIN DELETE FROM euph_trees - WHERE room = new.room + WHERE domain = new.domain + AND room = new.room AND id = new.id; - INSERT OR IGNORE INTO euph_trees (room, id) + INSERT OR IGNORE INTO euph_trees (domain, room, id) SELECT * - FROM (VALUES (new.room, new.parent)) + FROM (VALUES (new.domain, new.room, new.parent)) WHERE NOT EXISTS( SELECT * FROM euph_msgs - WHERE room = new.room + WHERE domain = new.domain + AND room = new.room AND id = new.parent AND parent IS NOT NULL ); @@ -67,35 +74,37 @@ pub fn prepare(conn: &mut Connection) -> rusqlite::Result<()> { conn.execute_batch( " CREATE TEMPORARY TABLE euph_unseen_counts ( + domain TEXT NOT NULL, room TEXT NOT NULL, amount INTEGER NOT NULL, - PRIMARY KEY (room) + PRIMARY KEY (domain, room) ) STRICT; -- There must be an entry for every existing room. - INSERT INTO euph_unseen_counts (room, amount) - SELECT room, 0 + INSERT INTO euph_unseen_counts (domain, room, amount) + SELECT domain, room, 0 FROM euph_rooms; - INSERT OR REPLACE INTO euph_unseen_counts (room, amount) - SELECT room, COUNT(*) + INSERT OR REPLACE INTO euph_unseen_counts (domain, room, amount) + SELECT domain, room, COUNT(*) FROM euph_msgs WHERE NOT seen - GROUP BY room; + GROUP BY domain, room; CREATE TEMPORARY TRIGGER euc_insert_room AFTER INSERT ON main.euph_rooms BEGIN - INSERT INTO euph_unseen_counts (room, amount) - VALUES (new.room, 0); + INSERT INTO euph_unseen_counts (domain, room, amount) + VALUES (new.domain, new.room, 0); END; CREATE TEMPORARY TRIGGER euc_delete_room AFTER DELETE ON main.euph_rooms BEGIN DELETE FROM euph_unseen_counts - WHERE room = old.room; + WHERE domain = old.domain + AND room = old.room; END; CREATE TEMPORARY TRIGGER euc_insert_msg @@ -104,7 +113,8 @@ pub fn prepare(conn: &mut Connection) -> rusqlite::Result<()> { BEGIN UPDATE euph_unseen_counts SET amount = amount + 1 - WHERE room = new.room; + WHERE domain = new.domain + AND room = new.room; END; CREATE TEMPORARY TRIGGER euc_update_msg @@ -113,7 +123,8 @@ pub fn prepare(conn: &mut Connection) -> rusqlite::Result<()> { BEGIN UPDATE euph_unseen_counts SET amount = CASE WHEN new.seen THEN amount - 1 ELSE amount + 1 END - WHERE room = new.room; + WHERE domain = new.domain + AND room = new.room; END; ", )?; From 6b7ab3584aa051ddc491312a97cb52812e714b7c Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 31 Dec 2023 20:15:13 +0100 Subject: [PATCH 161/266] Switch domain mentions to euphoria.leet.nu --- CHANGELOG.md | 6 ++++++ README.md | 2 +- flake.nix | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47d75a8..366a11a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,12 @@ Procedure when bumping the version number: ## Unreleased +### Added +- Support for multiple euph domains + +### Changed +- Switch default euph domain to https://euphoria.leet.nu/ + ## v0.7.1 - 2023-08-31 ### Changed diff --git a/README.md b/README.md index e5ee2c8..e99e545 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # cove -Cove is a TUI client for [euphoria.io](https://euphoria.io/), a threaded +Cove is a TUI client for [euphoria.leet.nu](https://euphoria.leet.nu/), a threaded real-time chat platform. ![A very meta screenshot](screenshot.png) diff --git a/flake.nix b/flake.nix index 707e335..286f9b7 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "TUI client for euphoria.io, a threaded real-time chat platform"; + description = "TUI client for euphoria.leet.nu, a threaded real-time chat platform"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs"; From da1d23646a8774dd812b8627b6bd07d636bb550d Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 31 Dec 2023 20:16:16 +0100 Subject: [PATCH 162/266] Migrate euph vault to respect domain --- cove/src/vault.rs | 2 +- cove/src/vault/euph.rs | 358 +++++++++++++++++++++++++---------------- 2 files changed, 221 insertions(+), 139 deletions(-) diff --git a/cove/src/vault.rs b/cove/src/vault.rs index 7a7e4ba..6861901 100644 --- a/cove/src/vault.rs +++ b/cove/src/vault.rs @@ -9,7 +9,7 @@ use rusqlite::Connection; use vault::tokio::TokioVault; use vault::Action; -pub use self::euph::{EuphRoomVault, EuphVault}; +pub use self::euph::{EuphRoomVault, EuphVault, RoomIdentifier}; #[derive(Debug, Clone)] pub struct Vault { diff --git a/cove/src/vault/euph.rs b/cove/src/vault/euph.rs index e9f363e..6f23261 100644 --- a/cove/src/vault/euph.rs +++ b/cove/src/vault/euph.rs @@ -1,5 +1,5 @@ -use std::mem; use std::str::FromStr; +use std::{fmt, mem}; use async_trait::async_trait; use cookie::{Cookie, CookieJar}; @@ -12,10 +12,6 @@ use vault::Action; use crate::euph::SmallMessage; use crate::store::{MsgStore, Path, Tree}; -/////////////////// -// Wrapper types // -/////////////////// - /// Wrapper for [`Snowflake`] that implements useful rusqlite traits. struct WSnowflake(Snowflake); @@ -50,6 +46,24 @@ impl FromSql for WTime { } } +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct RoomIdentifier { + pub domain: String, + pub name: String, +} + +impl fmt::Display for RoomIdentifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "&{}@{}", self.name, self.domain) + } +} + +impl RoomIdentifier { + pub fn new(domain: String, name: String) -> Self { + Self { domain, name } + } +} + /////////////// // EuphVault // /////////////// @@ -68,10 +82,10 @@ impl EuphVault { &self.vault } - pub fn room(&self, name: String) -> EuphRoomVault { + pub fn room(&self, room: RoomIdentifier) -> EuphRoomVault { EuphRoomVault { vault: self.clone(), - room: name, + room, } } } @@ -97,9 +111,9 @@ macro_rules! euph_vault_actions { } euph_vault_actions! { - GetCookies : cookies() -> CookieJar; - SetCookies : set_cookies(cookies: CookieJar) -> (); - GetRooms : rooms() -> Vec<String>; + GetCookies : cookies(domain: String) -> CookieJar; + SetCookies : set_cookies(domain: String, cookies: CookieJar) -> (); + GetRooms : rooms() -> Vec<RoomIdentifier>; } impl Action for GetCookies { @@ -112,9 +126,10 @@ impl Action for GetCookies { " SELECT cookie FROM euph_cookies + WHERE domain = ? ", )? - .query_map([], |row| { + .query_map([self.domain], |row| { let cookie_str: String = row.get(0)?; Ok(Cookie::from_str(&cookie_str).expect("cookie in db is valid")) })? @@ -137,16 +152,21 @@ impl Action for SetCookies { // Since euphoria sets all cookies on every response, we can just delete // all previous cookies. - tx.execute_batch("DELETE FROM euph_cookies")?; + tx.execute( + " + DELETE FROM euph_cookies + WHERE domain = ?", + [&self.domain], + )?; let mut insert_cookie = tx.prepare( " - INSERT INTO euph_cookies (cookie) - VALUES (?) + INSERT INTO euph_cookies (domain, cookie) + VALUES (?, ?) ", )?; for cookie in self.cookies.iter() { - insert_cookie.execute([format!("{cookie}")])?; + insert_cookie.execute([self.domain, format!("{cookie}")])?; } drop(insert_cookie); @@ -156,17 +176,22 @@ impl Action for SetCookies { } impl Action for GetRooms { - type Output = Vec<String>; + type Output = Vec<RoomIdentifier>; type Error = rusqlite::Error; fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { conn.prepare( " - SELECT room + SELECT room, domain FROM euph_rooms ", )? - .query_map([], |row| row.get(0))? + .query_map([], |row| { + Ok(RoomIdentifier { + domain: row.get(0)?, + name: row.get(1)?, + }) + })? .collect::<rusqlite::Result<_>>() } } @@ -178,7 +203,7 @@ impl Action for GetRooms { #[derive(Debug, Clone)] pub struct EuphRoomVault { vault: EuphVault, - room: String, + room: RoomIdentifier, } impl EuphRoomVault { @@ -186,7 +211,7 @@ impl EuphRoomVault { &self.vault } - pub fn room(&self) -> &str { + pub fn room(&self) -> &RoomIdentifier { &self.room } } @@ -197,7 +222,7 @@ macro_rules! euph_room_vault_actions { )* ) => { $( struct $struct { - room: String, + room: RoomIdentifier, $( $arg: $arg_ty, )* } )* @@ -253,12 +278,16 @@ impl Action for Join { fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { conn.execute( " - INSERT INTO euph_rooms (room, first_joined, last_joined) - VALUES (:room, :time, :time) - ON CONFLICT (room) DO UPDATE + INSERT INTO euph_rooms (domain, room, first_joined, last_joined) + VALUES (:domain, :room, :time, :time) + ON CONFLICT (domain, room) DO UPDATE SET last_joined = :time ", - named_params! {":room": self.room, ":time": WTime(self.time)}, + named_params! { + ":domain": self.room.domain, + ":room": self.room.name, + ":time": WTime(self.time), + }, )?; Ok(()) } @@ -272,9 +301,10 @@ impl Action for Delete { conn.execute( " DELETE FROM euph_rooms - WHERE room = ? + WHERE domain = ? + AND room = ? ", - [&self.room], + [&self.room.domain, &self.room.name], )?; Ok(()) } @@ -282,29 +312,33 @@ impl Action for Delete { fn insert_msgs( tx: &Transaction<'_>, - room: &str, + room: &RoomIdentifier, own_user_id: &Option<UserId>, msgs: Vec<Message>, ) -> rusqlite::Result<()> { let mut insert_msg = tx.prepare( " INSERT INTO euph_msgs ( - room, id, parent, previous_edit_id, time, content, encryption_key_id, edited, deleted, truncated, + domain, room, + id, parent, previous_edit_id, time, content, encryption_key_id, edited, deleted, truncated, user_id, name, server_id, server_era, session_id, is_staff, is_manager, client_address, real_client_address, seen ) VALUES ( - :room, :id, :parent, :previous_edit_id, :time, :content, :encryption_key_id, :edited, :deleted, :truncated, + :domain, :room, + :id, :parent, :previous_edit_id, :time, :content, :encryption_key_id, :edited, :deleted, :truncated, :user_id, :name, :server_id, :server_era, :session_id, :is_staff, :is_manager, :client_address, :real_client_address, (:user_id == :own_user_id OR EXISTS( SELECT 1 FROM euph_rooms - WHERE room = :room + WHERE domain = :domain + AND room = :room AND :time < first_joined )) ) - ON CONFLICT (room, id) DO UPDATE + ON CONFLICT (domain, room, id) DO UPDATE SET + domain = :domain, room = :room, id = :id, parent = :parent, @@ -331,7 +365,8 @@ fn insert_msgs( let own_user_id = own_user_id.as_ref().map(|u| &u.0); for msg in msgs { insert_msg.execute(named_params! { - ":room": room, + ":domain": room.domain, + ":room": room.name, ":id": WSnowflake(msg.id.0), ":parent": msg.parent.map(|id| WSnowflake(id.0)), ":previous_edit_id": msg.previous_edit_id.map(WSnowflake), @@ -359,7 +394,7 @@ fn insert_msgs( fn add_span( tx: &Transaction<'_>, - room: &str, + room: &RoomIdentifier, start: Option<MessageId>, end: Option<MessageId>, ) -> rusqlite::Result<()> { @@ -369,10 +404,11 @@ fn add_span( " SELECT start, end FROM euph_spans - WHERE room = ? + WHERE domain = ? + AND room = ? ", )? - .query_map([room], |row| { + .query_map([&room.domain, &room.name], |row| { let start = row.get::<_, Option<WSnowflake>>(0)?.map(|s| MessageId(s.0)); let end = row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)); Ok((start, end)) @@ -412,21 +448,23 @@ fn add_span( tx.execute( " DELETE FROM euph_spans - WHERE room = ? + WHERE domain = ? + AND room = ? ", - [room], + [&room.domain, &room.name], )?; // Re-insert combined spans for the room let mut stmt = tx.prepare( " - INSERT INTO euph_spans (room, start, end) - VALUES (?, ?, ?) + INSERT INTO euph_spans (domain, room, start, end) + VALUES (?, ?, ?, ?) ", )?; for (start, end) in result { stmt.execute(params![ - room, + room.domain, + room.name, start.map(|id| WSnowflake(id.0)), end.map(|id| WSnowflake(id.0)) ])?; @@ -485,12 +523,13 @@ impl Action for GetLastSpan { " SELECT start, end FROM euph_spans - WHERE room = ? + WHERE domain = ? + AND room = ? ORDER BY start DESC LIMIT 1 ", )? - .query_row([self.room], |row| { + .query_row([&self.room.domain, &self.room.name], |row| { Ok(( row.get::<_, Option<WSnowflake>>(0)?.map(|s| MessageId(s.0)), row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)), @@ -510,12 +549,12 @@ impl Action for GetPath { .prepare( " WITH RECURSIVE - path (room, id) AS ( - VALUES (?, ?) + path (domain, room, id) AS ( + VALUES (?, ?, ?) UNION - SELECT room, parent + SELECT domain, room, parent FROM euph_msgs - JOIN path USING (room, id) + JOIN path USING (domain, room, id) ) SELECT id FROM path @@ -523,9 +562,10 @@ impl Action for GetPath { ORDER BY id ASC ", )? - .query_map(params![self.room, WSnowflake(self.id.0)], |row| { - row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) - })? + .query_map( + params![self.room.domain, self.room.name, WSnowflake(self.id.0)], + |row| row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)), + )? .collect::<rusqlite::Result<_>>()?; Ok(Path::new(path)) } @@ -541,10 +581,11 @@ impl Action for GetMsg { " SELECT id, parent, time, name, content, seen FROM euph_msgs - WHERE room = ? + WHERE domain = ? + AND room = ? AND id = ? ", - params![self.room, WSnowflake(self.id.0)], + params![self.room.domain, self.room.name, WSnowflake(self.id.0)], |row| { Ok(SmallMessage { id: MessageId(row.get::<_, WSnowflake>(0)?.0), @@ -572,36 +613,40 @@ impl Action for GetFullMsg { id, parent, previous_edit_id, time, content, encryption_key_id, edited, deleted, truncated, user_id, name, server_id, server_era, session_id, is_staff, is_manager, client_address, real_client_address FROM euph_msgs - WHERE room = ? + WHERE domain = ? + AND room = ? AND id = ? " )?; let msg = query - .query_row(params![self.room, WSnowflake(self.id.0)], |row| { - Ok(Message { - id: MessageId(row.get::<_, WSnowflake>(0)?.0), - parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)), - previous_edit_id: row.get::<_, Option<WSnowflake>>(2)?.map(|s| s.0), - time: row.get::<_, WTime>(3)?.0, - content: row.get(4)?, - encryption_key_id: row.get(5)?, - edited: row.get::<_, Option<WTime>>(6)?.map(|t| t.0), - deleted: row.get::<_, Option<WTime>>(7)?.map(|t| t.0), - truncated: row.get(8)?, - sender: SessionView { - id: UserId(row.get(9)?), - name: row.get(10)?, - server_id: row.get(11)?, - server_era: row.get(12)?, - session_id: SessionId(row.get(13)?), - is_staff: row.get(14)?, - is_manager: row.get(15)?, - client_address: row.get(16)?, - real_client_address: row.get(17)?, - }, - }) - }) + .query_row( + params![self.room.domain, self.room.name, WSnowflake(self.id.0)], + |row| { + Ok(Message { + id: MessageId(row.get::<_, WSnowflake>(0)?.0), + parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)), + previous_edit_id: row.get::<_, Option<WSnowflake>>(2)?.map(|s| s.0), + time: row.get::<_, WTime>(3)?.0, + content: row.get(4)?, + encryption_key_id: row.get(5)?, + edited: row.get::<_, Option<WTime>>(6)?.map(|t| t.0), + deleted: row.get::<_, Option<WTime>>(7)?.map(|t| t.0), + truncated: row.get(8)?, + sender: SessionView { + id: UserId(row.get(9)?), + name: row.get(10)?, + server_id: row.get(11)?, + server_era: row.get(12)?, + session_id: SessionId(row.get(13)?), + is_staff: row.get(14)?, + is_manager: row.get(15)?, + client_address: row.get(16)?, + real_client_address: row.get(17)?, + }, + }) + }, + ) .optional()?; Ok(msg) } @@ -616,31 +661,35 @@ impl Action for GetTree { .prepare( " WITH RECURSIVE - tree (room, id) AS ( - VALUES (?, ?) + tree (domain, room, id) AS ( + VALUES (?, ?, ?) UNION - SELECT euph_msgs.room, euph_msgs.id + SELECT euph_msgs.domain, euph_msgs.room, euph_msgs.id FROM euph_msgs JOIN tree - ON tree.room = euph_msgs.room + ON tree.domain = euph_msgs.domain + AND tree.room = euph_msgs.room AND tree.id = euph_msgs.parent ) SELECT id, parent, time, name, content, seen FROM euph_msgs - JOIN tree USING (room, id) + JOIN tree USING (domain, room, id) ORDER BY id ASC ", )? - .query_map(params![self.room, WSnowflake(self.root_id.0)], |row| { - Ok(SmallMessage { - id: MessageId(row.get::<_, WSnowflake>(0)?.0), - parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)), - time: row.get::<_, WTime>(2)?.0, - nick: row.get(3)?, - content: row.get(4)?, - seen: row.get(5)?, - }) - })? + .query_map( + params![self.room.domain, self.room.name, WSnowflake(self.root_id.0)], + |row| { + Ok(SmallMessage { + id: MessageId(row.get::<_, WSnowflake>(0)?.0), + parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)), + time: row.get::<_, WTime>(2)?.0, + nick: row.get(3)?, + content: row.get(4)?, + seen: row.get(5)?, + }) + }, + )? .collect::<rusqlite::Result<_>>()?; Ok(Tree::new(self.root_id, msgs)) } @@ -656,12 +705,13 @@ impl Action for GetFirstRootId { " SELECT id FROM euph_trees - WHERE room = ? + WHERE domain = ? + AND room = ? ORDER BY id ASC LIMIT 1 ", )? - .query_row([self.room], |row| { + .query_row([&self.room.domain, &self.room.name], |row| { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; @@ -679,12 +729,13 @@ impl Action for GetLastRootId { " SELECT id FROM euph_trees - WHERE room = ? + WHERE domain = ? + AND room = ? ORDER BY id DESC LIMIT 1 ", )? - .query_row([self.room], |row| { + .query_row([&self.room.domain, &self.room.name], |row| { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; @@ -702,15 +753,17 @@ impl Action for GetPrevRootId { " SELECT id FROM euph_trees - WHERE room = ? + WHERE domain = ? + AND room = ? AND id < ? ORDER BY id DESC LIMIT 1 ", )? - .query_row(params![self.room, WSnowflake(self.root_id.0)], |row| { - row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) - }) + .query_row( + params![self.room.domain, self.room.name, WSnowflake(self.root_id.0)], + |row| row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)), + ) .optional()?; Ok(root_id) } @@ -726,15 +779,17 @@ impl Action for GetNextRootId { " SELECT id FROM euph_trees - WHERE room = ? + WHERE domain = ? + AND room = ? AND id > ? ORDER BY id ASC LIMIT 1 ", )? - .query_row(params![self.room, WSnowflake(self.root_id.0)], |row| { - row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) - }) + .query_row( + params![self.room.domain, self.room.name, WSnowflake(self.root_id.0)], + |row| row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)), + ) .optional()?; Ok(root_id) } @@ -750,12 +805,13 @@ impl Action for GetOldestMsgId { " SELECT id FROM euph_msgs - WHERE room = ? + WHERE domain = ? + AND room = ? ORDER BY id ASC LIMIT 1 ", )? - .query_row([self.room], |row| { + .query_row([&self.room.domain, &self.room.name], |row| { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; @@ -773,12 +829,13 @@ impl Action for GetNewestMsgId { " SELECT id FROM euph_msgs - WHERE room = ? + WHERE domain = ? + AND room = ? ORDER BY id DESC LIMIT 1 ", )? - .query_row([self.room], |row| { + .query_row([&self.room.domain, &self.room.name], |row| { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; @@ -796,15 +853,17 @@ impl Action for GetOlderMsgId { " SELECT id FROM euph_msgs - WHERE room = ? + WHERE domain = ? + AND room = ? AND id < ? ORDER BY id DESC LIMIT 1 ", )? - .query_row(params![self.room, WSnowflake(self.id.0)], |row| { - row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) - }) + .query_row( + params![self.room.domain, self.room.name, WSnowflake(self.id.0)], + |row| row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)), + ) .optional()?; Ok(msg_id) } @@ -819,15 +878,17 @@ impl Action for GetNewerMsgId { " SELECT id FROM euph_msgs - WHERE room = ? + WHERE domain = ? + AND room = ? AND id > ? ORDER BY id ASC LIMIT 1 ", )? - .query_row(params![self.room, WSnowflake(self.id.0)], |row| { - row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) - }) + .query_row( + params![self.room.domain, self.room.name, WSnowflake(self.id.0)], + |row| row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)), + ) .optional()?; Ok(msg_id) } @@ -843,13 +904,14 @@ impl Action for GetOldestUnseenMsgId { " SELECT id FROM euph_msgs - WHERE room = ? + WHERE domain = ? + AND room = ? AND NOT seen ORDER BY id ASC LIMIT 1 ", )? - .query_row([self.room], |row| { + .query_row([&self.room.domain, &self.room.name], |row| { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; @@ -867,13 +929,14 @@ impl Action for GetNewestUnseenMsgId { " SELECT id FROM euph_msgs - WHERE room = ? + WHERE domain = ? + AND room = ? AND NOT seen ORDER BY id DESC LIMIT 1 ", )? - .query_row([self.room], |row| { + .query_row([&self.room.domain, &self.room.name], |row| { row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) }) .optional()?; @@ -891,16 +954,18 @@ impl Action for GetOlderUnseenMsgId { " SELECT id FROM euph_msgs - WHERE room = ? + WHERE domain = ? + AND room = ? AND NOT seen AND id < ? ORDER BY id DESC LIMIT 1 ", )? - .query_row(params![self.room, WSnowflake(self.id.0)], |row| { - row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) - }) + .query_row( + params![self.room.domain, self.room.name, WSnowflake(self.id.0)], + |row| row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)), + ) .optional()?; Ok(msg_id) } @@ -916,16 +981,18 @@ impl Action for GetNewerUnseenMsgId { " SELECT id FROM euph_msgs - WHERE room = ? + WHERE domain = ? + AND room = ? AND NOT seen AND id > ? ORDER BY id ASC LIMIT 1 ", )? - .query_row(params![self.room, WSnowflake(self.id.0)], |row| { - row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)) - }) + .query_row( + params![self.room.domain, self.room.name, WSnowflake(self.id.0)], + |row| row.get::<_, WSnowflake>(0).map(|s| MessageId(s.0)), + ) .optional()?; Ok(msg_id) } @@ -941,10 +1008,11 @@ impl Action for GetUnseenMsgsCount { " SELECT amount FROM euph_unseen_counts - WHERE room = ? + WHERE domain = ? + AND room = ? ", )? - .query_row(params![self.room], |row| row.get(0)) + .query_row(params![self.room.domain, self.room.name], |row| row.get(0)) .optional()? .unwrap_or(0); Ok(amount) @@ -960,10 +1028,16 @@ impl Action for SetSeen { " UPDATE euph_msgs SET seen = :seen - WHERE room = :room + WHERE domain = :domain + AND room = :room AND id = :id ", - named_params! { ":room": self.room, ":id": WSnowflake(self.id.0), ":seen": self.seen }, + named_params! { + ":domain": self.room.domain, + ":room": self.room.name, + ":id": WSnowflake(self.id.0), + ":seen": self.seen, + }, )?; Ok(()) } @@ -978,11 +1052,17 @@ impl Action for SetOlderSeen { " UPDATE euph_msgs SET seen = :seen - WHERE room = :room + WHERE domain = :domain + AND room = :room AND id <= :id AND seen != :seen ", - named_params! { ":room": self.room, ":id": WSnowflake(self.id.0), ":seen": self.seen }, + named_params! { + ":domain": self.room.domain, + ":room": self.room.name, + ":id": WSnowflake(self.id.0), + ":seen": self.seen, + }, )?; Ok(()) } @@ -1024,12 +1104,13 @@ impl Action for GetChunkAfter { id, parent, previous_edit_id, time, content, encryption_key_id, edited, deleted, truncated, user_id, name, server_id, server_era, session_id, is_staff, is_manager, client_address, real_client_address FROM euph_msgs - WHERE room = ? + WHERE domain = ? + AND room = ? AND id > ? ORDER BY id ASC LIMIT ? ")? - .query_map(params![self.room, WSnowflake(id.0), self.amount], row2msg)? + .query_map(params![self.room.domain, self.room.name, WSnowflake(id.0), self.amount], row2msg)? .collect::<rusqlite::Result<_>>()? } else { conn.prepare(" @@ -1037,11 +1118,12 @@ impl Action for GetChunkAfter { id, parent, previous_edit_id, time, content, encryption_key_id, edited, deleted, truncated, user_id, name, server_id, server_era, session_id, is_staff, is_manager, client_address, real_client_address FROM euph_msgs - WHERE room = ? + WHERE domain = ? + AND room = ? ORDER BY id ASC LIMIT ? ")? - .query_map(params![self.room, self.amount], row2msg)? + .query_map(params![self.room.domain, self.room.name, self.amount], row2msg)? .collect::<rusqlite::Result<_>>()? }; From 2bbfca7002f7d46a027d0f500865ce480d7bf0dd Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 31 Dec 2023 20:17:12 +0100 Subject: [PATCH 163/266] Respect domain in euph room --- cove/src/euph/room.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cove/src/euph/room.rs b/cove/src/euph/room.rs index 2b7d861..22bebb1 100644 --- a/cove/src/euph/room.rs +++ b/cove/src/euph/room.rs @@ -1,4 +1,5 @@ // TODO Stop if room does not exist (e.g. 404) +// TODO Remove rl2dev-specific code use std::convert::Infallible; use std::time::Duration; @@ -19,6 +20,7 @@ use crate::vault::EuphRoomVault; const LOG_INTERVAL: Duration = Duration::from_secs(10); +#[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum State { Disconnected, @@ -69,7 +71,8 @@ impl Room { // a certain point results in errors. Cove should not keep retrying log // requests when hitting that limit, so &rl2dev is always opened in // ephemeral mode. - let ephemeral = vault.vault().vault().ephemeral() || vault.room() == "rl2dev"; + let is_rl2dev = vault.room().domain == "euphoria.io" && vault.room().name == "rl2dev"; + let ephemeral = vault.vault().vault().ephemeral() || is_rl2dev; Self { vault, @@ -125,7 +128,8 @@ impl Room { let cookies = &*self.instance.config().server.cookies; let cookies = cookies.lock().unwrap().clone(); - logging_unwrap!(self.vault.vault().set_cookies(cookies).await); + let domain = self.vault.room().domain.clone(); + logging_unwrap!(self.vault.vault().set_cookies(domain, cookies).await); } Event::Packet(_, packet, ConnSnapshot { conn_tx, state }) => { self.state = State::Connected(conn_tx, state); @@ -189,7 +193,8 @@ impl Room { // a certain point results in errors. By reducing the amount of messages // in each log request, we can get closer to this point. Since &rl2dev // is fairly low in activity, this should be fine. - let n = if vault.room() == "rl2dev" { 50 } else { 1000 }; + let is_rl2dev = vault.room().domain == "euphoria.io" && vault.room().name == "rl2dev"; + let n = if is_rl2dev { 50 } else { 1000 }; let _ = conn_tx.send(Log { n, before }).await; // The code handling incoming events and replies also handles From 1f1795f11188f63904e383a2f9974e0e75735bf4 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 31 Dec 2023 20:20:28 +0100 Subject: [PATCH 164/266] Support domain when exporting room logs --- cove/src/export.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/cove/src/export.rs b/cove/src/export.rs index 545f48b..9d9c60b 100644 --- a/cove/src/export.rs +++ b/cove/src/export.rs @@ -6,7 +6,7 @@ mod text; use std::fs::File; use std::io::{self, BufWriter, Write}; -use crate::vault::{EuphRoomVault, EuphVault}; +use crate::vault::{EuphRoomVault, EuphVault, RoomIdentifier}; #[derive(Debug, Clone, Copy, clap::ValueEnum)] pub enum Format { @@ -43,6 +43,10 @@ pub struct Args { #[arg(long, short)] all: bool, + /// Domain to resolve the room names with. + #[arg(long, short, default_value = "euphoria.leet.nu")] + domain: String, + /// Format of the output file. #[arg(long, short, value_enum, default_value_t = Format::Text)] format: Format, @@ -85,7 +89,12 @@ pub async fn export(vault: &EuphVault, mut args: Args) -> anyhow::Result<()> { } let rooms = if args.all { - let mut rooms = vault.rooms().await?; + let mut rooms = vault + .rooms() + .await? + .into_iter() + .map(|id| id.name) + .collect::<Vec<_>>(); rooms.sort_unstable(); rooms } else { @@ -101,14 +110,14 @@ pub async fn export(vault: &EuphVault, mut args: Args) -> anyhow::Result<()> { for room in rooms { if args.out == "-" { eprintln!("Exporting &{room} as {} to stdout", args.format.name()); - let vault = vault.room(room); + let vault = vault.room(RoomIdentifier::new(args.domain.clone(), room)); let mut stdout = BufWriter::new(io::stdout()); export_room(&vault, &mut stdout, args.format).await?; stdout.flush()?; } else { let out = format_out(&args.out, &room, args.format); eprintln!("Exporting &{room} as {} to {out}", args.format.name()); - let vault = vault.room(room); + let vault = vault.room(RoomIdentifier::new(args.domain.clone(), room)); let mut file = BufWriter::new(File::create(out)?); export_room(&vault, &mut file, args.format).await?; file.flush()?; From 708d66b256d7a1853d2ee15def71087d2fc53da1 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 31 Dec 2023 20:27:01 +0100 Subject: [PATCH 165/266] Support domain when clearing cookies --- cove/src/main.rs | 17 ++++++++++++----- cove/src/vault/euph.rs | 18 +++++++++++++++++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/cove/src/main.rs b/cove/src/main.rs index e9dc920..cc5ae63 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -26,7 +26,6 @@ mod vault; use std::path::PathBuf; use clap::Parser; -use cookie::CookieJar; use cove_config::doc::Document; use cove_config::Config; use directories::{BaseDirs, ProjectDirs}; @@ -47,7 +46,11 @@ enum Command { /// Compact and clean up vault. Gc, /// Clear euphoria session cookies. - ClearCookies, + ClearCookies { + /// Clear cookies for a specific domain only. + #[arg(long, short)] + domain: Option<String>, + }, /// Print config documentation as markdown. HelpConfig, } @@ -154,7 +157,7 @@ async fn main() -> anyhow::Result<()> { Command::Run => run(logger, logger_rx, config, &dirs).await?, Command::Export(args) => export(config, &dirs, args).await?, Command::Gc => gc(config, &dirs).await?, - Command::ClearCookies => clear_cookies(config, &dirs).await?, + Command::ClearCookies { domain } => clear_cookies(config, &dirs, domain).await?, Command::HelpConfig => help_config(), } @@ -214,11 +217,15 @@ async fn gc(config: &'static Config, dirs: &ProjectDirs) -> anyhow::Result<()> { Ok(()) } -async fn clear_cookies(config: &'static Config, dirs: &ProjectDirs) -> anyhow::Result<()> { +async fn clear_cookies( + config: &'static Config, + dirs: &ProjectDirs, + domain: Option<String>, +) -> anyhow::Result<()> { let vault = open_vault(config, dirs)?; eprintln!("Clearing cookies"); - vault.euph().set_cookies(CookieJar::new()).await?; + vault.euph().clear_cookies(domain).await?; vault.close().await; Ok(()) diff --git a/cove/src/vault/euph.rs b/cove/src/vault/euph.rs index 6f23261..7a5ec3f 100644 --- a/cove/src/vault/euph.rs +++ b/cove/src/vault/euph.rs @@ -113,6 +113,7 @@ macro_rules! euph_vault_actions { euph_vault_actions! { GetCookies : cookies(domain: String) -> CookieJar; SetCookies : set_cookies(domain: String, cookies: CookieJar) -> (); + ClearCookies : clear_cookies(domain: Option<String>) -> (); GetRooms : rooms() -> Vec<RoomIdentifier>; } @@ -166,7 +167,7 @@ impl Action for SetCookies { ", )?; for cookie in self.cookies.iter() { - insert_cookie.execute([self.domain, format!("{cookie}")])?; + insert_cookie.execute(params![self.domain, format!("{cookie}")])?; } drop(insert_cookie); @@ -175,6 +176,21 @@ impl Action for SetCookies { } } +impl Action for ClearCookies { + type Output = (); + type Error = rusqlite::Error; + + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { + if let Some(domain) = self.domain { + conn.execute("DELETE FROM euph_cookies WHERE domain = ?", [domain])?; + } else { + conn.execute_batch("DELETE FROM euph_cookies")?; + } + + Ok(()) + } +} + impl Action for GetRooms { type Output = Vec<RoomIdentifier>; type Error = rusqlite::Error; From 78bbfac2f3a4419c7f8cd410c98750b0daa2de2d Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Mon, 1 Jan 2024 01:38:41 +0100 Subject: [PATCH 166/266] Fix domain errors in UI --- cove/src/ui/euph/room.rs | 8 +- cove/src/ui/rooms.rs | 199 ++++++++++++++++++++++++++------------- 2 files changed, 141 insertions(+), 66 deletions(-) diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index 5f26304..30d40a8 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -87,8 +87,12 @@ impl EuphRoom { self.chat.store() } + fn domain(&self) -> &str { + &self.vault().room().domain + } + fn name(&self) -> &str { - self.vault().room() + &self.vault().room().name } pub fn connect(&mut self, next_instance_id: &mut usize) { @@ -307,6 +311,8 @@ impl EuphRoom { } }; + info = info.then(format!(" - {}", self.domain()), Style::new().grey()); + let unseen = self.unseen_msgs_count().await; if unseen > 0 { info = info diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index 3dfc7e1..c0816c6 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -1,3 +1,4 @@ +use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; use std::iter; use std::sync::{Arc, Mutex}; @@ -14,7 +15,7 @@ use toss::{Style, Styled, Widget, WidgetExt}; use crate::euph; use crate::macros::logging_unwrap; -use crate::vault::Vault; +use crate::vault::{EuphVault, RoomIdentifier, Vault}; use super::euph::room::EuphRoom; use super::widgets::{ListBuilder, ListState, Popup}; @@ -22,9 +23,9 @@ use super::{key_bindings, util, UiError, UiEvent}; enum State { ShowList, - ShowRoom(String), + ShowRoom(RoomIdentifier), Connect(EditorState), - Delete(String, EditorState), + Delete(RoomIdentifier, EditorState), } #[derive(Clone, Copy)] @@ -42,6 +43,24 @@ impl Order { } } +struct EuphServer { + config: ServerConfig, + next_instance_id: usize, +} + +impl EuphServer { + async fn new(vault: &EuphVault, domain: String) -> Self { + let cookies = logging_unwrap!(vault.cookies(domain.clone()).await); + let config = ServerConfig::default() + .domain(domain) + .cookies(Arc::new(Mutex::new(cookies))); + Self { + config, + next_instance_id: 0, + } + } +} + pub struct Rooms { config: &'static Config, @@ -50,12 +69,11 @@ pub struct Rooms { state: State, - list: ListState<String>, + list: ListState<RoomIdentifier>, order: Order, - euph_server_config: ServerConfig, - euph_next_instance_id: usize, - euph_rooms: HashMap<String, EuphRoom>, + euph_servers: HashMap<String, EuphServer>, + euph_rooms: HashMap<RoomIdentifier, EuphRoom>, } impl Rooms { @@ -64,9 +82,6 @@ impl Rooms { vault: Vault, ui_event_tx: mpsc::UnboundedSender<UiEvent>, ) -> Self { - let cookies = logging_unwrap!(vault.euph().cookies().await); - let euph_server_config = ServerConfig::default().cookies(Arc::new(Mutex::new(cookies))); - let mut result = Self { config, vault, @@ -74,15 +89,20 @@ impl Rooms { state: State::ShowList, list: ListState::new(), order: Order::from_rooms_sort_order(config.rooms_sort_order), - euph_server_config, - euph_next_instance_id: 0, + euph_servers: HashMap::new(), euph_rooms: HashMap::new(), }; if !config.offline { for (name, config) in &config.euph.rooms { if config.autojoin { - result.connect_to_room(name.clone()); + result + .connect_to_room(RoomIdentifier { + // TODO Remove hardcoded domain + domain: "euphoria.leet.nu".to_string(), + name: name.clone(), + }) + .await; } } } @@ -90,39 +110,66 @@ impl Rooms { result } - fn get_or_insert_room(&mut self, name: String) -> &mut EuphRoom { - self.euph_rooms.entry(name.clone()).or_insert_with(|| { + async fn get_or_insert_server<'a>( + vault: &Vault, + euph_servers: &'a mut HashMap<String, EuphServer>, + domain: String, + ) -> &'a mut EuphServer { + match euph_servers.entry(domain.clone()) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + let server = EuphServer::new(&vault.euph(), domain).await; + entry.insert(server) + } + } + } + + async fn get_or_insert_room(&mut self, room: RoomIdentifier) -> &mut EuphRoom { + let server = + Self::get_or_insert_server(&self.vault, &mut self.euph_servers, room.domain.clone()) + .await; + + self.euph_rooms.entry(room.clone()).or_insert_with(|| { EuphRoom::new( self.config, - self.euph_server_config.clone(), - self.config.euph_room(&name), - self.vault.euph().room(name), + server.config.clone(), + self.config.euph_room(&room.name), + self.vault.euph().room(room), self.ui_event_tx.clone(), ) }) } - fn connect_to_room(&mut self, name: String) { - let room = self.euph_rooms.entry(name.clone()).or_insert_with(|| { + async fn connect_to_room(&mut self, room: RoomIdentifier) { + let server = + Self::get_or_insert_server(&self.vault, &mut self.euph_servers, room.domain.clone()) + .await; + + let room = self.euph_rooms.entry(room.clone()).or_insert_with(|| { EuphRoom::new( self.config, - self.euph_server_config.clone(), - self.config.euph_room(&name), - self.vault.euph().room(name), + server.config.clone(), + self.config.euph_room(&room.name), + self.vault.euph().room(room), self.ui_event_tx.clone(), ) }); - room.connect(&mut self.euph_next_instance_id); + + room.connect(&mut server.next_instance_id); } - fn connect_to_all_rooms(&mut self) { - for room in self.euph_rooms.values_mut() { - room.connect(&mut self.euph_next_instance_id); + async fn connect_to_all_rooms(&mut self) { + for (id, room) in &mut self.euph_rooms { + let server = + Self::get_or_insert_server(&self.vault, &mut self.euph_servers, id.domain.clone()) + .await; + + room.connect(&mut server.next_instance_id); } } - fn disconnect_from_room(&mut self, name: &str) { - if let Some(room) = self.euph_rooms.get_mut(name) { + fn disconnect_from_room(&mut self, room: &RoomIdentifier) { + if let Some(room) = self.euph_rooms.get_mut(room) { room.disconnect(); } } @@ -145,7 +192,11 @@ impl Rooms { let rooms = logging_unwrap!(self.vault.euph().rooms().await); let mut rooms_set = rooms .into_iter() - .chain(self.config.euph.rooms.keys().cloned()) + .chain(self.config.euph.rooms.keys().map(|name| RoomIdentifier { + // TODO Remove hardcoded domain + domain: "euphoria.leet.nu".to_string(), + name: name.clone(), + })) .collect::<HashSet<_>>(); // Prevent room that is currently being shown from being removed. This @@ -161,7 +212,7 @@ impl Rooms { .retain(|n, r| !r.stopped() || rooms_set.contains(n)); for room in rooms_set { - self.get_or_insert_room(room).retain(); + self.get_or_insert_room(room).await.retain(); } } @@ -179,9 +230,9 @@ impl Rooms { .boxed_async() } - State::ShowRoom(name) => { + State::ShowRoom(id) => { self.euph_rooms - .get_mut(name) + .get_mut(id) .expect("room exists after stabilization") .widget() .await @@ -195,10 +246,11 @@ impl Rooms { .boxed_async() } - State::Delete(name, editor) => { + State::Delete(id, editor) => { Self::rooms_widget(self.config, &mut self.list, self.order, &self.euph_rooms) .await - .below(Self::delete_room_widget(name, editor)) + // TODO Respect domain + .below(Self::delete_room_widget(&id.name, editor)) .desync() .boxed_async() } @@ -345,20 +397,20 @@ impl Rooms { } } - fn sort_rooms(rooms: &mut [(&String, Option<&euph::State>, usize)], order: Order) { + fn sort_rooms(rooms: &mut [(&RoomIdentifier, Option<&euph::State>, usize)], order: Order) { match order { - Order::Alphabet => rooms.sort_unstable_by_key(|(name, _, _)| *name), - Order::Importance => rooms.sort_unstable_by_key(|(name, state, unseen)| { - (state.is_none(), *unseen == 0, *name) + Order::Alphabet => rooms.sort_unstable_by_key(|(id, _, _)| (&id.name, &id.domain)), + Order::Importance => rooms.sort_unstable_by_key(|(id, state, unseen)| { + (state.is_none(), *unseen == 0, &id.name, &id.domain) }), } } async fn render_rows( config: &Config, - list_builder: &mut ListBuilder<'_, String, Text>, + list_builder: &mut ListBuilder<'_, RoomIdentifier, Text>, order: Order, - euph_rooms: &HashMap<String, EuphRoom>, + euph_rooms: &HashMap<RoomIdentifier, EuphRoom>, ) { if euph_rooms.is_empty() { let style = Style::new().grey().italic(); @@ -370,23 +422,25 @@ impl Rooms { } let mut rooms = vec![]; - for (name, room) in euph_rooms { + for (id, room) in euph_rooms { let state = room.room_state(); let unseen = room.unseen_msgs_count().await; - rooms.push((name, state, unseen)); + rooms.push((id, state, unseen)); } Self::sort_rooms(&mut rooms, order); - for (name, state, unseen) in rooms { - let name = name.clone(); + for (id, state, unseen) in rooms { + let id = id.clone(); let info = Self::format_room_info(state, unseen); - list_builder.add_sel(name.clone(), move |selected| { + list_builder.add_sel(id.clone(), move |selected| { let style = if selected { Style::new().bold().black().on_white() } else { Style::new().bold().blue() }; - let text = Styled::new(format!("&{name}"), style).and_then(info); + let text = Styled::new(format!("&{}", id.name), style) + .and_then(info) + .then(format!(" - {}", id.domain), Style::new().grey()); Text::new(text) }); @@ -395,9 +449,9 @@ impl Rooms { async fn rooms_widget<'a>( config: &Config, - list: &'a mut ListState<String>, + list: &'a mut ListState<RoomIdentifier>, order: Order, - euph_rooms: &HashMap<String, EuphRoom>, + euph_rooms: &HashMap<RoomIdentifier, EuphRoom>, ) -> impl Widget<UiError> + 'a { let heading_style = Style::new().bold(); let heading_text = @@ -416,7 +470,11 @@ impl Rooms { c.is_ascii_alphanumeric() || c == '_' } - fn handle_showlist_input_event(&mut self, event: &mut InputEvent<'_>, keys: &Keys) -> bool { + async fn handle_showlist_input_event( + &mut self, + event: &mut InputEvent<'_>, + keys: &Keys, + ) -> bool { // Open room if event.matches(&keys.general.confirm) { if let Some(name) = self.list.selected() { @@ -433,17 +491,17 @@ impl Rooms { // Room actions if event.matches(&keys.rooms.action.connect) { if let Some(name) = self.list.selected() { - self.connect_to_room(name.clone()); + self.connect_to_room(name.clone()).await; } return true; } if event.matches(&keys.rooms.action.connect_all) { - self.connect_to_all_rooms(); + self.connect_to_all_rooms().await; return true; } if event.matches(&keys.rooms.action.disconnect) { - if let Some(name) = self.list.selected() { - self.disconnect_from_room(&name.clone()); + if let Some(room) = self.list.selected() { + self.disconnect_from_room(&room.clone()); } return true; } @@ -454,18 +512,23 @@ impl Rooms { if event.matches(&keys.rooms.action.connect_autojoin) { for (name, options) in &self.config.euph.rooms { if options.autojoin { - self.connect_to_room(name.clone()); + let room = RoomIdentifier { + // TODO Remove hardcoded domain + domain: "euphoria.leet.nu".to_string(), + name: name.clone(), + }; + self.connect_to_room(room).await; } } return true; } if event.matches(&keys.rooms.action.disconnect_non_autojoin) { - for (name, room) in &mut self.euph_rooms { + for (id, room) in &mut self.euph_rooms { let autojoin = self .config .euph .rooms - .get(name) + .get(&id.name) // TODO Respect domain .map(|r| r.autojoin) .unwrap_or(false); if !autojoin { @@ -479,8 +542,8 @@ impl Rooms { return true; } if event.matches(&keys.rooms.action.delete) { - if let Some(name) = self.list.selected() { - self.state = State::Delete(name.clone(), EditorState::new()); + if let Some(room) = self.list.selected() { + self.state = State::Delete(room.clone(), EditorState::new()); } return true; } @@ -500,7 +563,7 @@ impl Rooms { match &mut self.state { State::ShowList => { - if self.handle_showlist_input_event(event, keys) { + if self.handle_showlist_input_event(event, keys).await { return true; } } @@ -523,8 +586,13 @@ impl Rooms { if event.matches(&keys.general.confirm) { let name = editor.text().to_string(); if !name.is_empty() { - self.connect_to_room(name.clone()); - self.state = State::ShowRoom(name); + let room = RoomIdentifier { + // TODO Remove hardcoded domain + domain: "euphoria.leet.nu".to_string(), + name, + }; + self.connect_to_room(room.clone()).await; + self.state = State::ShowRoom(room); } return true; } @@ -553,15 +621,16 @@ impl Rooms { } pub async fn handle_euph_event(&mut self, event: Event) -> bool { - let room_name = event.config().room.clone(); - let Some(room) = self.euph_rooms.get_mut(&room_name) else { + let config = event.config(); + let room_id = RoomIdentifier::new(config.server.domain.clone(), config.room.clone()); + let Some(room) = self.euph_rooms.get_mut(&room_id) else { return false; }; let handled = room.handle_event(event).await; let room_visible = match &self.state { - State::ShowRoom(name) => *name == room_name, + State::ShowRoom(id) => *id == room_id, _ => true, }; handled && room_visible From 60fdc40a21426a12f239cb3f58ab417ec2fc3a66 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Mon, 1 Jan 2024 01:49:11 +0100 Subject: [PATCH 167/266] Fix incorrect room name in url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I naïvely implemented Display for RoomIdentifier, which lead to me passing room names like "&test@euphoria.leet.nu" to euphoxide instead of "test". Of course, no room of that name exists, so every attempted connection failed. To figure this out, I manually relaxed the verbosity filters to let through all euphoxide log messages and recompiled. Only implementing Debug led to compile errors whereever I misused the Disply instance, so at least the bug fix was nice and easy once I knew what happened. --- cove/src/euph/room.rs | 2 +- cove/src/ui/euph/room.rs | 4 ++-- cove/src/vault/euph.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cove/src/euph/room.rs b/cove/src/euph/room.rs index 22bebb1..6ae3a17 100644 --- a/cove/src/euph/room.rs +++ b/cove/src/euph/room.rs @@ -187,7 +187,7 @@ impl Room { None => None, }; - debug!("{}: requesting logs", vault.room()); + debug!("{:?}: requesting logs", vault.room()); // &rl2dev's message history is broken and requesting old messages past // a certain point results in errors. By reducing the amount of messages diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index 30d40a8..e27e2b6 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -101,8 +101,8 @@ impl EuphRoom { let instance_config = self .server_config .clone() - .room(self.vault().room().to_string()) - .name(format!("{room}-{}", next_instance_id)) + .room(self.vault().room().name.clone()) + .name(format!("{room:?}-{}", next_instance_id)) .human(true) .username(self.room_config.username.clone()) .force_username(self.room_config.force_username) diff --git a/cove/src/vault/euph.rs b/cove/src/vault/euph.rs index 7a5ec3f..0e84ba1 100644 --- a/cove/src/vault/euph.rs +++ b/cove/src/vault/euph.rs @@ -46,13 +46,13 @@ impl FromSql for WTime { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct RoomIdentifier { pub domain: String, pub name: String, } -impl fmt::Display for RoomIdentifier { +impl fmt::Debug for RoomIdentifier { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "&{}@{}", self.name, self.domain) } From c094f526a5bc41f84c27ea91febbf14ae22eb240 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Mon, 1 Jan 2024 13:06:48 +0100 Subject: [PATCH 168/266] Fix room names and domains being swapped in room list --- cove/src/vault/euph.rs | 2 +- cove/src/vault/migrate.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cove/src/vault/euph.rs b/cove/src/vault/euph.rs index 0e84ba1..4664446 100644 --- a/cove/src/vault/euph.rs +++ b/cove/src/vault/euph.rs @@ -198,7 +198,7 @@ impl Action for GetRooms { fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { conn.prepare( " - SELECT room, domain + SELECT domain, room FROM euph_rooms ", )? diff --git a/cove/src/vault/migrate.rs b/cove/src/vault/migrate.rs index 85b9309..ed26db6 100644 --- a/cove/src/vault/migrate.rs +++ b/cove/src/vault/migrate.rs @@ -153,7 +153,7 @@ fn m3(tx: &mut Transaction<'_>, nr: usize, total: usize) -> rusqlite::Result<()> start INT, end INT, - UNIQUE (room, domain, start, end), + UNIQUE (domain, room, start, end), FOREIGN KEY (domain, room) REFERENCES euph_rooms (domain, room) ON DELETE CASCADE, CHECK (start IS NULL OR end IS NOT NULL) From 5995d06cad31f63617c61747e9d11c703b166f64 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Mon, 1 Jan 2024 19:35:46 +0100 Subject: [PATCH 169/266] Support domain in config file --- cove-config/src/euph.rs | 8 +++++- cove-config/src/lib.rs | 9 +++++-- cove/src/ui/rooms.rs | 60 +++++++++++++++++++---------------------- 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/cove-config/src/euph.rs b/cove-config/src/euph.rs index 0584933..5b1f12a 100644 --- a/cove-config/src/euph.rs +++ b/cove-config/src/euph.rs @@ -35,7 +35,13 @@ pub struct EuphRoom { } #[derive(Debug, Default, Deserialize, Document)] -pub struct Euph { +pub struct EuphServer { #[document(metavar = "room")] pub rooms: HashMap<String, EuphRoom>, } + +#[derive(Debug, Default, Deserialize, Document)] +pub struct Euph { + #[document(metavar = "domain")] + pub servers: HashMap<String, EuphServer>, +} diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index d2a773a..5aa6522 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -107,7 +107,12 @@ impl Config { }) } - pub fn euph_room(&self, name: &str) -> EuphRoom { - self.euph.rooms.get(name).cloned().unwrap_or_default() + pub fn euph_room(&self, domain: &str, name: &str) -> EuphRoom { + if let Some(server) = self.euph.servers.get(domain) { + if let Some(room) = server.rooms.get(name) { + return room.clone(); + } + } + EuphRoom::default() } } diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index c0816c6..23537eb 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -94,15 +94,12 @@ impl Rooms { }; if !config.offline { - for (name, config) in &config.euph.rooms { - if config.autojoin { - result - .connect_to_room(RoomIdentifier { - // TODO Remove hardcoded domain - domain: "euphoria.leet.nu".to_string(), - name: name.clone(), - }) - .await; + for (domain, server) in &config.euph.servers { + for (name, room) in &server.rooms { + if room.autojoin { + let id = RoomIdentifier::new(domain.clone(), name.clone()); + result.connect_to_room(id).await; + } } } } @@ -133,7 +130,7 @@ impl Rooms { EuphRoom::new( self.config, server.config.clone(), - self.config.euph_room(&room.name), + self.config.euph_room(&room.domain, &room.name), self.vault.euph().room(room), self.ui_event_tx.clone(), ) @@ -149,7 +146,7 @@ impl Rooms { EuphRoom::new( self.config, server.config.clone(), - self.config.euph_room(&room.name), + self.config.euph_room(&room.domain, &room.name), self.vault.euph().room(room), self.ui_event_tx.clone(), ) @@ -189,14 +186,21 @@ impl Rooms { /// - rooms that were deleted from the db. async fn stabilize_rooms(&mut self) { // Collect all rooms from the db and config file - let rooms = logging_unwrap!(self.vault.euph().rooms().await); - let mut rooms_set = rooms + let rooms_from_db = logging_unwrap!(self.vault.euph().rooms().await); + let rooms_from_config = self + .config + .euph + .servers + .iter() + .flat_map(|(domain, server)| { + server + .rooms + .keys() + .map(|name| RoomIdentifier::new(domain.clone(), name.clone())) + }); + let mut rooms_set = rooms_from_db .into_iter() - .chain(self.config.euph.rooms.keys().map(|name| RoomIdentifier { - // TODO Remove hardcoded domain - domain: "euphoria.leet.nu".to_string(), - name: name.clone(), - })) + .chain(rooms_from_config) .collect::<HashSet<_>>(); // Prevent room that is currently being shown from being removed. This @@ -510,27 +514,17 @@ impl Rooms { return true; } if event.matches(&keys.rooms.action.connect_autojoin) { - for (name, options) in &self.config.euph.rooms { - if options.autojoin { - let room = RoomIdentifier { - // TODO Remove hardcoded domain - domain: "euphoria.leet.nu".to_string(), - name: name.clone(), - }; - self.connect_to_room(room).await; + for (domain, server) in &self.config.euph.servers { + for name in server.rooms.keys() { + let id = RoomIdentifier::new(domain.clone(), name.clone()); + self.connect_to_room(id).await; } } return true; } if event.matches(&keys.rooms.action.disconnect_non_autojoin) { for (id, room) in &mut self.euph_rooms { - let autojoin = self - .config - .euph - .rooms - .get(&id.name) // TODO Respect domain - .map(|r| r.autojoin) - .unwrap_or(false); + let autojoin = self.config.euph_room(&id.domain, &id.name).autojoin; if !autojoin { room.disconnect(); } From fdbd6e0c553c06b4b76b7c8047e849c759e584a2 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Mon, 1 Jan 2024 19:45:32 +0100 Subject: [PATCH 170/266] Simplify getting Joined room state --- cove/src/ui/euph/room.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index e27e2b6..862d902 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -132,7 +132,12 @@ impl EuphRoom { } } - // TODO fn room_state_joined(&self) -> Option<&Joined> {} + pub fn room_state_joined(&self) -> Option<&Joined> { + match self.room_state() { + Some(euph::State::Connected(_, conn::State::Joined(ref joined))) => Some(joined), + _ => None, + } + } pub fn stopped(&self) -> bool { self.room.as_ref().map(|r| r.stopped()).unwrap_or(true) @@ -167,9 +172,8 @@ impl EuphRoom { } fn stabilize_focus(&mut self) { - match self.room_state() { - Some(euph::State::Connected(_, conn::State::Joined(_))) => {} - _ => self.focus = Focus::Chat, // There is no nick list to focus on + if self.room_state_joined().is_none() { + self.focus = Focus::Chat; // There is no nick list to focus on } } @@ -325,10 +329,7 @@ impl EuphRoom { } async fn handle_chat_input_event(&mut self, event: &mut InputEvent<'_>, keys: &Keys) -> bool { - let can_compose = matches!( - self.room_state(), - Some(euph::State::Connected(_, conn::State::Joined(_))) - ); + let can_compose = self.room_state_joined().is_some(); let reaction = self.chat.handle_input_event(event, keys, can_compose).await; let reaction = logging_unwrap!(reaction); @@ -448,8 +449,7 @@ impl EuphRoom { } if event.matches(&keys.tree.action.inspect) { - if let Some(euph::State::Connected(_, conn::State::Joined(joined))) = self.room_state() - { + if let Some(joined) = self.room_state_joined() { if let Some(id) = self.nick_list.selected() { if *id == joined.session.session_id { self.state = @@ -472,11 +472,9 @@ impl EuphRoom { return true; } - if let Some(euph::State::Connected(_, conn::State::Joined(_))) = self.room_state() { - if event.matches(&keys.general.focus) { - self.focus = Focus::NickList; - return true; - } + if self.room_state_joined().is_some() && event.matches(&keys.general.focus) { + self.focus = Focus::NickList; + return true; } } Focus::NickList => { From 6a2f9de85b20835f8d4915bb052a6f60bcfb1d0b Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 12:35:31 +0100 Subject: [PATCH 171/266] Update changelog --- CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 366a11a..e296137 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Procedure when bumping the version number: -1. Update dependencies in a separate commit +1. Update dependencies and flake in a separate commit 2. Set version number in `Cargo.toml` 3. Add new section in this changelog 4. Run `cargo run help-config > CONFIG.md` @@ -17,9 +17,13 @@ Procedure when bumping the version number: ### Added - Support for multiple euph domains + - Room domain names are now visible in the UI +- `--domain` option to `cove export` command ### Changed -- Switch default euph domain to https://euphoria.leet.nu/ +- The default euph domain is now https://euphoria.leet.nu/ +- The config file format was changed to support multiple euph servers with different domains. + Options previously located at `euph.rooms.*` should be reviewed and moved to `euph.servers."euphoria.leet.nu".rooms.*`. ## v0.7.1 - 2023-08-31 From 2a10a7a39f29a20b6faee9a943d8a5594aeae2ee Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 12:36:54 +0100 Subject: [PATCH 172/266] Add todos --- Cargo.toml | 2 ++ cove/src/ui/euph/room.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index b9284b9..fbd9231 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,5 @@ +# TODO Configure lints in here + [workspace] resolver = "2" members = ["cove", "cove-*"] diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index 862d902..7fa266f 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -98,6 +98,7 @@ impl EuphRoom { pub fn connect(&mut self, next_instance_id: &mut usize) { if self.room.is_none() { let room = self.vault().room(); + // TODO Decrease ping timeout let instance_config = self .server_config .clone() From 13a4fa09384fe1e3ed9a15133c2f24a068d1b30c Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 12:53:59 +0100 Subject: [PATCH 173/266] Support domain in room deletion popup --- cove/src/ui/rooms.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index 23537eb..fe7a6f3 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -254,7 +254,7 @@ impl Rooms { Self::rooms_widget(self.config, &mut self.list, self.order, &self.euph_rooms) .await // TODO Respect domain - .below(Self::delete_room_widget(&id.name, editor)) + .below(Self::delete_room_widget(id, editor)) .desync() .boxed_async() } @@ -276,15 +276,17 @@ impl Rooms { } fn delete_room_widget<'a>( - name: &str, + id: &RoomIdentifier, editor: &'a mut EditorState, ) -> impl Widget<UiError> + 'a { let warn_style = Style::new().bold().red(); let room_style = Style::new().bold().blue(); let text = Styled::new_plain("Are you sure you want to delete ") .then("&", room_style) - .then(name, room_style) - .then_plain("?\n\n") + .then(&id.name, room_style) + .then_plain(" on the ") + .then(&id.domain, Style::new().grey()) + .then_plain(" server?\n\n") .then_plain("This will delete the entire room history from your vault. ") .then_plain("To shrink your vault afterwards, run ") .then("cove gc", Style::new().italic().grey()) From f4967731a1a16a07e45f95c8b45ebb2c323d04ce Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 12:56:37 +0100 Subject: [PATCH 174/266] Fix room deletion popup not checking entered name From the looks of it, I accidentally broke it in v0.7.0 in commit 9bc6931fae84769c2fba35ec06b48d3dfea0d8c2. --- CHANGELOG.md | 3 +++ cove/src/ui/rooms.rs | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e296137..e34f128 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,9 @@ Procedure when bumping the version number: - The config file format was changed to support multiple euph servers with different domains. Options previously located at `euph.rooms.*` should be reviewed and moved to `euph.servers."euphoria.leet.nu".rooms.*`. +### Fixed +- Room deletion popup accepting any room name + ## v0.7.1 - 2023-08-31 ### Changed diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index fe7a6f3..57b5aca 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -596,14 +596,14 @@ impl Rooms { return true; } } - State::Delete(name, editor) => { + State::Delete(id, editor) => { if event.matches(&keys.general.abort) { self.state = State::ShowList; return true; } - if event.matches(&keys.general.confirm) { - self.euph_rooms.remove(name); - logging_unwrap!(self.vault.euph().room(name.clone()).delete().await); + if event.matches(&keys.general.confirm) && editor.text() == id.name { + self.euph_rooms.remove(id); + logging_unwrap!(self.vault.euph().room(id.clone()).delete().await); self.state = State::ShowList; return true; } From 970bc07ed9154247555613a6d89f991a6567373b Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 13:51:51 +0100 Subject: [PATCH 175/266] Support choosing domain in room connection popup --- cove/src/ui/rooms.rs | 55 +++++----------- cove/src/ui/rooms/connect.rs | 120 +++++++++++++++++++++++++++++++++++ cove/src/ui/util.rs | 5 ++ 3 files changed, 142 insertions(+), 38 deletions(-) create mode 100644 cove/src/ui/rooms/connect.rs diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index 57b5aca..3521318 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -1,3 +1,5 @@ +mod connect; + use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; use std::iter; @@ -17,6 +19,8 @@ use crate::euph; use crate::macros::logging_unwrap; use crate::vault::{EuphVault, RoomIdentifier, Vault}; +use self::connect::{ConnectResult, ConnectState}; + use super::euph::room::EuphRoom; use super::widgets::{ListBuilder, ListState, Popup}; use super::{key_bindings, util, UiError, UiEvent}; @@ -24,7 +28,7 @@ use super::{key_bindings, util, UiError, UiEvent}; enum State { ShowList, ShowRoom(RoomIdentifier), - Connect(EditorState), + Connect(ConnectState), Delete(RoomIdentifier, EditorState), } @@ -242,10 +246,10 @@ impl Rooms { .await } - State::Connect(editor) => { + State::Connect(connect) => { Self::rooms_widget(self.config, &mut self.list, self.order, &self.euph_rooms) .await - .below(Self::new_room_widget(editor)) + .below(connect.widget()) .desync() .boxed_async() } @@ -261,20 +265,6 @@ impl Rooms { } } - fn new_room_widget(editor: &mut EditorState) -> impl Widget<UiError> + '_ { - let room_style = Style::new().bold().blue(); - - let inner = Join2::horizontal( - Text::new(("&", room_style)).segment().with_fixed(true), - editor - .widget() - .with_highlight(|s| Styled::new(s, room_style)) - .segment(), - ); - - Popup::new(inner, "Connect to") - } - fn delete_room_widget<'a>( id: &RoomIdentifier, editor: &'a mut EditorState, @@ -472,10 +462,6 @@ impl Rooms { ) } - fn room_char(c: char) -> bool { - c.is_ascii_alphanumeric() || c == '_' - } - async fn handle_showlist_input_event( &mut self, event: &mut InputEvent<'_>, @@ -534,7 +520,7 @@ impl Rooms { return true; } if event.matches(&keys.rooms.action.new) { - self.state = State::Connect(EditorState::new()); + self.state = State::Connect(ConnectState::new()); return true; } if event.matches(&keys.rooms.action.delete) { @@ -574,28 +560,21 @@ impl Rooms { } } } - State::Connect(editor) => { - if event.matches(&keys.general.abort) { + State::Connect(connect) => match connect.handle_input_event(event, keys) { + ConnectResult::Close => { self.state = State::ShowList; return true; } - if event.matches(&keys.general.confirm) { - let name = editor.text().to_string(); - if !name.is_empty() { - let room = RoomIdentifier { - // TODO Remove hardcoded domain - domain: "euphoria.leet.nu".to_string(), - name, - }; - self.connect_to_room(room.clone()).await; - self.state = State::ShowRoom(room); - } + ConnectResult::Connect(room) => { + self.connect_to_room(room.clone()).await; + self.state = State::ShowRoom(room); return true; } - if util::handle_editor_input_event(editor, event, keys, Self::room_char) { + ConnectResult::Handled => { return true; } - } + ConnectResult::Unhandled => {} + }, State::Delete(id, editor) => { if event.matches(&keys.general.abort) { self.state = State::ShowList; @@ -607,7 +586,7 @@ impl Rooms { self.state = State::ShowList; return true; } - if util::handle_editor_input_event(editor, event, keys, Self::room_char) { + if util::handle_editor_input_event(editor, event, keys, util::is_room_char) { return true; } } diff --git a/cove/src/ui/rooms/connect.rs b/cove/src/ui/rooms/connect.rs new file mode 100644 index 0000000..fed584b --- /dev/null +++ b/cove/src/ui/rooms/connect.rs @@ -0,0 +1,120 @@ +use cove_config::Keys; +use cove_input::InputEvent; +use crossterm::style::Stylize; +use toss::widgets::{EditorState, Empty, Join2, Join3, Text}; +use toss::{Style, Styled, Widget, WidgetExt}; + +use crate::ui::widgets::Popup; +use crate::ui::{util, UiError}; +use crate::vault::RoomIdentifier; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Focus { + Name, + Domain, +} + +impl Focus { + fn advance(self) -> Self { + match self { + Self::Name => Self::Domain, + Self::Domain => Self::Name, + } + } +} + +pub struct ConnectState { + focus: Focus, + name: EditorState, + domain: EditorState, +} + +pub enum ConnectResult { + Close, + Connect(RoomIdentifier), + Handled, + Unhandled, +} + +impl ConnectState { + pub fn new() -> Self { + Self { + focus: Focus::Name, + name: EditorState::new(), + domain: EditorState::with_initial_text("euphoria.leet.nu".to_string()), + } + } + + pub fn handle_input_event(&mut self, event: &mut InputEvent<'_>, keys: &Keys) -> ConnectResult { + if event.matches(&keys.general.abort) { + return ConnectResult::Close; + } + + if event.matches(&keys.general.focus) { + self.focus = self.focus.advance(); + return ConnectResult::Handled; + } + + if event.matches(&keys.general.confirm) { + let id = RoomIdentifier { + domain: self.domain.text().to_string(), + name: self.name.text().to_string(), + }; + if !id.domain.is_empty() && !id.name.is_empty() { + return ConnectResult::Connect(id); + } + } + + let handled = match self.focus { + Focus::Name => { + util::handle_editor_input_event(&mut self.name, event, keys, util::is_room_char) + } + Focus::Domain => { + util::handle_editor_input_event(&mut self.domain, event, keys, util::is_room_char) + } + }; + + if handled { + return ConnectResult::Handled; + } + + ConnectResult::Unhandled + } + + pub fn widget(&mut self) -> impl Widget<UiError> + '_ { + let room_style = Style::new().bold().blue(); + let domain_style = Style::new().grey(); + + let name = Join2::horizontal( + Text::new(Styled::new_plain("Room: ").then("&", room_style)) + .with_wrap(false) + .segment() + .with_fixed(true), + self.name + .widget() + .with_highlight(|s| Styled::new(s, room_style)) + .with_focus(self.focus == Focus::Name) + .segment(), + ); + + let domain = Join3::horizontal( + Text::new("Domain:") + .with_wrap(false) + .segment() + .with_fixed(true), + Empty::new().with_width(1).segment().with_fixed(true), + self.domain + .widget() + .with_highlight(|s| Styled::new(s, domain_style)) + .with_focus(self.focus == Focus::Domain) + .segment(), + ); + + let inner = Join2::vertical( + name.segment().with_fixed(true), + domain.segment().with_fixed(true), + ); + + Popup::new(inner, "Connect to") + } +} diff --git a/cove/src/ui/util.rs b/cove/src/ui/util.rs index fa434fe..b358588 100644 --- a/cove/src/ui/util.rs +++ b/cove/src/ui/util.rs @@ -5,6 +5,11 @@ use toss::widgets::EditorState; use super::widgets::ListState; +/// Test if a character is allowed to be typed in a room name. +pub fn is_room_char(c: char) -> bool { + c.is_ascii_alphanumeric() || c == '_' +} + ////////// // List // ////////// From 85cf99387e49cedd1bb707440f81162f66b11df5 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 13:58:49 +0100 Subject: [PATCH 176/266] Extract room deletion popup into module --- cove/src/ui/rooms.rs | 74 +++++++------------------------ cove/src/ui/rooms/delete.rs | 87 +++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 58 deletions(-) create mode 100644 cove/src/ui/rooms/delete.rs diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index 3521318..914bf7e 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -1,4 +1,5 @@ mod connect; +mod delete; use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; @@ -12,7 +13,7 @@ use euphoxide::api::SessionType; use euphoxide::bot::instance::{Event, ServerConfig}; use euphoxide::conn::{self, Joined}; use tokio::sync::mpsc; -use toss::widgets::{BoxedAsync, EditorState, Empty, Join2, Text}; +use toss::widgets::{BoxedAsync, Join2, Text}; use toss::{Style, Styled, Widget, WidgetExt}; use crate::euph; @@ -20,16 +21,17 @@ use crate::macros::logging_unwrap; use crate::vault::{EuphVault, RoomIdentifier, Vault}; use self::connect::{ConnectResult, ConnectState}; +use self::delete::{DeleteResult, DeleteState}; use super::euph::room::EuphRoom; -use super::widgets::{ListBuilder, ListState, Popup}; +use super::widgets::{ListBuilder, ListState}; use super::{key_bindings, util, UiError, UiEvent}; enum State { ShowList, ShowRoom(RoomIdentifier), Connect(ConnectState), - Delete(RoomIdentifier, EditorState), + Delete(DeleteState), } #[derive(Clone, Copy)] @@ -254,61 +256,16 @@ impl Rooms { .boxed_async() } - State::Delete(id, editor) => { + State::Delete(delete) => { Self::rooms_widget(self.config, &mut self.list, self.order, &self.euph_rooms) .await - // TODO Respect domain - .below(Self::delete_room_widget(id, editor)) + .below(delete.widget()) .desync() .boxed_async() } } } - fn delete_room_widget<'a>( - id: &RoomIdentifier, - editor: &'a mut EditorState, - ) -> impl Widget<UiError> + 'a { - let warn_style = Style::new().bold().red(); - let room_style = Style::new().bold().blue(); - let text = Styled::new_plain("Are you sure you want to delete ") - .then("&", room_style) - .then(&id.name, room_style) - .then_plain(" on the ") - .then(&id.domain, Style::new().grey()) - .then_plain(" server?\n\n") - .then_plain("This will delete the entire room history from your vault. ") - .then_plain("To shrink your vault afterwards, run ") - .then("cove gc", Style::new().italic().grey()) - .then_plain(".\n\n") - .then_plain("To confirm the deletion, ") - .then_plain("enter the full name of the room and press enter:"); - - let inner = Join2::vertical( - // The Join prevents the text from filling up the entire available - // space if the editor is wider than the text. - Join2::horizontal( - Text::new(text) - .resize() - .with_max_width(54) - .segment() - .with_growing(false), - Empty::new().segment(), - ) - .segment(), - Join2::horizontal( - Text::new(("&", room_style)).segment().with_fixed(true), - editor - .widget() - .with_highlight(|s| Styled::new(s, room_style)) - .segment(), - ) - .segment(), - ); - - Popup::new(inner, "Delete room").with_border_style(warn_style) - } - fn format_pbln(joined: &Joined) -> String { let mut p = 0_usize; let mut b = 0_usize; @@ -525,7 +482,7 @@ impl Rooms { } if event.matches(&keys.rooms.action.delete) { if let Some(room) = self.list.selected() { - self.state = State::Delete(room.clone(), EditorState::new()); + self.state = State::Delete(DeleteState::new(room.clone())); } return true; } @@ -575,21 +532,22 @@ impl Rooms { } ConnectResult::Unhandled => {} }, - State::Delete(id, editor) => { - if event.matches(&keys.general.abort) { + State::Delete(delete) => match delete.handle_input_event(event, keys) { + DeleteResult::Close => { self.state = State::ShowList; return true; } - if event.matches(&keys.general.confirm) && editor.text() == id.name { - self.euph_rooms.remove(id); - logging_unwrap!(self.vault.euph().room(id.clone()).delete().await); + DeleteResult::Delete(room) => { + self.euph_rooms.remove(&room); + logging_unwrap!(self.vault.euph().room(room).delete().await); self.state = State::ShowList; return true; } - if util::handle_editor_input_event(editor, event, keys, util::is_room_char) { + DeleteResult::Handled => { return true; } - } + DeleteResult::Unhandled => {} + }, } false diff --git a/cove/src/ui/rooms/delete.rs b/cove/src/ui/rooms/delete.rs new file mode 100644 index 0000000..5a20415 --- /dev/null +++ b/cove/src/ui/rooms/delete.rs @@ -0,0 +1,87 @@ +use cove_config::Keys; +use cove_input::InputEvent; +use crossterm::style::Stylize; +use toss::widgets::{EditorState, Empty, Join2, Text}; +use toss::{Style, Styled, Widget, WidgetExt}; + +use crate::ui::widgets::Popup; +use crate::ui::{util, UiError}; +use crate::vault::RoomIdentifier; + +pub struct DeleteState { + id: RoomIdentifier, + name: EditorState, +} + +pub enum DeleteResult { + Close, + Delete(RoomIdentifier), + Handled, + Unhandled, +} + +impl DeleteState { + pub fn new(id: RoomIdentifier) -> Self { + Self { + id, + name: EditorState::new(), + } + } + + pub fn handle_input_event(&mut self, event: &mut InputEvent<'_>, keys: &Keys) -> DeleteResult { + if event.matches(&keys.general.abort) { + return DeleteResult::Close; + } + + if event.matches(&keys.general.confirm) && self.name.text() == self.id.name { + return DeleteResult::Delete(self.id.clone()); + } + + if util::handle_editor_input_event(&mut self.name, event, keys, util::is_room_char) { + return DeleteResult::Handled; + } + + DeleteResult::Unhandled + } + + pub fn widget(&mut self) -> impl Widget<UiError> + '_ { + let warn_style = Style::new().bold().red(); + let room_style = Style::new().bold().blue(); + let text = Styled::new_plain("Are you sure you want to delete ") + .then("&", room_style) + .then(&self.id.name, room_style) + .then_plain(" on the ") + .then(&self.id.domain, Style::new().grey()) + .then_plain(" server?\n\n") + .then_plain("This will delete the entire room history from your vault. ") + .then_plain("To shrink your vault afterwards, run ") + .then("cove gc", Style::new().italic().grey()) + .then_plain(".\n\n") + .then_plain("To confirm the deletion, ") + .then_plain("enter the full name of the room and press enter:"); + + let inner = Join2::vertical( + // The Join prevents the text from filling up the entire available + // space if the editor is wider than the text. + Join2::horizontal( + Text::new(text) + .resize() + .with_max_width(54) + .segment() + .with_growing(false), + Empty::new().segment(), + ) + .segment(), + Join2::horizontal( + Text::new(("&", room_style)).segment().with_fixed(true), + self.name + .widget() + .with_highlight(|s| Styled::new(s, room_style)) + .segment(), + ) + .segment(), + ); + + Popup::new(inner, "Delete room").with_border_style(warn_style) + } +} From 72310d87f5fb46c40222e3fc7c826c291b79d1c9 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 15:02:15 +0100 Subject: [PATCH 177/266] Update changelog --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e34f128..e881bbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,12 +16,13 @@ Procedure when bumping the version number: ## Unreleased ### Added -- Support for multiple euph domains - - Room domain names are now visible in the UI +- Support for multiple euph server domains +- Room domains are visible in the UI +- Domain field to "connect to new room" popup - `--domain` option to `cove export` command ### Changed -- The default euph domain is now https://euphoria.leet.nu/ +- The default euph domain is now https://euphoria.leet.nu/ everywhere - The config file format was changed to support multiple euph servers with different domains. Options previously located at `euph.rooms.*` should be reviewed and moved to `euph.servers."euphoria.leet.nu".rooms.*`. From 4e6e413f3d9284c0e3291c6babb386ec0db53c25 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 15:27:30 +0100 Subject: [PATCH 178/266] Fix not being able to enter . in domain field --- cove/src/ui/rooms/connect.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cove/src/ui/rooms/connect.rs b/cove/src/ui/rooms/connect.rs index fed584b..2bf90c5 100644 --- a/cove/src/ui/rooms/connect.rs +++ b/cove/src/ui/rooms/connect.rs @@ -70,7 +70,7 @@ impl ConnectState { util::handle_editor_input_event(&mut self.name, event, keys, util::is_room_char) } Focus::Domain => { - util::handle_editor_input_event(&mut self.domain, event, keys, util::is_room_char) + util::handle_editor_input_event(&mut self.domain, event, keys, |c| c != '\n') } }; From a522b09f790e17387de9f8faa40a9094f322bcc6 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 15:43:47 +0100 Subject: [PATCH 179/266] Use let-else in some more places --- cove/src/ui.rs | 5 ++--- cove/src/ui/euph/room.rs | 5 +---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/cove/src/ui.rs b/cove/src/ui.rs index cb85876..95cc4d1 100644 --- a/cove/src/ui.rs +++ b/cove/src/ui.rs @@ -181,9 +181,8 @@ impl Ui { } // Handle events (in batches) - let mut event = match event_rx.recv().await { - Some(event) => event, - None => return Ok(()), + let Some(mut event) = event_rx.recv().await else { + return Ok(()); }; let end_time = Instant::now() + EVENT_PROCESSING_TIME; loop { diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index 7fa266f..defb955 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -532,10 +532,7 @@ impl EuphRoom { } pub async fn handle_event(&mut self, event: Event) -> bool { - let room = match &self.room { - None => return false, - Some(room) => room, - }; + let Some(room) = &self.room else { return false }; if event.config().name != room.instance().config().name { // If we allowed names other than the current one, old instances From 2deecc2084d15e17c5bf6f52011c243a43642099 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 17:01:33 +0100 Subject: [PATCH 180/266] Add welcome info box next to room list --- CHANGELOG.md | 2 +- cove/src/main.rs | 8 +++----- cove/src/ui/rooms.rs | 46 +++++++++++++++++++++++++++----------------- cove/src/version.rs | 2 ++ 4 files changed, 34 insertions(+), 24 deletions(-) create mode 100644 cove/src/version.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index e881bbf..b1532cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,9 +17,9 @@ Procedure when bumping the version number: ### Added - Support for multiple euph server domains -- Room domains are visible in the UI - Domain field to "connect to new room" popup - `--domain` option to `cove export` command +- Welcome info box next to room list ### Changed - The default euph domain is now https://euphoria.leet.nu/ everywhere diff --git a/cove/src/main.rs b/cove/src/main.rs index cc5ae63..e5eeb5c 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -22,6 +22,7 @@ mod store; mod ui; mod util; mod vault; +mod version; use std::path::PathBuf; @@ -36,6 +37,7 @@ use toss::Terminal; use crate::logger::Logger; use crate::ui::Ui; use crate::vault::Vault; +use crate::version::{NAME, VERSION}; #[derive(Debug, clap::Parser)] enum Command { @@ -176,11 +178,7 @@ async fn run( config: &'static Config, dirs: &ProjectDirs, ) -> anyhow::Result<()> { - info!( - "Welcome to {} {}", - env!("CARGO_PKG_NAME"), - env!("CARGO_PKG_VERSION") - ); + info!("Welcome to {NAME} {VERSION}",); let vault = open_vault(config, dirs)?; diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index 914bf7e..a2319f9 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -13,12 +13,13 @@ use euphoxide::api::SessionType; use euphoxide::bot::instance::{Event, ServerConfig}; use euphoxide::conn::{self, Joined}; use tokio::sync::mpsc; -use toss::widgets::{BoxedAsync, Join2, Text}; +use toss::widgets::{BoxedAsync, Empty, Join2, Text}; use toss::{Style, Styled, Widget, WidgetExt}; use crate::euph; use crate::macros::logging_unwrap; use crate::vault::{EuphVault, RoomIdentifier, Vault}; +use crate::version::{NAME, VERSION}; use self::connect::{ConnectResult, ConnectState}; use self::delete::{DeleteResult, DeleteState}; @@ -360,20 +361,10 @@ impl Rooms { } async fn render_rows( - config: &Config, list_builder: &mut ListBuilder<'_, RoomIdentifier, Text>, order: Order, euph_rooms: &HashMap<RoomIdentifier, EuphRoom>, ) { - if euph_rooms.is_empty() { - let style = Style::new().grey().italic(); - list_builder.add_unsel(Text::new( - Styled::new("Press ", style) - .and_then(key_bindings::format_binding(&config.keys.general.help)) - .then(" for key bindings", style), - )); - } - let mut rooms = vec![]; for (id, room) in euph_rooms { let state = room.room_state(); @@ -406,16 +397,35 @@ impl Rooms { order: Order, euph_rooms: &HashMap<RoomIdentifier, EuphRoom>, ) -> impl Widget<UiError> + 'a { - let heading_style = Style::new().bold(); - let heading_text = - Styled::new("Rooms", heading_style).then_plain(format!(" ({})", euph_rooms.len())); + let version_info = Styled::new_plain("Welcome to ") + .then(format!("{NAME} {VERSION}"), Style::new().yellow().bold()) + .then_plain("!"); + let help_info = Styled::new("Press ", Style::new().grey()) + .and_then(key_bindings::format_binding(&config.keys.general.help)) + .then(" for key bindings.", Style::new().grey()); + let info = Join2::vertical( + Text::new(version_info).float().with_center_h().segment(), + Text::new(help_info).segment(), + ) + .padding() + .with_horizontal(1) + .border(); + + let heading = Styled::new("Rooms", Style::new().bold()) + .then_plain(format!(" ({})", euph_rooms.len())); let mut list_builder = ListBuilder::new(); - Self::render_rows(config, &mut list_builder, order, euph_rooms).await; + Self::render_rows(&mut list_builder, order, euph_rooms).await; - Join2::vertical( - Text::new(heading_text).segment().with_fixed(true), - list_builder.build(list).segment(), + Join2::horizontal( + Join2::vertical( + Text::new(heading).segment().with_fixed(true), + list_builder.build(list).segment(), + ) + .segment(), + Join2::vertical(info.segment().with_growing(false), Empty::new().segment()) + .segment() + .with_growing(false), ) } diff --git a/cove/src/version.rs b/cove/src/version.rs new file mode 100644 index 0000000..2a4c731 --- /dev/null +++ b/cove/src/version.rs @@ -0,0 +1,2 @@ +pub const NAME: &str = env!("CARGO_PKG_NAME"); +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); From b302229d7c98133f57b25cea9b7e0cd580945769 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 17:17:30 +0100 Subject: [PATCH 181/266] Mention that F1 popup can be scrolled --- CHANGELOG.md | 1 + cove/src/ui/key_bindings.rs | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1532cc..0e2bb08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Procedure when bumping the version number: - The default euph domain is now https://euphoria.leet.nu/ everywhere - The config file format was changed to support multiple euph servers with different domains. Options previously located at `euph.rooms.*` should be reviewed and moved to `euph.servers."euphoria.leet.nu".rooms.*`. +- Tweaked F1 popup ### Fixed - Room deletion popup accepting any room name diff --git a/cove/src/ui/key_bindings.rs b/cove/src/ui/key_bindings.rs index 679c929..8fceda6 100644 --- a/cove/src/ui/key_bindings.rs +++ b/cove/src/ui/key_bindings.rs @@ -79,7 +79,23 @@ pub fn widget<'a>( render_group_info(&mut list_builder, group_info); } - Popup::new(list_builder.build(list), "Key bindings") + let scroll_info_style = Style::new().grey().italic(); + let scroll_info = Styled::new("(Scroll with ", scroll_info_style) + .and_then(format_binding(&config.keys.cursor.down)) + .then(" and ", scroll_info_style) + .and_then(format_binding(&config.keys.cursor.up)) + .then(")", scroll_info_style); + + let inner = Join2::vertical( + list_builder.build(list).segment(), + Text::new(scroll_info) + .float() + .with_center_h() + .segment() + .with_growing(false), + ); + + Popup::new(inner, "Key bindings") } pub fn handle_input_event( From 5bbf389dbe8172343d10916387c159b3e28dd8ae Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 17:26:18 +0100 Subject: [PATCH 182/266] Remove some old todos --- cove-config/src/keys.rs | 1 - cove/src/euph/room.rs | 2 -- cove/src/macros.rs | 1 - 3 files changed, 4 deletions(-) diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index 3ecbb5b..1f8a818 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -122,7 +122,6 @@ pub struct General { #[serde(default = "default::general::confirm")] pub confirm: KeyBinding, /// Advance focus. - // TODO Mention examples where this is used #[serde(default = "default::general::focus")] pub focus: KeyBinding, /// Show this help. diff --git a/cove/src/euph/room.rs b/cove/src/euph/room.rs index 6ae3a17..2831bc6 100644 --- a/cove/src/euph/room.rs +++ b/cove/src/euph/room.rs @@ -1,4 +1,3 @@ -// TODO Stop if room does not exist (e.g. 404) // TODO Remove rl2dev-specific code use std::convert::Infallible; @@ -141,7 +140,6 @@ impl Room { self.log_request_canary = None; } Event::Stopped(_) => { - // TODO Remove room somewhere if this happens? If it doesn't already happen during stabilization self.state = State::Stopped; } } diff --git a/cove/src/macros.rs b/cove/src/macros.rs index a20cec9..bb5834c 100644 --- a/cove/src/macros.rs +++ b/cove/src/macros.rs @@ -1,4 +1,3 @@ -// TODO Get rid of this macro as much as possible macro_rules! logging_unwrap { ($e:expr) => { match $e { From cc1a2866eb08d8c733a20373d14da3d462f49742 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 17:32:59 +0100 Subject: [PATCH 183/266] Simplify retrieving Joined from room::State --- cove/src/euph/room.rs | 9 ++++++++- cove/src/ui/euph/room.rs | 15 +++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/cove/src/euph/room.rs b/cove/src/euph/room.rs index 2831bc6..64ddfe6 100644 --- a/cove/src/euph/room.rs +++ b/cove/src/euph/room.rs @@ -9,7 +9,7 @@ use euphoxide::api::{ UserId, }; use euphoxide::bot::instance::{ConnSnapshot, Event, Instance, InstanceConfig}; -use euphoxide::conn::{self, ConnTx}; +use euphoxide::conn::{self, ConnTx, Joined}; use log::{debug, error, info, warn}; use tokio::select; use tokio::sync::oneshot; @@ -36,6 +36,13 @@ impl State { None } } + + pub fn joined(&self) -> Option<&Joined> { + match self { + Self::Connected(_, conn::State::Joined(joined)) => Some(joined), + _ => None, + } + } } #[derive(Debug, thiserror::Error)] diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index defb955..8240ba5 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -134,10 +134,7 @@ impl EuphRoom { } pub fn room_state_joined(&self) -> Option<&Joined> { - match self.room_state() { - Some(euph::State::Connected(_, conn::State::Joined(ref joined))) => Some(joined), - _ => None, - } + self.room_state().and_then(|s| s.joined()) } pub fn stopped(&self) -> bool { @@ -216,17 +213,15 @@ impl EuphRoom { let room_state = self.room.as_ref().map(|room| room.state()); let status_widget = self.status_widget(room_state).await; - let chat = if let Some(euph::State::Connected(_, conn::State::Joined(joined))) = room_state - { - Self::widget_with_nick_list( + let chat = match room_state.and_then(|s| s.joined()) { + Some(joined) => Self::widget_with_nick_list( &mut self.chat, status_widget, &mut self.nick_list, joined, self.focus, - ) - } else { - Self::widget_without_nick_list(&mut self.chat, status_widget) + ), + None => Self::widget_without_nick_list(&mut self.chat, status_widget), }; let mut layers = vec![chat]; From 1bf4035c575952cd733c20e608c41bd856017c6b Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 17:52:32 +0100 Subject: [PATCH 184/266] Remove message editor cursor when unfocused --- CHANGELOG.md | 1 + cove/src/ui/chat/tree/renderer.rs | 9 ++++++--- cove/src/ui/chat/tree/widgets.rs | 6 +++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e2bb08..d0c101c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Procedure when bumping the version number: - The config file format was changed to support multiple euph servers with different domains. Options previously located at `euph.rooms.*` should be reviewed and moved to `euph.servers."euphoria.leet.nu".rooms.*`. - Tweaked F1 popup +- Tweaked chat message editor when nick list is foused ### Fixed - Room deletion popup accepting any room name diff --git a/cove/src/ui/chat/tree/renderer.rs b/cove/src/ui/chat/tree/renderer.rs index e6753c7..945f77c 100644 --- a/cove/src/ui/chat/tree/renderer.rs +++ b/cove/src/ui/chat/tree/renderer.rs @@ -148,8 +148,12 @@ where None => TreeBlockId::Bottom, }; - // TODO Unhighlighted version when focusing on nick list - let widget = widgets::editor::<M>(indent, &self.context.nick, self.editor); + let widget = widgets::editor::<M>( + indent, + &self.context.nick, + self.context.focused, + self.editor, + ); let widget = Self::predraw(widget, self.context.size, self.widthdb); let mut block = Block::new(id, widget, false); @@ -167,7 +171,6 @@ where None => TreeBlockId::Bottom, }; - // TODO Unhighlighted version when focusing on nick list let widget = widgets::pseudo::<M>(indent, &self.context.nick, self.editor); let widget = Self::predraw(widget, self.context.size, self.widthdb); Block::new(id, widget, false) diff --git a/cove/src/ui/chat/tree/widgets.rs b/cove/src/ui/chat/tree/widgets.rs index ecdb7f4..e52704e 100644 --- a/cove/src/ui/chat/tree/widgets.rs +++ b/cove/src/ui/chat/tree/widgets.rs @@ -116,10 +116,14 @@ pub fn msg_placeholder( pub fn editor<'a, M: ChatMsg>( indent: usize, nick: &str, + focus: bool, editor: &'a mut EditorState, ) -> Boxed<'a, Infallible> { let (nick, content) = M::edit(nick, editor.text()); - let editor = editor.widget().with_highlight(|_| content); + let editor = editor + .widget() + .with_highlight(|_| content) + .with_focus(focus); Join5::horizontal( Seen::new(true).segment().with_fixed(true), From a1b0e151ff966fea2aeb089520d71caa1add477f Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 18:21:55 +0100 Subject: [PATCH 185/266] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0c101c..c4d7166 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,9 @@ Procedure when bumping the version number: ### Added - Support for multiple euph server domains -- Domain field to "connect to new room" popup - `--domain` option to `cove export` command +- `--domain` option to `cove clear-cookies` command +- Domain field to "connect to new room" popup - Welcome info box next to room list ### Changed From 956d3013ea8f9fb52a7af161c44630653798909e Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 2 Jan 2024 18:25:31 +0100 Subject: [PATCH 186/266] Remove unused code --- cove/src/store.rs | 4 ---- cove/src/ui/chat/blocks.rs | 8 -------- cove/src/ui/chat/renderer.rs | 21 --------------------- 3 files changed, 33 deletions(-) diff --git a/cove/src/store.rs b/cove/src/store.rs index 35e02a6..a3601a8 100644 --- a/cove/src/store.rs +++ b/cove/src/store.rs @@ -27,10 +27,6 @@ impl<I> Path<I> { self.0.iter().take(self.0.len() - 1) } - pub fn push(&mut self, segment: I) { - self.0.push(segment) - } - pub fn first(&self) -> &I { self.0.first().expect("path is empty") } diff --git a/cove/src/ui/chat/blocks.rs b/cove/src/ui/chat/blocks.rs index 2a9eb0a..1b91864 100644 --- a/cove/src/ui/chat/blocks.rs +++ b/cove/src/ui/chat/blocks.rs @@ -161,14 +161,6 @@ impl<Id> Blocks<Id> { pub fn shift(&mut self, delta: i32) { self.range = self.range.shifted(delta); } - - pub fn set_top(&mut self, top: i32) { - self.shift(top - self.range.top); - } - - pub fn set_bottom(&mut self, bottom: i32) { - self.shift(bottom - self.range.bottom); - } } pub struct Iter<'a, Id> { diff --git a/cove/src/ui/chat/renderer.rs b/cove/src/ui/chat/renderer.rs index 1edde46..ae0ad8f 100644 --- a/cove/src/ui/chat/renderer.rs +++ b/cove/src/ui/chat/renderer.rs @@ -275,27 +275,6 @@ where } } -pub fn clamp_scroll_biased_upwards<Id, R>(r: &mut R) -where - R: Renderer<Id>, -{ - let area = visible_area(r); - let blocks = r.blocks().range(); - - // Delta that moves blocks.top to the top of the screen. If this is - // negative, we need to move the blocks because they're too low. - let move_to_top = blocks.top - area.top; - - // Delta that moves blocks.bottom to the bottom of the screen. If this is - // positive, we need to move the blocks because they're too high. - let move_to_bottom = blocks.bottom - area.bottom; - - // If the screen is higher, the blocks should rather be moved to the top - // than the bottom because of the upwards bias. - let delta = 0.max(move_to_bottom).min(move_to_top); - r.blocks_mut().shift(delta); -} - pub fn clamp_scroll_biased_downwards<Id, R>(r: &mut R) where R: Renderer<Id>, From c286e0244c289f7139a06787f5e91bab635f258b Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 3 Jan 2024 02:05:49 +0100 Subject: [PATCH 187/266] Add time_zone config option --- CHANGELOG.md | 1 + cove-config/src/lib.rs | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4d7166..6a22b86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Procedure when bumping the version number: ### Added - Support for multiple euph server domains +- `time_zone` config option - `--domain` option to `cove export` command - `--domain` option to `cove clear-cookies` command - Domain field to "connect to new room" popup diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 5aa6522..5d88867 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -77,18 +77,40 @@ pub struct Config { /// Initial sort order of rooms list. /// - /// `alphabet` sorts rooms in alphabetic order. + /// `"alphabet"` sorts rooms in alphabetic order. /// - /// `importance` sorts rooms by the following criteria (in descending order - /// of priority): + /// `"importance"` sorts rooms by the following criteria (in descending + /// order of priority): /// /// 1. connected rooms before unconnected rooms /// 2. rooms with unread messages before rooms without /// 3. alphabetic order #[serde(default)] - #[document(default = "`alphabet`")] + #[document(default = "`\"alphabet\"`")] pub rooms_sort_order: RoomsSortOrder, + /// Time zone that chat timestamps should be displayed in. + /// + /// This option is interpreted as a POSIX TZ string. It is described here in + /// further detail: + /// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html + /// + /// On a normal system, the string `"localtime"` as well as any value from + /// the "TZ identifier" column of the following wikipedia article should be + /// valid TZ strings: + /// https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + /// + /// If the option is not specified, cove uses the contents of the `TZ` + /// environment variable instead. If the `TZ` environment variable does not + /// exist, cove uses the system's local time zone. + /// + /// **Warning:** On Windows, cove can't get the local time zone and uses UTC + /// instead. However, you can still specify a path to a tz data file or a + /// custom time zone string. + #[serde(default)] + #[document(default = "$TZ or local time zone")] + pub time_zone: Option<String>, + #[serde(default)] #[document(no_default)] pub euph: Euph, @@ -115,4 +137,8 @@ impl Config { } EuphRoom::default() } + + pub fn time_zone_ref(&self) -> Option<&str> { + self.time_zone.as_ref().map(|s| s as &str) + } } From 43a8b91dca9eccaab27f2fd0597afccc0d63cbfb Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 3 Jan 2024 02:06:33 +0100 Subject: [PATCH 188/266] Load time zone when opening vault --- Cargo.lock | 16 ++++++++++++++++ cove/Cargo.toml | 1 + cove/src/main.rs | 14 +++++++++----- cove/src/util.rs | 22 ++++++++++++++++++++++ cove/src/vault.rs | 17 ++++++++++++----- 5 files changed, 60 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ed64bb..4cdb574 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,6 +246,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "const_fn" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" + [[package]] name = "cookie" version = "0.18.0" @@ -297,6 +303,7 @@ dependencies = [ "tokio", "tokio-tungstenite", "toss", + "tz-rs", "unicode-segmentation", "unicode-width", "vault", @@ -1507,6 +1514,15 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "tz-rs" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33851b15c848fad2cf4b105c6bb66eb9512b6f6c44a4b13f57c53c73c707e2b4" +dependencies = [ + "const_fn", +] + [[package]] name = "unicode-bidi" version = "0.3.14" diff --git a/cove/Cargo.toml b/cove/Cargo.toml index 9687080..b764a4a 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -24,6 +24,7 @@ open = "5.0.1" rusqlite = { version = "0.30.0", features = ["bundled", "time"] } serde_json = "1.0.108" tokio = { version = "1.35.1", features = ["full"] } +tz-rs = "0.6.14" unicode-segmentation = "1.10.1" unicode-width = "0.1.11" diff --git a/cove/src/main.rs b/cove/src/main.rs index e5eeb5c..0308604 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -129,14 +129,18 @@ fn update_config_with_args(config: &mut Config, args: &Args) { config.offline |= args.offline; } -fn open_vault(config: &Config, dirs: &ProjectDirs) -> rusqlite::Result<Vault> { - if config.ephemeral { - vault::launch_in_memory() +fn open_vault(config: &Config, dirs: &ProjectDirs) -> anyhow::Result<Vault> { + let time_zone = util::load_time_zone(config.time_zone_ref())?; + + let vault = if config.ephemeral { + vault::launch_in_memory(time_zone)? } else { let data_dir = data_dir(config, dirs); eprintln!("Data dir: {}", data_dir.to_string_lossy()); - vault::launch(&data_dir.join("vault.db")) - } + vault::launch(&data_dir.join("vault.db"), time_zone)? + }; + + Ok(vault) } #[tokio::main] diff --git a/cove/src/util.rs b/cove/src/util.rs index b6e7c97..67c0e68 100644 --- a/cove/src/util.rs +++ b/cove/src/util.rs @@ -1,4 +1,7 @@ use std::convert::Infallible; +use std::env; + +use tz::{TimeZone, TzError}; pub trait InfallibleExt { type Inner; @@ -13,3 +16,22 @@ impl<T> InfallibleExt for Result<T, Infallible> { self.expect("infallible") } } + +/// Load a [`TimeZone`] specified by a string, or by the `TZ` environment +/// variable if no string is provided. +/// +/// If a string is provided, it is interpreted in the same format that the `TZ` +/// environment variable uses. +/// +/// If no string and no `TZ` environment variable could be found, the system +/// local time is used. +pub fn load_time_zone(tz_string: Option<&str>) -> Result<TimeZone, TzError> { + let env_tz = env::var("TZ").ok(); + let tz_string = tz_string.or(env_tz.as_ref().map(|s| s as &str)); + + match &tz_string { + // At the moment, TimeZone::from_posix_tz does not support "localtime" on windows, but other time zon + Some("localtime") | None => TimeZone::local(), + Some(tz_string) => TimeZone::from_posix_tz(tz_string), + } +} diff --git a/cove/src/vault.rs b/cove/src/vault.rs index 6861901..250456d 100644 --- a/cove/src/vault.rs +++ b/cove/src/vault.rs @@ -6,6 +6,7 @@ use std::fs; use std::path::Path; use rusqlite::Connection; +use tz::TimeZone; use vault::tokio::TokioVault; use vault::Action; @@ -14,6 +15,7 @@ pub use self::euph::{EuphRoomVault, EuphVault, RoomIdentifier}; #[derive(Debug, Clone)] pub struct Vault { tokio_vault: TokioVault, + time_zone: TimeZone, ephemeral: bool, } @@ -46,18 +48,23 @@ impl Vault { } } -fn launch_from_connection(conn: Connection, ephemeral: bool) -> rusqlite::Result<Vault> { +fn launch_from_connection( + conn: Connection, + time_zone: TimeZone, + ephemeral: bool, +) -> rusqlite::Result<Vault> { conn.pragma_update(None, "foreign_keys", true)?; conn.pragma_update(None, "trusted_schema", false)?; let tokio_vault = TokioVault::launch_and_prepare(conn, &migrate::MIGRATIONS, prepare::prepare)?; Ok(Vault { tokio_vault, + time_zone, ephemeral, }) } -pub fn launch(path: &Path) -> rusqlite::Result<Vault> { +pub fn launch(path: &Path, time_zone: TimeZone) -> rusqlite::Result<Vault> { // If this fails, rusqlite will complain about not being able to open the db // file, which saves me from adding a separate vault error type. let _ = fs::create_dir_all(path.parent().expect("path to file")); @@ -72,10 +79,10 @@ pub fn launch(path: &Path) -> rusqlite::Result<Vault> { conn.pragma_update(None, "locking_mode", "exclusive")?; conn.pragma_update(None, "journal_mode", "wal")?; - launch_from_connection(conn, false) + launch_from_connection(conn, time_zone, false) } -pub fn launch_in_memory() -> rusqlite::Result<Vault> { +pub fn launch_in_memory(time_zone: TimeZone) -> rusqlite::Result<Vault> { let conn = Connection::open_in_memory()?; - launch_from_connection(conn, true) + launch_from_connection(conn, time_zone, true) } From 1189e3eb7b12cea9c6e2ce36067d4048d8ead337 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 3 Jan 2024 02:20:59 +0100 Subject: [PATCH 189/266] Display chat time stamps in configured time zone --- cove/src/euph/small_message.rs | 6 ++++-- cove/src/logger.rs | 4 ++-- cove/src/main.rs | 1 + cove/src/ui/chat.rs | 2 +- cove/src/ui/chat/tree/widgets.rs | 2 +- cove/src/util.rs | 12 ++++++++++++ cove/src/vault.rs | 8 ++++---- cove/src/vault/euph.rs | 5 +++++ 8 files changed, 30 insertions(+), 10 deletions(-) diff --git a/cove/src/euph/small_message.rs b/cove/src/euph/small_message.rs index 2751058..0642801 100644 --- a/cove/src/euph/small_message.rs +++ b/cove/src/euph/small_message.rs @@ -4,6 +4,7 @@ use crossterm::style::Stylize; use euphoxide::api::{MessageId, Snowflake, Time}; use time::OffsetDateTime; use toss::{Style, Styled}; +use tz::TimeZone; use crate::store::Msg; use crate::ui::ChatMsg; @@ -207,6 +208,7 @@ pub struct SmallMessage { pub id: MessageId, pub parent: Option<MessageId>, pub time: Time, + pub time_zone: &'static TimeZone, pub nick: String, pub content: String, pub seen: bool, @@ -270,8 +272,8 @@ impl Msg for SmallMessage { } impl ChatMsg for SmallMessage { - fn time(&self) -> OffsetDateTime { - self.time.0 + fn time(&self) -> Option<OffsetDateTime> { + crate::util::convert_to_time_zone(self.time_zone, self.time.0) } fn styled(&self) -> (Styled, Styled) { diff --git a/cove/src/logger.rs b/cove/src/logger.rs index 731a000..b8b696b 100644 --- a/cove/src/logger.rs +++ b/cove/src/logger.rs @@ -42,8 +42,8 @@ impl Msg for LogMsg { } impl ChatMsg for LogMsg { - fn time(&self) -> OffsetDateTime { - self.time + fn time(&self) -> Option<OffsetDateTime> { + Some(self.time) } fn styled(&self) -> (Styled, Styled) { diff --git a/cove/src/main.rs b/cove/src/main.rs index 0308604..338642c 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -131,6 +131,7 @@ fn update_config_with_args(config: &mut Config, args: &Args) { fn open_vault(config: &Config, dirs: &ProjectDirs) -> anyhow::Result<Vault> { let time_zone = util::load_time_zone(config.time_zone_ref())?; + let time_zone = Box::leak(Box::new(time_zone)); let vault = if config.ephemeral { vault::launch_in_memory(time_zone)? diff --git a/cove/src/ui/chat.rs b/cove/src/ui/chat.rs index 24ea82e..6a9c9c2 100644 --- a/cove/src/ui/chat.rs +++ b/cove/src/ui/chat.rs @@ -18,7 +18,7 @@ use self::tree::TreeViewState; use super::UiError; pub trait ChatMsg { - fn time(&self) -> OffsetDateTime; + fn time(&self) -> Option<OffsetDateTime>; fn styled(&self) -> (Styled, Styled); fn edit(nick: &str, content: &str) -> (Styled, Styled); fn pseudo(nick: &str, content: &str) -> (Styled, Styled); diff --git a/cove/src/ui/chat/tree/widgets.rs b/cove/src/ui/chat/tree/widgets.rs index e52704e..2f1a1ff 100644 --- a/cove/src/ui/chat/tree/widgets.rs +++ b/cove/src/ui/chat/tree/widgets.rs @@ -58,7 +58,7 @@ pub fn msg<M: Msg + ChatMsg>( Join5::horizontal( Seen::new(msg.seen()).segment().with_fixed(true), - Time::new(Some(msg.time()), style_time(highlighted)) + Time::new(msg.time(), style_time(highlighted)) .padding() .with_right(1) .with_stretch(true) diff --git a/cove/src/util.rs b/cove/src/util.rs index 67c0e68..1bc9d6b 100644 --- a/cove/src/util.rs +++ b/cove/src/util.rs @@ -1,6 +1,7 @@ use std::convert::Infallible; use std::env; +use time::{OffsetDateTime, UtcOffset}; use tz::{TimeZone, TzError}; pub trait InfallibleExt { @@ -35,3 +36,14 @@ pub fn load_time_zone(tz_string: Option<&str>) -> Result<TimeZone, TzError> { Some(tz_string) => TimeZone::from_posix_tz(tz_string), } } + +pub fn convert_to_time_zone(tz: &TimeZone, time: OffsetDateTime) -> Option<OffsetDateTime> { + let utc_offset_in_seconds = tz + .find_local_time_type(time.unix_timestamp()) + .ok()? + .ut_offset(); + + let utc_offset = UtcOffset::from_whole_seconds(utc_offset_in_seconds).ok()?; + + Some(time.to_offset(utc_offset)) +} diff --git a/cove/src/vault.rs b/cove/src/vault.rs index 250456d..55abbf0 100644 --- a/cove/src/vault.rs +++ b/cove/src/vault.rs @@ -15,7 +15,7 @@ pub use self::euph::{EuphRoomVault, EuphVault, RoomIdentifier}; #[derive(Debug, Clone)] pub struct Vault { tokio_vault: TokioVault, - time_zone: TimeZone, + time_zone: &'static TimeZone, ephemeral: bool, } @@ -50,7 +50,7 @@ impl Vault { fn launch_from_connection( conn: Connection, - time_zone: TimeZone, + time_zone: &'static TimeZone, ephemeral: bool, ) -> rusqlite::Result<Vault> { conn.pragma_update(None, "foreign_keys", true)?; @@ -64,7 +64,7 @@ fn launch_from_connection( }) } -pub fn launch(path: &Path, time_zone: TimeZone) -> rusqlite::Result<Vault> { +pub fn launch(path: &Path, time_zone: &'static TimeZone) -> rusqlite::Result<Vault> { // If this fails, rusqlite will complain about not being able to open the db // file, which saves me from adding a separate vault error type. let _ = fs::create_dir_all(path.parent().expect("path to file")); @@ -82,7 +82,7 @@ pub fn launch(path: &Path, time_zone: TimeZone) -> rusqlite::Result<Vault> { launch_from_connection(conn, time_zone, false) } -pub fn launch_in_memory(time_zone: TimeZone) -> rusqlite::Result<Vault> { +pub fn launch_in_memory(time_zone: &'static TimeZone) -> rusqlite::Result<Vault> { let conn = Connection::open_in_memory()?; launch_from_connection(conn, time_zone, true) } diff --git a/cove/src/vault/euph.rs b/cove/src/vault/euph.rs index 4664446..8091613 100644 --- a/cove/src/vault/euph.rs +++ b/cove/src/vault/euph.rs @@ -239,6 +239,8 @@ macro_rules! euph_room_vault_actions { $( struct $struct { room: RoomIdentifier, + #[allow(unused)] + time_zone: &'static tz::TimeZone, $( $arg: $arg_ty, )* } )* @@ -248,6 +250,7 @@ macro_rules! euph_room_vault_actions { pub async fn $fn(&self, $( $arg: $arg_ty, )* ) -> Result<$res, vault::tokio::Error<rusqlite::Error>> { self.vault.vault.tokio_vault.execute($struct { room: self.room.clone(), + time_zone: self.vault.vault.time_zone, $( $arg, )* }).await } @@ -607,6 +610,7 @@ impl Action for GetMsg { id: MessageId(row.get::<_, WSnowflake>(0)?.0), parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)), time: row.get::<_, WTime>(2)?.0, + time_zone: self.time_zone, nick: row.get(3)?, content: row.get(4)?, seen: row.get(5)?, @@ -700,6 +704,7 @@ impl Action for GetTree { id: MessageId(row.get::<_, WSnowflake>(0)?.0), parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)), time: row.get::<_, WTime>(2)?.0, + time_zone: self.time_zone, nick: row.get(3)?, content: row.get(4)?, seen: row.get(5)?, From f555414716666197e021d1038c9bae9af3027785 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 3 Jan 2024 02:53:04 +0100 Subject: [PATCH 190/266] Improve tz load error message very slightly --- cove/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cove/src/main.rs b/cove/src/main.rs index 338642c..5bce993 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -26,6 +26,7 @@ mod version; use std::path::PathBuf; +use anyhow::Context; use clap::Parser; use cove_config::doc::Document; use cove_config::Config; @@ -130,7 +131,8 @@ fn update_config_with_args(config: &mut Config, args: &Args) { } fn open_vault(config: &Config, dirs: &ProjectDirs) -> anyhow::Result<Vault> { - let time_zone = util::load_time_zone(config.time_zone_ref())?; + let time_zone = + util::load_time_zone(config.time_zone_ref()).context("failed to load time zone")?; let time_zone = Box::leak(Box::new(time_zone)); let vault = if config.ephemeral { From f94eb00dcd068878223306a90efc81348bddaa09 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 3 Jan 2024 16:30:01 +0100 Subject: [PATCH 191/266] Clean up config docs --- cove-config/src/doc.rs | 25 +++++-------------------- cove-config/src/lib.rs | 6 +----- 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/cove-config/src/doc.rs b/cove-config/src/doc.rs index 3221eb5..16ed3ac 100644 --- a/cove-config/src/doc.rs +++ b/cove-config/src/doc.rs @@ -17,15 +17,11 @@ Here is an example config that changes a few different options: measure_widths = true rooms_sort_order = "importance" -[euph.rooms.welcome] -autojoin = true - -[euph.rooms.test] -username = "badingle" -force_username = true - -[euph.rooms.private] -password = "foobar" +[euph.servers."euphoria.leet.nu".rooms] +welcome.autojoin = true +test.username = "badingle" +test.force_username = true +private.password = "foobar" [keys] general.abort = ["esc", "ctrl+c"] @@ -33,17 +29,6 @@ general.exit = "ctrl+q" tree.action.fold_tree = "f" ``` -If you want to configure lots of rooms, TOML lets you write this in a more -compact way: - -```toml -[euph.rooms] -foo = { autojoin = true } -bar = { autojoin = true } -baz = { autojoin = true } -private = { autojoin = true, password = "foobar" } -``` - ## Key bindings Key bindings are specified as strings or lists of strings. Each string specifies diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 5d88867..4b5e2fe 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -49,7 +49,6 @@ pub struct Config { /// /// See also the `--ephemeral` command line option. #[serde(default)] - #[document(default = "`false`")] pub ephemeral: bool, /// Whether to measure the width of characters as displayed by the terminal @@ -61,7 +60,6 @@ pub struct Config { /// /// See also the `--measure-graphemes` command line option. #[serde(default)] - #[document(default = "`false`")] pub measure_widths: bool, /// Whether to start in offline mode. @@ -72,7 +70,6 @@ pub struct Config { /// /// See also the `--offline` command line option. #[serde(default)] - #[document(default = "`false`")] pub offline: bool, /// Initial sort order of rooms list. @@ -86,7 +83,6 @@ pub struct Config { /// 2. rooms with unread messages before rooms without /// 3. alphabetic order #[serde(default)] - #[document(default = "`\"alphabet\"`")] pub rooms_sort_order: RoomsSortOrder, /// Time zone that chat timestamps should be displayed in. @@ -108,7 +104,7 @@ pub struct Config { /// instead. However, you can still specify a path to a tz data file or a /// custom time zone string. #[serde(default)] - #[document(default = "$TZ or local time zone")] + #[document(default = "`$TZ` or local system time zone")] pub time_zone: Option<String>, #[serde(default)] From a8570cf9c5ac71ce350a83a3f47e31b8c3059cf2 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 3 Jan 2024 17:02:57 +0100 Subject: [PATCH 192/266] Make TZ env var override time_zone config option --- cove-config/src/lib.rs | 5 ++--- cove/src/util.rs | 15 ++++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 4b5e2fe..22fc332 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -96,9 +96,8 @@ pub struct Config { /// valid TZ strings: /// https://en.wikipedia.org/wiki/List_of_tz_database_time_zones /// - /// If the option is not specified, cove uses the contents of the `TZ` - /// environment variable instead. If the `TZ` environment variable does not - /// exist, cove uses the system's local time zone. + /// If the `TZ` environment variable exists, it overrides this option. If + /// neither exist, cove uses the system's local time zone. /// /// **Warning:** On Windows, cove can't get the local time zone and uses UTC /// instead. However, you can still specify a path to a tz data file or a diff --git a/cove/src/util.rs b/cove/src/util.rs index 1bc9d6b..6bcbf3e 100644 --- a/cove/src/util.rs +++ b/cove/src/util.rs @@ -18,20 +18,21 @@ impl<T> InfallibleExt for Result<T, Infallible> { } } -/// Load a [`TimeZone`] specified by a string, or by the `TZ` environment -/// variable if no string is provided. +/// Load a [`TimeZone`] specified by the `TZ` environment varible, or by the +/// provided string if the environment variable does not exist. /// /// If a string is provided, it is interpreted in the same format that the `TZ` /// environment variable uses. /// -/// If no string and no `TZ` environment variable could be found, the system -/// local time is used. +/// If no `TZ` environment variable could be found and no string is provided, +/// the system local time (or UTC on Windows) is used. pub fn load_time_zone(tz_string: Option<&str>) -> Result<TimeZone, TzError> { - let env_tz = env::var("TZ").ok(); - let tz_string = tz_string.or(env_tz.as_ref().map(|s| s as &str)); + let env_string = env::var("TZ").ok(); + let tz_string = env_string.as_ref().map(|s| s as &str).or(tz_string); match &tz_string { - // At the moment, TimeZone::from_posix_tz does not support "localtime" on windows, but other time zon + // At the moment, TimeZone::from_posix_tz does not support "localtime" + // on Windows, so we handle that case manually. Some("localtime") | None => TimeZone::local(), Some(tz_string) => TimeZone::from_posix_tz(tz_string), } From 2d2dab11ba869d98f7df500893cb6e0f19850073 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 3 Jan 2024 18:13:15 +0100 Subject: [PATCH 193/266] Reduce connection timout to 10 seconds --- CHANGELOG.md | 1 + cove/src/ui/euph/room.rs | 1 - cove/src/ui/rooms.rs | 5 ++++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a22b86..cfc6a53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Procedure when bumping the version number: Options previously located at `euph.rooms.*` should be reviewed and moved to `euph.servers."euphoria.leet.nu".rooms.*`. - Tweaked F1 popup - Tweaked chat message editor when nick list is foused +- Reduced connection timeout from 30 seconds to 10 seconds ### Fixed - Room deletion popup accepting any room name diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index 8240ba5..c1c4fec 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -98,7 +98,6 @@ impl EuphRoom { pub fn connect(&mut self, next_instance_id: &mut usize) { if self.room.is_none() { let room = self.vault().room(); - // TODO Decrease ping timeout let instance_config = self .server_config .clone() diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index a2319f9..48e3ad3 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -5,6 +5,7 @@ use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; use std::iter; use std::sync::{Arc, Mutex}; +use std::time::Duration; use cove_config::{Config, Keys, RoomsSortOrder}; use cove_input::InputEvent; @@ -60,7 +61,9 @@ impl EuphServer { let cookies = logging_unwrap!(vault.cookies(domain.clone()).await); let config = ServerConfig::default() .domain(domain) - .cookies(Arc::new(Mutex::new(cookies))); + .cookies(Arc::new(Mutex::new(cookies))) + .timeout(Duration::from_secs(10)); + Self { config, next_instance_id: 0, From 88ba77b95575ed2cd3396813eefb9b497421714e Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 3 Jan 2024 18:24:17 +0100 Subject: [PATCH 194/266] Move domains in front of room names in the UI The reasoning behind this change in the room list is that putting the domain (which rarely changes) in front of the room name (which often changes) is a lot more readable. It also moves it away from the status info parentheses, making them more obvious again. The reasoning for the individual room view is consistency. Putting the domain at the end here looked fine, but putting it in front matches the room list and still looks fine. --- cove-config/CONFIG.md | 0 cove/src/ui/euph/room.rs | 5 ++--- cove/src/ui/rooms.rs | 21 +++++++++++++-------- 3 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 cove-config/CONFIG.md diff --git a/cove-config/CONFIG.md b/cove-config/CONFIG.md new file mode 100644 index 0000000..e69de29 diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index c1c4fec..b226b75 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -287,7 +287,8 @@ impl EuphRoom { async fn status_widget(&self, state: Option<&euph::State>) -> impl Widget<UiError> { let room_style = Style::new().bold().blue(); - let mut info = Styled::new(format!("&{}", self.name()), room_style); + let mut info = Styled::new(format!("{} ", self.domain()), Style::new().grey()) + .then(format!("&{}", self.name()), room_style); info = match state { None | Some(euph::State::Stopped) => info.then_plain(", archive"), @@ -310,8 +311,6 @@ impl EuphRoom { } }; - info = info.then(format!(" - {}", self.domain()), Style::new().grey()); - let unseen = self.unseen_msgs_count().await; if unseen > 0 { info = info diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index 48e3ad3..4275b48 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -356,10 +356,9 @@ impl Rooms { fn sort_rooms(rooms: &mut [(&RoomIdentifier, Option<&euph::State>, usize)], order: Order) { match order { - Order::Alphabet => rooms.sort_unstable_by_key(|(id, _, _)| (&id.name, &id.domain)), - Order::Importance => rooms.sort_unstable_by_key(|(id, state, unseen)| { - (state.is_none(), *unseen == 0, &id.name, &id.domain) - }), + Order::Alphabet => rooms.sort_unstable_by_key(|(id, _, _)| *id), + Order::Importance => rooms + .sort_unstable_by_key(|(id, state, unseen)| (state.is_none(), *unseen == 0, *id)), } } @@ -379,15 +378,21 @@ impl Rooms { let id = id.clone(); let info = Self::format_room_info(state, unseen); list_builder.add_sel(id.clone(), move |selected| { - let style = if selected { + let domain_style = if selected { + Style::new().black().on_white() + } else { + Style::new().grey() + }; + + let room_style = if selected { Style::new().bold().black().on_white() } else { Style::new().bold().blue() }; - let text = Styled::new(format!("&{}", id.name), style) - .and_then(info) - .then(format!(" - {}", id.domain), Style::new().grey()); + let text = Styled::new(format!("{} ", id.domain), domain_style) + .then(format!("&{}", id.name), room_style) + .and_then(info); Text::new(text) }); From e0948c4f1ceabcf16b6cc5c6688af976ac4bd3f0 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 3 Jan 2024 23:51:20 +0100 Subject: [PATCH 195/266] Fix duplicated key presses on windows --- CHANGELOG.md | 1 + cove-input/src/lib.rs | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfc6a53..c4c3968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Procedure when bumping the version number: ### Fixed - Room deletion popup accepting any room name +- Duplicated key presses on Windows ## v0.7.1 - 2023-08-31 diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index b42fdcb..ba3bd63 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -4,7 +4,7 @@ use std::io; use std::sync::Arc; pub use cove_macro::KeyGroup; -use crossterm::event::{Event, KeyEvent}; +use crossterm::event::{Event, KeyEvent, KeyEventKind}; use parking_lot::FairMutex; use toss::{Frame, Terminal, WidthDb}; @@ -58,11 +58,15 @@ impl<'a> InputEvent<'a> { } } + /// If the current event represents a key press, returns the [`KeyEvent`] + /// associated with that key press. pub fn key_event(&self) -> Option<KeyEvent> { - match &self.event { - Event::Key(event) => Some(*event), - _ => None, + if let Event::Key(event) = &self.event { + if matches!(event.kind, KeyEventKind::Press | KeyEventKind::Repeat) { + return Some(*event); + } } + None } pub fn paste_event(&self) -> Option<&str> { From dadbb205e51ce43a69fc57e27f00fd7e7dde2766 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 3 Jan 2024 23:51:27 +0100 Subject: [PATCH 196/266] Fix cargo doc warnings --- cove-config/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 22fc332..fc1e6af 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -89,12 +89,12 @@ pub struct Config { /// /// This option is interpreted as a POSIX TZ string. It is described here in /// further detail: - /// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html + /// <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html> /// /// On a normal system, the string `"localtime"` as well as any value from /// the "TZ identifier" column of the following wikipedia article should be /// valid TZ strings: - /// https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + /// <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones> /// /// If the `TZ` environment variable exists, it overrides this option. If /// neither exist, cove uses the system's local time zone. From a0b029b353bc80692c90513244bf9371297ebc7b Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 4 Jan 2024 13:52:18 +0100 Subject: [PATCH 197/266] Update dependencies --- Cargo.lock | 60 +++++++++++++++++++++---------------------- Cargo.toml | 4 +-- cove-macro/Cargo.toml | 6 ++--- cove/Cargo.toml | 6 ++--- flake.lock | 12 ++++----- 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4cdb574..9061b62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if", "once_cell", @@ -94,15 +94,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.77" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9d19de80eff169429ac1e9f48fffb163916b448a44e8e046186232046d9e1f9" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "async-trait" -version = "0.1.75" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", @@ -396,9 +396,9 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "deranged" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", @@ -742,9 +742,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "miniz_oxide" @@ -900,18 +900,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -1088,9 +1088,9 @@ checksum = "9e9d979b3ce68192e42760c7810125eb6cf2ea10efae545a156063e61f314e2a" [[package]] name = "rustls-webpki" -version = "0.102.0" +version = "0.102.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de2635c8bc2b88d367767c5de8ea1d8db9af3f6219eba28442242d9ab81d1b89" +checksum = "ef4ca26037c909dedb327b48c3327d0ba91d3dd3c4e05dad328f210ffb68e95b" dependencies = [ "ring", "rustls-pki-types", @@ -1143,9 +1143,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.193" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" dependencies = [ "serde_derive", ] @@ -1162,9 +1162,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" dependencies = [ "proc-macro2", "quote", @@ -1183,9 +1183,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", @@ -1287,9 +1287,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.43" +version = "2.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +checksum = "1726efe18f42ae774cc644f330953a5e7b3c3003d3edcecf18850fe9d4dd9afb" dependencies = [ "proc-macro2", "quote", @@ -1311,18 +1311,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.52" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.52" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", @@ -1786,9 +1786,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.31" +version = "0.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c" +checksum = "8434aeec7b290e8da5c3f0d628cb0eac6cabcb31d14bb74f779a08109a5914d6" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index fbd9231..19a2d92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,9 @@ edition = "2021" [workspace.dependencies] crossterm = "0.27.0" parking_lot = "0.12.1" -serde = { version = "1.0.193", features = ["derive"] } +serde = { version = "1.0.194", features = ["derive"] } serde_either = "0.2.1" -thiserror = "1.0.52" +thiserror = "1.0.56" [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" diff --git a/cove-macro/Cargo.toml b/cove-macro/Cargo.toml index 637722e..f0ab3fe 100644 --- a/cove-macro/Cargo.toml +++ b/cove-macro/Cargo.toml @@ -5,9 +5,9 @@ edition = { workspace = true } [dependencies] case = "1.0.0" -proc-macro2 = "1.0.71" -quote = "1.0.33" -syn = "2.0.43" +proc-macro2 = "1.0.75" +quote = "1.0.35" +syn = "2.0.47" [lib] proc-macro = true diff --git a/cove/Cargo.toml b/cove/Cargo.toml index b764a4a..15d5233 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -12,8 +12,8 @@ parking_lot = { workspace = true } thiserror = { workspace = true } toss = { workspace = true } -anyhow = "1.0.77" -async-trait = "0.1.75" +anyhow = "1.0.79" +async-trait = "0.1.77" clap = { version = "4.4.12", features = ["derive", "deprecated"] } cookie = "0.18.0" directories = "5.0.1" @@ -22,7 +22,7 @@ log = { version = "0.4.20", features = ["std"] } once_cell = "1.19.0" open = "5.0.1" rusqlite = { version = "0.30.0", features = ["bundled", "time"] } -serde_json = "1.0.108" +serde_json = "1.0.111" tokio = { version = "1.35.1", features = ["full"] } tz-rs = "0.6.14" unicode-segmentation = "1.10.1" diff --git a/flake.lock b/flake.lock index bc5ec53..4a3030f 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1679567394, - "narHash": "sha256-ZvLuzPeARDLiQUt6zSZFGOs+HZmE+3g4QURc8mkBsfM=", + "lastModified": 1698420672, + "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", "owner": "nix-community", "repo": "naersk", - "rev": "88cd22380154a2c36799fe8098888f0f59861a15", + "rev": "aeb58d5e8faead8980a807c840232697982d47b9", "type": "github" }, "original": { @@ -22,11 +22,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1680643271, - "narHash": "sha256-m76rYcvqs+NzTyETfxh1o/9gKdBuJ/Hl+PI/kp73mDw=", + "lastModified": 1704371841, + "narHash": "sha256-ScUTxDRvgEK6W0hJqzodk4VZM1pqVJO3o/Ru99Oc7mI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "246567a3ad88e3119c2001e2fe78be233474cde0", + "rev": "526411af967efacb9f1efefe9c8664bede47b8b8", "type": "github" }, "original": { From 6352fcf6390cf21fa324331939d0b9f5333faf99 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 4 Jan 2024 13:54:03 +0100 Subject: [PATCH 198/266] Bump version to v0.8.0 --- CHANGELOG.md | 3 +++ CONFIG.md | 65 ++++++++++++++++++++++++++++++---------------------- Cargo.lock | 8 +++---- Cargo.toml | 2 +- 4 files changed, 45 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4c3968..6d9b50e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,11 @@ Procedure when bumping the version number: ## Unreleased +## v0.8.0 - 2024-01-04 + ### Added - Support for multiple euph server domains +- Support for `TZ` environment variable - `time_zone` config option - `--domain` option to `cove export` command - `--domain` option to `cove clear-cookies` command diff --git a/CONFIG.md b/CONFIG.md index a3f25ac..fca0589 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -8,15 +8,11 @@ Here is an example config that changes a few different options: measure_widths = true rooms_sort_order = "importance" -[euph.rooms.welcome] -autojoin = true - -[euph.rooms.test] -username = "badingle" -force_username = true - -[euph.rooms.private] -password = "foobar" +[euph.servers."euphoria.leet.nu".rooms] +welcome.autojoin = true +test.username = "badingle" +test.force_username = true +private.password = "foobar" [keys] general.abort = ["esc", "ctrl+c"] @@ -24,17 +20,6 @@ general.exit = "ctrl+q" tree.action.fold_tree = "f" ``` -If you want to configure lots of rooms, TOML lets you write this in a more -compact way: - -```toml -[euph.rooms] -foo = { autojoin = true } -bar = { autojoin = true } -baz = { autojoin = true } -private = { autojoin = true, password = "foobar" } -``` - ## Key bindings Key bindings are specified as strings or lists of strings. Each string specifies @@ -94,7 +79,7 @@ any options related to the data dir. See also the `--ephemeral` command line option. -### `euph.rooms.<room>.autojoin` +### `euph.servers.<domain>.rooms.<room>.autojoin` **Required:** yes **Type:** boolean @@ -102,7 +87,7 @@ See also the `--ephemeral` command line option. Whether to automatically join this room on startup. -### `euph.rooms.<room>.force_username` +### `euph.servers.<domain>.rooms.<room>.force_username` **Required:** yes **Type:** boolean @@ -112,7 +97,7 @@ If `euph.rooms.<room>.username` is set, this will force cove to set the username even if there is already a different username associated with the current session. -### `euph.rooms.<room>.password` +### `euph.servers.<domain>.rooms.<room>.password` **Required:** no **Type:** string @@ -120,7 +105,7 @@ the current session. If set, cove will try once to use this password to authenticate, should the room be password-protected. -### `euph.rooms.<room>.username` +### `euph.servers.<domain>.rooms.<room>.username` **Required:** no **Type:** string @@ -642,15 +627,39 @@ See also the `--offline` command line option. **Required:** yes **Type:** string **Values:** `"alphabet"`, `"importance"` -**Default:** `alphabet` +**Default:** `"alphabet"` Initial sort order of rooms list. -`alphabet` sorts rooms in alphabetic order. +`"alphabet"` sorts rooms in alphabetic order. -`importance` sorts rooms by the following criteria (in descending order -of priority): +`"importance"` sorts rooms by the following criteria (in descending +order of priority): 1. connected rooms before unconnected rooms 2. rooms with unread messages before rooms without 3. alphabetic order + +### `time_zone` + +**Required:** no +**Type:** string +**Default:** `$TZ` or local system time zone + +Time zone that chat timestamps should be displayed in. + +This option is interpreted as a POSIX TZ string. It is described here in +further detail: +<https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html> + +On a normal system, the string `"localtime"` as well as any value from +the "TZ identifier" column of the following wikipedia article should be +valid TZ strings: +<https://en.wikipedia.org/wiki/List_of_tz_database_time_zones> + +If the `TZ` environment variable exists, it overrides this option. If +neither exist, cove uses the system's local time zone. + +**Warning:** On Windows, cove can't get the local time zone and uses UTC +instead. However, you can still specify a path to a tz data file or a +custom time zone string. diff --git a/Cargo.lock b/Cargo.lock index 9061b62..4e31d05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,7 +280,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cove" -version = "0.7.1" +version = "0.8.0" dependencies = [ "anyhow", "async-trait", @@ -311,7 +311,7 @@ dependencies = [ [[package]] name = "cove-config" -version = "0.7.1" +version = "0.8.0" dependencies = [ "cove-input", "cove-macro", @@ -322,7 +322,7 @@ dependencies = [ [[package]] name = "cove-input" -version = "0.7.1" +version = "0.8.0" dependencies = [ "cove-macro", "crossterm", @@ -336,7 +336,7 @@ dependencies = [ [[package]] name = "cove-macro" -version = "0.7.1" +version = "0.8.0" dependencies = [ "case", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 19a2d92..f7b582f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "2" members = ["cove", "cove-*"] [workspace.package] -version = "0.7.1" +version = "0.8.0" edition = "2021" [workspace.dependencies] From ed7bd3ddb442ef85b94d3b5c749a267494dc08bf Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 4 Jan 2024 23:50:35 +0100 Subject: [PATCH 199/266] Remove key binding to open present page --- CHANGELOG.md | 3 +++ cove-config/src/keys.rs | 4 ---- cove/src/ui/euph/room.rs | 12 ------------ 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d9b50e..0392309 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Procedure when bumping the version number: ## Unreleased +### Removed +- Key binding to open present page + ## v0.8.0 - 2024-01-04 ### Added diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index 1f8a818..422798e 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -81,7 +81,6 @@ default_bindings! { pub fn nick => ["n"]; pub fn more_messages => ["m"]; pub fn account => ["A"]; - pub fn present => ["ctrl+p"]; } pub mod tree_cursor { @@ -286,9 +285,6 @@ pub struct RoomAction { /// Manage account. #[serde(default = "default::room_action::account")] pub account: KeyBinding, - /// Open room's plugh.de/present page. - #[serde(default = "default::room_action::present")] - pub present: KeyBinding, } #[derive(Debug, Default, Deserialize, Document)] diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index b226b75..d79ab3d 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -382,18 +382,6 @@ impl EuphRoom { _ => {} } - // Always applicable - if event.matches(&keys.room.action.present) { - let link = format!("https://plugh.de/present/{}/", self.name()); - if let Err(error) = open::that(&link) { - self.popups.push_front(RoomPopup::Error { - description: format!("Failed to open link: {link}"), - reason: format!("{error}"), - }); - } - return true; - } - false } From ab81c89854199306c00bcc5c1c30d497d34033c7 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 5 Jan 2024 14:25:09 +0100 Subject: [PATCH 200/266] Set window title The title includes the amount of unseen messages in the room or room list. The room list heading also now contains the amount of connected rooms as well as the amount of unseen messages in total (if any). --- CHANGELOG.md | 4 +++ Cargo.lock | 4 +-- Cargo.toml | 2 +- cove/src/ui/euph/room.rs | 12 ++++++- cove/src/ui/rooms.rs | 74 ++++++++++++++++++++++++++++------------ cove/src/vault/euph.rs | 16 +++++++++ 6 files changed, 86 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0392309..0ac199d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ Procedure when bumping the version number: ## Unreleased +### Added +- Support for setting window title +- More information to room list heading + ### Removed - Key binding to open present page diff --git a/Cargo.lock b/Cargo.lock index 4e31d05..f6f8d1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1477,8 +1477,8 @@ dependencies = [ [[package]] name = "toss" -version = "0.2.0" -source = "git+https://github.com/Garmelon/toss.git?tag=v0.2.0#2c7888fa413c9b12bec7d55a73051aa96d59386f" +version = "0.2.1" +source = "git+https://github.com/Garmelon/toss.git?tag=v0.2.1#b01ee297d5bdbb3b28cafe2b5b130c2767667974" dependencies = [ "async-trait", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index f7b582f..aa6a315 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ thiserror = "1.0.56" [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" -tag = "v0.2.0" +tag = "v0.2.1" [profile.dev.package."*"] opt-level = 3 diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index d79ab3d..8fc2fe5 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -319,7 +319,17 @@ impl EuphRoom { .then_plain(")"); } - Text::new(info).padding().with_horizontal(1).border() + let title = if unseen > 0 { + format!("&{} ({unseen})", self.name()) + } else { + format!("&{}", self.name()) + }; + + Text::new(info) + .padding() + .with_horizontal(1) + .border() + .title(title) } async fn handle_chat_input_event(&mut self, event: &mut InputEvent<'_>, keys: &Keys) -> bool { diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index 4275b48..f40bfc5 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -237,12 +237,16 @@ impl Rooms { } match &mut self.state { - State::ShowList => { - Self::rooms_widget(self.config, &mut self.list, self.order, &self.euph_rooms) - .await - .desync() - .boxed_async() - } + State::ShowList => Self::rooms_widget( + &self.vault, + self.config, + &mut self.list, + self.order, + &self.euph_rooms, + ) + .await + .desync() + .boxed_async(), State::ShowRoom(id) => { self.euph_rooms @@ -252,21 +256,29 @@ impl Rooms { .await } - State::Connect(connect) => { - Self::rooms_widget(self.config, &mut self.list, self.order, &self.euph_rooms) - .await - .below(connect.widget()) - .desync() - .boxed_async() - } + State::Connect(connect) => Self::rooms_widget( + &self.vault, + self.config, + &mut self.list, + self.order, + &self.euph_rooms, + ) + .await + .below(connect.widget()) + .desync() + .boxed_async(), - State::Delete(delete) => { - Self::rooms_widget(self.config, &mut self.list, self.order, &self.euph_rooms) - .await - .below(delete.widget()) - .desync() - .boxed_async() - } + State::Delete(delete) => Self::rooms_widget( + &self.vault, + self.config, + &mut self.list, + self.order, + &self.euph_rooms, + ) + .await + .below(delete.widget()) + .desync() + .boxed_async(), } } @@ -400,6 +412,7 @@ impl Rooms { } async fn rooms_widget<'a>( + vault: &Vault, config: &Config, list: &'a mut ListState<RoomIdentifier>, order: Order, @@ -419,8 +432,24 @@ impl Rooms { .with_horizontal(1) .border(); - let heading = Styled::new("Rooms", Style::new().bold()) - .then_plain(format!(" ({})", euph_rooms.len())); + let mut heading = Styled::new("Rooms", Style::new().bold()); + let mut title = "Rooms".to_string(); + + let total_rooms = euph_rooms.len(); + let connected_rooms = euph_rooms + .iter() + .filter(|r| r.1.room_state().is_some()) + .count(); + let total_unseen = logging_unwrap!(vault.euph().total_unseen_msgs_count().await); + if total_unseen > 0 { + heading = heading + .then_plain(format!(" ({connected_rooms}/{total_rooms}, ")) + .then(format!("{total_unseen}"), Style::new().bold().green()) + .then_plain(")"); + title.push_str(&format!(" ({total_unseen})")); + } else { + heading = heading.then_plain(format!(" ({connected_rooms}/{total_rooms})")) + } let mut list_builder = ListBuilder::new(); Self::render_rows(&mut list_builder, order, euph_rooms).await; @@ -435,6 +464,7 @@ impl Rooms { .segment() .with_growing(false), ) + .title(title) } async fn handle_showlist_input_event( diff --git a/cove/src/vault/euph.rs b/cove/src/vault/euph.rs index 8091613..f922345 100644 --- a/cove/src/vault/euph.rs +++ b/cove/src/vault/euph.rs @@ -115,6 +115,7 @@ euph_vault_actions! { SetCookies : set_cookies(domain: String, cookies: CookieJar) -> (); ClearCookies : clear_cookies(domain: Option<String>) -> (); GetRooms : rooms() -> Vec<RoomIdentifier>; + GetTotalUnseenMsgsCount : total_unseen_msgs_count() -> usize; } impl Action for GetCookies { @@ -212,6 +213,21 @@ impl Action for GetRooms { } } +impl Action for GetTotalUnseenMsgsCount { + type Output = usize; + type Error = rusqlite::Error; + + fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> { + conn.prepare( + " + SELECT COALESCE(SUM(amount), 0) + FROM euph_unseen_counts + ", + )? + .query_row([], |row| row.get(0)) + } +} + /////////////////// // EuphRoomVault // /////////////////// From ee7121b04e1c0fb9ba09d68aa1e4e670cced877f Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 5 Jan 2024 23:21:06 +0100 Subject: [PATCH 201/266] Implement live caesar cipher --- CHANGELOG.md | 1 + cove-config/src/keys.rs | 8 +++++++ cove/src/ui/chat.rs | 36 +++++++++++++++++++++++++++---- cove/src/ui/chat/tree.rs | 4 ++++ cove/src/ui/chat/tree/renderer.rs | 3 ++- cove/src/ui/chat/tree/scroll.rs | 1 + cove/src/ui/chat/tree/widgets.rs | 14 ++++++++++++ cove/src/util.rs | 19 ++++++++++++++++ 8 files changed, 81 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ac199d..51bbdc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Procedure when bumping the version number: ### Added - Support for setting window title - More information to room list heading +- Key bindings for live caesar cipher de- and encoding ### Removed - Key binding to open present page diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index 422798e..8b5adeb 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -104,6 +104,8 @@ default_bindings! { pub fn mark_older_seen => ["ctrl+s"]; pub fn info => ["i"]; pub fn links => ["I"]; + pub fn increase_caesar => ["c"]; + pub fn decrease_caesar => ["C"]; } } @@ -354,6 +356,12 @@ pub struct TreeAction { /// List links found in message. #[serde(default = "default::tree_action::links")] pub links: KeyBinding, + /// Increase caesar cipher rotation. + #[serde(default = "default::tree_action::increase_caesar")] + pub increase_caesar: KeyBinding, + /// Decrease caesar cipher rotation. + #[serde(default = "default::tree_action::decrease_caesar")] + pub decrease_caesar: KeyBinding, } #[derive(Debug, Default, Deserialize, Document)] diff --git a/cove/src/ui/chat.rs b/cove/src/ui/chat.rs index 6a9c9c2..c8a310c 100644 --- a/cove/src/ui/chat.rs +++ b/cove/src/ui/chat.rs @@ -11,6 +11,7 @@ use toss::widgets::{BoxedAsync, EditorState}; use toss::{Styled, WidgetExt}; use crate::store::{Msg, MsgStore}; +use crate::util; use self::cursor::Cursor; use self::tree::TreeViewState; @@ -33,6 +34,7 @@ pub struct ChatState<M: Msg, S: MsgStore<M>> { cursor: Cursor<M::Id>, editor: EditorState, + caesar: i8, mode: Mode, tree: TreeViewState<M, S>, @@ -43,6 +45,7 @@ impl<M: Msg, S: MsgStore<M> + Clone> ChatState<M, S> { Self { cursor: Cursor::Bottom, editor: EditorState::new(), + caesar: 0, mode: Mode::Tree, tree: TreeViewState::new(store.clone()), @@ -68,7 +71,13 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> { match self.mode { Mode::Tree => self .tree - .widget(&mut self.cursor, &mut self.editor, nick, focused) + .widget( + &mut self.cursor, + &mut self.editor, + nick, + focused, + self.caesar, + ) .boxed_async(), } } @@ -85,7 +94,7 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> { S: Send + Sync, S::Error: Send, { - match self.mode { + let reaction = match self.mode { Mode::Tree => { self.tree .handle_input_event( @@ -95,9 +104,28 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> { &mut self.editor, can_compose, ) - .await + .await? } - } + }; + + Ok(match reaction { + Reaction::Composed { parent, content } if self.caesar != 0 => { + let content = util::caesar(&content, self.caesar); + Reaction::Composed { parent, content } + } + + Reaction::NotHandled if event.matches(&keys.tree.action.increase_caesar) => { + self.caesar = (self.caesar + 1).rem_euclid(26); + Reaction::Handled + } + + Reaction::NotHandled if event.matches(&keys.tree.action.decrease_caesar) => { + self.caesar = (self.caesar - 1).rem_euclid(26); + Reaction::Handled + } + + reaction => reaction, + }) } pub fn cursor(&self) -> Option<&M::Id> { diff --git a/cove/src/ui/chat/tree.rs b/cove/src/ui/chat/tree.rs index 772363f..37972e5 100644 --- a/cove/src/ui/chat/tree.rs +++ b/cove/src/ui/chat/tree.rs @@ -386,6 +386,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> { editor: &'a mut EditorState, nick: String, focused: bool, + caesar: i8, ) -> TreeView<'a, M, S> { TreeView { state: self, @@ -393,6 +394,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> { editor, nick, focused, + caesar, } } } @@ -405,6 +407,7 @@ pub struct TreeView<'a, M: Msg, S: MsgStore<M>> { nick: String, focused: bool, + caesar: i8, } #[async_trait] @@ -432,6 +435,7 @@ where size, nick: self.nick.clone(), focused: self.focused, + caesar: self.caesar, last_cursor: self.state.last_cursor.clone(), last_cursor_top: self.state.last_cursor_top, }; diff --git a/cove/src/ui/chat/tree/renderer.rs b/cove/src/ui/chat/tree/renderer.rs index 945f77c..99f32b5 100644 --- a/cove/src/ui/chat/tree/renderer.rs +++ b/cove/src/ui/chat/tree/renderer.rs @@ -72,6 +72,7 @@ pub struct TreeContext<Id> { pub size: Size, pub nick: String, pub focused: bool, + pub caesar: i8, pub last_cursor: Cursor<Id>, pub last_cursor_top: i32, } @@ -190,7 +191,7 @@ where }; let highlighted = highlighted && self.context.focused; - let widget = widgets::msg(highlighted, indent, msg, folded_info); + let widget = widgets::msg(highlighted, indent, msg, self.context.caesar, folded_info); let widget = Self::predraw(widget, self.context.size, self.widthdb); Block::new(TreeBlockId::Msg(msg_id), widget, true) } diff --git a/cove/src/ui/chat/tree/scroll.rs b/cove/src/ui/chat/tree/scroll.rs index 482c7ca..822b0b5 100644 --- a/cove/src/ui/chat/tree/scroll.rs +++ b/cove/src/ui/chat/tree/scroll.rs @@ -20,6 +20,7 @@ where size: self.last_size, nick: self.last_nick.clone(), focused: true, + caesar: 0, last_cursor: self.last_cursor.clone(), last_cursor_top: self.last_cursor_top, } diff --git a/cove/src/ui/chat/tree/widgets.rs b/cove/src/ui/chat/tree/widgets.rs index 2f1a1ff..9eb6690 100644 --- a/cove/src/ui/chat/tree/widgets.rs +++ b/cove/src/ui/chat/tree/widgets.rs @@ -7,6 +7,7 @@ use toss::{Style, Styled, WidgetExt}; use crate::store::Msg; use crate::ui::chat::widgets::{Indent, Seen, Time}; use crate::ui::ChatMsg; +use crate::util; pub const PLACEHOLDER: &str = "[...]"; @@ -30,6 +31,10 @@ fn style_indent(highlighted: bool) -> Style { } } +fn style_caesar() -> Style { + Style::new().green() +} + fn style_info() -> Style { Style::new().italic().dark_grey() } @@ -46,10 +51,19 @@ pub fn msg<M: Msg + ChatMsg>( highlighted: bool, indent: usize, msg: &M, + caesar: i8, folded_info: Option<usize>, ) -> Boxed<'static, Infallible> { let (nick, mut content) = msg.styled(); + if caesar != 0 { + // Apply caesar in inverse because we're decoding + let rotated = util::caesar(content.text(), -caesar); + content = content + .then_plain("\n") + .then(format!("{rotated} [rot{caesar}]"), style_caesar()); + } + if let Some(amount) = folded_info { content = content .then_plain("\n") diff --git a/cove/src/util.rs b/cove/src/util.rs index 6bcbf3e..4844d68 100644 --- a/cove/src/util.rs +++ b/cove/src/util.rs @@ -48,3 +48,22 @@ pub fn convert_to_time_zone(tz: &TimeZone, time: OffsetDateTime) -> Option<Offse Some(time.to_offset(utc_offset)) } + +pub fn caesar(text: &str, by: i8) -> String { + let by = by.rem_euclid(26) as u8; + text.chars() + .map(|c| { + if c.is_ascii_lowercase() { + let c = c as u8 - b'a'; + let c = (c + by) % 26; + (c + b'a') as char + } else if c.is_ascii_uppercase() { + let c = c as u8 - b'A'; + let c = (c + by) % 26; + (c + b'A') as char + } else { + c + } + }) + .collect() +} From 37e4c6b845b9c8eb7603dc7c642621f29d987a0c Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 11 Jan 2024 11:22:34 +0100 Subject: [PATCH 202/266] Update dependencies --- Cargo.lock | 48 +++++++++++++++++++++---------------------- Cargo.toml | 2 +- cove-macro/Cargo.toml | 4 ++-- cove/Cargo.toml | 2 +- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6f8d1c..fd1c572 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,9 +132,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.5" +version = "0.21.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9" [[package]] name = "bitflags" @@ -202,9 +202,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.4.12" +version = "4.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" +checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2" dependencies = [ "clap_builder", "clap_derive", @@ -212,9 +212,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.12" +version = "4.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" +checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370" dependencies = [ "anstream", "anstyle", @@ -346,9 +346,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -564,9 +564,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -683,9 +683,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libredox" @@ -900,9 +900,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.75" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -1045,9 +1045,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6b63262c9fcac8659abfaa96cac103d28166d3ff3eaf8f412e19f3ae9e5a48" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" dependencies = [ "log", "ring", @@ -1143,9 +1143,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.194" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] @@ -1162,9 +1162,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.194" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", @@ -1287,9 +1287,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.47" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1726efe18f42ae774cc644f330953a5e7b3c3003d3edcecf18850fe9d4dd9afb" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -1786,9 +1786,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.32" +version = "0.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8434aeec7b290e8da5c3f0d628cb0eac6cabcb31d14bb74f779a08109a5914d6" +checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index aa6a315..8d469eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" [workspace.dependencies] crossterm = "0.27.0" parking_lot = "0.12.1" -serde = { version = "1.0.194", features = ["derive"] } +serde = { version = "1.0.195", features = ["derive"] } serde_either = "0.2.1" thiserror = "1.0.56" diff --git a/cove-macro/Cargo.toml b/cove-macro/Cargo.toml index f0ab3fe..dcfc17b 100644 --- a/cove-macro/Cargo.toml +++ b/cove-macro/Cargo.toml @@ -5,9 +5,9 @@ edition = { workspace = true } [dependencies] case = "1.0.0" -proc-macro2 = "1.0.75" +proc-macro2 = "1.0.76" quote = "1.0.35" -syn = "2.0.47" +syn = "2.0.48" [lib] proc-macro = true diff --git a/cove/Cargo.toml b/cove/Cargo.toml index 15d5233..d1e41aa 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -14,7 +14,7 @@ toss = { workspace = true } anyhow = "1.0.79" async-trait = "0.1.77" -clap = { version = "4.4.12", features = ["derive", "deprecated"] } +clap = { version = "4.4.14", features = ["derive", "deprecated"] } cookie = "0.18.0" directories = "5.0.1" linkify = "0.10.0" From 133681fc621540a95443d5b09af704b57e24c857 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 11 Jan 2024 11:25:45 +0100 Subject: [PATCH 203/266] Bump version to v0.8.1 --- CHANGELOG.md | 2 ++ CONFIG.md | 24 ++++++++++++++++-------- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51bbdc8..2ea84d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Procedure when bumping the version number: ## Unreleased +## v0.8.1 - 2024-01-11 + ### Added - Support for setting window title - More information to room list heading diff --git a/CONFIG.md b/CONFIG.md index fca0589..af50003 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -321,14 +321,6 @@ Download more messages. Change nick. -### `keys.room.action.present` - -**Required:** yes -**Type:** key binding -**Default:** `"ctrl+p"` - -Open room's plugh.de/present page. - ### `keys.rooms.action.change_sort_order` **Required:** yes @@ -457,6 +449,14 @@ Scroll up half a screen. Scroll up one line. +### `keys.tree.action.decrease_caesar` + +**Required:** yes +**Type:** key binding +**Default:** `"C"` + +Decrease caesar cipher rotation. + ### `keys.tree.action.fold_tree` **Required:** yes @@ -465,6 +465,14 @@ Scroll up one line. Fold current message's subtree. +### `keys.tree.action.increase_caesar` + +**Required:** yes +**Type:** key binding +**Default:** `"c"` + +Increase caesar cipher rotation. + ### `keys.tree.action.inspect` **Required:** yes diff --git a/Cargo.lock b/Cargo.lock index fd1c572..9be7313 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,7 +280,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cove" -version = "0.8.0" +version = "0.8.1" dependencies = [ "anyhow", "async-trait", @@ -311,7 +311,7 @@ dependencies = [ [[package]] name = "cove-config" -version = "0.8.0" +version = "0.8.1" dependencies = [ "cove-input", "cove-macro", @@ -322,7 +322,7 @@ dependencies = [ [[package]] name = "cove-input" -version = "0.8.0" +version = "0.8.1" dependencies = [ "cove-macro", "crossterm", @@ -336,7 +336,7 @@ dependencies = [ [[package]] name = "cove-macro" -version = "0.8.0" +version = "0.8.1" dependencies = [ "case", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 8d469eb..9117885 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "2" members = ["cove", "cove-*"] [workspace.package] -version = "0.8.0" +version = "0.8.1" edition = "2021" [workspace.dependencies] From a5b33440c5f9c4724954d17294957aaa2c9eb82c Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 12 Jan 2024 22:21:42 +0100 Subject: [PATCH 204/266] Fix spelling of "indexes" --- cove/src/vault/migrate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cove/src/vault/migrate.rs b/cove/src/vault/migrate.rs index ed26db6..cc85c2c 100644 --- a/cove/src/vault/migrate.rs +++ b/cove/src/vault/migrate.rs @@ -194,7 +194,7 @@ fn m3(tx: &mut Transaction<'_>, nr: usize, total: usize) -> rusqlite::Result<()> ", )?; - eprintln!(" Recreating indices..."); + eprintln!(" Recreating indexes..."); tx.execute_batch( " CREATE INDEX euph_idx_msgs_domain_room_id_parent From 998a2f2ffdf248bd0d0b378330cc2e25a568a60c Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 14 Jan 2024 12:41:48 +0100 Subject: [PATCH 205/266] Fix crash when window too small with msg editor visible --- CHANGELOG.md | 3 +++ Cargo.lock | 4 ++-- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ea84d9..8f5a49a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,9 @@ Procedure when bumping the version number: ### Removed - Key binding to open present page +### Fixed +- Crash when window is too small while empty message editor is visible + ## v0.8.0 - 2024-01-04 ### Added diff --git a/Cargo.lock b/Cargo.lock index 9be7313..62cfe27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1477,8 +1477,8 @@ dependencies = [ [[package]] name = "toss" -version = "0.2.1" -source = "git+https://github.com/Garmelon/toss.git?tag=v0.2.1#b01ee297d5bdbb3b28cafe2b5b130c2767667974" +version = "0.2.2" +source = "git+https://github.com/Garmelon/toss.git?tag=v0.2.2#761e8baeba09b923e2a409ea7df7bb363fc77fd5" dependencies = [ "async-trait", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 9117885..da87015 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ thiserror = "1.0.56" [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" -tag = "v0.2.1" +tag = "v0.2.2" [profile.dev.package."*"] opt-level = 3 From 50be653244492fcd1d3c6f486446b180739e9944 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 27 Jan 2024 13:21:59 +0100 Subject: [PATCH 206/266] Fix incorrect cli option reference --- CHANGELOG.md | 7 ++++--- cove-config/src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f5a49a..a792d9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ Procedure when bumping the version number: ## Unreleased +### Fixed +- Crash when window is too small while empty message editor is visible +- Mistakes in output and docs + ## v0.8.1 - 2024-01-11 ### Added @@ -25,9 +29,6 @@ Procedure when bumping the version number: ### Removed - Key binding to open present page -### Fixed -- Crash when window is too small while empty message editor is visible - ## v0.8.0 - 2024-01-04 ### Added diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index fc1e6af..76f56e1 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -58,7 +58,7 @@ pub struct Config { /// might also flash when encountering new characters (or, more accurately, /// graphemes). /// - /// See also the `--measure-graphemes` command line option. + /// See also the `--measure-widths` command line option. #[serde(default)] pub measure_widths: bool, From 131b5818802328f23aca2050f43e16949f4bcedf Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 8 Mar 2024 22:19:21 +0100 Subject: [PATCH 207/266] Change json-stream export format to json-lines --- CHANGELOG.md | 4 ++++ cove/src/export.rs | 12 +++++++----- cove/src/export/json.rs | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a792d9e..614d940 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ Procedure when bumping the version number: ## Unreleased +### Changed +- Renamed `json-stream` export format to `json-lines` (see <https://jsonlines.org/>) +- Changed `json-lines` file extension from `.json` to `.jsonl` + ### Fixed - Crash when window is too small while empty message editor is visible - Mistakes in output and docs diff --git a/cove/src/export.rs b/cove/src/export.rs index 9d9c60b..0ad9414 100644 --- a/cove/src/export.rs +++ b/cove/src/export.rs @@ -14,8 +14,9 @@ pub enum Format { Text, /// Array of message objects in the same format as the euphoria API uses. Json, - /// Message objects in the same format as the euphoria API uses, one per line. - JsonStream, + /// Message objects in the same format as the euphoria API uses, one per + /// line (https://jsonlines.org/). + JsonLines, } impl Format { @@ -23,14 +24,15 @@ impl Format { match self { Self::Text => "text", Self::Json => "json", - Self::JsonStream => "json stream", + Self::JsonLines => "json lines", } } fn extension(&self) -> &'static str { match self { Self::Text => "txt", - Self::Json | Self::JsonStream => "json", + Self::Json => "json", + Self::JsonLines => "jsonl", } } } @@ -78,7 +80,7 @@ async fn export_room<W: Write>( match format { Format::Text => text::export(vault, out).await?, Format::Json => json::export(vault, out).await?, - Format::JsonStream => json::export_stream(vault, out).await?, + Format::JsonLines => json::export_lines(vault, out).await?, } Ok(()) } diff --git a/cove/src/export/json.rs b/cove/src/export/json.rs index e72a0b8..9c16e46 100644 --- a/cove/src/export/json.rs +++ b/cove/src/export/json.rs @@ -37,7 +37,7 @@ pub async fn export<W: Write>(vault: &EuphRoomVault, file: &mut W) -> anyhow::Re Ok(()) } -pub async fn export_stream<W: Write>(vault: &EuphRoomVault, file: &mut W) -> anyhow::Result<()> { +pub async fn export_lines<W: Write>(vault: &EuphRoomVault, file: &mut W) -> anyhow::Result<()> { let mut total = 0; let mut last_msg_id = None; loop { From db529688e97cb3bc1177f28ccdf628474cb41d9c Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 25 Apr 2024 20:36:14 +0200 Subject: [PATCH 208/266] Use cargo workspace lints --- Cargo.toml | 9 +++++++++ cove-config/Cargo.toml | 3 +++ cove-config/src/lib.rs | 11 ----------- cove-input/Cargo.toml | 3 +++ cove-macro/Cargo.toml | 3 +++ cove-macro/src/lib.rs | 11 ----------- cove/Cargo.toml | 3 +++ cove/src/main.rs | 11 ----------- 8 files changed, 21 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index da87015..a6d5cec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,5 +19,14 @@ thiserror = "1.0.56" git = "https://github.com/Garmelon/toss.git" tag = "v0.2.2" +[workspace.lints] +rust.unsafe_code = "forbid" +rust.future_incompatible = "warn" +rust.rust_2018_idioms = "warn" +rust.unused = "warn" +rust.noop_method_call = "warn" +rust.single_use_lifetimes = "warn" +clippy.use_self = "warn" + [profile.dev.package."*"] opt-level = 3 diff --git a/cove-config/Cargo.toml b/cove-config/Cargo.toml index c05257d..296899f 100644 --- a/cove-config/Cargo.toml +++ b/cove-config/Cargo.toml @@ -11,3 +11,6 @@ serde = { workspace = true } thiserror = { workspace = true } toml = "0.8.8" + +[lints] +workspace = true diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 76f56e1..086e372 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -1,14 +1,3 @@ -#![forbid(unsafe_code)] -// Rustc lint groups -#![warn(future_incompatible)] -#![warn(rust_2018_idioms)] -#![warn(unused)] -// Rustc lints -#![warn(noop_method_call)] -#![warn(single_use_lifetimes)] -// Clippy lints -#![warn(clippy::use_self)] - pub mod doc; mod euph; mod keys; diff --git a/cove-input/Cargo.toml b/cove-input/Cargo.toml index dd6d23d..428060f 100644 --- a/cove-input/Cargo.toml +++ b/cove-input/Cargo.toml @@ -14,3 +14,6 @@ thiserror = { workspace = true } toss = { workspace = true } edit = "0.1.5" + +[lints] +workspace = true diff --git a/cove-macro/Cargo.toml b/cove-macro/Cargo.toml index dcfc17b..bd11d13 100644 --- a/cove-macro/Cargo.toml +++ b/cove-macro/Cargo.toml @@ -11,3 +11,6 @@ syn = "2.0.48" [lib] proc-macro = true + +[lints] +workspace = true diff --git a/cove-macro/src/lib.rs b/cove-macro/src/lib.rs index 82ef61a..fd09f5f 100644 --- a/cove-macro/src/lib.rs +++ b/cove-macro/src/lib.rs @@ -1,14 +1,3 @@ -#![forbid(unsafe_code)] -// Rustc lint groups -#![warn(future_incompatible)] -#![warn(rust_2018_idioms)] -#![warn(unused)] -// Rustc lints -#![warn(noop_method_call)] -#![warn(single_use_lifetimes)] -// Clippy lints -#![warn(clippy::use_self)] - use syn::{parse_macro_input, DeriveInput}; mod document; diff --git a/cove/Cargo.toml b/cove/Cargo.toml index d1e41aa..7783bbb 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -45,3 +45,6 @@ features = ["bot"] git = "https://github.com/Garmelon/vault.git" tag = "v0.3.0" features = ["tokio"] + +[lints] +workspace = true diff --git a/cove/src/main.rs b/cove/src/main.rs index 5bce993..8f51b40 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -1,14 +1,3 @@ -#![forbid(unsafe_code)] -// Rustc lint groups -#![warn(future_incompatible)] -#![warn(rust_2018_idioms)] -#![warn(unused)] -// Rustc lints -#![warn(noop_method_call)] -#![warn(single_use_lifetimes)] -// Clippy lints -#![warn(clippy::use_self)] - // TODO Enable warn(unreachable_pub)? // TODO Remove unnecessary Debug impls and compare compile times // TODO Time zones other than UTC From 3a3d42bcf3dcf985b17649232eb40d878050922d Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 25 Apr 2024 20:36:34 +0200 Subject: [PATCH 209/266] Fix clippy warning --- cove-input/src/keys.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cove-input/src/keys.rs b/cove-input/src/keys.rs index 4ede713..337a5f3 100644 --- a/cove-input/src/keys.rs +++ b/cove-input/src/keys.rs @@ -151,7 +151,7 @@ impl FromStr for KeyPress { let mut parts = s.split('+'); let code = parts.next_back().ok_or(ParseKeysError::NoKeyCode)?; - let mut keys = KeyPress::parse_key_code(code)?; + let mut keys = Self::parse_key_code(code)?; let shift_allowed = !conflicts_with_shift(keys.code); for modifier in parts { keys.parse_modifier(modifier, shift_allowed)?; From c2cfa6e527b65458094bed1a4da0e8fb7908823b Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 25 Apr 2024 20:37:42 +0200 Subject: [PATCH 210/266] Remove todos --- Cargo.toml | 2 -- cove/src/main.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a6d5cec..f0bb211 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,3 @@ -# TODO Configure lints in here - [workspace] resolver = "2" members = ["cove", "cove-*"] diff --git a/cove/src/main.rs b/cove/src/main.rs index 8f51b40..fc14b56 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -1,6 +1,4 @@ -// TODO Enable warn(unreachable_pub)? // TODO Remove unnecessary Debug impls and compare compile times -// TODO Time zones other than UTC // TODO Invoke external notification command? mod euph; From d3666674b254c4926d567bf3dc44690aa36d4859 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 25 Apr 2024 20:32:41 +0200 Subject: [PATCH 211/266] Update dependencies --- CHANGELOG.md | 1 + Cargo.lock | 399 +++++++++++++++++++++-------------------- Cargo.toml | 6 +- cove-config/Cargo.toml | 2 +- cove-macro/Cargo.toml | 6 +- cove/Cargo.toml | 24 +-- flake.lock | 12 +- 7 files changed, 231 insertions(+), 219 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 614d940..595d8fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Procedure when bumping the version number: ### Fixed - Crash when window is too small while empty message editor is visible - Mistakes in output and docs +- Cove not cleaning up terminal state properly ## v0.8.1 - 2024-01-11 diff --git a/Cargo.lock b/Cargo.lock index 62cfe27..08bbc62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", @@ -31,24 +31,24 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "anstream" -version = "0.6.5" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -60,9 +60,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" @@ -94,15 +94,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", @@ -111,15 +111,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -132,9 +132,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.6" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" [[package]] name = "bitflags" @@ -144,9 +144,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "block-buffer" @@ -165,9 +165,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "case" @@ -187,12 +187,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" [[package]] name = "cfg-if" @@ -202,9 +199,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.4.14" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -212,9 +209,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.14" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -224,9 +221,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck", "proc-macro2", @@ -236,9 +233,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" @@ -248,15 +245,15 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "const_fn" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" +checksum = "373e9fafaa20882876db20562275ff58d50e0caa2590077fe7ce7bef90211d0d" [[package]] name = "cookie" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd91cf61412820176e137621345ee43b3f4423e589e7ae4e50d601d93e35ef8" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "time", "version_check", @@ -359,7 +356,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "crossterm_winapi", "libc", "mio", @@ -447,9 +444,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "equivalent" @@ -501,9 +498,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "fnv" @@ -564,9 +561,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -591,24 +588,24 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.8.4" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +checksum = "692eaaf7f7607518dd3cef090f1474b61edc5301d8012f09579920df68b725ee" dependencies = [ "hashbrown", ] [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "home" @@ -621,9 +618,9 @@ dependencies = [ [[package]] name = "http" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -648,9 +645,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", @@ -677,32 +674,31 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "libc", - "redox_syscall", ] [[package]] name = "libsqlite3-sys" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" dependencies = [ "cc", "pkg-config", @@ -720,9 +716,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -736,30 +732,30 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", @@ -768,10 +764,16 @@ dependencies = [ ] [[package]] -name = "num-traits" -version = "0.2.17" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -803,9 +805,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "open" -version = "5.0.1" +version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349" +checksum = "449f0ff855d85ddbf1edd5b646d65249ead3f5e422aaa86b7d2d0b049b103e32" dependencies = [ "is-wsl", "libc", @@ -870,9 +872,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -882,9 +884,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "powerfmt" @@ -900,18 +902,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -957,9 +959,9 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", @@ -968,9 +970,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -980,9 +982,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -991,31 +993,32 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "rusqlite" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d" +checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -1032,11 +1035,11 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -1045,9 +1048,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.2" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", "ring", @@ -1072,9 +1075,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.0.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ "base64", "rustls-pki-types", @@ -1082,15 +1085,15 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e9d979b3ce68192e42760c7810125eb6cf2ea10efae545a156063e61f314e2a" +checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" [[package]] name = "rustls-webpki" -version = "0.102.1" +version = "0.102.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4ca26037c909dedb327b48c3327d0ba91d3dd3c4e05dad328f210ffb68e95b" +checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" dependencies = [ "ring", "rustls-pki-types", @@ -1099,9 +1102,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "schannel" @@ -1120,9 +1123,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -1133,9 +1136,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -1143,9 +1146,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.195" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] @@ -1162,9 +1165,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.195" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", @@ -1183,9 +1186,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.111" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -1235,9 +1238,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -1253,18 +1256,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1275,9 +1278,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" @@ -1287,9 +1290,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.48" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -1298,31 +1301,30 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", @@ -1331,12 +1333,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.31" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -1351,10 +1354,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -1375,9 +1379,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -1416,9 +1420,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -1443,9 +1447,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.8" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", @@ -1464,9 +1468,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.22.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" dependencies = [ "indexmap", "serde", @@ -1477,8 +1481,8 @@ dependencies = [ [[package]] name = "toss" -version = "0.2.2" -source = "git+https://github.com/Garmelon/toss.git?tag=v0.2.2#761e8baeba09b923e2a409ea7df7bb363fc77fd5" +version = "0.2.3" +source = "git+https://github.com/Garmelon/toss.git?tag=v0.2.3#b1d7221bae9e1bb57d8e5b49c315dc3ca56e947a" dependencies = [ "async-trait", "crossterm", @@ -1525,9 +1529,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -1543,18 +1547,18 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" @@ -1593,8 +1597,8 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "vault" -version = "0.3.0" -source = "git+https://github.com/Garmelon/vault.git?tag=v0.3.0#6640f601f3b4eef4ed7201e9fc197cbac3228dad" +version = "0.4.0" +source = "git+https://github.com/Garmelon/vault.git?tag=v0.4.0#a53254d2e787d15fd2d00584fddf9b84e79572ee" dependencies = [ "rusqlite", "tokio", @@ -1667,7 +1671,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.5", ] [[package]] @@ -1687,17 +1691,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -1708,9 +1713,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -1720,9 +1725,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -1732,9 +1737,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -1744,9 +1755,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -1756,9 +1767,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -1768,9 +1779,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -1780,15 +1791,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.5.34" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" +checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index f0bb211..c3b37a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,13 @@ edition = "2021" [workspace.dependencies] crossterm = "0.27.0" parking_lot = "0.12.1" -serde = { version = "1.0.195", features = ["derive"] } +serde = { version = "1.0.198", features = ["derive"] } serde_either = "0.2.1" -thiserror = "1.0.56" +thiserror = "1.0.59" [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" -tag = "v0.2.2" +tag = "v0.2.3" [workspace.lints] rust.unsafe_code = "forbid" diff --git a/cove-config/Cargo.toml b/cove-config/Cargo.toml index 296899f..b9c7300 100644 --- a/cove-config/Cargo.toml +++ b/cove-config/Cargo.toml @@ -10,7 +10,7 @@ cove-macro = { path = "../cove-macro" } serde = { workspace = true } thiserror = { workspace = true } -toml = "0.8.8" +toml = "0.8.12" [lints] workspace = true diff --git a/cove-macro/Cargo.toml b/cove-macro/Cargo.toml index bd11d13..acd55fd 100644 --- a/cove-macro/Cargo.toml +++ b/cove-macro/Cargo.toml @@ -5,9 +5,9 @@ edition = { workspace = true } [dependencies] case = "1.0.0" -proc-macro2 = "1.0.76" -quote = "1.0.35" -syn = "2.0.48" +proc-macro2 = "1.0.81" +quote = "1.0.36" +syn = "2.0.60" [lib] proc-macro = true diff --git a/cove/Cargo.toml b/cove/Cargo.toml index 7783bbb..7ea2b12 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -12,24 +12,24 @@ parking_lot = { workspace = true } thiserror = { workspace = true } toss = { workspace = true } -anyhow = "1.0.79" -async-trait = "0.1.77" -clap = { version = "4.4.14", features = ["derive", "deprecated"] } -cookie = "0.18.0" +anyhow = "1.0.82" +async-trait = "0.1.80" +clap = { version = "4.5.4", features = ["derive", "deprecated"] } +cookie = "0.18.1" directories = "5.0.1" linkify = "0.10.0" -log = { version = "0.4.20", features = ["std"] } +log = { version = "0.4.21", features = ["std"] } once_cell = "1.19.0" -open = "5.0.1" -rusqlite = { version = "0.30.0", features = ["bundled", "time"] } -serde_json = "1.0.111" -tokio = { version = "1.35.1", features = ["full"] } +open = "5.1.2" +rusqlite = { version = "0.31.0", features = ["bundled", "time"] } +serde_json = "1.0.116" +tokio = { version = "1.37.0", features = ["full"] } tz-rs = "0.6.14" -unicode-segmentation = "1.10.1" +unicode-segmentation = "1.11.0" unicode-width = "0.1.11" [dependencies.time] -version = "0.3.31" +version = "0.3.36" features = ["macros", "formatting", "parsing", "serde"] [dependencies.tokio-tungstenite] @@ -43,7 +43,7 @@ features = ["bot"] [dependencies.vault] git = "https://github.com/Garmelon/vault.git" -tag = "v0.3.0" +tag = "v0.4.0" features = ["tokio"] [lints] diff --git a/flake.lock b/flake.lock index 4a3030f..1e52d47 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1698420672, - "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", + "lastModified": 1713520724, + "narHash": "sha256-CO8MmVDmqZX2FovL75pu5BvwhW+Vugc7Q6ze7Hj8heI=", "owner": "nix-community", "repo": "naersk", - "rev": "aeb58d5e8faead8980a807c840232697982d47b9", + "rev": "c5037590290c6c7dae2e42e7da1e247e54ed2d49", "type": "github" }, "original": { @@ -22,11 +22,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1704371841, - "narHash": "sha256-ScUTxDRvgEK6W0hJqzodk4VZM1pqVJO3o/Ru99Oc7mI=", + "lastModified": 1714068967, + "narHash": "sha256-jfQUewdwBVs0HHLH10qxyn0+J53e1aQoPSkuBnYf15s=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "526411af967efacb9f1efefe9c8664bede47b8b8", + "rev": "10b682b6e5ed139ee2bef863ada3043f2d79c1cc", "type": "github" }, "original": { From db734c57406d5b42ca07a020e385fc349551932d Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 25 Apr 2024 20:45:00 +0200 Subject: [PATCH 212/266] Bump version to 0.8.2 --- CHANGELOG.md | 2 ++ CONFIG.md | 2 +- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 595d8fb..d9f1927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Procedure when bumping the version number: ## Unreleased +## v0.8.2 - 2024-04-25 + ### Changed - Renamed `json-stream` export format to `json-lines` (see <https://jsonlines.org/>) - Changed `json-lines` file extension from `.json` to `.jsonl` diff --git a/CONFIG.md b/CONFIG.md index af50003..66625d0 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -614,7 +614,7 @@ Enabling this makes rendering a bit slower but more accurate. The screen might also flash when encountering new characters (or, more accurately, graphemes). -See also the `--measure-graphemes` command line option. +See also the `--measure-widths` command line option. ### `offline` diff --git a/Cargo.lock b/Cargo.lock index 08bbc62..441e80e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,7 +277,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cove" -version = "0.8.1" +version = "0.8.2" dependencies = [ "anyhow", "async-trait", @@ -308,7 +308,7 @@ dependencies = [ [[package]] name = "cove-config" -version = "0.8.1" +version = "0.8.2" dependencies = [ "cove-input", "cove-macro", @@ -319,7 +319,7 @@ dependencies = [ [[package]] name = "cove-input" -version = "0.8.1" +version = "0.8.2" dependencies = [ "cove-macro", "crossterm", @@ -333,7 +333,7 @@ dependencies = [ [[package]] name = "cove-macro" -version = "0.8.1" +version = "0.8.2" dependencies = [ "case", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index c3b37a1..178b652 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["cove", "cove-*"] [workspace.package] -version = "0.8.1" +version = "0.8.2" edition = "2021" [workspace.dependencies] From 19242a658ecdd45438b0852cff53c8fb9ec6e379 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Mon, 20 May 2024 19:12:41 +0200 Subject: [PATCH 213/266] Update dependencies --- Cargo.lock | 230 ++++++++++++++++++++--------------------- Cargo.toml | 6 +- cove-config/Cargo.toml | 2 +- cove-macro/Cargo.toml | 4 +- cove/Cargo.toml | 10 +- 5 files changed, 123 insertions(+), 129 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 441e80e..40f6699 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,55 +38,50 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -94,9 +89,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "async-trait" @@ -111,9 +106,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" @@ -132,15 +127,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" @@ -187,9 +176,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" [[package]] name = "cfg-if" @@ -239,9 +228,9 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "const_fn" @@ -356,7 +345,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.5.0", + "bitflags", "crossterm_winapi", "libc", "mio", @@ -387,9 +376,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "deranged" @@ -444,9 +433,9 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "equivalent" @@ -456,9 +445,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -466,8 +455,8 @@ dependencies = [ [[package]] name = "euphoxide" -version = "0.5.0" -source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.5.0#276ff685127c4c392a2ab001f80f7a053e58746b" +version = "0.5.1" +source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.5.1#0256329f65f3ed853092cc210ae2a4a8c526a4bf" dependencies = [ "async-trait", "caseless", @@ -498,9 +487,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fnv" @@ -561,9 +550,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -578,19 +567,18 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", - "allocator-api2", ] [[package]] name = "hashlink" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692eaaf7f7607518dd3cef090f1474b61edc5301d8012f09579920df68b725ee" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ "hashbrown", ] @@ -672,6 +660,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itoa" version = "1.0.11" @@ -680,9 +674,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libredox" @@ -690,7 +684,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.5.0", + "bitflags", "libc", ] @@ -716,15 +710,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -744,9 +738,9 @@ checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] @@ -771,9 +765,9 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -805,9 +799,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "open" -version = "5.1.2" +version = "5.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "449f0ff855d85ddbf1edd5b646d65249ead3f5e422aaa86b7d2d0b049b103e32" +checksum = "2eb49fbd5616580e9974662cb96a3463da4476e649a7e4b258df0de065db0657" dependencies = [ "is-wsl", "libc", @@ -837,9 +831,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -847,15 +841,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -902,9 +896,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" dependencies = [ "unicode-ident", ] @@ -950,11 +944,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] @@ -1018,7 +1012,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" dependencies = [ - "bitflags 2.5.0", + "bitflags", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -1029,9 +1023,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" @@ -1039,7 +1033,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1085,15 +1079,15 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" -version = "0.102.3" +version = "0.102.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" dependencies = [ "ring", "rustls-pki-types", @@ -1102,9 +1096,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" @@ -1123,11 +1117,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -1136,9 +1130,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -1146,9 +1140,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.198" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] @@ -1165,9 +1159,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", @@ -1186,9 +1180,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -1197,9 +1191,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -1262,9 +1256,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1290,9 +1284,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.60" +version = "2.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" dependencies = [ "proc-macro2", "quote", @@ -1313,18 +1307,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", @@ -1447,9 +1441,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" dependencies = [ "serde", "serde_spanned", @@ -1459,18 +1453,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.12" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" dependencies = [ "indexmap", "serde", @@ -1562,9 +1556,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "untrusted" @@ -1797,27 +1791,27 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" +checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" dependencies = [ "memchr", ] [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 178b652..f9538c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,10 @@ edition = "2021" [workspace.dependencies] crossterm = "0.27.0" -parking_lot = "0.12.1" -serde = { version = "1.0.198", features = ["derive"] } +parking_lot = "0.12.2" +serde = { version = "1.0.202", features = ["derive"] } serde_either = "0.2.1" -thiserror = "1.0.59" +thiserror = "1.0.61" [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" diff --git a/cove-config/Cargo.toml b/cove-config/Cargo.toml index b9c7300..f860469 100644 --- a/cove-config/Cargo.toml +++ b/cove-config/Cargo.toml @@ -10,7 +10,7 @@ cove-macro = { path = "../cove-macro" } serde = { workspace = true } thiserror = { workspace = true } -toml = "0.8.12" +toml = "0.8.13" [lints] workspace = true diff --git a/cove-macro/Cargo.toml b/cove-macro/Cargo.toml index acd55fd..e550ae5 100644 --- a/cove-macro/Cargo.toml +++ b/cove-macro/Cargo.toml @@ -5,9 +5,9 @@ edition = { workspace = true } [dependencies] case = "1.0.0" -proc-macro2 = "1.0.81" +proc-macro2 = "1.0.83" quote = "1.0.36" -syn = "2.0.60" +syn = "2.0.65" [lib] proc-macro = true diff --git a/cove/Cargo.toml b/cove/Cargo.toml index 7ea2b12..ed44f0b 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -12,7 +12,7 @@ parking_lot = { workspace = true } thiserror = { workspace = true } toss = { workspace = true } -anyhow = "1.0.82" +anyhow = "1.0.86" async-trait = "0.1.80" clap = { version = "4.5.4", features = ["derive", "deprecated"] } cookie = "0.18.1" @@ -20,13 +20,13 @@ directories = "5.0.1" linkify = "0.10.0" log = { version = "0.4.21", features = ["std"] } once_cell = "1.19.0" -open = "5.1.2" +open = "5.1.3" rusqlite = { version = "0.31.0", features = ["bundled", "time"] } -serde_json = "1.0.116" +serde_json = "1.0.117" tokio = { version = "1.37.0", features = ["full"] } tz-rs = "0.6.14" unicode-segmentation = "1.11.0" -unicode-width = "0.1.11" +unicode-width = "0.1.12" [dependencies.time] version = "0.3.36" @@ -38,7 +38,7 @@ features = ["rustls-tls-native-roots"] [dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -tag = "v0.5.0" +tag = "v0.5.1" features = ["bot"] [dependencies.vault] From 7aba041c9f90dda045c50ac36276d2843002f44a Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Mon, 20 May 2024 19:18:13 +0200 Subject: [PATCH 214/266] Bump version to 0.8.3 --- CHANGELOG.md | 5 +++++ Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9f1927..ec5600e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,11 @@ Procedure when bumping the version number: ## Unreleased +## v0.8.3 - 2024-05-20 + +### Changed +- Updated list of emoji names + ## v0.8.2 - 2024-04-25 ### Changed diff --git a/Cargo.lock b/Cargo.lock index 40f6699..da7fbb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -266,7 +266,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cove" -version = "0.8.2" +version = "0.8.3" dependencies = [ "anyhow", "async-trait", @@ -297,7 +297,7 @@ dependencies = [ [[package]] name = "cove-config" -version = "0.8.2" +version = "0.8.3" dependencies = [ "cove-input", "cove-macro", @@ -308,7 +308,7 @@ dependencies = [ [[package]] name = "cove-input" -version = "0.8.2" +version = "0.8.3" dependencies = [ "cove-macro", "crossterm", @@ -322,7 +322,7 @@ dependencies = [ [[package]] name = "cove-macro" -version = "0.8.2" +version = "0.8.3" dependencies = [ "case", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index f9538c7..fb3b90b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["cove", "cove-*"] [workspace.package] -version = "0.8.2" +version = "0.8.3" edition = "2021" [workspace.dependencies] From 106a047b785384e7f6758ea88209193880672c7c Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 6 Nov 2024 22:01:54 +0100 Subject: [PATCH 215/266] Fix clippy lint about lints --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fb3b90b..7438604 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ git = "https://github.com/Garmelon/toss.git" tag = "v0.2.3" [workspace.lints] -rust.unsafe_code = "forbid" +rust.unsafe_code = { level = "forbid", priority = 1 } rust.future_incompatible = "warn" rust.rust_2018_idioms = "warn" rust.unused = "warn" From 461cc37d8800c858175aa62ba27ca7ff99bfed89 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 6 Nov 2024 22:01:41 +0100 Subject: [PATCH 216/266] Remove unused method --- cove/src/ui/chat/renderer.rs | 1 - cove/src/ui/chat/tree/renderer.rs | 4 ---- 2 files changed, 5 deletions(-) diff --git a/cove/src/ui/chat/renderer.rs b/cove/src/ui/chat/renderer.rs index ae0ad8f..a619e7c 100644 --- a/cove/src/ui/chat/renderer.rs +++ b/cove/src/ui/chat/renderer.rs @@ -14,7 +14,6 @@ pub trait Renderer<Id> { fn blocks(&self) -> &Blocks<Id>; fn blocks_mut(&mut self) -> &mut Blocks<Id>; - fn into_blocks(self) -> Blocks<Id>; async fn expand_top(&mut self) -> Result<(), Self::Error>; async fn expand_bottom(&mut self) -> Result<(), Self::Error>; diff --git a/cove/src/ui/chat/tree/renderer.rs b/cove/src/ui/chat/tree/renderer.rs index 99f32b5..8b7b192 100644 --- a/cove/src/ui/chat/tree/renderer.rs +++ b/cove/src/ui/chat/tree/renderer.rs @@ -460,10 +460,6 @@ where &mut self.blocks } - fn into_blocks(self) -> TreeBlocks<M::Id> { - self.blocks - } - async fn expand_top(&mut self) -> Result<(), Self::Error> { let prev_root_id = if let Some(top_root_id) = &self.top_root_id { self.store.prev_root_id(top_root_id).await? From e43b27acfda653ac8f432e21f7b506450247b729 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 6 Nov 2024 22:04:57 +0100 Subject: [PATCH 217/266] Satisfy clippy lint --- cove/src/store.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cove/src/store.rs b/cove/src/store.rs index a3601a8..f6c85b7 100644 --- a/cove/src/store.rs +++ b/cove/src/store.rs @@ -130,6 +130,7 @@ impl<M: Msg> Tree<M> { } } +#[allow(dead_code)] #[async_trait] pub trait MsgStore<M: Msg> { type Error; From cff933b0bf7d1e0b72228320dd9660b62b629359 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 4 Dec 2024 20:12:44 +0100 Subject: [PATCH 218/266] Add and fix more lints --- Cargo.lock | 11 +---------- Cargo.toml | 16 +++++++++++++++- cove-input/src/lib.rs | 2 +- cove-macro/Cargo.toml | 1 - cove-macro/src/key_group.rs | 4 ++-- cove/Cargo.toml | 5 ----- 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da7fbb2..e31a89c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -158,12 +158,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" -[[package]] -name = "case" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6c0e7b807d60291f42f33f58480c0bfafe28ed08286446f45e463728cf9c1c" - [[package]] name = "caseless" version = "0.2.1" @@ -287,10 +281,8 @@ dependencies = [ "thiserror", "time", "tokio", - "tokio-tungstenite", "toss", "tz-rs", - "unicode-segmentation", "unicode-width", "vault", ] @@ -324,7 +316,6 @@ dependencies = [ name = "cove-macro" version = "0.8.3" dependencies = [ - "case", "proc-macro2", "quote", "syn", diff --git a/Cargo.toml b/Cargo.toml index 7438604..bda342f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,12 +19,26 @@ tag = "v0.2.3" [workspace.lints] rust.unsafe_code = { level = "forbid", priority = 1 } +# Lint groups +rust.deprecated_safe = "warn" rust.future_incompatible = "warn" +rust.keyword_idents = "warn" rust.rust_2018_idioms = "warn" rust.unused = "warn" -rust.noop_method_call = "warn" +# Individual lints +rust.non_local_definitions = "warn" +rust.redundant_imports = "warn" +rust.redundant_lifetimes = "warn" rust.single_use_lifetimes = "warn" +rust.unit_bindings = "warn" +rust.unnameable_types = "warn" +rust.unused_crate_dependencies = "warn" +rust.unused_import_braces = "warn" +rust.unused_lifetimes = "warn" +rust.unused_qualifications = "warn" +# Clippy clippy.use_self = "warn" + [profile.dev.package."*"] opt-level = 3 diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index ba3bd63..c15c4c3 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -40,7 +40,7 @@ impl<'a> KeyGroupInfo<'a> { } pub struct InputEvent<'a> { - event: crossterm::event::Event, + event: Event, terminal: &'a mut Terminal, crossterm_lock: Arc<FairMutex<()>>, } diff --git a/cove-macro/Cargo.toml b/cove-macro/Cargo.toml index e550ae5..0bda727 100644 --- a/cove-macro/Cargo.toml +++ b/cove-macro/Cargo.toml @@ -4,7 +4,6 @@ version = { workspace = true } edition = { workspace = true } [dependencies] -case = "1.0.0" proc-macro2 = "1.0.83" quote = "1.0.36" syn = "2.0.65" diff --git a/cove-macro/src/key_group.rs b/cove-macro/src/key_group.rs index bc7bdea..84d8cff 100644 --- a/cove-macro/src/key_group.rs +++ b/cove-macro/src/key_group.rs @@ -3,7 +3,7 @@ use quote::quote; use syn::spanned::Spanned; use syn::{Data, DeriveInput}; -use crate::util::{self, bail}; +use crate::util; fn decapitalize(s: &str) -> String { let mut chars = s.chars(); @@ -34,7 +34,7 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> { let default = util::serde_default(field)?; let Some(default) = default else { - return bail(field_ident.span(), "must have serde default"); + return util::bail(field_ident.span(), "must have serde default"); }; let default_value = default.value(); diff --git a/cove/Cargo.toml b/cove/Cargo.toml index ed44f0b..8b9f85a 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -25,17 +25,12 @@ rusqlite = { version = "0.31.0", features = ["bundled", "time"] } serde_json = "1.0.117" tokio = { version = "1.37.0", features = ["full"] } tz-rs = "0.6.14" -unicode-segmentation = "1.11.0" unicode-width = "0.1.12" [dependencies.time] version = "0.3.36" features = ["macros", "formatting", "parsing", "serde"] -[dependencies.tokio-tungstenite] -version = "0.21.0" -features = ["rustls-tls-native-roots"] - [dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" tag = "v0.5.1" From 2ecc4825334de9db880836c958c9e410241d9d0f Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 4 Dec 2024 20:20:07 +0100 Subject: [PATCH 219/266] Configure all deps in workspace --- Cargo.toml | 33 ++++++++++++++++++++++++ cove-config/Cargo.toml | 11 ++++---- cove-input/Cargo.toml | 19 +++++++------- cove-macro/Cargo.toml | 10 ++++---- cove/Cargo.toml | 58 +++++++++++++++++------------------------- 5 files changed, 75 insertions(+), 56 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bda342f..ffb4919 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,16 +7,49 @@ version = "0.8.3" edition = "2021" [workspace.dependencies] +anyhow = "1.0.86" +async-trait = "0.1.80" +clap = { version = "4.5.4", features = ["derive", "deprecated"] } +cookie = "0.18.1" crossterm = "0.27.0" +directories = "5.0.1" +edit = "0.1.5" +linkify = "0.10.0" +log = { version = "0.4.21", features = ["std"] } +once_cell = "1.19.0" +open = "5.1.3" parking_lot = "0.12.2" +proc-macro2 = "1.0.83" +quote = "1.0.36" +rusqlite = { version = "0.31.0", features = ["bundled", "time"] } serde = { version = "1.0.202", features = ["derive"] } serde_either = "0.2.1" +serde_json = "1.0.117" +syn = "2.0.65" thiserror = "1.0.61" +tokio = { version = "1.37.0", features = ["full"] } +toml = "0.8.13" +tz-rs = "0.6.14" +unicode-width = "0.1.12" + +[workspace.dependencies.euphoxide] +git = "https://github.com/Garmelon/euphoxide.git" +tag = "v0.5.1" +features = ["bot"] + +[workspace.dependencies.time] +version = "0.3.36" +features = ["macros", "formatting", "parsing", "serde"] [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" tag = "v0.2.3" +[workspace.dependencies.vault] +git = "https://github.com/Garmelon/vault.git" +tag = "v0.4.0" +features = ["tokio"] + [workspace.lints] rust.unsafe_code = { level = "forbid", priority = 1 } # Lint groups diff --git a/cove-config/Cargo.toml b/cove-config/Cargo.toml index f860469..9102bfd 100644 --- a/cove-config/Cargo.toml +++ b/cove-config/Cargo.toml @@ -1,16 +1,15 @@ [package] name = "cove-config" -version = { workspace = true } -edition = { workspace = true } +version.workspace = true +edition.workspace = true [dependencies] cove-input = { path = "../cove-input" } cove-macro = { path = "../cove-macro" } -serde = { workspace = true } -thiserror = { workspace = true } - -toml = "0.8.13" +serde.workspace = true +thiserror.workspace = true +toml.workspace = true [lints] workspace = true diff --git a/cove-input/Cargo.toml b/cove-input/Cargo.toml index 428060f..5005be2 100644 --- a/cove-input/Cargo.toml +++ b/cove-input/Cargo.toml @@ -1,19 +1,18 @@ [package] name = "cove-input" -version = { workspace = true } -edition = { workspace = true } +version.workspace = true +edition.workspace = true [dependencies] cove-macro = { path = "../cove-macro" } -crossterm = { workspace = true } -parking_lot = { workspace = true } -serde = { workspace = true } -serde_either = { workspace = true } -thiserror = { workspace = true } -toss = { workspace = true } - -edit = "0.1.5" +crossterm.workspace = true +edit.workspace = true +parking_lot.workspace = true +serde.workspace = true +serde_either.workspace = true +thiserror.workspace = true +toss.workspace = true [lints] workspace = true diff --git a/cove-macro/Cargo.toml b/cove-macro/Cargo.toml index 0bda727..6c01b7d 100644 --- a/cove-macro/Cargo.toml +++ b/cove-macro/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "cove-macro" -version = { workspace = true } -edition = { workspace = true } +version.workspace = true +edition.workspace = true [dependencies] -proc-macro2 = "1.0.83" -quote = "1.0.36" -syn = "2.0.65" +proc-macro2.workspace = true +quote.workspace = true +syn.workspace = true [lib] proc-macro = true diff --git a/cove/Cargo.toml b/cove/Cargo.toml index 8b9f85a..ad0e7f9 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -1,45 +1,33 @@ [package] name = "cove" -version = { workspace = true } -edition = { workspace = true } +version.workspace = true +edition.workspace = true [dependencies] cove-config = { path = "../cove-config" } cove-input = { path = "../cove-input" } -crossterm = { workspace = true } -parking_lot = { workspace = true } -thiserror = { workspace = true } -toss = { workspace = true } - -anyhow = "1.0.86" -async-trait = "0.1.80" -clap = { version = "4.5.4", features = ["derive", "deprecated"] } -cookie = "0.18.1" -directories = "5.0.1" -linkify = "0.10.0" -log = { version = "0.4.21", features = ["std"] } -once_cell = "1.19.0" -open = "5.1.3" -rusqlite = { version = "0.31.0", features = ["bundled", "time"] } -serde_json = "1.0.117" -tokio = { version = "1.37.0", features = ["full"] } -tz-rs = "0.6.14" -unicode-width = "0.1.12" - -[dependencies.time] -version = "0.3.36" -features = ["macros", "formatting", "parsing", "serde"] - -[dependencies.euphoxide] -git = "https://github.com/Garmelon/euphoxide.git" -tag = "v0.5.1" -features = ["bot"] - -[dependencies.vault] -git = "https://github.com/Garmelon/vault.git" -tag = "v0.4.0" -features = ["tokio"] +anyhow.workspace = true +async-trait.workspace = true +clap.workspace = true +cookie.workspace = true +crossterm.workspace = true +directories.workspace = true +euphoxide.workspace = true +linkify.workspace = true +log.workspace = true +once_cell.workspace = true +open.workspace = true +parking_lot.workspace = true +rusqlite.workspace = true +serde_json.workspace = true +thiserror.workspace = true +time.workspace = true +tokio.workspace = true +toss.workspace = true +tz-rs.workspace = true +unicode-width.workspace = true +vault.workspace = true [lints] workspace = true From f471b9ce00dbe553604ed50948a3d9e908600116 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 4 Dec 2024 20:56:39 +0100 Subject: [PATCH 220/266] Switch to jiff from time --- Cargo.lock | 275 ++++++++++++------------------ Cargo.toml | 7 +- cove/Cargo.toml | 3 +- cove/src/euph/small_message.rs | 8 +- cove/src/export/text.rs | 11 +- cove/src/logger.rs | 8 +- cove/src/main.rs | 12 +- cove/src/ui.rs | 6 +- cove/src/ui/chat.rs | 9 +- cove/src/ui/chat/tree.rs | 6 +- cove/src/ui/chat/tree/renderer.rs | 13 +- cove/src/ui/chat/tree/scroll.rs | 2 + cove/src/ui/chat/tree/widgets.rs | 4 +- cove/src/ui/chat/widgets.rs | 10 +- cove/src/ui/euph/room.rs | 4 +- cove/src/ui/rooms.rs | 6 + cove/src/util.rs | 34 ++-- cove/src/vault.rs | 17 +- cove/src/vault/euph.rs | 12 +- 19 files changed, 194 insertions(+), 253 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e31a89c..a0c630d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,9 +55,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" @@ -95,9 +95,9 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", @@ -125,17 +125,11 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" @@ -182,9 +176,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.4" +version = "4.5.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" dependencies = [ "clap_builder", "clap_derive", @@ -192,9 +186,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" dependencies = [ "anstream", "anstyle", @@ -204,9 +198,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", @@ -226,12 +220,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" -[[package]] -name = "const_fn" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373e9fafaa20882876db20562275ff58d50e0caa2590077fe7ce7bef90211d0d" - [[package]] name = "cookie" version = "0.18.1" @@ -244,9 +232,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" dependencies = [ "core-foundation-sys", "libc", @@ -254,9 +242,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cove" @@ -271,6 +259,7 @@ dependencies = [ "crossterm", "directories", "euphoxide", + "jiff", "linkify", "log", "once_cell", @@ -279,10 +268,8 @@ dependencies = [ "rusqlite", "serde_json", "thiserror", - "time", "tokio", "toss", - "tz-rs", "unicode-width", "vault", ] @@ -339,7 +326,7 @@ dependencies = [ "bitflags", "crossterm_winapi", "libc", - "mio", + "mio 0.8.11", "parking_lot", "signal-hook", "signal-hook-mio", @@ -378,7 +365,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", - "serde", ] [[package]] @@ -447,17 +433,17 @@ dependencies = [ [[package]] name = "euphoxide" version = "0.5.1" -source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.5.1#0256329f65f3ed853092cc210ae2a4a8c526a4bf" +source = "git+https://github.com/Garmelon/euphoxide.git#fe6869493225abb1229631816ffdc2fdae5d32e3" dependencies = [ "async-trait", "caseless", "clap", "cookie", "futures-util", + "jiff", "log", "serde", "serde_json", - "time", "tokio", "tokio-stream", "tokio-tungstenite", @@ -488,38 +474,29 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-sink", @@ -580,12 +557,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "home" version = "0.5.9" @@ -612,16 +583,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "indexmap" version = "2.2.6" @@ -664,10 +625,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] -name = "libc" -version = "0.2.155" +name = "jiff" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "db69f08d4fb10524cacdb074c10b296299d71274ddbc830a8ee65666867002e9" +dependencies = [ + "jiff-tzdb-platform", + "serde", + "windows-sys 0.52.0", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "libc" +version = "0.2.167" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libredox" @@ -717,9 +704,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" @@ -748,6 +735,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -763,16 +761,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" version = "0.32.2" @@ -849,12 +837,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1033,12 +1015,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.4" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ - "log", - "ring", + "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", @@ -1047,38 +1028,27 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", - "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" -dependencies = [ - "base64", - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -1108,9 +1078,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.11.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "e1415a607e92bec364ea2cf9264646dcce0f91e6d65281bd6f2819cca3bf39c8" dependencies = [ "bitflags", "core-foundation", @@ -1121,9 +1091,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -1131,9 +1101,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.202" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -1150,9 +1120,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -1171,11 +1141,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1217,7 +1188,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" dependencies = [ "libc", - "mio", + "mio 0.8.11", "signal-hook", ] @@ -1275,9 +1246,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.65" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -1364,28 +1335,27 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", "libc", - "mio", - "num_cpus", + "mio 1.0.3", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", @@ -1394,9 +1364,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ "rustls", "rustls-pki-types", @@ -1405,9 +1375,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -1416,9 +1386,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.21.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" dependencies = [ "futures-util", "log", @@ -1478,9 +1448,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.21.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" dependencies = [ "byteorder", "bytes", @@ -1493,7 +1463,6 @@ dependencies = [ "rustls-pki-types", "sha1", "thiserror", - "url", "utf-8", ] @@ -1503,21 +1472,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "tz-rs" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33851b15c848fad2cf4b105c6bb66eb9512b6f6c44a4b13f57c53c73c707e2b4" -dependencies = [ - "const_fn", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" version = "1.0.12" @@ -1532,9 +1486,9 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -1557,17 +1511,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "url" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - [[package]] name = "utf-8" version = "0.7.6" diff --git a/Cargo.toml b/Cargo.toml index ffb4919..8ec7185 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ cookie = "0.18.1" crossterm = "0.27.0" directories = "5.0.1" edit = "0.1.5" +jiff = "0.1.15" linkify = "0.10.0" log = { version = "0.4.21", features = ["std"] } once_cell = "1.19.0" @@ -29,18 +30,12 @@ syn = "2.0.65" thiserror = "1.0.61" tokio = { version = "1.37.0", features = ["full"] } toml = "0.8.13" -tz-rs = "0.6.14" unicode-width = "0.1.12" [workspace.dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -tag = "v0.5.1" features = ["bot"] -[workspace.dependencies.time] -version = "0.3.36" -features = ["macros", "formatting", "parsing", "serde"] - [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" tag = "v0.2.3" diff --git a/cove/Cargo.toml b/cove/Cargo.toml index ad0e7f9..115d781 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -14,6 +14,7 @@ cookie.workspace = true crossterm.workspace = true directories.workspace = true euphoxide.workspace = true +jiff.workspace = true linkify.workspace = true log.workspace = true once_cell.workspace = true @@ -22,10 +23,8 @@ parking_lot.workspace = true rusqlite.workspace = true serde_json.workspace = true thiserror.workspace = true -time.workspace = true tokio.workspace = true toss.workspace = true -tz-rs.workspace = true unicode-width.workspace = true vault.workspace = true diff --git a/cove/src/euph/small_message.rs b/cove/src/euph/small_message.rs index 0642801..994b0ae 100644 --- a/cove/src/euph/small_message.rs +++ b/cove/src/euph/small_message.rs @@ -2,9 +2,8 @@ use std::mem; use crossterm::style::Stylize; use euphoxide::api::{MessageId, Snowflake, Time}; -use time::OffsetDateTime; +use jiff::Timestamp; use toss::{Style, Styled}; -use tz::TimeZone; use crate::store::Msg; use crate::ui::ChatMsg; @@ -208,7 +207,6 @@ pub struct SmallMessage { pub id: MessageId, pub parent: Option<MessageId>, pub time: Time, - pub time_zone: &'static TimeZone, pub nick: String, pub content: String, pub seen: bool, @@ -272,8 +270,8 @@ impl Msg for SmallMessage { } impl ChatMsg for SmallMessage { - fn time(&self) -> Option<OffsetDateTime> { - crate::util::convert_to_time_zone(self.time_zone, self.time.0) + fn time(&self) -> Option<Timestamp> { + Some(self.time.as_timestamp()) } fn styled(&self) -> (Styled, Styled) { diff --git a/cove/src/export/text.rs b/cove/src/export/text.rs index bb3cfa1..23a75d8 100644 --- a/cove/src/export/text.rs +++ b/cove/src/export/text.rs @@ -1,16 +1,13 @@ use std::io::Write; use euphoxide::api::MessageId; -use time::format_description::FormatItem; -use time::macros::format_description; use unicode_width::UnicodeWidthStr; use crate::euph::SmallMessage; use crate::store::Tree; use crate::vault::EuphRoomVault; -const TIME_FORMAT: &[FormatItem<'_>] = - format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); +const TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; const TIME_EMPTY: &str = " "; pub async fn export<W: Write>(vault: &EuphRoomVault, out: &mut W) -> anyhow::Result<()> { @@ -67,11 +64,7 @@ fn write_msg<W: Write>( for (i, line) in msg.content.lines().enumerate() { if i == 0 { - let time = msg - .time - .0 - .format(TIME_FORMAT) - .expect("time can be formatted"); + let time = msg.time.as_timestamp().strftime(TIME_FORMAT); writeln!(file, "{time} {indent_string}[{nick}] {line}")?; } else { writeln!(file, "{TIME_EMPTY} {indent_string}| {nick_empty} {line}")?; diff --git a/cove/src/logger.rs b/cove/src/logger.rs index b8b696b..5574960 100644 --- a/cove/src/logger.rs +++ b/cove/src/logger.rs @@ -4,9 +4,9 @@ use std::vec; use async_trait::async_trait; use crossterm::style::Stylize; +use jiff::Timestamp; use log::{Level, LevelFilter, Log}; use parking_lot::Mutex; -use time::OffsetDateTime; use tokio::sync::mpsc; use toss::{Style, Styled}; @@ -16,7 +16,7 @@ use crate::ui::ChatMsg; #[derive(Debug, Clone)] pub struct LogMsg { id: usize, - time: OffsetDateTime, + time: Timestamp, level: Level, content: String, } @@ -42,7 +42,7 @@ impl Msg for LogMsg { } impl ChatMsg for LogMsg { - fn time(&self) -> Option<OffsetDateTime> { + fn time(&self) -> Option<Timestamp> { Some(self.time) } @@ -209,7 +209,7 @@ impl Log for Logger { let mut guard = self.messages.lock(); let msg = LogMsg { id: guard.len(), - time: OffsetDateTime::now_utc(), + time: Timestamp::now(), level: record.level(), content: format!("<{}> {}", record.target(), record.args()), }; diff --git a/cove/src/main.rs b/cove/src/main.rs index fc14b56..4a3ce9e 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -118,16 +118,12 @@ fn update_config_with_args(config: &mut Config, args: &Args) { } fn open_vault(config: &Config, dirs: &ProjectDirs) -> anyhow::Result<Vault> { - let time_zone = - util::load_time_zone(config.time_zone_ref()).context("failed to load time zone")?; - let time_zone = Box::leak(Box::new(time_zone)); - let vault = if config.ephemeral { - vault::launch_in_memory(time_zone)? + vault::launch_in_memory()? } else { let data_dir = data_dir(config, dirs); eprintln!("Data dir: {}", data_dir.to_string_lossy()); - vault::launch(&data_dir.join("vault.db"), time_zone)? + vault::launch(&data_dir.join("vault.db"))? }; Ok(vault) @@ -174,11 +170,13 @@ async fn run( ) -> anyhow::Result<()> { info!("Welcome to {NAME} {VERSION}",); + let tz = util::load_time_zone(config.time_zone_ref()).context("failed to load time zone")?; + let vault = open_vault(config, dirs)?; let mut terminal = Terminal::new()?; terminal.set_measuring(config.measure_widths); - Ui::run(config, &mut terminal, vault.clone(), logger, logger_rx).await?; + Ui::run(config, tz, &mut terminal, vault.clone(), logger, logger_rx).await?; drop(terminal); vault.close().await; diff --git a/cove/src/ui.rs b/cove/src/ui.rs index 95cc4d1..0263325 100644 --- a/cove/src/ui.rs +++ b/cove/src/ui.rs @@ -12,6 +12,7 @@ use std::time::{Duration, Instant}; use cove_config::Config; use cove_input::InputEvent; +use jiff::tz::TimeZone; use parking_lot::FairMutex; use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; @@ -84,6 +85,7 @@ impl Ui { pub async fn run( config: &'static Config, + tz: TimeZone, terminal: &mut Terminal, vault: Vault, logger: Logger, @@ -112,8 +114,8 @@ impl Ui { config, event_tx: event_tx.clone(), mode: Mode::Main, - rooms: Rooms::new(config, vault, event_tx.clone()).await, - log_chat: ChatState::new(logger), + rooms: Rooms::new(config, tz.clone(), vault, event_tx.clone()).await, + log_chat: ChatState::new(logger, tz), key_bindings_visible: false, key_bindings_list: ListState::new(), }; diff --git a/cove/src/ui/chat.rs b/cove/src/ui/chat.rs index c8a310c..cc7acc7 100644 --- a/cove/src/ui/chat.rs +++ b/cove/src/ui/chat.rs @@ -6,7 +6,8 @@ mod widgets; use cove_config::Keys; use cove_input::InputEvent; -use time::OffsetDateTime; +use jiff::tz::TimeZone; +use jiff::Timestamp; use toss::widgets::{BoxedAsync, EditorState}; use toss::{Styled, WidgetExt}; @@ -19,7 +20,7 @@ use self::tree::TreeViewState; use super::UiError; pub trait ChatMsg { - fn time(&self) -> Option<OffsetDateTime>; + fn time(&self) -> Option<Timestamp>; fn styled(&self) -> (Styled, Styled); fn edit(nick: &str, content: &str) -> (Styled, Styled); fn pseudo(nick: &str, content: &str) -> (Styled, Styled); @@ -41,14 +42,14 @@ pub struct ChatState<M: Msg, S: MsgStore<M>> { } impl<M: Msg, S: MsgStore<M> + Clone> ChatState<M, S> { - pub fn new(store: S) -> Self { + pub fn new(store: S, tz: TimeZone) -> Self { Self { cursor: Cursor::Bottom, editor: EditorState::new(), caesar: 0, mode: Mode::Tree, - tree: TreeViewState::new(store.clone()), + tree: TreeViewState::new(store.clone(), tz), store, } diff --git a/cove/src/ui/chat/tree.rs b/cove/src/ui/chat/tree.rs index 37972e5..b01602c 100644 --- a/cove/src/ui/chat/tree.rs +++ b/cove/src/ui/chat/tree.rs @@ -11,6 +11,7 @@ use std::collections::HashSet; use async_trait::async_trait; use cove_config::Keys; use cove_input::InputEvent; +use jiff::tz::TimeZone; use toss::widgets::EditorState; use toss::{AsyncWidget, Frame, Pos, Size, WidgetExt, WidthDb}; @@ -25,6 +26,7 @@ use super::Reaction; pub struct TreeViewState<M: Msg, S: MsgStore<M>> { store: S, + tz: TimeZone, last_size: Size, last_nick: String, @@ -36,9 +38,10 @@ pub struct TreeViewState<M: Msg, S: MsgStore<M>> { } impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> { - pub fn new(store: S) -> Self { + pub fn new(store: S, tz: TimeZone) -> Self { Self { store, + tz, last_size: Size::ZERO, last_nick: String::new(), last_cursor: Cursor::Bottom, @@ -443,6 +446,7 @@ where let mut renderer = TreeRenderer::new( context, &self.state.store, + &self.state.tz, &mut self.state.folded, self.cursor, self.editor, diff --git a/cove/src/ui/chat/tree/renderer.rs b/cove/src/ui/chat/tree/renderer.rs index 8b7b192..845e803 100644 --- a/cove/src/ui/chat/tree/renderer.rs +++ b/cove/src/ui/chat/tree/renderer.rs @@ -4,6 +4,7 @@ use std::collections::HashSet; use std::convert::Infallible; use async_trait::async_trait; +use jiff::tz::TimeZone; use toss::widgets::{EditorState, Empty, Predrawn, Resize}; use toss::{Size, Widget, WidthDb}; @@ -81,6 +82,7 @@ pub struct TreeRenderer<'a, M: Msg, S: MsgStore<M>> { context: TreeContext<M::Id>, store: &'a S, + tz: &'a TimeZone, folded: &'a mut HashSet<M::Id>, cursor: &'a mut Cursor<M::Id>, editor: &'a mut EditorState, @@ -108,6 +110,7 @@ where pub fn new( context: TreeContext<M::Id>, store: &'a S, + tz: &'a TimeZone, folded: &'a mut HashSet<M::Id>, cursor: &'a mut Cursor<M::Id>, editor: &'a mut EditorState, @@ -116,6 +119,7 @@ where Self { context, store, + tz, folded, cursor, editor, @@ -191,7 +195,14 @@ where }; let highlighted = highlighted && self.context.focused; - let widget = widgets::msg(highlighted, indent, msg, self.context.caesar, folded_info); + let widget = widgets::msg( + highlighted, + self.tz.clone(), + indent, + msg, + self.context.caesar, + folded_info, + ); let widget = Self::predraw(widget, self.context.size, self.widthdb); Block::new(TreeBlockId::Msg(msg_id), widget, true) } diff --git a/cove/src/ui/chat/tree/scroll.rs b/cove/src/ui/chat/tree/scroll.rs index 822b0b5..b02c4a1 100644 --- a/cove/src/ui/chat/tree/scroll.rs +++ b/cove/src/ui/chat/tree/scroll.rs @@ -37,6 +37,7 @@ where let mut renderer = TreeRenderer::new( context, &self.store, + &self.tz, &mut self.folded, cursor, editor, @@ -64,6 +65,7 @@ where let mut renderer = TreeRenderer::new( context, &self.store, + &self.tz, &mut self.folded, cursor, editor, diff --git a/cove/src/ui/chat/tree/widgets.rs b/cove/src/ui/chat/tree/widgets.rs index 9eb6690..b302670 100644 --- a/cove/src/ui/chat/tree/widgets.rs +++ b/cove/src/ui/chat/tree/widgets.rs @@ -1,6 +1,7 @@ use std::convert::Infallible; use crossterm::style::Stylize; +use jiff::tz::TimeZone; use toss::widgets::{Boxed, EditorState, Join2, Join4, Join5, Text}; use toss::{Style, Styled, WidgetExt}; @@ -49,6 +50,7 @@ fn style_pseudo_highlight() -> Style { pub fn msg<M: Msg + ChatMsg>( highlighted: bool, + tz: TimeZone, indent: usize, msg: &M, caesar: i8, @@ -72,7 +74,7 @@ pub fn msg<M: Msg + ChatMsg>( Join5::horizontal( Seen::new(msg.seen()).segment().with_fixed(true), - Time::new(msg.time(), style_time(highlighted)) + Time::new(msg.time().map(|t| t.to_zoned(tz)), style_time(highlighted)) .padding() .with_right(1) .with_stretch(true) diff --git a/cove/src/ui/chat/widgets.rs b/cove/src/ui/chat/widgets.rs index 5d35e9c..43ad29e 100644 --- a/cove/src/ui/chat/widgets.rs +++ b/cove/src/ui/chat/widgets.rs @@ -1,9 +1,7 @@ use std::convert::Infallible; use crossterm::style::Stylize; -use time::format_description::FormatItem; -use time::macros::format_description; -use time::OffsetDateTime; +use jiff::Zoned; use toss::widgets::{Boxed, Empty, Text}; use toss::{Frame, Pos, Size, Style, Widget, WidgetExt, WidthDb}; @@ -46,15 +44,15 @@ impl<E> Widget<E> for Indent { } } -const TIME_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day] [hour]:[minute]"); +const TIME_FORMAT: &str = "%Y-%m-%d %H:%M"; const TIME_WIDTH: u16 = 16; pub struct Time(Boxed<'static, Infallible>); impl Time { - pub fn new(time: Option<OffsetDateTime>, style: Style) -> Self { + pub fn new(time: Option<Zoned>, style: Style) -> Self { let widget = if let Some(time) = time { - let text = time.format(TIME_FORMAT).expect("could not format time"); + let text = time.strftime(TIME_FORMAT).to_string(); Text::new((text, style)) .background() .with_style(style) diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index 8fc2fe5..15da008 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -6,6 +6,7 @@ use crossterm::style::Stylize; use euphoxide::api::{Data, Message, MessageId, PacketType, SessionId}; use euphoxide::bot::instance::{Event, ServerConfig}; use euphoxide::conn::{self, Joined, Joining, SessionInfo}; +use jiff::tz::TimeZone; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::{mpsc, oneshot}; use toss::widgets::{BoxedAsync, EditorState, Join2, Layer, Text}; @@ -66,6 +67,7 @@ impl EuphRoom { server_config: ServerConfig, room_config: cove_config::EuphRoom, vault: EuphRoomVault, + tz: TimeZone, ui_event_tx: mpsc::UnboundedSender<UiEvent>, ) -> Self { Self { @@ -77,7 +79,7 @@ impl EuphRoom { focus: Focus::Chat, state: State::Normal, popups: VecDeque::new(), - chat: ChatState::new(vault), + chat: ChatState::new(vault, tz), last_msg_sent: None, nick_list: ListState::new(), } diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index f40bfc5..0157b01 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -13,6 +13,7 @@ use crossterm::style::Stylize; use euphoxide::api::SessionType; use euphoxide::bot::instance::{Event, ServerConfig}; use euphoxide::conn::{self, Joined}; +use jiff::tz::TimeZone; use tokio::sync::mpsc; use toss::widgets::{BoxedAsync, Empty, Join2, Text}; use toss::{Style, Styled, Widget, WidgetExt}; @@ -73,6 +74,7 @@ impl EuphServer { pub struct Rooms { config: &'static Config, + tz: TimeZone, vault: Vault, ui_event_tx: mpsc::UnboundedSender<UiEvent>, @@ -89,11 +91,13 @@ pub struct Rooms { impl Rooms { pub async fn new( config: &'static Config, + tz: TimeZone, vault: Vault, ui_event_tx: mpsc::UnboundedSender<UiEvent>, ) -> Self { let mut result = Self { config, + tz, vault, ui_event_tx, state: State::ShowList, @@ -142,6 +146,7 @@ impl Rooms { server.config.clone(), self.config.euph_room(&room.domain, &room.name), self.vault.euph().room(room), + self.tz.clone(), self.ui_event_tx.clone(), ) }) @@ -158,6 +163,7 @@ impl Rooms { server.config.clone(), self.config.euph_room(&room.domain, &room.name), self.vault.euph().room(room), + self.tz.clone(), self.ui_event_tx.clone(), ) }); diff --git a/cove/src/util.rs b/cove/src/util.rs index 4844d68..ff8a05a 100644 --- a/cove/src/util.rs +++ b/cove/src/util.rs @@ -1,8 +1,7 @@ use std::convert::Infallible; use std::env; -use time::{OffsetDateTime, UtcOffset}; -use tz::{TimeZone, TzError}; +use jiff::tz::TimeZone; pub trait InfallibleExt { type Inner; @@ -26,27 +25,30 @@ impl<T> InfallibleExt for Result<T, Infallible> { /// /// If no `TZ` environment variable could be found and no string is provided, /// the system local time (or UTC on Windows) is used. -pub fn load_time_zone(tz_string: Option<&str>) -> Result<TimeZone, TzError> { +pub fn load_time_zone(tz_string: Option<&str>) -> Result<TimeZone, jiff::Error> { let env_string = env::var("TZ").ok(); let tz_string = env_string.as_ref().map(|s| s as &str).or(tz_string); - match &tz_string { - // At the moment, TimeZone::from_posix_tz does not support "localtime" - // on Windows, so we handle that case manually. - Some("localtime") | None => TimeZone::local(), - Some(tz_string) => TimeZone::from_posix_tz(tz_string), + let Some(tz_string) = tz_string else { + return Ok(TimeZone::system()); + }; + + if tz_string == "localtime" { + return Ok(TimeZone::system()); } -} -pub fn convert_to_time_zone(tz: &TimeZone, time: OffsetDateTime) -> Option<OffsetDateTime> { - let utc_offset_in_seconds = tz - .find_local_time_type(time.unix_timestamp()) - .ok()? - .ut_offset(); + if let Some(tz_string) = tz_string.strip_prefix(':') { + return TimeZone::get(tz_string); + } - let utc_offset = UtcOffset::from_whole_seconds(utc_offset_in_seconds).ok()?; + // The time zone is either a manually specified string or a file in the tz + // database. We'll try to parse it as a manually specified string first + // because that doesn't require a fs lookup. + if let Ok(tz) = TimeZone::posix(tz_string) { + return Ok(tz); + } - Some(time.to_offset(utc_offset)) + TimeZone::get(tz_string) } pub fn caesar(text: &str, by: i8) -> String { diff --git a/cove/src/vault.rs b/cove/src/vault.rs index 55abbf0..6861901 100644 --- a/cove/src/vault.rs +++ b/cove/src/vault.rs @@ -6,7 +6,6 @@ use std::fs; use std::path::Path; use rusqlite::Connection; -use tz::TimeZone; use vault::tokio::TokioVault; use vault::Action; @@ -15,7 +14,6 @@ pub use self::euph::{EuphRoomVault, EuphVault, RoomIdentifier}; #[derive(Debug, Clone)] pub struct Vault { tokio_vault: TokioVault, - time_zone: &'static TimeZone, ephemeral: bool, } @@ -48,23 +46,18 @@ impl Vault { } } -fn launch_from_connection( - conn: Connection, - time_zone: &'static TimeZone, - ephemeral: bool, -) -> rusqlite::Result<Vault> { +fn launch_from_connection(conn: Connection, ephemeral: bool) -> rusqlite::Result<Vault> { conn.pragma_update(None, "foreign_keys", true)?; conn.pragma_update(None, "trusted_schema", false)?; let tokio_vault = TokioVault::launch_and_prepare(conn, &migrate::MIGRATIONS, prepare::prepare)?; Ok(Vault { tokio_vault, - time_zone, ephemeral, }) } -pub fn launch(path: &Path, time_zone: &'static TimeZone) -> rusqlite::Result<Vault> { +pub fn launch(path: &Path) -> rusqlite::Result<Vault> { // If this fails, rusqlite will complain about not being able to open the db // file, which saves me from adding a separate vault error type. let _ = fs::create_dir_all(path.parent().expect("path to file")); @@ -79,10 +72,10 @@ pub fn launch(path: &Path, time_zone: &'static TimeZone) -> rusqlite::Result<Vau conn.pragma_update(None, "locking_mode", "exclusive")?; conn.pragma_update(None, "journal_mode", "wal")?; - launch_from_connection(conn, time_zone, false) + launch_from_connection(conn, false) } -pub fn launch_in_memory(time_zone: &'static TimeZone) -> rusqlite::Result<Vault> { +pub fn launch_in_memory() -> rusqlite::Result<Vault> { let conn = Connection::open_in_memory()?; - launch_from_connection(conn, time_zone, true) + launch_from_connection(conn, true) } diff --git a/cove/src/vault/euph.rs b/cove/src/vault/euph.rs index f922345..c7d6410 100644 --- a/cove/src/vault/euph.rs +++ b/cove/src/vault/euph.rs @@ -6,7 +6,6 @@ use cookie::{Cookie, CookieJar}; use euphoxide::api::{Message, MessageId, SessionId, SessionView, Snowflake, Time, UserId}; use rusqlite::types::{FromSql, FromSqlError, ToSqlOutput, Value, ValueRef}; use rusqlite::{named_params, params, Connection, OptionalExtension, Row, ToSql, Transaction}; -use time::OffsetDateTime; use vault::Action; use crate::euph::SmallMessage; @@ -32,7 +31,7 @@ struct WTime(Time); impl ToSql for WTime { fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> { - let timestamp = self.0 .0.unix_timestamp(); + let timestamp = self.0 .0; Ok(ToSqlOutput::Owned(Value::Integer(timestamp))) } } @@ -40,9 +39,7 @@ impl ToSql for WTime { impl FromSql for WTime { fn column_result(value: ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> { let timestamp = i64::column_result(value)?; - Ok(Self(Time( - OffsetDateTime::from_unix_timestamp(timestamp).expect("timestamp in range"), - ))) + Ok(Self(Time(timestamp))) } } @@ -255,8 +252,6 @@ macro_rules! euph_room_vault_actions { $( struct $struct { room: RoomIdentifier, - #[allow(unused)] - time_zone: &'static tz::TimeZone, $( $arg: $arg_ty, )* } )* @@ -266,7 +261,6 @@ macro_rules! euph_room_vault_actions { pub async fn $fn(&self, $( $arg: $arg_ty, )* ) -> Result<$res, vault::tokio::Error<rusqlite::Error>> { self.vault.vault.tokio_vault.execute($struct { room: self.room.clone(), - time_zone: self.vault.vault.time_zone, $( $arg, )* }).await } @@ -626,7 +620,6 @@ impl Action for GetMsg { id: MessageId(row.get::<_, WSnowflake>(0)?.0), parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)), time: row.get::<_, WTime>(2)?.0, - time_zone: self.time_zone, nick: row.get(3)?, content: row.get(4)?, seen: row.get(5)?, @@ -720,7 +713,6 @@ impl Action for GetTree { id: MessageId(row.get::<_, WSnowflake>(0)?.0), parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)), time: row.get::<_, WTime>(2)?.0, - time_zone: self.time_zone, nick: row.get(3)?, content: row.get(4)?, seen: row.get(5)?, From e80d41cc47699e457c050dea13b0a97d006dd689 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Wed, 4 Dec 2024 21:13:19 +0100 Subject: [PATCH 221/266] Fix panic due to rustls --- Cargo.lock | 200 ++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + cove/Cargo.toml | 1 + cove/src/main.rs | 5 ++ 4 files changed, 205 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0c630d..365b6e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,6 +110,33 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "aws-lc-rs" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f95446d919226d587817a7d21379e6eb099b97b45110a7f272a444ca5c54070" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3ddc4a5b231dd6958b140ff3151b6412b3f4321fab354f399eec8f14b06df62" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -125,6 +152,29 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + [[package]] name = "bitflags" version = "2.6.0" @@ -164,9 +214,23 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.98" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] name = "cfg-if" @@ -174,6 +238,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.22" @@ -214,6 +289,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "cmake" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.1" @@ -266,6 +350,7 @@ dependencies = [ "open", "parking_lot", "rusqlite", + "rustls", "serde_json", "thiserror", "tokio", @@ -398,6 +483,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "edit" version = "0.1.5" @@ -474,6 +565,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures-core" version = "0.3.31" @@ -533,6 +630,12 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "hashbrown" version = "0.14.5" @@ -618,6 +721,15 @@ version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -650,12 +762,43 @@ dependencies = [ "jiff-tzdb", ] +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.5", +] + [[package]] name = "libredox" version = "0.1.3" @@ -714,6 +857,12 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.3" @@ -746,6 +895,22 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -831,6 +996,12 @@ dependencies = [ "windows-targets 0.52.5", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pathdiff" version = "0.2.1" @@ -867,6 +1038,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.83" @@ -1000,6 +1181,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.38.34" @@ -1019,6 +1206,8 @@ version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ + "aws-lc-rs", + "log", "once_cell", "rustls-pki-types", "rustls-webpki", @@ -1050,6 +1239,7 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -1171,6 +1361,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.17" diff --git a/Cargo.toml b/Cargo.toml index 8ec7185..e273b84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ parking_lot = "0.12.2" proc-macro2 = "1.0.83" quote = "1.0.36" rusqlite = { version = "0.31.0", features = ["bundled", "time"] } +rustls = "0.23.19" serde = { version = "1.0.202", features = ["derive"] } serde_either = "0.2.1" serde_json = "1.0.117" diff --git a/cove/Cargo.toml b/cove/Cargo.toml index 115d781..0ca2a2f 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -27,6 +27,7 @@ tokio.workspace = true toss.workspace = true unicode-width.workspace = true vault.workspace = true +rustls.workspace = true [lints] workspace = true diff --git a/cove/src/main.rs b/cove/src/main.rs index 4a3ce9e..6596eab 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -136,6 +136,11 @@ async fn main() -> anyhow::Result<()> { let (logger, logger_guard, logger_rx) = Logger::init(args.verbose); let dirs = ProjectDirs::from("de", "plugh", "cove").expect("failed to find config directory"); + // https://github.com/snapview/tokio-tungstenite/issues/353#issuecomment-2455247837 + rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .unwrap(); + // Locate config let config_path = config_path(&args, &dirs); eprintln!("Config file: {}", config_path.to_string_lossy()); From 55d43217700ba8fc5ebacae89a2809f4331a3a40 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 20 Feb 2025 19:40:29 +0100 Subject: [PATCH 222/266] Fix outdated reference in config docs Thanks, JRF! --- cove-config/src/euph.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cove-config/src/euph.rs b/cove-config/src/euph.rs index 5b1f12a..5ed0fb5 100644 --- a/cove-config/src/euph.rs +++ b/cove-config/src/euph.rs @@ -23,9 +23,9 @@ pub struct EuphRoom { /// associated with the current session. pub username: Option<String>, - /// If `euph.rooms.<room>.username` is set, this will force cove to set the - /// username even if there is already a different username associated with - /// the current session. + /// If `euph.servers.<domain>.rooms.<room>.username` is set, this will force + /// cove to set the username even if there is already a different username + /// associated with the current session. #[serde(default)] pub force_username: bool, From edc4219258a8625ec4e1034fa344e9fc4c7894d4 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 20 Feb 2025 20:58:00 +0100 Subject: [PATCH 223/266] Update euphoxide and jiff --- Cargo.lock | 223 ++++++++++++++++++++++++++++++++++++----------------- Cargo.toml | 2 +- 2 files changed, 153 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 365b6e3..18a0db8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,7 +26,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.34", ] [[package]] @@ -95,9 +95,9 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" dependencies = [ "proc-macro2", "quote", @@ -190,25 +190,18 @@ dependencies = [ "generic-array", ] -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" -version = "1.6.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "caseless" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808dab3318747be122cb31d36de18d4d1c81277a76f8332a02b81a3d73463d7f" +checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8" dependencies = [ - "regex", "unicode-normalization", ] @@ -251,9 +244,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.22" +version = "4.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" +checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" dependencies = [ "clap_builder", "clap_derive", @@ -261,9 +254,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.22" +version = "4.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" +checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" dependencies = [ "anstream", "anstyle", @@ -273,9 +266,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" dependencies = [ "heck", "proc-macro2", @@ -285,9 +278,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "cmake" @@ -352,7 +345,7 @@ dependencies = [ "rusqlite", "rustls", "serde_json", - "thiserror", + "thiserror 1.0.61", "tokio", "toss", "unicode-width", @@ -366,7 +359,7 @@ dependencies = [ "cove-input", "cove-macro", "serde", - "thiserror", + "thiserror 1.0.61", "toml", ] @@ -380,7 +373,7 @@ dependencies = [ "parking_lot", "serde", "serde_either", - "thiserror", + "thiserror 1.0.61", "toss", ] @@ -524,7 +517,7 @@ dependencies = [ [[package]] name = "euphoxide" version = "0.5.1" -source = "git+https://github.com/Garmelon/euphoxide.git#fe6869493225abb1229631816ffdc2fdae5d32e3" +source = "git+https://github.com/Garmelon/euphoxide.git#1d444684f7f292183c1ab5c89fef3872dadf96fd" dependencies = [ "async-trait", "caseless", @@ -621,7 +614,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.5", ] [[package]] @@ -738,26 +743,29 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jiff" -version = "0.1.15" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db69f08d4fb10524cacdb074c10b296299d71274ddbc830a8ee65666867002e9" +checksum = "3590fea8e9e22d449600c9bbd481a8163bef223e4ff938e5f55899f8cf1adb93" dependencies = [ "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", "serde", "windows-sys 0.52.0", ] [[package]] name = "jiff-tzdb" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653" +checksum = "cf2cec2f5d266af45a071ece48b1fb89f3b00b2421ac3a5fe10285a6caaa60d3" [[package]] name = "jiff-tzdb-platform" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329" +checksum = "a63c62e404e7b92979d2792352d885a7f8f83fd1d0d31eea582d77b2ceca697e" dependencies = [ "jiff-tzdb", ] @@ -785,9 +793,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" @@ -796,7 +804,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.5", + "windows-targets 0.48.5", ] [[package]] @@ -847,9 +855,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "memchr" @@ -880,7 +888,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -891,7 +899,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -1026,6 +1034,21 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1068,20 +1091,20 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ - "libc", "rand_chacha", "rand_core", + "zerocopy 0.8.20", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", @@ -1089,11 +1112,12 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3" dependencies = [ - "getrandom", + "getrandom 0.3.1", + "zerocopy 0.8.20", ] [[package]] @@ -1111,9 +1135,9 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -1153,7 +1177,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin", "untrusted", @@ -1291,9 +1315,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] @@ -1310,9 +1334,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", @@ -1331,9 +1355,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" dependencies = [ "itoa", "memchr", @@ -1469,7 +1493,16 @@ version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.61", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", ] [[package]] @@ -1483,6 +1516,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.3.36" @@ -1531,9 +1575,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.42.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -1549,9 +1593,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", @@ -1571,9 +1615,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -1582,9 +1626,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.24.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" dependencies = [ "futures-util", "log", @@ -1644,11 +1688,10 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.24.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" dependencies = [ - "byteorder", "bytes", "data-encoding", "http", @@ -1658,7 +1701,7 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror", + "thiserror 2.0.11", "utf-8", ] @@ -1746,6 +1789,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "which" version = "4.4.2" @@ -1928,13 +1980,31 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + [[package]] name = "zerocopy" version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.7.34", +] + +[[package]] +name = "zerocopy" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" +dependencies = [ + "zerocopy-derive 0.8.20", ] [[package]] @@ -1948,6 +2018,17 @@ dependencies = [ "syn", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index e273b84..a4bbb26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ cookie = "0.18.1" crossterm = "0.27.0" directories = "5.0.1" edit = "0.1.5" -jiff = "0.1.15" +jiff = "0.2.1" linkify = "0.10.0" log = { version = "0.4.21", features = ["std"] } once_cell = "1.19.0" From e1ba15cb9e0ae51fb0900d34eee46907000e0151 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 21 Feb 2025 00:32:44 +0100 Subject: [PATCH 224/266] Update time_zone docs --- CHANGELOG.md | 3 +++ cove-config/src/lib.rs | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec5600e..6864032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Procedure when bumping the version number: ## Unreleased +### Updated +- Documentation for `time_zone` config option + ## v0.8.3 - 2024-05-20 ### Changed diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 086e372..026ce9e 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -76,21 +76,21 @@ pub struct Config { /// Time zone that chat timestamps should be displayed in. /// - /// This option is interpreted as a POSIX TZ string. It is described here in - /// further detail: - /// <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html> + /// This option can either be the string `"localtime"`, a [POSIX TZ string], + /// or a [tz identifier] from the [tz database]. /// - /// On a normal system, the string `"localtime"` as well as any value from - /// the "TZ identifier" column of the following wikipedia article should be - /// valid TZ strings: - /// <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones> + /// When not set or when set to `"localtime"`, cove attempts to use your + /// system's configured time zone, falling back to UTC. /// - /// If the `TZ` environment variable exists, it overrides this option. If - /// neither exist, cove uses the system's local time zone. + /// When the string begins with a colon or doesn't match the a POSIX TZ + /// string format, it is interpreted as a tz identifier and looked up in + /// your system's tz database (or a bundled tz database on Windows). /// - /// **Warning:** On Windows, cove can't get the local time zone and uses UTC - /// instead. However, you can still specify a path to a tz data file or a - /// custom time zone string. + /// If the `TZ` environment variable exists, it overrides this option. + /// + /// [POSIX TZ string]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03 + /// [tz identifier]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + /// [tz database]: https://en.wikipedia.org/wiki/Tz_database #[serde(default)] #[document(default = "`$TZ` or local system time zone")] pub time_zone: Option<String>, From bd43fe060b6a0ab85b8ff28e92dd36820500e5a3 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Thu, 20 Feb 2025 22:35:09 +0100 Subject: [PATCH 225/266] Update dependencies Except rusqlite and vault, because newer sqlite versions appear to result in a *very* big performance drop. I suspect the query planner, though I really have no idea. --- Cargo.lock | 597 +++++++++++++++++++++++------------------------------ Cargo.toml | 41 ++-- 2 files changed, 277 insertions(+), 361 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18a0db8..e77a3ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 4 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" @@ -26,7 +26,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy 0.7.34", + "zerocopy 0.7.35", ] [[package]] @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -61,37 +61,38 @@ checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" [[package]] name = "async-trait" @@ -106,50 +107,48 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" -version = "1.9.0" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f95446d919226d587817a7d21379e6eb099b97b45110a7f272a444ca5c54070" +checksum = "4cd755adf9707cf671e31d944a189be3deaaeee11c8bc1d669bb8022ac90fbd0" dependencies = [ "aws-lc-sys", - "mirai-annotations", "paste", "zeroize", ] [[package]] name = "aws-lc-sys" -version = "0.21.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3ddc4a5b231dd6958b140ff3151b6412b3f4321fab354f399eec8f14b06df62" +checksum = "0f9dd2e03ee80ca2822dd6ea431163d2ef259f2066a4d6ccaca6d9dcb386aa43" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", - "libc", "paste", ] [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets", ] [[package]] @@ -177,9 +176,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "block-buffer" @@ -190,6 +189,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.0" @@ -207,9 +212,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.2" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" dependencies = [ "jobserver", "libc", @@ -284,18 +289,18 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "cmake" -version = "0.1.52" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" dependencies = [ "cc", ] [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "cookie" @@ -345,7 +350,7 @@ dependencies = [ "rusqlite", "rustls", "serde_json", - "thiserror 1.0.61", + "thiserror", "tokio", "toss", "unicode-width", @@ -359,7 +364,7 @@ dependencies = [ "cove-input", "cove-macro", "serde", - "thiserror 1.0.61", + "thiserror", "toml", ] @@ -373,7 +378,7 @@ dependencies = [ "parking_lot", "serde", "serde_either", - "thiserror 1.0.61", + "thiserror", "toss", ] @@ -388,24 +393,24 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crossterm" -version = "0.27.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags", "crossterm_winapi", - "libc", - "mio 0.8.11", + "mio", "parking_lot", + "rustix", "signal-hook", "signal-hook-mio", "winapi", @@ -432,9 +437,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" [[package]] name = "deranged" @@ -457,23 +462,23 @@ dependencies = [ [[package]] name = "directories" -version = "5.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -494,30 +499,30 @@ dependencies = [ [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "euphoxide" -version = "0.5.1" -source = "git+https://github.com/Garmelon/euphoxide.git#1d444684f7f292183c1ab5c89fef3872dadf96fd" +version = "0.6.0" +source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.6.0#4f7cc49b636301ce9beea9324a0a1390f8391009" dependencies = [ "async-trait", "caseless", @@ -548,9 +553,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fnv" @@ -626,20 +631,20 @@ dependencies = [ "cfg-if", "libc", "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.5", + "windows-targets", ] [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "hashbrown" @@ -650,13 +655,19 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "hashlink" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -667,18 +678,18 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -687,18 +698,18 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "indexmap" -version = "2.2.6" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -722,9 +733,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -737,9 +748,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jiff" @@ -752,7 +763,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -804,7 +815,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets", ] [[package]] @@ -839,9 +850,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "lock_api" @@ -861,9 +872,9 @@ checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" @@ -873,23 +884,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "adler2", ] [[package]] @@ -899,16 +898,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] -[[package]] -name = "mirai-annotations" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" - [[package]] name = "nom" version = "7.1.3" @@ -936,24 +930,24 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "open" -version = "5.1.3" +version = "5.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb49fbd5616580e9974662cb96a3463da4476e649a7e4b258df0de065db0657" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" dependencies = [ "is-wsl", "libc", @@ -962,9 +956,9 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "option-ext" @@ -983,9 +977,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1001,7 +995,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets", ] [[package]] @@ -1012,15 +1006,15 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1030,9 +1024,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "portable-atomic" @@ -1057,15 +1051,18 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy 0.7.35", +] [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", "syn", @@ -1073,18 +1070,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -1122,29 +1119,29 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 1.0.61", + "thiserror", ] [[package]] name = "regex" -version = "1.10.4" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1154,9 +1151,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1165,21 +1162,20 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ring" -version = "0.17.8" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] @@ -1213,22 +1209,22 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.19" +version = "0.23.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" dependencies = [ "aws-lc-rs", "log", @@ -1253,9 +1249,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" @@ -1271,17 +1267,17 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1292,9 +1288,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "3.0.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1415a607e92bec364ea2cf9264646dcce0f91e6d65281bd6f2819cca3bf39c8" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags", "core-foundation", @@ -1305,9 +1301,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -1367,9 +1363,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -1403,12 +1399,12 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio 0.8.11", + "mio", "signal-hook", ] @@ -1432,26 +1428,20 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "strsim" version = "0.11.1" @@ -1460,15 +1450,15 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.87" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -1477,23 +1467,16 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" dependencies = [ "cfg-if", "fastrand", + "getrandom 0.3.1", + "once_cell", "rustix", - "windows-sys 0.52.0", -] - -[[package]] -name = "thiserror" -version = "1.0.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" -dependencies = [ - "thiserror-impl 1.0.61", + "windows-sys 0.59.0", ] [[package]] @@ -1502,18 +1485,7 @@ version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.11", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -1529,9 +1501,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -1550,9 +1522,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -1560,9 +1532,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -1582,7 +1554,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.3", + "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -1604,12 +1576,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] @@ -1642,9 +1613,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.13" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -1654,18 +1625,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.13" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "serde", @@ -1676,8 +1647,8 @@ dependencies = [ [[package]] name = "toss" -version = "0.2.3" -source = "git+https://github.com/Garmelon/toss.git?tag=v0.2.3#b1d7221bae9e1bb57d8e5b49c315dc3ca56e947a" +version = "0.3.1" +source = "git+https://github.com/Garmelon/toss.git?tag=v0.3.1#be7eff0979e0e95d070e7c9cea42c328ffd04cc4" dependencies = [ "async-trait", "crossterm", @@ -1701,21 +1672,21 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror 2.0.11", + "thiserror", "utf-8", ] [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" [[package]] name = "unicode-linebreak" @@ -1734,15 +1705,15 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.12" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "untrusted" @@ -1758,9 +1729,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "vault" @@ -1779,9 +1750,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" @@ -1832,150 +1803,93 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" -dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.8" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" +checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" dependencies = [ "memchr", ] @@ -1991,11 +1905,12 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "zerocopy-derive 0.7.34", + "byteorder", + "zerocopy-derive 0.7.35", ] [[package]] @@ -2009,9 +1924,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", @@ -2031,6 +1946,6 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index a4bbb26..10e1968 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,39 +7,40 @@ version = "0.8.3" edition = "2021" [workspace.dependencies] -anyhow = "1.0.86" -async-trait = "0.1.80" -clap = { version = "4.5.4", features = ["derive", "deprecated"] } +anyhow = "1.0.96" +async-trait = "0.1.86" +clap = { version = "4.5.30", features = ["derive", "deprecated"] } cookie = "0.18.1" -crossterm = "0.27.0" -directories = "5.0.1" +crossterm = "0.28.1" +directories = "6.0.0" edit = "0.1.5" jiff = "0.2.1" linkify = "0.10.0" -log = { version = "0.4.21", features = ["std"] } -once_cell = "1.19.0" -open = "5.1.3" -parking_lot = "0.12.2" -proc-macro2 = "1.0.83" -quote = "1.0.36" +log = { version = "0.4.25", features = ["std"] } +once_cell = "1.20.2" +open = "5.3.2" +parking_lot = "0.12.3" +proc-macro2 = "1.0.93" +quote = "1.0.38" rusqlite = { version = "0.31.0", features = ["bundled", "time"] } -rustls = "0.23.19" -serde = { version = "1.0.202", features = ["derive"] } +rustls = "0.23.23" +serde = { version = "1.0.218", features = ["derive"] } serde_either = "0.2.1" -serde_json = "1.0.117" -syn = "2.0.65" -thiserror = "1.0.61" -tokio = { version = "1.37.0", features = ["full"] } -toml = "0.8.13" -unicode-width = "0.1.12" +serde_json = "1.0.139" +syn = "2.0.98" +thiserror = "2.0.11" +tokio = { version = "1.43.0", features = ["full"] } +toml = "0.8.20" +unicode-width = "0.2.0" [workspace.dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" +tag = "v0.6.0" features = ["bot"] [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" -tag = "v0.2.3" +tag = "v0.3.1" [workspace.dependencies.vault] git = "https://github.com/Garmelon/vault.git" From f45e66f572e10b993bbec6db4e36519143c07a94 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 21 Feb 2025 12:11:58 +0100 Subject: [PATCH 226/266] Fix or ignore 2024 edition migration lints --- Cargo.toml | 6 +++++- cove/src/ui/chat/tree/renderer.rs | 2 +- cove/src/ui/euph/account.rs | 2 +- cove/src/ui/euph/inspect.rs | 4 ++-- cove/src/ui/euph/nick_list.rs | 2 +- cove/src/ui/euph/popup.rs | 4 ++-- cove/src/ui/euph/room.rs | 2 +- cove/src/ui/key_bindings.rs | 2 +- cove/src/ui/rooms.rs | 2 +- 9 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 10e1968..96c02c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,11 @@ rust.unused_lifetimes = "warn" rust.unused_qualifications = "warn" # Clippy clippy.use_self = "warn" - +# Migrating to the 2024 edition +rust.rust_2024_compatibility = "warn" +rust.edition_2024_expr_fragment_specifier = { level = "allow", priority = 1 } +rust.if_let_rescope = { level = "allow", priority = 1 } +rust.tail_expr_drop_order = { level = "allow", priority = 1 } [profile.dev.package."*"] opt-level = 3 diff --git a/cove/src/ui/chat/tree/renderer.rs b/cove/src/ui/chat/tree/renderer.rs index 845e803..3aeadb1 100644 --- a/cove/src/ui/chat/tree/renderer.rs +++ b/cove/src/ui/chat/tree/renderer.rs @@ -437,7 +437,7 @@ where pub fn into_visible_blocks( self, - ) -> impl Iterator<Item = (Range<i32>, Block<TreeBlockId<M::Id>>)> { + ) -> impl Iterator<Item = (Range<i32>, Block<TreeBlockId<M::Id>>)> + use<M, S> { let area = renderer::visible_area(&self); self.blocks .into_iter() diff --git a/cove/src/ui/euph/account.rs b/cove/src/ui/euph/account.rs index 359e9d5..b97f014 100644 --- a/cove/src/ui/euph/account.rs +++ b/cove/src/ui/euph/account.rs @@ -66,7 +66,7 @@ impl LoggedOut { pub struct LoggedIn(PersonalAccountView); impl LoggedIn { - fn widget(&self) -> impl Widget<UiError> { + fn widget(&self) -> impl Widget<UiError> + use<> { let bold = Style::new().bold(); Join5::vertical( Text::new(("Logged in", bold.green())).segment(), diff --git a/cove/src/ui/euph/inspect.rs b/cove/src/ui/euph/inspect.rs index 25620a2..d1e2380 100644 --- a/cove/src/ui/euph/inspect.rs +++ b/cove/src/ui/euph/inspect.rs @@ -91,7 +91,7 @@ fn message_lines(mut text: Styled, msg: &Message) -> Styled { text } -pub fn session_widget(session: &SessionInfo) -> impl Widget<UiError> { +pub fn session_widget(session: &SessionInfo) -> impl Widget<UiError> + use<> { let heading_style = Style::new().bold(); let text = match session { @@ -108,7 +108,7 @@ pub fn session_widget(session: &SessionInfo) -> impl Widget<UiError> { Popup::new(Text::new(text), "Inspect session") } -pub fn message_widget(msg: &Message) -> impl Widget<UiError> { +pub fn message_widget(msg: &Message) -> impl Widget<UiError> + use<> { let heading_style = Style::new().bold(); let mut text = Styled::new("Message", heading_style).then_plain("\n"); diff --git a/cove/src/ui/euph/nick_list.rs b/cove/src/ui/euph/nick_list.rs index 23160bd..47f09c7 100644 --- a/cove/src/ui/euph/nick_list.rs +++ b/cove/src/ui/euph/nick_list.rs @@ -14,7 +14,7 @@ pub fn widget<'a>( list: &'a mut ListState<SessionId>, joined: &Joined, focused: bool, -) -> impl Widget<UiError> + 'a { +) -> impl Widget<UiError> + use<'a> { let mut list_builder = ListBuilder::new(); render_rows(&mut list_builder, joined, focused); list_builder.build(list) diff --git a/cove/src/ui/euph/popup.rs b/cove/src/ui/euph/popup.rs index f70e999..61b3ad5 100644 --- a/cove/src/ui/euph/popup.rs +++ b/cove/src/ui/euph/popup.rs @@ -12,7 +12,7 @@ pub enum RoomPopup { } impl RoomPopup { - fn server_error_widget(description: &str, reason: &str) -> impl Widget<UiError> { + fn server_error_widget(description: &str, reason: &str) -> impl Widget<UiError> + use<> { let border_style = Style::new().red().bold(); let text = Styled::new_plain(description) .then_plain("\n\n") @@ -23,7 +23,7 @@ impl RoomPopup { Popup::new(Text::new(text), ("Error", border_style)).with_border_style(border_style) } - pub fn widget(&self) -> impl Widget<UiError> { + pub fn widget(&self) -> impl Widget<UiError> + use<> { match self { Self::Error { description, diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index 15da008..0b36535 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -287,7 +287,7 @@ impl EuphRoom { .boxed_async() } - async fn status_widget(&self, state: Option<&euph::State>) -> impl Widget<UiError> { + async fn status_widget(&self, state: Option<&euph::State>) -> impl Widget<UiError> + use<> { let room_style = Style::new().bold().blue(); let mut info = Styled::new(format!("{} ", self.domain()), Style::new().grey()) .then(format!("&{}", self.name()), room_style); diff --git a/cove/src/ui/key_bindings.rs b/cove/src/ui/key_bindings.rs index 8fceda6..f5fa714 100644 --- a/cove/src/ui/key_bindings.rs +++ b/cove/src/ui/key_bindings.rs @@ -69,7 +69,7 @@ fn render_group_info(builder: &mut Builder, group_info: KeyGroupInfo<'_>) { pub fn widget<'a>( list: &'a mut ListState<Infallible>, config: &Config, -) -> impl Widget<UiError> + 'a { +) -> impl Widget<UiError> + use<'a> { let mut list_builder = ListBuilder::new(); for group_info in config.keys.groups() { diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index 0157b01..a7bb6f8 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -423,7 +423,7 @@ impl Rooms { list: &'a mut ListState<RoomIdentifier>, order: Order, euph_rooms: &HashMap<RoomIdentifier, EuphRoom>, - ) -> impl Widget<UiError> + 'a { + ) -> impl Widget<UiError> + use<'a> { let version_info = Styled::new_plain("Welcome to ") .then(format!("{NAME} {VERSION}"), Style::new().yellow().bold()) .then_plain("!"); From 25d2cc7c9826295ea535e47863b26a18efe349ef Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 21 Feb 2025 12:15:13 +0100 Subject: [PATCH 227/266] Migrate to 2024 edition --- Cargo.toml | 9 ++------- rustfmt.toml | 1 + 2 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 rustfmt.toml diff --git a/Cargo.toml b/Cargo.toml index 96c02c1..a58b43d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [workspace] -resolver = "2" +resolver = "3" members = ["cove", "cove-*"] [workspace.package] version = "0.8.3" -edition = "2021" +edition = "2024" [workspace.dependencies] anyhow = "1.0.96" @@ -68,11 +68,6 @@ rust.unused_lifetimes = "warn" rust.unused_qualifications = "warn" # Clippy clippy.use_self = "warn" -# Migrating to the 2024 edition -rust.rust_2024_compatibility = "warn" -rust.edition_2024_expr_fragment_specifier = { level = "allow", priority = 1 } -rust.if_let_rescope = { level = "allow", priority = 1 } -rust.tail_expr_drop_order = { level = "allow", priority = 1 } [profile.dev.package."*"] opt-level = 3 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..8153a3d --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +style_edition = "2021" From 816d8f86a3034b7fccd871e9f6dcfaabf046c123 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 21 Feb 2025 12:17:43 +0100 Subject: [PATCH 228/266] Migrate rustfmt style to 2024 edition --- cove-input/src/keys.rs | 4 ++-- cove-macro/src/lib.rs | 2 +- cove/src/main.rs | 2 +- cove/src/ui/chat.rs | 2 +- cove/src/ui/chat/blocks.rs | 2 +- cove/src/ui/chat/tree.rs | 4 ++-- cove/src/ui/chat/tree/renderer.rs | 4 ++-- cove/src/ui/chat/tree/scroll.rs | 6 +++--- cove/src/ui/chat/tree/widgets.rs | 2 +- cove/src/ui/euph/account.rs | 2 +- cove/src/ui/euph/auth.rs | 4 ++-- cove/src/ui/euph/inspect.rs | 2 +- cove/src/ui/euph/links.rs | 2 +- cove/src/ui/euph/nick.rs | 2 +- cove/src/ui/euph/nick_list.rs | 2 +- cove/src/ui/euph/popup.rs | 2 +- cove/src/ui/euph/room.rs | 2 +- cove/src/ui/key_bindings.rs | 2 +- cove/src/ui/rooms.rs | 2 +- cove/src/ui/rooms/connect.rs | 2 +- cove/src/ui/rooms/delete.rs | 2 +- cove/src/vault.rs | 2 +- cove/src/vault/euph.rs | 6 +++--- rustfmt.toml | 1 - 24 files changed, 31 insertions(+), 32 deletions(-) delete mode 100644 rustfmt.toml diff --git a/cove-input/src/keys.rs b/cove-input/src/keys.rs index 337a5f3..7e5cfa0 100644 --- a/cove-input/src/keys.rs +++ b/cove-input/src/keys.rs @@ -3,7 +3,7 @@ use std::num::ParseIntError; use std::str::FromStr; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use serde::{de::Error, Deserialize, Deserializer}; +use serde::{Deserialize, Deserializer, de::Error}; use serde::{Serialize, Serializer}; use serde_either::SingleOrVec; @@ -117,7 +117,7 @@ impl KeyPress { "alt" if !self.alt => self.alt = true, "any" if !self.shift && !self.ctrl && !self.alt => self.any = true, m @ ("shift" | "ctrl" | "alt" | "any") => { - return Err(ParseKeysError::ConflictingModifier(m.to_string())) + return Err(ParseKeysError::ConflictingModifier(m.to_string())); } m => return Err(ParseKeysError::UnknownModifier(m.to_string())), } diff --git a/cove-macro/src/lib.rs b/cove-macro/src/lib.rs index fd09f5f..c655f2a 100644 --- a/cove-macro/src/lib.rs +++ b/cove-macro/src/lib.rs @@ -1,4 +1,4 @@ -use syn::{parse_macro_input, DeriveInput}; +use syn::{DeriveInput, parse_macro_input}; mod document; mod key_group; diff --git a/cove/src/main.rs b/cove/src/main.rs index 6596eab..fe9a9c1 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -15,8 +15,8 @@ use std::path::PathBuf; use anyhow::Context; use clap::Parser; -use cove_config::doc::Document; use cove_config::Config; +use cove_config::doc::Document; use directories::{BaseDirs, ProjectDirs}; use log::info; use tokio::sync::mpsc; diff --git a/cove/src/ui/chat.rs b/cove/src/ui/chat.rs index cc7acc7..69f5e2b 100644 --- a/cove/src/ui/chat.rs +++ b/cove/src/ui/chat.rs @@ -6,8 +6,8 @@ mod widgets; use cove_config::Keys; use cove_input::InputEvent; -use jiff::tz::TimeZone; use jiff::Timestamp; +use jiff::tz::TimeZone; use toss::widgets::{BoxedAsync, EditorState}; use toss::{Styled, WidgetExt}; diff --git a/cove/src/ui/chat/blocks.rs b/cove/src/ui/chat/blocks.rs index 1b91864..8360e83 100644 --- a/cove/src/ui/chat/blocks.rs +++ b/cove/src/ui/chat/blocks.rs @@ -1,6 +1,6 @@ //! Common rendering logic. -use std::collections::{vec_deque, VecDeque}; +use std::collections::{VecDeque, vec_deque}; use toss::widgets::Predrawn; diff --git a/cove/src/ui/chat/tree.rs b/cove/src/ui/chat/tree.rs index b01602c..9ac31f8 100644 --- a/cove/src/ui/chat/tree.rs +++ b/cove/src/ui/chat/tree.rs @@ -16,13 +16,13 @@ use toss::widgets::EditorState; use toss::{AsyncWidget, Frame, Pos, Size, WidgetExt, WidthDb}; use crate::store::{Msg, MsgStore}; -use crate::ui::{util, ChatMsg, UiError}; +use crate::ui::{ChatMsg, UiError, util}; use crate::util::InfallibleExt; use self::renderer::{TreeContext, TreeRenderer}; -use super::cursor::Cursor; use super::Reaction; +use super::cursor::Cursor; pub struct TreeViewState<M: Msg, S: MsgStore<M>> { store: S, diff --git a/cove/src/ui/chat/tree/renderer.rs b/cove/src/ui/chat/tree/renderer.rs index 3aeadb1..192e46c 100644 --- a/cove/src/ui/chat/tree/renderer.rs +++ b/cove/src/ui/chat/tree/renderer.rs @@ -9,10 +9,10 @@ use toss::widgets::{EditorState, Empty, Predrawn, Resize}; use toss::{Size, Widget, WidthDb}; use crate::store::{Msg, MsgStore, Tree}; +use crate::ui::ChatMsg; use crate::ui::chat::blocks::{Block, Blocks, Range}; use crate::ui::chat::cursor::Cursor; -use crate::ui::chat::renderer::{self, overlaps, Renderer}; -use crate::ui::ChatMsg; +use crate::ui::chat::renderer::{self, Renderer, overlaps}; use crate::util::InfallibleExt; use super::widgets; diff --git a/cove/src/ui/chat/tree/scroll.rs b/cove/src/ui/chat/tree/scroll.rs index b02c4a1..73e0e71 100644 --- a/cove/src/ui/chat/tree/scroll.rs +++ b/cove/src/ui/chat/tree/scroll.rs @@ -1,12 +1,12 @@ -use toss::widgets::EditorState; use toss::WidthDb; +use toss::widgets::EditorState; use crate::store::{Msg, MsgStore}; -use crate::ui::chat::cursor::Cursor; use crate::ui::ChatMsg; +use crate::ui::chat::cursor::Cursor; -use super::renderer::{TreeContext, TreeRenderer}; use super::TreeViewState; +use super::renderer::{TreeContext, TreeRenderer}; impl<M, S> TreeViewState<M, S> where diff --git a/cove/src/ui/chat/tree/widgets.rs b/cove/src/ui/chat/tree/widgets.rs index b302670..bc807d7 100644 --- a/cove/src/ui/chat/tree/widgets.rs +++ b/cove/src/ui/chat/tree/widgets.rs @@ -6,8 +6,8 @@ use toss::widgets::{Boxed, EditorState, Join2, Join4, Join5, Text}; use toss::{Style, Styled, WidgetExt}; use crate::store::Msg; -use crate::ui::chat::widgets::{Indent, Seen, Time}; use crate::ui::ChatMsg; +use crate::ui::chat::widgets::{Indent, Seen, Time}; use crate::util; pub const PLACEHOLDER: &str = "[...]"; diff --git a/cove/src/ui/euph/account.rs b/cove/src/ui/euph/account.rs index b97f014..a982711 100644 --- a/cove/src/ui/euph/account.rs +++ b/cove/src/ui/euph/account.rs @@ -8,7 +8,7 @@ use toss::{Style, Widget, WidgetExt}; use crate::euph::{self, Room}; use crate::ui::widgets::Popup; -use crate::ui::{util, UiError}; +use crate::ui::{UiError, util}; use super::popup::PopupResult; diff --git a/cove/src/ui/euph/auth.rs b/cove/src/ui/euph/auth.rs index b938ff1..2fbc1c0 100644 --- a/cove/src/ui/euph/auth.rs +++ b/cove/src/ui/euph/auth.rs @@ -1,11 +1,11 @@ use cove_config::Keys; use cove_input::InputEvent; -use toss::widgets::EditorState; use toss::Widget; +use toss::widgets::EditorState; use crate::euph::Room; use crate::ui::widgets::Popup; -use crate::ui::{util, UiError}; +use crate::ui::{UiError, util}; use super::popup::PopupResult; diff --git a/cove/src/ui/euph/inspect.rs b/cove/src/ui/euph/inspect.rs index d1e2380..e2bcf33 100644 --- a/cove/src/ui/euph/inspect.rs +++ b/cove/src/ui/euph/inspect.rs @@ -6,8 +6,8 @@ use euphoxide::conn::SessionInfo; use toss::widgets::Text; use toss::{Style, Styled, Widget}; -use crate::ui::widgets::Popup; use crate::ui::UiError; +use crate::ui::widgets::Popup; use super::popup::PopupResult; diff --git a/cove/src/ui/euph/links.rs b/cove/src/ui/euph/links.rs index 8e3f535..b3e5fb4 100644 --- a/cove/src/ui/euph/links.rs +++ b/cove/src/ui/euph/links.rs @@ -7,7 +7,7 @@ use toss::widgets::{Join2, Text}; use toss::{Style, Styled, Widget, WidgetExt}; use crate::ui::widgets::{ListBuilder, ListState, Popup}; -use crate::ui::{key_bindings, util, UiError}; +use crate::ui::{UiError, key_bindings, util}; use super::popup::PopupResult; diff --git a/cove/src/ui/euph/nick.rs b/cove/src/ui/euph/nick.rs index 0bb1062..91bdd10 100644 --- a/cove/src/ui/euph/nick.rs +++ b/cove/src/ui/euph/nick.rs @@ -6,7 +6,7 @@ use toss::{Style, Widget}; use crate::euph::{self, Room}; use crate::ui::widgets::Popup; -use crate::ui::{util, UiError}; +use crate::ui::{UiError, util}; use super::popup::PopupResult; diff --git a/cove/src/ui/euph/nick_list.rs b/cove/src/ui/euph/nick_list.rs index 47f09c7..8401c80 100644 --- a/cove/src/ui/euph/nick_list.rs +++ b/cove/src/ui/euph/nick_list.rs @@ -7,8 +7,8 @@ use toss::widgets::{Background, Text}; use toss::{Style, Styled, Widget, WidgetExt}; use crate::euph; -use crate::ui::widgets::{ListBuilder, ListState}; use crate::ui::UiError; +use crate::ui::widgets::{ListBuilder, ListState}; pub fn widget<'a>( list: &'a mut ListState<SessionId>, diff --git a/cove/src/ui/euph/popup.rs b/cove/src/ui/euph/popup.rs index 61b3ad5..e9d4671 100644 --- a/cove/src/ui/euph/popup.rs +++ b/cove/src/ui/euph/popup.rs @@ -4,8 +4,8 @@ use crossterm::style::Stylize; use toss::widgets::Text; use toss::{Style, Styled, Widget}; -use crate::ui::widgets::Popup; use crate::ui::UiError; +use crate::ui::widgets::Popup; pub enum RoomPopup { Error { description: String, reason: String }, diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index 0b36535..eafd789 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -16,7 +16,7 @@ use crate::euph; use crate::macros::logging_unwrap; use crate::ui::chat::{ChatState, Reaction}; use crate::ui::widgets::ListState; -use crate::ui::{util, UiError, UiEvent}; +use crate::ui::{UiError, UiEvent, util}; use crate::vault::EuphRoomVault; use super::account::AccountUiState; diff --git a/cove/src/ui/key_bindings.rs b/cove/src/ui/key_bindings.rs index f5fa714..de3c889 100644 --- a/cove/src/ui/key_bindings.rs +++ b/cove/src/ui/key_bindings.rs @@ -9,7 +9,7 @@ use toss::widgets::{Either2, Join2, Padding, Text}; use toss::{Style, Styled, Widget, WidgetExt}; use super::widgets::{ListBuilder, ListState, Popup}; -use super::{util, UiError}; +use super::{UiError, util}; type Line = Either2<Text, Join2<Padding<Text>, Text>>; type Builder = ListBuilder<'static, Infallible, Line>; diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index a7bb6f8..f26defa 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -28,7 +28,7 @@ use self::delete::{DeleteResult, DeleteState}; use super::euph::room::EuphRoom; use super::widgets::{ListBuilder, ListState}; -use super::{key_bindings, util, UiError, UiEvent}; +use super::{UiError, UiEvent, key_bindings, util}; enum State { ShowList, diff --git a/cove/src/ui/rooms/connect.rs b/cove/src/ui/rooms/connect.rs index 2bf90c5..4ad3c39 100644 --- a/cove/src/ui/rooms/connect.rs +++ b/cove/src/ui/rooms/connect.rs @@ -5,7 +5,7 @@ use toss::widgets::{EditorState, Empty, Join2, Join3, Text}; use toss::{Style, Styled, Widget, WidgetExt}; use crate::ui::widgets::Popup; -use crate::ui::{util, UiError}; +use crate::ui::{UiError, util}; use crate::vault::RoomIdentifier; #[derive(Clone, Copy, PartialEq, Eq)] diff --git a/cove/src/ui/rooms/delete.rs b/cove/src/ui/rooms/delete.rs index 5a20415..d5b6884 100644 --- a/cove/src/ui/rooms/delete.rs +++ b/cove/src/ui/rooms/delete.rs @@ -5,7 +5,7 @@ use toss::widgets::{EditorState, Empty, Join2, Text}; use toss::{Style, Styled, Widget, WidgetExt}; use crate::ui::widgets::Popup; -use crate::ui::{util, UiError}; +use crate::ui::{UiError, util}; use crate::vault::RoomIdentifier; pub struct DeleteState { diff --git a/cove/src/vault.rs b/cove/src/vault.rs index 6861901..512dfd2 100644 --- a/cove/src/vault.rs +++ b/cove/src/vault.rs @@ -6,8 +6,8 @@ use std::fs; use std::path::Path; use rusqlite::Connection; -use vault::tokio::TokioVault; use vault::Action; +use vault::tokio::TokioVault; pub use self::euph::{EuphRoomVault, EuphVault, RoomIdentifier}; diff --git a/cove/src/vault/euph.rs b/cove/src/vault/euph.rs index c7d6410..3e98590 100644 --- a/cove/src/vault/euph.rs +++ b/cove/src/vault/euph.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; use cookie::{Cookie, CookieJar}; use euphoxide::api::{Message, MessageId, SessionId, SessionView, Snowflake, Time, UserId}; use rusqlite::types::{FromSql, FromSqlError, ToSqlOutput, Value, ValueRef}; -use rusqlite::{named_params, params, Connection, OptionalExtension, Row, ToSql, Transaction}; +use rusqlite::{Connection, OptionalExtension, Row, ToSql, Transaction, named_params, params}; use vault::Action; use crate::euph::SmallMessage; @@ -16,7 +16,7 @@ struct WSnowflake(Snowflake); impl ToSql for WSnowflake { fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> { - self.0 .0.to_sql() + self.0.0.to_sql() } } @@ -31,7 +31,7 @@ struct WTime(Time); impl ToSql for WTime { fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> { - let timestamp = self.0 .0; + let timestamp = self.0.0; Ok(ToSqlOutput::Owned(Value::Integer(timestamp))) } } diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 8153a3d..0000000 --- a/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -style_edition = "2021" From fbc64de60713c8e7f065ad36ca055be9bf4f1b8d Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 21 Feb 2025 12:42:51 +0100 Subject: [PATCH 229/266] Merge and reorder imports This change brings them in-line with the default settings of rust-analyzer. Originally, I disliked this style, but by now, I've grown used to it and prefer it slightly. Also, I like staying on the default path since I usually don't need to check the imports to see if rust-analyzer messed anything up (except for omitting the self:: when importing modules defined in the current module, grr :D). I've also come around to the idea of trailing mod definitions instead of putting them at the top: I would also put module definitions that contain code instead of referencing another file below the imports. It feels weird to have code above imports. So it just makes sense to do the same for all types of mod definitions. If rustfmt ever gains stable support for grouping imports, I'll probably use that instead. --- .vscode/settings.json | 2 +- cove-config/src/doc.rs | 3 +- cove-config/src/lib.rs | 19 ++++++----- cove-input/src/keys.rs | 7 ++-- cove-input/src/lib.rs | 7 ++-- cove-macro/src/document.rs | 3 +- cove-macro/src/key_group.rs | 3 +- cove-macro/src/util.rs | 7 ++-- cove/src/euph.rs | 8 ++--- cove/src/euph/room.rs | 24 ++++++------- cove/src/euph/small_message.rs | 3 +- cove/src/export.rs | 12 ++++--- cove/src/export/text.rs | 4 +-- cove/src/logger.rs | 10 +++--- cove/src/main.rs | 33 +++++++++--------- cove/src/store.rs | 5 +-- cove/src/ui.rs | 52 ++++++++++++++-------------- cove/src/ui/chat.rs | 32 +++++++++--------- cove/src/ui/chat/cursor.rs | 3 +- cove/src/ui/chat/tree.rs | 22 ++++++------ cove/src/ui/chat/tree/renderer.rs | 27 +++++++++------ cove/src/ui/chat/tree/scroll.rs | 16 +++++---- cove/src/ui/chat/tree/widgets.rs | 18 ++++++---- cove/src/ui/chat/widgets.rs | 6 ++-- cove/src/ui/euph/account.rs | 16 +++++---- cove/src/ui/euph/auth.rs | 10 +++--- cove/src/ui/euph/inspect.rs | 12 +++---- cove/src/ui/euph/links.rs | 15 +++++---- cove/src/ui/euph/nick.rs | 10 +++--- cove/src/ui/euph/nick_list.rs | 22 ++++++++---- cove/src/ui/euph/popup.rs | 6 ++-- cove/src/ui/euph/room.rs | 48 ++++++++++++++++---------- cove/src/ui/key_bindings.rs | 12 ++++--- cove/src/ui/rooms.rs | 56 +++++++++++++++++++------------ cove/src/ui/rooms/connect.rs | 13 ++++--- cove/src/ui/rooms/delete.rs | 13 ++++--- cove/src/ui/widgets.rs | 6 ++-- cove/src/ui/widgets/popup.rs | 6 ++-- cove/src/util.rs | 3 +- cove/src/vault.rs | 16 ++++----- cove/src/vault/euph.rs | 15 +++++---- 41 files changed, 332 insertions(+), 273 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a89179..4e428aa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,7 @@ "files.insertFinalNewline": true, "rust-analyzer.cargo.features": "all", "rust-analyzer.imports.granularity.enforce": true, - "rust-analyzer.imports.granularity.group": "module", + "rust-analyzer.imports.granularity.group": "crate", "rust-analyzer.imports.group.enable": true, "evenBetterToml.formatter.columnWidth": 100, } diff --git a/cove-config/src/doc.rs b/cove-config/src/doc.rs index 16ed3ac..35f6074 100644 --- a/cove-config/src/doc.rs +++ b/cove-config/src/doc.rs @@ -1,7 +1,6 @@ //! Auto-generate markdown documentation. -use std::collections::HashMap; -use std::path::PathBuf; +use std::{collections::HashMap, path::PathBuf}; use cove_input::KeyBinding; pub use cove_macro::Document; diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 026ce9e..0d350ed 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -1,16 +1,17 @@ -pub mod doc; -mod euph; -mod keys; - -use std::io::ErrorKind; -use std::path::{Path, PathBuf}; -use std::{fs, io}; +use std::{ + fs, + io::{self, ErrorKind}, + path::{Path, PathBuf}, +}; use doc::Document; use serde::Deserialize; -pub use crate::euph::*; -pub use crate::keys::*; +pub use crate::{euph::*, keys::*}; + +pub mod doc; +mod euph; +mod keys; #[derive(Debug, thiserror::Error)] pub enum Error { diff --git a/cove-input/src/keys.rs b/cove-input/src/keys.rs index 7e5cfa0..8d2fdf1 100644 --- a/cove-input/src/keys.rs +++ b/cove-input/src/keys.rs @@ -1,10 +1,7 @@ -use std::fmt; -use std::num::ParseIntError; -use std::str::FromStr; +use std::{fmt, num::ParseIntError, str::FromStr}; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use serde::{Deserialize, Deserializer, de::Error}; -use serde::{Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; use serde_either::SingleOrVec; #[derive(Debug, thiserror::Error)] diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs index c15c4c3..f6b2e92 100644 --- a/cove-input/src/lib.rs +++ b/cove-input/src/lib.rs @@ -1,7 +1,4 @@ -mod keys; - -use std::io; -use std::sync::Arc; +use std::{io, sync::Arc}; pub use cove_macro::KeyGroup; use crossterm::event::{Event, KeyEvent, KeyEventKind}; @@ -10,6 +7,8 @@ use toss::{Frame, Terminal, WidthDb}; pub use crate::keys::*; +mod keys; + pub struct KeyBindingInfo<'a> { pub name: &'static str, pub binding: &'a KeyBinding, diff --git a/cove-macro/src/document.rs b/cove-macro/src/document.rs index e8e248e..afec84d 100644 --- a/cove-macro/src/document.rs +++ b/cove-macro/src/document.rs @@ -1,7 +1,6 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::spanned::Spanned; -use syn::{Data, DataEnum, DataStruct, DeriveInput, Field, Ident, LitStr}; +use syn::{Data, DataEnum, DataStruct, DeriveInput, Field, Ident, LitStr, spanned::Spanned}; use crate::util::{self, SerdeDefault}; diff --git a/cove-macro/src/key_group.rs b/cove-macro/src/key_group.rs index 84d8cff..832bfd3 100644 --- a/cove-macro/src/key_group.rs +++ b/cove-macro/src/key_group.rs @@ -1,7 +1,6 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::spanned::Spanned; -use syn::{Data, DeriveInput}; +use syn::{Data, DeriveInput, spanned::Spanned}; use crate::util; diff --git a/cove-macro/src/util.rs b/cove-macro/src/util.rs index b7bf62a..d73b7ca 100644 --- a/cove-macro/src/util.rs +++ b/cove-macro/src/util.rs @@ -1,8 +1,9 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::parse::Parse; -use syn::punctuated::Punctuated; -use syn::{Attribute, Expr, ExprLit, ExprPath, Field, Lit, LitStr, Path, Token, Type}; +use syn::{ + Attribute, Expr, ExprLit, ExprPath, Field, Lit, LitStr, Path, Token, Type, parse::Parse, + punctuated::Punctuated, +}; pub fn bail<T>(span: Span, message: &str) -> syn::Result<T> { Err(syn::Error::new(span, message)) diff --git a/cove/src/euph.rs b/cove/src/euph.rs index ab93753..d1fd872 100644 --- a/cove/src/euph.rs +++ b/cove/src/euph.rs @@ -1,7 +1,7 @@ -mod room; -mod small_message; -mod util; - pub use room::*; pub use small_message::*; pub use util::*; + +mod room; +mod small_message; +mod util; diff --git a/cove/src/euph/room.rs b/cove/src/euph/room.rs index 64ddfe6..17aafe4 100644 --- a/cove/src/euph/room.rs +++ b/cove/src/euph/room.rs @@ -1,21 +1,19 @@ // TODO Remove rl2dev-specific code -use std::convert::Infallible; -use std::time::Duration; +use std::{convert::Infallible, time::Duration}; -use euphoxide::api::packet::ParsedPacket; -use euphoxide::api::{ - Auth, AuthOption, Data, Log, Login, Logout, MessageId, Nick, Send, SendEvent, SendReply, Time, - UserId, +use euphoxide::{ + api::{ + Auth, AuthOption, Data, Log, Login, Logout, MessageId, Nick, Send, SendEvent, SendReply, + Time, UserId, packet::ParsedPacket, + }, + bot::instance::{ConnSnapshot, Event, Instance, InstanceConfig}, + conn::{self, ConnTx, Joined}, }; -use euphoxide::bot::instance::{ConnSnapshot, Event, Instance, InstanceConfig}; -use euphoxide::conn::{self, ConnTx, Joined}; -use log::{debug, error, info, warn}; -use tokio::select; -use tokio::sync::oneshot; +use log::{debug, info, warn}; +use tokio::{select, sync::oneshot}; -use crate::macros::logging_unwrap; -use crate::vault::EuphRoomVault; +use crate::{macros::logging_unwrap, vault::EuphRoomVault}; const LOG_INTERVAL: Duration = Duration::from_secs(10); diff --git a/cove/src/euph/small_message.rs b/cove/src/euph/small_message.rs index 994b0ae..20596fa 100644 --- a/cove/src/euph/small_message.rs +++ b/cove/src/euph/small_message.rs @@ -5,8 +5,7 @@ use euphoxide::api::{MessageId, Snowflake, Time}; use jiff::Timestamp; use toss::{Style, Styled}; -use crate::store::Msg; -use crate::ui::ChatMsg; +use crate::{store::Msg, ui::ChatMsg}; use super::util; diff --git a/cove/src/export.rs b/cove/src/export.rs index 0ad9414..80db7b6 100644 --- a/cove/src/export.rs +++ b/cove/src/export.rs @@ -1,13 +1,15 @@ //! Export logs from the vault to plain text files. +use std::{ + fs::File, + io::{self, BufWriter, Write}, +}; + +use crate::vault::{EuphRoomVault, EuphVault, RoomIdentifier}; + mod json; mod text; -use std::fs::File; -use std::io::{self, BufWriter, Write}; - -use crate::vault::{EuphRoomVault, EuphVault, RoomIdentifier}; - #[derive(Debug, Clone, Copy, clap::ValueEnum)] pub enum Format { /// Human-readable tree-structured messages. diff --git a/cove/src/export/text.rs b/cove/src/export/text.rs index 23a75d8..2ca6687 100644 --- a/cove/src/export/text.rs +++ b/cove/src/export/text.rs @@ -3,9 +3,7 @@ use std::io::Write; use euphoxide::api::MessageId; use unicode_width::UnicodeWidthStr; -use crate::euph::SmallMessage; -use crate::store::Tree; -use crate::vault::EuphRoomVault; +use crate::{euph::SmallMessage, store::Tree, vault::EuphRoomVault}; const TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; const TIME_EMPTY: &str = " "; diff --git a/cove/src/logger.rs b/cove/src/logger.rs index 5574960..940e1a9 100644 --- a/cove/src/logger.rs +++ b/cove/src/logger.rs @@ -1,6 +1,4 @@ -use std::convert::Infallible; -use std::sync::Arc; -use std::vec; +use std::{convert::Infallible, sync::Arc, vec}; use async_trait::async_trait; use crossterm::style::Stylize; @@ -10,8 +8,10 @@ use parking_lot::Mutex; use tokio::sync::mpsc; use toss::{Style, Styled}; -use crate::store::{Msg, MsgStore, Path, Tree}; -use crate::ui::ChatMsg; +use crate::{ + store::{Msg, MsgStore, Path, Tree}, + ui::ChatMsg, +}; #[derive(Debug, Clone)] pub struct LogMsg { diff --git a/cove/src/main.rs b/cove/src/main.rs index fe9a9c1..642b62e 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -1,6 +1,23 @@ // TODO Remove unnecessary Debug impls and compare compile times // TODO Invoke external notification command? +use std::path::PathBuf; + +use anyhow::Context; +use clap::Parser; +use cove_config::{Config, doc::Document}; +use directories::{BaseDirs, ProjectDirs}; +use log::info; +use tokio::sync::mpsc; +use toss::Terminal; + +use crate::{ + logger::Logger, + ui::Ui, + vault::Vault, + version::{NAME, VERSION}, +}; + mod euph; mod export; mod logger; @@ -11,22 +28,6 @@ mod util; mod vault; mod version; -use std::path::PathBuf; - -use anyhow::Context; -use clap::Parser; -use cove_config::Config; -use cove_config::doc::Document; -use directories::{BaseDirs, ProjectDirs}; -use log::info; -use tokio::sync::mpsc; -use toss::Terminal; - -use crate::logger::Logger; -use crate::ui::Ui; -use crate::vault::Vault; -use crate::version::{NAME, VERSION}; - #[derive(Debug, clap::Parser)] enum Command { /// Run the client interactively (default). diff --git a/cove/src/store.rs b/cove/src/store.rs index f6c85b7..f64f71e 100644 --- a/cove/src/store.rs +++ b/cove/src/store.rs @@ -1,7 +1,4 @@ -use std::collections::HashMap; -use std::fmt::Debug; -use std::hash::Hash; -use std::vec; +use std::{collections::HashMap, fmt::Debug, hash::Hash, vec}; use async_trait::async_trait; diff --git a/cove/src/ui.rs b/cove/src/ui.rs index 0263325..1c03834 100644 --- a/cove/src/ui.rs +++ b/cove/src/ui.rs @@ -1,3 +1,30 @@ +use std::{ + convert::Infallible, + io, + sync::{Arc, Weak}, + time::{Duration, Instant}, +}; + +use cove_config::Config; +use cove_input::InputEvent; +use jiff::tz::TimeZone; +use parking_lot::FairMutex; +use tokio::{ + sync::mpsc::{self, UnboundedReceiver, UnboundedSender, error::TryRecvError}, + task, +}; +use toss::{Terminal, WidgetExt, widgets::BoxedAsync}; + +use crate::{ + logger::{LogMsg, Logger}, + macros::logging_unwrap, + util::InfallibleExt, + vault::Vault, +}; + +pub use self::chat::ChatMsg; +use self::{chat::ChatState, rooms::Rooms, widgets::ListState}; + mod chat; mod euph; mod key_bindings; @@ -5,31 +32,6 @@ mod rooms; mod util; mod widgets; -use std::convert::Infallible; -use std::io; -use std::sync::{Arc, Weak}; -use std::time::{Duration, Instant}; - -use cove_config::Config; -use cove_input::InputEvent; -use jiff::tz::TimeZone; -use parking_lot::FairMutex; -use tokio::sync::mpsc::error::TryRecvError; -use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; -use tokio::task; -use toss::widgets::BoxedAsync; -use toss::{Terminal, WidgetExt}; - -use crate::logger::{LogMsg, Logger}; -use crate::macros::logging_unwrap; -use crate::util::InfallibleExt; -use crate::vault::Vault; - -pub use self::chat::ChatMsg; -use self::chat::ChatState; -use self::rooms::Rooms; -use self::widgets::ListState; - /// Time to spend batch processing events before redrawing the screen. const EVENT_PROCESSING_TIME: Duration = Duration::from_millis(1000 / 15); // 15 fps diff --git a/cove/src/ui/chat.rs b/cove/src/ui/chat.rs index 69f5e2b..405339b 100644 --- a/cove/src/ui/chat.rs +++ b/cove/src/ui/chat.rs @@ -1,24 +1,26 @@ +use cove_config::Keys; +use cove_input::InputEvent; +use jiff::{Timestamp, tz::TimeZone}; +use toss::{ + Styled, WidgetExt, + widgets::{BoxedAsync, EditorState}, +}; + +use crate::{ + store::{Msg, MsgStore}, + util, +}; + +use super::UiError; + +use self::{cursor::Cursor, tree::TreeViewState}; + mod blocks; mod cursor; mod renderer; mod tree; mod widgets; -use cove_config::Keys; -use cove_input::InputEvent; -use jiff::Timestamp; -use jiff::tz::TimeZone; -use toss::widgets::{BoxedAsync, EditorState}; -use toss::{Styled, WidgetExt}; - -use crate::store::{Msg, MsgStore}; -use crate::util; - -use self::cursor::Cursor; -use self::tree::TreeViewState; - -use super::UiError; - pub trait ChatMsg { fn time(&self) -> Option<Timestamp>; fn styled(&self) -> (Styled, Styled); diff --git a/cove/src/ui/chat/cursor.rs b/cove/src/ui/chat/cursor.rs index 561f4ed..87bd8fc 100644 --- a/cove/src/ui/chat/cursor.rs +++ b/cove/src/ui/chat/cursor.rs @@ -1,7 +1,6 @@ //! Common cursor movement logic. -use std::collections::HashSet; -use std::hash::Hash; +use std::{collections::HashSet, hash::Hash}; use crate::store::{Msg, MsgStore, Tree}; diff --git a/cove/src/ui/chat/tree.rs b/cove/src/ui/chat/tree.rs index 9ac31f8..043e109 100644 --- a/cove/src/ui/chat/tree.rs +++ b/cove/src/ui/chat/tree.rs @@ -2,27 +2,27 @@ // TODO Focusing on sub-trees -mod renderer; -mod scroll; -mod widgets; - use std::collections::HashSet; use async_trait::async_trait; use cove_config::Keys; use cove_input::InputEvent; use jiff::tz::TimeZone; -use toss::widgets::EditorState; -use toss::{AsyncWidget, Frame, Pos, Size, WidgetExt, WidthDb}; +use toss::{AsyncWidget, Frame, Pos, Size, WidgetExt, WidthDb, widgets::EditorState}; -use crate::store::{Msg, MsgStore}; -use crate::ui::{ChatMsg, UiError, util}; -use crate::util::InfallibleExt; +use crate::{ + store::{Msg, MsgStore}, + ui::{UiError, util}, + util::InfallibleExt, +}; + +use super::{ChatMsg, Reaction, cursor::Cursor}; use self::renderer::{TreeContext, TreeRenderer}; -use super::Reaction; -use super::cursor::Cursor; +mod renderer; +mod scroll; +mod widgets; pub struct TreeViewState<M: Msg, S: MsgStore<M>> { store: S, diff --git a/cove/src/ui/chat/tree/renderer.rs b/cove/src/ui/chat/tree/renderer.rs index 192e46c..142624e 100644 --- a/cove/src/ui/chat/tree/renderer.rs +++ b/cove/src/ui/chat/tree/renderer.rs @@ -1,19 +1,26 @@ //! A [`Renderer`] for message trees. -use std::collections::HashSet; -use std::convert::Infallible; +use std::{collections::HashSet, convert::Infallible}; use async_trait::async_trait; use jiff::tz::TimeZone; -use toss::widgets::{EditorState, Empty, Predrawn, Resize}; -use toss::{Size, Widget, WidthDb}; +use toss::{ + Size, Widget, WidthDb, + widgets::{EditorState, Empty, Predrawn, Resize}, +}; -use crate::store::{Msg, MsgStore, Tree}; -use crate::ui::ChatMsg; -use crate::ui::chat::blocks::{Block, Blocks, Range}; -use crate::ui::chat::cursor::Cursor; -use crate::ui::chat::renderer::{self, Renderer, overlaps}; -use crate::util::InfallibleExt; +use crate::{ + store::{Msg, MsgStore, Tree}, + ui::{ + ChatMsg, + chat::{ + blocks::{Block, Blocks, Range}, + cursor::Cursor, + renderer::{self, Renderer, overlaps}, + }, + }, + util::InfallibleExt, +}; use super::widgets; diff --git a/cove/src/ui/chat/tree/scroll.rs b/cove/src/ui/chat/tree/scroll.rs index 73e0e71..ab3ddae 100644 --- a/cove/src/ui/chat/tree/scroll.rs +++ b/cove/src/ui/chat/tree/scroll.rs @@ -1,12 +1,14 @@ -use toss::WidthDb; -use toss::widgets::EditorState; +use toss::{WidthDb, widgets::EditorState}; -use crate::store::{Msg, MsgStore}; -use crate::ui::ChatMsg; -use crate::ui::chat::cursor::Cursor; +use crate::{ + store::{Msg, MsgStore}, + ui::{ChatMsg, chat::cursor::Cursor}, +}; -use super::TreeViewState; -use super::renderer::{TreeContext, TreeRenderer}; +use super::{ + TreeViewState, + renderer::{TreeContext, TreeRenderer}, +}; impl<M, S> TreeViewState<M, S> where diff --git a/cove/src/ui/chat/tree/widgets.rs b/cove/src/ui/chat/tree/widgets.rs index bc807d7..d46920e 100644 --- a/cove/src/ui/chat/tree/widgets.rs +++ b/cove/src/ui/chat/tree/widgets.rs @@ -2,13 +2,19 @@ use std::convert::Infallible; use crossterm::style::Stylize; use jiff::tz::TimeZone; -use toss::widgets::{Boxed, EditorState, Join2, Join4, Join5, Text}; -use toss::{Style, Styled, WidgetExt}; +use toss::{ + Style, Styled, WidgetExt, + widgets::{Boxed, EditorState, Join2, Join4, Join5, Text}, +}; -use crate::store::Msg; -use crate::ui::ChatMsg; -use crate::ui::chat::widgets::{Indent, Seen, Time}; -use crate::util; +use crate::{ + store::Msg, + ui::{ + ChatMsg, + chat::widgets::{Indent, Seen, Time}, + }, + util, +}; pub const PLACEHOLDER: &str = "[...]"; diff --git a/cove/src/ui/chat/widgets.rs b/cove/src/ui/chat/widgets.rs index 43ad29e..e0e2fe5 100644 --- a/cove/src/ui/chat/widgets.rs +++ b/cove/src/ui/chat/widgets.rs @@ -2,8 +2,10 @@ use std::convert::Infallible; use crossterm::style::Stylize; use jiff::Zoned; -use toss::widgets::{Boxed, Empty, Text}; -use toss::{Frame, Pos, Size, Style, Widget, WidgetExt, WidthDb}; +use toss::{ + Frame, Pos, Size, Style, Widget, WidgetExt, WidthDb, + widgets::{Boxed, Empty, Text}, +}; use crate::util::InfallibleExt; diff --git a/cove/src/ui/euph/account.rs b/cove/src/ui/euph/account.rs index a982711..48731d9 100644 --- a/cove/src/ui/euph/account.rs +++ b/cove/src/ui/euph/account.rs @@ -1,14 +1,16 @@ use cove_config::Keys; use cove_input::InputEvent; use crossterm::style::Stylize; -use euphoxide::api::PersonalAccountView; -use euphoxide::conn; -use toss::widgets::{EditorState, Empty, Join3, Join4, Join5, Text}; -use toss::{Style, Widget, WidgetExt}; +use euphoxide::{api::PersonalAccountView, conn}; +use toss::{ + Style, Widget, WidgetExt, + widgets::{EditorState, Empty, Join3, Join4, Join5, Text}, +}; -use crate::euph::{self, Room}; -use crate::ui::widgets::Popup; -use crate::ui::{UiError, util}; +use crate::{ + euph::{self, Room}, + ui::{UiError, util, widgets::Popup}, +}; use super::popup::PopupResult; diff --git a/cove/src/ui/euph/auth.rs b/cove/src/ui/euph/auth.rs index 2fbc1c0..259e204 100644 --- a/cove/src/ui/euph/auth.rs +++ b/cove/src/ui/euph/auth.rs @@ -1,11 +1,11 @@ use cove_config::Keys; use cove_input::InputEvent; -use toss::Widget; -use toss::widgets::EditorState; +use toss::{Widget, widgets::EditorState}; -use crate::euph::Room; -use crate::ui::widgets::Popup; -use crate::ui::{UiError, util}; +use crate::{ + euph::Room, + ui::{UiError, util, widgets::Popup}, +}; use super::popup::PopupResult; diff --git a/cove/src/ui/euph/inspect.rs b/cove/src/ui/euph/inspect.rs index e2bcf33..b3c4e0e 100644 --- a/cove/src/ui/euph/inspect.rs +++ b/cove/src/ui/euph/inspect.rs @@ -1,13 +1,13 @@ use cove_config::Keys; use cove_input::InputEvent; use crossterm::style::Stylize; -use euphoxide::api::{Message, NickEvent, SessionView}; -use euphoxide::conn::SessionInfo; -use toss::widgets::Text; -use toss::{Style, Styled, Widget}; +use euphoxide::{ + api::{Message, NickEvent, SessionView}, + conn::SessionInfo, +}; +use toss::{Style, Styled, Widget, widgets::Text}; -use crate::ui::UiError; -use crate::ui::widgets::Popup; +use crate::ui::{UiError, widgets::Popup}; use super::popup::PopupResult; diff --git a/cove/src/ui/euph/links.rs b/cove/src/ui/euph/links.rs index b3e5fb4..14496a6 100644 --- a/cove/src/ui/euph/links.rs +++ b/cove/src/ui/euph/links.rs @@ -1,13 +1,16 @@ use cove_config::{Config, Keys}; use cove_input::InputEvent; -use crossterm::event::KeyCode; -use crossterm::style::Stylize; +use crossterm::{event::KeyCode, style::Stylize}; use linkify::{LinkFinder, LinkKind}; -use toss::widgets::{Join2, Text}; -use toss::{Style, Styled, Widget, WidgetExt}; +use toss::{ + Style, Styled, Widget, WidgetExt, + widgets::{Join2, Text}, +}; -use crate::ui::widgets::{ListBuilder, ListState, Popup}; -use crate::ui::{UiError, key_bindings, util}; +use crate::ui::{ + UiError, key_bindings, util, + widgets::{ListBuilder, ListState, Popup}, +}; use super::popup::PopupResult; diff --git a/cove/src/ui/euph/nick.rs b/cove/src/ui/euph/nick.rs index 91bdd10..1940fac 100644 --- a/cove/src/ui/euph/nick.rs +++ b/cove/src/ui/euph/nick.rs @@ -1,12 +1,12 @@ use cove_config::Keys; use cove_input::InputEvent; use euphoxide::conn::Joined; -use toss::widgets::EditorState; -use toss::{Style, Widget}; +use toss::{Style, Widget, widgets::EditorState}; -use crate::euph::{self, Room}; -use crate::ui::widgets::Popup; -use crate::ui::{UiError, util}; +use crate::{ + euph::{self, Room}, + ui::{UiError, util, widgets::Popup}, +}; use super::popup::PopupResult; diff --git a/cove/src/ui/euph/nick_list.rs b/cove/src/ui/euph/nick_list.rs index 8401c80..e1e4e3d 100644 --- a/cove/src/ui/euph/nick_list.rs +++ b/cove/src/ui/euph/nick_list.rs @@ -1,14 +1,22 @@ use std::iter; use crossterm::style::{Color, Stylize}; -use euphoxide::api::{NickEvent, SessionId, SessionType, SessionView, UserId}; -use euphoxide::conn::{Joined, SessionInfo}; -use toss::widgets::{Background, Text}; -use toss::{Style, Styled, Widget, WidgetExt}; +use euphoxide::{ + api::{NickEvent, SessionId, SessionType, SessionView, UserId}, + conn::{Joined, SessionInfo}, +}; +use toss::{ + Style, Styled, Widget, WidgetExt, + widgets::{Background, Text}, +}; -use crate::euph; -use crate::ui::UiError; -use crate::ui::widgets::{ListBuilder, ListState}; +use crate::{ + euph, + ui::{ + UiError, + widgets::{ListBuilder, ListState}, + }, +}; pub fn widget<'a>( list: &'a mut ListState<SessionId>, diff --git a/cove/src/ui/euph/popup.rs b/cove/src/ui/euph/popup.rs index e9d4671..3f8caaa 100644 --- a/cove/src/ui/euph/popup.rs +++ b/cove/src/ui/euph/popup.rs @@ -1,11 +1,9 @@ use std::io; use crossterm::style::Stylize; -use toss::widgets::Text; -use toss::{Style, Styled, Widget}; +use toss::{Style, Styled, Widget, widgets::Text}; -use crate::ui::UiError; -use crate::ui::widgets::Popup; +use crate::ui::{UiError, widgets::Popup}; pub enum RoomPopup { Error { description: String, reason: String }, diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index eafd789..7d4b49c 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -3,26 +3,40 @@ use std::collections::VecDeque; use cove_config::{Config, Keys}; use cove_input::InputEvent; use crossterm::style::Stylize; -use euphoxide::api::{Data, Message, MessageId, PacketType, SessionId}; -use euphoxide::bot::instance::{Event, ServerConfig}; -use euphoxide::conn::{self, Joined, Joining, SessionInfo}; +use euphoxide::{ + api::{Data, Message, MessageId, PacketType, SessionId}, + bot::instance::{Event, ServerConfig}, + conn::{self, Joined, Joining, SessionInfo}, +}; use jiff::tz::TimeZone; -use tokio::sync::oneshot::error::TryRecvError; -use tokio::sync::{mpsc, oneshot}; -use toss::widgets::{BoxedAsync, EditorState, Join2, Layer, Text}; -use toss::{Style, Styled, Widget, WidgetExt}; +use tokio::sync::{ + mpsc, + oneshot::{self, error::TryRecvError}, +}; +use toss::{ + Style, Styled, Widget, WidgetExt, + widgets::{BoxedAsync, EditorState, Join2, Layer, Text}, +}; -use crate::euph; -use crate::macros::logging_unwrap; -use crate::ui::chat::{ChatState, Reaction}; -use crate::ui::widgets::ListState; -use crate::ui::{UiError, UiEvent, util}; -use crate::vault::EuphRoomVault; +use crate::{ + euph, + macros::logging_unwrap, + ui::{ + UiError, UiEvent, + chat::{ChatState, Reaction}, + util, + widgets::ListState, + }, + vault::EuphRoomVault, +}; -use super::account::AccountUiState; -use super::links::LinksState; -use super::popup::{PopupResult, RoomPopup}; -use super::{auth, inspect, nick, nick_list}; +use super::{ + account::AccountUiState, + auth, inspect, + links::LinksState, + nick, nick_list, + popup::{PopupResult, RoomPopup}, +}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Focus { diff --git a/cove/src/ui/key_bindings.rs b/cove/src/ui/key_bindings.rs index de3c889..daedc16 100644 --- a/cove/src/ui/key_bindings.rs +++ b/cove/src/ui/key_bindings.rs @@ -5,11 +5,15 @@ use std::convert::Infallible; use cove_config::{Config, Keys}; use cove_input::{InputEvent, KeyBinding, KeyBindingInfo, KeyGroupInfo}; use crossterm::style::Stylize; -use toss::widgets::{Either2, Join2, Padding, Text}; -use toss::{Style, Styled, Widget, WidgetExt}; +use toss::{ + Style, Styled, Widget, WidgetExt, + widgets::{Either2, Join2, Padding, Text}, +}; -use super::widgets::{ListBuilder, ListState, Popup}; -use super::{UiError, util}; +use super::{ + UiError, util, + widgets::{ListBuilder, ListState, Popup}, +}; type Line = Either2<Text, Join2<Padding<Text>, Text>>; type Builder = ListBuilder<'static, Infallible, Line>; diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index f26defa..a6e7b34 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -1,34 +1,46 @@ -mod connect; -mod delete; - -use std::collections::hash_map::Entry; -use std::collections::{HashMap, HashSet}; -use std::iter; -use std::sync::{Arc, Mutex}; -use std::time::Duration; +use std::{ + collections::{HashMap, HashSet, hash_map::Entry}, + iter, + sync::{Arc, Mutex}, + time::Duration, +}; use cove_config::{Config, Keys, RoomsSortOrder}; use cove_input::InputEvent; use crossterm::style::Stylize; -use euphoxide::api::SessionType; -use euphoxide::bot::instance::{Event, ServerConfig}; -use euphoxide::conn::{self, Joined}; +use euphoxide::{ + api::SessionType, + bot::instance::{Event, ServerConfig}, + conn::{self, Joined}, +}; use jiff::tz::TimeZone; use tokio::sync::mpsc; -use toss::widgets::{BoxedAsync, Empty, Join2, Text}; -use toss::{Style, Styled, Widget, WidgetExt}; +use toss::{ + Style, Styled, Widget, WidgetExt, + widgets::{BoxedAsync, Empty, Join2, Text}, +}; -use crate::euph; -use crate::macros::logging_unwrap; -use crate::vault::{EuphVault, RoomIdentifier, Vault}; -use crate::version::{NAME, VERSION}; +use crate::{ + euph, + macros::logging_unwrap, + vault::{EuphVault, RoomIdentifier, Vault}, + version::{NAME, VERSION}, +}; -use self::connect::{ConnectResult, ConnectState}; -use self::delete::{DeleteResult, DeleteState}; +use super::{ + UiError, UiEvent, + euph::room::EuphRoom, + key_bindings, util, + widgets::{ListBuilder, ListState}, +}; -use super::euph::room::EuphRoom; -use super::widgets::{ListBuilder, ListState}; -use super::{UiError, UiEvent, key_bindings, util}; +use self::{ + connect::{ConnectResult, ConnectState}, + delete::{DeleteResult, DeleteState}, +}; + +mod connect; +mod delete; enum State { ShowList, diff --git a/cove/src/ui/rooms/connect.rs b/cove/src/ui/rooms/connect.rs index 4ad3c39..ce53775 100644 --- a/cove/src/ui/rooms/connect.rs +++ b/cove/src/ui/rooms/connect.rs @@ -1,12 +1,15 @@ use cove_config::Keys; use cove_input::InputEvent; use crossterm::style::Stylize; -use toss::widgets::{EditorState, Empty, Join2, Join3, Text}; -use toss::{Style, Styled, Widget, WidgetExt}; +use toss::{ + Style, Styled, Widget, WidgetExt, + widgets::{EditorState, Empty, Join2, Join3, Text}, +}; -use crate::ui::widgets::Popup; -use crate::ui::{UiError, util}; -use crate::vault::RoomIdentifier; +use crate::{ + ui::{UiError, util, widgets::Popup}, + vault::RoomIdentifier, +}; #[derive(Clone, Copy, PartialEq, Eq)] enum Focus { diff --git a/cove/src/ui/rooms/delete.rs b/cove/src/ui/rooms/delete.rs index d5b6884..aafaad8 100644 --- a/cove/src/ui/rooms/delete.rs +++ b/cove/src/ui/rooms/delete.rs @@ -1,12 +1,15 @@ use cove_config::Keys; use cove_input::InputEvent; use crossterm::style::Stylize; -use toss::widgets::{EditorState, Empty, Join2, Text}; -use toss::{Style, Styled, Widget, WidgetExt}; +use toss::{ + Style, Styled, Widget, WidgetExt, + widgets::{EditorState, Empty, Join2, Text}, +}; -use crate::ui::widgets::Popup; -use crate::ui::{UiError, util}; -use crate::vault::RoomIdentifier; +use crate::{ + ui::{UiError, util, widgets::Popup}, + vault::RoomIdentifier, +}; pub struct DeleteState { id: RoomIdentifier, diff --git a/cove/src/ui/widgets.rs b/cove/src/ui/widgets.rs index aed063a..c00d26e 100644 --- a/cove/src/ui/widgets.rs +++ b/cove/src/ui/widgets.rs @@ -1,5 +1,5 @@ -mod list; -mod popup; - pub use self::list::*; pub use self::popup::*; + +mod list; +mod popup; diff --git a/cove/src/ui/widgets/popup.rs b/cove/src/ui/widgets/popup.rs index 40b41cb..559e283 100644 --- a/cove/src/ui/widgets/popup.rs +++ b/cove/src/ui/widgets/popup.rs @@ -1,5 +1,7 @@ -use toss::widgets::{Background, Border, Desync, Float, Layer2, Padding, Text}; -use toss::{Frame, Size, Style, Styled, Widget, WidgetExt, WidthDb}; +use toss::{ + Frame, Size, Style, Styled, Widget, WidgetExt, WidthDb, + widgets::{Background, Border, Desync, Float, Layer2, Padding, Text}, +}; type Body<I> = Background<Border<Padding<I>>>; type Title = Float<Padding<Background<Padding<Text>>>>; diff --git a/cove/src/util.rs b/cove/src/util.rs index ff8a05a..c6a572c 100644 --- a/cove/src/util.rs +++ b/cove/src/util.rs @@ -1,5 +1,4 @@ -use std::convert::Infallible; -use std::env; +use std::{convert::Infallible, env}; use jiff::tz::TimeZone; diff --git a/cove/src/vault.rs b/cove/src/vault.rs index 512dfd2..05bd1a5 100644 --- a/cove/src/vault.rs +++ b/cove/src/vault.rs @@ -1,16 +1,14 @@ +use std::{fs, path::Path}; + +use rusqlite::Connection; +use vault::{Action, tokio::TokioVault}; + +pub use self::euph::{EuphRoomVault, EuphVault, RoomIdentifier}; + mod euph; mod migrate; mod prepare; -use std::fs; -use std::path::Path; - -use rusqlite::Connection; -use vault::Action; -use vault::tokio::TokioVault; - -pub use self::euph::{EuphRoomVault, EuphVault, RoomIdentifier}; - #[derive(Debug, Clone)] pub struct Vault { tokio_vault: TokioVault, diff --git a/cove/src/vault/euph.rs b/cove/src/vault/euph.rs index 3e98590..931091c 100644 --- a/cove/src/vault/euph.rs +++ b/cove/src/vault/euph.rs @@ -1,15 +1,18 @@ -use std::str::FromStr; -use std::{fmt, mem}; +use std::{fmt, mem, str::FromStr}; use async_trait::async_trait; use cookie::{Cookie, CookieJar}; use euphoxide::api::{Message, MessageId, SessionId, SessionView, Snowflake, Time, UserId}; -use rusqlite::types::{FromSql, FromSqlError, ToSqlOutput, Value, ValueRef}; -use rusqlite::{Connection, OptionalExtension, Row, ToSql, Transaction, named_params, params}; +use rusqlite::{ + Connection, OptionalExtension, Row, ToSql, Transaction, named_params, params, + types::{FromSql, FromSqlError, ToSqlOutput, Value, ValueRef}, +}; use vault::Action; -use crate::euph::SmallMessage; -use crate::store::{MsgStore, Path, Tree}; +use crate::{ + euph::SmallMessage, + store::{MsgStore, Path, Tree}, +}; /// Wrapper for [`Snowflake`] that implements useful rusqlite traits. struct WSnowflake(Snowflake); From d29e3e6651cf3a62c419c324a225e6202ffebfcf Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 21 Feb 2025 13:24:04 +0100 Subject: [PATCH 230/266] Set up GitHub CI --- .github/workflows/build.yml | 72 +++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..f02d5dd --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,72 @@ +# What software is installed by default: +# https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources + +name: build + +on: + push: + pull_request: + +defaults: + run: + shell: bash + +jobs: + build: + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + - macos-13 + runs-on: ${{ matrix.os }} + steps: + - name: Check out repo + uses: actions/checkout@v4 + + - name: Set up rust + run: rustup update + + - name: Build + run: cargo build --release + + - name: Record target triple + run: rustc -vV | awk '/^host/ { print $2 }' > target/release/host + + - name: Upload + uses: actions/upload-artifact@v4 + with: + name: cove-${{ matrix.os }} + path: | + target/release/cove + target/release/cove.exe + target/release/host + + release: + runs-on: ubuntu-latest + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + needs: + - build + permissions: + contents: write + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + + - name: Zip artifacts + run: | + chmod +x cove-ubuntu-latest/cove + chmod +x cove-windows-latest/cove.exe + chmod +x cove-macos-latest/cove + chmod +x cove-macos-13/cove + zip -jr "cove-$(cat cove-ubuntu-latest/host).zip" cove-ubuntu-latest/cove + zip -jr "cove-$(cat cove-windows-latest/host).zip" cove-windows-latest/cove.exe + zip -jr "cove-$(cat cove-macos-latest/host).zip" cove-macos-latest/cove + zip -jr "cove-$(cat cove-macos-13/host).zip" cove-macos-13/cove + + - name: Create new release + uses: softprops/action-gh-release@v2 + with: + body: Automated release, see [CHANGELOG.md](CHANGELOG.md) for more details. + files: "*.zip" From 6c884f3077e9f951c63b58fcbf6864f3065f6036 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 21 Feb 2025 19:15:33 +0100 Subject: [PATCH 231/266] Update RPIT lifetime bounds for the 2024 edition When the bound matches the implicit bound, i.e. when you'd just write impl ... + use<'_> then it can be omitted. My gut instinct is to always write the bound explicitly, but maybe that'll harm readability once I'm more used to how bounds work now. Anyways, always try to keep the bound as small as possible, ideally just impl ... + use<> --- cove/src/ui/euph/account.rs | 4 ++-- cove/src/ui/euph/auth.rs | 2 +- cove/src/ui/euph/links.rs | 2 +- cove/src/ui/euph/nick.rs | 2 +- cove/src/ui/rooms/connect.rs | 2 +- cove/src/ui/rooms/delete.rs | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cove/src/ui/euph/account.rs b/cove/src/ui/euph/account.rs index 48731d9..7aa776f 100644 --- a/cove/src/ui/euph/account.rs +++ b/cove/src/ui/euph/account.rs @@ -35,7 +35,7 @@ impl LoggedOut { } } - fn widget(&mut self) -> impl Widget<UiError> + '_ { + fn widget(&mut self) -> impl Widget<UiError> { let bold = Style::new().bold(); Join4::vertical( Text::new(("Not logged in", bold.yellow())).segment(), @@ -111,7 +111,7 @@ impl AccountUiState { } } - pub fn widget(&mut self) -> impl Widget<UiError> + '_ { + pub fn widget(&mut self) -> impl Widget<UiError> { let inner = match self { Self::LoggedOut(logged_out) => logged_out.widget().first2(), Self::LoggedIn(logged_in) => logged_in.widget().second2(), diff --git a/cove/src/ui/euph/auth.rs b/cove/src/ui/euph/auth.rs index 259e204..15f8fe1 100644 --- a/cove/src/ui/euph/auth.rs +++ b/cove/src/ui/euph/auth.rs @@ -13,7 +13,7 @@ pub fn new() -> EditorState { EditorState::new() } -pub fn widget(editor: &mut EditorState) -> impl Widget<UiError> + '_ { +pub fn widget(editor: &mut EditorState) -> impl Widget<UiError> { Popup::new( editor.widget().with_hidden_default_placeholder(), "Enter password", diff --git a/cove/src/ui/euph/links.rs b/cove/src/ui/euph/links.rs index 14496a6..a452669 100644 --- a/cove/src/ui/euph/links.rs +++ b/cove/src/ui/euph/links.rs @@ -38,7 +38,7 @@ impl LinksState { } } - pub fn widget(&mut self) -> impl Widget<UiError> + '_ { + pub fn widget(&mut self) -> impl Widget<UiError> { let style_selected = Style::new().black().on_white(); let mut list_builder = ListBuilder::new(); diff --git a/cove/src/ui/euph/nick.rs b/cove/src/ui/euph/nick.rs index 1940fac..707e992 100644 --- a/cove/src/ui/euph/nick.rs +++ b/cove/src/ui/euph/nick.rs @@ -14,7 +14,7 @@ pub fn new(joined: Joined) -> EditorState { EditorState::with_initial_text(joined.session.name) } -pub fn widget(editor: &mut EditorState) -> impl Widget<UiError> + '_ { +pub fn widget(editor: &mut EditorState) -> impl Widget<UiError> { let inner = editor .widget() .with_highlight(|s| euph::style_nick_exact(s, Style::new())); diff --git a/cove/src/ui/rooms/connect.rs b/cove/src/ui/rooms/connect.rs index ce53775..83a359e 100644 --- a/cove/src/ui/rooms/connect.rs +++ b/cove/src/ui/rooms/connect.rs @@ -84,7 +84,7 @@ impl ConnectState { ConnectResult::Unhandled } - pub fn widget(&mut self) -> impl Widget<UiError> + '_ { + pub fn widget(&mut self) -> impl Widget<UiError> { let room_style = Style::new().bold().blue(); let domain_style = Style::new().grey(); diff --git a/cove/src/ui/rooms/delete.rs b/cove/src/ui/rooms/delete.rs index aafaad8..baa96b1 100644 --- a/cove/src/ui/rooms/delete.rs +++ b/cove/src/ui/rooms/delete.rs @@ -47,7 +47,7 @@ impl DeleteState { DeleteResult::Unhandled } - pub fn widget(&mut self) -> impl Widget<UiError> + '_ { + pub fn widget(&mut self) -> impl Widget<UiError> { let warn_style = Style::new().bold().red(); let room_style = Style::new().bold().blue(); let text = Styled::new_plain("Are you sure you want to delete ") From bf11e055b6fff0e51f2c5f71d3b9e0ae2db26782 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 22 Feb 2025 12:33:59 +0100 Subject: [PATCH 232/266] Reformat changelog There should always be space around headlines and lists in markdown documents. --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6864032..f365f7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Procedure when bumping the version number: + 1. Update dependencies and flake in a separate commit 2. Set version number in `Cargo.toml` 3. Add new section in this changelog @@ -16,20 +17,24 @@ Procedure when bumping the version number: ## Unreleased ### Updated + - Documentation for `time_zone` config option ## v0.8.3 - 2024-05-20 ### Changed + - Updated list of emoji names ## v0.8.2 - 2024-04-25 ### Changed + - Renamed `json-stream` export format to `json-lines` (see <https://jsonlines.org/>) - Changed `json-lines` file extension from `.json` to `.jsonl` ### Fixed + - Crash when window is too small while empty message editor is visible - Mistakes in output and docs - Cove not cleaning up terminal state properly @@ -37,16 +42,19 @@ Procedure when bumping the version number: ## v0.8.1 - 2024-01-11 ### Added + - Support for setting window title - More information to room list heading - Key bindings for live caesar cipher de- and encoding ### Removed + - Key binding to open present page ## v0.8.0 - 2024-01-04 ### Added + - Support for multiple euph server domains - Support for `TZ` environment variable - `time_zone` config option @@ -56,6 +64,7 @@ Procedure when bumping the version number: - Welcome info box next to room list ### Changed + - The default euph domain is now https://euphoria.leet.nu/ everywhere - The config file format was changed to support multiple euph servers with different domains. Options previously located at `euph.rooms.*` should be reviewed and moved to `euph.servers."euphoria.leet.nu".rooms.*`. @@ -64,17 +73,20 @@ Procedure when bumping the version number: - Reduced connection timeout from 30 seconds to 10 seconds ### Fixed + - Room deletion popup accepting any room name - Duplicated key presses on Windows ## v0.7.1 - 2023-08-31 ### Changed + - Updated dependencies ## v0.7.0 - 2023-05-14 ### Added + - Auto-generated config documentation - in [CONFIG.md](CONFIG.md) - via `help-config` CLI command @@ -82,6 +94,7 @@ Procedure when bumping the version number: - `measure_widths` config option ### Changed + - Overhauled widget system and extracted generic widgets to [toss](https://github.com/Garmelon/toss) - Overhauled config system to support auto-generating documentation - Overhauled key binding system to make key bindings configurable @@ -95,15 +108,18 @@ Procedure when bumping the version number: ## v0.6.1 - 2023-04-10 ### Changed + - Improved JSON export performance - Always show rooms from config file in room list ### Fixed + - Rooms reconnecting instead of showing error popups ## v0.6.0 - 2023-04-04 ### Added + - Emoji support - `flake.nix`, making cove available as a nix flake - `json-stream` room export format @@ -111,31 +127,37 @@ Procedure when bumping the version number: - `--verbose` flag ### Changed + - Non-export info is now printed to stderr instead of stdout - Recognizes links without scheme (e.g. `euphoria.io` instead of `https://euphoria.io`) - Rooms waiting for reconnect are no longer sorted to bottom in default sort order ### Fixed + - Mentions not being stopped by `>` ## v0.5.2 - 2023-01-14 ### Added + - Key binding to open present page ### Changed + - Always connect to &rl2dev in ephemeral mode - Reduce amount of messages per &rl2dev log request ## v0.5.1 - 2022-11-27 ### Changed + - Increase reconnect delay to one minute - Print errors that occurred while cove was running more compactly ## v0.5.0 - 2022-09-26 ### Added + - Key bindings to navigate nick list - Room deletion confirmation popup - Message inspection popup @@ -144,10 +166,12 @@ Procedure when bumping the version number: - `rooms_sort_order` config option ### Changed + - Use nick changes to detect sessions for nick list - Support Unicode 15 ### Fixed + - Cursor being visible through popups - Cursor in lists when highlighted item moves off-screen - User disappearing from nick list when only one of their sessions disconnects @@ -155,6 +179,7 @@ Procedure when bumping the version number: ## v0.4.0 - 2022-09-01 ### Added + - Config file and `--config` cli option - `data_dir` config option - `ephemeral` config option @@ -170,14 +195,17 @@ Procedure when bumping the version number: - Key bindings to view and open links in a message ### Changed + - Some key bindings in the rooms list ### Fixed + - Rooms being stuck in "Connecting" state ## v0.3.0 - 2022-08-22 ### Added + - Account login and logout - Authentication dialog for password-protected rooms - Error popups in rooms when something goes wrong @@ -185,10 +213,12 @@ Procedure when bumping the version number: - Key binding to download more logs ### Changed + - Reduced amount of unnecessary redraws - Description of `export` CLI command ### Fixed + - Crash when connecting to nonexistent rooms - Crash when connecting to rooms that require authentication - Pasting multi-line strings into the editor @@ -196,15 +226,18 @@ Procedure when bumping the version number: ## v0.2.1 - 2022-08-11 ### Added + - Support for modifiers on special keys via the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/) ### Fixed + - Joining new rooms no longer crashes cove - Scrolling when exiting message editor ## v0.2.0 - 2022-08-10 ### Added + - New messages are now marked as unseen - Sub-trees can now be folded - Support for pasting text into editors @@ -217,10 +250,12 @@ Procedure when bumping the version number: - Support for exporting multiple/all rooms at once ### Changed + - Reorganized export command - Slowed down room history download speed ### Fixed + - Chat rendering when deleting and re-joining a room - Spacing in some popups From 866176dab64fc2a08b393dcc33076d05da38eafa Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 22 Feb 2025 12:30:38 +0100 Subject: [PATCH 233/266] Move cursor to new room in room list --- CHANGELOG.md | 1 + cove/src/ui/rooms.rs | 1 + cove/src/ui/widgets/list.rs | 6 ++++++ 3 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f365f7b..fb950a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Procedure when bumping the version number: ### Updated - Documentation for `time_zone` config option +- When connecting to a room using `n` in the room list, the cursor now moves to that room ## v0.8.3 - 2024-05-20 diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index a6e7b34..04bce02 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -589,6 +589,7 @@ impl Rooms { return true; } ConnectResult::Connect(room) => { + self.list.move_cursor_to_id(&room); self.connect_to_room(room.clone()).await; self.state = State::ShowRoom(room); return true; diff --git a/cove/src/ui/widgets/list.rs b/cove/src/ui/widgets/list.rs index bb27540..3d7c6c8 100644 --- a/cove/src/ui/widgets/list.rs +++ b/cove/src/ui/widgets/list.rs @@ -239,6 +239,12 @@ impl<Id: Clone + Eq> ListState<Id> { }) } + pub fn move_cursor_to_id(&mut self, id: &Id) { + if let Some(new_cursor) = self.selectable_of_id(id) { + self.move_cursor_to(new_cursor); + } + } + fn fix_cursor(&mut self) { let new_cursor = if let Some(cursor) = &self.cursor { self.selectable_of_id(&cursor.id) From e750f81b11395843da3ee0e44b477dfd9d1e67bb Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 22 Feb 2025 16:42:26 +0100 Subject: [PATCH 234/266] Drop once_cell dependency It's now part of the standard library :D --- Cargo.lock | 1 - Cargo.toml | 1 - cove/Cargo.toml | 1 - cove/src/euph/util.rs | 5 +++-- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e77a3ad..6b262c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -344,7 +344,6 @@ dependencies = [ "jiff", "linkify", "log", - "once_cell", "open", "parking_lot", "rusqlite", diff --git a/Cargo.toml b/Cargo.toml index a58b43d..93e13c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ edit = "0.1.5" jiff = "0.2.1" linkify = "0.10.0" log = { version = "0.4.25", features = ["std"] } -once_cell = "1.20.2" open = "5.3.2" parking_lot = "0.12.3" proc-macro2 = "1.0.93" diff --git a/cove/Cargo.toml b/cove/Cargo.toml index 0ca2a2f..3a60a5d 100644 --- a/cove/Cargo.toml +++ b/cove/Cargo.toml @@ -17,7 +17,6 @@ euphoxide.workspace = true jiff.workspace = true linkify.workspace = true log.workspace = true -once_cell.workspace = true open.workspace = true parking_lot.workspace = true rusqlite.workspace = true diff --git a/cove/src/euph/util.rs b/cove/src/euph/util.rs index fdf11a3..aff2192 100644 --- a/cove/src/euph/util.rs +++ b/cove/src/euph/util.rs @@ -1,9 +1,10 @@ +use std::sync::LazyLock; + use crossterm::style::{Color, Stylize}; use euphoxide::Emoji; -use once_cell::sync::Lazy; use toss::{Style, Styled}; -pub static EMOJI: Lazy<Emoji> = Lazy::new(Emoji::load); +pub static EMOJI: LazyLock<Emoji> = LazyLock::new(Emoji::load); /// Convert HSL to RGB following [this approach from wikipedia][1]. /// From 2fa1bec421ec9c9f621cf660bd7684924e6cf206 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 04:24:06 +0100 Subject: [PATCH 235/266] Remove special rl2dev code --- CHANGELOG.md | 4 ++++ cove/src/euph/room.rs | 22 +++------------------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb950a5..2335f41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ Procedure when bumping the version number: - Documentation for `time_zone` config option - When connecting to a room using `n` in the room list, the cursor now moves to that room +### Removed + +- Special handling of &rl2dev + ## v0.8.3 - 2024-05-20 ### Changed diff --git a/cove/src/euph/room.rs b/cove/src/euph/room.rs index 17aafe4..a4e29cf 100644 --- a/cove/src/euph/room.rs +++ b/cove/src/euph/room.rs @@ -1,5 +1,3 @@ -// TODO Remove rl2dev-specific code - use std::{convert::Infallible, time::Duration}; use euphoxide::{ @@ -71,20 +69,13 @@ impl Room { where F: Fn(Event) + std::marker::Send + Sync + 'static, { - // &rl2dev's message history is broken and requesting old messages past - // a certain point results in errors. Cove should not keep retrying log - // requests when hitting that limit, so &rl2dev is always opened in - // ephemeral mode. - let is_rl2dev = vault.room().domain == "euphoria.io" && vault.room().name == "rl2dev"; - let ephemeral = vault.vault().vault().ephemeral() || is_rl2dev; - Self { - vault, - ephemeral, + ephemeral: vault.vault().vault().ephemeral(), instance: instance_config.build(on_event), state: State::Disconnected, last_msg_id: None, log_request_canary: None, + vault, } } @@ -192,14 +183,7 @@ impl Room { debug!("{:?}: requesting logs", vault.room()); - // &rl2dev's message history is broken and requesting old messages past - // a certain point results in errors. By reducing the amount of messages - // in each log request, we can get closer to this point. Since &rl2dev - // is fairly low in activity, this should be fine. - let is_rl2dev = vault.room().domain == "euphoria.io" && vault.room().name == "rl2dev"; - let n = if is_rl2dev { 50 } else { 1000 }; - - let _ = conn_tx.send(Log { n, before }).await; + let _ = conn_tx.send(Log { n: 1000, before }).await; // The code handling incoming events and replies also handles // `LogReply`s, so we don't need to do anything special here. } From 900a686d0deab4ce64b1b4a4f78824af8eea0f71 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 04:28:21 +0100 Subject: [PATCH 236/266] Fix changelog heading "Updated" does not conform to Keep a Changelog. --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2335f41..a4339bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,9 +16,9 @@ Procedure when bumping the version number: ## Unreleased -### Updated +### Changed -- Documentation for `time_zone` config option +- Updated documentation for `time_zone` config option - When connecting to a room using `n` in the room list, the cursor now moves to that room ### Removed From 17185ea5362c8f9b46f9a12a3e55a51faffc5840 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 18:31:20 +0100 Subject: [PATCH 237/266] Add unicode-based grapheme width estimation method --- CHANGELOG.md | 6 ++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- cove-config/src/lib.rs | 32 +++++++++++++++++++++++++++----- cove/src/main.rs | 21 +++++++++++++++++++++ 5 files changed, 56 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4339bb..433634a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ Procedure when bumping the version number: ## Unreleased +### Added + +- Unicode-based grapheme width estimation method + - `width_estimation_method` config option + - `--width-estimation-method` option + ### Changed - Updated documentation for `time_zone` config option diff --git a/Cargo.lock b/Cargo.lock index 6b262c4..86ebb73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1647,7 +1647,7 @@ dependencies = [ [[package]] name = "toss" version = "0.3.1" -source = "git+https://github.com/Garmelon/toss.git?tag=v0.3.1#be7eff0979e0e95d070e7c9cea42c328ffd04cc4" +source = "git+https://github.com/Garmelon/toss.git?rev=423dd100c1360decffc5107ea4757d751ac0f4db#423dd100c1360decffc5107ea4757d751ac0f4db" dependencies = [ "async-trait", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 93e13c4..f8445e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ features = ["bot"] [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" -tag = "v0.3.1" +rev = "423dd100c1360decffc5107ea4757d751ac0f4db" [workspace.dependencies.vault] git = "https://github.com/Garmelon/vault.git" diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 0d350ed..85b418e 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -5,7 +5,7 @@ use std::{ }; use doc::Document; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; pub use crate::{euph::*, keys::*}; @@ -21,6 +21,14 @@ pub enum Error { Toml(#[from] toml::de::Error), } +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, Document)] +#[serde(rename_all = "snake_case")] +pub enum WidthEstimationMethod { + #[default] + Legacy, + Unicode, +} + #[derive(Debug, Default, Deserialize, Document)] pub struct Config { /// The directory that cove stores its data in when not running in ephemeral @@ -41,12 +49,26 @@ pub struct Config { #[serde(default)] pub ephemeral: bool, - /// Whether to measure the width of characters as displayed by the terminal - /// emulator instead of guessing the width. + /// How to estimate the width of graphemes (i.e. characters) as displayed by + /// the terminal emulator. + /// + /// `"legacy"`: Use a legacy method that should mostly work on most terminal + /// emulators. This method will never be correct in all cases since every + /// terminal emulator handles grapheme widths slightly differently. However, + /// those cases are usually rare (unless you view a lot of emoji). + /// + /// `"unicode"`: Use the unicode standard in a best-effort manner to + /// determine grapheme widths. + /// + /// This method is used when `measure_widths` is set to `false`. + #[serde(default)] + pub width_estimation_method: WidthEstimationMethod, + + /// Whether to measure the width of graphemes (i.e. characters) as displayed + /// by the terminal emulator instead of estimating the width. /// /// Enabling this makes rendering a bit slower but more accurate. The screen - /// might also flash when encountering new characters (or, more accurately, - /// graphemes). + /// might also flash when encountering new graphemes. /// /// See also the `--measure-widths` command line option. #[serde(default)] diff --git a/cove/src/main.rs b/cove/src/main.rs index 642b62e..51bc502 100644 --- a/cove/src/main.rs +++ b/cove/src/main.rs @@ -46,6 +46,12 @@ enum Command { HelpConfig, } +#[derive(Debug, Clone, Copy, clap::ValueEnum)] +enum WidthEstimationMethod { + Legacy, + Unicode, +} + impl Default for Command { fn default() -> Self { Self::Run @@ -79,6 +85,11 @@ struct Args { #[arg(long, short)] offline: bool, + /// Method for estimating the width of characters as displayed by the + /// terminal emulator. + #[arg(long, short)] + width_estimation_method: Option<WidthEstimationMethod>, + /// Measure the width of characters as displayed by the terminal emulator /// instead of guessing the width. #[arg(long, short)] @@ -114,6 +125,12 @@ fn update_config_with_args(config: &mut Config, args: &Args) { } config.ephemeral |= args.ephemeral; + if let Some(method) = args.width_estimation_method { + config.width_estimation_method = match method { + WidthEstimationMethod::Legacy => cove_config::WidthEstimationMethod::Legacy, + WidthEstimationMethod::Unicode => cove_config::WidthEstimationMethod::Unicode, + } + } config.measure_widths |= args.measure_widths; config.offline |= args.offline; } @@ -182,6 +199,10 @@ async fn run( let mut terminal = Terminal::new()?; terminal.set_measuring(config.measure_widths); + terminal.set_width_estimation_method(match config.width_estimation_method { + cove_config::WidthEstimationMethod::Legacy => toss::WidthEstimationMethod::Legacy, + cove_config::WidthEstimationMethod::Unicode => toss::WidthEstimationMethod::Unicode, + }); Ui::run(config, tz, &mut terminal, vault.clone(), logger, logger_rx).await?; drop(terminal); From 8040b82ff15909aa2b6f169b9feb017e00105167 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 20:22:04 +0100 Subject: [PATCH 238/266] Refactor euph message content highlighting --- cove/src/euph.rs | 2 + cove/src/euph/highlight.rs | 203 ++++++++++++++++++++++++++++++++ cove/src/euph/small_message.rs | 206 +-------------------------------- 3 files changed, 210 insertions(+), 201 deletions(-) create mode 100644 cove/src/euph/highlight.rs diff --git a/cove/src/euph.rs b/cove/src/euph.rs index d1fd872..77bf1db 100644 --- a/cove/src/euph.rs +++ b/cove/src/euph.rs @@ -1,7 +1,9 @@ +pub use highlight::*; pub use room::*; pub use small_message::*; pub use util::*; +mod highlight; mod room; mod small_message; mod util; diff --git a/cove/src/euph/highlight.rs b/cove/src/euph/highlight.rs new file mode 100644 index 0000000..e60a302 --- /dev/null +++ b/cove/src/euph/highlight.rs @@ -0,0 +1,203 @@ +use std::ops::Range; + +use crossterm::style::Stylize; +use toss::{Style, Styled}; + +use crate::euph::util; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SpanType { + Mention, + Room, + Emoji, +} + +fn nick_char(ch: char) -> bool { + // Closely following the heim mention regex: + // https://github.com/euphoria-io/heim/blob/978c921063e6b06012fc8d16d9fbf1b3a0be1191/client/lib/stores/chat.js#L14-L15 + // `>` has been experimentally confirmed to delimit mentions as well. + match ch { + ',' | '.' | '!' | '?' | ';' | '&' | '<' | '>' | '\'' | '"' => false, + _ => !ch.is_whitespace(), + } +} + +fn room_char(ch: char) -> bool { + // Basically just \w, see also + // https://github.com/euphoria-io/heim/blob/978c921063e6b06012fc8d16d9fbf1b3a0be1191/client/lib/ui/MessageText.js#L66 + ch.is_ascii_alphanumeric() || ch == '_' +} + +struct SpanFinder<'a> { + content: &'a str, + + span: Option<(SpanType, usize)>, + room_or_mention_possible: bool, + + result: Vec<(SpanType, Range<usize>)>, +} + +impl<'a> SpanFinder<'a> { + fn is_valid_span(&self, span: SpanType, range: Range<usize>) -> bool { + let text = &self.content[range.start..range.end]; + match span { + SpanType::Mention => range.len() > 1 && text.starts_with('@'), + SpanType::Room => range.len() > 1 && text.starts_with('&'), + SpanType::Emoji => { + if range.len() <= 2 { + return false; + } + + let Some(name) = Some(text) + .and_then(|it| it.strip_prefix(':')) + .and_then(|it| it.strip_suffix(':')) + else { + return false; + }; + + util::EMOJI.get(name).is_some() + } + } + } + + fn close_span(&mut self, end: usize) { + let Some((span, start)) = self.span else { + return; + }; + if self.is_valid_span(span, start..end) { + self.result.push((span, start..end)); + } + self.span = None; + } + + fn open_span(&mut self, span: SpanType, start: usize) { + self.close_span(start); + self.span = Some((span, start)) + } + + fn step(&mut self, idx: usize, char: char) { + match (char, self.span) { + ('@', _) if self.room_or_mention_possible => self.open_span(SpanType::Mention, idx), + ('&', _) if self.room_or_mention_possible => self.open_span(SpanType::Room, idx), + (':', None) => self.open_span(SpanType::Emoji, idx), + (':', Some((SpanType::Emoji, _))) => self.close_span(idx + 1), + (c, Some((SpanType::Mention, _))) if !nick_char(c) => self.close_span(idx), + (c, Some((SpanType::Room, _))) if !room_char(c) => self.close_span(idx), + _ => {} + } + + // More permissive than the heim web client + self.room_or_mention_possible = !char.is_alphanumeric(); + } + + fn find(content: &'a str) -> Vec<(SpanType, Range<usize>)> { + let mut this = Self { + content, + span: None, + room_or_mention_possible: true, + result: vec![], + }; + + for (idx, char) in content.char_indices() { + this.step(idx, char); + } + + this.close_span(content.len()); + + this.result + } +} + +pub fn find_spans(content: &str) -> Vec<(SpanType, Range<usize>)> { + SpanFinder::find(content) +} + +/// Highlight spans in a string. +/// +/// The list of spans must be non-overlapping and in ascending order. +/// +/// If `exact` is specified, colon-delimited emoji are not replaced with their +/// unicode counterparts. +pub fn apply_spans( + content: &str, + spans: &[(SpanType, Range<usize>)], + base: Style, + exact: bool, +) -> Styled { + let mut result = Styled::default(); + let mut i = 0; + + for (span, range) in spans { + assert!(i <= range.start); + assert!(range.end <= content.len()); + + if i < range.start { + result = result.then_plain(&content[i..range.start]); + } + + let text = &content[range.start..range.end]; + result = match span { + SpanType::Mention if exact => result.and_then(util::style_nick_exact(text, base)), + SpanType::Mention => result.and_then(util::style_nick(text, base)), + SpanType::Room => result.then(text, base.blue().bold()), + SpanType::Emoji if exact => result.then(text, base.magenta()), + SpanType::Emoji => { + let name = text.strip_prefix(':').unwrap_or(text); + let name = name.strip_suffix(':').unwrap_or(name); + if let Some(Some(replacement)) = util::EMOJI.get(name) { + result.then_plain(replacement) + } else { + result.then(name, base.magenta()) + } + } + }; + + i = range.end; + } + + if i < content.len() { + result = result.then_plain(&content[i..]); + } + + result +} + +/// Highlight an euphoria message's content. +/// +/// If `exact` is specified, colon-delimited emoji are not replaced with their +/// unicode counterparts. +pub fn highlight(content: &str, base: Style, exact: bool) -> Styled { + apply_spans(content, &find_spans(content), base, exact) +} + +#[cfg(test)] +mod tests { + + use crate::euph::SpanType; + + use super::find_spans; + + #[test] + fn mentions() { + assert_eq!(find_spans("@foo"), vec![(SpanType::Mention, 0..4)]); + assert_eq!(find_spans("a @foo b"), vec![(SpanType::Mention, 2..6)]); + assert_eq!(find_spans("@@foo@"), vec![(SpanType::Mention, 1..6)]); + assert_eq!(find_spans("a @b@c d"), vec![(SpanType::Mention, 2..6)]); + assert_eq!( + find_spans("a @b @c d"), + vec![(SpanType::Mention, 2..4), (SpanType::Mention, 5..7)] + ); + } + + #[test] + fn rooms() { + assert_eq!(find_spans("&foo"), vec![(SpanType::Room, 0..4)]); + assert_eq!(find_spans("a &foo b"), vec![(SpanType::Room, 2..6)]); + assert_eq!(find_spans("&&foo&"), vec![(SpanType::Room, 1..5)]); + assert_eq!(find_spans("a &b&c d"), vec![(SpanType::Room, 2..4)]); + assert_eq!( + find_spans("a &b &c d"), + vec![(SpanType::Room, 2..4), (SpanType::Room, 5..7)] + ); + } +} diff --git a/cove/src/euph/small_message.rs b/cove/src/euph/small_message.rs index 20596fa..003cd40 100644 --- a/cove/src/euph/small_message.rs +++ b/cove/src/euph/small_message.rs @@ -1,5 +1,3 @@ -use std::mem; - use crossterm::style::Stylize; use euphoxide::api::{MessageId, Snowflake, Time}; use jiff::Timestamp; @@ -7,200 +5,6 @@ use toss::{Style, Styled}; use crate::{store::Msg, ui::ChatMsg}; -use super::util; - -fn nick_char(ch: char) -> bool { - // Closely following the heim mention regex: - // https://github.com/euphoria-io/heim/blob/978c921063e6b06012fc8d16d9fbf1b3a0be1191/client/lib/stores/chat.js#L14-L15 - // `>` has been experimentally confirmed to delimit mentions as well. - match ch { - ',' | '.' | '!' | '?' | ';' | '&' | '<' | '>' | '\'' | '"' => false, - _ => !ch.is_whitespace(), - } -} - -fn room_char(ch: char) -> bool { - // Basically just \w, see also - // https://github.com/euphoria-io/heim/blob/978c921063e6b06012fc8d16d9fbf1b3a0be1191/client/lib/ui/MessageText.js#L66 - ch.is_ascii_alphanumeric() || ch == '_' -} - -enum Span { - Nothing, - Mention, - Room, - Emoji, -} - -struct Highlighter<'a> { - content: &'a str, - base_style: Style, - exact: bool, - - span: Span, - span_start: usize, - room_or_mention_possible: bool, - - result: Styled, -} - -impl<'a> Highlighter<'a> { - /// Does *not* guarantee `self.span_start == idx` after running! - fn close_mention(&mut self, idx: usize) { - let span_length = idx.saturating_sub(self.span_start); - if span_length <= 1 { - // We can repurpose the current span - self.span = Span::Nothing; - return; - } - - let text = &self.content[self.span_start..idx]; // Includes @ - self.result = mem::take(&mut self.result).and_then(if self.exact { - util::style_nick_exact(text, self.base_style) - } else { - util::style_nick(text, self.base_style) - }); - - self.span = Span::Nothing; - self.span_start = idx; - } - - /// Does *not* guarantee `self.span_start == idx` after running! - fn close_room(&mut self, idx: usize) { - let span_length = idx.saturating_sub(self.span_start); - if span_length <= 1 { - // We can repurpose the current span - self.span = Span::Nothing; - return; - } - - self.result = mem::take(&mut self.result).then( - &self.content[self.span_start..idx], - self.base_style.blue().bold(), - ); - - self.span = Span::Nothing; - self.span_start = idx; - } - - // Warning: `idx` is the index of the closing colon. - fn close_emoji(&mut self, idx: usize) { - let name = &self.content[self.span_start + 1..idx]; - if let Some(replace) = util::EMOJI.get(name) { - match replace { - Some(replace) if !self.exact => { - self.result = mem::take(&mut self.result).then(replace, self.base_style); - } - _ => { - let text = &self.content[self.span_start..=idx]; - let style = self.base_style.magenta(); - self.result = mem::take(&mut self.result).then(text, style); - } - } - - self.span = Span::Nothing; - self.span_start = idx + 1; - } else { - self.close_plain(idx); - self.span = Span::Emoji; - } - } - - /// Guarantees `self.span_start == idx` after running. - fn close_plain(&mut self, idx: usize) { - if self.span_start == idx { - // Span has length 0 - return; - } - - self.result = - mem::take(&mut self.result).then(&self.content[self.span_start..idx], self.base_style); - - self.span = Span::Nothing; - self.span_start = idx; - } - - fn close_span_before_current_char(&mut self, idx: usize, char: char) { - match self.span { - Span::Mention if !nick_char(char) => self.close_mention(idx), - Span::Room if !room_char(char) => self.close_room(idx), - Span::Emoji if char == '&' || char == '@' => { - self.span = Span::Nothing; - } - _ => {} - } - } - - fn update_span_with_current_char(&mut self, idx: usize, char: char) { - match self.span { - Span::Nothing if char == '@' && self.room_or_mention_possible => { - self.close_plain(idx); - self.span = Span::Mention; - } - Span::Nothing if char == '&' && self.room_or_mention_possible => { - self.close_plain(idx); - self.span = Span::Room; - } - Span::Nothing if char == ':' => { - self.close_plain(idx); - self.span = Span::Emoji; - } - Span::Emoji if char == ':' => self.close_emoji(idx), - _ => {} - } - } - - fn close_final_span(&mut self) { - let idx = self.content.len(); - if self.span_start >= idx { - return; // Span has no contents - } - - match self.span { - Span::Mention => self.close_mention(idx), - Span::Room => self.close_room(idx), - _ => {} - } - - self.close_plain(idx); - } - - fn step(&mut self, idx: usize, char: char) { - if self.span_start < idx { - self.close_span_before_current_char(idx, char); - } - - self.update_span_with_current_char(idx, char); - - // More permissive than the heim web client - self.room_or_mention_possible = !char.is_alphanumeric(); - } - - fn highlight(content: &'a str, base_style: Style, exact: bool) -> Styled { - let mut this = Self { - content: if exact { content } else { content.trim() }, - base_style, - exact, - span: Span::Nothing, - span_start: 0, - room_or_mention_possible: true, - result: Styled::default(), - }; - - for (idx, char) in (if exact { content } else { content.trim() }).char_indices() { - this.step(idx, char); - } - - this.close_final_span(); - - this.result - } -} - -fn highlight_content(content: &str, base_style: Style, exact: bool) -> Styled { - Highlighter::highlight(content, base_style, exact) -} - #[derive(Debug, Clone)] pub struct SmallMessage { pub id: MessageId, @@ -221,22 +25,22 @@ fn style_me() -> Style { fn styled_nick(nick: &str) -> Styled { Styled::new_plain("[") - .and_then(util::style_nick(nick, Style::new())) + .and_then(super::style_nick(nick, Style::new())) .then_plain("]") } fn styled_nick_me(nick: &str) -> Styled { let style = style_me(); - Styled::new("*", style).and_then(util::style_nick(nick, style)) + Styled::new("*", style).and_then(super::style_nick(nick, style)) } fn styled_content(content: &str) -> Styled { - highlight_content(content.trim(), Style::new(), false) + super::highlight(content.trim(), Style::new(), false) } fn styled_content_me(content: &str) -> Styled { let style = style_me(); - highlight_content(content.trim(), style, false).then("*", style) + super::highlight(content.trim(), style, false).then("*", style) } fn styled_editor_content(content: &str) -> Styled { @@ -245,7 +49,7 @@ fn styled_editor_content(content: &str) -> Styled { } else { Style::new() }; - highlight_content(content, style, true) + super::highlight(content, style, true) } impl Msg for SmallMessage { From bf9a9d640ba7a15d37dc906bfabbb21b4e316629 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 21:32:44 +0100 Subject: [PATCH 239/266] Navigate to rooms using message links list --- CHANGELOG.md | 1 + cove/src/ui/euph/links.rs | 117 +++++++++++++++++++++++++------------- cove/src/ui/euph/popup.rs | 1 + cove/src/ui/euph/room.rs | 43 +++++++++++--- cove/src/ui/rooms.rs | 13 ++++- 5 files changed, 123 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 433634a..f75a3d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Procedure when bumping the version number: - Unicode-based grapheme width estimation method - `width_estimation_method` config option - `--width-estimation-method` option +- Room links are now included in the `I` message links list ### Changed diff --git a/cove/src/ui/euph/links.rs b/cove/src/ui/euph/links.rs index a452669..20e2219 100644 --- a/cove/src/ui/euph/links.rs +++ b/cove/src/ui/euph/links.rs @@ -7,16 +7,25 @@ use toss::{ widgets::{Join2, Text}, }; -use crate::ui::{ - UiError, key_bindings, util, - widgets::{ListBuilder, ListState, Popup}, +use crate::{ + euph::{self, SpanType}, + ui::{ + UiError, key_bindings, util, + widgets::{ListBuilder, ListState, Popup}, + }, }; use super::popup::PopupResult; +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +enum Link { + Url(String), + Room(String), +} + pub struct LinksState { config: &'static Config, - links: Vec<String>, + links: Vec<Link>, list: ListState<usize>, } @@ -24,12 +33,34 @@ const NUMBER_KEYS: [char; 10] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0 impl LinksState { pub fn new(config: &'static Config, content: &str) -> Self { - let links = LinkFinder::new() + let mut links = vec![]; + + // Collect URL-like links + for link in LinkFinder::new() .url_must_have_scheme(false) .kinds(&[LinkKind::Url]) .links(content) - .map(|l| l.as_str().to_string()) - .collect(); + { + links.push(( + link.start(), + link.end(), + Link::Url(link.as_str().to_string()), + )); + } + + // Collect room links + for (span, range) in euph::find_spans(content) { + if span == SpanType::Room { + let name = &content[range.start + 1..range.end]; + links.push((range.start, range.end, Link::Room(name.to_string()))); + } + } + + links.sort(); + let links = links + .into_iter() + .map(|(_, _, link)| link) + .collect::<Vec<_>>(); Self { config, @@ -49,29 +80,29 @@ impl LinksState { for (id, link) in self.links.iter().enumerate() { let link = link.clone(); - if let Some(&number_key) = NUMBER_KEYS.get(id) { - list_builder.add_sel(id, move |selected| { - let text = if selected { - Styled::new(format!("[{number_key}]"), style_selected.bold()) - .then(" ", style_selected) - .then(link, style_selected) - } else { - Styled::new(format!("[{number_key}]"), Style::new().dark_grey().bold()) - .then_plain(" ") - .then_plain(link) - }; - Text::new(text) - }); - } else { - list_builder.add_sel(id, move |selected| { - let text = if selected { - Styled::new(format!(" {link}"), style_selected) - } else { - Styled::new_plain(format!(" {link}")) - }; - Text::new(text) - }); - } + list_builder.add_sel(id, move |selected| { + let mut text = Styled::default(); + + // Number key indicator + text = match NUMBER_KEYS.get(id) { + None if selected => text.then(" ", style_selected), + None => text.then_plain(" "), + Some(key) if selected => text.then(format!("[{key}] "), style_selected.bold()), + Some(key) => text.then(format!("[{key}] "), Style::new().dark_grey().bold()), + }; + + // The link itself + text = match link { + Link::Url(url) if selected => text.then(url, style_selected), + Link::Url(url) => text.then_plain(url), + Link::Room(name) if selected => { + text.then(format!("&{name}"), style_selected.bold()) + } + Link::Room(name) => text.then(format!("&{name}"), Style::new().blue().bold()), + }; + + Text::new(text) + }); } let hint_style = Style::new().grey().italic(); @@ -95,18 +126,24 @@ impl LinksState { } fn open_link_by_id(&self, id: usize) -> PopupResult { - if let Some(link) = self.links.get(id) { - // The `http://` or `https://` schema is necessary for open::that to - // successfully open the link in the browser. - let link = if link.starts_with("http://") || link.starts_with("https://") { - link.clone() - } else { - format!("https://{link}") - }; + match self.links.get(id) { + Some(Link::Url(url)) => { + // The `http://` or `https://` schema is necessary for + // open::that to successfully open the link in the browser. + let link = if url.starts_with("http://") || url.starts_with("https://") { + url.clone() + } else { + format!("https://{url}") + }; - if let Err(error) = open::that(&link) { - return PopupResult::ErrorOpeningLink { link, error }; + if let Err(error) = open::that(&link) { + return PopupResult::ErrorOpeningLink { link, error }; + } } + + Some(Link::Room(name)) => return PopupResult::SwitchToRoom { name: name.clone() }, + + _ => {} } PopupResult::Handled } diff --git a/cove/src/ui/euph/popup.rs b/cove/src/ui/euph/popup.rs index 3f8caaa..c434fb6 100644 --- a/cove/src/ui/euph/popup.rs +++ b/cove/src/ui/euph/popup.rs @@ -35,5 +35,6 @@ pub enum PopupResult { NotHandled, Handled, Close, + SwitchToRoom { name: String }, ErrorOpeningLink { link: String, error: io::Error }, } diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index 7d4b49c..83d7e96 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -27,7 +27,7 @@ use crate::{ util, widgets::ListState, }, - vault::EuphRoomVault, + vault::{EuphRoomVault, RoomIdentifier}, }; use super::{ @@ -500,18 +500,22 @@ impl EuphRoom { false } - pub async fn handle_input_event(&mut self, event: &mut InputEvent<'_>, keys: &Keys) -> bool { + pub async fn handle_input_event( + &mut self, + event: &mut InputEvent<'_>, + keys: &Keys, + ) -> RoomResult { if !self.popups.is_empty() { if event.matches(&keys.general.abort) { self.popups.pop_back(); - return true; + return RoomResult::Handled; } // Prevent event from reaching anything below the popup - return false; + return RoomResult::NotHandled; } let result = match &mut self.state { - State::Normal => return self.handle_normal_input_event(event, keys).await, + State::Normal => return self.handle_normal_input_event(event, keys).await.into(), State::Auth(editor) => auth::handle_input_event(event, keys, &self.room, editor), State::Nick(editor) => nick::handle_input_event(event, keys, &self.room, editor), State::Account(account) => account.handle_input_event(event, keys, &self.room), @@ -522,18 +526,24 @@ impl EuphRoom { }; match result { - PopupResult::NotHandled => false, - PopupResult::Handled => true, + PopupResult::NotHandled => RoomResult::NotHandled, + PopupResult::Handled => RoomResult::Handled, PopupResult::Close => { self.state = State::Normal; - true + RoomResult::Handled } + PopupResult::SwitchToRoom { name } => RoomResult::SwitchToRoom { + room: RoomIdentifier { + domain: self.vault().room().domain.clone(), + name, + }, + }, PopupResult::ErrorOpeningLink { link, error } => { self.popups.push_front(RoomPopup::Error { description: format!("Failed to open link: {link}"), reason: format!("{error}"), }); - true + RoomResult::Handled } } } @@ -638,3 +648,18 @@ impl EuphRoom { true } } + +pub enum RoomResult { + NotHandled, + Handled, + SwitchToRoom { room: RoomIdentifier }, +} + +impl From<bool> for RoomResult { + fn from(value: bool) -> Self { + match value { + true => Self::Handled, + false => Self::NotHandled, + } + } +} diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index 04bce02..80089da 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -29,7 +29,7 @@ use crate::{ use super::{ UiError, UiEvent, - euph::room::EuphRoom, + euph::room::{EuphRoom, RoomResult}, key_bindings, util, widgets::{ListBuilder, ListState}, }; @@ -574,8 +574,15 @@ impl Rooms { } State::ShowRoom(name) => { if let Some(room) = self.euph_rooms.get_mut(name) { - if room.handle_input_event(event, keys).await { - return true; + match room.handle_input_event(event, keys).await { + RoomResult::NotHandled => {} + RoomResult::Handled => return true, + RoomResult::SwitchToRoom { room } => { + self.list.move_cursor_to_id(&room); + self.connect_to_room(room.clone()).await; + self.state = State::ShowRoom(room); + return true; + } } if event.matches(&keys.general.abort) { self.state = State::ShowList; From 24c8c92070a067c2aac0c107a30a93bae0e5c27a Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 21:42:13 +0100 Subject: [PATCH 240/266] Update emoji --- CHANGELOG.md | 1 + Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f75a3d8..ccf0f11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Procedure when bumping the version number: - Updated documentation for `time_zone` config option - When connecting to a room using `n` in the room list, the cursor now moves to that room +- Updated list of emoji names ### Removed diff --git a/Cargo.lock b/Cargo.lock index 86ebb73..b289bd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -521,7 +521,7 @@ dependencies = [ [[package]] name = "euphoxide" version = "0.6.0" -source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.6.0#4f7cc49b636301ce9beea9324a0a1390f8391009" +source = "git+https://github.com/Garmelon/euphoxide.git?rev=6eea194d52fb63d7fb260d06e61d0625addf8c67#6eea194d52fb63d7fb260d06e61d0625addf8c67" dependencies = [ "async-trait", "caseless", diff --git a/Cargo.toml b/Cargo.toml index f8445e5..a0de23f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ unicode-width = "0.2.0" [workspace.dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -tag = "v0.6.0" +rev = "6eea194d52fb63d7fb260d06e61d0625addf8c67" features = ["bot"] [workspace.dependencies.toss] From 315db430105b58a9cdfbededc06876257df56992 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 21:46:32 +0100 Subject: [PATCH 241/266] Fix /me not being grey and italic --- cove/src/euph/highlight.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cove/src/euph/highlight.rs b/cove/src/euph/highlight.rs index e60a302..e7892a8 100644 --- a/cove/src/euph/highlight.rs +++ b/cove/src/euph/highlight.rs @@ -132,7 +132,7 @@ pub fn apply_spans( assert!(range.end <= content.len()); if i < range.start { - result = result.then_plain(&content[i..range.start]); + result = result.then(&content[i..range.start], base); } let text = &content[range.start..range.end]; @@ -145,7 +145,7 @@ pub fn apply_spans( let name = text.strip_prefix(':').unwrap_or(text); let name = name.strip_suffix(':').unwrap_or(name); if let Some(Some(replacement)) = util::EMOJI.get(name) { - result.then_plain(replacement) + result.then(replacement, base) } else { result.then(name, base.magenta()) } @@ -156,7 +156,7 @@ pub fn apply_spans( } if i < content.len() { - result = result.then_plain(&content[i..]); + result = result.then(&content[i..], base); } result From 9435fbece6abecd148d0937283abb315cbfcb659 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 21:56:18 +0100 Subject: [PATCH 242/266] Fix mention highlighting --- cove/src/euph/highlight.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cove/src/euph/highlight.rs b/cove/src/euph/highlight.rs index e7892a8..e69fd10 100644 --- a/cove/src/euph/highlight.rs +++ b/cove/src/euph/highlight.rs @@ -77,6 +77,7 @@ impl<'a> SpanFinder<'a> { fn step(&mut self, idx: usize, char: char) { match (char, self.span) { + ('@', Some((SpanType::Mention, _))) => {} // Continue the mention ('@', _) if self.room_or_mention_possible => self.open_span(SpanType::Mention, idx), ('&', _) if self.room_or_mention_possible => self.open_span(SpanType::Room, idx), (':', None) => self.open_span(SpanType::Emoji, idx), @@ -180,8 +181,9 @@ mod tests { #[test] fn mentions() { assert_eq!(find_spans("@foo"), vec![(SpanType::Mention, 0..4)]); + assert_eq!(find_spans("&@foo"), vec![(SpanType::Mention, 1..5)]); assert_eq!(find_spans("a @foo b"), vec![(SpanType::Mention, 2..6)]); - assert_eq!(find_spans("@@foo@"), vec![(SpanType::Mention, 1..6)]); + assert_eq!(find_spans("@@foo@@"), vec![(SpanType::Mention, 0..7)]); assert_eq!(find_spans("a @b@c d"), vec![(SpanType::Mention, 2..6)]); assert_eq!( find_spans("a @b @c d"), @@ -192,12 +194,18 @@ mod tests { #[test] fn rooms() { assert_eq!(find_spans("&foo"), vec![(SpanType::Room, 0..4)]); + assert_eq!(find_spans("@&foo"), vec![(SpanType::Room, 1..5)]); assert_eq!(find_spans("a &foo b"), vec![(SpanType::Room, 2..6)]); - assert_eq!(find_spans("&&foo&"), vec![(SpanType::Room, 1..5)]); + assert_eq!(find_spans("&&foo&&"), vec![(SpanType::Room, 1..5)]); assert_eq!(find_spans("a &b&c d"), vec![(SpanType::Room, 2..4)]); assert_eq!( find_spans("a &b &c d"), vec![(SpanType::Room, 2..4), (SpanType::Room, 5..7)] ); } + + #[test] + fn emoji_in_mentions() { + assert_eq!(find_spans(" @a:b:c "), vec![(SpanType::Mention, 1..7)]); + } } From b4c4a89625b50a3c723146435b4c253980e8476f Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 22:03:42 +0100 Subject: [PATCH 243/266] Fix mention color of non-ascii nicks The old code included the @ in mention color computations. If the nick consisted only of weird unicode characters, this resulted in an incorrect color being computed. --- cove/src/euph/highlight.rs | 4 ++-- cove/src/euph/util.rs | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/cove/src/euph/highlight.rs b/cove/src/euph/highlight.rs index e69fd10..0e7fb56 100644 --- a/cove/src/euph/highlight.rs +++ b/cove/src/euph/highlight.rs @@ -138,8 +138,8 @@ pub fn apply_spans( let text = &content[range.start..range.end]; result = match span { - SpanType::Mention if exact => result.and_then(util::style_nick_exact(text, base)), - SpanType::Mention => result.and_then(util::style_nick(text, base)), + SpanType::Mention if exact => result.and_then(util::style_mention_exact(text, base)), + SpanType::Mention => result.and_then(util::style_mention(text, base)), SpanType::Room => result.then(text, base.blue().bold()), SpanType::Emoji if exact => result.then(text, base.magenta()), SpanType::Emoji => { diff --git a/cove/src/euph/util.rs b/cove/src/euph/util.rs index aff2192..c2928ab 100644 --- a/cove/src/euph/util.rs +++ b/cove/src/euph/util.rs @@ -55,3 +55,17 @@ pub fn style_nick(nick: &str, base: Style) -> Styled { pub fn style_nick_exact(nick: &str, base: Style) -> Styled { Styled::new(nick, nick_style(nick, base)) } + +pub fn style_mention(mention: &str, base: Style) -> Styled { + let nick = mention + .strip_prefix('@') + .expect("mention must start with @"); + Styled::new(EMOJI.replace(mention), nick_style(nick, base)) +} + +pub fn style_mention_exact(mention: &str, base: Style) -> Styled { + let nick = mention + .strip_prefix('@') + .expect("mention must start with @"); + Styled::new(mention, nick_style(nick, base)) +} From b64f56fce54219c60b2e27591951b6c963f93711 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 22:41:25 +0100 Subject: [PATCH 244/266] Fix nick color in rare edge cases --- CHANGELOG.md | 4 ++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccf0f11..86159d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,10 @@ Procedure when bumping the version number: - Special handling of &rl2dev +### Fixed + +- Nick color in rare edge cases + ## v0.8.3 - 2024-05-20 ### Changed diff --git a/Cargo.lock b/Cargo.lock index b289bd3..98db605 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -521,7 +521,7 @@ dependencies = [ [[package]] name = "euphoxide" version = "0.6.0" -source = "git+https://github.com/Garmelon/euphoxide.git?rev=6eea194d52fb63d7fb260d06e61d0625addf8c67#6eea194d52fb63d7fb260d06e61d0625addf8c67" +source = "git+https://github.com/Garmelon/euphoxide.git?rev=095d2cea86a574732e82385e217381b35cf65e4d#095d2cea86a574732e82385e217381b35cf65e4d" dependencies = [ "async-trait", "caseless", diff --git a/Cargo.toml b/Cargo.toml index a0de23f..6fbcf0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ unicode-width = "0.2.0" [workspace.dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -rev = "6eea194d52fb63d7fb260d06e61d0625addf8c67" +rev = "095d2cea86a574732e82385e217381b35cf65e4d" features = ["bot"] [workspace.dependencies.toss] From 972e4938aab8b8a7cc8c2fe5b89f9ddbf2cca618 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 22:46:09 +0100 Subject: [PATCH 245/266] Run tests in CI --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f02d5dd..012c48e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,6 +31,9 @@ jobs: - name: Build run: cargo build --release + - name: Test + run: cargo test --release + - name: Record target triple run: rustc -vV | awk '/^host/ { print $2 }' > target/release/host From 967293db379d4ba962cfb851d5cf4750b54d2101 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 22:54:03 +0100 Subject: [PATCH 246/266] Fix links wrapping into oblivion --- CHANGELOG.md | 1 + cove/src/ui/euph/links.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86159d0..e2344ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Procedure when bumping the version number: ### Fixed - Nick color in rare edge cases +- Message link list rendering bug ## v0.8.3 - 2024-05-20 diff --git a/cove/src/ui/euph/links.rs b/cove/src/ui/euph/links.rs index 20e2219..c64830d 100644 --- a/cove/src/ui/euph/links.rs +++ b/cove/src/ui/euph/links.rs @@ -101,7 +101,7 @@ impl LinksState { Link::Room(name) => text.then(format!("&{name}"), Style::new().blue().bold()), }; - Text::new(text) + Text::new(text).with_wrap(false) }); } From cab37cb633961ff1b791e2ea5a79bae03576f477 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 22:55:47 +0100 Subject: [PATCH 247/266] Fix non-replaceable emoji being rendered without colons --- cove/src/euph/highlight.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cove/src/euph/highlight.rs b/cove/src/euph/highlight.rs index 0e7fb56..1c9abd0 100644 --- a/cove/src/euph/highlight.rs +++ b/cove/src/euph/highlight.rs @@ -148,7 +148,7 @@ pub fn apply_spans( if let Some(Some(replacement)) = util::EMOJI.get(name) { result.then(replacement, base) } else { - result.then(name, base.magenta()) + result.then(text, base.magenta()) } } }; From 03b91ec1cdf871370208d03dd1e86faf789f41cc Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 23:08:17 +0100 Subject: [PATCH 248/266] Mention ghostty in config docs --- cove-config/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 85b418e..24ffcd3 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -58,7 +58,8 @@ pub struct Config { /// those cases are usually rare (unless you view a lot of emoji). /// /// `"unicode"`: Use the unicode standard in a best-effort manner to - /// determine grapheme widths. + /// determine grapheme widths. Some terminals (e.g. ghostty) can make use of + /// this. /// /// This method is used when `measure_widths` is set to `false`. #[serde(default)] From 56896a861e33e6d546fb00fa040942b13ec84863 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 23:08:29 +0100 Subject: [PATCH 249/266] Mention release binaries in readme --- README.md | 63 +++++-------------------------------------------------- 1 file changed, 5 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index e99e545..22fef83 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,11 @@ real-time chat platform. It runs on Linux, Windows, and macOS. +## Installing cove + +Download a binary of your choice from the +[latest release on GitHub](https://github.com/Garmelon/cove/releases/latest). + ## Using cove To start cove, simply run `cove` in your terminal. For more info about the @@ -26,61 +31,3 @@ file or via `cove help-config`. When launched, cove prints the location it is loading its config file from. To configure cove, create a config file at that location. This location can be changed via the `--config` command line option. - -## Installation - -At this point, cove is not available via any package manager. - -Cove is available as a Nix Flake. To try it out, you can use -```bash -$ nix run --override-input nixpkgs nixpkgs github:Garmelon/cove/latest -``` - -## Manual installation - -This section contains instructions on how to install cove by compiling it yourself. -It doesn't assume you know how to program, but it does assume basic familiarity with the command line on your platform of choice. -Cove runs in the terminal, after all. - -### Installing rustup - -Cove is written in Rust, so the first step is to install rustup. Either install -it from your package manager of choice (if you have one) or use the -[installer](https://rustup.rs/). - -Test your installation by running `rustup --version` and `cargo --version`. If -rustup is installed correctly, both of these should show a version number. - -Cove is designed on the current version of the stable toolchain. If cove doesn't -compile, you can try switching to the stable toolchain and updating it using the -following commands: -```bash -$ rustup default stable -$ rustup update -``` - -### Installing cove - -To install or update to the latest release of cove, run the following command: - -```bash -$ cargo install --force --git https://github.com/Garmelon/cove --branch latest -``` - -If you like to live dangerously and want to install or update to the latest, -bleeding-edge, possibly-broken commit from the repo's main branch, run the -following command. - -**Warning:** This could corrupt your vault. Make sure to make a backup before -running the command. - -```bash -$ cargo install --force --git https://github.com/Garmelon/cove -``` - -To install a specific version of cove, run the following command and substitute -in the full version you want to install: - -```bash -$ cargo install --force --git https://github.com/Garmelon/cove --tag v0.1.0 -``` From cc436bbb3a1332bde383b7e5b440e45ecd0144a4 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 23:40:58 +0100 Subject: [PATCH 250/266] Mention --width-estimation-method in config docs --- cove-config/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index 24ffcd3..a2a5cb9 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -62,6 +62,8 @@ pub struct Config { /// this. /// /// This method is used when `measure_widths` is set to `false`. + /// + /// See also the `--width-estimation-method` command line option. #[serde(default)] pub width_estimation_method: WidthEstimationMethod, From 676c92752df7070b1fb6c874d4978c65fc8571a9 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 23:39:22 +0100 Subject: [PATCH 251/266] Remove flake A bit annoying to keep up-to-date since I don't use it myself. --- flake.lock | 47 ----------------------------------------------- flake.nix | 29 ----------------------------- 2 files changed, 76 deletions(-) delete mode 100644 flake.lock delete mode 100644 flake.nix diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 1e52d47..0000000 --- a/flake.lock +++ /dev/null @@ -1,47 +0,0 @@ -{ - "nodes": { - "naersk": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1713520724, - "narHash": "sha256-CO8MmVDmqZX2FovL75pu5BvwhW+Vugc7Q6ze7Hj8heI=", - "owner": "nix-community", - "repo": "naersk", - "rev": "c5037590290c6c7dae2e42e7da1e247e54ed2d49", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "naersk", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1714068967, - "narHash": "sha256-jfQUewdwBVs0HHLH10qxyn0+J53e1aQoPSkuBnYf15s=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "10b682b6e5ed139ee2bef863ada3043f2d79c1cc", - "type": "github" - }, - "original": { - "owner": "NixOS", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "naersk": "naersk", - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index 286f9b7..0000000 --- a/flake.nix +++ /dev/null @@ -1,29 +0,0 @@ -{ - description = "TUI client for euphoria.leet.nu, a threaded real-time chat platform"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs"; - - naersk.url = "github:nix-community/naersk"; - naersk.inputs.nixpkgs.follows = "nixpkgs"; - }; - - outputs = { self, nixpkgs, naersk }: - let forAllSystems = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed; - in { - packages = forAllSystems (system: - let - pkgs = import nixpkgs { inherit system; }; - naersk' = pkgs.callPackage naersk { }; - cargoToml = pkgs.lib.importTOML ./Cargo.toml; - in - { - default = naersk'.buildPackage { - name = "cove"; - version = cargoToml.workspace.package.version; - root = ./.; - }; - } - ); - }; -} From b207e91c256046691e1e450419129c61ea74db69 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 23:38:02 +0100 Subject: [PATCH 252/266] Update dependencies --- Cargo.lock | 12 ++++++------ Cargo.toml | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98db605..0f41db7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -520,8 +520,8 @@ dependencies = [ [[package]] name = "euphoxide" -version = "0.6.0" -source = "git+https://github.com/Garmelon/euphoxide.git?rev=095d2cea86a574732e82385e217381b35cf65e4d#095d2cea86a574732e82385e217381b35cf65e4d" +version = "0.6.1" +source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.6.1#7a292c429ad44aa6aa52fc381e3168841d6303b0" dependencies = [ "async-trait", "caseless", @@ -865,9 +865,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "memchr" @@ -1646,8 +1646,8 @@ dependencies = [ [[package]] name = "toss" -version = "0.3.1" -source = "git+https://github.com/Garmelon/toss.git?rev=423dd100c1360decffc5107ea4757d751ac0f4db#423dd100c1360decffc5107ea4757d751ac0f4db" +version = "0.3.2" +source = "git+https://github.com/Garmelon/toss.git?tag=v0.3.2#d28ce90ec7590778e6035a7b00b1d85064f03dbf" dependencies = [ "async-trait", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 6fbcf0c..119f98e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ directories = "6.0.0" edit = "0.1.5" jiff = "0.2.1" linkify = "0.10.0" -log = { version = "0.4.25", features = ["std"] } +log = { version = "0.4.26", features = ["std"] } open = "5.3.2" parking_lot = "0.12.3" proc-macro2 = "1.0.93" @@ -34,12 +34,12 @@ unicode-width = "0.2.0" [workspace.dependencies.euphoxide] git = "https://github.com/Garmelon/euphoxide.git" -rev = "095d2cea86a574732e82385e217381b35cf65e4d" +tag = "v0.6.1" features = ["bot"] [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" -rev = "423dd100c1360decffc5107ea4757d751ac0f4db" +tag = "v0.3.2" [workspace.dependencies.vault] git = "https://github.com/Garmelon/vault.git" From 4cf6a15577ad5abd31550f5b23a72ab979a865dd Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sun, 23 Feb 2025 23:43:38 +0100 Subject: [PATCH 253/266] Bump version to 0.9.0 --- CHANGELOG.md | 5 +++-- CONFIG.md | 60 +++++++++++++++++++++++++++++++++++----------------- Cargo.lock | 8 +++---- Cargo.toml | 2 +- 4 files changed, 49 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2344ff..ed799ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,12 @@ Procedure when bumping the version number: 4. Run `cargo run help-config > CONFIG.md` 5. Commit with message `Bump version to X.Y.Z` 6. Create tag named `vX.Y.Z` -7. Fast-forward branch `latest` -8. Push `master`, `latest` and the new tag +7. Push `master` and the new tag ## Unreleased +## v0.9.0 - 2025-02-23 + ### Added - Unicode-based grapheme width estimation method diff --git a/CONFIG.md b/CONFIG.md index 66625d0..feec645 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -93,9 +93,9 @@ Whether to automatically join this room on startup. **Type:** boolean **Default:** `false` -If `euph.rooms.<room>.username` is set, this will force cove to set the -username even if there is already a different username associated with -the current session. +If `euph.servers.<domain>.rooms.<room>.username` is set, this will force +cove to set the username even if there is already a different username +associated with the current session. ### `euph.servers.<domain>.rooms.<room>.password` @@ -607,12 +607,11 @@ Move to root. **Type:** boolean **Default:** `false` -Whether to measure the width of characters as displayed by the terminal -emulator instead of guessing the width. +Whether to measure the width of graphemes (i.e. characters) as displayed +by the terminal emulator instead of estimating the width. Enabling this makes rendering a bit slower but more accurate. The screen -might also flash when encountering new characters (or, more accurately, -graphemes). +might also flash when encountering new graphemes. See also the `--measure-widths` command line option. @@ -656,18 +655,41 @@ order of priority): Time zone that chat timestamps should be displayed in. -This option is interpreted as a POSIX TZ string. It is described here in -further detail: -<https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html> +This option can either be the string `"localtime"`, a [POSIX TZ string], +or a [tz identifier] from the [tz database]. -On a normal system, the string `"localtime"` as well as any value from -the "TZ identifier" column of the following wikipedia article should be -valid TZ strings: -<https://en.wikipedia.org/wiki/List_of_tz_database_time_zones> +When not set or when set to `"localtime"`, cove attempts to use your +system's configured time zone, falling back to UTC. -If the `TZ` environment variable exists, it overrides this option. If -neither exist, cove uses the system's local time zone. +When the string begins with a colon or doesn't match the a POSIX TZ +string format, it is interpreted as a tz identifier and looked up in +your system's tz database (or a bundled tz database on Windows). -**Warning:** On Windows, cove can't get the local time zone and uses UTC -instead. However, you can still specify a path to a tz data file or a -custom time zone string. +If the `TZ` environment variable exists, it overrides this option. + +[POSIX TZ string]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03 +[tz identifier]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +[tz database]: https://en.wikipedia.org/wiki/Tz_database + +### `width_estimation_method` + +**Required:** yes +**Type:** string +**Values:** `"legacy"`, `"unicode"` +**Default:** `"legacy"` + +How to estimate the width of graphemes (i.e. characters) as displayed by +the terminal emulator. + +`"legacy"`: Use a legacy method that should mostly work on most terminal +emulators. This method will never be correct in all cases since every +terminal emulator handles grapheme widths slightly differently. However, +those cases are usually rare (unless you view a lot of emoji). + +`"unicode"`: Use the unicode standard in a best-effort manner to +determine grapheme widths. Some terminals (e.g. ghostty) can make use of +this. + +This method is used when `measure_widths` is set to `false`. + +See also the `--width-estimation-method` command line option. diff --git a/Cargo.lock b/Cargo.lock index 0f41db7..cc86a84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -330,7 +330,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cove" -version = "0.8.3" +version = "0.9.0" dependencies = [ "anyhow", "async-trait", @@ -358,7 +358,7 @@ dependencies = [ [[package]] name = "cove-config" -version = "0.8.3" +version = "0.9.0" dependencies = [ "cove-input", "cove-macro", @@ -369,7 +369,7 @@ dependencies = [ [[package]] name = "cove-input" -version = "0.8.3" +version = "0.9.0" dependencies = [ "cove-macro", "crossterm", @@ -383,7 +383,7 @@ dependencies = [ [[package]] name = "cove-macro" -version = "0.8.3" +version = "0.9.0" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 119f98e..26aa29d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "3" members = ["cove", "cove-*"] [workspace.package] -version = "0.8.3" +version = "0.9.0" edition = "2024" [workspace.dependencies] From 30c344031a9f5d7f45ec8311902aca84d7484e42 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 28 Feb 2025 14:32:30 +0100 Subject: [PATCH 254/266] Fix rendering glitches with unicode-based width estimation --- CHANGELOG.md | 4 ++++ Cargo.lock | 4 ++-- Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed799ba..41d2586 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ Procedure when bumping the version number: ## Unreleased +### Fixed + +- Rendering glitches with unicode-based width estimation + ## v0.9.0 - 2025-02-23 ### Added diff --git a/Cargo.lock b/Cargo.lock index cc86a84..e9ceb10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1646,8 +1646,8 @@ dependencies = [ [[package]] name = "toss" -version = "0.3.2" -source = "git+https://github.com/Garmelon/toss.git?tag=v0.3.2#d28ce90ec7590778e6035a7b00b1d85064f03dbf" +version = "0.3.3" +source = "git+https://github.com/Garmelon/toss.git?tag=v0.3.3#96b2e13c4a4b0174601d90246d92d148c4230eeb" dependencies = [ "async-trait", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 26aa29d..322724f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ features = ["bot"] [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" -tag = "v0.3.2" +tag = "v0.3.3" [workspace.dependencies.vault] git = "https://github.com/Garmelon/vault.git" From 6157ca5088ac4d6e6ebc6452d0704d2d6f48ff7a Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 1 Mar 2025 14:26:18 +0100 Subject: [PATCH 255/266] Update dependencies --- Cargo.lock | 62 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9ceb10..5e5b86a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,9 +113,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" -version = "1.12.4" +version = "1.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd755adf9707cf671e31d944a189be3deaaeee11c8bc1d669bb8022ac90fbd0" +checksum = "5e4e8200b9a4a5801a769d50eeabc05670fec7e959a8cb7a63a93e4e519942ae" dependencies = [ "aws-lc-sys", "paste", @@ -176,9 +176,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "block-buffer" @@ -212,9 +212,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.14" +version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ "jobserver", "libc", @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.30" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" +checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" dependencies = [ "clap_builder", "clap_derive", @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.30" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" +checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" dependencies = [ "anstream", "anstyle", @@ -498,9 +498,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" [[package]] name = "equivalent" @@ -803,9 +803,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "libloading" @@ -883,9 +883,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] @@ -1029,9 +1029,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "portable-atomic" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" [[package]] name = "portable-atomic-util" @@ -1093,7 +1093,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha", "rand_core", - "zerocopy 0.8.20", + "zerocopy 0.8.21", ] [[package]] @@ -1108,19 +1108,19 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3" +checksum = "7a509b1a2ffbe92afab0e55c8fd99dea1c280e8171bd2d88682bb20bc41cbc2c" dependencies = [ "getrandom 0.3.1", - "zerocopy 0.8.20", + "zerocopy 0.8.21", ] [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" dependencies = [ "bitflags", ] @@ -1167,9 +1167,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ring" -version = "0.17.9" +version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24" +checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" dependencies = [ "cc", "cfg-if", @@ -1914,11 +1914,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" +checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478" dependencies = [ - "zerocopy-derive 0.8.20", + "zerocopy-derive 0.8.21", ] [[package]] @@ -1934,9 +1934,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" +checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 322724f..cd023a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2024" [workspace.dependencies] anyhow = "1.0.96" async-trait = "0.1.86" -clap = { version = "4.5.30", features = ["derive", "deprecated"] } +clap = { version = "4.5.31", features = ["derive", "deprecated"] } cookie = "0.18.1" crossterm = "0.28.1" directories = "6.0.0" From 496cdde18d33cd898e6628ba0636dc03761cf9c8 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 1 Mar 2025 14:38:41 +0100 Subject: [PATCH 256/266] Bump version to 0.9.1 --- CHANGELOG.md | 2 ++ Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41d2586..de7a07e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Procedure when bumping the version number: ## Unreleased +## v0.9.1 - 2025-03-01 + ### Fixed - Rendering glitches with unicode-based width estimation diff --git a/Cargo.lock b/Cargo.lock index 5e5b86a..60c89b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -330,7 +330,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cove" -version = "0.9.0" +version = "0.9.1" dependencies = [ "anyhow", "async-trait", @@ -358,7 +358,7 @@ dependencies = [ [[package]] name = "cove-config" -version = "0.9.0" +version = "0.9.1" dependencies = [ "cove-input", "cove-macro", @@ -369,7 +369,7 @@ dependencies = [ [[package]] name = "cove-input" -version = "0.9.0" +version = "0.9.1" dependencies = [ "cove-macro", "crossterm", @@ -383,7 +383,7 @@ dependencies = [ [[package]] name = "cove-macro" -version = "0.9.0" +version = "0.9.1" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index cd023a1..2937074 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "3" members = ["cove", "cove-*"] [workspace.package] -version = "0.9.0" +version = "0.9.1" edition = "2024" [workspace.dependencies] From a17630aeaaa902ead46de58d87ceb1ec8703fcfc Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 8 Mar 2025 19:21:01 +0100 Subject: [PATCH 257/266] Ring bell when mentioned --- CHANGELOG.md | 4 ++++ Cargo.lock | 4 ++-- Cargo.toml | 2 +- cove-config/src/lib.rs | 4 ++++ cove/src/ui/euph/room.rs | 44 +++++++++++++++++++++++++++++++++++++--- cove/src/ui/rooms.rs | 16 ++++++++++++--- 6 files changed, 65 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de7a07e..4136ab3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ Procedure when bumping the version number: ## Unreleased +### Added + +- `bell_on_mention` config option + ## v0.9.1 - 2025-03-01 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 60c89b6..58a545e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1646,8 +1646,8 @@ dependencies = [ [[package]] name = "toss" -version = "0.3.3" -source = "git+https://github.com/Garmelon/toss.git?tag=v0.3.3#96b2e13c4a4b0174601d90246d92d148c4230eeb" +version = "0.3.4" +source = "git+https://github.com/Garmelon/toss.git?tag=v0.3.4#57aa8c59308f6f0aa82bde415a42b56c3d6f7c4d" dependencies = [ "async-trait", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 2937074..a4278ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ features = ["bot"] [workspace.dependencies.toss] git = "https://github.com/Garmelon/toss.git" -tag = "v0.3.3" +tag = "v0.3.4" [workspace.dependencies.vault] git = "https://github.com/Garmelon/vault.git" diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs index a2a5cb9..0cb6cc7 100644 --- a/cove-config/src/lib.rs +++ b/cove-config/src/lib.rs @@ -100,6 +100,10 @@ pub struct Config { #[serde(default)] pub rooms_sort_order: RoomsSortOrder, + /// Ring the bell (character 0x07) when you are mentioned in a room. + #[serde(default)] + pub bell_on_mention: bool, + /// Time zone that chat timestamps should be displayed in. /// /// This option can either be the string `"localtime"`, a [POSIX TZ string], diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index 83d7e96..ebae5a8 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -4,8 +4,8 @@ use cove_config::{Config, Keys}; use cove_input::InputEvent; use crossterm::style::Stylize; use euphoxide::{ - api::{Data, Message, MessageId, PacketType, SessionId}, - bot::instance::{Event, ServerConfig}, + api::{Data, Message, MessageId, PacketType, SessionId, packet::ParsedPacket}, + bot::instance::{ConnSnapshot, Event, ServerConfig}, conn::{self, Joined, Joining, SessionInfo}, }; use jiff::tz::TimeZone; @@ -19,7 +19,7 @@ use toss::{ }; use crate::{ - euph, + euph::{self, SpanType}, macros::logging_unwrap, ui::{ UiError, UiEvent, @@ -73,6 +73,8 @@ pub struct EuphRoom { last_msg_sent: Option<oneshot::Receiver<MessageId>>, nick_list: ListState<SessionId>, + + mentioned: bool, } impl EuphRoom { @@ -96,6 +98,7 @@ impl EuphRoom { chat: ChatState::new(vault, tz), last_msg_sent: None, nick_list: ListState::new(), + mentioned: false, } } @@ -164,6 +167,12 @@ impl EuphRoom { } } + pub fn retrieve_mentioned(&mut self) -> bool { + let mentioned = self.mentioned; + self.mentioned = false; + mentioned + } + pub async fn unseen_msgs_count(&self) -> usize { logging_unwrap!(self.vault().unseen_msgs_count().await) } @@ -557,6 +566,35 @@ impl EuphRoom { return false; } + if let Event::Packet( + _, + ParsedPacket { + content: Ok(Data::SendEvent(send)), + .. + }, + ConnSnapshot { + state: conn::State::Joined(joined), + .. + }, + ) = &event + { + let normalized_name = euphoxide::nick::normalize(&joined.session.name); + let content = &*send.0.content; + for (rtype, rspan) in euph::find_spans(content) { + if rtype != SpanType::Mention { + continue; + } + let Some(mention) = content[rspan].strip_prefix('@') else { + continue; + }; + let normalized_mention = euphoxide::nick::normalize(mention); + if normalized_name == normalized_mention { + self.mentioned = true; + break; + } + } + } + // We handle the packet internally first because the room event handling // will consume it while we only need a reference. let handled = if let Event::Packet(_, packet, _) = &event { diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index 80089da..f901f30 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -17,7 +17,7 @@ use jiff::tz::TimeZone; use tokio::sync::mpsc; use toss::{ Style, Styled, Widget, WidgetExt, - widgets::{BoxedAsync, Empty, Join2, Text}, + widgets::{BellState, BoxedAsync, Empty, Join2, Text}, }; use crate::{ @@ -95,6 +95,7 @@ pub struct Rooms { list: ListState<RoomIdentifier>, order: Order, + bell: BellState, euph_servers: HashMap<String, EuphServer>, euph_rooms: HashMap<RoomIdentifier, EuphRoom>, @@ -115,6 +116,7 @@ impl Rooms { state: State::ShowList, list: ListState::new(), order: Order::from_rooms_sort_order(config.rooms_sort_order), + bell: BellState::new(), euph_servers: HashMap::new(), euph_rooms: HashMap::new(), }; @@ -244,7 +246,9 @@ impl Rooms { .retain(|n, r| !r.stopped() || rooms_set.contains(n)); for room in rooms_set { - self.get_or_insert_room(room).await.retain(); + let room = self.get_or_insert_room(room).await; + room.retain(); + self.bell.ring |= room.retrieve_mentioned(); } } @@ -254,7 +258,7 @@ impl Rooms { _ => self.stabilize_rooms().await, } - match &mut self.state { + let widget = match &mut self.state { State::ShowList => Self::rooms_widget( &self.vault, self.config, @@ -297,6 +301,12 @@ impl Rooms { .below(delete.widget()) .desync() .boxed_async(), + }; + + if self.config.bell_on_mention { + widget.above(self.bell.widget().desync()).boxed_async() + } else { + widget } } From 74fbf386b2e1769821a5f0bbdc4175a61ef63310 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 14 Mar 2025 15:08:00 +0100 Subject: [PATCH 258/266] Update dependencies --- CHANGELOG.md | 2 +- Cargo.lock | 236 +++++++++++++++++++++++++++------------------------ Cargo.toml | 22 ++--- 3 files changed, 137 insertions(+), 123 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4136ab3..a14ead2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Procedure when bumping the version number: -1. Update dependencies and flake in a separate commit +1. Update dependencies in a separate commit 2. Set version number in `Cargo.toml` 3. Add new section in this changelog 4. Run `cargo run help-config > CONFIG.md` diff --git a/Cargo.lock b/Cargo.lock index 58a545e..6a83e5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,15 +90,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" dependencies = [ "proc-macro2", "quote", @@ -113,27 +113,25 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" -version = "1.12.5" +version = "1.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4e8200b9a4a5801a769d50eeabc05670fec7e959a8cb7a63a93e4e519942ae" +checksum = "dabb68eb3a7aa08b46fddfd59a3d55c978243557a90ab804769f7e20e67d2b01" dependencies = [ "aws-lc-sys", - "paste", "zeroize", ] [[package]] name = "aws-lc-sys" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9dd2e03ee80ca2822dd6ea431163d2ef259f2066a4d6ccaca6d9dcb386aa43" +checksum = "6bbe221bbf523b625a4dd8585c7f38166e31167ec2ca98051dbcb4c3b6e825d2" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", - "paste", ] [[package]] @@ -189,17 +187,11 @@ dependencies = [ "generic-array", ] -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "caseless" @@ -249,9 +241,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.31" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" dependencies = [ "clap_builder", "clap_derive", @@ -259,9 +251,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" dependencies = [ "anstream", "anstyle", @@ -271,9 +263,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", @@ -409,7 +401,7 @@ dependencies = [ "crossterm_winapi", "mio", "parking_lot", - "rustix", + "rustix 0.38.44", "signal-hook", "signal-hook-mio", "winapi", @@ -498,9 +490,9 @@ dependencies = [ [[package]] name = "either" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" @@ -686,9 +678,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -697,15 +689,15 @@ dependencies = [ [[package]] name = "httparse" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "indexmap" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -747,16 +739,17 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3590fea8e9e22d449600c9bbd481a8163bef223e4ff938e5f55899f8cf1adb93" +checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" dependencies = [ + "jiff-static", "jiff-tzdb-platform", "log", "portable-atomic", @@ -766,10 +759,21 @@ dependencies = [ ] [[package]] -name = "jiff-tzdb" -version = "0.1.2" +name = "jiff-static" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2cec2f5d266af45a071ece48b1fb89f3b00b2421ac3a5fe10285a6caaa60d3" +checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "962e1dfe9b2d75a84536cf5bf5eaaa4319aa7906c7160134a22883ac316d5f31" [[package]] name = "jiff-tzdb-platform" @@ -803,9 +807,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.170" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libloading" @@ -853,6 +857,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" + [[package]] name = "lock_api" version = "0.4.12" @@ -938,9 +948,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" [[package]] name = "open" @@ -997,12 +1007,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pathdiff" version = "0.2.3" @@ -1023,9 +1027,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" @@ -1050,18 +1054,18 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy 0.8.23", ] [[package]] name = "prettyplease" -version = "0.2.29" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" +checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb" dependencies = [ "proc-macro2", "syn", @@ -1069,18 +1073,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -1093,7 +1097,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha", "rand_core", - "zerocopy 0.8.21", + "zerocopy 0.8.23", ] [[package]] @@ -1108,19 +1112,18 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a509b1a2ffbe92afab0e55c8fd99dea1c280e8171bd2d88682bb20bc41cbc2c" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.1", - "zerocopy 0.8.21", ] [[package]] name = "redox_syscall" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ "bitflags", ] @@ -1167,9 +1170,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ring" -version = "0.17.11" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", @@ -1215,7 +1218,20 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.3", "windows-sys 0.59.0", ] @@ -1266,9 +1282,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "schannel" @@ -1310,9 +1326,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -1329,9 +1345,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -1350,9 +1366,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.139" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -1455,9 +1471,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.98" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -1466,32 +1482,31 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.17.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" +checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600" dependencies = [ - "cfg-if", "fastrand", "getrandom 0.3.1", "once_cell", - "rustix", + "rustix 1.0.2", "windows-sys 0.59.0", ] [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -1500,9 +1515,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" dependencies = [ "deranged", "itoa", @@ -1515,15 +1530,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" dependencies = [ "num-conv", "time-core", @@ -1531,9 +1546,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -1546,9 +1561,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" dependencies = [ "backtrace", "bytes", @@ -1575,9 +1590,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", "tokio", @@ -1683,9 +1698,9 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-linebreak" @@ -1777,7 +1792,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix", + "rustix 0.38.44", ] [[package]] @@ -1886,9 +1901,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" dependencies = [ "memchr", ] @@ -1908,17 +1923,16 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", "zerocopy-derive 0.7.35", ] [[package]] name = "zerocopy" -version = "0.8.21" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" dependencies = [ - "zerocopy-derive 0.8.21", + "zerocopy-derive 0.8.23", ] [[package]] @@ -1934,9 +1948,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.21" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index a4278ed..f4f81a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,28 +7,28 @@ version = "0.9.1" edition = "2024" [workspace.dependencies] -anyhow = "1.0.96" -async-trait = "0.1.86" -clap = { version = "4.5.31", features = ["derive", "deprecated"] } +anyhow = "1.0.97" +async-trait = "0.1.87" +clap = { version = "4.5.32", features = ["derive", "deprecated"] } cookie = "0.18.1" crossterm = "0.28.1" directories = "6.0.0" edit = "0.1.5" -jiff = "0.2.1" +jiff = "0.2.4" linkify = "0.10.0" log = { version = "0.4.26", features = ["std"] } open = "5.3.2" parking_lot = "0.12.3" -proc-macro2 = "1.0.93" -quote = "1.0.38" +proc-macro2 = "1.0.94" +quote = "1.0.40" rusqlite = { version = "0.31.0", features = ["bundled", "time"] } rustls = "0.23.23" -serde = { version = "1.0.218", features = ["derive"] } +serde = { version = "1.0.219", features = ["derive"] } serde_either = "0.2.1" -serde_json = "1.0.139" -syn = "2.0.98" -thiserror = "2.0.11" -tokio = { version = "1.43.0", features = ["full"] } +serde_json = "1.0.140" +syn = "2.0.100" +thiserror = "2.0.12" +tokio = { version = "1.44.1", features = ["full"] } toml = "0.8.20" unicode-width = "0.2.0" From ca0f0b6c31d78420ad1599fb3f9d61052cb580da Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 14 Mar 2025 15:09:01 +0100 Subject: [PATCH 259/266] Bump version to 0.9.2 --- CHANGELOG.md | 2 ++ CONFIG.md | 8 ++++++++ Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a14ead2..1f06e21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Procedure when bumping the version number: ## Unreleased +## v0.9.2 - 2025-03-14 + ### Added - `bell_on_mention` config option diff --git a/CONFIG.md b/CONFIG.md index feec645..e537310 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -53,6 +53,14 @@ Available modifiers: ## Available options +### `bell_on_mention` + +**Required:** yes +**Type:** boolean +**Default:** `false` + +Ring the bell (character 0x07) when you are mentioned in a room. + ### `data_dir` **Required:** no diff --git a/Cargo.lock b/Cargo.lock index 6a83e5d..157b640 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -322,7 +322,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cove" -version = "0.9.1" +version = "0.9.2" dependencies = [ "anyhow", "async-trait", @@ -350,7 +350,7 @@ dependencies = [ [[package]] name = "cove-config" -version = "0.9.1" +version = "0.9.2" dependencies = [ "cove-input", "cove-macro", @@ -361,7 +361,7 @@ dependencies = [ [[package]] name = "cove-input" -version = "0.9.1" +version = "0.9.2" dependencies = [ "cove-macro", "crossterm", @@ -375,7 +375,7 @@ dependencies = [ [[package]] name = "cove-macro" -version = "0.9.1" +version = "0.9.2" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index f4f81a5..0fa473f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "3" members = ["cove", "cove-*"] [workspace.package] -version = "0.9.1" +version = "0.9.2" edition = "2024" [workspace.dependencies] From 8b928184e89b9ac5aee23eed4c9d084d5416c55d Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Tue, 8 Apr 2025 23:51:39 +0200 Subject: [PATCH 260/266] Fix autojoin key connecting to non-autojoin rooms --- CHANGELOG.md | 4 ++++ cove/src/ui/rooms.rs | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f06e21..3682c75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ Procedure when bumping the version number: ## Unreleased +### Fixed + +- `keys.rooms.action.connect_autojoin` connecting to non-autojoin rooms + ## v0.9.2 - 2025-03-14 ### Added diff --git a/cove/src/ui/rooms.rs b/cove/src/ui/rooms.rs index f901f30..c3d6a40 100644 --- a/cove/src/ui/rooms.rs +++ b/cove/src/ui/rooms.rs @@ -536,7 +536,10 @@ impl Rooms { } if event.matches(&keys.rooms.action.connect_autojoin) { for (domain, server) in &self.config.euph.servers { - for name in server.rooms.keys() { + for (name, room) in &server.rooms { + if !room.autojoin { + continue; + } let id = RoomIdentifier::new(domain.clone(), name.clone()); self.connect_to_room(id).await; } From 40de073799666aa3c4b2e00c078d38c638f29bd2 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 31 May 2025 13:11:50 +0200 Subject: [PATCH 261/266] Silence clippy warning --- cove/src/ui.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cove/src/ui.rs b/cove/src/ui.rs index 1c03834..5ebd540 100644 --- a/cove/src/ui.rs +++ b/cove/src/ui.rs @@ -50,6 +50,7 @@ impl From<Infallible> for UiError { } } +#[expect(clippy::large_enum_variant)] pub enum UiEvent { GraphemeWidthsChanged, LogChanged, From 732d4627754b5d8ccad6b26a065fbef27761ba5e Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 31 May 2025 13:38:54 +0200 Subject: [PATCH 262/266] Add user id based emoji hash Helpful in scenarios where you want to disambiguate people based on their user id at a glance. --- CHANGELOG.md | 4 ++++ cove-config/src/keys.rs | 4 ++++ cove/src/euph/small_message.rs | 15 ++++++++++++++- cove/src/euph/util.rs | 15 ++++++++++++++- cove/src/store.rs | 4 ++++ cove/src/ui/chat.rs | 8 ++++++++ cove/src/ui/chat/tree.rs | 5 +++++ cove/src/ui/chat/tree/renderer.rs | 2 ++ cove/src/ui/chat/tree/scroll.rs | 1 + cove/src/ui/chat/tree/widgets.rs | 9 ++++++++- cove/src/vault/euph.rs | 18 ++++++++++-------- 11 files changed, 74 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3682c75..e92f49b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ Procedure when bumping the version number: ## Unreleased +### Added + +- Key bindings for emoji-based user id hashing + ### Fixed - `keys.rooms.action.connect_autojoin` connecting to non-autojoin rooms diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs index 8b5adeb..47c171c 100644 --- a/cove-config/src/keys.rs +++ b/cove-config/src/keys.rs @@ -104,6 +104,7 @@ default_bindings! { pub fn mark_older_seen => ["ctrl+s"]; pub fn info => ["i"]; pub fn links => ["I"]; + pub fn toggle_nick_emoji => ["e"]; pub fn increase_caesar => ["c"]; pub fn decrease_caesar => ["C"]; } @@ -356,6 +357,9 @@ pub struct TreeAction { /// List links found in message. #[serde(default = "default::tree_action::links")] pub links: KeyBinding, + /// Toggle agent id based nick emoji. + #[serde(default = "default::tree_action::toggle_nick_emoji")] + pub toggle_nick_emoji: KeyBinding, /// Increase caesar cipher rotation. #[serde(default = "default::tree_action::increase_caesar")] pub increase_caesar: KeyBinding, diff --git a/cove/src/euph/small_message.rs b/cove/src/euph/small_message.rs index 003cd40..d306226 100644 --- a/cove/src/euph/small_message.rs +++ b/cove/src/euph/small_message.rs @@ -1,15 +1,20 @@ +use std::hash::{DefaultHasher, Hash, Hasher}; + use crossterm::style::Stylize; -use euphoxide::api::{MessageId, Snowflake, Time}; +use euphoxide::api::{MessageId, Snowflake, Time, UserId}; use jiff::Timestamp; use toss::{Style, Styled}; use crate::{store::Msg, ui::ChatMsg}; +use super::util; + #[derive(Debug, Clone)] pub struct SmallMessage { pub id: MessageId, pub parent: Option<MessageId>, pub time: Time, + pub user_id: UserId, pub nick: String, pub content: String, pub seen: bool, @@ -70,6 +75,14 @@ impl Msg for SmallMessage { fn last_possible_id() -> Self::Id { MessageId(Snowflake::MAX) } + + fn nick_emoji(&self) -> Option<String> { + let mut hasher = DefaultHasher::new(); + self.user_id.0.hash(&mut hasher); + let hash = hasher.finish(); + let emoji = &util::EMOJI_LIST[hash as usize % util::EMOJI_LIST.len()]; + Some(emoji.clone()) + } } impl ChatMsg for SmallMessage { diff --git a/cove/src/euph/util.rs b/cove/src/euph/util.rs index c2928ab..ecea304 100644 --- a/cove/src/euph/util.rs +++ b/cove/src/euph/util.rs @@ -1,4 +1,4 @@ -use std::sync::LazyLock; +use std::{collections::HashSet, sync::LazyLock}; use crossterm::style::{Color, Stylize}; use euphoxide::Emoji; @@ -6,6 +6,19 @@ use toss::{Style, Styled}; pub static EMOJI: LazyLock<Emoji> = LazyLock::new(Emoji::load); +pub static EMOJI_LIST: LazyLock<Vec<String>> = LazyLock::new(|| { + let mut list = EMOJI + .0 + .values() + .flatten() + .cloned() + .collect::<HashSet<_>>() + .into_iter() + .collect::<Vec<_>>(); + list.sort_unstable(); + list +}); + /// Convert HSL to RGB following [this approach from wikipedia][1]. /// /// `h` must be in the range `[0, 360]`, `s` and `l` in the range `[0, 1]`. diff --git a/cove/src/store.rs b/cove/src/store.rs index f64f71e..b7031c1 100644 --- a/cove/src/store.rs +++ b/cove/src/store.rs @@ -8,6 +8,10 @@ pub trait Msg { fn parent(&self) -> Option<Self::Id>; fn seen(&self) -> bool; + fn nick_emoji(&self) -> Option<String> { + None + } + fn last_possible_id() -> Self::Id; } diff --git a/cove/src/ui/chat.rs b/cove/src/ui/chat.rs index 405339b..02adeb7 100644 --- a/cove/src/ui/chat.rs +++ b/cove/src/ui/chat.rs @@ -37,6 +37,7 @@ pub struct ChatState<M: Msg, S: MsgStore<M>> { cursor: Cursor<M::Id>, editor: EditorState, + nick_emoji: bool, caesar: i8, mode: Mode, @@ -48,6 +49,7 @@ impl<M: Msg, S: MsgStore<M> + Clone> ChatState<M, S> { Self { cursor: Cursor::Bottom, editor: EditorState::new(), + nick_emoji: false, caesar: 0, mode: Mode::Tree, @@ -79,6 +81,7 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> { &mut self.editor, nick, focused, + self.nick_emoji, self.caesar, ) .boxed_async(), @@ -117,6 +120,11 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> { Reaction::Composed { parent, content } } + Reaction::NotHandled if event.matches(&keys.tree.action.toggle_nick_emoji) => { + self.nick_emoji = !self.nick_emoji; + Reaction::Handled + } + Reaction::NotHandled if event.matches(&keys.tree.action.increase_caesar) => { self.caesar = (self.caesar + 1).rem_euclid(26); Reaction::Handled diff --git a/cove/src/ui/chat/tree.rs b/cove/src/ui/chat/tree.rs index 043e109..d9905fc 100644 --- a/cove/src/ui/chat/tree.rs +++ b/cove/src/ui/chat/tree.rs @@ -389,6 +389,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> { editor: &'a mut EditorState, nick: String, focused: bool, + nick_emoji: bool, caesar: i8, ) -> TreeView<'a, M, S> { TreeView { @@ -397,6 +398,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> { editor, nick, focused, + nick_emoji, caesar, } } @@ -410,6 +412,8 @@ pub struct TreeView<'a, M: Msg, S: MsgStore<M>> { nick: String, focused: bool, + + nick_emoji: bool, caesar: i8, } @@ -438,6 +442,7 @@ where size, nick: self.nick.clone(), focused: self.focused, + nick_emoji: self.nick_emoji, caesar: self.caesar, last_cursor: self.state.last_cursor.clone(), last_cursor_top: self.state.last_cursor_top, diff --git a/cove/src/ui/chat/tree/renderer.rs b/cove/src/ui/chat/tree/renderer.rs index 142624e..225191b 100644 --- a/cove/src/ui/chat/tree/renderer.rs +++ b/cove/src/ui/chat/tree/renderer.rs @@ -80,6 +80,7 @@ pub struct TreeContext<Id> { pub size: Size, pub nick: String, pub focused: bool, + pub nick_emoji: bool, pub caesar: i8, pub last_cursor: Cursor<Id>, pub last_cursor_top: i32, @@ -207,6 +208,7 @@ where self.tz.clone(), indent, msg, + self.context.nick_emoji, self.context.caesar, folded_info, ); diff --git a/cove/src/ui/chat/tree/scroll.rs b/cove/src/ui/chat/tree/scroll.rs index ab3ddae..a8a1305 100644 --- a/cove/src/ui/chat/tree/scroll.rs +++ b/cove/src/ui/chat/tree/scroll.rs @@ -22,6 +22,7 @@ where size: self.last_size, nick: self.last_nick.clone(), focused: true, + nick_emoji: false, caesar: 0, last_cursor: self.last_cursor.clone(), last_cursor_top: self.last_cursor_top, diff --git a/cove/src/ui/chat/tree/widgets.rs b/cove/src/ui/chat/tree/widgets.rs index d46920e..dd7fa89 100644 --- a/cove/src/ui/chat/tree/widgets.rs +++ b/cove/src/ui/chat/tree/widgets.rs @@ -59,10 +59,17 @@ pub fn msg<M: Msg + ChatMsg>( tz: TimeZone, indent: usize, msg: &M, + nick_emoji: bool, caesar: i8, folded_info: Option<usize>, ) -> Boxed<'static, Infallible> { - let (nick, mut content) = msg.styled(); + let (mut nick, mut content) = msg.styled(); + + if nick_emoji { + if let Some(emoji) = msg.nick_emoji() { + nick = nick.then_plain("(").then_plain(emoji).then_plain(")"); + } + } if caesar != 0 { // Apply caesar in inverse because we're decoding diff --git a/cove/src/vault/euph.rs b/cove/src/vault/euph.rs index 931091c..4a4109e 100644 --- a/cove/src/vault/euph.rs +++ b/cove/src/vault/euph.rs @@ -611,7 +611,7 @@ impl Action for GetMsg { let msg = conn .query_row( " - SELECT id, parent, time, name, content, seen + SELECT id, parent, time, user_id, name, content, seen FROM euph_msgs WHERE domain = ? AND room = ? @@ -623,9 +623,10 @@ impl Action for GetMsg { id: MessageId(row.get::<_, WSnowflake>(0)?.0), parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)), time: row.get::<_, WTime>(2)?.0, - nick: row.get(3)?, - content: row.get(4)?, - seen: row.get(5)?, + user_id: UserId(row.get(3)?), + nick: row.get(4)?, + content: row.get(5)?, + seen: row.get(6)?, }) }, ) @@ -703,7 +704,7 @@ impl Action for GetTree { AND tree.room = euph_msgs.room AND tree.id = euph_msgs.parent ) - SELECT id, parent, time, name, content, seen + SELECT id, parent, time, user_id, name, content, seen FROM euph_msgs JOIN tree USING (domain, room, id) ORDER BY id ASC @@ -716,9 +717,10 @@ impl Action for GetTree { id: MessageId(row.get::<_, WSnowflake>(0)?.0), parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)), time: row.get::<_, WTime>(2)?.0, - nick: row.get(3)?, - content: row.get(4)?, - seen: row.get(5)?, + user_id: UserId(row.get(3)?), + nick: row.get(4)?, + content: row.get(5)?, + seen: row.get(6)?, }) }, )? From b70d7548da28ff89966eae52595cacb930b315ad Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 31 May 2025 13:54:07 +0200 Subject: [PATCH 263/266] Bump version to 0.9.3 --- CHANGELOG.md | 2 ++ CONFIG.md | 8 ++++++++ Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e92f49b..7d87d77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Procedure when bumping the version number: ## Unreleased +## v0.9.3 - 2025-05-31 + ### Added - Key bindings for emoji-based user id hashing diff --git a/CONFIG.md b/CONFIG.md index e537310..82a7242 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -537,6 +537,14 @@ Reply to message, inline if possible. Reply opposite to normal reply. +### `keys.tree.action.toggle_nick_emoji` + +**Required:** yes +**Type:** key binding +**Default:** `"e"` + +Toggle agent id based nick emoji. + ### `keys.tree.action.toggle_seen` **Required:** yes diff --git a/Cargo.lock b/Cargo.lock index 157b640..2f45a5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -322,7 +322,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cove" -version = "0.9.2" +version = "0.9.3" dependencies = [ "anyhow", "async-trait", @@ -350,7 +350,7 @@ dependencies = [ [[package]] name = "cove-config" -version = "0.9.2" +version = "0.9.3" dependencies = [ "cove-input", "cove-macro", @@ -361,7 +361,7 @@ dependencies = [ [[package]] name = "cove-input" -version = "0.9.2" +version = "0.9.3" dependencies = [ "cove-macro", "crossterm", @@ -375,7 +375,7 @@ dependencies = [ [[package]] name = "cove-macro" -version = "0.9.2" +version = "0.9.3" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 0fa473f..33f245f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "3" members = ["cove", "cove-*"] [workspace.package] -version = "0.9.2" +version = "0.9.3" edition = "2024" [workspace.dependencies] From 67e77c88800c02305ad2464380f38e4ff27d797d Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 31 May 2025 15:04:17 +0200 Subject: [PATCH 264/266] Show emoji hash in nick list --- CHANGELOG.md | 4 +++ cove/src/euph/small_message.rs | 8 +---- cove/src/euph/util.rs | 16 ++++++++-- cove/src/ui/chat.rs | 4 +++ cove/src/ui/euph/nick_list.rs | 56 +++++++++++++++++++++++++++++----- cove/src/ui/euph/room.rs | 15 ++++++--- 6 files changed, 81 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d87d77..76fa94a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ Procedure when bumping the version number: ## Unreleased +### Changed + +- Display emoji user id hashes in the nick list + ## v0.9.3 - 2025-05-31 ### Added diff --git a/cove/src/euph/small_message.rs b/cove/src/euph/small_message.rs index d306226..5db1790 100644 --- a/cove/src/euph/small_message.rs +++ b/cove/src/euph/small_message.rs @@ -1,5 +1,3 @@ -use std::hash::{DefaultHasher, Hash, Hasher}; - use crossterm::style::Stylize; use euphoxide::api::{MessageId, Snowflake, Time, UserId}; use jiff::Timestamp; @@ -77,11 +75,7 @@ impl Msg for SmallMessage { } fn nick_emoji(&self) -> Option<String> { - let mut hasher = DefaultHasher::new(); - self.user_id.0.hash(&mut hasher); - let hash = hasher.finish(); - let emoji = &util::EMOJI_LIST[hash as usize % util::EMOJI_LIST.len()]; - Some(emoji.clone()) + Some(util::user_id_emoji(&self.user_id)) } } diff --git a/cove/src/euph/util.rs b/cove/src/euph/util.rs index ecea304..ea1782a 100644 --- a/cove/src/euph/util.rs +++ b/cove/src/euph/util.rs @@ -1,7 +1,11 @@ -use std::{collections::HashSet, sync::LazyLock}; +use std::{ + collections::HashSet, + hash::{DefaultHasher, Hash, Hasher}, + sync::LazyLock, +}; use crossterm::style::{Color, Stylize}; -use euphoxide::Emoji; +use euphoxide::{Emoji, api::UserId}; use toss::{Style, Styled}; pub static EMOJI: LazyLock<Emoji> = LazyLock::new(Emoji::load); @@ -82,3 +86,11 @@ pub fn style_mention_exact(mention: &str, base: Style) -> Styled { .expect("mention must start with @"); Styled::new(mention, nick_style(nick, base)) } + +pub fn user_id_emoji(user_id: &UserId) -> String { + let mut hasher = DefaultHasher::new(); + user_id.0.hash(&mut hasher); + let hash = hasher.finish(); + let emoji = &EMOJI_LIST[hash as usize % EMOJI_LIST.len()]; + emoji.clone() +} diff --git a/cove/src/ui/chat.rs b/cove/src/ui/chat.rs index 02adeb7..1116935 100644 --- a/cove/src/ui/chat.rs +++ b/cove/src/ui/chat.rs @@ -58,6 +58,10 @@ impl<M: Msg, S: MsgStore<M> + Clone> ChatState<M, S> { store, } } + + pub fn nick_emoji(&self) -> bool { + self.nick_emoji + } } impl<M: Msg, S: MsgStore<M>> ChatState<M, S> { diff --git a/cove/src/ui/euph/nick_list.rs b/cove/src/ui/euph/nick_list.rs index e1e4e3d..8fbdb7b 100644 --- a/cove/src/ui/euph/nick_list.rs +++ b/cove/src/ui/euph/nick_list.rs @@ -22,9 +22,10 @@ pub fn widget<'a>( list: &'a mut ListState<SessionId>, joined: &Joined, focused: bool, + nick_emoji: bool, ) -> impl Widget<UiError> + use<'a> { let mut list_builder = ListBuilder::new(); - render_rows(&mut list_builder, joined, focused); + render_rows(&mut list_builder, joined, focused, nick_emoji); list_builder.build(list) } @@ -70,6 +71,7 @@ fn render_rows( list_builder: &mut ListBuilder<'_, SessionId, Background<Text>>, joined: &Joined, focused: bool, + nick_emoji: bool, ) { let mut people = vec![]; let mut bots = vec![]; @@ -95,10 +97,38 @@ fn render_rows( lurkers.sort_unstable(); nurkers.sort_unstable(); - render_section(list_builder, "People", &people, &joined.session, focused); - render_section(list_builder, "Bots", &bots, &joined.session, focused); - render_section(list_builder, "Lurkers", &lurkers, &joined.session, focused); - render_section(list_builder, "Nurkers", &nurkers, &joined.session, focused); + render_section( + list_builder, + "People", + &people, + &joined.session, + focused, + nick_emoji, + ); + render_section( + list_builder, + "Bots", + &bots, + &joined.session, + focused, + nick_emoji, + ); + render_section( + list_builder, + "Lurkers", + &lurkers, + &joined.session, + focused, + nick_emoji, + ); + render_section( + list_builder, + "Nurkers", + &nurkers, + &joined.session, + focused, + nick_emoji, + ); } fn render_section( @@ -107,6 +137,7 @@ fn render_section( sessions: &[HalfSession], own_session: &SessionView, focused: bool, + nick_emoji: bool, ) { if sessions.is_empty() { return; @@ -124,7 +155,7 @@ fn render_section( list_builder.add_unsel(Text::new(row).background()); for session in sessions { - render_row(list_builder, session, own_session, focused); + render_row(list_builder, session, own_session, focused, nick_emoji); } } @@ -133,6 +164,7 @@ fn render_row( session: &HalfSession, own_session: &SessionView, focused: bool, + nick_emoji: bool, ) { let (name, style, style_inv, perms_style_inv) = if session.name.is_empty() { let name = "lurk".to_string(); @@ -166,16 +198,24 @@ fn render_row( " " }; + let emoji = if nick_emoji { + format!(" ({})", euph::user_id_emoji(&session.id)) + } else { + "".to_string() + }; + list_builder.add_sel(session.session_id.clone(), move |selected| { if focused && selected { let text = Styled::new_plain(owner) .then(name, style_inv) - .then(perms, perms_style_inv); + .then(perms, perms_style_inv) + .then(emoji, perms_style_inv); Text::new(text).background().with_style(style_inv) } else { let text = Styled::new_plain(owner) .then(&name, style) - .then_plain(perms); + .then_plain(perms) + .then_plain(emoji); Text::new(text).background() } }); diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index ebae5a8..fe1f768 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -291,11 +291,16 @@ impl EuphRoom { joined: &Joined, focus: Focus, ) -> BoxedAsync<'a, UiError> { - let nick_list_widget = nick_list::widget(nick_list, joined, focus == Focus::NickList) - .padding() - .with_right(1) - .border() - .desync(); + let nick_list_widget = nick_list::widget( + nick_list, + joined, + focus == Focus::NickList, + chat.nick_emoji(), + ) + .padding() + .with_right(1) + .border() + .desync(); let chat_widget = chat.widget(joined.session.name.clone(), focus == Focus::Chat); From 2ca6190d97fb9ae7da4189720f89ba0b7579b02b Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Sat, 31 May 2025 15:08:24 +0200 Subject: [PATCH 265/266] Use older ubuntu runner for older glibc version --- .github/workflows/build.yml | 6 +++--- CHANGELOG.md | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 012c48e..4660d0f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: os: - - ubuntu-latest + - ubuntu-22.04 - windows-latest - macos-latest - macos-13 @@ -59,11 +59,11 @@ jobs: - name: Zip artifacts run: | - chmod +x cove-ubuntu-latest/cove + chmod +x cove-ubuntu-22.04/cove chmod +x cove-windows-latest/cove.exe chmod +x cove-macos-latest/cove chmod +x cove-macos-13/cove - zip -jr "cove-$(cat cove-ubuntu-latest/host).zip" cove-ubuntu-latest/cove + zip -jr "cove-$(cat cove-ubuntu-22.04/host).zip" cove-ubuntu-22.04/cove zip -jr "cove-$(cat cove-windows-latest/host).zip" cove-windows-latest/cove.exe zip -jr "cove-$(cat cove-macos-latest/host).zip" cove-macos-latest/cove zip -jr "cove-$(cat cove-macos-13/host).zip" cove-macos-13/cove diff --git a/CHANGELOG.md b/CHANGELOG.md index 76fa94a..3f9ce8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Procedure when bumping the version number: ### Changed - Display emoji user id hashes in the nick list +- Compile linux binary with older glibc version ## v0.9.3 - 2025-05-31 From 10214f33695ad3eb8b87802d8d683cea5c618bf4 Mon Sep 17 00:00:00 2001 From: Joscha <joscha@plugh.de> Date: Fri, 27 Jun 2025 11:03:06 +0200 Subject: [PATCH 266/266] Fix clippy warning --- cove/src/ui/euph/room.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cove/src/ui/euph/room.rs b/cove/src/ui/euph/room.rs index fe1f768..7e8ff99 100644 --- a/cove/src/ui/euph/room.rs +++ b/cove/src/ui/euph/room.rs @@ -121,7 +121,7 @@ impl EuphRoom { .server_config .clone() .room(self.vault().room().name.clone()) - .name(format!("{room:?}-{}", next_instance_id)) + .name(format!("{room:?}-{next_instance_id}")) .human(true) .username(self.room_config.username.clone()) .force_username(self.room_config.force_username)