channel_view.rs

  1use anyhow::{anyhow, Result};
  2use call::report_call_event_for_channel;
  3use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelId, ChannelStore};
  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, SaveIntent, 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_store: ModelHandle<ChannelStore>,
 40    channel_buffer: ModelHandle<ChannelBuffer>,
 41    remote_id: Option<ViewId>,
 42    _editor_event_subscription: Subscription,
 43}
 44
 45impl ChannelView {
 46    pub fn open(
 47        channel_id: ChannelId,
 48        workspace: ViewHandle<Workspace>,
 49        cx: &mut AppContext,
 50    ) -> Task<Result<ViewHandle<Self>>> {
 51        let pane = workspace.read(cx).active_pane().clone();
 52        let channel_view = Self::open_in_pane(channel_id, pane.clone(), workspace.clone(), cx);
 53        cx.spawn(|mut cx| async move {
 54            let channel_view = channel_view.await?;
 55            pane.update(&mut cx, |pane, cx| {
 56                report_call_event_for_channel(
 57                    "open channel notes",
 58                    channel_id,
 59                    &workspace.read(cx).app_state().client,
 60                    cx,
 61                );
 62                pane.add_item(Box::new(channel_view.clone()), true, true, None, cx);
 63            });
 64            anyhow::Ok(channel_view)
 65        })
 66    }
 67
 68    pub fn open_in_pane(
 69        channel_id: ChannelId,
 70        pane: ViewHandle<Pane>,
 71        workspace: ViewHandle<Workspace>,
 72        cx: &mut AppContext,
 73    ) -> Task<Result<ViewHandle<Self>>> {
 74        let workspace = workspace.read(cx);
 75        let project = workspace.project().to_owned();
 76        let channel_store = ChannelStore::global(cx);
 77        let markdown = workspace
 78            .app_state()
 79            .languages
 80            .language_for_name("Markdown");
 81        let channel_buffer =
 82            channel_store.update(cx, |store, cx| store.open_channel_buffer(channel_id, cx));
 83
 84        cx.spawn(|mut cx| async move {
 85            let channel_buffer = channel_buffer.await?;
 86
 87            if let Some(markdown) = markdown.await.log_err() {
 88                channel_buffer.update(&mut cx, |buffer, cx| {
 89                    buffer.buffer().update(cx, |buffer, cx| {
 90                        buffer.set_language(Some(markdown), cx);
 91                    })
 92                });
 93            }
 94
 95            pane.update(&mut cx, |pane, cx| {
 96                let buffer_id = channel_buffer.read(cx).remote_id(cx);
 97
 98                let existing_view = pane
 99                    .items_of_type::<Self>()
100                    .find(|view| view.read(cx).channel_buffer.read(cx).remote_id(cx) == buffer_id);
101
102                // If this channel buffer is already open in this pane, just return it.
103                if let Some(existing_view) = existing_view.clone() {
104                    if existing_view.read(cx).channel_buffer == channel_buffer {
105                        return existing_view;
106                    }
107                }
108
109                let view = cx.add_view(|cx| {
110                    let mut this = Self::new(project, channel_store, channel_buffer, cx);
111                    this.acknowledge_buffer_version(cx);
112                    this
113                });
114
115                // If the pane contained a disconnected view for this channel buffer,
116                // replace that.
117                if let Some(existing_item) = existing_view {
118                    if let Some(ix) = pane.index_for_item(&existing_item) {
119                        pane.close_item_by_id(existing_item.id(), SaveIntent::Skip, cx)
120                            .detach();
121                        pane.add_item(Box::new(view.clone()), true, true, Some(ix), cx);
122                    }
123                }
124
125                view
126            })
127            .ok_or_else(|| anyhow!("pane was dropped"))
128        })
129    }
130
131    pub fn new(
132        project: ModelHandle<Project>,
133        channel_store: ModelHandle<ChannelStore>,
134        channel_buffer: ModelHandle<ChannelBuffer>,
135        cx: &mut ViewContext<Self>,
136    ) -> Self {
137        let buffer = channel_buffer.read(cx).buffer();
138        let editor = cx.add_view(|cx| {
139            let mut editor = Editor::for_buffer(buffer, None, cx);
140            editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(
141                channel_buffer.clone(),
142            )));
143            editor
144        });
145        let _editor_event_subscription = cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone()));
146
147        cx.subscribe(&channel_buffer, Self::handle_channel_buffer_event)
148            .detach();
149
150        Self {
151            editor,
152            project,
153            channel_store,
154            channel_buffer,
155            remote_id: None,
156            _editor_event_subscription,
157        }
158    }
159
160    pub fn channel(&self, cx: &AppContext) -> Arc<Channel> {
161        self.channel_buffer.read(cx).channel()
162    }
163
164    fn handle_channel_buffer_event(
165        &mut self,
166        _: ModelHandle<ChannelBuffer>,
167        event: &ChannelBufferEvent,
168        cx: &mut ViewContext<Self>,
169    ) {
170        match event {
171            ChannelBufferEvent::Disconnected => self.editor.update(cx, |editor, cx| {
172                editor.set_read_only(true);
173                cx.notify();
174            }),
175            ChannelBufferEvent::BufferEdited => {
176                if cx.is_self_focused() || self.editor.is_focused(cx) {
177                    self.acknowledge_buffer_version(cx);
178                } else {
179                    self.channel_store.update(cx, |store, cx| {
180                        let channel_buffer = self.channel_buffer.read(cx);
181                        store.notes_changed(
182                            channel_buffer.channel().id,
183                            channel_buffer.epoch(),
184                            &channel_buffer.buffer().read(cx).version(),
185                            cx,
186                        )
187                    });
188                }
189            }
190            _ => {}
191        }
192    }
193
194    fn acknowledge_buffer_version(&mut self, cx: &mut ViewContext<'_, '_, ChannelView>) {
195        self.channel_store.update(cx, |store, cx| {
196            let channel_buffer = self.channel_buffer.read(cx);
197            store.acknowledge_notes_version(
198                channel_buffer.channel().id,
199                channel_buffer.epoch(),
200                &channel_buffer.buffer().read(cx).version(),
201                cx,
202            )
203        });
204        self.channel_buffer.update(cx, |buffer, cx| {
205            buffer.acknowledge_buffer_version(cx);
206        });
207    }
208}
209
210impl Entity for ChannelView {
211    type Event = editor::Event;
212}
213
214impl View for ChannelView {
215    fn ui_name() -> &'static str {
216        "ChannelView"
217    }
218
219    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
220        ChildView::new(self.editor.as_any(), cx).into_any()
221    }
222
223    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
224        if cx.is_self_focused() {
225            self.acknowledge_buffer_version(cx);
226            cx.focus(self.editor.as_any())
227        }
228    }
229}
230
231impl Item for ChannelView {
232    fn act_as_type<'a>(
233        &'a self,
234        type_id: TypeId,
235        self_handle: &'a ViewHandle<Self>,
236        _: &'a AppContext,
237    ) -> Option<&'a AnyViewHandle> {
238        if type_id == TypeId::of::<Self>() {
239            Some(self_handle)
240        } else if type_id == TypeId::of::<Editor>() {
241            Some(&self.editor)
242        } else {
243            None
244        }
245    }
246
247    fn tab_content<V: 'static>(
248        &self,
249        _: Option<usize>,
250        style: &theme::Tab,
251        cx: &gpui::AppContext,
252    ) -> AnyElement<V> {
253        let channel_name = &self.channel_buffer.read(cx).channel().name;
254        let label = if self.channel_buffer.read(cx).is_connected() {
255            format!("#{}", channel_name)
256        } else {
257            format!("#{} (disconnected)", channel_name)
258        };
259        Label::new(label, style.label.to_owned()).into_any()
260    }
261
262    fn clone_on_split(&self, _: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self> {
263        Some(Self::new(
264            self.project.clone(),
265            self.channel_store.clone(),
266            self.channel_buffer.clone(),
267            cx,
268        ))
269    }
270
271    fn is_singleton(&self, _cx: &AppContext) -> bool {
272        false
273    }
274
275    fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
276        self.editor
277            .update(cx, |editor, cx| editor.navigate(data, cx))
278    }
279
280    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
281        self.editor
282            .update(cx, |editor, cx| Item::deactivated(editor, cx))
283    }
284
285    fn set_nav_history(&mut self, history: ItemNavHistory, cx: &mut ViewContext<Self>) {
286        self.editor
287            .update(cx, |editor, cx| Item::set_nav_history(editor, history, cx))
288    }
289
290    fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
291        Some(Box::new(self.editor.clone()))
292    }
293
294    fn show_toolbar(&self) -> bool {
295        true
296    }
297
298    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
299        self.editor.read(cx).pixel_position_of_cursor(cx)
300    }
301}
302
303impl FollowableItem for ChannelView {
304    fn remote_id(&self) -> Option<workspace::ViewId> {
305        self.remote_id
306    }
307
308    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
309        let channel_buffer = self.channel_buffer.read(cx);
310        if !channel_buffer.is_connected() {
311            return None;
312        }
313
314        Some(proto::view::Variant::ChannelView(
315            proto::view::ChannelView {
316                channel_id: channel_buffer.channel().id,
317                editor: if let Some(proto::view::Variant::Editor(proto)) =
318                    self.editor.read(cx).to_state_proto(cx)
319                {
320                    Some(proto)
321                } else {
322                    None
323                },
324            },
325        ))
326    }
327
328    fn from_state_proto(
329        pane: ViewHandle<workspace::Pane>,
330        workspace: ViewHandle<workspace::Workspace>,
331        remote_id: workspace::ViewId,
332        state: &mut Option<proto::view::Variant>,
333        cx: &mut AppContext,
334    ) -> Option<gpui::Task<anyhow::Result<ViewHandle<Self>>>> {
335        let Some(proto::view::Variant::ChannelView(_)) = state else {
336            return None;
337        };
338        let Some(proto::view::Variant::ChannelView(state)) = state.take() else {
339            unreachable!()
340        };
341
342        let open = ChannelView::open_in_pane(state.channel_id, pane, workspace, cx);
343
344        Some(cx.spawn(|mut cx| async move {
345            let this = open.await?;
346
347            let task = this
348                .update(&mut cx, |this, cx| {
349                    this.remote_id = Some(remote_id);
350
351                    if let Some(state) = state.editor {
352                        Some(this.editor.update(cx, |editor, cx| {
353                            editor.apply_update_proto(
354                                &this.project,
355                                proto::update_view::Variant::Editor(proto::update_view::Editor {
356                                    selections: state.selections,
357                                    pending_selection: state.pending_selection,
358                                    scroll_top_anchor: state.scroll_top_anchor,
359                                    scroll_x: state.scroll_x,
360                                    scroll_y: state.scroll_y,
361                                    ..Default::default()
362                                }),
363                                cx,
364                            )
365                        }))
366                    } else {
367                        None
368                    }
369                })
370                .ok_or_else(|| anyhow!("window was closed"))?;
371
372            if let Some(task) = task {
373                task.await?;
374            }
375
376            Ok(this)
377        }))
378    }
379
380    fn add_event_to_update_proto(
381        &self,
382        event: &Self::Event,
383        update: &mut Option<proto::update_view::Variant>,
384        cx: &AppContext,
385    ) -> bool {
386        self.editor
387            .read(cx)
388            .add_event_to_update_proto(event, update, cx)
389    }
390
391    fn apply_update_proto(
392        &mut self,
393        project: &ModelHandle<Project>,
394        message: proto::update_view::Variant,
395        cx: &mut ViewContext<Self>,
396    ) -> gpui::Task<anyhow::Result<()>> {
397        self.editor.update(cx, |editor, cx| {
398            editor.apply_update_proto(project, message, cx)
399        })
400    }
401
402    fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) {
403        self.editor.update(cx, |editor, cx| {
404            editor.set_leader_peer_id(leader_peer_id, cx)
405        })
406    }
407
408    fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool {
409        Editor::should_unfollow_on_event(event, cx)
410    }
411
412    fn is_project_item(&self, _cx: &AppContext) -> bool {
413        false
414    }
415}
416
417struct ChannelBufferCollaborationHub(ModelHandle<ChannelBuffer>);
418
419impl CollaborationHub for ChannelBufferCollaborationHub {
420    fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
421        self.0.read(cx).collaborators()
422    }
423
424    fn user_participant_indices<'a>(
425        &self,
426        cx: &'a AppContext,
427    ) -> &'a HashMap<u64, ParticipantIndex> {
428        self.0.read(cx).user_store().read(cx).participant_indices()
429    }
430}