From 4f9723704999a5981f5166b06953e575591c4dce Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Wed, 18 Jan 2023 11:33:13 -0300 Subject: [PATCH] highlight npub/etc references in the textarea. --- Cargo.lock | 23 ++++++++++++++++ Cargo.toml | 1 + src/tags.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/ui/feed.rs | 11 ++++++-- 4 files changed, 108 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 48048e4d..5da60042 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1712,6 +1712,7 @@ dependencies = [ "image", "lazy_static", "linkify", + "memoize", "nostr-types", "parking_lot", "rand 0.8.5", @@ -2191,6 +2192,28 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoize" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c25d125e4063f313300d87c8f658e5b3d69257095df9a4221c12ba50b0421bff" +dependencies = [ + "lazy_static", + "memoize-inner", +] + +[[package]] +name = "memoize-inner" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7d5160e6ffcc59d4c571c38238ec5b7065bc91a5a24f511988dabcddda723" +dependencies = [ + "lazy_static", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "mime" version = "0.3.16" diff --git a/Cargo.toml b/Cargo.toml index 21440a8c..37666ca4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ http = "0.2" image = { version = "0.24", features = [ "png", "jpeg" ] } lazy_static = "1.4" linkify = "0.9" +memoize = "0.3" nostr-types = { git = "https://github.com/mikedilger/nostr-types" } parking_lot = "0.12" rand = "0.8" diff --git a/src/tags.rs b/src/tags.rs index 53ad9745..6e3b1fb8 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -1,4 +1,9 @@ use crate::db::DbRelay; +use eframe::epaint::{ + text::{LayoutJob, TextFormat}, + Color32, FontFamily, FontId, +}; +use memoize::memoize; use nostr_types::{Id, PublicKey, PublicKeyHex, Tag}; pub fn keys_from_text(text: &str) -> Vec<(String, PublicKey)> { @@ -87,6 +92,76 @@ pub async fn add_event_to_tags(existing_tags: &mut Vec, added: Id, marker: } } +enum HighlightType { + Nothing, + PublicKey, + Event, +} + +#[memoize] +pub fn textarea_highlighter(text: String) -> LayoutJob { + let mut job = LayoutJob::default(); + + let ids = notes_from_text(&text); + let pks = keys_from_text(&text); + + // we will gather indices such that we can split the text in chunks + let mut indices: Vec<(usize, HighlightType)> = vec![]; + for pk in pks { + for m in text.match_indices(&pk.0) { + indices.push((m.0, HighlightType::Nothing)); + indices.push((m.0 + pk.0.len(), HighlightType::PublicKey)); + } + } + for id in ids { + for m in text.match_indices(&id.0) { + indices.push((m.0, HighlightType::Nothing)); + indices.push((m.0 + id.0.len(), HighlightType::Event)); + } + } + indices.sort_by_key(|x| x.0); + indices.dedup_by_key(|x| x.0); + + // add a breakpoint at the end if it doesn't exist + if indices.is_empty() || indices[indices.len() - 1].0 != text.len() { + indices.push((text.len(), HighlightType::Nothing)); + } + + // now we will add each chunk back to the textarea with custom formatting + let mut curr = 0; + for (index, highlight) in indices { + let chunk = &text[curr..index]; + + job.append( + chunk, + 0.0, + match highlight { + HighlightType::Nothing => TextFormat { + font_id: FontId::new(14.0, FontFamily::Proportional), + color: Color32::BLACK, + ..Default::default() + }, + HighlightType::PublicKey => TextFormat { + font_id: FontId::new(16.0, FontFamily::Monospace), + background: Color32::LIGHT_GRAY, + color: Color32::DARK_GREEN, + ..Default::default() + }, + HighlightType::Event => TextFormat { + font_id: FontId::new(16.0, FontFamily::Monospace), + background: Color32::LIGHT_GRAY, + color: Color32::DARK_RED, + ..Default::default() + }, + }, + ); + + curr = index; + } + + job +} + #[cfg(test)] mod test { use super::*; diff --git a/src/ui/feed.rs b/src/ui/feed.rs index 8d407268..ec8bf80b 100644 --- a/src/ui/feed.rs +++ b/src/ui/feed.rs @@ -2,7 +2,7 @@ use super::{GossipUi, Page}; use crate::comms::ToOverlordMessage; use crate::feed::FeedKind; use crate::globals::{Globals, GLOBALS}; -use crate::tags::keys_from_text; +use crate::tags::{keys_from_text, textarea_highlighter}; use crate::ui::widgets::CopyButton; use crate::AVATAR_SIZE_F32; use eframe::egui; @@ -185,11 +185,18 @@ fn real_posting_area(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram }); // Text area + let mut layouter = |ui: &Ui, text: &str, wrap_width: f32| { + let mut layout_job = textarea_highlighter(text.to_owned()); + layout_job.wrap.max_width = wrap_width; + ui.fonts().layout_job(layout_job) + }; + ui.add( TextEdit::multiline(&mut app.draft) .hint_text("Type your message here") .desired_width(f32::INFINITY) - .lock_focus(true), + .lock_focus(true) + .layouter(&mut layouter), ); });