workspace.rs

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