Browse Source

room-history: Detect mentions with an iterator

Allow to reuse the logic in other places
merge-requests/1327/merge
Kévin Commaille 4 years ago
parent
commit
31e3cbc240
No known key found for this signature in database
GPG Key ID: DD507DAE96E8245C
  1. 148
      src/session/content/room_history/mod.rs

148
src/session/content/room_history/mod.rs

@ -461,6 +461,12 @@ impl RoomHistory {
self.imp().room.borrow().clone()
}
/// Get an iterator over chunks of the message entry's text between the
/// given start and end, split by mentions.
fn split_buffer_mentions(&self, start: gtk::TextIter, end: gtk::TextIter) -> SplitMentions {
SplitMentions { iter: start, end }
}
pub fn send_text_message(&self) {
let priv_ = self.imp();
let buffer = priv_.message_entry.buffer();
@ -472,56 +478,22 @@ impl RoomHistory {
let mut plain_body = String::with_capacity(body_len);
// formatted_body is Markdown if is_markdown is true, and HTML if false.
let mut formatted_body = String::with_capacity(body_len);
// uncopied_text_location is the start of the text we haven't copied to
// plain_body and formatted_body.
let mut uncopied_text_location = start_iter;
let mut iter = start_iter;
loop {
if let Some(anchor) = iter.child_anchor() {
let widgets = anchor.widgets();
let pill = widgets.first().unwrap().downcast_ref::<Pill>().unwrap();
let (url, label) = pill
.user()
.map(|user| {
(
user.user_id().matrix_to_uri().to_string(),
user.display_name(),
)
})
.or_else(|| {
pill.room().map(|room| {
(
// No server name needed. matrix.to URIs for mentions aren't
// routable
room.room_id().matrix_to_uri().to_string(),
room.display_name(),
)
})
})
.unwrap();
// Add more uncopied characters from message
let some_text = buffer.text(&uncopied_text_location, &iter, false);
plain_body.push_str(&some_text);
formatted_body.push_str(&some_text);
uncopied_text_location = iter;
// Add mention
has_mentions = true;
plain_body.push_str(&label);
formatted_body.push_str(&if is_markdown {
format!("[{}]({})", label, url)
} else {
format!("<a href='{}'>{}</a>", url, label)
});
}
if !iter.forward_char() {
// Add remaining uncopied characters
let some_text = buffer.text(&uncopied_text_location, &iter, false);
plain_body.push_str(&some_text);
formatted_body.push_str(&some_text);
break;
for chunk in self.split_buffer_mentions(start_iter, end_iter) {
match chunk {
MentionChunk::Text(text) => {
plain_body.push_str(&text);
formatted_body.push_str(&text);
}
MentionChunk::Mention { name, uri } => {
has_mentions = true;
plain_body.push_str(&name);
formatted_body.push_str(&if is_markdown {
format!("[{name}]({uri})")
} else {
format!("<a href=\"{uri}\">{name}</a>")
});
}
}
}
@ -959,3 +931,79 @@ impl Default for RoomHistory {
Self::new()
}
}
enum MentionChunk {
Text(String),
Mention { name: String, uri: String },
}
struct SplitMentions {
iter: gtk::TextIter,
end: gtk::TextIter,
}
impl Iterator for SplitMentions {
type Item = MentionChunk;
fn next(&mut self) -> Option<Self::Item> {
if self.iter == self.end {
// We reached the end.
return None;
}
if let Some(pill) = self
.iter
.child_anchor()
.map(|anchor| anchor.widgets())
.as_ref()
.and_then(|widgets| widgets.first())
.and_then(|widget| widget.downcast_ref::<Pill>())
{
// This chunk is a mention.
let (name, uri) = if let Some(user) = pill.user() {
(
user.display_name(),
user.user_id().matrix_to_uri().to_string(),
)
} else if let Some(room) = pill.room() {
(
room.display_name(),
room.room_id().matrix_to_uri().to_string(),
)
} else {
unreachable!()
};
self.iter.forward_cursor_position();
return Some(MentionChunk::Mention { name, uri });
}
// This chunk is not a mention. Go forward until the next mention or the
// end and return the text in between.
let start = self.iter;
while self.iter.forward_cursor_position() && self.iter != self.end {
if self
.iter
.child_anchor()
.map(|anchor| anchor.widgets())
.as_ref()
.and_then(|widgets| widgets.first())
.and_then(|widget| widget.downcast_ref::<Pill>())
.is_some()
{
break;
}
}
let text = self.iter.buffer().text(&start, &self.iter, false);
// We might somehow have an empty string before the end, or at the end,
// because of hidden `char`s in the buffer, so we must only return
// `None` when we have an empty string at the end.
if self.iter == self.end && text.is_empty() {
None
} else {
Some(MentionChunk::Text(text.into()))
}
}
}

Loading…
Cancel
Save