Render chat messages in ChatPanel
This commit is contained in:
parent
baded7d416
commit
405ff1d9db
4 changed files with 155 additions and 36 deletions
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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<ChannelMessage>,
|
||||
messages: SumTree<ChannelMessage>,
|
||||
pending_messages: Vec<PendingChannelMessage>,
|
||||
next_local_message_id: u64,
|
||||
rpc: Arc<Client>,
|
||||
_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<usize>,
|
||||
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<ChannelMessage> {
|
||||
pub fn messages(&self) -> &SumTree<ChannelMessage> {
|
||||
&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<Self>) {
|
||||
let mut old_cursor = self.messages.cursor::<u64, Count>();
|
||||
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<proto::Channel> for ChannelDetails {
|
||||
|
@ -234,22 +280,35 @@ impl From<proto::ChannelMessage> for ChannelMessage {
|
|||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ChannelMessage {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ChannelList>,
|
||||
active_channel: Option<(ModelHandle<Channel>, Subscription)>,
|
||||
messages: ListState,
|
||||
settings: watch::Receiver<Settings>,
|
||||
}
|
||||
|
||||
pub enum Event {}
|
||||
|
||||
impl ChatPanel {
|
||||
pub fn new(channel_list: ModelHandle<ChannelList>, cx: &mut ViewContext<Self>) -> Self {
|
||||
pub fn new(
|
||||
channel_list: ModelHandle<ChannelList>,
|
||||
settings: watch::Receiver<Settings>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> 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<Channel>, cx: &mut ViewContext<Self>) {
|
||||
fn channel_did_change(
|
||||
&mut self,
|
||||
_: ModelHandle<Channel>,
|
||||
event: &ChannelEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
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<Self>) -> gpui::ElementBox {
|
||||
List::new(self.messages.clone()).boxed()
|
||||
fn render(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||
Flex::column()
|
||||
.with_child(self.render_active_channel_messages())
|
||||
.with_child(self.render_input_box())
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue