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