workspace.rs

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