Skip to content

Commit

Permalink
Implement storing and rendering of mentions (#215)
Browse files Browse the repository at this point in the history
The placeholder obj tag sometimes has a space behind it and sometimes
not. For now, we replace just the obj tag, which might have several
spaces after it but it definitely works.

Closes #136
  • Loading branch information
boxdot authored Mar 18, 2023
1 parent 3f2c9d9 commit a0f93a6
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 14 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

### Added

- Copy selected message to clipboard ([#210])
- Copy selected message to clipboard ([#210])
- Implement storing and rendering of mentions ([#215, #136])

### Changed

Expand All @@ -17,6 +18,8 @@
[#203]: https://github.com/boxdot/gurk-rs/pull/203
[#204]: https://github.com/boxdot/gurk-rs/pull/204
[#210]: https://github.com/boxdot/gurk-rs/pull/210
[#136]: https://github.com/boxdot/gurk-rs/pull/136
[#215]: https://github.com/boxdot/gurk-rs/pull/215

## 0.3.0

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ tracing-subscriber = "0.3.16"
futures-channel = "0.3.25"
qr2term = "0.3.1"
clap = { version = "4.0.19", features = ["derive"] }
aho-corasick = "0.7.19"

# dev feature dependencies
prost = { version = "0.10.4", optional = true }
Expand Down
16 changes: 12 additions & 4 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::channels::SelectChannel;
use crate::config::Config;
use crate::data::{Channel, ChannelId, Message, TypingAction, TypingSet};
use crate::data::{BodyRange, Channel, ChannelId, Message, TypingAction, TypingSet};
use crate::input::Input;
use crate::receipt::{Receipt, ReceiptEvent, ReceiptHandler};
use crate::signal::{
Expand Down Expand Up @@ -440,6 +440,7 @@ impl App {
mut body,
attachments: attachment_pointers,
sticker,
body_ranges,
..
}),
..
Expand All @@ -451,7 +452,9 @@ impl App {
let attachments = self.save_attachments(attachment_pointers).await;
add_emoji_from_sticker(&mut body, sticker);

let message = Message::new(user_id, body, timestamp, attachments);
let body_ranges = body_ranges.into_iter().filter_map(BodyRange::from_proto);

let message = Message::new(user_id, body, body_ranges, timestamp, attachments);
(channel_idx, message)
}
// Direct/group message by us from a different device
Expand All @@ -477,6 +480,7 @@ impl App {
quote,
attachments: attachment_pointers,
sticker,
body_ranges,
..
}),
..
Expand Down Expand Up @@ -514,9 +518,10 @@ impl App {
add_emoji_from_sticker(&mut body, sticker);
let quote = quote.and_then(Message::from_quote).map(Box::new);
let attachments = self.save_attachments(attachment_pointers).await;
let body_ranges = body_ranges.into_iter().filter_map(BodyRange::from_proto);
let message = Message {
quote,
..Message::new(user_id, body, timestamp, attachments)
..Message::new(user_id, body, body_ranges, timestamp, attachments)
};

(channel_idx, message)
Expand All @@ -538,6 +543,7 @@ impl App {
quote,
attachments: attachment_pointers,
sticker,
body_ranges,
..
}),
) => {
Expand Down Expand Up @@ -594,9 +600,10 @@ impl App {
self.add_receipt_event(ReceiptEvent::new(uuid, timestamp, Receipt::Delivered));

let quote = quote.and_then(Message::from_quote).map(Box::new);
let body_ranges = body_ranges.into_iter().filter_map(BodyRange::from_proto);
let message = Message {
quote,
..Message::new(uuid, body, timestamp, attachments)
..Message::new(uuid, body, body_ranges, timestamp, attachments)
};

if message.is_empty() {
Expand Down Expand Up @@ -1395,6 +1402,7 @@ mod tests {
attachments: Default::default(),
reactions: Default::default(),
receipt: Default::default(),
body_ranges: Default::default(),
},
);

Expand Down
55 changes: 53 additions & 2 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::collections::HashSet;

use anyhow::anyhow;
use presage::prelude::proto::data_message::Quote;
use presage::prelude::proto::data_message::{self, Quote};
use presage::prelude::{GroupMasterKey, GroupSecretParams};
use serde::{Deserialize, Serialize};
use tracing::error;
Expand Down Expand Up @@ -128,12 +128,57 @@ pub struct Message {
pub reactions: Vec<(Uuid, String)>,
#[serde(default)]
pub receipt: Receipt,
#[serde(default)]
pub(crate) body_ranges: Vec<BodyRange>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct BodyRange {
pub(crate) start: u16,
pub(crate) end: u16,
pub(crate) value: AssociatedValue,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) enum AssociatedValue {
MentionUuid(Uuid),
}

impl From<&BodyRange> for data_message::BodyRange {
fn from(range: &BodyRange) -> Self {
match range.value {
AssociatedValue::MentionUuid(id) => Self {
start: Some(range.start.into()),
length: Some((range.end - range.start).into()),
associated_value: Some(data_message::body_range::AssociatedValue::MentionUuid(
id.to_string(),
)),
},
}
}
}

impl BodyRange {
pub(crate) fn from_proto(proto: data_message::BodyRange) -> Option<Self> {
let value = match proto.associated_value? {
data_message::body_range::AssociatedValue::MentionUuid(uuid) => {
let uuid = uuid.parse().ok()?;
AssociatedValue::MentionUuid(uuid)
}
};
Some(Self {
start: proto.start?.try_into().ok()?,
end: (proto.start? + proto.length?).try_into().ok()?,
value,
})
}
}

impl Message {
pub fn new(
pub(crate) fn new(
from_id: Uuid,
message: Option<String>,
body_ranges: impl IntoIterator<Item = BodyRange>,
arrived_at: u64,
attachments: Vec<Attachment>,
) -> Self {
Expand All @@ -145,6 +190,7 @@ impl Message {
attachments,
reactions: Default::default(),
receipt: Receipt::Sent,
body_ranges: body_ranges.into_iter().collect(),
}
}

Expand All @@ -157,6 +203,11 @@ impl Message {
attachments: Default::default(),
reactions: Default::default(),
receipt: Receipt::Sent,
body_ranges: quote
.body_ranges
.into_iter()
.filter_map(BodyRange::from_proto)
.collect(),
})
}

Expand Down
2 changes: 2 additions & 0 deletions src/signal/impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ impl SignalManager for PresageManager {
id: Some(message.arrived_at),
author_uuid: Some(message.from_id.to_string()),
text: message.message.clone(),
body_ranges: message.body_ranges.iter().map(From::from).collect(),
..Default::default()
});
let quote_message = quote.clone().and_then(Message::from_quote).map(Box::new);
Expand Down Expand Up @@ -213,6 +214,7 @@ impl SignalManager for PresageManager {
attachments: Default::default(),
reactions: Default::default(),
receipt: Receipt::Sent,
body_ranges: Default::default(),
}
}

Expand Down
1 change: 1 addition & 0 deletions src/signal/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ impl SignalManager for SignalManagerMock {
reactions: Default::default(),
// TODO make sure the message sending procedure did not fail
receipt: Receipt::Sent,
body_ranges: Default::default(),
};
self.sent_messages.borrow_mut().push(message.clone());
message
Expand Down
3 changes: 3 additions & 0 deletions src/storage/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ mod tests {
attachments: Default::default(),
reactions: Default::default(),
receipt: Default::default(),
body_ranges: Default::default(),
}],
unread_messages: 1,
typing: Some(TypingSet::SingleTyping(false)),
Expand All @@ -349,6 +350,7 @@ mod tests {
attachments: Default::default(),
reactions: Default::default(),
receipt: Default::default(),
body_ranges: Default::default(),
}],
unread_messages: 2,
typing: Some(TypingSet::GroupTyping(Default::default())),
Expand Down Expand Up @@ -496,6 +498,7 @@ mod tests {
attachments: Default::default(),
reactions: Default::default(),
receipt: Default::default(),
body_ranges: Default::default(),
},
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ expression: data
"quote": null,
"attachments": [],
"reactions": [],
"receipt": "Nothing"
"receipt": "Nothing",
"body_ranges": []
}
],
"unread_messages": 1
Expand Down Expand Up @@ -71,7 +72,8 @@ expression: data
"quote": null,
"attachments": [],
"reactions": [],
"receipt": "Nothing"
"receipt": "Nothing",
"body_ranges": []
}
],
"unread_messages": 2
Expand Down
81 changes: 76 additions & 5 deletions src/ui/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use uuid::Uuid;
use crate::app::App;
use crate::channels::SelectChannel;
use crate::cursor::Cursor;
use crate::data::Message;
use crate::data::{AssociatedValue, Message};
use crate::receipt::{Receipt, ReceiptEvent};
use crate::shortcuts::{ShortCut, SHORTCUTS};
use crate::storage::MessageId;
Expand Down Expand Up @@ -474,7 +474,8 @@ fn display_message(
.subsequent_indent(prefix);

// collect message text
let mut text = msg.message.clone().unwrap_or_default();
let text = msg.message.clone().unwrap_or_default();
let mut text = replace_mentions(msg, names, text);
add_attachments(msg, &mut text);
if text.is_empty() {
return None; // no text => nothing to render
Expand Down Expand Up @@ -543,6 +544,32 @@ fn display_message(
Some(ListItem::new(Text::from(spans)))
}

fn replace_mentions(msg: &Message, names: &NameResolver, text: String) -> String {
if msg.body_ranges.is_empty() {
return text;
}

let ac = aho_corasick::AhoCorasickBuilder::new()
.build(std::iter::repeat("").take(msg.body_ranges.len()));
let mut buf = String::with_capacity(text.len());
let mut ranges = msg.body_ranges.iter();
ac.replace_all_with(&text, &mut buf, |_, _, dst| {
// TODO: check ranges?
if let Some(range) = ranges.next() {
let (name, _color) = match range.value {
AssociatedValue::MentionUuid(id) => names.resolve(id),
};
dst.push('@');
dst.push_str(&name);
true
} else {
false
}
});

buf
}

fn display_date_line(
msg_timestamp: u64,
previous_msg_day: &mut i32,
Expand Down Expand Up @@ -668,7 +695,8 @@ fn draw_help<B: Backend>(f: &mut Frame<B>, app: &mut App, area: Rect) {

fn displayed_quote(names: &NameResolver, quote: &Message) -> Option<String> {
let (name, _) = names.resolve(quote.from_id);
Some(format!("({}) {}", name, quote.message.as_ref()?))
let text = format!("({}) {}", name, quote.message.as_ref()?);
Some(replace_mentions(quote, names, text))
}

fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
Expand Down Expand Up @@ -699,6 +727,7 @@ fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {

#[cfg(test)]
mod tests {
use crate::data::{AssociatedValue, BodyRange};
use crate::signal::Attachment;

use super::*;
Expand Down Expand Up @@ -729,9 +758,10 @@ mod tests {
message: None,
arrived_at: 1642334397421,
quote: None,
attachments: vec![],
reactions: vec![],
attachments: Default::default(),
reactions: Default::default(),
receipt: Receipt::Sent,
body_ranges: Default::default(),
}
}

Expand Down Expand Up @@ -914,4 +944,45 @@ mod tests {
])]));
assert_eq!(rendered, Some(expected));
}

#[test]
fn test_display_mention() {
let user_id = Uuid::from_u128(1);
let names = NameResolver::single_user(user_id, "boxdot".to_string(), Color::Green);
let msg = Message {
from_id: user_id,
message: Some("Mention  and even more  . End".into()),
receipt: Receipt::Read,
body_ranges: vec![
BodyRange {
start: 8,
end: 9,
value: AssociatedValue::MentionUuid(user_id),
},
BodyRange {
start: 25,
end: 26,
value: AssociatedValue::MentionUuid(user_id),
},
],
..test_message()
};
let show_receipt = ShowReceipt::from_msg(&msg, USER_ID, true);
let rendered = display_message(&names, &msg, PREFIX, WIDTH, HEIGHT, show_receipt);

let expected = ListItem::new(Text::from(vec![
Spans(vec![
Span::styled(" ", Style::default().fg(Color::Yellow)),
Span::styled(
display_time(msg.arrived_at),
Style::default().fg(Color::Yellow),
),
Span::styled("boxdot", Style::default().fg(Color::Green)),
Span::raw(": "),
Span::raw("Mention @boxdot and even more @boxdot ."),
]),
Spans(vec![Span::raw(" End")]),
]));
assert_eq!(rendered, Some(expected));
}
}

0 comments on commit a0f93a6

Please sign in to comment.