1use crate::{
2 pane::{self, Pane},
3 persistence::model::ItemId,
4 searchable::SearchableItemHandle,
5 workspace_settings::{AutosaveSetting, WorkspaceSettings},
6 DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, ToolbarItemLocation,
7 ViewId, Workspace, WorkspaceId,
8};
9use anyhow::Result;
10use client2::{
11 proto::{self, PeerId},
12 Client,
13};
14use gpui::{
15 AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, HighlightStyle,
16 Model, Pixels, Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext,
17};
18use project2::{Project, ProjectEntryId, ProjectPath};
19use schemars::JsonSchema;
20use serde::{Deserialize, Serialize};
21use settings2::Settings;
22use smallvec::SmallVec;
23use std::{
24 any::{Any, TypeId},
25 cell::RefCell,
26 ops::Range,
27 path::PathBuf,
28 rc::Rc,
29 sync::{
30 atomic::{AtomicBool, Ordering},
31 Arc,
32 },
33 time::Duration,
34};
35use theme2::Theme;
36
37#[derive(Deserialize)]
38pub struct ItemSettings {
39 pub git_status: bool,
40 pub close_position: ClosePosition,
41}
42
43#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
44#[serde(rename_all = "lowercase")]
45pub enum ClosePosition {
46 Left,
47 #[default]
48 Right,
49}
50
51impl ClosePosition {
52 pub fn right(&self) -> bool {
53 match self {
54 ClosePosition::Left => false,
55 ClosePosition::Right => true,
56 }
57 }
58}
59
60#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
61pub struct ItemSettingsContent {
62 git_status: Option<bool>,
63 close_position: Option<ClosePosition>,
64}
65
66impl Settings for ItemSettings {
67 const KEY: Option<&'static str> = Some("tabs");
68
69 type FileContent = ItemSettingsContent;
70
71 fn load(
72 default_value: &Self::FileContent,
73 user_values: &[&Self::FileContent],
74 _: &mut AppContext,
75 ) -> Result<Self> {
76 Self::load_via_json_merge(default_value, user_values)
77 }
78}
79
80#[derive(Eq, PartialEq, Hash, Debug)]
81pub enum ItemEvent {
82 CloseItem,
83 UpdateTab,
84 UpdateBreadcrumbs,
85 Edit,
86}
87
88// TODO: Combine this with existing HighlightedText struct?
89pub struct BreadcrumbText {
90 pub text: String,
91 pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
92}
93
94pub trait Item: Render + EventEmitter {
95 fn focus_handle(&self) -> FocusHandle;
96 fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
97 fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
98 fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
99 false
100 }
101 fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
102 None
103 }
104 fn tab_description(&self, _: usize, _: &AppContext) -> Option<SharedString> {
105 None
106 }
107 fn tab_content<V: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<V>;
108
109 fn for_each_project_item(
110 &self,
111 _: &AppContext,
112 _: &mut dyn FnMut(EntityId, &dyn project2::Item),
113 ) {
114 } // (model id, Item)
115 fn is_singleton(&self, _cx: &AppContext) -> bool {
116 false
117 }
118 fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
119 fn clone_on_split(
120 &self,
121 _workspace_id: WorkspaceId,
122 _: &mut ViewContext<Self>,
123 ) -> Option<View<Self>>
124 where
125 Self: Sized,
126 {
127 None
128 }
129 fn is_dirty(&self, _: &AppContext) -> bool {
130 false
131 }
132 fn has_conflict(&self, _: &AppContext) -> bool {
133 false
134 }
135 fn can_save(&self, _cx: &AppContext) -> bool {
136 false
137 }
138 fn save(&mut self, _project: Model<Project>, _cx: &mut ViewContext<Self>) -> Task<Result<()>> {
139 unimplemented!("save() must be implemented if can_save() returns true")
140 }
141 fn save_as(
142 &mut self,
143 _project: Model<Project>,
144 _abs_path: PathBuf,
145 _cx: &mut ViewContext<Self>,
146 ) -> Task<Result<()>> {
147 unimplemented!("save_as() must be implemented if can_save() returns true")
148 }
149 fn reload(
150 &mut self,
151 _project: Model<Project>,
152 _cx: &mut ViewContext<Self>,
153 ) -> Task<Result<()>> {
154 unimplemented!("reload() must be implemented if can_save() returns true")
155 }
156 fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
157 SmallVec::new()
158 }
159 fn should_close_item_on_event(_: &Self::Event) -> bool {
160 false
161 }
162 fn should_update_tab_on_event(_: &Self::Event) -> bool {
163 false
164 }
165
166 fn act_as_type<'a>(
167 &'a self,
168 type_id: TypeId,
169 self_handle: &'a View<Self>,
170 _: &'a AppContext,
171 ) -> Option<AnyView> {
172 if TypeId::of::<Self>() == type_id {
173 Some(self_handle.clone().into())
174 } else {
175 None
176 }
177 }
178
179 fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
180 None
181 }
182
183 fn breadcrumb_location(&self) -> ToolbarItemLocation {
184 ToolbarItemLocation::Hidden
185 }
186
187 fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
188 None
189 }
190
191 fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext<Self>) {}
192
193 fn serialized_item_kind() -> Option<&'static str> {
194 None
195 }
196
197 fn deserialize(
198 _project: Model<Project>,
199 _workspace: WeakView<Workspace>,
200 _workspace_id: WorkspaceId,
201 _item_id: ItemId,
202 _cx: &mut ViewContext<Pane>,
203 ) -> Task<Result<View<Self>>> {
204 unimplemented!(
205 "deserialize() must be implemented if serialized_item_kind() returns Some(_)"
206 )
207 }
208 fn show_toolbar(&self) -> bool {
209 true
210 }
211 fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Point<Pixels>> {
212 None
213 }
214}
215
216pub trait ItemHandle: 'static + Send {
217 fn focus_handle(&self, cx: &WindowContext) -> FocusHandle;
218 fn subscribe_to_item_events(
219 &self,
220 cx: &mut WindowContext,
221 handler: Box<dyn Fn(ItemEvent, &mut WindowContext) + Send>,
222 ) -> gpui::Subscription;
223 fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>;
224 fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>;
225 fn tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Pane>;
226 fn dragged_tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Workspace>;
227 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
228 fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
229 fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[EntityId; 3]>;
230 fn for_each_project_item(
231 &self,
232 _: &AppContext,
233 _: &mut dyn FnMut(EntityId, &dyn project2::Item),
234 );
235 fn is_singleton(&self, cx: &AppContext) -> bool;
236 fn boxed_clone(&self) -> Box<dyn ItemHandle>;
237 fn clone_on_split(
238 &self,
239 workspace_id: WorkspaceId,
240 cx: &mut WindowContext,
241 ) -> Option<Box<dyn ItemHandle>>;
242 fn added_to_pane(
243 &self,
244 workspace: &mut Workspace,
245 pane: View<Pane>,
246 cx: &mut ViewContext<Workspace>,
247 );
248 fn deactivated(&self, cx: &mut WindowContext);
249 fn workspace_deactivated(&self, cx: &mut WindowContext);
250 fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
251 fn id(&self) -> EntityId;
252 fn to_any(&self) -> AnyView;
253 fn is_dirty(&self, cx: &AppContext) -> bool;
254 fn has_conflict(&self, cx: &AppContext) -> bool;
255 fn can_save(&self, cx: &AppContext) -> bool;
256 fn save(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
257 fn save_as(
258 &self,
259 project: Model<Project>,
260 abs_path: PathBuf,
261 cx: &mut WindowContext,
262 ) -> Task<Result<()>>;
263 fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
264 fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyView>;
265 fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
266 fn on_release(
267 &self,
268 cx: &mut AppContext,
269 callback: Box<dyn FnOnce(&mut AppContext) + Send>,
270 ) -> gpui::Subscription;
271 fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
272 fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
273 fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
274 fn serialized_item_kind(&self) -> Option<&'static str>;
275 fn show_toolbar(&self, cx: &AppContext) -> bool;
276 fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>;
277}
278
279pub trait WeakItemHandle: Send + Sync {
280 fn id(&self) -> EntityId;
281 fn upgrade(&self) -> Option<Box<dyn ItemHandle>>;
282}
283
284impl dyn ItemHandle {
285 pub fn downcast<V: 'static>(&self) -> Option<View<V>> {
286 self.to_any().downcast().ok()
287 }
288
289 pub fn act_as<V: 'static>(&self, cx: &AppContext) -> Option<View<V>> {
290 self.act_as_type(TypeId::of::<V>(), cx)
291 .and_then(|t| t.downcast().ok())
292 }
293}
294
295impl<T: Item> ItemHandle for View<T> {
296 fn focus_handle(&self, cx: &WindowContext) -> FocusHandle {
297 self.read(cx).focus_handle()
298 }
299
300 fn subscribe_to_item_events(
301 &self,
302 cx: &mut WindowContext,
303 handler: Box<dyn Fn(ItemEvent, &mut WindowContext) + Send>,
304 ) -> gpui::Subscription {
305 cx.subscribe(self, move |_, event, cx| {
306 for item_event in T::to_item_events(event) {
307 handler(item_event, cx)
308 }
309 })
310 }
311
312 fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
313 self.read(cx).tab_tooltip_text(cx)
314 }
315
316 fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString> {
317 self.read(cx).tab_description(detail, cx)
318 }
319
320 fn tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Pane> {
321 self.read(cx).tab_content(detail, cx)
322 }
323
324 fn dragged_tab_content(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<Workspace> {
325 self.read(cx).tab_content(detail, cx)
326 }
327
328 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
329 let this = self.read(cx);
330 let mut result = None;
331 if this.is_singleton(cx) {
332 this.for_each_project_item(cx, &mut |_, item| {
333 result = item.project_path(cx);
334 });
335 }
336 result
337 }
338
339 fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
340 let mut result = SmallVec::new();
341 self.read(cx).for_each_project_item(cx, &mut |_, item| {
342 if let Some(id) = item.entry_id(cx) {
343 result.push(id);
344 }
345 });
346 result
347 }
348
349 fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[EntityId; 3]> {
350 let mut result = SmallVec::new();
351 self.read(cx).for_each_project_item(cx, &mut |id, _| {
352 result.push(id);
353 });
354 result
355 }
356
357 fn for_each_project_item(
358 &self,
359 cx: &AppContext,
360 f: &mut dyn FnMut(EntityId, &dyn project2::Item),
361 ) {
362 self.read(cx).for_each_project_item(cx, f)
363 }
364
365 fn is_singleton(&self, cx: &AppContext) -> bool {
366 self.read(cx).is_singleton(cx)
367 }
368
369 fn boxed_clone(&self) -> Box<dyn ItemHandle> {
370 Box::new(self.clone())
371 }
372
373 fn clone_on_split(
374 &self,
375 workspace_id: WorkspaceId,
376 cx: &mut WindowContext,
377 ) -> Option<Box<dyn ItemHandle>> {
378 self.update(cx, |item, cx| item.clone_on_split(workspace_id, cx))
379 .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
380 }
381
382 fn added_to_pane(
383 &self,
384 workspace: &mut Workspace,
385 pane: View<Pane>,
386 cx: &mut ViewContext<Workspace>,
387 ) {
388 let history = pane.read(cx).nav_history_for_item(self);
389 self.update(cx, |this, cx| {
390 this.set_nav_history(history, cx);
391 this.added_to_workspace(workspace, cx);
392 });
393
394 if let Some(followed_item) = self.to_followable_item_handle(cx) {
395 if let Some(message) = followed_item.to_state_proto(cx) {
396 workspace.update_followers(
397 followed_item.is_project_item(cx),
398 proto::update_followers::Variant::CreateView(proto::View {
399 id: followed_item
400 .remote_id(&workspace.app_state.client, cx)
401 .map(|id| id.to_proto()),
402 variant: Some(message),
403 leader_id: workspace.leader_for_pane(&pane),
404 }),
405 cx,
406 );
407 }
408 }
409
410 if workspace
411 .panes_by_item
412 .insert(self.id(), pane.downgrade())
413 .is_none()
414 {
415 let mut pending_autosave = DelayedDebouncedEditAction::new();
416 let pending_update = Rc::new(RefCell::new(None));
417 let pending_update_scheduled = Arc::new(AtomicBool::new(false));
418
419 let mut event_subscription =
420 Some(cx.subscribe(self, move |workspace, item, event, cx| {
421 let pane = if let Some(pane) = workspace
422 .panes_by_item
423 .get(&item.id())
424 .and_then(|pane| pane.upgrade())
425 {
426 pane
427 } else {
428 log::error!("unexpected item event after pane was dropped");
429 return;
430 };
431
432 if let Some(item) = item.to_followable_item_handle(cx) {
433 let is_project_item = item.is_project_item(cx);
434 let leader_id = workspace.leader_for_pane(&pane);
435
436 if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
437 workspace.unfollow(&pane, cx);
438 }
439
440 if item.add_event_to_update_proto(
441 event,
442 &mut *pending_update.borrow_mut(),
443 cx,
444 ) && !pending_update_scheduled.load(Ordering::SeqCst)
445 {
446 pending_update_scheduled.store(true, Ordering::SeqCst);
447 cx.on_next_frame({
448 let pending_update = pending_update.clone();
449 let pending_update_scheduled = pending_update_scheduled.clone();
450 move |this, cx| {
451 pending_update_scheduled.store(false, Ordering::SeqCst);
452 this.update_followers(
453 is_project_item,
454 proto::update_followers::Variant::UpdateView(
455 proto::UpdateView {
456 id: item
457 .remote_id(&this.app_state.client, cx)
458 .map(|id| id.to_proto()),
459 variant: pending_update.borrow_mut().take(),
460 leader_id,
461 },
462 ),
463 cx,
464 );
465 }
466 });
467 }
468 }
469
470 for item_event in T::to_item_events(event).into_iter() {
471 match item_event {
472 ItemEvent::CloseItem => {
473 pane.update(cx, |pane, cx| {
474 pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx)
475 })
476 .detach_and_log_err(cx);
477 return;
478 }
479
480 ItemEvent::UpdateTab => {
481 pane.update(cx, |_, cx| {
482 cx.emit(pane::Event::ChangeItemTitle);
483 cx.notify();
484 });
485 }
486
487 ItemEvent::Edit => {
488 let autosave = WorkspaceSettings::get_global(cx).autosave;
489 if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
490 let delay = Duration::from_millis(milliseconds);
491 let item = item.clone();
492 pending_autosave.fire_new(delay, cx, move |workspace, cx| {
493 Pane::autosave_item(&item, workspace.project().clone(), cx)
494 });
495 }
496 }
497
498 _ => {}
499 }
500 }
501 }));
502
503 // todo!()
504 // cx.observe_focus(self, move |workspace, item, focused, cx| {
505 // if !focused
506 // && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange
507 // {
508 // Pane::autosave_item(&item, workspace.project.clone(), cx)
509 // .detach_and_log_err(cx);
510 // }
511 // })
512 // .detach();
513
514 let item_id = self.id();
515 cx.observe_release(self, move |workspace, _, _| {
516 workspace.panes_by_item.remove(&item_id);
517 event_subscription.take();
518 })
519 .detach();
520 }
521
522 cx.defer(|workspace, cx| {
523 workspace.serialize_workspace(cx);
524 });
525 }
526
527 fn deactivated(&self, cx: &mut WindowContext) {
528 self.update(cx, |this, cx| this.deactivated(cx));
529 }
530
531 fn workspace_deactivated(&self, cx: &mut WindowContext) {
532 self.update(cx, |this, cx| this.workspace_deactivated(cx));
533 }
534
535 fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool {
536 self.update(cx, |this, cx| this.navigate(data, cx))
537 }
538
539 fn id(&self) -> EntityId {
540 self.entity_id()
541 }
542
543 fn to_any(&self) -> AnyView {
544 self.clone().into()
545 }
546
547 fn is_dirty(&self, cx: &AppContext) -> bool {
548 self.read(cx).is_dirty(cx)
549 }
550
551 fn has_conflict(&self, cx: &AppContext) -> bool {
552 self.read(cx).has_conflict(cx)
553 }
554
555 fn can_save(&self, cx: &AppContext) -> bool {
556 self.read(cx).can_save(cx)
557 }
558
559 fn save(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
560 self.update(cx, |item, cx| item.save(project, cx))
561 }
562
563 fn save_as(
564 &self,
565 project: Model<Project>,
566 abs_path: PathBuf,
567 cx: &mut WindowContext,
568 ) -> Task<anyhow::Result<()>> {
569 self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
570 }
571
572 fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
573 self.update(cx, |item, cx| item.reload(project, cx))
574 }
575
576 fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<AnyView> {
577 self.read(cx).act_as_type(type_id, self, cx)
578 }
579
580 fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
581 if cx.has_global::<FollowableItemBuilders>() {
582 let builders = cx.global::<FollowableItemBuilders>();
583 let item = self.to_any();
584 Some(builders.get(&item.entity_type())?.1(&item))
585 } else {
586 None
587 }
588 }
589
590 fn on_release(
591 &self,
592 cx: &mut AppContext,
593 callback: Box<dyn FnOnce(&mut AppContext) + Send>,
594 ) -> gpui::Subscription {
595 cx.observe_release(self, move |_, cx| callback(cx))
596 }
597
598 fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
599 self.read(cx).as_searchable(self)
600 }
601
602 fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
603 self.read(cx).breadcrumb_location()
604 }
605
606 fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
607 self.read(cx).breadcrumbs(theme, cx)
608 }
609
610 fn serialized_item_kind(&self) -> Option<&'static str> {
611 T::serialized_item_kind()
612 }
613
614 fn show_toolbar(&self, cx: &AppContext) -> bool {
615 self.read(cx).show_toolbar()
616 }
617
618 fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
619 self.read(cx).pixel_position_of_cursor(cx)
620 }
621}
622
623impl From<Box<dyn ItemHandle>> for AnyView {
624 fn from(val: Box<dyn ItemHandle>) -> Self {
625 val.to_any()
626 }
627}
628
629impl From<&Box<dyn ItemHandle>> for AnyView {
630 fn from(val: &Box<dyn ItemHandle>) -> Self {
631 val.to_any()
632 }
633}
634
635impl Clone for Box<dyn ItemHandle> {
636 fn clone(&self) -> Box<dyn ItemHandle> {
637 self.boxed_clone()
638 }
639}
640
641impl<T: Item> WeakItemHandle for WeakView<T> {
642 fn id(&self) -> EntityId {
643 self.entity_id()
644 }
645
646 fn upgrade(&self) -> Option<Box<dyn ItemHandle>> {
647 self.upgrade().map(|v| Box::new(v) as Box<dyn ItemHandle>)
648 }
649}
650
651pub trait ProjectItem: Item {
652 type Item: project2::Item;
653
654 fn for_project_item(
655 project: Model<Project>,
656 item: Model<Self::Item>,
657 cx: &mut ViewContext<Self>,
658 ) -> Self
659 where
660 Self: Sized;
661}
662
663pub trait FollowableItem: Item {
664 fn remote_id(&self) -> Option<ViewId>;
665 fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
666 fn from_state_proto(
667 pane: View<Pane>,
668 project: View<Workspace>,
669 id: ViewId,
670 state: &mut Option<proto::view::Variant>,
671 cx: &mut AppContext,
672 ) -> Option<Task<Result<View<Self>>>>;
673 fn add_event_to_update_proto(
674 &self,
675 event: &Self::Event,
676 update: &mut Option<proto::update_view::Variant>,
677 cx: &AppContext,
678 ) -> bool;
679 fn apply_update_proto(
680 &mut self,
681 project: &Model<Project>,
682 message: proto::update_view::Variant,
683 cx: &mut ViewContext<Self>,
684 ) -> Task<Result<()>>;
685 fn is_project_item(&self, cx: &AppContext) -> bool;
686
687 fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
688 fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
689}
690
691pub trait FollowableItemHandle: ItemHandle {
692 fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId>;
693 fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
694 fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
695 fn add_event_to_update_proto(
696 &self,
697 event: &dyn Any,
698 update: &mut Option<proto::update_view::Variant>,
699 cx: &AppContext,
700 ) -> bool;
701 fn apply_update_proto(
702 &self,
703 project: &Model<Project>,
704 message: proto::update_view::Variant,
705 cx: &mut WindowContext,
706 ) -> Task<Result<()>>;
707 fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
708 fn is_project_item(&self, cx: &AppContext) -> bool;
709}
710
711impl<T: FollowableItem> FollowableItemHandle for View<T> {
712 fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId> {
713 self.read(cx).remote_id().or_else(|| {
714 client.peer_id().map(|creator| ViewId {
715 creator,
716 id: self.id().as_u64(),
717 })
718 })
719 }
720
721 fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext) {
722 self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
723 }
724
725 fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
726 self.read(cx).to_state_proto(cx)
727 }
728
729 fn add_event_to_update_proto(
730 &self,
731 event: &dyn Any,
732 update: &mut Option<proto::update_view::Variant>,
733 cx: &AppContext,
734 ) -> bool {
735 if let Some(event) = event.downcast_ref() {
736 self.read(cx).add_event_to_update_proto(event, update, cx)
737 } else {
738 false
739 }
740 }
741
742 fn apply_update_proto(
743 &self,
744 project: &Model<Project>,
745 message: proto::update_view::Variant,
746 cx: &mut WindowContext,
747 ) -> Task<Result<()>> {
748 self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
749 }
750
751 fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
752 if let Some(event) = event.downcast_ref() {
753 T::should_unfollow_on_event(event, cx)
754 } else {
755 false
756 }
757 }
758
759 fn is_project_item(&self, cx: &AppContext) -> bool {
760 self.read(cx).is_project_item(cx)
761 }
762}
763
764// #[cfg(any(test, feature = "test-support"))]
765// pub mod test {
766// use super::{Item, ItemEvent};
767// use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
768// use gpui::{
769// elements::Empty, AnyElement, AppContext, Element, Entity, Model, Task, View,
770// ViewContext, View, WeakViewHandle,
771// };
772// use project2::{Project, ProjectEntryId, ProjectPath, WorktreeId};
773// use smallvec::SmallVec;
774// use std::{any::Any, borrow::Cow, cell::Cell, path::Path};
775
776// pub struct TestProjectItem {
777// pub entry_id: Option<ProjectEntryId>,
778// pub project_path: Option<ProjectPath>,
779// }
780
781// pub struct TestItem {
782// pub workspace_id: WorkspaceId,
783// pub state: String,
784// pub label: String,
785// pub save_count: usize,
786// pub save_as_count: usize,
787// pub reload_count: usize,
788// pub is_dirty: bool,
789// pub is_singleton: bool,
790// pub has_conflict: bool,
791// pub project_items: Vec<Model<TestProjectItem>>,
792// pub nav_history: Option<ItemNavHistory>,
793// pub tab_descriptions: Option<Vec<&'static str>>,
794// pub tab_detail: Cell<Option<usize>>,
795// }
796
797// impl Entity for TestProjectItem {
798// type Event = ();
799// }
800
801// impl project2::Item for TestProjectItem {
802// fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
803// self.entry_id
804// }
805
806// fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
807// self.project_path.clone()
808// }
809// }
810
811// pub enum TestItemEvent {
812// Edit,
813// }
814
815// impl Clone for TestItem {
816// fn clone(&self) -> Self {
817// Self {
818// state: self.state.clone(),
819// label: self.label.clone(),
820// save_count: self.save_count,
821// save_as_count: self.save_as_count,
822// reload_count: self.reload_count,
823// is_dirty: self.is_dirty,
824// is_singleton: self.is_singleton,
825// has_conflict: self.has_conflict,
826// project_items: self.project_items.clone(),
827// nav_history: None,
828// tab_descriptions: None,
829// tab_detail: Default::default(),
830// workspace_id: self.workspace_id,
831// }
832// }
833// }
834
835// impl TestProjectItem {
836// pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
837// let entry_id = Some(ProjectEntryId::from_proto(id));
838// let project_path = Some(ProjectPath {
839// worktree_id: WorktreeId::from_usize(0),
840// path: Path::new(path).into(),
841// });
842// cx.add_model(|_| Self {
843// entry_id,
844// project_path,
845// })
846// }
847
848// pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
849// cx.add_model(|_| Self {
850// project_path: None,
851// entry_id: None,
852// })
853// }
854// }
855
856// impl TestItem {
857// pub fn new() -> Self {
858// Self {
859// state: String::new(),
860// label: String::new(),
861// save_count: 0,
862// save_as_count: 0,
863// reload_count: 0,
864// is_dirty: false,
865// has_conflict: false,
866// project_items: Vec::new(),
867// is_singleton: true,
868// nav_history: None,
869// tab_descriptions: None,
870// tab_detail: Default::default(),
871// workspace_id: 0,
872// }
873// }
874
875// pub fn new_deserialized(id: WorkspaceId) -> Self {
876// let mut this = Self::new();
877// this.workspace_id = id;
878// this
879// }
880
881// pub fn with_label(mut self, state: &str) -> Self {
882// self.label = state.to_string();
883// self
884// }
885
886// pub fn with_singleton(mut self, singleton: bool) -> Self {
887// self.is_singleton = singleton;
888// self
889// }
890
891// pub fn with_dirty(mut self, dirty: bool) -> Self {
892// self.is_dirty = dirty;
893// self
894// }
895
896// pub fn with_conflict(mut self, has_conflict: bool) -> Self {
897// self.has_conflict = has_conflict;
898// self
899// }
900
901// pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
902// self.project_items.clear();
903// self.project_items.extend(items.iter().cloned());
904// self
905// }
906
907// pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
908// self.push_to_nav_history(cx);
909// self.state = state;
910// }
911
912// fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
913// if let Some(history) = &mut self.nav_history {
914// history.push(Some(Box::new(self.state.clone())), cx);
915// }
916// }
917// }
918
919// impl Entity for TestItem {
920// type Event = TestItemEvent;
921// }
922
923// impl View for TestItem {
924// fn ui_name() -> &'static str {
925// "TestItem"
926// }
927
928// fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
929// Empty::new().into_any()
930// }
931// }
932
933// impl Item for TestItem {
934// fn tab_description(&self, detail: usize, _: &AppContext) -> Option<Cow<str>> {
935// self.tab_descriptions.as_ref().and_then(|descriptions| {
936// let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
937// Some(description.into())
938// })
939// }
940
941// fn tab_content<V: 'static>(
942// &self,
943// detail: Option<usize>,
944// _: &theme2::Tab,
945// _: &AppContext,
946// ) -> AnyElement<V> {
947// self.tab_detail.set(detail);
948// Empty::new().into_any()
949// }
950
951// fn for_each_project_item(
952// &self,
953// cx: &AppContext,
954// f: &mut dyn FnMut(usize, &dyn project2::Item),
955// ) {
956// self.project_items
957// .iter()
958// .for_each(|item| f(item.id(), item.read(cx)))
959// }
960
961// fn is_singleton(&self, _: &AppContext) -> bool {
962// self.is_singleton
963// }
964
965// fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
966// self.nav_history = Some(history);
967// }
968
969// fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
970// let state = *state.downcast::<String>().unwrap_or_default();
971// if state != self.state {
972// self.state = state;
973// true
974// } else {
975// false
976// }
977// }
978
979// fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
980// self.push_to_nav_history(cx);
981// }
982
983// fn clone_on_split(
984// &self,
985// _workspace_id: WorkspaceId,
986// _: &mut ViewContext<Self>,
987// ) -> Option<Self>
988// where
989// Self: Sized,
990// {
991// Some(self.clone())
992// }
993
994// fn is_dirty(&self, _: &AppContext) -> bool {
995// self.is_dirty
996// }
997
998// fn has_conflict(&self, _: &AppContext) -> bool {
999// self.has_conflict
1000// }
1001
1002// fn can_save(&self, cx: &AppContext) -> bool {
1003// !self.project_items.is_empty()
1004// && self
1005// .project_items
1006// .iter()
1007// .all(|item| item.read(cx).entry_id.is_some())
1008// }
1009
1010// fn save(
1011// &mut self,
1012// _: Model<Project>,
1013// _: &mut ViewContext<Self>,
1014// ) -> Task<anyhow::Result<()>> {
1015// self.save_count += 1;
1016// self.is_dirty = false;
1017// Task::ready(Ok(()))
1018// }
1019
1020// fn save_as(
1021// &mut self,
1022// _: Model<Project>,
1023// _: std::path::PathBuf,
1024// _: &mut ViewContext<Self>,
1025// ) -> Task<anyhow::Result<()>> {
1026// self.save_as_count += 1;
1027// self.is_dirty = false;
1028// Task::ready(Ok(()))
1029// }
1030
1031// fn reload(
1032// &mut self,
1033// _: Model<Project>,
1034// _: &mut ViewContext<Self>,
1035// ) -> Task<anyhow::Result<()>> {
1036// self.reload_count += 1;
1037// self.is_dirty = false;
1038// Task::ready(Ok(()))
1039// }
1040
1041// fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
1042// [ItemEvent::UpdateTab, ItemEvent::Edit].into()
1043// }
1044
1045// fn serialized_item_kind() -> Option<&'static str> {
1046// Some("TestItem")
1047// }
1048
1049// fn deserialize(
1050// _project: Model<Project>,
1051// _workspace: WeakViewHandle<Workspace>,
1052// workspace_id: WorkspaceId,
1053// _item_id: ItemId,
1054// cx: &mut ViewContext<Pane>,
1055// ) -> Task<anyhow::Result<View<Self>>> {
1056// let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id));
1057// Task::Ready(Some(anyhow::Ok(view)))
1058// }
1059// }
1060// }