channel_view.rs

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