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, 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 pane.items_of_type::<Self>()
97 .find(|channel_view| channel_view.read(cx).channel_buffer == channel_buffer)
98 .unwrap_or_else(|| {
99 cx.add_view(|cx| {
100 let mut this = Self::new(project, channel_store, channel_buffer, cx);
101 this.acknowledge_buffer_version(cx);
102 this
103 })
104 })
105 })
106 .ok_or_else(|| anyhow!("pane was dropped"))
107 })
108 }
109
110 pub fn new(
111 project: ModelHandle<Project>,
112 channel_store: ModelHandle<ChannelStore>,
113 channel_buffer: ModelHandle<ChannelBuffer>,
114 cx: &mut ViewContext<Self>,
115 ) -> Self {
116 let buffer = channel_buffer.read(cx).buffer();
117 let editor = cx.add_view(|cx| {
118 let mut editor = Editor::for_buffer(buffer, None, cx);
119 editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(
120 channel_buffer.clone(),
121 )));
122 editor
123 });
124 let _editor_event_subscription = cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone()));
125
126 cx.subscribe(&channel_buffer, Self::handle_channel_buffer_event)
127 .detach();
128
129 Self {
130 editor,
131 project,
132 channel_store,
133 channel_buffer,
134 remote_id: None,
135 _editor_event_subscription,
136 }
137 }
138
139 pub fn channel(&self, cx: &AppContext) -> Arc<Channel> {
140 self.channel_buffer.read(cx).channel()
141 }
142
143 fn handle_channel_buffer_event(
144 &mut self,
145 _: ModelHandle<ChannelBuffer>,
146 event: &ChannelBufferEvent,
147 cx: &mut ViewContext<Self>,
148 ) {
149 match event {
150 ChannelBufferEvent::Disconnected => self.editor.update(cx, |editor, cx| {
151 editor.set_read_only(true);
152 cx.notify();
153 }),
154 ChannelBufferEvent::BufferEdited => {
155 if cx.is_self_focused() || self.editor.is_focused(cx) {
156 self.acknowledge_buffer_version(cx);
157 } else {
158 self.channel_store.update(cx, |store, cx| {
159 let channel_buffer = self.channel_buffer.read(cx);
160 store.notes_changed(
161 channel_buffer.channel().id,
162 channel_buffer.epoch(),
163 &channel_buffer.buffer().read(cx).version(),
164 cx,
165 )
166 });
167 }
168 }
169 _ => {}
170 }
171 }
172
173 fn acknowledge_buffer_version(&mut self, cx: &mut ViewContext<'_, '_, ChannelView>) {
174 self.channel_store.update(cx, |store, cx| {
175 let channel_buffer = self.channel_buffer.read(cx);
176 store.acknowledge_notes_version(
177 channel_buffer.channel().id,
178 channel_buffer.epoch(),
179 &channel_buffer.buffer().read(cx).version(),
180 cx,
181 )
182 });
183 self.channel_buffer.update(cx, |buffer, cx| {
184 buffer.acknowledge_buffer_version(cx);
185 });
186 }
187}
188
189impl Entity for ChannelView {
190 type Event = editor::Event;
191}
192
193impl View for ChannelView {
194 fn ui_name() -> &'static str {
195 "ChannelView"
196 }
197
198 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
199 ChildView::new(self.editor.as_any(), cx).into_any()
200 }
201
202 fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
203 if cx.is_self_focused() {
204 self.acknowledge_buffer_version(cx);
205 cx.focus(self.editor.as_any())
206 }
207 }
208}
209
210impl Item for ChannelView {
211 fn act_as_type<'a>(
212 &'a self,
213 type_id: TypeId,
214 self_handle: &'a ViewHandle<Self>,
215 _: &'a AppContext,
216 ) -> Option<&'a AnyViewHandle> {
217 if type_id == TypeId::of::<Self>() {
218 Some(self_handle)
219 } else if type_id == TypeId::of::<Editor>() {
220 Some(&self.editor)
221 } else {
222 None
223 }
224 }
225
226 fn tab_content<V: 'static>(
227 &self,
228 _: Option<usize>,
229 style: &theme::Tab,
230 cx: &gpui::AppContext,
231 ) -> AnyElement<V> {
232 let channel_name = &self.channel_buffer.read(cx).channel().name;
233 let label = if self.channel_buffer.read(cx).is_connected() {
234 format!("#{}", channel_name)
235 } else {
236 format!("#{} (disconnected)", channel_name)
237 };
238 Label::new(label, style.label.to_owned()).into_any()
239 }
240
241 fn clone_on_split(&self, _: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self> {
242 Some(Self::new(
243 self.project.clone(),
244 self.channel_store.clone(),
245 self.channel_buffer.clone(),
246 cx,
247 ))
248 }
249
250 fn is_singleton(&self, _cx: &AppContext) -> bool {
251 false
252 }
253
254 fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
255 self.editor
256 .update(cx, |editor, cx| editor.navigate(data, cx))
257 }
258
259 fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
260 self.editor
261 .update(cx, |editor, cx| Item::deactivated(editor, cx))
262 }
263
264 fn set_nav_history(&mut self, history: ItemNavHistory, cx: &mut ViewContext<Self>) {
265 self.editor
266 .update(cx, |editor, cx| Item::set_nav_history(editor, history, cx))
267 }
268
269 fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
270 Some(Box::new(self.editor.clone()))
271 }
272
273 fn show_toolbar(&self) -> bool {
274 true
275 }
276
277 fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
278 self.editor.read(cx).pixel_position_of_cursor(cx)
279 }
280}
281
282impl FollowableItem for ChannelView {
283 fn remote_id(&self) -> Option<workspace::ViewId> {
284 self.remote_id
285 }
286
287 fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
288 let channel_buffer = self.channel_buffer.read(cx);
289 if !channel_buffer.is_connected() {
290 return None;
291 }
292
293 Some(proto::view::Variant::ChannelView(
294 proto::view::ChannelView {
295 channel_id: channel_buffer.channel().id,
296 editor: if let Some(proto::view::Variant::Editor(proto)) =
297 self.editor.read(cx).to_state_proto(cx)
298 {
299 Some(proto)
300 } else {
301 None
302 },
303 },
304 ))
305 }
306
307 fn from_state_proto(
308 pane: ViewHandle<workspace::Pane>,
309 workspace: ViewHandle<workspace::Workspace>,
310 remote_id: workspace::ViewId,
311 state: &mut Option<proto::view::Variant>,
312 cx: &mut AppContext,
313 ) -> Option<gpui::Task<anyhow::Result<ViewHandle<Self>>>> {
314 let Some(proto::view::Variant::ChannelView(_)) = state else {
315 return None;
316 };
317 let Some(proto::view::Variant::ChannelView(state)) = state.take() else {
318 unreachable!()
319 };
320
321 let open = ChannelView::open_in_pane(state.channel_id, pane, workspace, cx);
322
323 Some(cx.spawn(|mut cx| async move {
324 let this = open.await?;
325
326 let task = this
327 .update(&mut cx, |this, cx| {
328 this.remote_id = Some(remote_id);
329
330 if let Some(state) = state.editor {
331 Some(this.editor.update(cx, |editor, cx| {
332 editor.apply_update_proto(
333 &this.project,
334 proto::update_view::Variant::Editor(proto::update_view::Editor {
335 selections: state.selections,
336 pending_selection: state.pending_selection,
337 scroll_top_anchor: state.scroll_top_anchor,
338 scroll_x: state.scroll_x,
339 scroll_y: state.scroll_y,
340 ..Default::default()
341 }),
342 cx,
343 )
344 }))
345 } else {
346 None
347 }
348 })
349 .ok_or_else(|| anyhow!("window was closed"))?;
350
351 if let Some(task) = task {
352 task.await?;
353 }
354
355 Ok(this)
356 }))
357 }
358
359 fn add_event_to_update_proto(
360 &self,
361 event: &Self::Event,
362 update: &mut Option<proto::update_view::Variant>,
363 cx: &AppContext,
364 ) -> bool {
365 self.editor
366 .read(cx)
367 .add_event_to_update_proto(event, update, cx)
368 }
369
370 fn apply_update_proto(
371 &mut self,
372 project: &ModelHandle<Project>,
373 message: proto::update_view::Variant,
374 cx: &mut ViewContext<Self>,
375 ) -> gpui::Task<anyhow::Result<()>> {
376 self.editor.update(cx, |editor, cx| {
377 editor.apply_update_proto(project, message, cx)
378 })
379 }
380
381 fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) {
382 self.editor.update(cx, |editor, cx| {
383 editor.set_leader_peer_id(leader_peer_id, cx)
384 })
385 }
386
387 fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool {
388 Editor::should_unfollow_on_event(event, cx)
389 }
390
391 fn is_project_item(&self, _cx: &AppContext) -> bool {
392 false
393 }
394}
395
396struct ChannelBufferCollaborationHub(ModelHandle<ChannelBuffer>);
397
398impl CollaborationHub for ChannelBufferCollaborationHub {
399 fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
400 self.0.read(cx).collaborators()
401 }
402
403 fn user_participant_indices<'a>(
404 &self,
405 cx: &'a AppContext,
406 ) -> &'a HashMap<u64, ParticipantIndex> {
407 self.0.read(cx).user_store().read(cx).participant_indices()
408 }
409}