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