channel_view.rs

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