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