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