1use std::{
2 any::{Any, TypeId},
3 borrow::Cow,
4 cell::RefCell,
5 fmt,
6 path::PathBuf,
7 rc::Rc,
8 sync::atomic::{AtomicBool, Ordering},
9 time::Duration,
10};
11
12use anyhow::Result;
13use client::proto;
14use gpui::{
15 AnyViewHandle, AppContext, ElementBox, ModelHandle, MutableAppContext, Task, View, ViewContext,
16 ViewHandle, WeakViewHandle,
17};
18use project::{Project, ProjectEntryId, ProjectPath};
19use settings::{Autosave, Settings};
20use smallvec::SmallVec;
21use theme::Theme;
22use util::ResultExt;
23
24use crate::{
25 pane, persistence::model::ItemId, searchable::SearchableItemHandle, DelayedDebouncedEditAction,
26 FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
27};
28
29#[derive(Eq, PartialEq, Hash)]
30pub enum ItemEvent {
31 CloseItem,
32 UpdateTab,
33 UpdateBreadcrumbs,
34 Edit,
35}
36
37pub trait Item: View {
38 fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
39 fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
40 fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
41 false
42 }
43 fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
44 None
45 }
46 fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
47 -> ElementBox;
48 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
49 fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
50 fn is_singleton(&self, cx: &AppContext) -> bool;
51 fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
52 fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext<Self>) -> Option<Self>
53 where
54 Self: Sized,
55 {
56 None
57 }
58 fn is_dirty(&self, _: &AppContext) -> bool {
59 false
60 }
61 fn has_conflict(&self, _: &AppContext) -> bool {
62 false
63 }
64 fn can_save(&self, cx: &AppContext) -> bool;
65 fn save(
66 &mut self,
67 project: ModelHandle<Project>,
68 cx: &mut ViewContext<Self>,
69 ) -> Task<Result<()>>;
70 fn save_as(
71 &mut self,
72 project: ModelHandle<Project>,
73 abs_path: PathBuf,
74 cx: &mut ViewContext<Self>,
75 ) -> Task<Result<()>>;
76 fn reload(
77 &mut self,
78 project: ModelHandle<Project>,
79 cx: &mut ViewContext<Self>,
80 ) -> Task<Result<()>>;
81 fn git_diff_recalc(
82 &mut self,
83 _project: ModelHandle<Project>,
84 _cx: &mut ViewContext<Self>,
85 ) -> Task<Result<()>> {
86 Task::ready(Ok(()))
87 }
88 fn to_item_events(event: &Self::Event) -> Vec<ItemEvent>;
89 fn should_close_item_on_event(_: &Self::Event) -> bool {
90 false
91 }
92 fn should_update_tab_on_event(_: &Self::Event) -> bool {
93 false
94 }
95 fn is_edit_event(_: &Self::Event) -> bool {
96 false
97 }
98 fn act_as_type(
99 &self,
100 type_id: TypeId,
101 self_handle: &ViewHandle<Self>,
102 _: &AppContext,
103 ) -> Option<AnyViewHandle> {
104 if TypeId::of::<Self>() == type_id {
105 Some(self_handle.into())
106 } else {
107 None
108 }
109 }
110 fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
111 None
112 }
113
114 fn breadcrumb_location(&self) -> ToolbarItemLocation {
115 ToolbarItemLocation::Hidden
116 }
117
118 fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<ElementBox>> {
119 None
120 }
121
122 fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext<Self>) {}
123
124 fn serialized_item_kind() -> Option<&'static str>;
125
126 fn deserialize(
127 project: ModelHandle<Project>,
128 workspace: WeakViewHandle<Workspace>,
129 workspace_id: WorkspaceId,
130 item_id: ItemId,
131 cx: &mut ViewContext<Pane>,
132 ) -> Task<Result<ViewHandle<Self>>>;
133}
134
135pub trait ItemHandle: 'static + fmt::Debug {
136 fn subscribe_to_item_events(
137 &self,
138 cx: &mut MutableAppContext,
139 handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
140 ) -> gpui::Subscription;
141 fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
142 fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
143 -> ElementBox;
144 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
145 fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
146 fn is_singleton(&self, cx: &AppContext) -> bool;
147 fn boxed_clone(&self) -> Box<dyn ItemHandle>;
148 fn clone_on_split(
149 &self,
150 workspace_id: WorkspaceId,
151 cx: &mut MutableAppContext,
152 ) -> Option<Box<dyn ItemHandle>>;
153 fn added_to_pane(
154 &self,
155 workspace: &mut Workspace,
156 pane: ViewHandle<Pane>,
157 cx: &mut ViewContext<Workspace>,
158 );
159 fn deactivated(&self, cx: &mut MutableAppContext);
160 fn workspace_deactivated(&self, cx: &mut MutableAppContext);
161 fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool;
162 fn id(&self) -> usize;
163 fn window_id(&self) -> usize;
164 fn to_any(&self) -> AnyViewHandle;
165 fn is_dirty(&self, cx: &AppContext) -> bool;
166 fn has_conflict(&self, cx: &AppContext) -> bool;
167 fn can_save(&self, cx: &AppContext) -> bool;
168 fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>>;
169 fn save_as(
170 &self,
171 project: ModelHandle<Project>,
172 abs_path: PathBuf,
173 cx: &mut MutableAppContext,
174 ) -> Task<Result<()>>;
175 fn reload(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext)
176 -> Task<Result<()>>;
177 fn git_diff_recalc(
178 &self,
179 project: ModelHandle<Project>,
180 cx: &mut MutableAppContext,
181 ) -> Task<Result<()>>;
182 fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle>;
183 fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
184 fn on_release(
185 &self,
186 cx: &mut MutableAppContext,
187 callback: Box<dyn FnOnce(&mut MutableAppContext)>,
188 ) -> gpui::Subscription;
189 fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
190 fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
191 fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>>;
192 fn serialized_item_kind(&self) -> Option<&'static str>;
193}
194
195pub trait WeakItemHandle {
196 fn id(&self) -> usize;
197 fn window_id(&self) -> usize;
198 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
199}
200
201impl dyn ItemHandle {
202 pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
203 self.to_any().downcast()
204 }
205
206 pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
207 self.act_as_type(TypeId::of::<T>(), cx)
208 .and_then(|t| t.downcast())
209 }
210}
211
212impl<T: Item> ItemHandle for ViewHandle<T> {
213 fn subscribe_to_item_events(
214 &self,
215 cx: &mut MutableAppContext,
216 handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
217 ) -> gpui::Subscription {
218 cx.subscribe(self, move |_, event, cx| {
219 for item_event in T::to_item_events(event) {
220 handler(item_event, cx)
221 }
222 })
223 }
224
225 fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
226 self.read(cx).tab_description(detail, cx)
227 }
228
229 fn tab_content(
230 &self,
231 detail: Option<usize>,
232 style: &theme::Tab,
233 cx: &AppContext,
234 ) -> ElementBox {
235 self.read(cx).tab_content(detail, style, cx)
236 }
237
238 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
239 self.read(cx).project_path(cx)
240 }
241
242 fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
243 self.read(cx).project_entry_ids(cx)
244 }
245
246 fn is_singleton(&self, cx: &AppContext) -> bool {
247 self.read(cx).is_singleton(cx)
248 }
249
250 fn boxed_clone(&self) -> Box<dyn ItemHandle> {
251 Box::new(self.clone())
252 }
253
254 fn clone_on_split(
255 &self,
256 workspace_id: WorkspaceId,
257 cx: &mut MutableAppContext,
258 ) -> Option<Box<dyn ItemHandle>> {
259 self.update(cx, |item, cx| {
260 cx.add_option_view(|cx| item.clone_on_split(workspace_id, cx))
261 })
262 .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
263 }
264
265 fn added_to_pane(
266 &self,
267 workspace: &mut Workspace,
268 pane: ViewHandle<Pane>,
269 cx: &mut ViewContext<Workspace>,
270 ) {
271 let history = pane.read(cx).nav_history_for_item(self);
272 self.update(cx, |this, cx| {
273 this.set_nav_history(history, cx);
274 this.added_to_workspace(workspace, cx);
275 });
276
277 if let Some(followed_item) = self.to_followable_item_handle(cx) {
278 if let Some(message) = followed_item.to_state_proto(cx) {
279 workspace.update_followers(
280 proto::update_followers::Variant::CreateView(proto::View {
281 id: followed_item.id() as u64,
282 variant: Some(message),
283 leader_id: workspace.leader_for_pane(&pane).map(|id| id.0),
284 }),
285 cx,
286 );
287 }
288 }
289
290 if workspace
291 .panes_by_item
292 .insert(self.id(), pane.downgrade())
293 .is_none()
294 {
295 let mut pending_autosave = DelayedDebouncedEditAction::new();
296 let mut pending_git_update = DelayedDebouncedEditAction::new();
297 let pending_update = Rc::new(RefCell::new(None));
298 let pending_update_scheduled = Rc::new(AtomicBool::new(false));
299
300 let mut event_subscription =
301 Some(cx.subscribe(self, move |workspace, item, event, cx| {
302 let pane = if let Some(pane) = workspace
303 .panes_by_item
304 .get(&item.id())
305 .and_then(|pane| pane.upgrade(cx))
306 {
307 pane
308 } else {
309 log::error!("unexpected item event after pane was dropped");
310 return;
311 };
312
313 if let Some(item) = item.to_followable_item_handle(cx) {
314 let leader_id = workspace.leader_for_pane(&pane);
315
316 if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
317 workspace.unfollow(&pane, cx);
318 }
319
320 if item.add_event_to_update_proto(
321 event,
322 &mut *pending_update.borrow_mut(),
323 cx,
324 ) && !pending_update_scheduled.load(Ordering::SeqCst)
325 {
326 pending_update_scheduled.store(true, Ordering::SeqCst);
327 cx.after_window_update({
328 let pending_update = pending_update.clone();
329 let pending_update_scheduled = pending_update_scheduled.clone();
330 move |this, cx| {
331 pending_update_scheduled.store(false, Ordering::SeqCst);
332 this.update_followers(
333 proto::update_followers::Variant::UpdateView(
334 proto::UpdateView {
335 id: item.id() as u64,
336 variant: pending_update.borrow_mut().take(),
337 leader_id: leader_id.map(|id| id.0),
338 },
339 ),
340 cx,
341 );
342 }
343 });
344 }
345 }
346
347 for item_event in T::to_item_events(event).into_iter() {
348 match item_event {
349 ItemEvent::CloseItem => {
350 Pane::close_item(workspace, pane, item.id(), cx)
351 .detach_and_log_err(cx);
352 return;
353 }
354
355 ItemEvent::UpdateTab => {
356 pane.update(cx, |_, cx| {
357 cx.emit(pane::Event::ChangeItemTitle);
358 cx.notify();
359 });
360 }
361
362 ItemEvent::Edit => {
363 if let Autosave::AfterDelay { milliseconds } =
364 cx.global::<Settings>().autosave
365 {
366 let delay = Duration::from_millis(milliseconds);
367 let item = item.clone();
368 pending_autosave.fire_new(
369 delay,
370 workspace,
371 cx,
372 |project, mut cx| async move {
373 cx.update(|cx| Pane::autosave_item(&item, project, cx))
374 .await
375 .log_err();
376 },
377 );
378 }
379
380 let settings = cx.global::<Settings>();
381 let debounce_delay = settings.git_overrides.gutter_debounce;
382
383 let item = item.clone();
384
385 if let Some(delay) = debounce_delay {
386 const MIN_GIT_DELAY: u64 = 50;
387
388 let delay = delay.max(MIN_GIT_DELAY);
389 let duration = Duration::from_millis(delay);
390
391 pending_git_update.fire_new(
392 duration,
393 workspace,
394 cx,
395 |project, mut cx| async move {
396 cx.update(|cx| item.git_diff_recalc(project, cx))
397 .await
398 .log_err();
399 },
400 );
401 } else {
402 let project = workspace.project().downgrade();
403 cx.spawn_weak(|_, mut cx| async move {
404 if let Some(project) = project.upgrade(&cx) {
405 cx.update(|cx| item.git_diff_recalc(project, cx))
406 .await
407 .log_err();
408 }
409 })
410 .detach();
411 }
412 }
413
414 _ => {}
415 }
416 }
417 }));
418
419 cx.observe_focus(self, move |workspace, item, focused, cx| {
420 if !focused && cx.global::<Settings>().autosave == Autosave::OnFocusChange {
421 Pane::autosave_item(&item, workspace.project.clone(), cx)
422 .detach_and_log_err(cx);
423 }
424 })
425 .detach();
426
427 let item_id = self.id();
428 cx.observe_release(self, move |workspace, _, _| {
429 workspace.panes_by_item.remove(&item_id);
430 event_subscription.take();
431 })
432 .detach();
433 }
434
435 cx.defer(|workspace, cx| {
436 workspace.serialize_workspace(cx);
437 });
438 }
439
440 fn deactivated(&self, cx: &mut MutableAppContext) {
441 self.update(cx, |this, cx| this.deactivated(cx));
442 }
443
444 fn workspace_deactivated(&self, cx: &mut MutableAppContext) {
445 self.update(cx, |this, cx| this.workspace_deactivated(cx));
446 }
447
448 fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool {
449 self.update(cx, |this, cx| this.navigate(data, cx))
450 }
451
452 fn id(&self) -> usize {
453 self.id()
454 }
455
456 fn window_id(&self) -> usize {
457 self.window_id()
458 }
459
460 fn to_any(&self) -> AnyViewHandle {
461 self.into()
462 }
463
464 fn is_dirty(&self, cx: &AppContext) -> bool {
465 self.read(cx).is_dirty(cx)
466 }
467
468 fn has_conflict(&self, cx: &AppContext) -> bool {
469 self.read(cx).has_conflict(cx)
470 }
471
472 fn can_save(&self, cx: &AppContext) -> bool {
473 self.read(cx).can_save(cx)
474 }
475
476 fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>> {
477 self.update(cx, |item, cx| item.save(project, cx))
478 }
479
480 fn save_as(
481 &self,
482 project: ModelHandle<Project>,
483 abs_path: PathBuf,
484 cx: &mut MutableAppContext,
485 ) -> Task<anyhow::Result<()>> {
486 self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
487 }
488
489 fn reload(
490 &self,
491 project: ModelHandle<Project>,
492 cx: &mut MutableAppContext,
493 ) -> Task<Result<()>> {
494 self.update(cx, |item, cx| item.reload(project, cx))
495 }
496
497 fn git_diff_recalc(
498 &self,
499 project: ModelHandle<Project>,
500 cx: &mut MutableAppContext,
501 ) -> Task<Result<()>> {
502 self.update(cx, |item, cx| item.git_diff_recalc(project, cx))
503 }
504
505 fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle> {
506 self.read(cx).act_as_type(type_id, self, cx)
507 }
508
509 fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
510 if cx.has_global::<FollowableItemBuilders>() {
511 let builders = cx.global::<FollowableItemBuilders>();
512 let item = self.to_any();
513 Some(builders.get(&item.view_type())?.1(item))
514 } else {
515 None
516 }
517 }
518
519 fn on_release(
520 &self,
521 cx: &mut MutableAppContext,
522 callback: Box<dyn FnOnce(&mut MutableAppContext)>,
523 ) -> gpui::Subscription {
524 cx.observe_release(self, move |_, cx| callback(cx))
525 }
526
527 fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
528 self.read(cx).as_searchable(self)
529 }
530
531 fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
532 self.read(cx).breadcrumb_location()
533 }
534
535 fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
536 self.read(cx).breadcrumbs(theme, cx)
537 }
538
539 fn serialized_item_kind(&self) -> Option<&'static str> {
540 T::serialized_item_kind()
541 }
542}
543
544impl From<Box<dyn ItemHandle>> for AnyViewHandle {
545 fn from(val: Box<dyn ItemHandle>) -> Self {
546 val.to_any()
547 }
548}
549
550impl From<&Box<dyn ItemHandle>> for AnyViewHandle {
551 fn from(val: &Box<dyn ItemHandle>) -> Self {
552 val.to_any()
553 }
554}
555
556impl Clone for Box<dyn ItemHandle> {
557 fn clone(&self) -> Box<dyn ItemHandle> {
558 self.boxed_clone()
559 }
560}
561
562impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
563 fn id(&self) -> usize {
564 self.id()
565 }
566
567 fn window_id(&self) -> usize {
568 self.window_id()
569 }
570
571 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
572 self.upgrade(cx).map(|v| Box::new(v) as Box<dyn ItemHandle>)
573 }
574}
575
576pub trait ProjectItem: Item {
577 type Item: project::Item;
578
579 fn for_project_item(
580 project: ModelHandle<Project>,
581 item: ModelHandle<Self::Item>,
582 cx: &mut ViewContext<Self>,
583 ) -> Self;
584}
585
586pub trait FollowableItem: Item {
587 fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
588 fn from_state_proto(
589 pane: ViewHandle<Pane>,
590 project: ModelHandle<Project>,
591 state: &mut Option<proto::view::Variant>,
592 cx: &mut MutableAppContext,
593 ) -> Option<Task<Result<ViewHandle<Self>>>>;
594 fn add_event_to_update_proto(
595 &self,
596 event: &Self::Event,
597 update: &mut Option<proto::update_view::Variant>,
598 cx: &AppContext,
599 ) -> bool;
600 fn apply_update_proto(
601 &mut self,
602 message: proto::update_view::Variant,
603 cx: &mut ViewContext<Self>,
604 ) -> Result<()>;
605
606 fn set_leader_replica_id(&mut self, leader_replica_id: Option<u16>, cx: &mut ViewContext<Self>);
607 fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
608}
609
610pub trait FollowableItemHandle: ItemHandle {
611 fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext);
612 fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
613 fn add_event_to_update_proto(
614 &self,
615 event: &dyn Any,
616 update: &mut Option<proto::update_view::Variant>,
617 cx: &AppContext,
618 ) -> bool;
619 fn apply_update_proto(
620 &self,
621 message: proto::update_view::Variant,
622 cx: &mut MutableAppContext,
623 ) -> Result<()>;
624 fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
625}
626
627impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
628 fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext) {
629 self.update(cx, |this, cx| {
630 this.set_leader_replica_id(leader_replica_id, cx)
631 })
632 }
633
634 fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
635 self.read(cx).to_state_proto(cx)
636 }
637
638 fn add_event_to_update_proto(
639 &self,
640 event: &dyn Any,
641 update: &mut Option<proto::update_view::Variant>,
642 cx: &AppContext,
643 ) -> bool {
644 if let Some(event) = event.downcast_ref() {
645 self.read(cx).add_event_to_update_proto(event, update, cx)
646 } else {
647 false
648 }
649 }
650
651 fn apply_update_proto(
652 &self,
653 message: proto::update_view::Variant,
654 cx: &mut MutableAppContext,
655 ) -> Result<()> {
656 self.update(cx, |this, cx| this.apply_update_proto(message, cx))
657 }
658
659 fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
660 if let Some(event) = event.downcast_ref() {
661 T::should_unfollow_on_event(event, cx)
662 } else {
663 false
664 }
665 }
666}
667
668#[cfg(test)]
669pub(crate) mod test {
670 use std::{any::Any, borrow::Cow, cell::Cell};
671
672 use gpui::{
673 elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, RenderContext, Task,
674 View, ViewContext, ViewHandle, WeakViewHandle,
675 };
676 use project::{Project, ProjectEntryId, ProjectPath};
677 use smallvec::SmallVec;
678
679 use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
680
681 use super::{Item, ItemEvent};
682
683 pub struct TestItem {
684 pub state: String,
685 pub label: String,
686 pub save_count: usize,
687 pub save_as_count: usize,
688 pub reload_count: usize,
689 pub is_dirty: bool,
690 pub is_singleton: bool,
691 pub has_conflict: bool,
692 pub project_entry_ids: Vec<ProjectEntryId>,
693 pub project_path: Option<ProjectPath>,
694 pub nav_history: Option<ItemNavHistory>,
695 pub tab_descriptions: Option<Vec<&'static str>>,
696 pub tab_detail: Cell<Option<usize>>,
697 }
698
699 pub enum TestItemEvent {
700 Edit,
701 }
702
703 impl Clone for TestItem {
704 fn clone(&self) -> Self {
705 Self {
706 state: self.state.clone(),
707 label: self.label.clone(),
708 save_count: self.save_count,
709 save_as_count: self.save_as_count,
710 reload_count: self.reload_count,
711 is_dirty: self.is_dirty,
712 is_singleton: self.is_singleton,
713 has_conflict: self.has_conflict,
714 project_entry_ids: self.project_entry_ids.clone(),
715 project_path: self.project_path.clone(),
716 nav_history: None,
717 tab_descriptions: None,
718 tab_detail: Default::default(),
719 }
720 }
721 }
722
723 impl TestItem {
724 pub fn new() -> Self {
725 Self {
726 state: String::new(),
727 label: String::new(),
728 save_count: 0,
729 save_as_count: 0,
730 reload_count: 0,
731 is_dirty: false,
732 has_conflict: false,
733 project_entry_ids: Vec::new(),
734 project_path: None,
735 is_singleton: true,
736 nav_history: None,
737 tab_descriptions: None,
738 tab_detail: Default::default(),
739 }
740 }
741
742 pub fn with_label(mut self, state: &str) -> Self {
743 self.label = state.to_string();
744 self
745 }
746
747 pub fn with_singleton(mut self, singleton: bool) -> Self {
748 self.is_singleton = singleton;
749 self
750 }
751
752 pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self {
753 self.project_entry_ids.extend(
754 project_entry_ids
755 .iter()
756 .copied()
757 .map(ProjectEntryId::from_proto),
758 );
759 self
760 }
761
762 pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
763 self.push_to_nav_history(cx);
764 self.state = state;
765 }
766
767 fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
768 if let Some(history) = &mut self.nav_history {
769 history.push(Some(Box::new(self.state.clone())), cx);
770 }
771 }
772 }
773
774 impl Entity for TestItem {
775 type Event = TestItemEvent;
776 }
777
778 impl View for TestItem {
779 fn ui_name() -> &'static str {
780 "TestItem"
781 }
782
783 fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
784 Empty::new().boxed()
785 }
786 }
787
788 impl Item for TestItem {
789 fn tab_description<'a>(&'a self, detail: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
790 self.tab_descriptions.as_ref().and_then(|descriptions| {
791 let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
792 Some(description.into())
793 })
794 }
795
796 fn tab_content(&self, detail: Option<usize>, _: &theme::Tab, _: &AppContext) -> ElementBox {
797 self.tab_detail.set(detail);
798 Empty::new().boxed()
799 }
800
801 fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
802 self.project_path.clone()
803 }
804
805 fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
806 self.project_entry_ids.iter().copied().collect()
807 }
808
809 fn is_singleton(&self, _: &AppContext) -> bool {
810 self.is_singleton
811 }
812
813 fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
814 self.nav_history = Some(history);
815 }
816
817 fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
818 let state = *state.downcast::<String>().unwrap_or_default();
819 if state != self.state {
820 self.state = state;
821 true
822 } else {
823 false
824 }
825 }
826
827 fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
828 self.push_to_nav_history(cx);
829 }
830
831 fn clone_on_split(
832 &self,
833 _workspace_id: WorkspaceId,
834 _: &mut ViewContext<Self>,
835 ) -> Option<Self>
836 where
837 Self: Sized,
838 {
839 Some(self.clone())
840 }
841
842 fn is_dirty(&self, _: &AppContext) -> bool {
843 self.is_dirty
844 }
845
846 fn has_conflict(&self, _: &AppContext) -> bool {
847 self.has_conflict
848 }
849
850 fn can_save(&self, _: &AppContext) -> bool {
851 !self.project_entry_ids.is_empty()
852 }
853
854 fn save(
855 &mut self,
856 _: ModelHandle<Project>,
857 _: &mut ViewContext<Self>,
858 ) -> Task<anyhow::Result<()>> {
859 self.save_count += 1;
860 self.is_dirty = false;
861 Task::ready(Ok(()))
862 }
863
864 fn save_as(
865 &mut self,
866 _: ModelHandle<Project>,
867 _: std::path::PathBuf,
868 _: &mut ViewContext<Self>,
869 ) -> Task<anyhow::Result<()>> {
870 self.save_as_count += 1;
871 self.is_dirty = false;
872 Task::ready(Ok(()))
873 }
874
875 fn reload(
876 &mut self,
877 _: ModelHandle<Project>,
878 _: &mut ViewContext<Self>,
879 ) -> Task<anyhow::Result<()>> {
880 self.reload_count += 1;
881 self.is_dirty = false;
882 Task::ready(Ok(()))
883 }
884
885 fn to_item_events(_: &Self::Event) -> Vec<ItemEvent> {
886 vec![ItemEvent::UpdateTab, ItemEvent::Edit]
887 }
888
889 fn serialized_item_kind() -> Option<&'static str> {
890 None
891 }
892
893 fn deserialize(
894 _project: ModelHandle<Project>,
895 _workspace: WeakViewHandle<Workspace>,
896 _workspace_id: WorkspaceId,
897 _item_id: ItemId,
898 _cx: &mut ViewContext<Pane>,
899 ) -> Task<anyhow::Result<ViewHandle<Self>>> {
900 unreachable!("Cannot deserialize test item")
901 }
902 }
903
904 impl SidebarItem for TestItem {}
905}