Skip to content

Commit

Permalink
Channel chat: Add edit message (zed-industries#9035)
Browse files Browse the repository at this point in the history
**Summary**:
- Removed reply message from message_menu
- Made render_popover_buttons a bit more reusable
- Fixed issue that you can't close the reply/edit preview when you are
not focusing the message editor
- Notify only the new people that were mentioned inside the edited
message

**Follow up**
- Fix that we update the notification message for the people that we
mentioned already
- Fix that we remove the notification when a message gets deleted.
  - Fix last acknowledge message id is in correct now

**Todo**:
- [x] Add tests
- [x] Change new added bindings to the `Editor::Cancel` event.

Release Notes:

- Added editing of chat messages
([zed-industries#6707](zed-industries#6707)).

<img width="239" alt="Screenshot 2024-03-09 at 11 55 23"
src="https://github.com/zed-industries/zed/assets/62463826/b0949f0d-0f8b-43e1-ac20-4c6d40ac41e1">
<img width="240" alt="Screenshot 2024-03-13 at 13 34 23"
src="https://github.com/zed-industries/zed/assets/62463826/d0636da2-c5aa-4fed-858e-4bebe5695ba7">

---------

Co-authored-by: Bennet Bo Fenner <[email protected]>
Co-authored-by: Conrad Irwin <[email protected]>
  • Loading branch information
3 people authored and pjlast committed Mar 26, 2024
1 parent 46c808d commit 06be943
Show file tree
Hide file tree
Showing 16 changed files with 741 additions and 147 deletions.
4 changes: 4 additions & 0 deletions assets/icons/pencil.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 0 additions & 6 deletions assets/keymaps/default-macos.json
Original file line number Diff line number Diff line change
Expand Up @@ -597,12 +597,6 @@
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "ChatPanel > MessageEditor",
"bindings": {
"escape": "chat_panel::CloseReplyPreview"
}
},
{
"context": "Terminal",
"bindings": {
Expand Down
104 changes: 104 additions & 0 deletions crates/channel/src/channel_chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub struct ChannelMessage {
pub nonce: u128,
pub mentions: Vec<(Range<usize>, UserId)>,
pub reply_to_message_id: Option<u64>,
pub edited_at: Option<OffsetDateTime>,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down Expand Up @@ -83,6 +84,10 @@ pub enum ChannelChatEvent {
old_range: Range<usize>,
new_count: usize,
},
UpdateMessage {
message_id: ChannelMessageId,
message_ix: usize,
},
NewMessage {
channel_id: ChannelId,
message_id: u64,
Expand All @@ -93,6 +98,7 @@ impl EventEmitter<ChannelChatEvent> for ChannelChat {}
pub fn init(client: &Arc<Client>) {
client.add_model_message_handler(ChannelChat::handle_message_sent);
client.add_model_message_handler(ChannelChat::handle_message_removed);
client.add_model_message_handler(ChannelChat::handle_message_updated);
}

impl ChannelChat {
Expand Down Expand Up @@ -189,6 +195,7 @@ impl ChannelChat {
mentions: message.mentions.clone(),
nonce,
reply_to_message_id: message.reply_to_message_id,
edited_at: None,
},
&(),
),
Expand Down Expand Up @@ -234,6 +241,35 @@ impl ChannelChat {
})
}

pub fn update_message(
&mut self,
id: u64,
message: MessageParams,
cx: &mut ModelContext<Self>,
) -> Result<Task<Result<()>>> {
self.message_update(
ChannelMessageId::Saved(id),
message.text.clone(),
message.mentions.clone(),
Some(OffsetDateTime::now_utc()),
cx,
);

let nonce: u128 = self.rng.gen();

let request = self.rpc.request(proto::UpdateChannelMessage {
channel_id: self.channel_id.0,
message_id: id,
body: message.text,
nonce: Some(nonce.into()),
mentions: mentions_to_proto(&message.mentions),
});
Ok(cx.spawn(move |_, _| async move {
request.await?;
Ok(())
}))
}

pub fn load_more_messages(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<Option<()>>> {
if self.loaded_all_messages {
return None;
Expand Down Expand Up @@ -523,6 +559,32 @@ impl ChannelChat {
Ok(())
}

async fn handle_message_updated(
this: Model<Self>,
message: TypedEnvelope<proto::ChannelMessageUpdate>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
let message = message
.payload
.message
.ok_or_else(|| anyhow!("empty message"))?;

let message = ChannelMessage::from_proto(message, &user_store, &mut cx).await?;

this.update(&mut cx, |this, cx| {
this.message_update(
message.id,
message.body,
message.mentions,
message.edited_at,
cx,
)
})?;
Ok(())
}

fn insert_messages(&mut self, messages: SumTree<ChannelMessage>, cx: &mut ModelContext<Self>) {
if let Some((first_message, last_message)) = messages.first().zip(messages.last()) {
let nonces = messages
Expand Down Expand Up @@ -599,6 +661,38 @@ impl ChannelChat {
}
}
}

fn message_update(
&mut self,
id: ChannelMessageId,
body: String,
mentions: Vec<(Range<usize>, u64)>,
edited_at: Option<OffsetDateTime>,
cx: &mut ModelContext<Self>,
) {
let mut cursor = self.messages.cursor::<ChannelMessageId>();
let mut messages = cursor.slice(&id, Bias::Left, &());
let ix = messages.summary().count;

if let Some(mut message_to_update) = cursor.item().cloned() {
message_to_update.body = body;
message_to_update.mentions = mentions;
message_to_update.edited_at = edited_at;
messages.push(message_to_update, &());
cursor.next(&());
}

messages.append(cursor.suffix(&()), &());
drop(cursor);
self.messages = messages;

cx.emit(ChannelChatEvent::UpdateMessage {
message_ix: ix,
message_id: id,
});

cx.notify();
}
}

async fn messages_from_proto(
Expand All @@ -623,6 +717,15 @@ impl ChannelMessage {
user_store.get_user(message.sender_id, cx)
})?
.await?;

let edited_at = message.edited_at.and_then(|t| -> Option<OffsetDateTime> {
if let Ok(a) = OffsetDateTime::from_unix_timestamp(t as i64) {
return Some(a);
}

None
});

Ok(ChannelMessage {
id: ChannelMessageId::Saved(message.id),
body: message.body,
Expand All @@ -641,6 +744,7 @@ impl ChannelMessage {
.ok_or_else(|| anyhow!("nonce is required"))?
.into(),
reply_to_message_id: message.reply_to_message_id,
edited_at,
})
}

Expand Down
5 changes: 5 additions & 0 deletions crates/channel/src/channel_store_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
mentions: vec![],
nonce: Some(1.into()),
reply_to_message_id: None,
edited_at: None,
},
proto::ChannelMessage {
id: 11,
Expand All @@ -195,6 +196,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
mentions: vec![],
nonce: Some(2.into()),
reply_to_message_id: None,
edited_at: None,
},
],
done: false,
Expand Down Expand Up @@ -243,6 +245,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
mentions: vec![],
nonce: Some(3.into()),
reply_to_message_id: None,
edited_at: None,
}),
});

Expand Down Expand Up @@ -297,6 +300,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
nonce: Some(4.into()),
mentions: vec![],
reply_to_message_id: None,
edited_at: None,
},
proto::ChannelMessage {
id: 9,
Expand All @@ -306,6 +310,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
nonce: Some(5.into()),
mentions: vec![],
reply_to_message_id: None,
edited_at: None,
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ CREATE TABLE IF NOT EXISTS "channel_messages" (
"sender_id" INTEGER NOT NULL REFERENCES users (id),
"body" TEXT NOT NULL,
"sent_at" TIMESTAMP,
"edited_at" TIMESTAMP,
"nonce" BLOB NOT NULL,
"reply_to_message_id" INTEGER DEFAULT NULL
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE channel_messages ADD edited_at TIMESTAMP DEFAULT NULL;
8 changes: 8 additions & 0 deletions crates/collab/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,14 @@ pub struct CreatedChannelMessage {
pub notifications: NotificationBatch,
}

pub struct UpdatedChannelMessage {
pub message_id: MessageId,
pub participant_connection_ids: Vec<ConnectionId>,
pub notifications: NotificationBatch,
pub reply_to_message_id: Option<MessageId>,
pub timestamp: PrimitiveDateTime,
}

#[derive(Clone, Debug, PartialEq, Eq, FromQueryResult, Serialize, Deserialize)]
pub struct Invite {
pub email_address: String,
Expand Down
Loading

0 comments on commit 06be943

Please sign in to comment.