channel_view.rs

  1use anyhow::{anyhow, Result};
  2use call::report_call_event_for_channel;
  3use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelId};
  4use client::{
  5    proto::{self, PeerId},
  6    Collaborator, ParticipantIndex,
  7};
  8use collections::HashMap;
  9use editor::{CollaborationHub, Editor};
 10use gpui::{
 11    actions,
 12    elements::{ChildView, Label},
 13    geometry::vector::Vector2F,
 14    AnyElement, AnyViewHandle, AppContext, Element, Entity, ModelHandle, Subscription, Task, View,
 15    ViewContext, ViewHandle,
 16};
 17use project::Project;
 18use std::{
 19    any::{Any, TypeId},
 20    sync::Arc,
 21};
 22use util::ResultExt;
 23use workspace::{
 24    item::{FollowableItem, Item, ItemHandle},
 25    register_followable_item,
 26    searchable::SearchableItemHandle,
 27    ItemNavHistory, Pane, ViewId, Workspace, WorkspaceId,
 28};
 29
 30actions!(channel_view, [Deploy]);
 31
 32pub fn init(cx: &mut AppContext) {
 33    register_followable_item::<ChannelView>(cx)
 34}
 35
 36pub struct ChannelView {
 37    pub editor: ViewHandle<Editor>,
 38    project: ModelHandle<Project>,
 39    channel_buffer: ModelHandle<ChannelBuffer>,
 40    remote_id: Option<ViewId>,
 41    _editor_event_subscription: Subscription,
 42}
 43
 44impl ChannelView {
 45    pub fn open(
 46        channel_id: ChannelId,
 47        workspace: ViewHandle<Workspace>,
 48        cx: &mut AppContext,
 49    ) -> Task<Result<ViewHandle<Self>>> {
 50        let pane = workspace.read(cx).active_pane().clone();
 51        let channel_view = Self::open_in_pane(channel_id, pane.clone(), workspace.clone(), cx);
 52        cx.spawn(|mut cx| async move {
 53            let channel_view = channel_view.await?;
 54            pane.update(&mut cx, |pane, cx| {
 55                report_call_event_for_channel(
 56                    "open channel notes",
 57                    channel_id,
 58                    &workspace.read(cx).app_state().client,
 59                    cx,
 60                );
 61                pane.add_item(Box::new(channel_view.clone()), true, true, None, cx);
 62            });
 63            anyhow::Ok(channel_view)
 64        })
 65    }
 66
 67    pub fn open_in_pane(
 68        channel_id: ChannelId,
 69        pane: ViewHandle<Pane>,
 70        workspace: ViewHandle<Workspace>,
 71        cx: &mut AppContext,
 72    ) -> Task<Result<ViewHandle<Self>>> {
 73        let workspace = workspace.read(cx);
 74        let project = workspace.project().to_owned();
 75        let channel_store = workspace.app_state().channel_store.clone();
 76        let markdown = workspace
 77            .app_state()
 78            .languages
 79            .language_for_name("Markdown");
 80        let channel_buffer =
 81            channel_store.update(cx, |store, cx| store.open_channel_buffer(channel_id, cx));
 82
 83        cx.spawn(|mut cx| async move {
 84            let channel_buffer = channel_buffer.await?;
 85
 86            if let Some(markdown) = markdown.await.log_err() {
 87                channel_buffer.update(&mut cx, |buffer, cx| {
 88                    buffer.buffer().update(cx, |buffer, cx| {
 89                        buffer.set_language(Some(markdown), cx);
 90                    })
 91                });
 92            }
 93
 94            pane.update(&mut cx, |pane, cx| {
 95                pane.items_of_type::<Self>()
 96                    .find(|channel_view| channel_view.read(cx).channel_buffer == channel_buffer)
 97                    .unwrap_or_else(|| cx.add_view(|cx| Self::new(project, channel_buffer, cx)))
 98            })
 99            .ok_or_else(|| anyhow!("pane was dropped"))
100        })
101    }
102
103    pub fn new(
104        project: ModelHandle<Project>,
105        channel_buffer: ModelHandle<ChannelBuffer>,
106        cx: &mut ViewContext<Self>,
107    ) -> Self {
108        let buffer = channel_buffer.read(cx).buffer();
109        let editor = cx.add_view(|cx| {
110            let mut editor = Editor::for_buffer(buffer, None, cx);
111            editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(
112                channel_buffer.clone(),
113            )));
114            editor
115        });
116        let _editor_event_subscription = cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone()));
117
118        cx.subscribe(&channel_buffer, Self::handle_channel_buffer_event)
119            .detach();
120
121        Self {
122            editor,
123            project,
124            channel_buffer,
125            remote_id: None,
126            _editor_event_subscription,
127        }
128    }
129
130    pub fn channel(&self, cx: &AppContext) -> Arc<Channel> {
131        self.channel_buffer.read(cx).channel()
132    }
133
134    fn handle_channel_buffer_event(
135        &mut self,
136        _: ModelHandle<ChannelBuffer>,
137        event: &ChannelBufferEvent,
138        cx: &mut ViewContext<Self>,
139    ) {
140        if let ChannelBufferEvent::Disconnected = event {
141            self.editor.update(cx, |editor, cx| {
142                editor.set_read_only(true);
143                cx.notify();
144            })
145        }
146    }
147}
148
149impl Entity for ChannelView {
150    type Event = editor::Event;
151}
152
153impl View for ChannelView {
154    fn ui_name() -> &'static str {
155        "ChannelView"
156    }
157
158    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
159        ChildView::new(self.editor.as_any(), cx).into_any()
160    }
161
162    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
163        if cx.is_self_focused() {
164            cx.focus(self.editor.as_any())
165        }
166    }
167}
168
169impl Item for ChannelView {
170    fn act_as_type<'a>(
171        &'a self,
172        type_id: TypeId,
173        self_handle: &'a ViewHandle<Self>,
174        _: &'a AppContext,
175    ) -> Option<&'a AnyViewHandle> {
176        if type_id == TypeId::of::<Self>() {
177            Some(self_handle)
178        } else if type_id == TypeId::of::<Editor>() {
179            Some(&self.editor)
180        } else {
181            None
182        }
183    }
184
185    fn tab_content<V: 'static>(
186        &self,
187        _: Option<usize>,
188        style: &theme::Tab,
189        cx: &gpui::AppContext,
190    ) -> AnyElement<V> {
191        let channel_name = &self.channel_buffer.read(cx).channel().name;
192        let label = if self.channel_buffer.read(cx).is_connected() {
193            format!("#{}", channel_name)
194        } else {
195            format!("#{} (disconnected)", channel_name)
196        };
197        Label::new(label, style.label.to_owned()).into_any()
198    }
199
200    fn clone_on_split(&self, _: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self> {
201        Some(Self::new(
202            self.project.clone(),
203            self.channel_buffer.clone(),
204            cx,
205        ))
206    }
207
208    fn is_singleton(&self, _cx: &AppContext) -> bool {
209        false
210    }
211
212    fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
213        self.editor
214            .update(cx, |editor, cx| editor.navigate(data, cx))
215    }
216
217    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
218        self.editor
219            .update(cx, |editor, cx| Item::deactivated(editor, cx))
220    }
221
222    fn set_nav_history(&mut self, history: ItemNavHistory, cx: &mut ViewContext<Self>) {
223        self.editor
224            .update(cx, |editor, cx| Item::set_nav_history(editor, history, cx))
225    }
226
227    fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
228        Some(Box::new(self.editor.clone()))
229    }
230
231    fn show_toolbar(&self) -> bool {
232        true
233    }
234
235    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
236        self.editor.read(cx).pixel_position_of_cursor(cx)
237    }
238}
239
240impl FollowableItem for ChannelView {
241    fn remote_id(&self) -> Option<workspace::ViewId> {
242        self.remote_id
243    }
244
245    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
246        let channel = self.channel_buffer.read(cx).channel();
247        Some(proto::view::Variant::ChannelView(
248            proto::view::ChannelView {
249                channel_id: channel.id,
250                editor: if let Some(proto::view::Variant::Editor(proto)) =
251                    self.editor.read(cx).to_state_proto(cx)
252                {
253                    Some(proto)
254                } else {
255                    None
256                },
257            },
258        ))
259    }
260
261    fn from_state_proto(
262        pane: ViewHandle<workspace::Pane>,
263        workspace: ViewHandle<workspace::Workspace>,
264        remote_id: workspace::ViewId,
265        state: &mut Option<proto::view::Variant>,
266        cx: &mut AppContext,
267    ) -> Option<gpui::Task<anyhow::Result<ViewHandle<Self>>>> {
268        let Some(proto::view::Variant::ChannelView(_)) = state else {
269            return None;
270        };
271        let Some(proto::view::Variant::ChannelView(state)) = state.take() else {
272            unreachable!()
273        };
274
275        let open = ChannelView::open_in_pane(state.channel_id, pane, workspace, cx);
276
277        Some(cx.spawn(|mut cx| async move {
278            let this = open.await?;
279
280            let task = this
281                .update(&mut cx, |this, cx| {
282                    this.remote_id = Some(remote_id);
283
284                    if let Some(state) = state.editor {
285                        Some(this.editor.update(cx, |editor, cx| {
286                            editor.apply_update_proto(
287                                &this.project,
288                                proto::update_view::Variant::Editor(proto::update_view::Editor {
289                                    selections: state.selections,
290                                    pending_selection: state.pending_selection,
291                                    scroll_top_anchor: state.scroll_top_anchor,
292                                    scroll_x: state.scroll_x,
293                                    scroll_y: state.scroll_y,
294                                    ..Default::default()
295                                }),
296                                cx,
297                            )
298                        }))
299                    } else {
300                        None
301                    }
302                })
303                .ok_or_else(|| anyhow!("window was closed"))?;
304
305            if let Some(task) = task {
306                task.await?;
307            }
308
309            Ok(this)
310        }))
311    }
312
313    fn add_event_to_update_proto(
314        &self,
315        event: &Self::Event,
316        update: &mut Option<proto::update_view::Variant>,
317        cx: &AppContext,
318    ) -> bool {
319        self.editor
320            .read(cx)
321            .add_event_to_update_proto(event, update, cx)
322    }
323
324    fn apply_update_proto(
325        &mut self,
326        project: &ModelHandle<Project>,
327        message: proto::update_view::Variant,
328        cx: &mut ViewContext<Self>,
329    ) -> gpui::Task<anyhow::Result<()>> {
330        self.editor.update(cx, |editor, cx| {
331            editor.apply_update_proto(project, message, cx)
332        })
333    }
334
335    fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) {
336        self.editor.update(cx, |editor, cx| {
337            editor.set_leader_peer_id(leader_peer_id, cx)
338        })
339    }
340
341    fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool {
342        Editor::should_unfollow_on_event(event, cx)
343    }
344
345    fn is_project_item(&self, _cx: &AppContext) -> bool {
346        false
347    }
348}
349
350struct ChannelBufferCollaborationHub(ModelHandle<ChannelBuffer>);
351
352impl CollaborationHub for ChannelBufferCollaborationHub {
353    fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
354        self.0.read(cx).collaborators()
355    }
356
357    fn user_participant_indices<'a>(
358        &self,
359        cx: &'a AppContext,
360    ) -> &'a HashMap<u64, ParticipantIndex> {
361        self.0.read(cx).user_store().read(cx).participant_indices()
362    }
363}