mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-14 14:11:34 +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",
|
"futures 0.3.28",
|
||||||
"fuzzy",
|
"fuzzy",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
"language",
|
||||||
"log",
|
"log",
|
||||||
"menu",
|
"menu",
|
||||||
"picker",
|
"picker",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::ChannelId;
|
use crate::{Channel, ChannelId, ChannelStore};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use client::Client;
|
use client::Client;
|
||||||
use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task};
|
use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task};
|
||||||
|
@ -16,6 +16,7 @@ pub struct ChannelBuffer {
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
collaborators: Vec<proto::Collaborator>,
|
collaborators: Vec<proto::Collaborator>,
|
||||||
buffer: ModelHandle<language::Buffer>,
|
buffer: ModelHandle<language::Buffer>,
|
||||||
|
channel_store: ModelHandle<ChannelStore>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
_subscription: client::Subscription,
|
_subscription: client::Subscription,
|
||||||
}
|
}
|
||||||
|
@ -33,7 +34,8 @@ impl Entity for ChannelBuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChannelBuffer {
|
impl ChannelBuffer {
|
||||||
pub fn join_channel(
|
pub(crate) fn new(
|
||||||
|
channel_store: ModelHandle<ChannelStore>,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
|
@ -65,6 +67,7 @@ impl ChannelBuffer {
|
||||||
buffer,
|
buffer,
|
||||||
client,
|
client,
|
||||||
channel_id,
|
channel_id,
|
||||||
|
channel_store,
|
||||||
collaborators,
|
collaborators,
|
||||||
_subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()),
|
_subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()),
|
||||||
}
|
}
|
||||||
|
@ -161,4 +164,11 @@ impl ChannelBuffer {
|
||||||
pub fn collaborators(&self) -> &[proto::Collaborator] {
|
pub fn collaborators(&self) -> &[proto::Collaborator] {
|
||||||
&self.collaborators
|
&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 std::sync::Arc;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
|
use crate::channel_buffer::ChannelBuffer;
|
||||||
|
|
||||||
pub type ChannelId = u64;
|
pub type ChannelId = u64;
|
||||||
|
|
||||||
pub struct ChannelStore {
|
pub struct ChannelStore {
|
||||||
|
@ -151,6 +153,14 @@ impl ChannelStore {
|
||||||
self.channels_by_id.get(&channel_id)
|
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 {
|
pub fn is_user_admin(&self, channel_id: ChannelId) -> bool {
|
||||||
self.channel_paths.iter().any(|path| {
|
self.channel_paths.iter().any(|path| {
|
||||||
if let Some(ix) = path.iter().position(|id| *id == channel_id) {
|
if let Some(ix) = path.iter().position(|id| *id == channel_id) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
|
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
|
||||||
|
|
||||||
use channel::channel_buffer::ChannelBuffer;
|
|
||||||
use client::UserId;
|
use client::UserId;
|
||||||
use gpui::{executor::Deterministic, ModelHandle, TestAppContext};
|
use gpui::{executor::Deterministic, ModelHandle, TestAppContext};
|
||||||
use rpc::{proto, RECEIVE_TIMEOUT};
|
use rpc::{proto, RECEIVE_TIMEOUT};
|
||||||
|
@ -22,8 +21,9 @@ async fn test_core_channel_buffers(
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Client A joins the channel buffer
|
// Client A joins the channel buffer
|
||||||
let channel_buffer_a = cx_a
|
let channel_buffer_a = client_a
|
||||||
.update(|cx| ChannelBuffer::join_channel(zed_id, client_a.client().to_owned(), cx))
|
.channel_store()
|
||||||
|
.update(cx_a, |channel, cx| channel.open_channel_buffer(zed_id, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -45,8 +45,9 @@ async fn test_core_channel_buffers(
|
||||||
assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
|
assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
|
||||||
|
|
||||||
// Client B joins the channel buffer
|
// Client B joins the channel buffer
|
||||||
let channel_buffer_b = cx_b
|
let channel_buffer_b = client_b
|
||||||
.update(|cx| ChannelBuffer::join_channel(zed_id, client_b.client().to_owned(), cx))
|
.channel_store()
|
||||||
|
.update(cx_b, |channel, cx| channel.open_channel_buffer(zed_id, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -79,8 +80,9 @@ async fn test_core_channel_buffers(
|
||||||
});
|
});
|
||||||
|
|
||||||
// Client A rejoins the channel buffer
|
// Client A rejoins the channel buffer
|
||||||
let _channel_buffer_a = cx_a
|
let _channel_buffer_a = client_a
|
||||||
.update(|cx| ChannelBuffer::join_channel(zed_id, client_a.client().to_owned(), cx))
|
.channel_store()
|
||||||
|
.update(cx_a, |channels, cx| channels.open_channel_buffer(zed_id, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
|
@ -104,7 +106,8 @@ async fn test_core_channel_buffers(
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO:
|
// 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]
|
#[track_caller]
|
||||||
|
|
|
@ -34,6 +34,7 @@ editor = { path = "../editor" }
|
||||||
feedback = { path = "../feedback" }
|
feedback = { path = "../feedback" }
|
||||||
fuzzy = { path = "../fuzzy" }
|
fuzzy = { path = "../fuzzy" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
|
language = { path = "../language" }
|
||||||
menu = { path = "../menu" }
|
menu = { path = "../menu" }
|
||||||
picker = { path = "../picker" }
|
picker = { path = "../picker" }
|
||||||
project = { path = "../project" }
|
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,
|
Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::face_pile::FacePile;
|
use crate::{
|
||||||
|
channel_view::{self, ChannelView},
|
||||||
|
face_pile::FacePile,
|
||||||
|
};
|
||||||
use channel_modal::ChannelModal;
|
use channel_modal::ChannelModal;
|
||||||
|
|
||||||
use self::contact_finder::ContactFinder;
|
use self::contact_finder::ContactFinder;
|
||||||
|
@ -77,6 +80,11 @@ struct RenameChannel {
|
||||||
channel_id: u64,
|
channel_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
struct OpenChannelBuffer {
|
||||||
|
channel_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
collab_panel,
|
collab_panel,
|
||||||
[
|
[
|
||||||
|
@ -96,7 +104,8 @@ impl_actions!(
|
||||||
InviteMembers,
|
InviteMembers,
|
||||||
ManageMembers,
|
ManageMembers,
|
||||||
RenameChannel,
|
RenameChannel,
|
||||||
ToggleCollapse
|
ToggleCollapse,
|
||||||
|
OpenChannelBuffer
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -106,6 +115,7 @@ pub fn init(_client: Arc<Client>, cx: &mut AppContext) {
|
||||||
settings::register::<panel_settings::CollaborationPanelSettings>(cx);
|
settings::register::<panel_settings::CollaborationPanelSettings>(cx);
|
||||||
contact_finder::init(cx);
|
contact_finder::init(cx);
|
||||||
channel_modal::init(cx);
|
channel_modal::init(cx);
|
||||||
|
channel_view::init(cx);
|
||||||
|
|
||||||
cx.add_action(CollabPanel::cancel);
|
cx.add_action(CollabPanel::cancel);
|
||||||
cx.add_action(CollabPanel::select_next);
|
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::rename_channel);
|
||||||
cx.add_action(CollabPanel::toggle_channel_collapsed);
|
cx.add_action(CollabPanel::toggle_channel_collapsed);
|
||||||
cx.add_action(CollabPanel::collapse_selected_channel);
|
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)]
|
#[derive(Debug)]
|
||||||
|
@ -1888,6 +1899,7 @@ impl CollabPanel {
|
||||||
vec![
|
vec![
|
||||||
ContextMenuItem::action(expand_action_name, ToggleCollapse { channel_id }),
|
ContextMenuItem::action(expand_action_name, ToggleCollapse { channel_id }),
|
||||||
ContextMenuItem::action("New Subchannel", NewChannel { channel_id }),
|
ContextMenuItem::action("New Subchannel", NewChannel { channel_id }),
|
||||||
|
ContextMenuItem::action("Open Notes", OpenChannelBuffer { channel_id }),
|
||||||
ContextMenuItem::Separator,
|
ContextMenuItem::Separator,
|
||||||
ContextMenuItem::action("Invite to Channel", InviteMembers { channel_id }),
|
ContextMenuItem::action("Invite to Channel", InviteMembers { channel_id }),
|
||||||
ContextMenuItem::Separator,
|
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>) {
|
fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
|
||||||
let Some(channel) = self.selected_channel() else {
|
let Some(channel) = self.selected_channel() else {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod channel_view;
|
||||||
pub mod collab_panel;
|
pub mod collab_panel;
|
||||||
mod collab_titlebar_item;
|
mod collab_titlebar_item;
|
||||||
mod contact_notification;
|
mod contact_notification;
|
||||||
|
|
|
@ -4687,12 +4687,13 @@ impl AnyWeakModelHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy)]
|
|
||||||
pub struct WeakViewHandle<T> {
|
pub struct WeakViewHandle<T> {
|
||||||
any_handle: AnyWeakViewHandle,
|
any_handle: AnyWeakViewHandle,
|
||||||
view_type: PhantomData<T>,
|
view_type: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Copy for WeakViewHandle<T> {}
|
||||||
|
|
||||||
impl<T> Debug for WeakViewHandle<T> {
|
impl<T> Debug for WeakViewHandle<T> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct(&format!("WeakViewHandle<{}>", type_name::<T>()))
|
f.debug_struct(&format!("WeakViewHandle<{}>", type_name::<T>()))
|
||||||
|
|
Loading…
Reference in a new issue