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