workspace.rs

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