Skip to content

Commit 42b3784

Browse files
committed
Add MSC4357 live messaging support
1 parent 55456db commit 42b3784

File tree

8 files changed

+1202
-56
lines changed

8 files changed

+1202
-56
lines changed

src/base.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ pub enum MessageAction {
178178
/// and error when it doesn't recognize it. The second [bool] argument forces it to be
179179
/// interpreted literally when it is `true`.
180180
Unreact(Option<String>, bool),
181+
181182
}
182183

183184
/// An action taken in the currently selected space.
@@ -898,6 +899,9 @@ pub struct RoomInfo {
898899
/// A map of message identifiers to thread replies.
899900
threads: HashMap<OwnedEventId, Messages>,
900901

902+
/// Set of event IDs for messages that are live (being typed)
903+
pub live_message_ids: HashSet<OwnedEventId>,
904+
901905
/// Whether the scrollback for this room is currently being fetched.
902906
pub fetching: bool,
903907

@@ -929,6 +933,7 @@ impl Default for RoomInfo {
929933
user_receipts: Default::default(),
930934
reactions: Default::default(),
931935
threads: Default::default(),
936+
live_message_ids: Default::default(),
932937
fetching: Default::default(),
933938
fetch_id: Default::default(),
934939
fetch_last: Default::default(),
@@ -1087,6 +1092,7 @@ impl RoomInfo {
10871092
let event_id = msg.event_id;
10881093
let new_msgtype = msg.new_content;
10891094

1095+
10901096
let Some(EventLocation::Message(thread, key)) = self.keys.get(&event_id) else {
10911097
return;
10921098
};
@@ -1103,9 +1109,12 @@ impl RoomInfo {
11031109
return;
11041110
};
11051111

1112+
// Update live status based on live_message_ids
1113+
msg.is_live = self.live_message_ids.contains(&event_id);
1114+
11061115
match &mut msg.event {
11071116
MessageEvent::Original(orig) => {
1108-
orig.content.apply_replacement(new_msgtype);
1117+
orig.content.apply_replacement(new_msgtype.clone());
11091118
},
11101119
MessageEvent::Local(_, content) => {
11111120
content.apply_replacement(new_msgtype);
@@ -1165,22 +1174,37 @@ impl RoomInfo {
11651174
let event_id = msg.event_id().to_owned();
11661175
let key = (msg.origin_server_ts().into(), event_id.clone());
11671176

1177+
// Check if this is a live message
1178+
let is_live = self.live_message_ids.contains(&event_id);
1179+
11681180
let loc = EventLocation::Message(None, key.clone());
1169-
self.keys.insert(event_id, loc);
1170-
self.messages.insert_message(key, msg);
1181+
self.keys.insert(event_id.clone(), loc);
1182+
1183+
// Convert to Message and set is_live flag if needed
1184+
let mut message: Message = msg.into();
1185+
message.is_live = is_live;
1186+
1187+
self.messages.insert_message(key, message);
11711188
}
11721189

11731190
fn insert_thread(&mut self, msg: RoomMessageEvent, thread_root: OwnedEventId) {
11741191
let event_id = msg.event_id().to_owned();
11751192
let key = (msg.origin_server_ts().into(), event_id.clone());
11761193

1194+
// Check if this is a live message
1195+
let is_live = self.live_message_ids.contains(&event_id);
1196+
11771197
let replies = self
11781198
.threads
11791199
.entry(thread_root.clone())
11801200
.or_insert_with(|| Messages::thread(thread_root.clone()));
11811201
let loc = EventLocation::Message(Some(thread_root), key.clone());
1182-
self.keys.insert(event_id, loc);
1183-
replies.insert_message(key, msg);
1202+
self.keys.insert(event_id.clone(), loc);
1203+
1204+
let mut message: Message = msg.into();
1205+
message.is_live = is_live;
1206+
1207+
replies.insert_message(key, message);
11841208
}
11851209

11861210
/// Insert a new message event.

src/message/compose.rs

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use matrix_sdk::ruma::events::room::message::{
1616
};
1717

1818
#[derive(Clone, Debug, Default)]
19-
enum SlashCommand {
19+
pub enum SlashCommand {
2020
/// Send an emote message.
2121
Emote,
2222

@@ -47,6 +47,9 @@ enum SlashCommand {
4747

4848
/// Send a message with heart effects in clients that show them.
4949
SpaceInvaders,
50+
51+
/// Send a live message that updates as you type (MSC4357)
52+
Live,
5053
}
5154

5255
impl SlashCommand {
@@ -95,6 +98,11 @@ impl SlashCommand {
9598
Default::default(),
9699
)?
97100
},
101+
SlashCommand::Live => {
102+
// Live messages are handled specially in the chat window
103+
// This should not be called directly
104+
return Err(anyhow::anyhow!("Live message"));
105+
},
98106
};
99107

100108
Ok(msgtype)
@@ -104,6 +112,7 @@ impl SlashCommand {
104112
fn parse_slash_command_inner(input: &str) -> IResult<&str, SlashCommand> {
105113
let (input, _) = space0(input)?;
106114
let (input, slash) = alt((
115+
// Commands that require text after them (with space)
107116
value(SlashCommand::Emote, tag("/me ")),
108117
value(SlashCommand::Html, tag("/h ")),
109118
value(SlashCommand::Html, tag("/html ")),
@@ -118,6 +127,7 @@ fn parse_slash_command_inner(input: &str) -> IResult<&str, SlashCommand> {
118127
value(SlashCommand::Rainfall, tag("/rainfall ")),
119128
value(SlashCommand::Snowfall, tag("/snowfall ")),
120129
value(SlashCommand::SpaceInvaders, tag("/spaceinvaders ")),
130+
value(SlashCommand::Live, tag("/live ")),
121131
))(input)?;
122132
let (input, _) = space0(input)?;
123133

@@ -170,12 +180,23 @@ fn text_to_message_content(input: String) -> TextMessageEventContent {
170180
}
171181
}
172182

183+
#[allow(dead_code)]
173184
pub fn text_to_message(input: String) -> RoomMessageEventContent {
174-
let msg = parse_slash_command(input.as_str())
175-
.and_then(|(input, slash)| slash.to_message(input))
176-
.unwrap_or_else(|_| MessageType::Text(text_to_message_content(input)));
185+
text_to_message_with_command(input).0
186+
}
177187

178-
RoomMessageEventContent::new(msg)
188+
pub fn text_to_message_with_command(input: String) -> (RoomMessageEventContent, Option<SlashCommand>) {
189+
match parse_slash_command(input.as_str()) {
190+
Ok((text, slash)) => {
191+
let msg = slash.to_message(text)
192+
.unwrap_or_else(|_| MessageType::Text(text_to_message_content(text.to_string())));
193+
(RoomMessageEventContent::new(msg), Some(slash))
194+
},
195+
Err(_) => {
196+
let msg = MessageType::Text(text_to_message_content(input));
197+
(RoomMessageEventContent::new(msg), None)
198+
}
199+
}
179200
}
180201

181202
#[cfg(test)]
@@ -330,8 +351,7 @@ pub mod tests {
330351
assert_eq!(content.body, "<b>bold</b>");
331352
assert_eq!(content.formatted.unwrap().body, "<b>bold</b>");
332353

333-
let MessageType::Text(content) = text_to_message("/plain <b>bold</b>".into()).msgtype
334-
else {
354+
let MessageType::Text(content) = text_to_message("/plain <b>bold</b>".into()).msgtype else {
335355
panic!("Expected MessageType::Text");
336356
};
337357
assert_eq!(content.body, "<b>bold</b>");
@@ -343,33 +363,40 @@ pub mod tests {
343363
assert_eq!(content.body, "<b>bold</b>");
344364
assert!(content.formatted.is_none(), "{:?}", content.formatted);
345365

346-
let MessageType::Emote(content) = text_to_message("/me *bold*".into()).msgtype else {
366+
let (msg, _) = text_to_message_with_command("/me *bold*".into());
367+
let MessageType::Emote(content) = msg.msgtype else {
347368
panic!("Expected MessageType::Emote");
348369
};
349370
assert_eq!(content.body, "*bold*");
350371
assert_eq!(content.formatted.unwrap().body, "<p><em>bold</em></p>\n");
351372

352-
let content = text_to_message("/confetti hello".into()).msgtype;
373+
let (msg, _) = text_to_message_with_command("/confetti hello".into());
374+
let content = msg.msgtype;
353375
assert_eq!(content.msgtype(), "nic.custom.confetti");
354376
assert_eq!(content.body(), "hello");
355377

356-
let content = text_to_message("/fireworks hello".into()).msgtype;
378+
let (msg, _) = text_to_message_with_command("/fireworks hello".into());
379+
let content = msg.msgtype;
357380
assert_eq!(content.msgtype(), "nic.custom.fireworks");
358381
assert_eq!(content.body(), "hello");
359382

360-
let content = text_to_message("/hearts hello".into()).msgtype;
383+
let (msg, _) = text_to_message_with_command("/hearts hello".into());
384+
let content = msg.msgtype;
361385
assert_eq!(content.msgtype(), "io.element.effect.hearts");
362386
assert_eq!(content.body(), "hello");
363387

364-
let content = text_to_message("/rainfall hello".into()).msgtype;
388+
let (msg, _) = text_to_message_with_command("/rainfall hello".into());
389+
let content = msg.msgtype;
365390
assert_eq!(content.msgtype(), "io.element.effect.rainfall");
366391
assert_eq!(content.body(), "hello");
367392

368-
let content = text_to_message("/snowfall hello".into()).msgtype;
393+
let (msg, _) = text_to_message_with_command("/snowfall hello".into());
394+
let content = msg.msgtype;
369395
assert_eq!(content.msgtype(), "io.element.effect.snowfall");
370396
assert_eq!(content.body(), "hello");
371397

372-
let content = text_to_message("/spaceinvaders hello".into()).msgtype;
398+
let (msg, _) = text_to_message_with_command("/spaceinvaders hello".into());
399+
let content = msg.msgtype;
373400
assert_eq!(content.msgtype(), "io.element.effects.space_invaders");
374401
assert_eq!(content.body(), "hello");
375402
}

0 commit comments

Comments
 (0)