mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-26 10:40:54 +00:00
Add channel notes view
co-authored-by: Max <max@zed.dev>
This commit is contained in:
parent
5a0315c4d5
commit
4eff8ad186
9 changed files with 150 additions and 14 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1529,6 +1529,7 @@ dependencies = [
|
|||
"futures 0.3.28",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"language",
|
||||
"log",
|
||||
"menu",
|
||||
"picker",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::ChannelId;
|
||||
use crate::{Channel, ChannelId, ChannelStore};
|
||||
use anyhow::Result;
|
||||
use client::Client;
|
||||
use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task};
|
||||
|
@ -16,6 +16,7 @@ pub struct ChannelBuffer {
|
|||
channel_id: ChannelId,
|
||||
collaborators: Vec<proto::Collaborator>,
|
||||
buffer: ModelHandle<language::Buffer>,
|
||||
channel_store: ModelHandle<ChannelStore>,
|
||||
client: Arc<Client>,
|
||||
_subscription: client::Subscription,
|
||||
}
|
||||
|
@ -33,7 +34,8 @@ impl Entity for ChannelBuffer {
|
|||
}
|
||||
|
||||
impl ChannelBuffer {
|
||||
pub fn join_channel(
|
||||
pub(crate) fn new(
|
||||
channel_store: ModelHandle<ChannelStore>,
|
||||
channel_id: ChannelId,
|
||||
client: Arc<Client>,
|
||||
cx: &mut AppContext,
|
||||
|
@ -65,6 +67,7 @@ impl ChannelBuffer {
|
|||
buffer,
|
||||
client,
|
||||
channel_id,
|
||||
channel_store,
|
||||
collaborators,
|
||||
_subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()),
|
||||
}
|
||||
|
@ -161,4 +164,11 @@ impl ChannelBuffer {
|
|||
pub fn collaborators(&self) -> &[proto::Collaborator] {
|
||||
&self.collaborators
|
||||
}
|
||||
|
||||
pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
|
||||
self.channel_store
|
||||
.read(cx)
|
||||
.channel_for_id(self.channel_id)
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ use rpc::{proto, TypedEnvelope};
|
|||
use std::sync::Arc;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::channel_buffer::ChannelBuffer;
|
||||
|
||||
pub type ChannelId = u64;
|
||||
|
||||
pub struct ChannelStore {
|
||||
|
@ -151,6 +153,14 @@ impl ChannelStore {
|
|||
self.channels_by_id.get(&channel_id)
|
||||
}
|
||||
|
||||
pub fn open_channel_buffer(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<ModelHandle<ChannelBuffer>>> {
|
||||
ChannelBuffer::new(cx.handle(), channel_id, self.client.clone(), cx)
|
||||
}
|
||||
|
||||
pub fn is_user_admin(&self, channel_id: ChannelId) -> bool {
|
||||
self.channel_paths.iter().any(|path| {
|
||||
if let Some(ix) = path.iter().position(|id| *id == channel_id) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
|
||||
|
||||
use channel::channel_buffer::ChannelBuffer;
|
||||
use client::UserId;
|
||||
use gpui::{executor::Deterministic, ModelHandle, TestAppContext};
|
||||
use rpc::{proto, RECEIVE_TIMEOUT};
|
||||
|
@ -22,8 +21,9 @@ async fn test_core_channel_buffers(
|
|||
.await;
|
||||
|
||||
// Client A joins the channel buffer
|
||||
let channel_buffer_a = cx_a
|
||||
.update(|cx| ChannelBuffer::join_channel(zed_id, client_a.client().to_owned(), cx))
|
||||
let channel_buffer_a = client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |channel, cx| channel.open_channel_buffer(zed_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -45,8 +45,9 @@ async fn test_core_channel_buffers(
|
|||
assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
|
||||
|
||||
// Client B joins the channel buffer
|
||||
let channel_buffer_b = cx_b
|
||||
.update(|cx| ChannelBuffer::join_channel(zed_id, client_b.client().to_owned(), cx))
|
||||
let channel_buffer_b = client_b
|
||||
.channel_store()
|
||||
.update(cx_b, |channel, cx| channel.open_channel_buffer(zed_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -79,8 +80,9 @@ async fn test_core_channel_buffers(
|
|||
});
|
||||
|
||||
// Client A rejoins the channel buffer
|
||||
let _channel_buffer_a = cx_a
|
||||
.update(|cx| ChannelBuffer::join_channel(zed_id, client_a.client().to_owned(), cx))
|
||||
let _channel_buffer_a = client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |channels, cx| channels.open_channel_buffer(zed_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
deterministic.run_until_parked();
|
||||
|
@ -104,7 +106,8 @@ async fn test_core_channel_buffers(
|
|||
});
|
||||
|
||||
// TODO:
|
||||
// - Test synchronizing offline updates, what happens to A's channel buffer?
|
||||
// - Test synchronizing offline updates, what happens to A's channel buffer when A disconnects
|
||||
// - Test interaction with channel deletion while buffer is open
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
|
|
@ -34,6 +34,7 @@ editor = { path = "../editor" }
|
|||
feedback = { path = "../feedback" }
|
||||
fuzzy = { path = "../fuzzy" }
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
menu = { path = "../menu" }
|
||||
picker = { path = "../picker" }
|
||||
project = { path = "../project" }
|
||||
|
|
69
crates/collab_ui/src/channel_view.rs
Normal file
69
crates/collab_ui/src/channel_view.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use channel::channel_buffer::ChannelBuffer;
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::{ChildView, Label},
|
||||
AnyElement, AppContext, Element, Entity, ModelHandle, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use language::Language;
|
||||
use std::sync::Arc;
|
||||
use workspace::item::{Item, ItemHandle};
|
||||
|
||||
actions!(channel_view, [Deploy]);
|
||||
|
||||
pub(crate) fn init(cx: &mut AppContext) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
pub struct ChannelView {
|
||||
editor: ViewHandle<Editor>,
|
||||
channel_buffer: ModelHandle<ChannelBuffer>,
|
||||
}
|
||||
|
||||
impl ChannelView {
|
||||
pub fn new(
|
||||
channel_buffer: ModelHandle<ChannelBuffer>,
|
||||
language: Arc<Language>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let buffer = channel_buffer.read(cx).buffer();
|
||||
buffer.update(cx, |buffer, cx| buffer.set_language(Some(language), cx));
|
||||
let editor = cx.add_view(|cx| Editor::for_buffer(buffer, None, cx));
|
||||
Self {
|
||||
editor,
|
||||
channel_buffer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for ChannelView {
|
||||
type Event = editor::Event;
|
||||
}
|
||||
|
||||
impl View for ChannelView {
|
||||
fn ui_name() -> &'static str {
|
||||
"ChannelView"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
|
||||
ChildView::new(self.editor.as_any(), cx).into_any()
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for ChannelView {
|
||||
fn tab_content<V: 'static>(
|
||||
&self,
|
||||
_: Option<usize>,
|
||||
style: &theme::Tab,
|
||||
cx: &gpui::AppContext,
|
||||
) -> AnyElement<V> {
|
||||
let channel_name = self
|
||||
.channel_buffer
|
||||
.read(cx)
|
||||
.channel(cx)
|
||||
.map_or("[Deleted channel]".to_string(), |channel| {
|
||||
format!("#{}", channel.name)
|
||||
});
|
||||
Label::new(channel_name, style.label.to_owned()).into_any()
|
||||
}
|
||||
}
|
|
@ -42,7 +42,10 @@ use workspace::{
|
|||
Workspace,
|
||||
};
|
||||
|
||||
use crate::face_pile::FacePile;
|
||||
use crate::{
|
||||
channel_view::{self, ChannelView},
|
||||
face_pile::FacePile,
|
||||
};
|
||||
use channel_modal::ChannelModal;
|
||||
|
||||
use self::contact_finder::ContactFinder;
|
||||
|
@ -77,6 +80,11 @@ struct RenameChannel {
|
|||
channel_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
struct OpenChannelBuffer {
|
||||
channel_id: u64,
|
||||
}
|
||||
|
||||
actions!(
|
||||
collab_panel,
|
||||
[
|
||||
|
@ -96,7 +104,8 @@ impl_actions!(
|
|||
InviteMembers,
|
||||
ManageMembers,
|
||||
RenameChannel,
|
||||
ToggleCollapse
|
||||
ToggleCollapse,
|
||||
OpenChannelBuffer
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -106,6 +115,7 @@ pub fn init(_client: Arc<Client>, cx: &mut AppContext) {
|
|||
settings::register::<panel_settings::CollaborationPanelSettings>(cx);
|
||||
contact_finder::init(cx);
|
||||
channel_modal::init(cx);
|
||||
channel_view::init(cx);
|
||||
|
||||
cx.add_action(CollabPanel::cancel);
|
||||
cx.add_action(CollabPanel::select_next);
|
||||
|
@ -121,7 +131,8 @@ pub fn init(_client: Arc<Client>, cx: &mut AppContext) {
|
|||
cx.add_action(CollabPanel::rename_channel);
|
||||
cx.add_action(CollabPanel::toggle_channel_collapsed);
|
||||
cx.add_action(CollabPanel::collapse_selected_channel);
|
||||
cx.add_action(CollabPanel::expand_selected_channel)
|
||||
cx.add_action(CollabPanel::expand_selected_channel);
|
||||
cx.add_action(CollabPanel::open_channel_buffer);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -1888,6 +1899,7 @@ impl CollabPanel {
|
|||
vec![
|
||||
ContextMenuItem::action(expand_action_name, ToggleCollapse { channel_id }),
|
||||
ContextMenuItem::action("New Subchannel", NewChannel { channel_id }),
|
||||
ContextMenuItem::action("Open Notes", OpenChannelBuffer { channel_id }),
|
||||
ContextMenuItem::Separator,
|
||||
ContextMenuItem::action("Invite to Channel", InviteMembers { channel_id }),
|
||||
ContextMenuItem::Separator,
|
||||
|
@ -2207,6 +2219,34 @@ impl CollabPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn open_channel_buffer(&mut self, action: &OpenChannelBuffer, cx: &mut ViewContext<Self>) {
|
||||
let workspace = self.workspace;
|
||||
let open = self.channel_store.update(cx, |channel_store, cx| {
|
||||
channel_store.open_channel_buffer(action.channel_id, cx)
|
||||
});
|
||||
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let channel_buffer = open.await?;
|
||||
|
||||
let markdown = workspace
|
||||
.read_with(&cx, |workspace, _| {
|
||||
workspace
|
||||
.app_state()
|
||||
.languages
|
||||
.language_for_name("Markdown")
|
||||
})?
|
||||
.await?;
|
||||
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let channel_view = cx.add_view(|cx| ChannelView::new(channel_buffer, markdown, cx));
|
||||
workspace.add_item(Box::new(channel_view), cx);
|
||||
})?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
|
||||
let Some(channel) = self.selected_channel() else {
|
||||
return;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod channel_view;
|
||||
pub mod collab_panel;
|
||||
mod collab_titlebar_item;
|
||||
mod contact_notification;
|
||||
|
|
|
@ -4687,12 +4687,13 @@ impl AnyWeakModelHandle {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy)]
|
||||
pub struct WeakViewHandle<T> {
|
||||
any_handle: AnyWeakViewHandle,
|
||||
view_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Copy for WeakViewHandle<T> {}
|
||||
|
||||
impl<T> Debug for WeakViewHandle<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct(&format!("WeakViewHandle<{}>", type_name::<T>()))
|
||||
|
|
Loading…
Reference in a new issue