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