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