1use crate::{
2 pane::{self, Pane},
3 persistence::model::ItemId,
4 searchable::SearchableItemHandle,
5 workspace_settings::{AutosaveSetting, WorkspaceSettings},
6 DelayedDebouncedEditAction, FollowableViewRegistry, ItemNavHistory, SerializableItemRegistry,
7 ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
8};
9use anyhow::Result;
10use client::{
11 proto::{self, PeerId},
12 Client,
13};
14use futures::{channel::mpsc, StreamExt};
15use gpui::{
16 AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
17 Font, HighlightStyle, Model, Pixels, Point, SharedString, Task, View, ViewContext, WeakView,
18 WindowContext,
19};
20use project::{Project, ProjectEntryId, ProjectPath};
21use schemars::JsonSchema;
22use serde::{Deserialize, Serialize};
23use settings::{Settings, SettingsLocation, SettingsSources};
24use smallvec::SmallVec;
25use std::{
26 any::{Any, TypeId},
27 cell::RefCell,
28 ops::Range,
29 rc::Rc,
30 sync::Arc,
31 time::Duration,
32};
33use theme::Theme;
34use ui::{Color, Element as _, Icon, IntoElement, Label, LabelCommon};
35use util::ResultExt;
36
37pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200);
38
39#[derive(Deserialize)]
40pub struct ItemSettings {
41 pub git_status: bool,
42 pub close_position: ClosePosition,
43 pub activate_on_close: ActivateOnClose,
44 pub file_icons: bool,
45 pub show_diagnostics: ShowDiagnostics,
46 pub always_show_close_button: bool,
47}
48
49#[derive(Deserialize)]
50pub struct PreviewTabsSettings {
51 pub enabled: bool,
52 pub enable_preview_from_file_finder: bool,
53 pub enable_preview_from_code_navigation: bool,
54}
55
56#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
57#[serde(rename_all = "lowercase")]
58pub enum ClosePosition {
59 Left,
60 #[default]
61 Right,
62}
63
64#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
65#[serde(rename_all = "snake_case")]
66pub enum ShowDiagnostics {
67 #[default]
68 Off,
69 Errors,
70 All,
71}
72
73#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
74#[serde(rename_all = "snake_case")]
75pub enum ActivateOnClose {
76 #[default]
77 History,
78 Neighbour,
79 LeftNeighbour,
80}
81
82#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
83pub struct ItemSettingsContent {
84 /// Whether to show the Git file status on a tab item.
85 ///
86 /// Default: false
87 git_status: Option<bool>,
88 /// Position of the close button in a tab.
89 ///
90 /// Default: right
91 close_position: Option<ClosePosition>,
92 /// Whether to show the file icon for a tab.
93 ///
94 /// Default: false
95 file_icons: Option<bool>,
96 /// What to do after closing the current tab.
97 ///
98 /// Default: history
99 pub activate_on_close: Option<ActivateOnClose>,
100 /// Which files containing diagnostic errors/warnings to mark in the tabs.
101 /// This setting can take the following three values:
102 ///
103 /// Default: off
104 show_diagnostics: Option<ShowDiagnostics>,
105 /// Whether to always show the close button on tabs.
106 ///
107 /// Default: false
108 always_show_close_button: Option<bool>,
109}
110
111#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
112pub struct PreviewTabsSettingsContent {
113 /// Whether to show opened editors as preview tabs.
114 /// Preview tabs do not stay open, are reused until explicitly set to be kept open opened (via double-click or editing) and show file names in italic.
115 ///
116 /// Default: true
117 enabled: Option<bool>,
118 /// Whether to open tabs in preview mode when selected from the file finder.
119 ///
120 /// Default: false
121 enable_preview_from_file_finder: Option<bool>,
122 /// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab.
123 ///
124 /// Default: false
125 enable_preview_from_code_navigation: Option<bool>,
126}
127
128impl Settings for ItemSettings {
129 const KEY: Option<&'static str> = Some("tabs");
130
131 type FileContent = ItemSettingsContent;
132
133 fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
134 sources.json_merge()
135 }
136}
137
138impl Settings for PreviewTabsSettings {
139 const KEY: Option<&'static str> = Some("preview_tabs");
140
141 type FileContent = PreviewTabsSettingsContent;
142
143 fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
144 sources.json_merge()
145 }
146}
147
148#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
149pub enum ItemEvent {
150 CloseItem,
151 UpdateTab,
152 UpdateBreadcrumbs,
153 Edit,
154}
155
156// TODO: Combine this with existing HighlightedText struct?
157pub struct BreadcrumbText {
158 pub text: String,
159 pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
160 pub font: Option<Font>,
161}
162
163#[derive(Debug, Clone, Copy)]
164pub struct TabContentParams {
165 pub detail: Option<usize>,
166 pub selected: bool,
167 pub preview: bool,
168}
169
170impl TabContentParams {
171 /// Returns the text color to be used for the tab content.
172 pub fn text_color(&self) -> Color {
173 if self.selected {
174 Color::Default
175 } else {
176 Color::Muted
177 }
178 }
179}
180
181pub enum TabTooltipContent {
182 Text(SharedString),
183 Custom(Box<dyn Fn(&mut WindowContext) -> AnyView>),
184}
185
186pub trait Item: FocusableView + EventEmitter<Self::Event> {
187 type Event;
188
189 /// Returns the tab contents.
190 ///
191 /// By default this returns a [`Label`] that displays that text from
192 /// `tab_content_text`.
193 fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {
194 let Some(text) = self.tab_content_text(cx) else {
195 return gpui::Empty.into_any();
196 };
197
198 Label::new(text)
199 .color(params.text_color())
200 .into_any_element()
201 }
202
203 /// Returns the textual contents of the tab.
204 ///
205 /// Use this if you don't need to customize the tab contents.
206 fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
207 None
208 }
209
210 fn tab_icon(&self, _cx: &WindowContext) -> Option<Icon> {
211 None
212 }
213
214 /// Returns the tab tooltip text.
215 ///
216 /// Use this if you don't need to customize the tab tooltip content.
217 fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
218 None
219 }
220
221 /// Returns the tab tooltip content.
222 ///
223 /// By default this returns a Tooltip text from
224 /// `tab_tooltip_text`.
225 fn tab_tooltip_content(&self, cx: &AppContext) -> Option<TabTooltipContent> {
226 self.tab_tooltip_text(cx).map(TabTooltipContent::Text)
227 }
228
229 fn tab_description(&self, _: usize, _: &AppContext) -> Option<SharedString> {
230 None
231 }
232
233 fn to_item_events(_event: &Self::Event, _f: impl FnMut(ItemEvent)) {}
234
235 fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
236 fn discarded(&self, _project: Model<Project>, _cx: &mut ViewContext<Self>) {}
237 fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
238 fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
239 false
240 }
241
242 fn telemetry_event_text(&self) -> Option<&'static str> {
243 None
244 }
245
246 /// (model id, Item)
247 fn for_each_project_item(
248 &self,
249 _: &AppContext,
250 _: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
251 ) {
252 }
253 fn is_singleton(&self, _cx: &AppContext) -> bool {
254 false
255 }
256 fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
257 fn clone_on_split(
258 &self,
259 _workspace_id: Option<WorkspaceId>,
260 _: &mut ViewContext<Self>,
261 ) -> Option<View<Self>>
262 where
263 Self: Sized,
264 {
265 None
266 }
267 fn is_dirty(&self, _: &AppContext) -> bool {
268 false
269 }
270 fn has_deleted_file(&self, _: &AppContext) -> bool {
271 false
272 }
273 fn has_conflict(&self, _: &AppContext) -> bool {
274 false
275 }
276 fn can_save(&self, _cx: &AppContext) -> bool {
277 false
278 }
279 fn save(
280 &mut self,
281 _format: bool,
282 _project: Model<Project>,
283 _cx: &mut ViewContext<Self>,
284 ) -> Task<Result<()>> {
285 unimplemented!("save() must be implemented if can_save() returns true")
286 }
287 fn save_as(
288 &mut self,
289 _project: Model<Project>,
290 _path: ProjectPath,
291 _cx: &mut ViewContext<Self>,
292 ) -> Task<Result<()>> {
293 unimplemented!("save_as() must be implemented if can_save() returns true")
294 }
295 fn reload(
296 &mut self,
297 _project: Model<Project>,
298 _cx: &mut ViewContext<Self>,
299 ) -> Task<Result<()>> {
300 unimplemented!("reload() must be implemented if can_save() returns true")
301 }
302
303 fn act_as_type<'a>(
304 &'a self,
305 type_id: TypeId,
306 self_handle: &'a View<Self>,
307 _: &'a AppContext,
308 ) -> Option<AnyView> {
309 if TypeId::of::<Self>() == type_id {
310 Some(self_handle.clone().into())
311 } else {
312 None
313 }
314 }
315
316 fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
317 None
318 }
319
320 fn breadcrumb_location(&self, _: &AppContext) -> ToolbarItemLocation {
321 ToolbarItemLocation::Hidden
322 }
323
324 fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
325 None
326 }
327
328 fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext<Self>) {}
329
330 fn show_toolbar(&self) -> bool {
331 true
332 }
333
334 fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Point<Pixels>> {
335 None
336 }
337
338 fn preserve_preview(&self, _cx: &AppContext) -> bool {
339 false
340 }
341
342 fn include_in_nav_history() -> bool {
343 true
344 }
345}
346
347pub trait SerializableItem: Item {
348 fn serialized_item_kind() -> &'static str;
349
350 fn cleanup(
351 workspace_id: WorkspaceId,
352 alive_items: Vec<ItemId>,
353 cx: &mut WindowContext,
354 ) -> Task<Result<()>>;
355
356 fn deserialize(
357 _project: Model<Project>,
358 _workspace: WeakView<Workspace>,
359 _workspace_id: WorkspaceId,
360 _item_id: ItemId,
361 _cx: &mut WindowContext,
362 ) -> Task<Result<View<Self>>>;
363
364 fn serialize(
365 &mut self,
366 workspace: &mut Workspace,
367 item_id: ItemId,
368 closing: bool,
369 cx: &mut ViewContext<Self>,
370 ) -> Option<Task<Result<()>>>;
371
372 fn should_serialize(&self, event: &Self::Event) -> bool;
373}
374
375pub trait SerializableItemHandle: ItemHandle {
376 fn serialized_item_kind(&self) -> &'static str;
377 fn serialize(
378 &self,
379 workspace: &mut Workspace,
380 closing: bool,
381 cx: &mut WindowContext,
382 ) -> Option<Task<Result<()>>>;
383 fn should_serialize(&self, event: &dyn Any, cx: &AppContext) -> bool;
384}
385
386impl<T> SerializableItemHandle for View<T>
387where
388 T: SerializableItem,
389{
390 fn serialized_item_kind(&self) -> &'static str {
391 T::serialized_item_kind()
392 }
393
394 fn serialize(
395 &self,
396 workspace: &mut Workspace,
397 closing: bool,
398 cx: &mut WindowContext,
399 ) -> Option<Task<Result<()>>> {
400 self.update(cx, |this, cx| {
401 this.serialize(workspace, cx.entity_id().as_u64(), closing, cx)
402 })
403 }
404
405 fn should_serialize(&self, event: &dyn Any, cx: &AppContext) -> bool {
406 event
407 .downcast_ref::<T::Event>()
408 .map_or(false, |event| self.read(cx).should_serialize(event))
409 }
410}
411
412pub trait ItemHandle: 'static + Send {
413 fn subscribe_to_item_events(
414 &self,
415 cx: &mut WindowContext,
416 handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
417 ) -> gpui::Subscription;
418 fn focus_handle(&self, cx: &WindowContext) -> FocusHandle;
419 fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>;
420 fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement;
421 fn tab_icon(&self, cx: &WindowContext) -> Option<Icon>;
422 fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>;
423 fn tab_tooltip_content(&self, cx: &AppContext) -> Option<TabTooltipContent>;
424 fn telemetry_event_text(&self, cx: &WindowContext) -> Option<&'static str>;
425 fn dragged_tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement;
426 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
427 fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
428 fn project_paths(&self, cx: &AppContext) -> SmallVec<[ProjectPath; 3]>;
429 fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[EntityId; 3]>;
430 fn for_each_project_item(
431 &self,
432 _: &AppContext,
433 _: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
434 );
435 fn is_singleton(&self, cx: &AppContext) -> bool;
436 fn boxed_clone(&self) -> Box<dyn ItemHandle>;
437 fn clone_on_split(
438 &self,
439 workspace_id: Option<WorkspaceId>,
440 cx: &mut WindowContext,
441 ) -> Option<Box<dyn ItemHandle>>;
442 fn added_to_pane(
443 &self,
444 workspace: &mut Workspace,
445 pane: View<Pane>,
446 cx: &mut ViewContext<Workspace>,
447 );
448 fn deactivated(&self, cx: &mut WindowContext);
449 fn discarded(&self, project: Model<Project>, cx: &mut WindowContext);
450 fn workspace_deactivated(&self, cx: &mut WindowContext);
451 fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
452 fn item_id(&self) -> EntityId;
453 fn to_any(&self) -> AnyView;
454 fn is_dirty(&self, cx: &AppContext) -> bool;
455 fn has_deleted_file(&self, cx: &AppContext) -> bool;
456 fn has_conflict(&self, cx: &AppContext) -> bool;
457 fn can_save(&self, cx: &AppContext) -> bool;
458 fn save(
459 &self,
460 format: bool,
461 project: Model<Project>,
462 cx: &mut WindowContext,
463 ) -> Task<Result<()>>;
464 fn save_as(
465 &self,
466 project: Model<Project>,
467 path: ProjectPath,
468 cx: &mut WindowContext,
469 ) -> Task<Result<()>>;
470 fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
471 fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyView>;
472 fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
473 fn to_serializable_item_handle(
474 &self,
475 cx: &AppContext,
476 ) -> Option<Box<dyn SerializableItemHandle>>;
477 fn on_release(
478 &self,
479 cx: &mut AppContext,
480 callback: Box<dyn FnOnce(&mut AppContext) + Send>,
481 ) -> gpui::Subscription;
482 fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
483 fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
484 fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
485 fn show_toolbar(&self, cx: &AppContext) -> bool;
486 fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>;
487 fn downgrade_item(&self) -> Box<dyn WeakItemHandle>;
488 fn workspace_settings<'a>(&self, cx: &'a AppContext) -> &'a WorkspaceSettings;
489 fn preserve_preview(&self, cx: &AppContext) -> bool;
490 fn include_in_nav_history(&self) -> bool;
491}
492
493pub trait WeakItemHandle: Send + Sync {
494 fn id(&self) -> EntityId;
495 fn boxed_clone(&self) -> Box<dyn WeakItemHandle>;
496 fn upgrade(&self) -> Option<Box<dyn ItemHandle>>;
497}
498
499impl dyn ItemHandle {
500 pub fn downcast<V: 'static>(&self) -> Option<View<V>> {
501 self.to_any().downcast().ok()
502 }
503
504 pub fn act_as<V: 'static>(&self, cx: &AppContext) -> Option<View<V>> {
505 self.act_as_type(TypeId::of::<V>(), cx)
506 .and_then(|t| t.downcast().ok())
507 }
508}
509
510impl<T: Item> ItemHandle for View<T> {
511 fn subscribe_to_item_events(
512 &self,
513 cx: &mut WindowContext,
514 handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
515 ) -> gpui::Subscription {
516 cx.subscribe(self, move |_, event, cx| {
517 T::to_item_events(event, |item_event| handler(item_event, cx));
518 })
519 }
520
521 fn focus_handle(&self, cx: &WindowContext) -> FocusHandle {
522 self.focus_handle(cx)
523 }
524
525 fn telemetry_event_text(&self, cx: &WindowContext) -> Option<&'static str> {
526 self.read(cx).telemetry_event_text()
527 }
528
529 fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString> {
530 self.read(cx).tab_description(detail, cx)
531 }
532
533 fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {
534 self.read(cx).tab_content(params, cx)
535 }
536
537 fn tab_icon(&self, cx: &WindowContext) -> Option<Icon> {
538 self.read(cx).tab_icon(cx)
539 }
540
541 fn tab_tooltip_content(&self, cx: &AppContext) -> Option<TabTooltipContent> {
542 self.read(cx).tab_tooltip_content(cx)
543 }
544
545 fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
546 self.read(cx).tab_tooltip_text(cx)
547 }
548
549 fn dragged_tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {
550 self.read(cx).tab_content(
551 TabContentParams {
552 selected: true,
553 ..params
554 },
555 cx,
556 )
557 }
558
559 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
560 let this = self.read(cx);
561 let mut result = None;
562 if this.is_singleton(cx) {
563 this.for_each_project_item(cx, &mut |_, item| {
564 result = item.project_path(cx);
565 });
566 }
567 result
568 }
569
570 fn workspace_settings<'a>(&self, cx: &'a AppContext) -> &'a WorkspaceSettings {
571 if let Some(project_path) = self.project_path(cx) {
572 WorkspaceSettings::get(
573 Some(SettingsLocation {
574 worktree_id: project_path.worktree_id,
575 path: &project_path.path,
576 }),
577 cx,
578 )
579 } else {
580 WorkspaceSettings::get_global(cx)
581 }
582 }
583
584 fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
585 let mut result = SmallVec::new();
586 self.read(cx).for_each_project_item(cx, &mut |_, item| {
587 if let Some(id) = item.entry_id(cx) {
588 result.push(id);
589 }
590 });
591 result
592 }
593
594 fn project_paths(&self, cx: &AppContext) -> SmallVec<[ProjectPath; 3]> {
595 let mut result = SmallVec::new();
596 self.read(cx).for_each_project_item(cx, &mut |_, item| {
597 if let Some(id) = item.project_path(cx) {
598 result.push(id);
599 }
600 });
601 result
602 }
603
604 fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[EntityId; 3]> {
605 let mut result = SmallVec::new();
606 self.read(cx).for_each_project_item(cx, &mut |id, _| {
607 result.push(id);
608 });
609 result
610 }
611
612 fn for_each_project_item(
613 &self,
614 cx: &AppContext,
615 f: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
616 ) {
617 self.read(cx).for_each_project_item(cx, f)
618 }
619
620 fn is_singleton(&self, cx: &AppContext) -> bool {
621 self.read(cx).is_singleton(cx)
622 }
623
624 fn boxed_clone(&self) -> Box<dyn ItemHandle> {
625 Box::new(self.clone())
626 }
627
628 fn clone_on_split(
629 &self,
630 workspace_id: Option<WorkspaceId>,
631 cx: &mut WindowContext,
632 ) -> Option<Box<dyn ItemHandle>> {
633 self.update(cx, |item, cx| item.clone_on_split(workspace_id, cx))
634 .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
635 }
636
637 fn added_to_pane(
638 &self,
639 workspace: &mut Workspace,
640 pane: View<Pane>,
641 cx: &mut ViewContext<Workspace>,
642 ) {
643 let weak_item = self.downgrade();
644 let history = pane.read(cx).nav_history_for_item(self);
645 self.update(cx, |this, cx| {
646 this.set_nav_history(history, cx);
647 this.added_to_workspace(workspace, cx);
648 });
649
650 if let Some(serializable_item) = self.to_serializable_item_handle(cx) {
651 workspace
652 .enqueue_item_serialization(serializable_item)
653 .log_err();
654 }
655
656 if workspace
657 .panes_by_item
658 .insert(self.item_id(), pane.downgrade())
659 .is_none()
660 {
661 let mut pending_autosave = DelayedDebouncedEditAction::new();
662 let (pending_update_tx, mut pending_update_rx) = mpsc::unbounded();
663 let pending_update = Rc::new(RefCell::new(None));
664
665 let mut send_follower_updates = None;
666 if let Some(item) = self.to_followable_item_handle(cx) {
667 let is_project_item = item.is_project_item(cx);
668 let item = item.downgrade();
669
670 send_follower_updates = Some(cx.spawn({
671 let pending_update = pending_update.clone();
672 |workspace, mut cx| async move {
673 while let Some(mut leader_id) = pending_update_rx.next().await {
674 while let Ok(Some(id)) = pending_update_rx.try_next() {
675 leader_id = id;
676 }
677
678 workspace.update(&mut cx, |workspace, cx| {
679 let Some(item) = item.upgrade() else { return };
680 workspace.update_followers(
681 is_project_item,
682 proto::update_followers::Variant::UpdateView(
683 proto::UpdateView {
684 id: item
685 .remote_id(workspace.client(), cx)
686 .map(|id| id.to_proto()),
687 variant: pending_update.borrow_mut().take(),
688 leader_id,
689 },
690 ),
691 cx,
692 );
693 })?;
694 cx.background_executor().timer(LEADER_UPDATE_THROTTLE).await;
695 }
696 anyhow::Ok(())
697 }
698 }));
699 }
700
701 let mut event_subscription = Some(cx.subscribe(
702 self,
703 move |workspace, item: View<T>, event, cx| {
704 let pane = if let Some(pane) = workspace
705 .panes_by_item
706 .get(&item.item_id())
707 .and_then(|pane| pane.upgrade())
708 {
709 pane
710 } else {
711 return;
712 };
713
714 if let Some(item) = item.to_followable_item_handle(cx) {
715 let leader_id = workspace.leader_for_pane(&pane);
716
717 if let Some(leader_id) = leader_id {
718 if let Some(FollowEvent::Unfollow) = item.to_follow_event(event) {
719 workspace.unfollow(leader_id, cx);
720 }
721 }
722
723 if item.focus_handle(cx).contains_focused(cx) {
724 item.add_event_to_update_proto(
725 event,
726 &mut pending_update.borrow_mut(),
727 cx,
728 );
729 pending_update_tx.unbounded_send(leader_id).ok();
730 }
731 }
732
733 if let Some(item) = item.to_serializable_item_handle(cx) {
734 if item.should_serialize(event, cx) {
735 workspace.enqueue_item_serialization(item).ok();
736 }
737 }
738
739 T::to_item_events(event, |event| match event {
740 ItemEvent::CloseItem => {
741 pane.update(cx, |pane, cx| {
742 pane.close_item_by_id(item.item_id(), crate::SaveIntent::Close, cx)
743 })
744 .detach_and_log_err(cx);
745 }
746
747 ItemEvent::UpdateTab => {
748 pane.update(cx, |_, cx| {
749 cx.emit(pane::Event::ChangeItemTitle);
750 cx.notify();
751 });
752 }
753
754 ItemEvent::Edit => {
755 let autosave = item.workspace_settings(cx).autosave;
756
757 if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
758 let delay = Duration::from_millis(milliseconds);
759 let item = item.clone();
760 pending_autosave.fire_new(delay, cx, move |workspace, cx| {
761 Pane::autosave_item(&item, workspace.project().clone(), cx)
762 });
763 }
764 pane.update(cx, |pane, cx| pane.handle_item_edit(item.item_id(), cx));
765 }
766
767 _ => {}
768 });
769 },
770 ));
771
772 cx.on_blur(&self.focus_handle(cx), move |workspace, cx| {
773 if let Some(item) = weak_item.upgrade() {
774 if item.workspace_settings(cx).autosave == AutosaveSetting::OnFocusChange {
775 Pane::autosave_item(&item, workspace.project.clone(), cx)
776 .detach_and_log_err(cx);
777 }
778 }
779 })
780 .detach();
781
782 let item_id = self.item_id();
783 cx.observe_release(self, move |workspace, _, _| {
784 workspace.panes_by_item.remove(&item_id);
785 event_subscription.take();
786 send_follower_updates.take();
787 })
788 .detach();
789 }
790
791 cx.defer(|workspace, cx| {
792 workspace.serialize_workspace(cx);
793 });
794 }
795
796 fn discarded(&self, project: Model<Project>, cx: &mut WindowContext) {
797 self.update(cx, |this, cx| this.discarded(project, cx));
798 }
799
800 fn deactivated(&self, cx: &mut WindowContext) {
801 self.update(cx, |this, cx| this.deactivated(cx));
802 }
803
804 fn workspace_deactivated(&self, cx: &mut WindowContext) {
805 self.update(cx, |this, cx| this.workspace_deactivated(cx));
806 }
807
808 fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool {
809 self.update(cx, |this, cx| this.navigate(data, cx))
810 }
811
812 fn item_id(&self) -> EntityId {
813 self.entity_id()
814 }
815
816 fn to_any(&self) -> AnyView {
817 self.clone().into()
818 }
819
820 fn is_dirty(&self, cx: &AppContext) -> bool {
821 self.read(cx).is_dirty(cx)
822 }
823
824 fn has_deleted_file(&self, cx: &AppContext) -> bool {
825 self.read(cx).has_deleted_file(cx)
826 }
827
828 fn has_conflict(&self, cx: &AppContext) -> bool {
829 self.read(cx).has_conflict(cx)
830 }
831
832 fn can_save(&self, cx: &AppContext) -> bool {
833 self.read(cx).can_save(cx)
834 }
835
836 fn save(
837 &self,
838 format: bool,
839 project: Model<Project>,
840 cx: &mut WindowContext,
841 ) -> Task<Result<()>> {
842 self.update(cx, |item, cx| item.save(format, project, cx))
843 }
844
845 fn save_as(
846 &self,
847 project: Model<Project>,
848 path: ProjectPath,
849 cx: &mut WindowContext,
850 ) -> Task<anyhow::Result<()>> {
851 self.update(cx, |item, cx| item.save_as(project, path, cx))
852 }
853
854 fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
855 self.update(cx, |item, cx| item.reload(project, cx))
856 }
857
858 fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<AnyView> {
859 self.read(cx).act_as_type(type_id, self, cx)
860 }
861
862 fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
863 FollowableViewRegistry::to_followable_view(self.clone(), cx)
864 }
865
866 fn on_release(
867 &self,
868 cx: &mut AppContext,
869 callback: Box<dyn FnOnce(&mut AppContext) + Send>,
870 ) -> gpui::Subscription {
871 cx.observe_release(self, move |_, cx| callback(cx))
872 }
873
874 fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
875 self.read(cx).as_searchable(self)
876 }
877
878 fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
879 self.read(cx).breadcrumb_location(cx)
880 }
881
882 fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
883 self.read(cx).breadcrumbs(theme, cx)
884 }
885
886 fn show_toolbar(&self, cx: &AppContext) -> bool {
887 self.read(cx).show_toolbar()
888 }
889
890 fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
891 self.read(cx).pixel_position_of_cursor(cx)
892 }
893
894 fn downgrade_item(&self) -> Box<dyn WeakItemHandle> {
895 Box::new(self.downgrade())
896 }
897
898 fn to_serializable_item_handle(
899 &self,
900 cx: &AppContext,
901 ) -> Option<Box<dyn SerializableItemHandle>> {
902 SerializableItemRegistry::view_to_serializable_item_handle(self.to_any(), cx)
903 }
904
905 fn preserve_preview(&self, cx: &AppContext) -> bool {
906 self.read(cx).preserve_preview(cx)
907 }
908
909 fn include_in_nav_history(&self) -> bool {
910 T::include_in_nav_history()
911 }
912}
913
914impl From<Box<dyn ItemHandle>> for AnyView {
915 fn from(val: Box<dyn ItemHandle>) -> Self {
916 val.to_any()
917 }
918}
919
920impl From<&Box<dyn ItemHandle>> for AnyView {
921 fn from(val: &Box<dyn ItemHandle>) -> Self {
922 val.to_any()
923 }
924}
925
926impl Clone for Box<dyn ItemHandle> {
927 fn clone(&self) -> Box<dyn ItemHandle> {
928 self.boxed_clone()
929 }
930}
931
932impl<T: Item> WeakItemHandle for WeakView<T> {
933 fn id(&self) -> EntityId {
934 self.entity_id()
935 }
936
937 fn boxed_clone(&self) -> Box<dyn WeakItemHandle> {
938 Box::new(self.clone())
939 }
940
941 fn upgrade(&self) -> Option<Box<dyn ItemHandle>> {
942 self.upgrade().map(|v| Box::new(v) as Box<dyn ItemHandle>)
943 }
944}
945
946pub trait ProjectItem: Item {
947 type Item: project::ProjectItem;
948
949 fn for_project_item(
950 project: Model<Project>,
951 item: Model<Self::Item>,
952 cx: &mut ViewContext<Self>,
953 ) -> Self
954 where
955 Self: Sized;
956}
957
958#[derive(Debug)]
959pub enum FollowEvent {
960 Unfollow,
961}
962
963pub enum Dedup {
964 KeepExisting,
965 ReplaceExisting,
966}
967
968pub trait FollowableItem: Item {
969 fn remote_id(&self) -> Option<ViewId>;
970 fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
971 fn from_state_proto(
972 project: View<Workspace>,
973 id: ViewId,
974 state: &mut Option<proto::view::Variant>,
975 cx: &mut WindowContext,
976 ) -> Option<Task<Result<View<Self>>>>;
977 fn to_follow_event(event: &Self::Event) -> Option<FollowEvent>;
978 fn add_event_to_update_proto(
979 &self,
980 event: &Self::Event,
981 update: &mut Option<proto::update_view::Variant>,
982 cx: &WindowContext,
983 ) -> bool;
984 fn apply_update_proto(
985 &mut self,
986 project: &Model<Project>,
987 message: proto::update_view::Variant,
988 cx: &mut ViewContext<Self>,
989 ) -> Task<Result<()>>;
990 fn is_project_item(&self, cx: &WindowContext) -> bool;
991 fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
992 fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<Dedup>;
993}
994
995pub trait FollowableItemHandle: ItemHandle {
996 fn remote_id(&self, client: &Arc<Client>, cx: &WindowContext) -> Option<ViewId>;
997 fn downgrade(&self) -> Box<dyn WeakFollowableItemHandle>;
998 fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
999 fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
1000 fn add_event_to_update_proto(
1001 &self,
1002 event: &dyn Any,
1003 update: &mut Option<proto::update_view::Variant>,
1004 cx: &WindowContext,
1005 ) -> bool;
1006 fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent>;
1007 fn apply_update_proto(
1008 &self,
1009 project: &Model<Project>,
1010 message: proto::update_view::Variant,
1011 cx: &mut WindowContext,
1012 ) -> Task<Result<()>>;
1013 fn is_project_item(&self, cx: &WindowContext) -> bool;
1014 fn dedup(&self, existing: &dyn FollowableItemHandle, cx: &WindowContext) -> Option<Dedup>;
1015}
1016
1017impl<T: FollowableItem> FollowableItemHandle for View<T> {
1018 fn remote_id(&self, client: &Arc<Client>, cx: &WindowContext) -> Option<ViewId> {
1019 self.read(cx).remote_id().or_else(|| {
1020 client.peer_id().map(|creator| ViewId {
1021 creator,
1022 id: self.item_id().as_u64(),
1023 })
1024 })
1025 }
1026
1027 fn downgrade(&self) -> Box<dyn WeakFollowableItemHandle> {
1028 Box::new(self.downgrade())
1029 }
1030
1031 fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext) {
1032 self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
1033 }
1034
1035 fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
1036 self.read(cx).to_state_proto(cx)
1037 }
1038
1039 fn add_event_to_update_proto(
1040 &self,
1041 event: &dyn Any,
1042 update: &mut Option<proto::update_view::Variant>,
1043 cx: &WindowContext,
1044 ) -> bool {
1045 if let Some(event) = event.downcast_ref() {
1046 self.read(cx).add_event_to_update_proto(event, update, cx)
1047 } else {
1048 false
1049 }
1050 }
1051
1052 fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent> {
1053 T::to_follow_event(event.downcast_ref()?)
1054 }
1055
1056 fn apply_update_proto(
1057 &self,
1058 project: &Model<Project>,
1059 message: proto::update_view::Variant,
1060 cx: &mut WindowContext,
1061 ) -> Task<Result<()>> {
1062 self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
1063 }
1064
1065 fn is_project_item(&self, cx: &WindowContext) -> bool {
1066 self.read(cx).is_project_item(cx)
1067 }
1068
1069 fn dedup(&self, existing: &dyn FollowableItemHandle, cx: &WindowContext) -> Option<Dedup> {
1070 let existing = existing.to_any().downcast::<T>().ok()?;
1071 self.read(cx).dedup(existing.read(cx), cx)
1072 }
1073}
1074
1075pub trait WeakFollowableItemHandle: Send + Sync {
1076 fn upgrade(&self) -> Option<Box<dyn FollowableItemHandle>>;
1077}
1078
1079impl<T: FollowableItem> WeakFollowableItemHandle for WeakView<T> {
1080 fn upgrade(&self) -> Option<Box<dyn FollowableItemHandle>> {
1081 Some(Box::new(self.upgrade()?))
1082 }
1083}
1084
1085#[cfg(any(test, feature = "test-support"))]
1086pub mod test {
1087 use super::{Item, ItemEvent, SerializableItem, TabContentParams};
1088 use crate::{ItemId, ItemNavHistory, Workspace, WorkspaceId};
1089 use gpui::{
1090 AnyElement, AppContext, Context as _, EntityId, EventEmitter, FocusableView,
1091 InteractiveElement, IntoElement, Model, Render, SharedString, Task, View, ViewContext,
1092 VisualContext, WeakView, WindowContext,
1093 };
1094 use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
1095 use std::{any::Any, cell::Cell, path::Path};
1096
1097 pub struct TestProjectItem {
1098 pub entry_id: Option<ProjectEntryId>,
1099 pub project_path: Option<ProjectPath>,
1100 pub is_dirty: bool,
1101 }
1102
1103 pub struct TestItem {
1104 pub workspace_id: Option<WorkspaceId>,
1105 pub state: String,
1106 pub label: String,
1107 pub save_count: usize,
1108 pub save_as_count: usize,
1109 pub reload_count: usize,
1110 pub is_dirty: bool,
1111 pub is_singleton: bool,
1112 pub has_conflict: bool,
1113 pub project_items: Vec<Model<TestProjectItem>>,
1114 pub nav_history: Option<ItemNavHistory>,
1115 pub tab_descriptions: Option<Vec<&'static str>>,
1116 pub tab_detail: Cell<Option<usize>>,
1117 serialize: Option<Box<dyn Fn() -> Option<Task<anyhow::Result<()>>>>>,
1118 focus_handle: gpui::FocusHandle,
1119 }
1120
1121 impl project::ProjectItem for TestProjectItem {
1122 fn try_open(
1123 _project: &Model<Project>,
1124 _path: &ProjectPath,
1125 _cx: &mut AppContext,
1126 ) -> Option<Task<gpui::Result<Model<Self>>>> {
1127 None
1128 }
1129 fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
1130 self.entry_id
1131 }
1132
1133 fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
1134 self.project_path.clone()
1135 }
1136
1137 fn is_dirty(&self) -> bool {
1138 self.is_dirty
1139 }
1140 }
1141
1142 pub enum TestItemEvent {
1143 Edit,
1144 }
1145
1146 impl TestProjectItem {
1147 pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
1148 let entry_id = Some(ProjectEntryId::from_proto(id));
1149 let project_path = Some(ProjectPath {
1150 worktree_id: WorktreeId::from_usize(0),
1151 path: Path::new(path).into(),
1152 });
1153 cx.new_model(|_| Self {
1154 entry_id,
1155 project_path,
1156 is_dirty: false,
1157 })
1158 }
1159
1160 pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
1161 cx.new_model(|_| Self {
1162 project_path: None,
1163 entry_id: None,
1164 is_dirty: false,
1165 })
1166 }
1167 }
1168
1169 impl TestItem {
1170 pub fn new(cx: &mut ViewContext<Self>) -> Self {
1171 Self {
1172 state: String::new(),
1173 label: String::new(),
1174 save_count: 0,
1175 save_as_count: 0,
1176 reload_count: 0,
1177 is_dirty: false,
1178 has_conflict: false,
1179 project_items: Vec::new(),
1180 is_singleton: true,
1181 nav_history: None,
1182 tab_descriptions: None,
1183 tab_detail: Default::default(),
1184 workspace_id: Default::default(),
1185 focus_handle: cx.focus_handle(),
1186 serialize: None,
1187 }
1188 }
1189
1190 pub fn new_deserialized(id: WorkspaceId, cx: &mut ViewContext<Self>) -> Self {
1191 let mut this = Self::new(cx);
1192 this.workspace_id = Some(id);
1193 this
1194 }
1195
1196 pub fn with_label(mut self, state: &str) -> Self {
1197 self.label = state.to_string();
1198 self
1199 }
1200
1201 pub fn with_singleton(mut self, singleton: bool) -> Self {
1202 self.is_singleton = singleton;
1203 self
1204 }
1205
1206 pub fn with_dirty(mut self, dirty: bool) -> Self {
1207 self.is_dirty = dirty;
1208 self
1209 }
1210
1211 pub fn with_conflict(mut self, has_conflict: bool) -> Self {
1212 self.has_conflict = has_conflict;
1213 self
1214 }
1215
1216 pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
1217 self.project_items.clear();
1218 self.project_items.extend(items.iter().cloned());
1219 self
1220 }
1221
1222 pub fn with_serialize(
1223 mut self,
1224 serialize: impl Fn() -> Option<Task<anyhow::Result<()>>> + 'static,
1225 ) -> Self {
1226 self.serialize = Some(Box::new(serialize));
1227 self
1228 }
1229
1230 pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
1231 self.push_to_nav_history(cx);
1232 self.state = state;
1233 }
1234
1235 fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
1236 if let Some(history) = &mut self.nav_history {
1237 history.push(Some(Box::new(self.state.clone())), cx);
1238 }
1239 }
1240 }
1241
1242 impl Render for TestItem {
1243 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1244 gpui::div().track_focus(&self.focus_handle(cx))
1245 }
1246 }
1247
1248 impl EventEmitter<ItemEvent> for TestItem {}
1249
1250 impl FocusableView for TestItem {
1251 fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
1252 self.focus_handle.clone()
1253 }
1254 }
1255
1256 impl Item for TestItem {
1257 type Event = ItemEvent;
1258
1259 fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
1260 f(*event)
1261 }
1262
1263 fn tab_description(&self, detail: usize, _: &AppContext) -> Option<SharedString> {
1264 self.tab_descriptions.as_ref().and_then(|descriptions| {
1265 let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
1266 Some(description.into())
1267 })
1268 }
1269
1270 fn telemetry_event_text(&self) -> Option<&'static str> {
1271 None
1272 }
1273
1274 fn tab_content(&self, params: TabContentParams, _cx: &WindowContext) -> AnyElement {
1275 self.tab_detail.set(params.detail);
1276 gpui::div().into_any_element()
1277 }
1278
1279 fn for_each_project_item(
1280 &self,
1281 cx: &AppContext,
1282 f: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
1283 ) {
1284 self.project_items
1285 .iter()
1286 .for_each(|item| f(item.entity_id(), item.read(cx)))
1287 }
1288
1289 fn is_singleton(&self, _: &AppContext) -> bool {
1290 self.is_singleton
1291 }
1292
1293 fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
1294 self.nav_history = Some(history);
1295 }
1296
1297 fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
1298 let state = *state.downcast::<String>().unwrap_or_default();
1299 if state != self.state {
1300 self.state = state;
1301 true
1302 } else {
1303 false
1304 }
1305 }
1306
1307 fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
1308 self.push_to_nav_history(cx);
1309 }
1310
1311 fn clone_on_split(
1312 &self,
1313 _workspace_id: Option<WorkspaceId>,
1314 cx: &mut ViewContext<Self>,
1315 ) -> Option<View<Self>>
1316 where
1317 Self: Sized,
1318 {
1319 Some(cx.new_view(|cx| Self {
1320 state: self.state.clone(),
1321 label: self.label.clone(),
1322 save_count: self.save_count,
1323 save_as_count: self.save_as_count,
1324 reload_count: self.reload_count,
1325 is_dirty: self.is_dirty,
1326 is_singleton: self.is_singleton,
1327 has_conflict: self.has_conflict,
1328 project_items: self.project_items.clone(),
1329 nav_history: None,
1330 tab_descriptions: None,
1331 tab_detail: Default::default(),
1332 workspace_id: self.workspace_id,
1333 focus_handle: cx.focus_handle(),
1334 serialize: None,
1335 }))
1336 }
1337
1338 fn is_dirty(&self, _: &AppContext) -> bool {
1339 self.is_dirty
1340 }
1341
1342 fn has_conflict(&self, _: &AppContext) -> bool {
1343 self.has_conflict
1344 }
1345
1346 fn can_save(&self, cx: &AppContext) -> bool {
1347 !self.project_items.is_empty()
1348 && self
1349 .project_items
1350 .iter()
1351 .all(|item| item.read(cx).entry_id.is_some())
1352 }
1353
1354 fn save(
1355 &mut self,
1356 _: bool,
1357 _: Model<Project>,
1358 _: &mut ViewContext<Self>,
1359 ) -> Task<anyhow::Result<()>> {
1360 self.save_count += 1;
1361 self.is_dirty = false;
1362 Task::ready(Ok(()))
1363 }
1364
1365 fn save_as(
1366 &mut self,
1367 _: Model<Project>,
1368 _: ProjectPath,
1369 _: &mut ViewContext<Self>,
1370 ) -> Task<anyhow::Result<()>> {
1371 self.save_as_count += 1;
1372 self.is_dirty = false;
1373 Task::ready(Ok(()))
1374 }
1375
1376 fn reload(
1377 &mut self,
1378 _: Model<Project>,
1379 _: &mut ViewContext<Self>,
1380 ) -> Task<anyhow::Result<()>> {
1381 self.reload_count += 1;
1382 self.is_dirty = false;
1383 Task::ready(Ok(()))
1384 }
1385 }
1386
1387 impl SerializableItem for TestItem {
1388 fn serialized_item_kind() -> &'static str {
1389 "TestItem"
1390 }
1391
1392 fn deserialize(
1393 _project: Model<Project>,
1394 _workspace: WeakView<Workspace>,
1395 workspace_id: WorkspaceId,
1396 _item_id: ItemId,
1397 cx: &mut WindowContext,
1398 ) -> Task<anyhow::Result<View<Self>>> {
1399 let view = cx.new_view(|cx| Self::new_deserialized(workspace_id, cx));
1400 Task::ready(Ok(view))
1401 }
1402
1403 fn cleanup(
1404 _workspace_id: WorkspaceId,
1405 _alive_items: Vec<ItemId>,
1406 _cx: &mut WindowContext,
1407 ) -> Task<anyhow::Result<()>> {
1408 Task::ready(Ok(()))
1409 }
1410
1411 fn serialize(
1412 &mut self,
1413 _workspace: &mut Workspace,
1414 _item_id: ItemId,
1415 _closing: bool,
1416 _cx: &mut ViewContext<Self>,
1417 ) -> Option<Task<anyhow::Result<()>>> {
1418 if let Some(serialize) = self.serialize.take() {
1419 let result = serialize();
1420 self.serialize = Some(serialize);
1421 result
1422 } else {
1423 None
1424 }
1425 }
1426
1427 fn should_serialize(&self, _event: &Self::Event) -> bool {
1428 false
1429 }
1430 }
1431}