workspace.rs

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