workspace.rs

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