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