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