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