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