From 405ff1d9dbacf1e54f6e4bd389563659cb02ce31 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 24 Aug 2021 12:23:50 +0200 Subject: [PATCH] Render chat messages in `ChatPanel` --- server/src/rpc.rs | 2 +- zed/src/channel.rs | 113 ++++++++++++++++++++++++++++++++---------- zed/src/chat_panel.rs | 66 +++++++++++++++++++++--- zed/src/workspace.rs | 10 +++- 4 files changed, 155 insertions(+), 36 deletions(-) diff --git a/server/src/rpc.rs b/server/src/rpc.rs index f489bfb57debddc255eee2e9da06cde6f011c264..a44738e8ffb1ae77715b3f7a5ec13056675973a3 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -1520,7 +1520,7 @@ mod tests { fn channel_messages(channel: &Channel) -> Vec<(u64, String)> { channel .messages() - .iter() + .cursor::<(), ()>() .map(|m| (m.sender_id, m.body.clone())) .collect() } diff --git a/zed/src/channel.rs b/zed/src/channel.rs index 148d541f03cea6c76fd336ce05785176b9755843..a82b7d4aee15cfdaad093afdb6823e8543d6a8bd 100644 --- a/zed/src/channel.rs +++ b/zed/src/channel.rs @@ -3,10 +3,13 @@ use crate::{ util::log_async_errors, }; use anyhow::{anyhow, Context, Result}; -use gpui::{Entity, ModelContext, ModelHandle, MutableAppContext, WeakModelHandle}; +use gpui::{ + sum_tree::{self, Bias, SumTree}, + Entity, ModelContext, ModelHandle, MutableAppContext, WeakModelHandle, +}; use std::{ - cmp::Ordering, - collections::{hash_map, BTreeSet, HashMap}, + collections::{hash_map, HashMap}, + ops::Range, sync::Arc, }; use zrpc::{ @@ -28,13 +31,14 @@ pub struct ChannelDetails { pub struct Channel { details: ChannelDetails, - messages: BTreeSet, + messages: SumTree, pending_messages: Vec, next_local_message_id: u64, rpc: Arc, _subscription: rpc::Subscription, } +#[derive(Clone, Debug)] pub struct ChannelMessage { pub id: u64, pub sender_id: u64, @@ -46,10 +50,26 @@ pub struct PendingChannelMessage { local_id: u64, } -pub enum Event {} +#[derive(Clone, Debug, Default)] +pub struct ChannelMessageSummary { + max_id: u64, + count: Count, +} + +#[derive(Copy, Clone, Debug, Default)] +struct Count(usize); + +pub enum ChannelListEvent {} + +pub enum ChannelEvent { + Message { + old_range: Range, + message: ChannelMessage, + }, +} impl Entity for ChannelList { - type Event = Event; + type Event = ChannelListEvent; } impl ChannelList { @@ -107,7 +127,7 @@ impl ChannelList { } impl Entity for Channel { - type Event = (); + type Event = ChannelEvent; fn release(&mut self, cx: &mut MutableAppContext) { let rpc = self.rpc.clone(); @@ -132,7 +152,10 @@ impl Channel { cx.spawn(|channel, mut cx| async move { match rpc.request(proto::JoinChannel { channel_id }).await { Ok(response) => channel.update(&mut cx, |channel, cx| { - channel.messages = response.messages.into_iter().map(Into::into).collect(); + channel.messages = SumTree::new(); + channel + .messages + .extend(response.messages.into_iter().map(Into::into), &()); cx.notify(); }), Err(error) => log::error!("error joining channel: {}", error), @@ -171,12 +194,14 @@ impl Channel { .binary_search_by_key(&local_id, |msg| msg.local_id) { let body = this.pending_messages.remove(i).body; - this.messages.insert(ChannelMessage { - id: response.message_id, - sender_id: current_user_id, - body, - }); - cx.notify(); + this.insert_message( + ChannelMessage { + id: response.message_id, + sender_id: current_user_id, + body, + }, + cx, + ); } }); Ok(()) @@ -187,7 +212,7 @@ impl Channel { Ok(()) } - pub fn messages(&self) -> &BTreeSet { + pub fn messages(&self) -> &SumTree { &self.messages } @@ -209,10 +234,31 @@ impl Channel { .payload .message .ok_or_else(|| anyhow!("empty message"))?; - self.messages.insert(message.into()); - cx.notify(); + self.insert_message(message.into(), cx); Ok(()) } + + fn insert_message(&mut self, message: ChannelMessage, cx: &mut ModelContext) { + let mut old_cursor = self.messages.cursor::(); + let mut new_messages = old_cursor.slice(&message.id, Bias::Left, &()); + let start_ix = old_cursor.sum_start().0; + let mut end_ix = start_ix; + if old_cursor.item().map_or(false, |m| m.id == message.id) { + old_cursor.next(&()); + end_ix += 1; + } + + new_messages.push(message.clone(), &()); + new_messages.push_tree(old_cursor.suffix(&()), &()); + drop(old_cursor); + self.messages = new_messages; + + cx.emit(ChannelEvent::Message { + old_range: start_ix..end_ix, + message, + }); + cx.notify(); + } } impl From for ChannelDetails { @@ -234,22 +280,35 @@ impl From for ChannelMessage { } } -impl PartialOrd for ChannelMessage { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) +impl sum_tree::Item for ChannelMessage { + type Summary = ChannelMessageSummary; + + fn summary(&self) -> Self::Summary { + ChannelMessageSummary { + max_id: self.id, + count: Count(1), + } } } -impl Ord for ChannelMessage { - fn cmp(&self, other: &Self) -> Ordering { - self.id.cmp(&other.id) +impl sum_tree::Summary for ChannelMessageSummary { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &()) { + self.max_id = summary.max_id; + self.count.0 += summary.count.0; } } -impl PartialEq for ChannelMessage { - fn eq(&self, other: &Self) -> bool { - self.id == other.id +impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for u64 { + fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) { + debug_assert!(summary.max_id > *self); + *self = summary.max_id; } } -impl Eq for ChannelMessage {} +impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for Count { + fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) { + self.0 += summary.count.0; + } +} diff --git a/zed/src/chat_panel.rs b/zed/src/chat_panel.rs index dfb4e545becb6f14f34d8f15652f352bb5767a2d..5e9b950b507d66ce6aa1607a31177f6b59665b6b 100644 --- a/zed/src/chat_panel.rs +++ b/zed/src/chat_panel.rs @@ -1,20 +1,30 @@ -use super::channel::{Channel, ChannelList}; +use crate::{ + channel::{Channel, ChannelEvent, ChannelList, ChannelMessage}, + Settings, +}; use gpui::{elements::*, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext}; +use postage::watch; pub struct ChatPanel { channel_list: ModelHandle, active_channel: Option<(ModelHandle, Subscription)>, messages: ListState, + settings: watch::Receiver, } pub enum Event {} impl ChatPanel { - pub fn new(channel_list: ModelHandle, cx: &mut ViewContext) -> Self { + pub fn new( + channel_list: ModelHandle, + settings: watch::Receiver, + cx: &mut ViewContext, + ) -> Self { let mut this = Self { channel_list, messages: ListState::new(Vec::new()), active_channel: None, + settings, }; this.assign_active_channel(cx); @@ -39,7 +49,15 @@ impl ChatPanel { }); if let Some(channel) = channel { if self.active_channel.as_ref().map(|e| &e.0) != Some(&channel) { - let subscription = cx.observe(&channel, Self::channel_did_change); + let subscription = cx.subscribe(&channel, Self::channel_did_change); + self.messages = ListState::new( + channel + .read(cx) + .messages() + .cursor::<(), ()>() + .map(|m| self.render_message(m)) + .collect(), + ); self.active_channel = Some((channel, subscription)); } } else { @@ -47,9 +65,42 @@ impl ChatPanel { } } - fn channel_did_change(&mut self, _: ModelHandle, cx: &mut ViewContext) { + fn channel_did_change( + &mut self, + _: ModelHandle, + event: &ChannelEvent, + cx: &mut ViewContext, + ) { + match event { + ChannelEvent::Message { old_range, message } => { + self.messages + .splice(old_range.clone(), Some(self.render_message(message))); + } + } cx.notify(); } + + fn render_active_channel_messages(&self) -> ElementBox { + Expanded::new(0.8, List::new(self.messages.clone()).boxed()).boxed() + } + + fn render_message(&self, message: &ChannelMessage) -> ElementBox { + let settings = self.settings.borrow(); + Flex::column() + .with_child( + Label::new( + message.body.clone(), + settings.ui_font_family, + settings.ui_font_size, + ) + .boxed(), + ) + .boxed() + } + + fn render_input_box(&self) -> ElementBox { + Empty::new().boxed() + } } impl Entity for ChatPanel { @@ -61,7 +112,10 @@ impl View for ChatPanel { "ChatPanel" } - fn render(&self, _: &RenderContext) -> gpui::ElementBox { - List::new(self.messages.clone()).boxed() + fn render(&self, _: &RenderContext) -> ElementBox { + Flex::column() + .with_child(self.render_active_channel_messages()) + .with_child(self.render_input_box()) + .boxed() } } diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index b6843fdc0bea5962a2b90e5a6869f0ef23cc7aba..28ed2701d123035c81493a5ce0d3e5b373b7a32e 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -374,8 +374,14 @@ impl Workspace { let mut right_sidebar = Sidebar::new(Side::Right); right_sidebar.add_item( "icons/comment-16.svg", - cx.add_view(|cx| ChatPanel::new(app_state.channel_list.clone(), cx)) - .into(), + cx.add_view(|cx| { + ChatPanel::new( + app_state.channel_list.clone(), + app_state.settings.clone(), + cx, + ) + }) + .into(), ); right_sidebar.add_item("icons/user-16.svg", cx.add_view(|_| ProjectBrowser).into());