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