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}