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}