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>) -> Element<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        ConstrainedBox::new(
2092            MouseEventHandler::<TitleBar, _>::new(0, cx, |_, cx| {
2093                Container::new(
2094                    Stack::new()
2095                        .with_children(
2096                            self.titlebar_item
2097                                .as_ref()
2098                                .map(|item| ChildView::new(item, cx).boxed()),
2099                        )
2100                        .boxed(),
2101                )
2102                .with_style(container_theme)
2103                .boxed()
2104            })
2105            .on_click(MouseButton::Left, |event, _, cx| {
2106                if event.click_count == 2 {
2107                    cx.zoom_window();
2108                }
2109            })
2110            .boxed(),
2111        )
2112        .with_height(theme.workspace.titlebar.height)
2113        .named("titlebar")
2114    }
2115
2116    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2117        let active_entry = self.active_project_path(cx);
2118        self.project
2119            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2120        self.update_window_title(cx);
2121    }
2122
2123    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2124        let project = self.project().read(cx);
2125        let mut title = String::new();
2126
2127        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2128            let filename = path
2129                .path
2130                .file_name()
2131                .map(|s| s.to_string_lossy())
2132                .or_else(|| {
2133                    Some(Cow::Borrowed(
2134                        project
2135                            .worktree_for_id(path.worktree_id, cx)?
2136                            .read(cx)
2137                            .root_name(),
2138                    ))
2139                });
2140
2141            if let Some(filename) = filename {
2142                title.push_str(filename.as_ref());
2143                title.push_str("");
2144            }
2145        }
2146
2147        for (i, name) in project.worktree_root_names(cx).enumerate() {
2148            if i > 0 {
2149                title.push_str(", ");
2150            }
2151            title.push_str(name);
2152        }
2153
2154        if title.is_empty() {
2155            title = "empty project".to_string();
2156        }
2157
2158        if project.is_remote() {
2159            title.push_str("");
2160        } else if project.is_shared() {
2161            title.push_str("");
2162        }
2163
2164        cx.set_window_title(&title);
2165    }
2166
2167    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2168        let is_edited = !self.project.read(cx).is_read_only()
2169            && self
2170                .items(cx)
2171                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2172        if is_edited != self.window_edited {
2173            self.window_edited = is_edited;
2174            cx.set_window_edited(self.window_edited)
2175        }
2176    }
2177
2178    fn render_disconnected_overlay(
2179        &self,
2180        cx: &mut ViewContext<Workspace>,
2181    ) -> Option<Element<Workspace>> {
2182        if self.project.read(cx).is_read_only() {
2183            enum DisconnectedOverlay {}
2184            Some(
2185                MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
2186                    let theme = &cx.global::<Settings>().theme;
2187                    Label::new(
2188                        "Your connection to the remote project has been lost.",
2189                        theme.workspace.disconnected_overlay.text.clone(),
2190                    )
2191                    .aligned()
2192                    .contained()
2193                    .with_style(theme.workspace.disconnected_overlay.container)
2194                    .boxed()
2195                })
2196                .with_cursor_style(CursorStyle::Arrow)
2197                .capture_all()
2198                .boxed(),
2199            )
2200        } else {
2201            None
2202        }
2203    }
2204
2205    fn render_notifications(
2206        &self,
2207        theme: &theme::Workspace,
2208        cx: &AppContext,
2209    ) -> Option<Element<Workspace>> {
2210        if self.notifications.is_empty() {
2211            None
2212        } else {
2213            Some(
2214                Flex::column()
2215                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2216                        ChildView::new(notification.as_any(), cx)
2217                            .contained()
2218                            .with_style(theme.notification)
2219                            .boxed()
2220                    }))
2221                    .constrained()
2222                    .with_width(theme.notifications.width)
2223                    .contained()
2224                    .with_style(theme.notifications.container)
2225                    .aligned()
2226                    .bottom()
2227                    .right()
2228                    .boxed(),
2229            )
2230        }
2231    }
2232
2233    // RPC handlers
2234
2235    async fn handle_follow(
2236        this: ViewHandle<Self>,
2237        envelope: TypedEnvelope<proto::Follow>,
2238        _: Arc<Client>,
2239        mut cx: AsyncAppContext,
2240    ) -> Result<proto::FollowResponse> {
2241        this.update(&mut cx, |this, cx| {
2242            let client = &this.client;
2243            this.leader_state
2244                .followers
2245                .insert(envelope.original_sender_id()?);
2246
2247            let active_view_id = this.active_item(cx).and_then(|i| {
2248                Some(
2249                    i.to_followable_item_handle(cx)?
2250                        .remote_id(client, cx)?
2251                        .to_proto(),
2252                )
2253            });
2254
2255            cx.notify();
2256
2257            Ok(proto::FollowResponse {
2258                active_view_id,
2259                views: this
2260                    .panes()
2261                    .iter()
2262                    .flat_map(|pane| {
2263                        let leader_id = this.leader_for_pane(pane);
2264                        pane.read(cx).items().filter_map({
2265                            let cx = &cx;
2266                            move |item| {
2267                                let item = item.to_followable_item_handle(cx)?;
2268                                let id = item.remote_id(client, cx)?.to_proto();
2269                                let variant = item.to_state_proto(cx)?;
2270                                Some(proto::View {
2271                                    id: Some(id),
2272                                    leader_id,
2273                                    variant: Some(variant),
2274                                })
2275                            }
2276                        })
2277                    })
2278                    .collect(),
2279            })
2280        })?
2281    }
2282
2283    async fn handle_unfollow(
2284        this: ViewHandle<Self>,
2285        envelope: TypedEnvelope<proto::Unfollow>,
2286        _: Arc<Client>,
2287        mut cx: AsyncAppContext,
2288    ) -> Result<()> {
2289        this.update(&mut cx, |this, cx| {
2290            this.leader_state
2291                .followers
2292                .remove(&envelope.original_sender_id()?);
2293            cx.notify();
2294            Ok(())
2295        })?
2296    }
2297
2298    async fn handle_update_followers(
2299        this: ViewHandle<Self>,
2300        envelope: TypedEnvelope<proto::UpdateFollowers>,
2301        _: Arc<Client>,
2302        cx: AsyncAppContext,
2303    ) -> Result<()> {
2304        let leader_id = envelope.original_sender_id()?;
2305        this.read_with(&cx, |this, _| {
2306            this.leader_updates_tx
2307                .unbounded_send((leader_id, envelope.payload))
2308        })?;
2309        Ok(())
2310    }
2311
2312    async fn process_leader_update(
2313        this: ViewHandle<Self>,
2314        leader_id: PeerId,
2315        update: proto::UpdateFollowers,
2316        cx: &mut AsyncAppContext,
2317    ) -> Result<()> {
2318        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2319            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2320                this.update(cx, |this, _| {
2321                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2322                        for state in state.values_mut() {
2323                            state.active_view_id =
2324                                if let Some(active_view_id) = update_active_view.id.clone() {
2325                                    Some(ViewId::from_proto(active_view_id)?)
2326                                } else {
2327                                    None
2328                                };
2329                        }
2330                    }
2331                    anyhow::Ok(())
2332                })??;
2333            }
2334            proto::update_followers::Variant::UpdateView(update_view) => {
2335                let variant = update_view
2336                    .variant
2337                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2338                let id = update_view
2339                    .id
2340                    .ok_or_else(|| anyhow!("missing update view id"))?;
2341                let mut tasks = Vec::new();
2342                this.update(cx, |this, cx| {
2343                    let project = this.project.clone();
2344                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2345                        for state in state.values_mut() {
2346                            let view_id = ViewId::from_proto(id.clone())?;
2347                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2348                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2349                            }
2350                        }
2351                    }
2352                    anyhow::Ok(())
2353                })??;
2354                try_join_all(tasks).await.log_err();
2355            }
2356            proto::update_followers::Variant::CreateView(view) => {
2357                let panes = this.read_with(cx, |this, _| {
2358                    this.follower_states_by_leader
2359                        .get(&leader_id)
2360                        .into_iter()
2361                        .flat_map(|states_by_pane| states_by_pane.keys())
2362                        .cloned()
2363                        .collect()
2364                });
2365                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2366            }
2367        }
2368        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2369        Ok(())
2370    }
2371
2372    async fn add_views_from_leader(
2373        this: ViewHandle<Self>,
2374        leader_id: PeerId,
2375        panes: Vec<ViewHandle<Pane>>,
2376        views: Vec<proto::View>,
2377        cx: &mut AsyncAppContext,
2378    ) -> Result<()> {
2379        let project = this.read_with(cx, |this, _| this.project.clone());
2380        let replica_id = project
2381            .read_with(cx, |project, _| {
2382                project
2383                    .collaborators()
2384                    .get(&leader_id)
2385                    .map(|c| c.replica_id)
2386            })
2387            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2388
2389        let item_builders = cx.update(|cx| {
2390            cx.default_global::<FollowableItemBuilders>()
2391                .values()
2392                .map(|b| b.0)
2393                .collect::<Vec<_>>()
2394        });
2395
2396        let mut item_tasks_by_pane = HashMap::default();
2397        for pane in panes {
2398            let mut item_tasks = Vec::new();
2399            let mut leader_view_ids = Vec::new();
2400            for view in &views {
2401                let Some(id) = &view.id else { continue };
2402                let id = ViewId::from_proto(id.clone())?;
2403                let mut variant = view.variant.clone();
2404                if variant.is_none() {
2405                    Err(anyhow!("missing variant"))?;
2406                }
2407                for build_item in &item_builders {
2408                    let task = cx.update(|cx| {
2409                        build_item(pane.clone(), project.clone(), id, &mut variant, cx)
2410                    });
2411                    if let Some(task) = task {
2412                        item_tasks.push(task);
2413                        leader_view_ids.push(id);
2414                        break;
2415                    } else {
2416                        assert!(variant.is_some());
2417                    }
2418                }
2419            }
2420
2421            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2422        }
2423
2424        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2425            let items = futures::future::try_join_all(item_tasks).await?;
2426            this.update(cx, |this, cx| {
2427                let state = this
2428                    .follower_states_by_leader
2429                    .get_mut(&leader_id)?
2430                    .get_mut(&pane)?;
2431
2432                for (id, item) in leader_view_ids.into_iter().zip(items) {
2433                    item.set_leader_replica_id(Some(replica_id), cx);
2434                    state.items_by_leader_view_id.insert(id, item);
2435                }
2436
2437                Some(())
2438            })?;
2439        }
2440        Ok(())
2441    }
2442
2443    fn update_followers(
2444        &self,
2445        update: proto::update_followers::Variant,
2446        cx: &AppContext,
2447    ) -> Option<()> {
2448        let project_id = self.project.read(cx).remote_id()?;
2449        if !self.leader_state.followers.is_empty() {
2450            self.client
2451                .send(proto::UpdateFollowers {
2452                    project_id,
2453                    follower_ids: self.leader_state.followers.iter().copied().collect(),
2454                    variant: Some(update),
2455                })
2456                .log_err();
2457        }
2458        None
2459    }
2460
2461    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2462        self.follower_states_by_leader
2463            .iter()
2464            .find_map(|(leader_id, state)| {
2465                if state.contains_key(pane) {
2466                    Some(*leader_id)
2467                } else {
2468                    None
2469                }
2470            })
2471    }
2472
2473    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2474        cx.notify();
2475
2476        let call = self.active_call()?;
2477        let room = call.read(cx).room()?.read(cx);
2478        let participant = room.remote_participant_for_peer_id(leader_id)?;
2479        let mut items_to_activate = Vec::new();
2480        match participant.location {
2481            call::ParticipantLocation::SharedProject { project_id } => {
2482                if Some(project_id) == self.project.read(cx).remote_id() {
2483                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2484                        if let Some(item) = state
2485                            .active_view_id
2486                            .and_then(|id| state.items_by_leader_view_id.get(&id))
2487                        {
2488                            items_to_activate.push((pane.clone(), item.boxed_clone()));
2489                        } else {
2490                            if let Some(shared_screen) =
2491                                self.shared_screen_for_peer(leader_id, pane, cx)
2492                            {
2493                                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2494                            }
2495                        }
2496                    }
2497                }
2498            }
2499            call::ParticipantLocation::UnsharedProject => {}
2500            call::ParticipantLocation::External => {
2501                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
2502                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2503                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2504                    }
2505                }
2506            }
2507        }
2508
2509        for (pane, item) in items_to_activate {
2510            let active_item_was_focused = pane
2511                .read(cx)
2512                .active_item()
2513                .map(|active_item| cx.is_child_focused(active_item.as_any()))
2514                .unwrap_or_default();
2515
2516            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2517                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2518            } else {
2519                Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
2520            }
2521
2522            if active_item_was_focused {
2523                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2524            }
2525        }
2526
2527        None
2528    }
2529
2530    fn shared_screen_for_peer(
2531        &self,
2532        peer_id: PeerId,
2533        pane: &ViewHandle<Pane>,
2534        cx: &mut ViewContext<Self>,
2535    ) -> Option<ViewHandle<SharedScreen>> {
2536        let call = self.active_call()?;
2537        let room = call.read(cx).room()?.read(cx);
2538        let participant = room.remote_participant_for_peer_id(peer_id)?;
2539        let track = participant.tracks.values().next()?.clone();
2540        let user = participant.user.clone();
2541
2542        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2543            if item.read(cx).peer_id == peer_id {
2544                return Some(item);
2545            }
2546        }
2547
2548        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2549    }
2550
2551    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2552        if active {
2553            cx.background()
2554                .spawn(persistence::DB.update_timestamp(self.database_id()))
2555                .detach();
2556        } else {
2557            for pane in &self.panes {
2558                pane.update(cx, |pane, cx| {
2559                    if let Some(item) = pane.active_item() {
2560                        item.workspace_deactivated(cx);
2561                    }
2562                    if matches!(
2563                        cx.global::<Settings>().autosave,
2564                        Autosave::OnWindowChange | Autosave::OnFocusChange
2565                    ) {
2566                        for item in pane.items() {
2567                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2568                                .detach_and_log_err(cx);
2569                        }
2570                    }
2571                });
2572            }
2573        }
2574    }
2575
2576    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
2577        self.active_call.as_ref().map(|(call, _)| call)
2578    }
2579
2580    fn on_active_call_event(
2581        &mut self,
2582        _: ModelHandle<ActiveCall>,
2583        event: &call::room::Event,
2584        cx: &mut ViewContext<Self>,
2585    ) {
2586        match event {
2587            call::room::Event::ParticipantLocationChanged { participant_id }
2588            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2589                self.leader_updated(*participant_id, cx);
2590            }
2591            _ => {}
2592        }
2593    }
2594
2595    pub fn database_id(&self) -> WorkspaceId {
2596        self.database_id
2597    }
2598
2599    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2600        let project = self.project().read(cx);
2601
2602        if project.is_local() {
2603            Some(
2604                project
2605                    .visible_worktrees(cx)
2606                    .map(|worktree| worktree.read(cx).abs_path())
2607                    .collect::<Vec<_>>()
2608                    .into(),
2609            )
2610        } else {
2611            None
2612        }
2613    }
2614
2615    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2616        match member {
2617            Member::Axis(PaneAxis { members, .. }) => {
2618                for child in members.iter() {
2619                    self.remove_panes(child.clone(), cx)
2620                }
2621            }
2622            Member::Pane(pane) => self.remove_pane(pane.clone(), cx),
2623        }
2624    }
2625
2626    fn serialize_workspace(&self, cx: &AppContext) {
2627        fn serialize_pane_handle(
2628            pane_handle: &ViewHandle<Pane>,
2629            cx: &AppContext,
2630        ) -> SerializedPane {
2631            let (items, active) = {
2632                let pane = pane_handle.read(cx);
2633                let active_item_id = pane.active_item().map(|item| item.id());
2634                (
2635                    pane.items()
2636                        .filter_map(|item_handle| {
2637                            Some(SerializedItem {
2638                                kind: Arc::from(item_handle.serialized_item_kind()?),
2639                                item_id: item_handle.id(),
2640                                active: Some(item_handle.id()) == active_item_id,
2641                            })
2642                        })
2643                        .collect::<Vec<_>>(),
2644                    pane.is_active(),
2645                )
2646            };
2647
2648            SerializedPane::new(items, active)
2649        }
2650
2651        fn build_serialized_pane_group(
2652            pane_group: &Member,
2653            cx: &AppContext,
2654        ) -> SerializedPaneGroup {
2655            match pane_group {
2656                Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group {
2657                    axis: *axis,
2658                    children: members
2659                        .iter()
2660                        .map(|member| build_serialized_pane_group(member, cx))
2661                        .collect::<Vec<_>>(),
2662                },
2663                Member::Pane(pane_handle) => {
2664                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
2665                }
2666            }
2667        }
2668
2669        if let Some(location) = self.location(cx) {
2670            // Load bearing special case:
2671            //  - with_local_workspace() relies on this to not have other stuff open
2672            //    when you open your log
2673            if !location.paths().is_empty() {
2674                let dock_pane = serialize_pane_handle(self.dock.pane(), cx);
2675                let center_group = build_serialized_pane_group(&self.center.root, cx);
2676
2677                let serialized_workspace = SerializedWorkspace {
2678                    id: self.database_id,
2679                    location,
2680                    dock_position: self.dock.position(),
2681                    dock_pane,
2682                    center_group,
2683                    left_sidebar_open: self.left_sidebar.read(cx).is_open(),
2684                    bounds: Default::default(),
2685                    display: Default::default(),
2686                };
2687
2688                cx.background()
2689                    .spawn(persistence::DB.save_workspace(serialized_workspace))
2690                    .detach();
2691            }
2692        }
2693    }
2694
2695    fn load_from_serialized_workspace(
2696        workspace: WeakViewHandle<Workspace>,
2697        serialized_workspace: SerializedWorkspace,
2698        cx: &mut AppContext,
2699    ) {
2700        cx.spawn(|mut cx| async move {
2701            if let Some(workspace) = workspace.upgrade(&cx) {
2702                let (project, dock_pane_handle, old_center_pane) =
2703                    workspace.read_with(&cx, |workspace, _| {
2704                        (
2705                            workspace.project().clone(),
2706                            workspace.dock_pane().clone(),
2707                            workspace.last_active_center_pane.clone(),
2708                        )
2709                    });
2710
2711                serialized_workspace
2712                    .dock_pane
2713                    .deserialize_to(
2714                        &project,
2715                        &dock_pane_handle,
2716                        serialized_workspace.id,
2717                        &workspace,
2718                        &mut cx,
2719                    )
2720                    .await?;
2721
2722                // Traverse the splits tree and add to things
2723                let center_group = serialized_workspace
2724                    .center_group
2725                    .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
2726                    .await;
2727
2728                // Remove old panes from workspace panes list
2729                workspace.update(&mut cx, |workspace, cx| {
2730                    if let Some((center_group, active_pane)) = center_group {
2731                        workspace.remove_panes(workspace.center.root.clone(), cx);
2732
2733                        // Swap workspace center group
2734                        workspace.center = PaneGroup::with_root(center_group);
2735
2736                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
2737                        cx.focus_self();
2738
2739                        if let Some(active_pane) = active_pane {
2740                            cx.focus(&active_pane);
2741                        } else {
2742                            cx.focus(workspace.panes.last().unwrap());
2743                        }
2744                    } else {
2745                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
2746                        if let Some(old_center_handle) = old_center_handle {
2747                            cx.focus(&old_center_handle)
2748                        } else {
2749                            cx.focus_self()
2750                        }
2751                    }
2752
2753                    if workspace.left_sidebar().read(cx).is_open()
2754                        != serialized_workspace.left_sidebar_open
2755                    {
2756                        workspace.toggle_sidebar(SidebarSide::Left, cx);
2757                    }
2758
2759                    // Note that without after_window, the focus_self() and
2760                    // the focus the dock generates start generating alternating
2761                    // focus due to the deferred execution each triggering each other
2762                    cx.after_window_update(move |workspace, cx| {
2763                        Dock::set_dock_position(
2764                            workspace,
2765                            serialized_workspace.dock_position,
2766                            true,
2767                            cx,
2768                        );
2769                    });
2770
2771                    cx.notify();
2772                })?;
2773
2774                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
2775                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))
2776            }
2777            anyhow::Ok(())
2778        })
2779        .detach_and_log_err(cx);
2780    }
2781
2782    #[cfg(any(test, feature = "test-support"))]
2783    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
2784        Self::new(None, 0, project, |_, _| None, || &[], cx)
2785    }
2786}
2787
2788fn notify_if_database_failed(workspace: &ViewHandle<Workspace>, cx: &mut AsyncAppContext) {
2789    workspace.update(cx, |workspace, cx| {
2790        if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
2791            workspace.show_notification_once(0, cx, |cx| {
2792                cx.add_view(|_| {
2793                    MessageNotification::new(
2794                        indoc::indoc! {"
2795                            Failed to load any database file :(
2796                        "},
2797                        OsOpen::new("https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml".to_string()),
2798                        "Click to let us know about this error"
2799                    )
2800                })
2801            });
2802        } else {
2803            let backup_path = (*db::BACKUP_DB_PATH).read();
2804            if let Some(backup_path) = &*backup_path {
2805                workspace.show_notification_once(0, cx, |cx| {
2806                    cx.add_view(|_| {
2807                        let backup_path = backup_path.to_string_lossy();
2808                        MessageNotification::new(
2809                            format!(
2810                                indoc::indoc! {"
2811                                Database file was corrupted :(
2812                                Old database backed up to:
2813                                {}
2814                                "},
2815                                backup_path
2816                            ),
2817                            OsOpen::new(backup_path.to_string()),
2818                            "Click to show old database in finder",
2819                        )
2820                    })
2821                });
2822            }
2823        }
2824    }).log_err();
2825}
2826
2827impl Entity for Workspace {
2828    type Event = Event;
2829}
2830
2831impl View for Workspace {
2832    fn ui_name() -> &'static str {
2833        "Workspace"
2834    }
2835
2836    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
2837        let theme = cx.global::<Settings>().theme.clone();
2838        Stack::new()
2839            .with_child(
2840                Flex::column()
2841                    .with_child(self.render_titlebar(&theme, cx))
2842                    .with_child(
2843                        Stack::new()
2844                            .with_child({
2845                                let project = self.project.clone();
2846                                Flex::row()
2847                                    .with_children(
2848                                        if self.left_sidebar.read(cx).active_item().is_some() {
2849                                            Some(
2850                                                ChildView::new(&self.left_sidebar, cx)
2851                                                    .constrained()
2852                                                    .dynamically(|constraint, _, cx| {
2853                                                        SizeConstraint::new(
2854                                                            Vector2F::new(20., constraint.min.y()),
2855                                                            Vector2F::new(
2856                                                                cx.window_size().x() * 0.8,
2857                                                                constraint.max.y(),
2858                                                            ),
2859                                                        )
2860                                                    })
2861                                                    .boxed(),
2862                                            )
2863                                        } else {
2864                                            None
2865                                        },
2866                                    )
2867                                    .with_child(
2868                                        FlexItem::new(
2869                                            Flex::column()
2870                                                .with_child(
2871                                                    FlexItem::new(self.center.render(
2872                                                        &project,
2873                                                        &theme,
2874                                                        &self.follower_states_by_leader,
2875                                                        self.active_call(),
2876                                                        self.active_pane(),
2877                                                        cx,
2878                                                    ))
2879                                                    .flex(1., true)
2880                                                    .boxed(),
2881                                                )
2882                                                .with_children(self.dock.render(
2883                                                    &theme,
2884                                                    DockAnchor::Bottom,
2885                                                    cx,
2886                                                ))
2887                                                .boxed(),
2888                                        )
2889                                        .flex(1., true)
2890                                        .boxed(),
2891                                    )
2892                                    .with_children(self.dock.render(&theme, DockAnchor::Right, cx))
2893                                    .with_children(
2894                                        if self.right_sidebar.read(cx).active_item().is_some() {
2895                                            Some(
2896                                                ChildView::new(&self.right_sidebar, cx)
2897                                                    .constrained()
2898                                                    .dynamically(|constraint, _, cx| {
2899                                                        SizeConstraint::new(
2900                                                            Vector2F::new(20., constraint.min.y()),
2901                                                            Vector2F::new(
2902                                                                cx.window_size().x() * 0.8,
2903                                                                constraint.max.y(),
2904                                                            ),
2905                                                        )
2906                                                    })
2907                                                    .boxed(),
2908                                            )
2909                                        } else {
2910                                            None
2911                                        },
2912                                    )
2913                                    .boxed()
2914                            })
2915                            .with_child(
2916                                Overlay::new(
2917                                    Stack::new()
2918                                        .with_children(self.dock.render(
2919                                            &theme,
2920                                            DockAnchor::Expanded,
2921                                            cx,
2922                                        ))
2923                                        .with_children(self.modal.as_ref().map(|modal| {
2924                                            ChildView::new(modal, cx)
2925                                                .contained()
2926                                                .with_style(theme.workspace.modal)
2927                                                .aligned()
2928                                                .top()
2929                                                .boxed()
2930                                        }))
2931                                        .with_children(
2932                                            self.render_notifications(&theme.workspace, cx),
2933                                        )
2934                                        .boxed(),
2935                                )
2936                                .boxed(),
2937                            )
2938                            .flex(1.0, true)
2939                            .boxed(),
2940                    )
2941                    .with_child(ChildView::new(&self.status_bar, cx).boxed())
2942                    .contained()
2943                    .with_background_color(theme.workspace.background)
2944                    .boxed(),
2945            )
2946            .with_children(DragAndDrop::render(cx))
2947            .with_children(self.render_disconnected_overlay(cx))
2948            .named("workspace")
2949    }
2950
2951    fn focus_in(&mut self, view: AnyViewHandle, cx: &mut ViewContext<Self>) {
2952        if cx.is_self_focused() {
2953            cx.focus(&self.active_pane);
2954        } else {
2955            for pane in self.panes() {
2956                let view = view.clone();
2957                if pane.update(cx, |_, cx| view.id() == cx.view_id() || cx.is_child(view)) {
2958                    self.handle_pane_focused(pane.clone(), cx);
2959                    break;
2960                }
2961            }
2962        }
2963    }
2964
2965    fn keymap_context(&self, _: &AppContext) -> KeymapContext {
2966        Self::default_keymap_context()
2967    }
2968}
2969
2970impl ViewId {
2971    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
2972        Ok(Self {
2973            creator: message
2974                .creator
2975                .ok_or_else(|| anyhow!("creator is missing"))?,
2976            id: message.id,
2977        })
2978    }
2979
2980    pub(crate) fn to_proto(&self) -> proto::ViewId {
2981        proto::ViewId {
2982            creator: Some(self.creator),
2983            id: self.id,
2984        }
2985    }
2986}
2987
2988pub trait WorkspaceHandle {
2989    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
2990}
2991
2992impl WorkspaceHandle for ViewHandle<Workspace> {
2993    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
2994        self.read(cx)
2995            .worktrees(cx)
2996            .flat_map(|worktree| {
2997                let worktree_id = worktree.read(cx).id();
2998                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
2999                    worktree_id,
3000                    path: f.path.clone(),
3001                })
3002            })
3003            .collect::<Vec<_>>()
3004    }
3005}
3006
3007impl std::fmt::Debug for OpenPaths {
3008    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3009        f.debug_struct("OpenPaths")
3010            .field("paths", &self.paths)
3011            .finish()
3012    }
3013}
3014
3015pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
3016
3017pub fn activate_workspace_for_project(
3018    cx: &mut AppContext,
3019    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3020) -> Option<ViewHandle<Workspace>> {
3021    for window_id in cx.window_ids().collect::<Vec<_>>() {
3022        let handle = cx
3023            .update_window(window_id, |cx| {
3024                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3025                    let project = workspace_handle.read(cx).project.clone();
3026                    if project.update(cx, &predicate) {
3027                        cx.activate_window();
3028                        return Some(workspace_handle.clone());
3029                    }
3030                }
3031                None
3032            })
3033            .flatten();
3034
3035        if handle.is_some() {
3036            return handle;
3037        }
3038    }
3039    None
3040}
3041
3042pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3043    DB.last_workspace().await.log_err().flatten()
3044}
3045
3046#[allow(clippy::type_complexity)]
3047pub fn open_paths(
3048    abs_paths: &[PathBuf],
3049    app_state: &Arc<AppState>,
3050    requesting_window_id: Option<usize>,
3051    cx: &mut AppContext,
3052) -> Task<
3053    Result<(
3054        ViewHandle<Workspace>,
3055        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3056    )>,
3057> {
3058    log::info!("open paths {:?}", abs_paths);
3059
3060    // Open paths in existing workspace if possible
3061    let existing =
3062        activate_workspace_for_project(cx, |project, cx| project.contains_paths(abs_paths, cx));
3063
3064    let app_state = app_state.clone();
3065    let abs_paths = abs_paths.to_vec();
3066    cx.spawn(|mut cx| async move {
3067        if let Some(existing) = existing {
3068            Ok((
3069                existing.clone(),
3070                existing
3071                    .update(&mut cx, |workspace, cx| {
3072                        workspace.open_paths(abs_paths, true, cx)
3073                    })?
3074                    .await,
3075            ))
3076        } else {
3077            let contains_directory =
3078                futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
3079                    .await
3080                    .contains(&false);
3081
3082            cx.update(|cx| {
3083                let task =
3084                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx);
3085
3086                cx.spawn(|mut cx| async move {
3087                    let (workspace, items) = task.await;
3088
3089                    workspace.update(&mut cx, |workspace, cx| {
3090                        if contains_directory {
3091                            workspace.toggle_sidebar(SidebarSide::Left, cx);
3092                        }
3093                    })?;
3094
3095                    anyhow::Ok((workspace, items))
3096                })
3097            })
3098            .await
3099        }
3100    })
3101}
3102
3103pub fn open_new(
3104    app_state: &Arc<AppState>,
3105    cx: &mut AppContext,
3106    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3107) -> Task<()> {
3108    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3109    cx.spawn(|mut cx| async move {
3110        let (workspace, opened_paths) = task.await;
3111
3112        workspace
3113            .update(&mut cx, |workspace, cx| {
3114                if opened_paths.is_empty() {
3115                    init(workspace, cx)
3116                }
3117            })
3118            .log_err();
3119    })
3120}
3121
3122fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
3123    let mut parts = value.split(',');
3124    let width: usize = parts.next()?.parse().ok()?;
3125    let height: usize = parts.next()?.parse().ok()?;
3126    Some(vec2f(width as f32, height as f32))
3127}
3128
3129#[cfg(test)]
3130mod tests {
3131    use std::{cell::RefCell, rc::Rc};
3132
3133    use crate::item::test::{TestItem, TestItemEvent, TestProjectItem};
3134
3135    use super::*;
3136    use fs::FakeFs;
3137    use gpui::{executor::Deterministic, TestAppContext};
3138    use project::{Project, ProjectEntryId};
3139    use serde_json::json;
3140
3141    #[gpui::test]
3142    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3143        cx.foreground().forbid_parking();
3144        Settings::test_async(cx);
3145
3146        let fs = FakeFs::new(cx.background());
3147        let project = Project::test(fs, [], cx).await;
3148        let (_, workspace) = cx.add_window(|cx| {
3149            Workspace::new(
3150                Default::default(),
3151                0,
3152                project.clone(),
3153                |_, _| None,
3154                || &[],
3155                cx,
3156            )
3157        });
3158
3159        // Adding an item with no ambiguity renders the tab without detail.
3160        let item1 = cx.add_view(&workspace, |_| {
3161            let mut item = TestItem::new();
3162            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3163            item
3164        });
3165        workspace.update(cx, |workspace, cx| {
3166            workspace.add_item(Box::new(item1.clone()), cx);
3167        });
3168        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3169
3170        // Adding an item that creates ambiguity increases the level of detail on
3171        // both tabs.
3172        let item2 = cx.add_view(&workspace, |_| {
3173            let mut item = TestItem::new();
3174            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3175            item
3176        });
3177        workspace.update(cx, |workspace, cx| {
3178            workspace.add_item(Box::new(item2.clone()), cx);
3179        });
3180        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3181        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3182
3183        // Adding an item that creates ambiguity increases the level of detail only
3184        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3185        // we stop at the highest detail available.
3186        let item3 = cx.add_view(&workspace, |_| {
3187            let mut item = TestItem::new();
3188            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3189            item
3190        });
3191        workspace.update(cx, |workspace, cx| {
3192            workspace.add_item(Box::new(item3.clone()), cx);
3193        });
3194        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3195        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3196        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3197    }
3198
3199    #[gpui::test]
3200    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3201        cx.foreground().forbid_parking();
3202        Settings::test_async(cx);
3203        let fs = FakeFs::new(cx.background());
3204        fs.insert_tree(
3205            "/root1",
3206            json!({
3207                "one.txt": "",
3208                "two.txt": "",
3209            }),
3210        )
3211        .await;
3212        fs.insert_tree(
3213            "/root2",
3214            json!({
3215                "three.txt": "",
3216            }),
3217        )
3218        .await;
3219
3220        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3221        let (window_id, workspace) = cx.add_window(|cx| {
3222            Workspace::new(
3223                Default::default(),
3224                0,
3225                project.clone(),
3226                |_, _| None,
3227                || &[],
3228                cx,
3229            )
3230        });
3231        let worktree_id = project.read_with(cx, |project, cx| {
3232            project.worktrees(cx).next().unwrap().read(cx).id()
3233        });
3234
3235        let item1 = cx.add_view(&workspace, |cx| {
3236            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
3237        });
3238        let item2 = cx.add_view(&workspace, |cx| {
3239            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
3240        });
3241
3242        // Add an item to an empty pane
3243        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3244        project.read_with(cx, |project, cx| {
3245            assert_eq!(
3246                project.active_entry(),
3247                project
3248                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3249                    .map(|e| e.id)
3250            );
3251        });
3252        assert_eq!(
3253            cx.current_window_title(window_id).as_deref(),
3254            Some("one.txt — root1")
3255        );
3256
3257        // Add a second item to a non-empty pane
3258        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3259        assert_eq!(
3260            cx.current_window_title(window_id).as_deref(),
3261            Some("two.txt — root1")
3262        );
3263        project.read_with(cx, |project, cx| {
3264            assert_eq!(
3265                project.active_entry(),
3266                project
3267                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3268                    .map(|e| e.id)
3269            );
3270        });
3271
3272        // Close the active item
3273        workspace
3274            .update(cx, |workspace, cx| {
3275                Pane::close_active_item(workspace, &Default::default(), cx).unwrap()
3276            })
3277            .await
3278            .unwrap();
3279        assert_eq!(
3280            cx.current_window_title(window_id).as_deref(),
3281            Some("one.txt — root1")
3282        );
3283        project.read_with(cx, |project, cx| {
3284            assert_eq!(
3285                project.active_entry(),
3286                project
3287                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3288                    .map(|e| e.id)
3289            );
3290        });
3291
3292        // Add a project folder
3293        project
3294            .update(cx, |project, cx| {
3295                project.find_or_create_local_worktree("/root2", true, cx)
3296            })
3297            .await
3298            .unwrap();
3299        assert_eq!(
3300            cx.current_window_title(window_id).as_deref(),
3301            Some("one.txt — root1, root2")
3302        );
3303
3304        // Remove a project folder
3305        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
3306        assert_eq!(
3307            cx.current_window_title(window_id).as_deref(),
3308            Some("one.txt — root2")
3309        );
3310    }
3311
3312    #[gpui::test]
3313    async fn test_close_window(cx: &mut TestAppContext) {
3314        cx.foreground().forbid_parking();
3315        Settings::test_async(cx);
3316        let fs = FakeFs::new(cx.background());
3317        fs.insert_tree("/root", json!({ "one": "" })).await;
3318
3319        let project = Project::test(fs, ["root".as_ref()], cx).await;
3320        let (window_id, workspace) = cx.add_window(|cx| {
3321            Workspace::new(
3322                Default::default(),
3323                0,
3324                project.clone(),
3325                |_, _| None,
3326                || &[],
3327                cx,
3328            )
3329        });
3330
3331        // When there are no dirty items, there's nothing to do.
3332        let item1 = cx.add_view(&workspace, |_| TestItem::new());
3333        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3334        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3335        assert!(task.await.unwrap());
3336
3337        // When there are dirty untitled items, prompt to save each one. If the user
3338        // cancels any prompt, then abort.
3339        let item2 = cx.add_view(&workspace, |_| TestItem::new().with_dirty(true));
3340        let item3 = cx.add_view(&workspace, |cx| {
3341            TestItem::new()
3342                .with_dirty(true)
3343                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3344        });
3345        workspace.update(cx, |w, cx| {
3346            w.add_item(Box::new(item2.clone()), cx);
3347            w.add_item(Box::new(item3.clone()), cx);
3348        });
3349        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3350        cx.foreground().run_until_parked();
3351        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3352        cx.foreground().run_until_parked();
3353        assert!(!cx.has_pending_prompt(window_id));
3354        assert!(!task.await.unwrap());
3355    }
3356
3357    #[gpui::test]
3358    async fn test_close_pane_items(cx: &mut TestAppContext) {
3359        cx.foreground().forbid_parking();
3360        Settings::test_async(cx);
3361        let fs = FakeFs::new(cx.background());
3362
3363        let project = Project::test(fs, None, cx).await;
3364        let (window_id, workspace) = cx.add_window(|cx| {
3365            Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
3366        });
3367
3368        let item1 = cx.add_view(&workspace, |cx| {
3369            TestItem::new()
3370                .with_dirty(true)
3371                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3372        });
3373        let item2 = cx.add_view(&workspace, |cx| {
3374            TestItem::new()
3375                .with_dirty(true)
3376                .with_conflict(true)
3377                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
3378        });
3379        let item3 = cx.add_view(&workspace, |cx| {
3380            TestItem::new()
3381                .with_dirty(true)
3382                .with_conflict(true)
3383                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
3384        });
3385        let item4 = cx.add_view(&workspace, |cx| {
3386            TestItem::new()
3387                .with_dirty(true)
3388                .with_project_items(&[TestProjectItem::new_untitled(cx)])
3389        });
3390        let pane = workspace.update(cx, |workspace, cx| {
3391            workspace.add_item(Box::new(item1.clone()), cx);
3392            workspace.add_item(Box::new(item2.clone()), cx);
3393            workspace.add_item(Box::new(item3.clone()), cx);
3394            workspace.add_item(Box::new(item4.clone()), cx);
3395            workspace.active_pane().clone()
3396        });
3397
3398        let close_items = workspace.update(cx, |workspace, cx| {
3399            pane.update(cx, |pane, cx| {
3400                pane.activate_item(1, true, true, cx);
3401                assert_eq!(pane.active_item().unwrap().id(), item2.id());
3402            });
3403
3404            let item1_id = item1.id();
3405            let item3_id = item3.id();
3406            let item4_id = item4.id();
3407            Pane::close_items(workspace, pane.clone(), cx, move |id| {
3408                [item1_id, item3_id, item4_id].contains(&id)
3409            })
3410        });
3411        cx.foreground().run_until_parked();
3412
3413        // There's a prompt to save item 1.
3414        pane.read_with(cx, |pane, _| {
3415            assert_eq!(pane.items_len(), 4);
3416            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3417        });
3418        assert!(cx.has_pending_prompt(window_id));
3419
3420        // Confirm saving item 1.
3421        cx.simulate_prompt_answer(window_id, 0);
3422        cx.foreground().run_until_parked();
3423
3424        // Item 1 is saved. There's a prompt to save item 3.
3425        pane.read_with(cx, |pane, cx| {
3426            assert_eq!(item1.read(cx).save_count, 1);
3427            assert_eq!(item1.read(cx).save_as_count, 0);
3428            assert_eq!(item1.read(cx).reload_count, 0);
3429            assert_eq!(pane.items_len(), 3);
3430            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3431        });
3432        assert!(cx.has_pending_prompt(window_id));
3433
3434        // Cancel saving item 3.
3435        cx.simulate_prompt_answer(window_id, 1);
3436        cx.foreground().run_until_parked();
3437
3438        // Item 3 is reloaded. There's a prompt to save item 4.
3439        pane.read_with(cx, |pane, cx| {
3440            assert_eq!(item3.read(cx).save_count, 0);
3441            assert_eq!(item3.read(cx).save_as_count, 0);
3442            assert_eq!(item3.read(cx).reload_count, 1);
3443            assert_eq!(pane.items_len(), 2);
3444            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3445        });
3446        assert!(cx.has_pending_prompt(window_id));
3447
3448        // Confirm saving item 4.
3449        cx.simulate_prompt_answer(window_id, 0);
3450        cx.foreground().run_until_parked();
3451
3452        // There's a prompt for a path for item 4.
3453        cx.simulate_new_path_selection(|_| Some(Default::default()));
3454        close_items.await.unwrap();
3455
3456        // The requested items are closed.
3457        pane.read_with(cx, |pane, cx| {
3458            assert_eq!(item4.read(cx).save_count, 0);
3459            assert_eq!(item4.read(cx).save_as_count, 1);
3460            assert_eq!(item4.read(cx).reload_count, 0);
3461            assert_eq!(pane.items_len(), 1);
3462            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3463        });
3464    }
3465
3466    #[gpui::test]
3467    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3468        cx.foreground().forbid_parking();
3469        Settings::test_async(cx);
3470        let fs = FakeFs::new(cx.background());
3471
3472        let project = Project::test(fs, [], cx).await;
3473        let (window_id, workspace) = cx.add_window(|cx| {
3474            Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
3475        });
3476
3477        // Create several workspace items with single project entries, and two
3478        // workspace items with multiple project entries.
3479        let single_entry_items = (0..=4)
3480            .map(|project_entry_id| {
3481                cx.add_view(&workspace, |cx| {
3482                    TestItem::new()
3483                        .with_dirty(true)
3484                        .with_project_items(&[TestProjectItem::new(
3485                            project_entry_id,
3486                            &format!("{project_entry_id}.txt"),
3487                            cx,
3488                        )])
3489                })
3490            })
3491            .collect::<Vec<_>>();
3492        let item_2_3 = cx.add_view(&workspace, |cx| {
3493            TestItem::new()
3494                .with_dirty(true)
3495                .with_singleton(false)
3496                .with_project_items(&[
3497                    single_entry_items[2].read(cx).project_items[0].clone(),
3498                    single_entry_items[3].read(cx).project_items[0].clone(),
3499                ])
3500        });
3501        let item_3_4 = cx.add_view(&workspace, |cx| {
3502            TestItem::new()
3503                .with_dirty(true)
3504                .with_singleton(false)
3505                .with_project_items(&[
3506                    single_entry_items[3].read(cx).project_items[0].clone(),
3507                    single_entry_items[4].read(cx).project_items[0].clone(),
3508                ])
3509        });
3510
3511        // Create two panes that contain the following project entries:
3512        //   left pane:
3513        //     multi-entry items:   (2, 3)
3514        //     single-entry items:  0, 1, 2, 3, 4
3515        //   right pane:
3516        //     single-entry items:  1
3517        //     multi-entry items:   (3, 4)
3518        let left_pane = workspace.update(cx, |workspace, cx| {
3519            let left_pane = workspace.active_pane().clone();
3520            workspace.add_item(Box::new(item_2_3.clone()), cx);
3521            for item in single_entry_items {
3522                workspace.add_item(Box::new(item), cx);
3523            }
3524            left_pane.update(cx, |pane, cx| {
3525                pane.activate_item(2, true, true, cx);
3526            });
3527
3528            workspace
3529                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3530                .unwrap();
3531
3532            left_pane
3533        });
3534
3535        //Need to cause an effect flush in order to respect new focus
3536        workspace.update(cx, |workspace, cx| {
3537            workspace.add_item(Box::new(item_3_4.clone()), cx);
3538            cx.focus(&left_pane);
3539        });
3540
3541        // When closing all of the items in the left pane, we should be prompted twice:
3542        // once for project entry 0, and once for project entry 2. After those two
3543        // prompts, the task should complete.
3544
3545        let close = workspace.update(cx, |workspace, cx| {
3546            Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
3547        });
3548
3549        cx.foreground().run_until_parked();
3550        left_pane.read_with(cx, |pane, cx| {
3551            assert_eq!(
3552                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3553                &[ProjectEntryId::from_proto(0)]
3554            );
3555        });
3556        cx.simulate_prompt_answer(window_id, 0);
3557
3558        cx.foreground().run_until_parked();
3559        left_pane.read_with(cx, |pane, cx| {
3560            assert_eq!(
3561                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3562                &[ProjectEntryId::from_proto(2)]
3563            );
3564        });
3565        cx.simulate_prompt_answer(window_id, 0);
3566
3567        cx.foreground().run_until_parked();
3568        close.await.unwrap();
3569        left_pane.read_with(cx, |pane, _| {
3570            assert_eq!(pane.items_len(), 0);
3571        });
3572    }
3573
3574    #[gpui::test]
3575    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3576        deterministic.forbid_parking();
3577
3578        Settings::test_async(cx);
3579        let fs = FakeFs::new(cx.background());
3580
3581        let project = Project::test(fs, [], cx).await;
3582        let (window_id, workspace) = cx.add_window(|cx| {
3583            Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
3584        });
3585
3586        let item = cx.add_view(&workspace, |cx| {
3587            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3588        });
3589        let item_id = item.id();
3590        workspace.update(cx, |workspace, cx| {
3591            workspace.add_item(Box::new(item.clone()), cx);
3592        });
3593
3594        // Autosave on window change.
3595        item.update(cx, |item, cx| {
3596            cx.update_global(|settings: &mut Settings, _| {
3597                settings.autosave = Autosave::OnWindowChange;
3598            });
3599            item.is_dirty = true;
3600        });
3601
3602        // Deactivating the window saves the file.
3603        cx.simulate_window_activation(None);
3604        deterministic.run_until_parked();
3605        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3606
3607        // Autosave on focus change.
3608        item.update(cx, |item, cx| {
3609            cx.focus_self();
3610            cx.update_global(|settings: &mut Settings, _| {
3611                settings.autosave = Autosave::OnFocusChange;
3612            });
3613            item.is_dirty = true;
3614        });
3615
3616        // Blurring the item saves the file.
3617        item.update(cx, |_, cx| cx.blur());
3618        deterministic.run_until_parked();
3619        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3620
3621        // Deactivating the window still saves the file.
3622        cx.simulate_window_activation(Some(window_id));
3623        item.update(cx, |item, cx| {
3624            cx.focus_self();
3625            item.is_dirty = true;
3626        });
3627        cx.simulate_window_activation(None);
3628
3629        deterministic.run_until_parked();
3630        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3631
3632        // Autosave after delay.
3633        item.update(cx, |item, cx| {
3634            cx.update_global(|settings: &mut Settings, _| {
3635                settings.autosave = Autosave::AfterDelay { milliseconds: 500 };
3636            });
3637            item.is_dirty = true;
3638            cx.emit(TestItemEvent::Edit);
3639        });
3640
3641        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3642        deterministic.advance_clock(Duration::from_millis(250));
3643        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3644
3645        // After delay expires, the file is saved.
3646        deterministic.advance_clock(Duration::from_millis(250));
3647        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3648
3649        // Autosave on focus change, ensuring closing the tab counts as such.
3650        item.update(cx, |item, cx| {
3651            cx.update_global(|settings: &mut Settings, _| {
3652                settings.autosave = Autosave::OnFocusChange;
3653            });
3654            item.is_dirty = true;
3655        });
3656
3657        workspace
3658            .update(cx, |workspace, cx| {
3659                let pane = workspace.active_pane().clone();
3660                Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3661            })
3662            .await
3663            .unwrap();
3664        assert!(!cx.has_pending_prompt(window_id));
3665        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3666
3667        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3668        workspace.update(cx, |workspace, cx| {
3669            workspace.add_item(Box::new(item.clone()), cx);
3670        });
3671        item.update(cx, |item, cx| {
3672            item.project_items[0].update(cx, |item, _| {
3673                item.entry_id = None;
3674            });
3675            item.is_dirty = true;
3676            cx.blur();
3677        });
3678        deterministic.run_until_parked();
3679        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3680
3681        // Ensure autosave is prevented for deleted files also when closing the buffer.
3682        let _close_items = workspace.update(cx, |workspace, cx| {
3683            let pane = workspace.active_pane().clone();
3684            Pane::close_items(workspace, pane, cx, move |id| id == item_id)
3685        });
3686        deterministic.run_until_parked();
3687        assert!(cx.has_pending_prompt(window_id));
3688        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3689    }
3690
3691    #[gpui::test]
3692    async fn test_pane_navigation(
3693        deterministic: Arc<Deterministic>,
3694        cx: &mut gpui::TestAppContext,
3695    ) {
3696        deterministic.forbid_parking();
3697        Settings::test_async(cx);
3698        let fs = FakeFs::new(cx.background());
3699
3700        let project = Project::test(fs, [], cx).await;
3701        let (_, workspace) = cx.add_window(|cx| {
3702            Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
3703        });
3704
3705        let item = cx.add_view(&workspace, |cx| {
3706            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3707        });
3708        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3709        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3710        let toolbar_notify_count = Rc::new(RefCell::new(0));
3711
3712        workspace.update(cx, |workspace, cx| {
3713            workspace.add_item(Box::new(item.clone()), cx);
3714            let toolbar_notification_count = toolbar_notify_count.clone();
3715            cx.observe(&toolbar, move |_, _, _| {
3716                *toolbar_notification_count.borrow_mut() += 1
3717            })
3718            .detach();
3719        });
3720
3721        pane.read_with(cx, |pane, _| {
3722            assert!(!pane.can_navigate_backward());
3723            assert!(!pane.can_navigate_forward());
3724        });
3725
3726        item.update(cx, |item, cx| {
3727            item.set_state("one".to_string(), cx);
3728        });
3729
3730        // Toolbar must be notified to re-render the navigation buttons
3731        assert_eq!(*toolbar_notify_count.borrow(), 1);
3732
3733        pane.read_with(cx, |pane, _| {
3734            assert!(pane.can_navigate_backward());
3735            assert!(!pane.can_navigate_forward());
3736        });
3737
3738        workspace
3739            .update(cx, |workspace, cx| {
3740                Pane::go_back(workspace, Some(pane.clone()), cx)
3741            })
3742            .await
3743            .unwrap();
3744
3745        assert_eq!(*toolbar_notify_count.borrow(), 3);
3746        pane.read_with(cx, |pane, _| {
3747            assert!(!pane.can_navigate_backward());
3748            assert!(pane.can_navigate_forward());
3749        });
3750    }
3751}