channel_view.rs

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