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