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