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