1/// NOTE: Focus only 'takes' after an update has flushed_effects.
2///
3/// This may cause issues when you're trying to write tests that use workspace focus to add items at
4/// specific locations.
5pub mod dock;
6pub mod pane;
7pub mod pane_group;
8pub mod searchable;
9pub mod sidebar;
10mod status_bar;
11mod toolbar;
12
13use anyhow::{anyhow, Context, Result};
14use call::ActiveCall;
15use client::{proto, Client, PeerId, TypedEnvelope, UserStore};
16use collections::{hash_map, HashMap, HashSet};
17use db::{SerializedItem, SerializedItemKind};
18use dock::{DefaultItemFactory, Dock, ToggleDockButton};
19use drag_and_drop::DragAndDrop;
20use fs::{self, Fs};
21use futures::{channel::oneshot, FutureExt, StreamExt};
22use gpui::{
23 actions,
24 elements::*,
25 impl_actions, impl_internal_actions,
26 platform::{CursorStyle, WindowOptions},
27 AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
28 MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View,
29 ViewContext, ViewHandle, WeakViewHandle,
30};
31use language::LanguageRegistry;
32use log::{error, warn};
33pub use pane::*;
34pub use pane_group::*;
35use postage::prelude::Stream;
36use project::{Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId};
37use searchable::SearchableItemHandle;
38use serde::Deserialize;
39use settings::{Autosave, DockAnchor, Settings};
40use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem};
41use smallvec::SmallVec;
42use status_bar::StatusBar;
43pub use status_bar::StatusItemView;
44use std::{
45 any::{Any, TypeId},
46 borrow::Cow,
47 cell::RefCell,
48 fmt,
49 future::Future,
50 path::{Path, PathBuf},
51 rc::Rc,
52 sync::{
53 atomic::{AtomicBool, Ordering::SeqCst},
54 Arc,
55 },
56 time::Duration,
57};
58use theme::{Theme, ThemeRegistry};
59pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
60use util::ResultExt;
61
62type ProjectItemBuilders = HashMap<
63 TypeId,
64 fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
65>;
66
67type FollowableItemBuilder = fn(
68 ViewHandle<Pane>,
69 ModelHandle<Project>,
70 &mut Option<proto::view::Variant>,
71 &mut MutableAppContext,
72) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
73type FollowableItemBuilders = HashMap<
74 TypeId,
75 (
76 FollowableItemBuilder,
77 fn(AnyViewHandle) -> Box<dyn FollowableItemHandle>,
78 ),
79>;
80
81type ItemDeserializers =
82 HashMap<SerializedItemKind, fn(SerializedItem, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
83
84#[derive(Clone, PartialEq)]
85pub struct RemoveWorktreeFromProject(pub WorktreeId);
86
87actions!(
88 workspace,
89 [
90 Open,
91 NewFile,
92 NewWindow,
93 CloseWindow,
94 AddFolderToProject,
95 Unfollow,
96 Save,
97 SaveAs,
98 SaveAll,
99 ActivatePreviousPane,
100 ActivateNextPane,
101 FollowNextCollaborator,
102 ToggleLeftSidebar,
103 ToggleRightSidebar,
104 NewTerminal,
105 NewSearch
106 ]
107);
108
109#[derive(Clone, PartialEq)]
110pub struct OpenPaths {
111 pub paths: Vec<PathBuf>,
112}
113
114#[derive(Clone, Deserialize, PartialEq)]
115pub struct ActivatePane(pub usize);
116
117#[derive(Clone, PartialEq)]
118pub struct ToggleFollow(pub PeerId);
119
120#[derive(Clone, PartialEq)]
121pub struct JoinProject {
122 pub project_id: u64,
123 pub follow_user_id: u64,
124}
125
126impl_internal_actions!(
127 workspace,
128 [
129 OpenPaths,
130 ToggleFollow,
131 JoinProject,
132 RemoveWorktreeFromProject
133 ]
134);
135impl_actions!(workspace, [ActivatePane]);
136
137pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
138 pane::init(cx);
139 dock::init(cx);
140
141 cx.add_global_action(open);
142 cx.add_global_action({
143 let app_state = Arc::downgrade(&app_state);
144 move |action: &OpenPaths, cx: &mut MutableAppContext| {
145 if let Some(app_state) = app_state.upgrade() {
146 open_paths(&action.paths, &app_state, cx).detach();
147 }
148 }
149 });
150 cx.add_global_action({
151 let app_state = Arc::downgrade(&app_state);
152 move |_: &NewFile, cx: &mut MutableAppContext| {
153 if let Some(app_state) = app_state.upgrade() {
154 open_new(&app_state, cx)
155 }
156 }
157 });
158 cx.add_global_action({
159 let app_state = Arc::downgrade(&app_state);
160 move |_: &NewWindow, cx: &mut MutableAppContext| {
161 if let Some(app_state) = app_state.upgrade() {
162 open_new(&app_state, cx)
163 }
164 }
165 });
166
167 cx.add_async_action(Workspace::toggle_follow);
168 cx.add_async_action(Workspace::follow_next_collaborator);
169 cx.add_async_action(Workspace::close);
170 cx.add_async_action(Workspace::save_all);
171 cx.add_action(Workspace::add_folder_to_project);
172 cx.add_action(Workspace::remove_folder_from_project);
173 cx.add_action(
174 |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
175 let pane = workspace.active_pane().clone();
176 workspace.unfollow(&pane, cx);
177 },
178 );
179 cx.add_action(
180 |workspace: &mut Workspace, _: &Save, cx: &mut ViewContext<Workspace>| {
181 workspace.save_active_item(false, cx).detach_and_log_err(cx);
182 },
183 );
184 cx.add_action(
185 |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
186 workspace.save_active_item(true, cx).detach_and_log_err(cx);
187 },
188 );
189 cx.add_action(Workspace::toggle_sidebar_item);
190 cx.add_action(Workspace::focus_center);
191 cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
192 workspace.activate_previous_pane(cx)
193 });
194 cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
195 workspace.activate_next_pane(cx)
196 });
197 cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftSidebar, cx| {
198 workspace.toggle_sidebar(SidebarSide::Left, cx);
199 });
200 cx.add_action(|workspace: &mut Workspace, _: &ToggleRightSidebar, cx| {
201 workspace.toggle_sidebar(SidebarSide::Right, cx);
202 });
203 cx.add_action(Workspace::activate_pane_at_index);
204
205 let client = &app_state.client;
206 client.add_view_request_handler(Workspace::handle_follow);
207 client.add_view_message_handler(Workspace::handle_unfollow);
208 client.add_view_message_handler(Workspace::handle_update_followers);
209}
210
211pub fn register_project_item<I: ProjectItem>(cx: &mut MutableAppContext) {
212 cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
213 builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
214 let item = model.downcast::<I::Item>().unwrap();
215 Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
216 });
217 });
218}
219
220pub fn register_followable_item<I: FollowableItem>(cx: &mut MutableAppContext) {
221 cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
222 builders.insert(
223 TypeId::of::<I>(),
224 (
225 |pane, project, state, cx| {
226 I::from_state_proto(pane, project, state, cx).map(|task| {
227 cx.foreground()
228 .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
229 })
230 },
231 |this| Box::new(this.downcast::<I>().unwrap()),
232 ),
233 );
234 });
235}
236
237pub fn register_deserializable_item<I: Item>(cx: &mut MutableAppContext) {
238 cx.update_default_global(|deserializers: &mut ItemDeserializers, _| {
239 deserializers.insert(I::serialized_item_kind(), |serialized_item, cx| {
240 Box::new(cx.add_view(|cx| I::deserialize(serialized_item, cx)))
241 })
242 });
243}
244
245pub struct AppState {
246 pub languages: Arc<LanguageRegistry>,
247 pub themes: Arc<ThemeRegistry>,
248 pub client: Arc<client::Client>,
249 pub user_store: ModelHandle<client::UserStore>,
250 pub project_store: ModelHandle<ProjectStore>,
251 pub fs: Arc<dyn fs::Fs>,
252 pub build_window_options: fn() -> WindowOptions<'static>,
253 pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>),
254 pub default_item_factory: DefaultItemFactory,
255}
256
257#[derive(Eq, PartialEq, Hash)]
258pub enum ItemEvent {
259 CloseItem,
260 UpdateTab,
261 UpdateBreadcrumbs,
262 Edit,
263}
264
265pub trait Item: View {
266 fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
267 fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
268 fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
269 false
270 }
271 fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
272 None
273 }
274 fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
275 -> ElementBox;
276 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
277 fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
278 fn is_singleton(&self, cx: &AppContext) -> bool;
279 fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
280 fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
281 where
282 Self: Sized,
283 {
284 None
285 }
286 fn is_dirty(&self, _: &AppContext) -> bool {
287 false
288 }
289 fn has_conflict(&self, _: &AppContext) -> bool {
290 false
291 }
292 fn can_save(&self, cx: &AppContext) -> bool;
293 fn save(
294 &mut self,
295 project: ModelHandle<Project>,
296 cx: &mut ViewContext<Self>,
297 ) -> Task<Result<()>>;
298 fn save_as(
299 &mut self,
300 project: ModelHandle<Project>,
301 abs_path: PathBuf,
302 cx: &mut ViewContext<Self>,
303 ) -> Task<Result<()>>;
304 fn reload(
305 &mut self,
306 project: ModelHandle<Project>,
307 cx: &mut ViewContext<Self>,
308 ) -> Task<Result<()>>;
309 fn git_diff_recalc(
310 &mut self,
311 _project: ModelHandle<Project>,
312 _cx: &mut ViewContext<Self>,
313 ) -> Task<Result<()>> {
314 Task::ready(Ok(()))
315 }
316 fn to_item_events(event: &Self::Event) -> Vec<ItemEvent>;
317 fn should_close_item_on_event(_: &Self::Event) -> bool {
318 false
319 }
320 fn should_update_tab_on_event(_: &Self::Event) -> bool {
321 false
322 }
323 fn is_edit_event(_: &Self::Event) -> bool {
324 false
325 }
326 fn act_as_type(
327 &self,
328 type_id: TypeId,
329 self_handle: &ViewHandle<Self>,
330 _: &AppContext,
331 ) -> Option<AnyViewHandle> {
332 if TypeId::of::<Self>() == type_id {
333 Some(self_handle.into())
334 } else {
335 None
336 }
337 }
338 fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
339 None
340 }
341
342 fn breadcrumb_location(&self) -> ToolbarItemLocation {
343 ToolbarItemLocation::Hidden
344 }
345 fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<ElementBox>> {
346 None
347 }
348 fn serialized_item_kind() -> SerializedItemKind;
349 fn deserialize(serialized_item: SerializedItem, cx: &mut ViewContext<Self>) -> Self;
350 fn serialize(&self) -> SerializedItem;
351}
352
353pub trait ProjectItem: Item {
354 type Item: project::Item;
355
356 fn for_project_item(
357 project: ModelHandle<Project>,
358 item: ModelHandle<Self::Item>,
359 cx: &mut ViewContext<Self>,
360 ) -> Self;
361}
362
363pub trait FollowableItem: Item {
364 fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
365 fn from_state_proto(
366 pane: ViewHandle<Pane>,
367 project: ModelHandle<Project>,
368 state: &mut Option<proto::view::Variant>,
369 cx: &mut MutableAppContext,
370 ) -> Option<Task<Result<ViewHandle<Self>>>>;
371 fn add_event_to_update_proto(
372 &self,
373 event: &Self::Event,
374 update: &mut Option<proto::update_view::Variant>,
375 cx: &AppContext,
376 ) -> bool;
377 fn apply_update_proto(
378 &mut self,
379 message: proto::update_view::Variant,
380 cx: &mut ViewContext<Self>,
381 ) -> Result<()>;
382
383 fn set_leader_replica_id(&mut self, leader_replica_id: Option<u16>, cx: &mut ViewContext<Self>);
384 fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
385}
386
387pub trait FollowableItemHandle: ItemHandle {
388 fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext);
389 fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
390 fn add_event_to_update_proto(
391 &self,
392 event: &dyn Any,
393 update: &mut Option<proto::update_view::Variant>,
394 cx: &AppContext,
395 ) -> bool;
396 fn apply_update_proto(
397 &self,
398 message: proto::update_view::Variant,
399 cx: &mut MutableAppContext,
400 ) -> Result<()>;
401 fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
402}
403
404impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
405 fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut MutableAppContext) {
406 self.update(cx, |this, cx| {
407 this.set_leader_replica_id(leader_replica_id, cx)
408 })
409 }
410
411 fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
412 self.read(cx).to_state_proto(cx)
413 }
414
415 fn add_event_to_update_proto(
416 &self,
417 event: &dyn Any,
418 update: &mut Option<proto::update_view::Variant>,
419 cx: &AppContext,
420 ) -> bool {
421 if let Some(event) = event.downcast_ref() {
422 self.read(cx).add_event_to_update_proto(event, update, cx)
423 } else {
424 false
425 }
426 }
427
428 fn apply_update_proto(
429 &self,
430 message: proto::update_view::Variant,
431 cx: &mut MutableAppContext,
432 ) -> Result<()> {
433 self.update(cx, |this, cx| this.apply_update_proto(message, cx))
434 }
435
436 fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
437 if let Some(event) = event.downcast_ref() {
438 T::should_unfollow_on_event(event, cx)
439 } else {
440 false
441 }
442 }
443}
444
445struct DelayedDebouncedEditAction {
446 task: Option<Task<()>>,
447 cancel_channel: Option<oneshot::Sender<()>>,
448}
449
450impl DelayedDebouncedEditAction {
451 fn new() -> DelayedDebouncedEditAction {
452 DelayedDebouncedEditAction {
453 task: None,
454 cancel_channel: None,
455 }
456 }
457
458 fn fire_new<F, Fut>(
459 &mut self,
460 delay: Duration,
461 workspace: &Workspace,
462 cx: &mut ViewContext<Workspace>,
463 f: F,
464 ) where
465 F: FnOnce(ModelHandle<Project>, AsyncAppContext) -> Fut + 'static,
466 Fut: 'static + Future<Output = ()>,
467 {
468 if let Some(channel) = self.cancel_channel.take() {
469 _ = channel.send(());
470 }
471
472 let project = workspace.project().downgrade();
473
474 let (sender, mut receiver) = oneshot::channel::<()>();
475 self.cancel_channel = Some(sender);
476
477 let previous_task = self.task.take();
478 self.task = Some(cx.spawn_weak(|_, cx| async move {
479 let mut timer = cx.background().timer(delay).fuse();
480 if let Some(previous_task) = previous_task {
481 previous_task.await;
482 }
483
484 futures::select_biased! {
485 _ = receiver => return,
486 _ = timer => {}
487 }
488
489 if let Some(project) = project.upgrade(&cx) {
490 (f)(project, cx).await;
491 }
492 }));
493 }
494}
495
496pub trait ItemHandle: 'static + fmt::Debug {
497 fn subscribe_to_item_events(
498 &self,
499 cx: &mut MutableAppContext,
500 handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
501 ) -> gpui::Subscription;
502 fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
503 fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
504 -> ElementBox;
505 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
506 fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
507 fn is_singleton(&self, cx: &AppContext) -> bool;
508 fn boxed_clone(&self) -> Box<dyn ItemHandle>;
509 fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>>;
510 fn added_to_pane(
511 &self,
512 workspace: &mut Workspace,
513 pane: ViewHandle<Pane>,
514 cx: &mut ViewContext<Workspace>,
515 );
516 fn deactivated(&self, cx: &mut MutableAppContext);
517 fn workspace_deactivated(&self, cx: &mut MutableAppContext);
518 fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool;
519 fn id(&self) -> usize;
520 fn window_id(&self) -> usize;
521 fn to_any(&self) -> AnyViewHandle;
522 fn is_dirty(&self, cx: &AppContext) -> bool;
523 fn has_conflict(&self, cx: &AppContext) -> bool;
524 fn can_save(&self, cx: &AppContext) -> bool;
525 fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>>;
526 fn save_as(
527 &self,
528 project: ModelHandle<Project>,
529 abs_path: PathBuf,
530 cx: &mut MutableAppContext,
531 ) -> Task<Result<()>>;
532 fn reload(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext)
533 -> Task<Result<()>>;
534 fn git_diff_recalc(
535 &self,
536 project: ModelHandle<Project>,
537 cx: &mut MutableAppContext,
538 ) -> Task<Result<()>>;
539 fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle>;
540 fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
541 fn on_release(
542 &self,
543 cx: &mut MutableAppContext,
544 callback: Box<dyn FnOnce(&mut MutableAppContext)>,
545 ) -> gpui::Subscription;
546 fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
547 fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
548 fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>>;
549}
550
551pub trait WeakItemHandle {
552 fn id(&self) -> usize;
553 fn window_id(&self) -> usize;
554 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
555}
556
557impl dyn ItemHandle {
558 pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
559 self.to_any().downcast()
560 }
561
562 pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
563 self.act_as_type(TypeId::of::<T>(), cx)
564 .and_then(|t| t.downcast())
565 }
566}
567
568impl<T: Item> ItemHandle for ViewHandle<T> {
569 fn subscribe_to_item_events(
570 &self,
571 cx: &mut MutableAppContext,
572 handler: Box<dyn Fn(ItemEvent, &mut MutableAppContext)>,
573 ) -> gpui::Subscription {
574 cx.subscribe(self, move |_, event, cx| {
575 for item_event in T::to_item_events(event) {
576 handler(item_event, cx)
577 }
578 })
579 }
580
581 fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
582 self.read(cx).tab_description(detail, cx)
583 }
584
585 fn tab_content(
586 &self,
587 detail: Option<usize>,
588 style: &theme::Tab,
589 cx: &AppContext,
590 ) -> ElementBox {
591 self.read(cx).tab_content(detail, style, cx)
592 }
593
594 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
595 self.read(cx).project_path(cx)
596 }
597
598 fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
599 self.read(cx).project_entry_ids(cx)
600 }
601
602 fn is_singleton(&self, cx: &AppContext) -> bool {
603 self.read(cx).is_singleton(cx)
604 }
605
606 fn boxed_clone(&self) -> Box<dyn ItemHandle> {
607 Box::new(self.clone())
608 }
609
610 fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>> {
611 self.update(cx, |item, cx| {
612 cx.add_option_view(|cx| item.clone_on_split(cx))
613 })
614 .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
615 }
616
617 fn added_to_pane(
618 &self,
619 workspace: &mut Workspace,
620 pane: ViewHandle<Pane>,
621 cx: &mut ViewContext<Workspace>,
622 ) {
623 let history = pane.read(cx).nav_history_for_item(self);
624 self.update(cx, |this, cx| this.set_nav_history(history, cx));
625
626 if let Some(followed_item) = self.to_followable_item_handle(cx) {
627 if let Some(message) = followed_item.to_state_proto(cx) {
628 workspace.update_followers(
629 proto::update_followers::Variant::CreateView(proto::View {
630 id: followed_item.id() as u64,
631 variant: Some(message),
632 leader_id: workspace.leader_for_pane(&pane).map(|id| id.0),
633 }),
634 cx,
635 );
636 }
637 }
638
639 if workspace
640 .panes_by_item
641 .insert(self.id(), pane.downgrade())
642 .is_none()
643 {
644 let mut pending_autosave = DelayedDebouncedEditAction::new();
645 let mut pending_git_update = DelayedDebouncedEditAction::new();
646 let pending_update = Rc::new(RefCell::new(None));
647 let pending_update_scheduled = Rc::new(AtomicBool::new(false));
648
649 let mut event_subscription =
650 Some(cx.subscribe(self, move |workspace, item, event, cx| {
651 let pane = if let Some(pane) = workspace
652 .panes_by_item
653 .get(&item.id())
654 .and_then(|pane| pane.upgrade(cx))
655 {
656 pane
657 } else {
658 log::error!("unexpected item event after pane was dropped");
659 return;
660 };
661
662 if let Some(item) = item.to_followable_item_handle(cx) {
663 let leader_id = workspace.leader_for_pane(&pane);
664
665 if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
666 workspace.unfollow(&pane, cx);
667 }
668
669 if item.add_event_to_update_proto(
670 event,
671 &mut *pending_update.borrow_mut(),
672 cx,
673 ) && !pending_update_scheduled.load(SeqCst)
674 {
675 pending_update_scheduled.store(true, SeqCst);
676 cx.after_window_update({
677 let pending_update = pending_update.clone();
678 let pending_update_scheduled = pending_update_scheduled.clone();
679 move |this, cx| {
680 pending_update_scheduled.store(false, SeqCst);
681 this.update_followers(
682 proto::update_followers::Variant::UpdateView(
683 proto::UpdateView {
684 id: item.id() as u64,
685 variant: pending_update.borrow_mut().take(),
686 leader_id: leader_id.map(|id| id.0),
687 },
688 ),
689 cx,
690 );
691 }
692 });
693 }
694 }
695
696 for item_event in T::to_item_events(event).into_iter() {
697 match item_event {
698 ItemEvent::CloseItem => {
699 Pane::close_item(workspace, pane, item.id(), cx)
700 .detach_and_log_err(cx);
701 return;
702 }
703
704 ItemEvent::UpdateTab => {
705 pane.update(cx, |_, cx| {
706 cx.emit(pane::Event::ChangeItemTitle);
707 cx.notify();
708 });
709 }
710
711 ItemEvent::Edit => {
712 if let Autosave::AfterDelay { milliseconds } =
713 cx.global::<Settings>().autosave
714 {
715 let delay = Duration::from_millis(milliseconds);
716 let item = item.clone();
717 pending_autosave.fire_new(
718 delay,
719 workspace,
720 cx,
721 |project, mut cx| async move {
722 cx.update(|cx| Pane::autosave_item(&item, project, cx))
723 .await
724 .log_err();
725 },
726 );
727 }
728
729 let settings = cx.global::<Settings>();
730 let debounce_delay = settings.git_overrides.gutter_debounce;
731
732 let item = item.clone();
733
734 if let Some(delay) = debounce_delay {
735 const MIN_GIT_DELAY: u64 = 50;
736
737 let delay = delay.max(MIN_GIT_DELAY);
738 let duration = Duration::from_millis(delay);
739
740 pending_git_update.fire_new(
741 duration,
742 workspace,
743 cx,
744 |project, mut cx| async move {
745 cx.update(|cx| item.git_diff_recalc(project, cx))
746 .await
747 .log_err();
748 },
749 );
750 } else {
751 let project = workspace.project().downgrade();
752 cx.spawn_weak(|_, mut cx| async move {
753 if let Some(project) = project.upgrade(&cx) {
754 cx.update(|cx| item.git_diff_recalc(project, cx))
755 .await
756 .log_err();
757 }
758 })
759 .detach();
760 }
761 }
762
763 _ => {}
764 }
765 }
766 }));
767
768 cx.observe_focus(self, move |workspace, item, focused, cx| {
769 if !focused && cx.global::<Settings>().autosave == Autosave::OnFocusChange {
770 Pane::autosave_item(&item, workspace.project.clone(), cx)
771 .detach_and_log_err(cx);
772 }
773 })
774 .detach();
775
776 let item_id = self.id();
777 cx.observe_release(self, move |workspace, _, _| {
778 workspace.panes_by_item.remove(&item_id);
779 event_subscription.take();
780 })
781 .detach();
782 }
783 }
784
785 fn deactivated(&self, cx: &mut MutableAppContext) {
786 self.update(cx, |this, cx| this.deactivated(cx));
787 }
788
789 fn workspace_deactivated(&self, cx: &mut MutableAppContext) {
790 self.update(cx, |this, cx| this.workspace_deactivated(cx));
791 }
792
793 fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext) -> bool {
794 self.update(cx, |this, cx| this.navigate(data, cx))
795 }
796
797 fn id(&self) -> usize {
798 self.id()
799 }
800
801 fn window_id(&self) -> usize {
802 self.window_id()
803 }
804
805 fn to_any(&self) -> AnyViewHandle {
806 self.into()
807 }
808
809 fn is_dirty(&self, cx: &AppContext) -> bool {
810 self.read(cx).is_dirty(cx)
811 }
812
813 fn has_conflict(&self, cx: &AppContext) -> bool {
814 self.read(cx).has_conflict(cx)
815 }
816
817 fn can_save(&self, cx: &AppContext) -> bool {
818 self.read(cx).can_save(cx)
819 }
820
821 fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>> {
822 self.update(cx, |item, cx| item.save(project, cx))
823 }
824
825 fn save_as(
826 &self,
827 project: ModelHandle<Project>,
828 abs_path: PathBuf,
829 cx: &mut MutableAppContext,
830 ) -> Task<anyhow::Result<()>> {
831 self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
832 }
833
834 fn reload(
835 &self,
836 project: ModelHandle<Project>,
837 cx: &mut MutableAppContext,
838 ) -> Task<Result<()>> {
839 self.update(cx, |item, cx| item.reload(project, cx))
840 }
841
842 fn git_diff_recalc(
843 &self,
844 project: ModelHandle<Project>,
845 cx: &mut MutableAppContext,
846 ) -> Task<Result<()>> {
847 self.update(cx, |item, cx| item.git_diff_recalc(project, cx))
848 }
849
850 fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle> {
851 self.read(cx).act_as_type(type_id, self, cx)
852 }
853
854 fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
855 if cx.has_global::<FollowableItemBuilders>() {
856 let builders = cx.global::<FollowableItemBuilders>();
857 let item = self.to_any();
858 Some(builders.get(&item.view_type())?.1(item))
859 } else {
860 None
861 }
862 }
863
864 fn on_release(
865 &self,
866 cx: &mut MutableAppContext,
867 callback: Box<dyn FnOnce(&mut MutableAppContext)>,
868 ) -> gpui::Subscription {
869 cx.observe_release(self, move |_, cx| callback(cx))
870 }
871
872 fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
873 self.read(cx).as_searchable(self)
874 }
875
876 fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
877 self.read(cx).breadcrumb_location()
878 }
879
880 fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
881 self.read(cx).breadcrumbs(theme, cx)
882 }
883}
884
885impl From<Box<dyn ItemHandle>> for AnyViewHandle {
886 fn from(val: Box<dyn ItemHandle>) -> Self {
887 val.to_any()
888 }
889}
890
891impl From<&Box<dyn ItemHandle>> for AnyViewHandle {
892 fn from(val: &Box<dyn ItemHandle>) -> Self {
893 val.to_any()
894 }
895}
896
897impl Clone for Box<dyn ItemHandle> {
898 fn clone(&self) -> Box<dyn ItemHandle> {
899 self.boxed_clone()
900 }
901}
902
903impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
904 fn id(&self) -> usize {
905 self.id()
906 }
907
908 fn window_id(&self) -> usize {
909 self.window_id()
910 }
911
912 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
913 self.upgrade(cx).map(|v| Box::new(v) as Box<dyn ItemHandle>)
914 }
915}
916
917pub trait Notification: View {
918 fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool;
919}
920
921pub trait NotificationHandle {
922 fn id(&self) -> usize;
923 fn to_any(&self) -> AnyViewHandle;
924}
925
926impl<T: Notification> NotificationHandle for ViewHandle<T> {
927 fn id(&self) -> usize {
928 self.id()
929 }
930
931 fn to_any(&self) -> AnyViewHandle {
932 self.into()
933 }
934}
935
936impl From<&dyn NotificationHandle> for AnyViewHandle {
937 fn from(val: &dyn NotificationHandle) -> Self {
938 val.to_any()
939 }
940}
941
942impl AppState {
943 #[cfg(any(test, feature = "test-support"))]
944 pub fn test(cx: &mut MutableAppContext) -> Arc<Self> {
945 let settings = Settings::test(cx);
946 cx.set_global(settings);
947
948 let fs = fs::FakeFs::new(cx.background().clone());
949 let languages = Arc::new(LanguageRegistry::test());
950 let http_client = client::test::FakeHttpClient::with_404_response();
951 let client = Client::new(http_client.clone(), cx);
952 let project_store = cx.add_model(|_| ProjectStore::new());
953 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
954 let themes = ThemeRegistry::new((), cx.font_cache().clone());
955 Arc::new(Self {
956 client,
957 themes,
958 fs,
959 languages,
960 user_store,
961 project_store,
962 initialize_workspace: |_, _, _| {},
963 build_window_options: Default::default,
964 default_item_factory: |_, _| unimplemented!(),
965 })
966 }
967}
968
969pub enum Event {
970 DockAnchorChanged,
971 PaneAdded(ViewHandle<Pane>),
972 ContactRequestedJoin(u64),
973}
974
975pub struct Workspace {
976 weak_self: WeakViewHandle<Self>,
977 client: Arc<Client>,
978 user_store: ModelHandle<client::UserStore>,
979 remote_entity_subscription: Option<client::Subscription>,
980 fs: Arc<dyn Fs>,
981 modal: Option<AnyViewHandle>,
982 center: PaneGroup,
983 left_sidebar: ViewHandle<Sidebar>,
984 right_sidebar: ViewHandle<Sidebar>,
985 panes: Vec<ViewHandle<Pane>>,
986 panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
987 active_pane: ViewHandle<Pane>,
988 last_active_center_pane: Option<WeakViewHandle<Pane>>,
989 status_bar: ViewHandle<StatusBar>,
990 titlebar_item: Option<AnyViewHandle>,
991 dock: Dock,
992 notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
993 project: ModelHandle<Project>,
994 leader_state: LeaderState,
995 follower_states_by_leader: FollowerStatesByLeader,
996 last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
997 window_edited: bool,
998 active_call: Option<ModelHandle<ActiveCall>>,
999 _observe_current_user: Task<()>,
1000 _active_call_observation: Option<gpui::Subscription>,
1001}
1002
1003#[derive(Default)]
1004struct LeaderState {
1005 followers: HashSet<PeerId>,
1006}
1007
1008type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
1009
1010#[derive(Default)]
1011struct FollowerState {
1012 active_view_id: Option<u64>,
1013 items_by_leader_view_id: HashMap<u64, FollowerItem>,
1014}
1015
1016#[derive(Debug)]
1017enum FollowerItem {
1018 Loading(Vec<proto::update_view::Variant>),
1019 Loaded(Box<dyn FollowableItemHandle>),
1020}
1021
1022impl Workspace {
1023 pub fn new(
1024 project: ModelHandle<Project>,
1025 dock_default_factory: DefaultItemFactory,
1026 cx: &mut ViewContext<Self>,
1027 ) -> Self {
1028 cx.observe_fullscreen(|_, _, cx| cx.notify()).detach();
1029
1030 cx.observe_window_activation(Self::on_window_activation_changed)
1031 .detach();
1032 cx.observe(&project, |_, _, cx| cx.notify()).detach();
1033 cx.subscribe(&project, move |this, _, event, cx| {
1034 match event {
1035 project::Event::RemoteIdChanged(remote_id) => {
1036 this.project_remote_id_changed(*remote_id, cx);
1037 }
1038 project::Event::CollaboratorLeft(peer_id) => {
1039 this.collaborator_left(*peer_id, cx);
1040 }
1041 project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
1042 this.update_window_title(cx);
1043 }
1044 project::Event::DisconnectedFromHost => {
1045 this.update_window_edited(cx);
1046 cx.blur();
1047 }
1048 _ => {}
1049 }
1050 cx.notify()
1051 })
1052 .detach();
1053
1054 let center_pane = cx.add_view(|cx| Pane::new(None, cx));
1055 let pane_id = center_pane.id();
1056 cx.subscribe(¢er_pane, move |this, _, event, cx| {
1057 this.handle_pane_event(pane_id, event, cx)
1058 })
1059 .detach();
1060 cx.focus(¢er_pane);
1061 cx.emit(Event::PaneAdded(center_pane.clone()));
1062
1063 let fs = project.read(cx).fs().clone();
1064 let user_store = project.read(cx).user_store();
1065 let client = project.read(cx).client();
1066 let mut current_user = user_store.read(cx).watch_current_user();
1067 let mut connection_status = client.status();
1068 let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
1069 current_user.recv().await;
1070 connection_status.recv().await;
1071 let mut stream =
1072 Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
1073
1074 while stream.recv().await.is_some() {
1075 cx.update(|cx| {
1076 if let Some(this) = this.upgrade(cx) {
1077 this.update(cx, |_, cx| cx.notify());
1078 }
1079 })
1080 }
1081 });
1082
1083 let handle = cx.handle();
1084 let weak_handle = cx.weak_handle();
1085
1086 cx.emit_global(WorkspaceCreated(weak_handle.clone()));
1087
1088 let dock = Dock::new(cx, dock_default_factory);
1089 let dock_pane = dock.pane().clone();
1090
1091 let left_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Left));
1092 let right_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Right));
1093 let left_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), cx));
1094 let toggle_dock = cx.add_view(|cx| ToggleDockButton::new(handle, cx));
1095 let right_sidebar_buttons =
1096 cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), cx));
1097 let status_bar = cx.add_view(|cx| {
1098 let mut status_bar = StatusBar::new(¢er_pane.clone(), cx);
1099 status_bar.add_left_item(left_sidebar_buttons, cx);
1100 status_bar.add_right_item(right_sidebar_buttons, cx);
1101 status_bar.add_right_item(toggle_dock, cx);
1102 status_bar
1103 });
1104
1105 cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
1106 drag_and_drop.register_container(weak_handle.clone());
1107 });
1108
1109 let mut active_call = None;
1110 let mut active_call_observation = None;
1111 if cx.has_global::<ModelHandle<ActiveCall>>() {
1112 let call = cx.global::<ModelHandle<ActiveCall>>().clone();
1113 active_call_observation = Some(cx.observe(&call, |_, _, cx| cx.notify()));
1114 active_call = Some(call);
1115 }
1116
1117 let mut this = Workspace {
1118 modal: None,
1119 weak_self: weak_handle,
1120 center: PaneGroup::new(center_pane.clone()),
1121 dock,
1122 // When removing an item, the last element remaining in this array
1123 // is used to find where focus should fallback to. As such, the order
1124 // of these two variables is important.
1125 panes: vec![dock_pane, center_pane.clone()],
1126 panes_by_item: Default::default(),
1127 active_pane: center_pane.clone(),
1128 last_active_center_pane: Some(center_pane.downgrade()),
1129 status_bar,
1130 titlebar_item: None,
1131 notifications: Default::default(),
1132 client,
1133 remote_entity_subscription: None,
1134 user_store,
1135 fs,
1136 left_sidebar,
1137 right_sidebar,
1138 project,
1139 leader_state: Default::default(),
1140 follower_states_by_leader: Default::default(),
1141 last_leaders_by_pane: Default::default(),
1142 window_edited: false,
1143 active_call,
1144 _observe_current_user,
1145 _active_call_observation: active_call_observation,
1146 };
1147 this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
1148 cx.defer(|this, cx| this.update_window_title(cx));
1149
1150 this
1151 }
1152
1153 pub fn weak_handle(&self) -> WeakViewHandle<Self> {
1154 self.weak_self.clone()
1155 }
1156
1157 pub fn left_sidebar(&self) -> &ViewHandle<Sidebar> {
1158 &self.left_sidebar
1159 }
1160
1161 pub fn right_sidebar(&self) -> &ViewHandle<Sidebar> {
1162 &self.right_sidebar
1163 }
1164
1165 pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
1166 &self.status_bar
1167 }
1168
1169 pub fn user_store(&self) -> &ModelHandle<UserStore> {
1170 &self.user_store
1171 }
1172
1173 pub fn project(&self) -> &ModelHandle<Project> {
1174 &self.project
1175 }
1176
1177 pub fn client(&self) -> &Arc<Client> {
1178 &self.client
1179 }
1180
1181 pub fn set_titlebar_item(
1182 &mut self,
1183 item: impl Into<AnyViewHandle>,
1184 cx: &mut ViewContext<Self>,
1185 ) {
1186 self.titlebar_item = Some(item.into());
1187 cx.notify();
1188 }
1189
1190 pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
1191 self.titlebar_item.clone()
1192 }
1193
1194 /// Call the given callback with a workspace whose project is local.
1195 ///
1196 /// If the given workspace has a local project, then it will be passed
1197 /// to the callback. Otherwise, a new empty window will be created.
1198 pub fn with_local_workspace<T, F>(
1199 &mut self,
1200 cx: &mut ViewContext<Self>,
1201 app_state: Arc<AppState>,
1202 callback: F,
1203 ) -> T
1204 where
1205 T: 'static,
1206 F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1207 {
1208 if self.project.read(cx).is_local() {
1209 callback(self, cx)
1210 } else {
1211 let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
1212 let mut workspace = Workspace::new(
1213 Project::local(
1214 app_state.client.clone(),
1215 app_state.user_store.clone(),
1216 app_state.project_store.clone(),
1217 app_state.languages.clone(),
1218 app_state.fs.clone(),
1219 cx,
1220 ),
1221 app_state.default_item_factory,
1222 cx,
1223 );
1224 (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
1225 workspace
1226 });
1227 workspace.update(cx, callback)
1228 }
1229 }
1230
1231 pub fn worktrees<'a>(
1232 &self,
1233 cx: &'a AppContext,
1234 ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1235 self.project.read(cx).worktrees(cx)
1236 }
1237
1238 pub fn visible_worktrees<'a>(
1239 &self,
1240 cx: &'a AppContext,
1241 ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1242 self.project.read(cx).visible_worktrees(cx)
1243 }
1244
1245 pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1246 let futures = self
1247 .worktrees(cx)
1248 .filter_map(|worktree| worktree.read(cx).as_local())
1249 .map(|worktree| worktree.scan_complete())
1250 .collect::<Vec<_>>();
1251 async move {
1252 for future in futures {
1253 future.await;
1254 }
1255 }
1256 }
1257
1258 pub fn close(
1259 &mut self,
1260 _: &CloseWindow,
1261 cx: &mut ViewContext<Self>,
1262 ) -> Option<Task<Result<()>>> {
1263 let prepare = self.prepare_to_close(false, cx);
1264 Some(cx.spawn(|this, mut cx| async move {
1265 if prepare.await? {
1266 this.update(&mut cx, |_, cx| {
1267 let window_id = cx.window_id();
1268 cx.remove_window(window_id);
1269 });
1270 }
1271 Ok(())
1272 }))
1273 }
1274
1275 pub fn prepare_to_close(
1276 &mut self,
1277 quitting: bool,
1278 cx: &mut ViewContext<Self>,
1279 ) -> Task<Result<bool>> {
1280 let active_call = self.active_call.clone();
1281 let window_id = cx.window_id();
1282 let workspace_count = cx
1283 .window_ids()
1284 .flat_map(|window_id| cx.root_view::<Workspace>(window_id))
1285 .count();
1286 cx.spawn(|this, mut cx| async move {
1287 if let Some(active_call) = active_call {
1288 if !quitting
1289 && workspace_count == 1
1290 && active_call.read_with(&cx, |call, _| call.room().is_some())
1291 {
1292 let answer = cx
1293 .prompt(
1294 window_id,
1295 PromptLevel::Warning,
1296 "Do you want to leave the current call?",
1297 &["Close window and hang up", "Cancel"],
1298 )
1299 .next()
1300 .await;
1301 if answer == Some(1) {
1302 return anyhow::Ok(false);
1303 } else {
1304 active_call.update(&mut cx, |call, cx| call.hang_up(cx))?;
1305 }
1306 }
1307 }
1308
1309 Ok(this
1310 .update(&mut cx, |this, cx| this.save_all_internal(true, cx))
1311 .await?)
1312 })
1313 }
1314
1315 fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1316 let save_all = self.save_all_internal(false, cx);
1317 Some(cx.foreground().spawn(async move {
1318 save_all.await?;
1319 Ok(())
1320 }))
1321 }
1322
1323 fn save_all_internal(
1324 &mut self,
1325 should_prompt_to_save: bool,
1326 cx: &mut ViewContext<Self>,
1327 ) -> Task<Result<bool>> {
1328 if self.project.read(cx).is_read_only() {
1329 return Task::ready(Ok(true));
1330 }
1331
1332 let dirty_items = self
1333 .panes
1334 .iter()
1335 .flat_map(|pane| {
1336 pane.read(cx).items().filter_map(|item| {
1337 if item.is_dirty(cx) {
1338 Some((pane.clone(), item.boxed_clone()))
1339 } else {
1340 None
1341 }
1342 })
1343 })
1344 .collect::<Vec<_>>();
1345
1346 let project = self.project.clone();
1347 cx.spawn_weak(|_, mut cx| async move {
1348 for (pane, item) in dirty_items {
1349 let (singleton, project_entry_ids) =
1350 cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1351 if singleton || !project_entry_ids.is_empty() {
1352 if let Some(ix) =
1353 pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))
1354 {
1355 if !Pane::save_item(
1356 project.clone(),
1357 &pane,
1358 ix,
1359 &*item,
1360 should_prompt_to_save,
1361 &mut cx,
1362 )
1363 .await?
1364 {
1365 return Ok(false);
1366 }
1367 }
1368 }
1369 }
1370 Ok(true)
1371 })
1372 }
1373
1374 #[allow(clippy::type_complexity)]
1375 pub fn open_paths(
1376 &mut self,
1377 mut abs_paths: Vec<PathBuf>,
1378 visible: bool,
1379 cx: &mut ViewContext<Self>,
1380 ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>> {
1381 let fs = self.fs.clone();
1382
1383 // Sort the paths to ensure we add worktrees for parents before their children.
1384 abs_paths.sort_unstable();
1385 cx.spawn(|this, mut cx| async move {
1386 let mut project_paths = Vec::new();
1387 for path in &abs_paths {
1388 project_paths.push(
1389 this.update(&mut cx, |this, cx| {
1390 this.project_path_for_path(path, visible, cx)
1391 })
1392 .await
1393 .log_err(),
1394 );
1395 }
1396
1397 let tasks = abs_paths
1398 .iter()
1399 .cloned()
1400 .zip(project_paths.into_iter())
1401 .map(|(abs_path, project_path)| {
1402 let this = this.clone();
1403 cx.spawn(|mut cx| {
1404 let fs = fs.clone();
1405 async move {
1406 let (_worktree, project_path) = project_path?;
1407 if fs.is_file(&abs_path).await {
1408 Some(
1409 this.update(&mut cx, |this, cx| {
1410 this.open_path(project_path, true, cx)
1411 })
1412 .await,
1413 )
1414 } else {
1415 None
1416 }
1417 }
1418 })
1419 })
1420 .collect::<Vec<_>>();
1421
1422 futures::future::join_all(tasks).await
1423 })
1424 }
1425
1426 fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1427 let mut paths = cx.prompt_for_paths(PathPromptOptions {
1428 files: false,
1429 directories: true,
1430 multiple: true,
1431 });
1432 cx.spawn(|this, mut cx| async move {
1433 if let Some(paths) = paths.recv().await.flatten() {
1434 let results = this
1435 .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))
1436 .await;
1437 for result in results.into_iter().flatten() {
1438 result.log_err();
1439 }
1440 }
1441 })
1442 .detach();
1443 }
1444
1445 fn remove_folder_from_project(
1446 &mut self,
1447 RemoveWorktreeFromProject(worktree_id): &RemoveWorktreeFromProject,
1448 cx: &mut ViewContext<Self>,
1449 ) {
1450 self.project
1451 .update(cx, |project, cx| project.remove_worktree(*worktree_id, cx));
1452 }
1453
1454 fn project_path_for_path(
1455 &self,
1456 abs_path: &Path,
1457 visible: bool,
1458 cx: &mut ViewContext<Self>,
1459 ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1460 let entry = self.project().update(cx, |project, cx| {
1461 project.find_or_create_local_worktree(abs_path, visible, cx)
1462 });
1463 cx.spawn(|_, cx| async move {
1464 let (worktree, path) = entry.await?;
1465 let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1466 Ok((
1467 worktree,
1468 ProjectPath {
1469 worktree_id,
1470 path: path.into(),
1471 },
1472 ))
1473 })
1474 }
1475
1476 /// Returns the modal that was toggled closed if it was open.
1477 pub fn toggle_modal<V, F>(
1478 &mut self,
1479 cx: &mut ViewContext<Self>,
1480 add_view: F,
1481 ) -> Option<ViewHandle<V>>
1482 where
1483 V: 'static + View,
1484 F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1485 {
1486 cx.notify();
1487 // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1488 // it. Otherwise, create a new modal and set it as active.
1489 let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1490 if let Some(already_open_modal) = already_open_modal {
1491 cx.focus_self();
1492 Some(already_open_modal)
1493 } else {
1494 let modal = add_view(self, cx);
1495 cx.focus(&modal);
1496 self.modal = Some(modal.into());
1497 None
1498 }
1499 }
1500
1501 pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1502 self.modal
1503 .as_ref()
1504 .and_then(|modal| modal.clone().downcast::<V>())
1505 }
1506
1507 pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1508 if self.modal.take().is_some() {
1509 cx.focus(&self.active_pane);
1510 cx.notify();
1511 }
1512 }
1513
1514 pub fn show_notification<V: Notification>(
1515 &mut self,
1516 id: usize,
1517 cx: &mut ViewContext<Self>,
1518 build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
1519 ) {
1520 let type_id = TypeId::of::<V>();
1521 if self
1522 .notifications
1523 .iter()
1524 .all(|(existing_type_id, existing_id, _)| {
1525 (*existing_type_id, *existing_id) != (type_id, id)
1526 })
1527 {
1528 let notification = build_notification(cx);
1529 cx.subscribe(¬ification, move |this, handle, event, cx| {
1530 if handle.read(cx).should_dismiss_notification_on_event(event) {
1531 this.dismiss_notification(type_id, id, cx);
1532 }
1533 })
1534 .detach();
1535 self.notifications
1536 .push((type_id, id, Box::new(notification)));
1537 cx.notify();
1538 }
1539 }
1540
1541 fn dismiss_notification(&mut self, type_id: TypeId, id: usize, cx: &mut ViewContext<Self>) {
1542 self.notifications
1543 .retain(|(existing_type_id, existing_id, _)| {
1544 if (*existing_type_id, *existing_id) == (type_id, id) {
1545 cx.notify();
1546 false
1547 } else {
1548 true
1549 }
1550 });
1551 }
1552
1553 pub fn items<'a>(
1554 &'a self,
1555 cx: &'a AppContext,
1556 ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1557 self.panes.iter().flat_map(|pane| pane.read(cx).items())
1558 }
1559
1560 pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1561 self.items_of_type(cx).max_by_key(|item| item.id())
1562 }
1563
1564 pub fn items_of_type<'a, T: Item>(
1565 &'a self,
1566 cx: &'a AppContext,
1567 ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1568 self.panes
1569 .iter()
1570 .flat_map(|pane| pane.read(cx).items_of_type())
1571 }
1572
1573 pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1574 self.active_pane().read(cx).active_item()
1575 }
1576
1577 fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1578 self.active_item(cx).and_then(|item| item.project_path(cx))
1579 }
1580
1581 pub fn save_active_item(
1582 &mut self,
1583 force_name_change: bool,
1584 cx: &mut ViewContext<Self>,
1585 ) -> Task<Result<()>> {
1586 let project = self.project.clone();
1587 if let Some(item) = self.active_item(cx) {
1588 if !force_name_change && item.can_save(cx) {
1589 if item.has_conflict(cx.as_ref()) {
1590 const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1591
1592 let mut answer = cx.prompt(
1593 PromptLevel::Warning,
1594 CONFLICT_MESSAGE,
1595 &["Overwrite", "Cancel"],
1596 );
1597 cx.spawn(|_, mut cx| async move {
1598 let answer = answer.recv().await;
1599 if answer == Some(0) {
1600 cx.update(|cx| item.save(project, cx)).await?;
1601 }
1602 Ok(())
1603 })
1604 } else {
1605 item.save(project, cx)
1606 }
1607 } else if item.is_singleton(cx) {
1608 let worktree = self.worktrees(cx).next();
1609 let start_abs_path = worktree
1610 .and_then(|w| w.read(cx).as_local())
1611 .map_or(Path::new(""), |w| w.abs_path())
1612 .to_path_buf();
1613 let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1614 cx.spawn(|_, mut cx| async move {
1615 if let Some(abs_path) = abs_path.recv().await.flatten() {
1616 cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
1617 }
1618 Ok(())
1619 })
1620 } else {
1621 Task::ready(Ok(()))
1622 }
1623 } else {
1624 Task::ready(Ok(()))
1625 }
1626 }
1627
1628 pub fn toggle_sidebar(&mut self, sidebar_side: SidebarSide, cx: &mut ViewContext<Self>) {
1629 let sidebar = match sidebar_side {
1630 SidebarSide::Left => &mut self.left_sidebar,
1631 SidebarSide::Right => &mut self.right_sidebar,
1632 };
1633 let open = sidebar.update(cx, |sidebar, cx| {
1634 let open = !sidebar.is_open();
1635 sidebar.set_open(open, cx);
1636 open
1637 });
1638
1639 if open {
1640 Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1641 }
1642
1643 cx.focus_self();
1644 cx.notify();
1645 }
1646
1647 pub fn toggle_sidebar_item(&mut self, action: &ToggleSidebarItem, cx: &mut ViewContext<Self>) {
1648 let sidebar = match action.sidebar_side {
1649 SidebarSide::Left => &mut self.left_sidebar,
1650 SidebarSide::Right => &mut self.right_sidebar,
1651 };
1652 let active_item = sidebar.update(cx, move |sidebar, cx| {
1653 if sidebar.is_open() && sidebar.active_item_ix() == action.item_index {
1654 sidebar.set_open(false, cx);
1655 None
1656 } else {
1657 sidebar.set_open(true, cx);
1658 sidebar.activate_item(action.item_index, cx);
1659 sidebar.active_item().cloned()
1660 }
1661 });
1662
1663 if let Some(active_item) = active_item {
1664 Dock::hide_on_sidebar_shown(self, action.sidebar_side, cx);
1665
1666 if active_item.is_focused(cx) {
1667 cx.focus_self();
1668 } else {
1669 cx.focus(active_item.to_any());
1670 }
1671 } else {
1672 cx.focus_self();
1673 }
1674 cx.notify();
1675 }
1676
1677 pub fn toggle_sidebar_item_focus(
1678 &mut self,
1679 sidebar_side: SidebarSide,
1680 item_index: usize,
1681 cx: &mut ViewContext<Self>,
1682 ) {
1683 let sidebar = match sidebar_side {
1684 SidebarSide::Left => &mut self.left_sidebar,
1685 SidebarSide::Right => &mut self.right_sidebar,
1686 };
1687 let active_item = sidebar.update(cx, |sidebar, cx| {
1688 sidebar.set_open(true, cx);
1689 sidebar.activate_item(item_index, cx);
1690 sidebar.active_item().cloned()
1691 });
1692 if let Some(active_item) = active_item {
1693 Dock::hide_on_sidebar_shown(self, sidebar_side, cx);
1694
1695 if active_item.is_focused(cx) {
1696 cx.focus_self();
1697 } else {
1698 cx.focus(active_item.to_any());
1699 }
1700 }
1701 cx.notify();
1702 }
1703
1704 pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
1705 cx.focus_self();
1706 cx.notify();
1707 }
1708
1709 fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1710 let pane = cx.add_view(|cx| Pane::new(None, cx));
1711 let pane_id = pane.id();
1712 cx.subscribe(&pane, move |this, _, event, cx| {
1713 this.handle_pane_event(pane_id, event, cx)
1714 })
1715 .detach();
1716 self.panes.push(pane.clone());
1717 cx.focus(pane.clone());
1718 cx.emit(Event::PaneAdded(pane.clone()));
1719 pane
1720 }
1721
1722 pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1723 let active_pane = self.active_pane().clone();
1724 Pane::add_item(self, &active_pane, item, true, true, None, cx);
1725 }
1726
1727 pub fn open_path(
1728 &mut self,
1729 path: impl Into<ProjectPath>,
1730 focus_item: bool,
1731 cx: &mut ViewContext<Self>,
1732 ) -> Task<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>> {
1733 let pane = self.active_pane().downgrade();
1734 let task = self.load_path(path.into(), cx);
1735 cx.spawn(|this, mut cx| async move {
1736 let (project_entry_id, build_item) = task.await?;
1737 let pane = pane
1738 .upgrade(&cx)
1739 .ok_or_else(|| anyhow!("pane was closed"))?;
1740 this.update(&mut cx, |this, cx| {
1741 Ok(Pane::open_item(
1742 this,
1743 pane,
1744 project_entry_id,
1745 focus_item,
1746 cx,
1747 build_item,
1748 ))
1749 })
1750 })
1751 }
1752
1753 pub(crate) fn load_path(
1754 &mut self,
1755 path: ProjectPath,
1756 cx: &mut ViewContext<Self>,
1757 ) -> Task<
1758 Result<(
1759 ProjectEntryId,
1760 impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1761 )>,
1762 > {
1763 let project = self.project().clone();
1764 let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1765 cx.as_mut().spawn(|mut cx| async move {
1766 let (project_entry_id, project_item) = project_item.await?;
1767 let build_item = cx.update(|cx| {
1768 cx.default_global::<ProjectItemBuilders>()
1769 .get(&project_item.model_type())
1770 .ok_or_else(|| anyhow!("no item builder for project item"))
1771 .cloned()
1772 })?;
1773 let build_item =
1774 move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1775 Ok((project_entry_id, build_item))
1776 })
1777 }
1778
1779 pub fn open_project_item<T>(
1780 &mut self,
1781 project_item: ModelHandle<T::Item>,
1782 cx: &mut ViewContext<Self>,
1783 ) -> ViewHandle<T>
1784 where
1785 T: ProjectItem,
1786 {
1787 use project::Item as _;
1788
1789 let entry_id = project_item.read(cx).entry_id(cx);
1790 if let Some(item) = entry_id
1791 .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1792 .and_then(|item| item.downcast())
1793 {
1794 self.activate_item(&item, cx);
1795 return item;
1796 }
1797
1798 let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1799 self.add_item(Box::new(item.clone()), cx);
1800 item
1801 }
1802
1803 pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1804 let result = self.panes.iter().find_map(|pane| {
1805 pane.read(cx)
1806 .index_for_item(item)
1807 .map(|ix| (pane.clone(), ix))
1808 });
1809 if let Some((pane, ix)) = result {
1810 pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1811 true
1812 } else {
1813 false
1814 }
1815 }
1816
1817 fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1818 let panes = self.center.panes();
1819 if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1820 cx.focus(pane);
1821 } else {
1822 self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1823 }
1824 }
1825
1826 pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1827 let next_pane = {
1828 let panes = self.center.panes();
1829 let ix = panes
1830 .iter()
1831 .position(|pane| **pane == self.active_pane)
1832 .unwrap();
1833 let next_ix = (ix + 1) % panes.len();
1834 panes[next_ix].clone()
1835 };
1836 cx.focus(next_pane);
1837 }
1838
1839 pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1840 let prev_pane = {
1841 let panes = self.center.panes();
1842 let ix = panes
1843 .iter()
1844 .position(|pane| **pane == self.active_pane)
1845 .unwrap();
1846 let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 };
1847 panes[prev_ix].clone()
1848 };
1849 cx.focus(prev_pane);
1850 }
1851
1852 fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1853 if self.active_pane != pane {
1854 self.active_pane
1855 .update(cx, |pane, cx| pane.set_active(false, cx));
1856 self.active_pane = pane.clone();
1857 self.active_pane
1858 .update(cx, |pane, cx| pane.set_active(true, cx));
1859 self.status_bar.update(cx, |status_bar, cx| {
1860 status_bar.set_active_pane(&self.active_pane, cx);
1861 });
1862 self.active_item_path_changed(cx);
1863
1864 if &pane == self.dock_pane() {
1865 Dock::show(self, cx);
1866 } else {
1867 self.last_active_center_pane = Some(pane.downgrade());
1868 if self.dock.is_anchored_at(DockAnchor::Expanded) {
1869 Dock::hide(self, cx);
1870 }
1871 }
1872 cx.notify();
1873 }
1874
1875 self.update_followers(
1876 proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1877 id: self.active_item(cx).map(|item| item.id() as u64),
1878 leader_id: self.leader_for_pane(&pane).map(|id| id.0),
1879 }),
1880 cx,
1881 );
1882 }
1883
1884 fn handle_pane_event(
1885 &mut self,
1886 pane_id: usize,
1887 event: &pane::Event,
1888 cx: &mut ViewContext<Self>,
1889 ) {
1890 if let Some(pane) = self.pane(pane_id) {
1891 let is_dock = &pane == self.dock.pane();
1892 match event {
1893 pane::Event::Split(direction) if !is_dock => {
1894 self.split_pane(pane, *direction, cx);
1895 }
1896 pane::Event::Remove if !is_dock => self.remove_pane(pane, cx),
1897 pane::Event::Remove if is_dock => Dock::hide(self, cx),
1898 pane::Event::ActivateItem { local } => {
1899 if *local {
1900 self.unfollow(&pane, cx);
1901 }
1902 if &pane == self.active_pane() {
1903 self.active_item_path_changed(cx);
1904 }
1905 }
1906 pane::Event::ChangeItemTitle => {
1907 if pane == self.active_pane {
1908 self.active_item_path_changed(cx);
1909 }
1910 self.update_window_edited(cx);
1911 }
1912 pane::Event::RemoveItem { item_id } => {
1913 self.update_window_edited(cx);
1914 if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
1915 if entry.get().id() == pane.id() {
1916 entry.remove();
1917 }
1918 }
1919 }
1920 _ => {}
1921 }
1922 } else if self.dock.visible_pane().is_none() {
1923 error!("pane {} not found", pane_id);
1924 }
1925 }
1926
1927 pub fn split_pane(
1928 &mut self,
1929 pane: ViewHandle<Pane>,
1930 direction: SplitDirection,
1931 cx: &mut ViewContext<Self>,
1932 ) -> Option<ViewHandle<Pane>> {
1933 if &pane == self.dock_pane() {
1934 warn!("Can't split dock pane.");
1935 return None;
1936 }
1937
1938 pane.read(cx).active_item().map(|item| {
1939 let new_pane = self.add_pane(cx);
1940 if let Some(clone) = item.clone_on_split(cx.as_mut()) {
1941 Pane::add_item(self, &new_pane, clone, true, true, None, cx);
1942 }
1943 self.center.split(&pane, &new_pane, direction).unwrap();
1944 cx.notify();
1945 new_pane
1946 })
1947 }
1948
1949 fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1950 if self.center.remove(&pane).unwrap() {
1951 self.panes.retain(|p| p != &pane);
1952 cx.focus(self.panes.last().unwrap().clone());
1953 self.unfollow(&pane, cx);
1954 self.last_leaders_by_pane.remove(&pane.downgrade());
1955 for removed_item in pane.read(cx).items() {
1956 self.panes_by_item.remove(&removed_item.id());
1957 }
1958 if self.last_active_center_pane == Some(pane.downgrade()) {
1959 self.last_active_center_pane = None;
1960 }
1961
1962 cx.notify();
1963 } else {
1964 self.active_item_path_changed(cx);
1965 }
1966 }
1967
1968 pub fn panes(&self) -> &[ViewHandle<Pane>] {
1969 &self.panes
1970 }
1971
1972 fn pane(&self, pane_id: usize) -> Option<ViewHandle<Pane>> {
1973 self.panes.iter().find(|pane| pane.id() == pane_id).cloned()
1974 }
1975
1976 pub fn active_pane(&self) -> &ViewHandle<Pane> {
1977 &self.active_pane
1978 }
1979
1980 pub fn dock_pane(&self) -> &ViewHandle<Pane> {
1981 self.dock.pane()
1982 }
1983
1984 fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1985 if let Some(remote_id) = remote_id {
1986 self.remote_entity_subscription =
1987 Some(self.client.add_view_for_remote_entity(remote_id, cx));
1988 } else {
1989 self.remote_entity_subscription.take();
1990 }
1991 }
1992
1993 fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1994 self.leader_state.followers.remove(&peer_id);
1995 if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1996 for state in states_by_pane.into_values() {
1997 for item in state.items_by_leader_view_id.into_values() {
1998 if let FollowerItem::Loaded(item) = item {
1999 item.set_leader_replica_id(None, cx);
2000 }
2001 }
2002 }
2003 }
2004 cx.notify();
2005 }
2006
2007 pub fn toggle_follow(
2008 &mut self,
2009 ToggleFollow(leader_id): &ToggleFollow,
2010 cx: &mut ViewContext<Self>,
2011 ) -> Option<Task<Result<()>>> {
2012 let leader_id = *leader_id;
2013 let pane = self.active_pane().clone();
2014
2015 if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
2016 if leader_id == prev_leader_id {
2017 return None;
2018 }
2019 }
2020
2021 self.last_leaders_by_pane
2022 .insert(pane.downgrade(), leader_id);
2023 self.follower_states_by_leader
2024 .entry(leader_id)
2025 .or_default()
2026 .insert(pane.clone(), Default::default());
2027 cx.notify();
2028
2029 let project_id = self.project.read(cx).remote_id()?;
2030 let request = self.client.request(proto::Follow {
2031 project_id,
2032 leader_id: leader_id.0,
2033 });
2034 Some(cx.spawn_weak(|this, mut cx| async move {
2035 let response = request.await?;
2036 if let Some(this) = this.upgrade(&cx) {
2037 this.update(&mut cx, |this, _| {
2038 let state = this
2039 .follower_states_by_leader
2040 .get_mut(&leader_id)
2041 .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
2042 .ok_or_else(|| anyhow!("following interrupted"))?;
2043 state.active_view_id = response.active_view_id;
2044 Ok::<_, anyhow::Error>(())
2045 })?;
2046 Self::add_views_from_leader(this, leader_id, vec![pane], response.views, &mut cx)
2047 .await?;
2048 }
2049 Ok(())
2050 }))
2051 }
2052
2053 pub fn follow_next_collaborator(
2054 &mut self,
2055 _: &FollowNextCollaborator,
2056 cx: &mut ViewContext<Self>,
2057 ) -> Option<Task<Result<()>>> {
2058 let collaborators = self.project.read(cx).collaborators();
2059 let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2060 let mut collaborators = collaborators.keys().copied();
2061 for peer_id in collaborators.by_ref() {
2062 if peer_id == leader_id {
2063 break;
2064 }
2065 }
2066 collaborators.next()
2067 } else if let Some(last_leader_id) =
2068 self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2069 {
2070 if collaborators.contains_key(last_leader_id) {
2071 Some(*last_leader_id)
2072 } else {
2073 None
2074 }
2075 } else {
2076 None
2077 };
2078
2079 next_leader_id
2080 .or_else(|| collaborators.keys().copied().next())
2081 .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
2082 }
2083
2084 pub fn unfollow(
2085 &mut self,
2086 pane: &ViewHandle<Pane>,
2087 cx: &mut ViewContext<Self>,
2088 ) -> Option<PeerId> {
2089 for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2090 let leader_id = *leader_id;
2091 if let Some(state) = states_by_pane.remove(pane) {
2092 for (_, item) in state.items_by_leader_view_id {
2093 if let FollowerItem::Loaded(item) = item {
2094 item.set_leader_replica_id(None, cx);
2095 }
2096 }
2097
2098 if states_by_pane.is_empty() {
2099 self.follower_states_by_leader.remove(&leader_id);
2100 if let Some(project_id) = self.project.read(cx).remote_id() {
2101 self.client
2102 .send(proto::Unfollow {
2103 project_id,
2104 leader_id: leader_id.0,
2105 })
2106 .log_err();
2107 }
2108 }
2109
2110 cx.notify();
2111 return Some(leader_id);
2112 }
2113 }
2114 None
2115 }
2116
2117 pub fn is_following(&self, peer_id: PeerId) -> bool {
2118 self.follower_states_by_leader.contains_key(&peer_id)
2119 }
2120
2121 fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
2122 let project = &self.project.read(cx);
2123 let mut worktree_root_names = String::new();
2124 for (i, name) in project.worktree_root_names(cx).enumerate() {
2125 if i > 0 {
2126 worktree_root_names.push_str(", ");
2127 }
2128 worktree_root_names.push_str(name);
2129 }
2130
2131 // TODO: There should be a better system in place for this
2132 // (https://github.com/zed-industries/zed/issues/1290)
2133 let is_fullscreen = cx.window_is_fullscreen(cx.window_id());
2134 let container_theme = if is_fullscreen {
2135 let mut container_theme = theme.workspace.titlebar.container;
2136 container_theme.padding.left = container_theme.padding.right;
2137 container_theme
2138 } else {
2139 theme.workspace.titlebar.container
2140 };
2141
2142 enum TitleBar {}
2143 ConstrainedBox::new(
2144 MouseEventHandler::<TitleBar>::new(0, cx, |_, cx| {
2145 Container::new(
2146 Stack::new()
2147 .with_child(
2148 Label::new(worktree_root_names, theme.workspace.titlebar.title.clone())
2149 .aligned()
2150 .left()
2151 .boxed(),
2152 )
2153 .with_children(
2154 self.titlebar_item
2155 .as_ref()
2156 .map(|item| ChildView::new(item, cx).aligned().right().boxed()),
2157 )
2158 .boxed(),
2159 )
2160 .with_style(container_theme)
2161 .boxed()
2162 })
2163 .on_click(MouseButton::Left, |event, cx| {
2164 if event.click_count == 2 {
2165 cx.zoom_window(cx.window_id());
2166 }
2167 })
2168 .boxed(),
2169 )
2170 .with_height(theme.workspace.titlebar.height)
2171 .named("titlebar")
2172 }
2173
2174 fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2175 let active_entry = self.active_project_path(cx);
2176 self.project
2177 .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2178 self.update_window_title(cx);
2179 }
2180
2181 fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2182 let mut title = String::new();
2183 let project = self.project().read(cx);
2184 if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2185 let filename = path
2186 .path
2187 .file_name()
2188 .map(|s| s.to_string_lossy())
2189 .or_else(|| {
2190 Some(Cow::Borrowed(
2191 project
2192 .worktree_for_id(path.worktree_id, cx)?
2193 .read(cx)
2194 .root_name(),
2195 ))
2196 });
2197 if let Some(filename) = filename {
2198 title.push_str(filename.as_ref());
2199 title.push_str(" — ");
2200 }
2201 }
2202 for (i, name) in project.worktree_root_names(cx).enumerate() {
2203 if i > 0 {
2204 title.push_str(", ");
2205 }
2206 title.push_str(name);
2207 }
2208 if title.is_empty() {
2209 title = "empty project".to_string();
2210 }
2211 cx.set_window_title(&title);
2212 }
2213
2214 fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2215 let is_edited = !self.project.read(cx).is_read_only()
2216 && self
2217 .items(cx)
2218 .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2219 if is_edited != self.window_edited {
2220 self.window_edited = is_edited;
2221 cx.set_window_edited(self.window_edited)
2222 }
2223 }
2224
2225 fn render_disconnected_overlay(&self, cx: &mut RenderContext<Workspace>) -> Option<ElementBox> {
2226 if self.project.read(cx).is_read_only() {
2227 enum DisconnectedOverlay {}
2228 Some(
2229 MouseEventHandler::<DisconnectedOverlay>::new(0, cx, |_, cx| {
2230 let theme = &cx.global::<Settings>().theme;
2231 Label::new(
2232 "Your connection to the remote project has been lost.".to_string(),
2233 theme.workspace.disconnected_overlay.text.clone(),
2234 )
2235 .aligned()
2236 .contained()
2237 .with_style(theme.workspace.disconnected_overlay.container)
2238 .boxed()
2239 })
2240 .with_cursor_style(CursorStyle::Arrow)
2241 .capture_all()
2242 .boxed(),
2243 )
2244 } else {
2245 None
2246 }
2247 }
2248
2249 fn render_notifications(
2250 &self,
2251 theme: &theme::Workspace,
2252 cx: &AppContext,
2253 ) -> Option<ElementBox> {
2254 if self.notifications.is_empty() {
2255 None
2256 } else {
2257 Some(
2258 Flex::column()
2259 .with_children(self.notifications.iter().map(|(_, _, notification)| {
2260 ChildView::new(notification.as_ref(), cx)
2261 .contained()
2262 .with_style(theme.notification)
2263 .boxed()
2264 }))
2265 .constrained()
2266 .with_width(theme.notifications.width)
2267 .contained()
2268 .with_style(theme.notifications.container)
2269 .aligned()
2270 .bottom()
2271 .right()
2272 .boxed(),
2273 )
2274 }
2275 }
2276
2277 // RPC handlers
2278
2279 async fn handle_follow(
2280 this: ViewHandle<Self>,
2281 envelope: TypedEnvelope<proto::Follow>,
2282 _: Arc<Client>,
2283 mut cx: AsyncAppContext,
2284 ) -> Result<proto::FollowResponse> {
2285 this.update(&mut cx, |this, cx| {
2286 this.leader_state
2287 .followers
2288 .insert(envelope.original_sender_id()?);
2289
2290 let active_view_id = this
2291 .active_item(cx)
2292 .and_then(|i| i.to_followable_item_handle(cx))
2293 .map(|i| i.id() as u64);
2294 Ok(proto::FollowResponse {
2295 active_view_id,
2296 views: this
2297 .panes()
2298 .iter()
2299 .flat_map(|pane| {
2300 let leader_id = this.leader_for_pane(pane).map(|id| id.0);
2301 pane.read(cx).items().filter_map({
2302 let cx = &cx;
2303 move |item| {
2304 let id = item.id() as u64;
2305 let item = item.to_followable_item_handle(cx)?;
2306 let variant = item.to_state_proto(cx)?;
2307 Some(proto::View {
2308 id,
2309 leader_id,
2310 variant: Some(variant),
2311 })
2312 }
2313 })
2314 })
2315 .collect(),
2316 })
2317 })
2318 }
2319
2320 async fn handle_unfollow(
2321 this: ViewHandle<Self>,
2322 envelope: TypedEnvelope<proto::Unfollow>,
2323 _: Arc<Client>,
2324 mut cx: AsyncAppContext,
2325 ) -> Result<()> {
2326 this.update(&mut cx, |this, _| {
2327 this.leader_state
2328 .followers
2329 .remove(&envelope.original_sender_id()?);
2330 Ok(())
2331 })
2332 }
2333
2334 async fn handle_update_followers(
2335 this: ViewHandle<Self>,
2336 envelope: TypedEnvelope<proto::UpdateFollowers>,
2337 _: Arc<Client>,
2338 mut cx: AsyncAppContext,
2339 ) -> Result<()> {
2340 let leader_id = envelope.original_sender_id()?;
2341 match envelope
2342 .payload
2343 .variant
2344 .ok_or_else(|| anyhow!("invalid update"))?
2345 {
2346 proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2347 this.update(&mut cx, |this, cx| {
2348 this.update_leader_state(leader_id, cx, |state, _| {
2349 state.active_view_id = update_active_view.id;
2350 });
2351 Ok::<_, anyhow::Error>(())
2352 })
2353 }
2354 proto::update_followers::Variant::UpdateView(update_view) => {
2355 this.update(&mut cx, |this, cx| {
2356 let variant = update_view
2357 .variant
2358 .ok_or_else(|| anyhow!("missing update view variant"))?;
2359 this.update_leader_state(leader_id, cx, |state, cx| {
2360 let variant = variant.clone();
2361 match state
2362 .items_by_leader_view_id
2363 .entry(update_view.id)
2364 .or_insert(FollowerItem::Loading(Vec::new()))
2365 {
2366 FollowerItem::Loaded(item) => {
2367 item.apply_update_proto(variant, cx).log_err();
2368 }
2369 FollowerItem::Loading(updates) => updates.push(variant),
2370 }
2371 });
2372 Ok(())
2373 })
2374 }
2375 proto::update_followers::Variant::CreateView(view) => {
2376 let panes = this.read_with(&cx, |this, _| {
2377 this.follower_states_by_leader
2378 .get(&leader_id)
2379 .into_iter()
2380 .flat_map(|states_by_pane| states_by_pane.keys())
2381 .cloned()
2382 .collect()
2383 });
2384 Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], &mut cx)
2385 .await?;
2386 Ok(())
2387 }
2388 }
2389 .log_err();
2390
2391 Ok(())
2392 }
2393
2394 async fn add_views_from_leader(
2395 this: ViewHandle<Self>,
2396 leader_id: PeerId,
2397 panes: Vec<ViewHandle<Pane>>,
2398 views: Vec<proto::View>,
2399 cx: &mut AsyncAppContext,
2400 ) -> Result<()> {
2401 let project = this.read_with(cx, |this, _| this.project.clone());
2402 let replica_id = project
2403 .read_with(cx, |project, _| {
2404 project
2405 .collaborators()
2406 .get(&leader_id)
2407 .map(|c| c.replica_id)
2408 })
2409 .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2410
2411 let item_builders = cx.update(|cx| {
2412 cx.default_global::<FollowableItemBuilders>()
2413 .values()
2414 .map(|b| b.0)
2415 .collect::<Vec<_>>()
2416 });
2417
2418 let mut item_tasks_by_pane = HashMap::default();
2419 for pane in panes {
2420 let mut item_tasks = Vec::new();
2421 let mut leader_view_ids = Vec::new();
2422 for view in &views {
2423 let mut variant = view.variant.clone();
2424 if variant.is_none() {
2425 Err(anyhow!("missing variant"))?;
2426 }
2427 for build_item in &item_builders {
2428 let task =
2429 cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx));
2430 if let Some(task) = task {
2431 item_tasks.push(task);
2432 leader_view_ids.push(view.id);
2433 break;
2434 } else {
2435 assert!(variant.is_some());
2436 }
2437 }
2438 }
2439
2440 item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2441 }
2442
2443 for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2444 let items = futures::future::try_join_all(item_tasks).await?;
2445 this.update(cx, |this, cx| {
2446 let state = this
2447 .follower_states_by_leader
2448 .get_mut(&leader_id)?
2449 .get_mut(&pane)?;
2450
2451 for (id, item) in leader_view_ids.into_iter().zip(items) {
2452 item.set_leader_replica_id(Some(replica_id), cx);
2453 match state.items_by_leader_view_id.entry(id) {
2454 hash_map::Entry::Occupied(e) => {
2455 let e = e.into_mut();
2456 if let FollowerItem::Loading(updates) = e {
2457 for update in updates.drain(..) {
2458 item.apply_update_proto(update, cx)
2459 .context("failed to apply view update")
2460 .log_err();
2461 }
2462 }
2463 *e = FollowerItem::Loaded(item);
2464 }
2465 hash_map::Entry::Vacant(e) => {
2466 e.insert(FollowerItem::Loaded(item));
2467 }
2468 }
2469 }
2470
2471 Some(())
2472 });
2473 }
2474 this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
2475
2476 Ok(())
2477 }
2478
2479 fn update_followers(
2480 &self,
2481 update: proto::update_followers::Variant,
2482 cx: &AppContext,
2483 ) -> Option<()> {
2484 let project_id = self.project.read(cx).remote_id()?;
2485 if !self.leader_state.followers.is_empty() {
2486 self.client
2487 .send(proto::UpdateFollowers {
2488 project_id,
2489 follower_ids: self.leader_state.followers.iter().map(|f| f.0).collect(),
2490 variant: Some(update),
2491 })
2492 .log_err();
2493 }
2494 None
2495 }
2496
2497 pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2498 self.follower_states_by_leader
2499 .iter()
2500 .find_map(|(leader_id, state)| {
2501 if state.contains_key(pane) {
2502 Some(*leader_id)
2503 } else {
2504 None
2505 }
2506 })
2507 }
2508
2509 fn update_leader_state(
2510 &mut self,
2511 leader_id: PeerId,
2512 cx: &mut ViewContext<Self>,
2513 mut update_fn: impl FnMut(&mut FollowerState, &mut ViewContext<Self>),
2514 ) {
2515 for (_, state) in self
2516 .follower_states_by_leader
2517 .get_mut(&leader_id)
2518 .into_iter()
2519 .flatten()
2520 {
2521 update_fn(state, cx);
2522 }
2523 self.leader_updated(leader_id, cx);
2524 }
2525
2526 fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2527 let mut items_to_add = Vec::new();
2528 for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2529 if let Some(FollowerItem::Loaded(item)) = state
2530 .active_view_id
2531 .and_then(|id| state.items_by_leader_view_id.get(&id))
2532 {
2533 items_to_add.push((pane.clone(), item.boxed_clone()));
2534 }
2535 }
2536
2537 for (pane, item) in items_to_add {
2538 Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
2539 if pane == self.active_pane {
2540 pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2541 }
2542 cx.notify();
2543 }
2544 None
2545 }
2546
2547 pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2548 if !active {
2549 for pane in &self.panes {
2550 pane.update(cx, |pane, cx| {
2551 if let Some(item) = pane.active_item() {
2552 item.workspace_deactivated(cx);
2553 }
2554 if matches!(
2555 cx.global::<Settings>().autosave,
2556 Autosave::OnWindowChange | Autosave::OnFocusChange
2557 ) {
2558 for item in pane.items() {
2559 Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2560 .detach_and_log_err(cx);
2561 }
2562 }
2563 });
2564 }
2565 }
2566 }
2567}
2568
2569impl Entity for Workspace {
2570 type Event = Event;
2571}
2572
2573impl View for Workspace {
2574 fn ui_name() -> &'static str {
2575 "Workspace"
2576 }
2577
2578 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
2579 let theme = cx.global::<Settings>().theme.clone();
2580 Stack::new()
2581 .with_child(
2582 Flex::column()
2583 .with_child(self.render_titlebar(&theme, cx))
2584 .with_child(
2585 Stack::new()
2586 .with_child({
2587 let project = self.project.clone();
2588 Flex::row()
2589 .with_children(
2590 if self.left_sidebar.read(cx).active_item().is_some() {
2591 Some(
2592 ChildView::new(&self.left_sidebar, cx)
2593 .flex(0.8, false)
2594 .boxed(),
2595 )
2596 } else {
2597 None
2598 },
2599 )
2600 .with_child(
2601 FlexItem::new(
2602 Flex::column()
2603 .with_child(
2604 FlexItem::new(self.center.render(
2605 &project,
2606 &theme,
2607 &self.follower_states_by_leader,
2608 self.active_call.as_ref(),
2609 cx,
2610 ))
2611 .flex(1., true)
2612 .boxed(),
2613 )
2614 .with_children(self.dock.render(
2615 &theme,
2616 DockAnchor::Bottom,
2617 cx,
2618 ))
2619 .boxed(),
2620 )
2621 .flex(1., true)
2622 .boxed(),
2623 )
2624 .with_children(self.dock.render(&theme, DockAnchor::Right, cx))
2625 .with_children(
2626 if self.right_sidebar.read(cx).active_item().is_some() {
2627 Some(
2628 ChildView::new(&self.right_sidebar, cx)
2629 .flex(0.8, false)
2630 .boxed(),
2631 )
2632 } else {
2633 None
2634 },
2635 )
2636 .boxed()
2637 })
2638 .with_child(
2639 Overlay::new(
2640 Stack::new()
2641 .with_children(self.dock.render(
2642 &theme,
2643 DockAnchor::Expanded,
2644 cx,
2645 ))
2646 .with_children(self.modal.as_ref().map(|modal| {
2647 ChildView::new(modal, cx)
2648 .contained()
2649 .with_style(theme.workspace.modal)
2650 .aligned()
2651 .top()
2652 .boxed()
2653 }))
2654 .with_children(
2655 self.render_notifications(&theme.workspace, cx),
2656 )
2657 .boxed(),
2658 )
2659 .boxed(),
2660 )
2661 .flex(1.0, true)
2662 .boxed(),
2663 )
2664 .with_child(ChildView::new(&self.status_bar, cx).boxed())
2665 .contained()
2666 .with_background_color(theme.workspace.background)
2667 .boxed(),
2668 )
2669 .with_children(DragAndDrop::render(cx))
2670 .with_children(self.render_disconnected_overlay(cx))
2671 .named("workspace")
2672 }
2673
2674 fn on_focus_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext<Self>) {
2675 if cx.is_self_focused() {
2676 cx.focus(&self.active_pane);
2677 } else {
2678 for pane in self.panes() {
2679 let view = view.clone();
2680 if pane.update(cx, |_, cx| cx.is_child(view)) {
2681 self.handle_pane_focused(pane.clone(), cx);
2682 break;
2683 }
2684 }
2685 }
2686 }
2687
2688 fn keymap_context(&self, _: &AppContext) -> gpui::keymap::Context {
2689 let mut keymap = Self::default_keymap_context();
2690 if self.active_pane() == self.dock_pane() {
2691 keymap.set.insert("Dock".into());
2692 }
2693 keymap
2694 }
2695}
2696
2697pub trait WorkspaceHandle {
2698 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2699}
2700
2701impl WorkspaceHandle for ViewHandle<Workspace> {
2702 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2703 self.read(cx)
2704 .worktrees(cx)
2705 .flat_map(|worktree| {
2706 let worktree_id = worktree.read(cx).id();
2707 worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2708 worktree_id,
2709 path: f.path.clone(),
2710 })
2711 })
2712 .collect::<Vec<_>>()
2713 }
2714}
2715
2716impl std::fmt::Debug for OpenPaths {
2717 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2718 f.debug_struct("OpenPaths")
2719 .field("paths", &self.paths)
2720 .finish()
2721 }
2722}
2723
2724fn open(_: &Open, cx: &mut MutableAppContext) {
2725 let mut paths = cx.prompt_for_paths(PathPromptOptions {
2726 files: true,
2727 directories: true,
2728 multiple: true,
2729 });
2730 cx.spawn(|mut cx| async move {
2731 if let Some(paths) = paths.recv().await.flatten() {
2732 cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
2733 }
2734 })
2735 .detach();
2736}
2737
2738pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
2739
2740pub fn activate_workspace_for_project(
2741 cx: &mut MutableAppContext,
2742 predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
2743) -> Option<ViewHandle<Workspace>> {
2744 for window_id in cx.window_ids().collect::<Vec<_>>() {
2745 if let Some(workspace_handle) = cx.root_view::<Workspace>(window_id) {
2746 let project = workspace_handle.read(cx).project.clone();
2747 if project.update(cx, &predicate) {
2748 cx.activate_window(window_id);
2749 return Some(workspace_handle);
2750 }
2751 }
2752 }
2753 None
2754}
2755
2756#[allow(clippy::type_complexity)]
2757pub fn open_paths(
2758 abs_paths: &[PathBuf],
2759 app_state: &Arc<AppState>,
2760 cx: &mut MutableAppContext,
2761) -> Task<(
2762 ViewHandle<Workspace>,
2763 Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
2764)> {
2765 log::info!("open paths {:?}", abs_paths);
2766
2767 // Open paths in existing workspace if possible
2768 let existing =
2769 activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
2770
2771 let app_state = app_state.clone();
2772 let abs_paths = abs_paths.to_vec();
2773 cx.spawn(|mut cx| async move {
2774 let mut new_project = None;
2775 let workspace = if let Some(existing) = existing {
2776 existing
2777 } else {
2778 let contains_directory =
2779 futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
2780 .await
2781 .contains(&false);
2782
2783 cx.add_window((app_state.build_window_options)(), |cx| {
2784 let project = Project::local(
2785 app_state.client.clone(),
2786 app_state.user_store.clone(),
2787 app_state.project_store.clone(),
2788 app_state.languages.clone(),
2789 app_state.fs.clone(),
2790 cx,
2791 );
2792 new_project = Some(project.clone());
2793 let mut workspace = Workspace::new(project, app_state.default_item_factory, cx);
2794 (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
2795 if contains_directory {
2796 workspace.toggle_sidebar(SidebarSide::Left, cx);
2797 }
2798 workspace
2799 })
2800 .1
2801 };
2802
2803 let items = workspace
2804 .update(&mut cx, |workspace, cx| {
2805 workspace.open_paths(abs_paths, true, cx)
2806 })
2807 .await;
2808
2809 (workspace, items)
2810 })
2811}
2812
2813fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
2814 let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
2815 let mut workspace = Workspace::new(
2816 Project::local(
2817 app_state.client.clone(),
2818 app_state.user_store.clone(),
2819 app_state.project_store.clone(),
2820 app_state.languages.clone(),
2821 app_state.fs.clone(),
2822 cx,
2823 ),
2824 app_state.default_item_factory,
2825 cx,
2826 );
2827 (app_state.initialize_workspace)(&mut workspace, app_state, cx);
2828 workspace
2829 });
2830 cx.dispatch_action_at(window_id, workspace.id(), NewFile);
2831}
2832
2833#[cfg(test)]
2834mod tests {
2835 use std::cell::Cell;
2836
2837 use crate::sidebar::SidebarItem;
2838
2839 use super::*;
2840 use fs::FakeFs;
2841 use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
2842 use project::{Project, ProjectEntryId};
2843 use serde_json::json;
2844
2845 pub fn default_item_factory(
2846 _workspace: &mut Workspace,
2847 _cx: &mut ViewContext<Workspace>,
2848 ) -> Box<dyn ItemHandle> {
2849 unimplemented!();
2850 }
2851
2852 #[gpui::test]
2853 async fn test_tab_disambiguation(cx: &mut TestAppContext) {
2854 cx.foreground().forbid_parking();
2855 Settings::test_async(cx);
2856
2857 let fs = FakeFs::new(cx.background());
2858 let project = Project::test(fs, [], cx).await;
2859 let (_, workspace) =
2860 cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
2861
2862 // Adding an item with no ambiguity renders the tab without detail.
2863 let item1 = cx.add_view(&workspace, |_| {
2864 let mut item = TestItem::new();
2865 item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
2866 item
2867 });
2868 workspace.update(cx, |workspace, cx| {
2869 workspace.add_item(Box::new(item1.clone()), cx);
2870 });
2871 item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
2872
2873 // Adding an item that creates ambiguity increases the level of detail on
2874 // both tabs.
2875 let item2 = cx.add_view(&workspace, |_| {
2876 let mut item = TestItem::new();
2877 item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
2878 item
2879 });
2880 workspace.update(cx, |workspace, cx| {
2881 workspace.add_item(Box::new(item2.clone()), cx);
2882 });
2883 item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2884 item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2885
2886 // Adding an item that creates ambiguity increases the level of detail only
2887 // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
2888 // we stop at the highest detail available.
2889 let item3 = cx.add_view(&workspace, |_| {
2890 let mut item = TestItem::new();
2891 item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
2892 item
2893 });
2894 workspace.update(cx, |workspace, cx| {
2895 workspace.add_item(Box::new(item3.clone()), cx);
2896 });
2897 item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
2898 item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
2899 item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
2900 }
2901
2902 #[gpui::test]
2903 async fn test_tracking_active_path(cx: &mut TestAppContext) {
2904 cx.foreground().forbid_parking();
2905 Settings::test_async(cx);
2906 let fs = FakeFs::new(cx.background());
2907 fs.insert_tree(
2908 "/root1",
2909 json!({
2910 "one.txt": "",
2911 "two.txt": "",
2912 }),
2913 )
2914 .await;
2915 fs.insert_tree(
2916 "/root2",
2917 json!({
2918 "three.txt": "",
2919 }),
2920 )
2921 .await;
2922
2923 let project = Project::test(fs, ["root1".as_ref()], cx).await;
2924 let (window_id, workspace) =
2925 cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
2926 let worktree_id = project.read_with(cx, |project, cx| {
2927 project.worktrees(cx).next().unwrap().read(cx).id()
2928 });
2929
2930 let item1 = cx.add_view(&workspace, |_| {
2931 let mut item = TestItem::new();
2932 item.project_path = Some((worktree_id, "one.txt").into());
2933 item
2934 });
2935 let item2 = cx.add_view(&workspace, |_| {
2936 let mut item = TestItem::new();
2937 item.project_path = Some((worktree_id, "two.txt").into());
2938 item
2939 });
2940
2941 // Add an item to an empty pane
2942 workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
2943 project.read_with(cx, |project, cx| {
2944 assert_eq!(
2945 project.active_entry(),
2946 project
2947 .entry_for_path(&(worktree_id, "one.txt").into(), cx)
2948 .map(|e| e.id)
2949 );
2950 });
2951 assert_eq!(
2952 cx.current_window_title(window_id).as_deref(),
2953 Some("one.txt — root1")
2954 );
2955
2956 // Add a second item to a non-empty pane
2957 workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
2958 assert_eq!(
2959 cx.current_window_title(window_id).as_deref(),
2960 Some("two.txt — root1")
2961 );
2962 project.read_with(cx, |project, cx| {
2963 assert_eq!(
2964 project.active_entry(),
2965 project
2966 .entry_for_path(&(worktree_id, "two.txt").into(), cx)
2967 .map(|e| e.id)
2968 );
2969 });
2970
2971 // Close the active item
2972 workspace
2973 .update(cx, |workspace, cx| {
2974 Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
2975 })
2976 .await
2977 .unwrap();
2978 assert_eq!(
2979 cx.current_window_title(window_id).as_deref(),
2980 Some("one.txt — root1")
2981 );
2982 project.read_with(cx, |project, cx| {
2983 assert_eq!(
2984 project.active_entry(),
2985 project
2986 .entry_for_path(&(worktree_id, "one.txt").into(), cx)
2987 .map(|e| e.id)
2988 );
2989 });
2990
2991 // Add a project folder
2992 project
2993 .update(cx, |project, cx| {
2994 project.find_or_create_local_worktree("/root2", true, cx)
2995 })
2996 .await
2997 .unwrap();
2998 assert_eq!(
2999 cx.current_window_title(window_id).as_deref(),
3000 Some("one.txt — root1, root2")
3001 );
3002
3003 // Remove a project folder
3004 project.update(cx, |project, cx| {
3005 project.remove_worktree(worktree_id, cx);
3006 });
3007 assert_eq!(
3008 cx.current_window_title(window_id).as_deref(),
3009 Some("one.txt — root2")
3010 );
3011 }
3012
3013 #[gpui::test]
3014 async fn test_close_window(cx: &mut TestAppContext) {
3015 cx.foreground().forbid_parking();
3016 Settings::test_async(cx);
3017 let fs = FakeFs::new(cx.background());
3018 fs.insert_tree("/root", json!({ "one": "" })).await;
3019
3020 let project = Project::test(fs, ["root".as_ref()], cx).await;
3021 let (window_id, workspace) =
3022 cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx));
3023
3024 // When there are no dirty items, there's nothing to do.
3025 let item1 = cx.add_view(&workspace, |_| TestItem::new());
3026 workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3027 let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3028 assert!(task.await.unwrap());
3029
3030 // When there are dirty untitled items, prompt to save each one. If the user
3031 // cancels any prompt, then abort.
3032 let item2 = cx.add_view(&workspace, |_| {
3033 let mut item = TestItem::new();
3034 item.is_dirty = true;
3035 item
3036 });
3037 let item3 = cx.add_view(&workspace, |_| {
3038 let mut item = TestItem::new();
3039 item.is_dirty = true;
3040 item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3041 item
3042 });
3043 workspace.update(cx, |w, cx| {
3044 w.add_item(Box::new(item2.clone()), cx);
3045 w.add_item(Box::new(item3.clone()), cx);
3046 });
3047 let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3048 cx.foreground().run_until_parked();
3049 cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3050 cx.foreground().run_until_parked();
3051 assert!(!cx.has_pending_prompt(window_id));
3052 assert!(!task.await.unwrap());
3053 }
3054
3055 #[gpui::test]
3056 async fn test_close_pane_items(cx: &mut TestAppContext) {
3057 cx.foreground().forbid_parking();
3058 Settings::test_async(cx);
3059 let fs = FakeFs::new(cx.background());
3060
3061 let project = Project::test(fs, None, cx).await;
3062 let (window_id, workspace) =
3063 cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3064
3065 let item1 = cx.add_view(&workspace, |_| {
3066 let mut item = TestItem::new();
3067 item.is_dirty = true;
3068 item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3069 item
3070 });
3071 let item2 = cx.add_view(&workspace, |_| {
3072 let mut item = TestItem::new();
3073 item.is_dirty = true;
3074 item.has_conflict = true;
3075 item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
3076 item
3077 });
3078 let item3 = cx.add_view(&workspace, |_| {
3079 let mut item = TestItem::new();
3080 item.is_dirty = true;
3081 item.has_conflict = true;
3082 item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
3083 item
3084 });
3085 let item4 = cx.add_view(&workspace, |_| {
3086 let mut item = TestItem::new();
3087 item.is_dirty = true;
3088 item
3089 });
3090 let pane = workspace.update(cx, |workspace, cx| {
3091 workspace.add_item(Box::new(item1.clone()), cx);
3092 workspace.add_item(Box::new(item2.clone()), cx);
3093 workspace.add_item(Box::new(item3.clone()), cx);
3094 workspace.add_item(Box::new(item4.clone()), cx);
3095 workspace.active_pane().clone()
3096 });
3097
3098 let close_items = workspace.update(cx, |workspace, cx| {
3099 pane.update(cx, |pane, cx| {
3100 pane.activate_item(1, true, true, cx);
3101 assert_eq!(pane.active_item().unwrap().id(), item2.id());
3102 });
3103
3104 let item1_id = item1.id();
3105 let item3_id = item3.id();
3106 let item4_id = item4.id();
3107 Pane::close_items(workspace, pane.clone(), cx, move |id| {
3108 [item1_id, item3_id, item4_id].contains(&id)
3109 })
3110 });
3111
3112 cx.foreground().run_until_parked();
3113 pane.read_with(cx, |pane, _| {
3114 assert_eq!(pane.items().count(), 4);
3115 assert_eq!(pane.active_item().unwrap().id(), item1.id());
3116 });
3117
3118 cx.simulate_prompt_answer(window_id, 0);
3119 cx.foreground().run_until_parked();
3120 pane.read_with(cx, |pane, cx| {
3121 assert_eq!(item1.read(cx).save_count, 1);
3122 assert_eq!(item1.read(cx).save_as_count, 0);
3123 assert_eq!(item1.read(cx).reload_count, 0);
3124 assert_eq!(pane.items().count(), 3);
3125 assert_eq!(pane.active_item().unwrap().id(), item3.id());
3126 });
3127
3128 cx.simulate_prompt_answer(window_id, 1);
3129 cx.foreground().run_until_parked();
3130 pane.read_with(cx, |pane, cx| {
3131 assert_eq!(item3.read(cx).save_count, 0);
3132 assert_eq!(item3.read(cx).save_as_count, 0);
3133 assert_eq!(item3.read(cx).reload_count, 1);
3134 assert_eq!(pane.items().count(), 2);
3135 assert_eq!(pane.active_item().unwrap().id(), item4.id());
3136 });
3137
3138 cx.simulate_prompt_answer(window_id, 0);
3139 cx.foreground().run_until_parked();
3140 cx.simulate_new_path_selection(|_| Some(Default::default()));
3141 close_items.await.unwrap();
3142 pane.read_with(cx, |pane, cx| {
3143 assert_eq!(item4.read(cx).save_count, 0);
3144 assert_eq!(item4.read(cx).save_as_count, 1);
3145 assert_eq!(item4.read(cx).reload_count, 0);
3146 assert_eq!(pane.items().count(), 1);
3147 assert_eq!(pane.active_item().unwrap().id(), item2.id());
3148 });
3149 }
3150
3151 #[gpui::test]
3152 async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3153 cx.foreground().forbid_parking();
3154 Settings::test_async(cx);
3155 let fs = FakeFs::new(cx.background());
3156
3157 let project = Project::test(fs, [], cx).await;
3158 let (window_id, workspace) =
3159 cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3160
3161 // Create several workspace items with single project entries, and two
3162 // workspace items with multiple project entries.
3163 let single_entry_items = (0..=4)
3164 .map(|project_entry_id| {
3165 let mut item = TestItem::new();
3166 item.is_dirty = true;
3167 item.project_entry_ids = vec![ProjectEntryId::from_proto(project_entry_id)];
3168 item.is_singleton = true;
3169 item
3170 })
3171 .collect::<Vec<_>>();
3172 let item_2_3 = {
3173 let mut item = TestItem::new();
3174 item.is_dirty = true;
3175 item.is_singleton = false;
3176 item.project_entry_ids =
3177 vec![ProjectEntryId::from_proto(2), ProjectEntryId::from_proto(3)];
3178 item
3179 };
3180 let item_3_4 = {
3181 let mut item = TestItem::new();
3182 item.is_dirty = true;
3183 item.is_singleton = false;
3184 item.project_entry_ids =
3185 vec![ProjectEntryId::from_proto(3), ProjectEntryId::from_proto(4)];
3186 item
3187 };
3188
3189 // Create two panes that contain the following project entries:
3190 // left pane:
3191 // multi-entry items: (2, 3)
3192 // single-entry items: 0, 1, 2, 3, 4
3193 // right pane:
3194 // single-entry items: 1
3195 // multi-entry items: (3, 4)
3196 let left_pane = workspace.update(cx, |workspace, cx| {
3197 let left_pane = workspace.active_pane().clone();
3198 workspace.add_item(Box::new(cx.add_view(|_| item_2_3.clone())), cx);
3199 for item in &single_entry_items {
3200 workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
3201 }
3202 left_pane.update(cx, |pane, cx| {
3203 pane.activate_item(2, true, true, cx);
3204 });
3205
3206 workspace
3207 .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3208 .unwrap();
3209
3210 left_pane
3211 });
3212
3213 //Need to cause an effect flush in order to respect new focus
3214 workspace.update(cx, |workspace, cx| {
3215 workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
3216 cx.focus(left_pane.clone());
3217 });
3218
3219 // When closing all of the items in the left pane, we should be prompted twice:
3220 // once for project entry 0, and once for project entry 2. After those two
3221 // prompts, the task should complete.
3222
3223 let close = workspace.update(cx, |workspace, cx| {
3224 Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3225 });
3226
3227 cx.foreground().run_until_parked();
3228 left_pane.read_with(cx, |pane, cx| {
3229 assert_eq!(
3230 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3231 &[ProjectEntryId::from_proto(0)]
3232 );
3233 });
3234 cx.simulate_prompt_answer(window_id, 0);
3235
3236 cx.foreground().run_until_parked();
3237 left_pane.read_with(cx, |pane, cx| {
3238 assert_eq!(
3239 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3240 &[ProjectEntryId::from_proto(2)]
3241 );
3242 });
3243 cx.simulate_prompt_answer(window_id, 0);
3244
3245 cx.foreground().run_until_parked();
3246 close.await.unwrap();
3247 left_pane.read_with(cx, |pane, _| {
3248 assert_eq!(pane.items().count(), 0);
3249 });
3250 }
3251
3252 #[gpui::test]
3253 async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3254 deterministic.forbid_parking();
3255
3256 Settings::test_async(cx);
3257 let fs = FakeFs::new(cx.background());
3258
3259 let project = Project::test(fs, [], cx).await;
3260 let (window_id, workspace) =
3261 cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3262
3263 let item = cx.add_view(&workspace, |_| {
3264 let mut item = TestItem::new();
3265 item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3266 item
3267 });
3268 let item_id = item.id();
3269 workspace.update(cx, |workspace, cx| {
3270 workspace.add_item(Box::new(item.clone()), cx);
3271 });
3272
3273 // Autosave on window change.
3274 item.update(cx, |item, cx| {
3275 cx.update_global(|settings: &mut Settings, _| {
3276 settings.autosave = Autosave::OnWindowChange;
3277 });
3278 item.is_dirty = true;
3279 });
3280
3281 // Deactivating the window saves the file.
3282 cx.simulate_window_activation(None);
3283 deterministic.run_until_parked();
3284 item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3285
3286 // Autosave on focus change.
3287 item.update(cx, |item, cx| {
3288 cx.focus_self();
3289 cx.update_global(|settings: &mut Settings, _| {
3290 settings.autosave = Autosave::OnFocusChange;
3291 });
3292 item.is_dirty = true;
3293 });
3294
3295 // Blurring the item saves the file.
3296 item.update(cx, |_, cx| cx.blur());
3297 deterministic.run_until_parked();
3298 item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3299
3300 // Deactivating the window still saves the file.
3301 cx.simulate_window_activation(Some(window_id));
3302 item.update(cx, |item, cx| {
3303 cx.focus_self();
3304 item.is_dirty = true;
3305 });
3306 cx.simulate_window_activation(None);
3307
3308 deterministic.run_until_parked();
3309 item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3310
3311 // Autosave after delay.
3312 item.update(cx, |item, cx| {
3313 cx.update_global(|settings: &mut Settings, _| {
3314 settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3315 });
3316 item.is_dirty = true;
3317 cx.emit(TestItemEvent::Edit);
3318 });
3319
3320 // Delay hasn't fully expired, so the file is still dirty and unsaved.
3321 deterministic.advance_clock(Duration::from_millis(250));
3322 item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3323
3324 // After delay expires, the file is saved.
3325 deterministic.advance_clock(Duration::from_millis(250));
3326 item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3327
3328 // Autosave on focus change, ensuring closing the tab counts as such.
3329 item.update(cx, |item, cx| {
3330 cx.update_global(|settings: &mut Settings, _| {
3331 settings.autosave = Autosave::OnFocusChange;
3332 });
3333 item.is_dirty = true;
3334 });
3335
3336 workspace
3337 .update(cx, |workspace, cx| {
3338 let pane = workspace.active_pane().clone();
3339 Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3340 })
3341 .await
3342 .unwrap();
3343 assert!(!cx.has_pending_prompt(window_id));
3344 item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3345
3346 // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3347 workspace.update(cx, |workspace, cx| {
3348 workspace.add_item(Box::new(item.clone()), cx);
3349 });
3350 item.update(cx, |item, cx| {
3351 item.project_entry_ids = Default::default();
3352 item.is_dirty = true;
3353 cx.blur();
3354 });
3355 deterministic.run_until_parked();
3356 item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3357
3358 // Ensure autosave is prevented for deleted files also when closing the buffer.
3359 let _close_items = workspace.update(cx, |workspace, cx| {
3360 let pane = workspace.active_pane().clone();
3361 Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3362 });
3363 deterministic.run_until_parked();
3364 assert!(cx.has_pending_prompt(window_id));
3365 item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3366 }
3367
3368 #[gpui::test]
3369 async fn test_pane_navigation(
3370 deterministic: Arc<Deterministic>,
3371 cx: &mut gpui::TestAppContext,
3372 ) {
3373 deterministic.forbid_parking();
3374 Settings::test_async(cx);
3375 let fs = FakeFs::new(cx.background());
3376
3377 let project = Project::test(fs, [], cx).await;
3378 let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx));
3379
3380 let item = cx.add_view(&workspace, |_| {
3381 let mut item = TestItem::new();
3382 item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
3383 item
3384 });
3385 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3386 let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3387 let toolbar_notify_count = Rc::new(RefCell::new(0));
3388
3389 workspace.update(cx, |workspace, cx| {
3390 workspace.add_item(Box::new(item.clone()), cx);
3391 let toolbar_notification_count = toolbar_notify_count.clone();
3392 cx.observe(&toolbar, move |_, _, _| {
3393 *toolbar_notification_count.borrow_mut() += 1
3394 })
3395 .detach();
3396 });
3397
3398 pane.read_with(cx, |pane, _| {
3399 assert!(!pane.can_navigate_backward());
3400 assert!(!pane.can_navigate_forward());
3401 });
3402
3403 item.update(cx, |item, cx| {
3404 item.set_state("one".to_string(), cx);
3405 });
3406
3407 // Toolbar must be notified to re-render the navigation buttons
3408 assert_eq!(*toolbar_notify_count.borrow(), 1);
3409
3410 pane.read_with(cx, |pane, _| {
3411 assert!(pane.can_navigate_backward());
3412 assert!(!pane.can_navigate_forward());
3413 });
3414
3415 workspace
3416 .update(cx, |workspace, cx| {
3417 Pane::go_back(workspace, Some(pane.clone()), cx)
3418 })
3419 .await;
3420
3421 assert_eq!(*toolbar_notify_count.borrow(), 3);
3422 pane.read_with(cx, |pane, _| {
3423 assert!(!pane.can_navigate_backward());
3424 assert!(pane.can_navigate_forward());
3425 });
3426 }
3427
3428 pub struct TestItem {
3429 state: String,
3430 pub label: String,
3431 save_count: usize,
3432 save_as_count: usize,
3433 reload_count: usize,
3434 is_dirty: bool,
3435 is_singleton: bool,
3436 has_conflict: bool,
3437 project_entry_ids: Vec<ProjectEntryId>,
3438 project_path: Option<ProjectPath>,
3439 nav_history: Option<ItemNavHistory>,
3440 tab_descriptions: Option<Vec<&'static str>>,
3441 tab_detail: Cell<Option<usize>>,
3442 }
3443
3444 pub enum TestItemEvent {
3445 Edit,
3446 }
3447
3448 impl Clone for TestItem {
3449 fn clone(&self) -> Self {
3450 Self {
3451 state: self.state.clone(),
3452 label: self.label.clone(),
3453 save_count: self.save_count,
3454 save_as_count: self.save_as_count,
3455 reload_count: self.reload_count,
3456 is_dirty: self.is_dirty,
3457 is_singleton: self.is_singleton,
3458 has_conflict: self.has_conflict,
3459 project_entry_ids: self.project_entry_ids.clone(),
3460 project_path: self.project_path.clone(),
3461 nav_history: None,
3462 tab_descriptions: None,
3463 tab_detail: Default::default(),
3464 }
3465 }
3466 }
3467
3468 impl TestItem {
3469 pub fn new() -> Self {
3470 Self {
3471 state: String::new(),
3472 label: String::new(),
3473 save_count: 0,
3474 save_as_count: 0,
3475 reload_count: 0,
3476 is_dirty: false,
3477 has_conflict: false,
3478 project_entry_ids: Vec::new(),
3479 project_path: None,
3480 is_singleton: true,
3481 nav_history: None,
3482 tab_descriptions: None,
3483 tab_detail: Default::default(),
3484 }
3485 }
3486
3487 pub fn with_label(mut self, state: &str) -> Self {
3488 self.label = state.to_string();
3489 self
3490 }
3491
3492 pub fn with_singleton(mut self, singleton: bool) -> Self {
3493 self.is_singleton = singleton;
3494 self
3495 }
3496
3497 pub fn with_project_entry_ids(mut self, project_entry_ids: &[u64]) -> Self {
3498 self.project_entry_ids.extend(
3499 project_entry_ids
3500 .iter()
3501 .copied()
3502 .map(ProjectEntryId::from_proto),
3503 );
3504 self
3505 }
3506
3507 fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
3508 self.push_to_nav_history(cx);
3509 self.state = state;
3510 }
3511
3512 fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
3513 if let Some(history) = &mut self.nav_history {
3514 history.push(Some(Box::new(self.state.clone())), cx);
3515 }
3516 }
3517 }
3518
3519 impl Entity for TestItem {
3520 type Event = TestItemEvent;
3521 }
3522
3523 impl View for TestItem {
3524 fn ui_name() -> &'static str {
3525 "TestItem"
3526 }
3527
3528 fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
3529 Empty::new().boxed()
3530 }
3531 }
3532
3533 impl Item for TestItem {
3534 fn tab_description<'a>(&'a self, detail: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
3535 self.tab_descriptions.as_ref().and_then(|descriptions| {
3536 let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
3537 Some(description.into())
3538 })
3539 }
3540
3541 fn tab_content(&self, detail: Option<usize>, _: &theme::Tab, _: &AppContext) -> ElementBox {
3542 self.tab_detail.set(detail);
3543 Empty::new().boxed()
3544 }
3545
3546 fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
3547 self.project_path.clone()
3548 }
3549
3550 fn project_entry_ids(&self, _: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
3551 self.project_entry_ids.iter().copied().collect()
3552 }
3553
3554 fn is_singleton(&self, _: &AppContext) -> bool {
3555 self.is_singleton
3556 }
3557
3558 fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
3559 self.nav_history = Some(history);
3560 }
3561
3562 fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
3563 let state = *state.downcast::<String>().unwrap_or_default();
3564 if state != self.state {
3565 self.state = state;
3566 true
3567 } else {
3568 false
3569 }
3570 }
3571
3572 fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
3573 self.push_to_nav_history(cx);
3574 }
3575
3576 fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
3577 where
3578 Self: Sized,
3579 {
3580 Some(self.clone())
3581 }
3582
3583 fn is_dirty(&self, _: &AppContext) -> bool {
3584 self.is_dirty
3585 }
3586
3587 fn has_conflict(&self, _: &AppContext) -> bool {
3588 self.has_conflict
3589 }
3590
3591 fn can_save(&self, _: &AppContext) -> bool {
3592 !self.project_entry_ids.is_empty()
3593 }
3594
3595 fn save(
3596 &mut self,
3597 _: ModelHandle<Project>,
3598 _: &mut ViewContext<Self>,
3599 ) -> Task<anyhow::Result<()>> {
3600 self.save_count += 1;
3601 self.is_dirty = false;
3602 Task::ready(Ok(()))
3603 }
3604
3605 fn save_as(
3606 &mut self,
3607 _: ModelHandle<Project>,
3608 _: std::path::PathBuf,
3609 _: &mut ViewContext<Self>,
3610 ) -> Task<anyhow::Result<()>> {
3611 self.save_as_count += 1;
3612 self.is_dirty = false;
3613 Task::ready(Ok(()))
3614 }
3615
3616 fn reload(
3617 &mut self,
3618 _: ModelHandle<Project>,
3619 _: &mut ViewContext<Self>,
3620 ) -> Task<anyhow::Result<()>> {
3621 self.reload_count += 1;
3622 self.is_dirty = false;
3623 Task::ready(Ok(()))
3624 }
3625
3626 fn to_item_events(_: &Self::Event) -> Vec<ItemEvent> {
3627 vec![ItemEvent::UpdateTab, ItemEvent::Edit]
3628 }
3629
3630 fn serialized_item_kind() -> SerializedItemKind {
3631 unimplemented!()
3632 }
3633
3634 fn deserialize(_serialized_item: SerializedItem, _cx: &mut ViewContext<Self>) -> Self {
3635 unimplemented!()
3636 }
3637
3638 fn serialize(&self) -> SerializedItem {
3639 unimplemented!()
3640 }
3641 }
3642
3643 impl SidebarItem for TestItem {}
3644}