workspace.rs

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