workspace.rs

   1pub mod dock;
   2pub mod item;
   3pub mod notifications;
   4pub mod pane;
   5pub mod pane_group;
   6mod persistence;
   7pub mod searchable;
   8pub mod shared_screen;
   9mod status_bar;
  10mod toolbar;
  11mod workspace_settings;
  12
  13use anyhow::{anyhow, Context, Result};
  14use call::ActiveCall;
  15use channel::ChannelStore;
  16use client::{
  17    proto::{self, PeerId},
  18    Client, TypedEnvelope, UserStore,
  19};
  20use collections::{hash_map, HashMap, HashSet};
  21use drag_and_drop::DragAndDrop;
  22use futures::{
  23    channel::{mpsc, oneshot},
  24    future::try_join_all,
  25    FutureExt, StreamExt,
  26};
  27use gpui::{
  28    actions,
  29    elements::*,
  30    geometry::{
  31        rect::RectF,
  32        vector::{vec2f, Vector2F},
  33    },
  34    impl_actions,
  35    platform::{
  36        CursorStyle, ModifiersChangedEvent, MouseButton, PathPromptOptions, Platform, PromptLevel,
  37        WindowBounds, WindowOptions,
  38    },
  39    AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
  40    ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
  41    WeakViewHandle, WindowContext, WindowHandle,
  42};
  43use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
  44use itertools::Itertools;
  45use language::{LanguageRegistry, Rope};
  46use std::{
  47    any::TypeId,
  48    borrow::Cow,
  49    cmp, env,
  50    future::Future,
  51    path::{Path, PathBuf},
  52    rc::Rc,
  53    str,
  54    sync::{atomic::AtomicUsize, Arc},
  55    time::Duration,
  56};
  57
  58use crate::{
  59    notifications::{simple_message_notification::MessageNotification, NotificationTracker},
  60    persistence::model::{
  61        DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
  62    },
  63};
  64use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  65use lazy_static::lazy_static;
  66use notifications::{NotificationHandle, NotifyResultExt};
  67pub use pane::*;
  68pub use pane_group::*;
  69use persistence::{model::SerializedItem, DB};
  70pub use persistence::{
  71    model::{ItemId, WorkspaceLocation},
  72    WorkspaceDb, DB as WORKSPACE_DB,
  73};
  74use postage::prelude::Stream;
  75use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  76use serde::Deserialize;
  77use shared_screen::SharedScreen;
  78use status_bar::StatusBar;
  79pub use status_bar::StatusItemView;
  80use theme::{Theme, ThemeSettings};
  81pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
  82use util::{async_iife, ResultExt};
  83pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
  84
  85lazy_static! {
  86    static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
  87        .ok()
  88        .as_deref()
  89        .and_then(parse_pixel_position_env_var);
  90    static ref ZED_WINDOW_POSITION: Option<Vector2F> = env::var("ZED_WINDOW_POSITION")
  91        .ok()
  92        .as_deref()
  93        .and_then(parse_pixel_position_env_var);
  94}
  95
  96pub trait Modal: View {
  97    fn has_focus(&self) -> bool;
  98    fn dismiss_on_event(event: &Self::Event) -> bool;
  99}
 100
 101trait ModalHandle {
 102    fn as_any(&self) -> &AnyViewHandle;
 103    fn has_focus(&self, cx: &WindowContext) -> bool;
 104}
 105
 106impl<T: Modal> ModalHandle for ViewHandle<T> {
 107    fn as_any(&self) -> &AnyViewHandle {
 108        self
 109    }
 110
 111    fn has_focus(&self, cx: &WindowContext) -> bool {
 112        self.read(cx).has_focus()
 113    }
 114}
 115
 116#[derive(Clone, PartialEq)]
 117pub struct RemoveWorktreeFromProject(pub WorktreeId);
 118
 119actions!(
 120    workspace,
 121    [
 122        Open,
 123        NewFile,
 124        NewWindow,
 125        CloseWindow,
 126        CloseInactiveTabsAndPanes,
 127        AddFolderToProject,
 128        Unfollow,
 129        SaveAs,
 130        ReloadActiveItem,
 131        ActivatePreviousPane,
 132        ActivateNextPane,
 133        FollowNextCollaborator,
 134        NewTerminal,
 135        NewCenterTerminal,
 136        ToggleTerminalFocus,
 137        NewSearch,
 138        Feedback,
 139        Restart,
 140        Welcome,
 141        ToggleZoom,
 142        ToggleLeftDock,
 143        ToggleRightDock,
 144        ToggleBottomDock,
 145        CloseAllDocks,
 146    ]
 147);
 148
 149#[derive(Clone, PartialEq)]
 150pub struct OpenPaths {
 151    pub paths: Vec<PathBuf>,
 152}
 153
 154#[derive(Clone, Deserialize, PartialEq)]
 155pub struct ActivatePane(pub usize);
 156
 157#[derive(Clone, Deserialize, PartialEq)]
 158pub struct ActivatePaneInDirection(pub SplitDirection);
 159
 160#[derive(Clone, Deserialize, PartialEq)]
 161pub struct NewFileInDirection(pub SplitDirection);
 162
 163#[derive(Clone, PartialEq, Debug, Deserialize)]
 164#[serde(rename_all = "camelCase")]
 165pub struct SaveAll {
 166    pub save_behavior: Option<SaveIntent>,
 167}
 168
 169#[derive(Clone, PartialEq, Debug, Deserialize)]
 170#[serde(rename_all = "camelCase")]
 171pub struct Save {
 172    pub save_behavior: Option<SaveIntent>,
 173}
 174
 175#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 176#[serde(rename_all = "camelCase")]
 177pub struct CloseAllItemsAndPanes {
 178    pub save_behavior: Option<SaveIntent>,
 179}
 180
 181#[derive(Deserialize)]
 182pub struct Toast {
 183    id: usize,
 184    msg: Cow<'static, str>,
 185    #[serde(skip)]
 186    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 187}
 188
 189impl Toast {
 190    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 191        Toast {
 192            id,
 193            msg: msg.into(),
 194            on_click: None,
 195        }
 196    }
 197
 198    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 199    where
 200        M: Into<Cow<'static, str>>,
 201        F: Fn(&mut WindowContext) + 'static,
 202    {
 203        self.on_click = Some((message.into(), Arc::new(on_click)));
 204        self
 205    }
 206}
 207
 208impl PartialEq for Toast {
 209    fn eq(&self, other: &Self) -> bool {
 210        self.id == other.id
 211            && self.msg == other.msg
 212            && self.on_click.is_some() == other.on_click.is_some()
 213    }
 214}
 215
 216impl Clone for Toast {
 217    fn clone(&self) -> Self {
 218        Toast {
 219            id: self.id,
 220            msg: self.msg.to_owned(),
 221            on_click: self.on_click.clone(),
 222        }
 223    }
 224}
 225
 226#[derive(Clone, Deserialize, PartialEq)]
 227pub struct OpenTerminal {
 228    pub working_directory: PathBuf,
 229}
 230
 231impl_actions!(
 232    workspace,
 233    [
 234        ActivatePane,
 235        ActivatePaneInDirection,
 236        NewFileInDirection,
 237        Toast,
 238        OpenTerminal,
 239        SaveAll,
 240        Save,
 241        CloseAllItemsAndPanes,
 242    ]
 243);
 244
 245pub type WorkspaceId = i64;
 246
 247pub fn init_settings(cx: &mut AppContext) {
 248    settings::register::<WorkspaceSettings>(cx);
 249    settings::register::<item::ItemSettings>(cx);
 250}
 251
 252pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 253    init_settings(cx);
 254    pane::init(cx);
 255    notifications::init(cx);
 256
 257    cx.add_global_action({
 258        let app_state = Arc::downgrade(&app_state);
 259        move |_: &Open, cx: &mut AppContext| {
 260            let mut paths = cx.prompt_for_paths(PathPromptOptions {
 261                files: true,
 262                directories: true,
 263                multiple: true,
 264            });
 265
 266            if let Some(app_state) = app_state.upgrade() {
 267                cx.spawn(move |mut cx| async move {
 268                    if let Some(paths) = paths.recv().await.flatten() {
 269                        cx.update(|cx| {
 270                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 271                        });
 272                    }
 273                })
 274                .detach();
 275            }
 276        }
 277    });
 278    cx.add_async_action(Workspace::open);
 279
 280    cx.add_async_action(Workspace::follow_next_collaborator);
 281    cx.add_async_action(Workspace::close);
 282    cx.add_async_action(Workspace::close_inactive_items_and_panes);
 283    cx.add_async_action(Workspace::close_all_items_and_panes);
 284    cx.add_global_action(Workspace::close_global);
 285    cx.add_global_action(restart);
 286    cx.add_async_action(Workspace::save_all);
 287    cx.add_action(Workspace::add_folder_to_project);
 288    cx.add_action(
 289        |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
 290            let pane = workspace.active_pane().clone();
 291            workspace.unfollow(&pane, cx);
 292        },
 293    );
 294    cx.add_action(
 295        |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext<Workspace>| {
 296            workspace
 297                .save_active_item(action.save_behavior.unwrap_or(SaveIntent::Save), cx)
 298                .detach_and_log_err(cx);
 299        },
 300    );
 301    cx.add_action(
 302        |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
 303            workspace
 304                .save_active_item(SaveIntent::SaveAs, cx)
 305                .detach_and_log_err(cx);
 306        },
 307    );
 308    cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
 309        workspace.activate_previous_pane(cx)
 310    });
 311    cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
 312        workspace.activate_next_pane(cx)
 313    });
 314
 315    cx.add_action(
 316        |workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| {
 317            workspace.activate_pane_in_direction(action.0, cx)
 318        },
 319    );
 320
 321    cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
 322        workspace.toggle_dock(DockPosition::Left, cx);
 323    });
 324    cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
 325        workspace.toggle_dock(DockPosition::Right, cx);
 326    });
 327    cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
 328        workspace.toggle_dock(DockPosition::Bottom, cx);
 329    });
 330    cx.add_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
 331        workspace.close_all_docks(cx);
 332    });
 333    cx.add_action(Workspace::activate_pane_at_index);
 334    cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
 335        workspace.reopen_closed_item(cx).detach();
 336    });
 337    cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
 338        workspace
 339            .go_back(workspace.active_pane().downgrade(), cx)
 340            .detach();
 341    });
 342    cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
 343        workspace
 344            .go_forward(workspace.active_pane().downgrade(), cx)
 345            .detach();
 346    });
 347
 348    cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
 349        cx.spawn(|workspace, mut cx| async move {
 350            let err = install_cli::install_cli(&cx)
 351                .await
 352                .context("Failed to create CLI symlink");
 353
 354            workspace.update(&mut cx, |workspace, cx| {
 355                if matches!(err, Err(_)) {
 356                    err.notify_err(workspace, cx);
 357                } else {
 358                    workspace.show_notification(1, cx, |cx| {
 359                        cx.add_view(|_| {
 360                            MessageNotification::new("Successfully installed the `zed` binary")
 361                        })
 362                    });
 363                }
 364            })
 365        })
 366        .detach();
 367    });
 368
 369    let client = &app_state.client;
 370    client.add_view_request_handler(Workspace::handle_follow);
 371    client.add_view_message_handler(Workspace::handle_unfollow);
 372    client.add_view_message_handler(Workspace::handle_update_followers);
 373}
 374
 375type ProjectItemBuilders = HashMap<
 376    TypeId,
 377    fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
 378>;
 379pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 380    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
 381        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 382            let item = model.downcast::<I::Item>().unwrap();
 383            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
 384        });
 385    });
 386}
 387
 388type FollowableItemBuilder = fn(
 389    ViewHandle<Pane>,
 390    ViewHandle<Workspace>,
 391    ViewId,
 392    &mut Option<proto::view::Variant>,
 393    &mut AppContext,
 394) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 395type FollowableItemBuilders = HashMap<
 396    TypeId,
 397    (
 398        FollowableItemBuilder,
 399        fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
 400    ),
 401>;
 402pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 403    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
 404        builders.insert(
 405            TypeId::of::<I>(),
 406            (
 407                |pane, workspace, id, state, cx| {
 408                    I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
 409                        cx.foreground()
 410                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 411                    })
 412                },
 413                |this| Box::new(this.clone().downcast::<I>().unwrap()),
 414            ),
 415        );
 416    });
 417}
 418
 419type ItemDeserializers = HashMap<
 420    Arc<str>,
 421    fn(
 422        ModelHandle<Project>,
 423        WeakViewHandle<Workspace>,
 424        WorkspaceId,
 425        ItemId,
 426        &mut ViewContext<Pane>,
 427    ) -> Task<Result<Box<dyn ItemHandle>>>,
 428>;
 429pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 430    cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
 431        if let Some(serialized_item_kind) = I::serialized_item_kind() {
 432            deserializers.insert(
 433                Arc::from(serialized_item_kind),
 434                |project, workspace, workspace_id, item_id, cx| {
 435                    let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 436                    cx.foreground()
 437                        .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 438                },
 439            );
 440        }
 441    });
 442}
 443
 444pub struct AppState {
 445    pub languages: Arc<LanguageRegistry>,
 446    pub client: Arc<Client>,
 447    pub user_store: ModelHandle<UserStore>,
 448    pub channel_store: ModelHandle<ChannelStore>,
 449    pub fs: Arc<dyn fs::Fs>,
 450    pub build_window_options:
 451        fn(Option<WindowBounds>, Option<uuid::Uuid>, &dyn Platform) -> WindowOptions<'static>,
 452    pub initialize_workspace:
 453        fn(WeakViewHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<Result<()>>,
 454    pub background_actions: BackgroundActions,
 455}
 456
 457impl AppState {
 458    #[cfg(any(test, feature = "test-support"))]
 459    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 460        use settings::SettingsStore;
 461
 462        if !cx.has_global::<SettingsStore>() {
 463            cx.set_global(SettingsStore::test(cx));
 464        }
 465
 466        let fs = fs::FakeFs::new(cx.background().clone());
 467        let languages = Arc::new(LanguageRegistry::test());
 468        let http_client = util::http::FakeHttpClient::with_404_response();
 469        let client = Client::new(http_client.clone(), cx);
 470        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 471        let channel_store =
 472            cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
 473
 474        theme::init((), cx);
 475        client::init(&client, cx);
 476        crate::init_settings(cx);
 477
 478        Arc::new(Self {
 479            client,
 480            fs,
 481            languages,
 482            user_store,
 483            channel_store,
 484            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
 485            build_window_options: |_, _, _| Default::default(),
 486            background_actions: || &[],
 487        })
 488    }
 489}
 490
 491struct DelayedDebouncedEditAction {
 492    task: Option<Task<()>>,
 493    cancel_channel: Option<oneshot::Sender<()>>,
 494}
 495
 496impl DelayedDebouncedEditAction {
 497    fn new() -> DelayedDebouncedEditAction {
 498        DelayedDebouncedEditAction {
 499            task: None,
 500            cancel_channel: None,
 501        }
 502    }
 503
 504    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 505    where
 506        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 507    {
 508        if let Some(channel) = self.cancel_channel.take() {
 509            _ = channel.send(());
 510        }
 511
 512        let (sender, mut receiver) = oneshot::channel::<()>();
 513        self.cancel_channel = Some(sender);
 514
 515        let previous_task = self.task.take();
 516        self.task = Some(cx.spawn(|workspace, mut cx| async move {
 517            let mut timer = cx.background().timer(delay).fuse();
 518            if let Some(previous_task) = previous_task {
 519                previous_task.await;
 520            }
 521
 522            futures::select_biased! {
 523                _ = receiver => return,
 524                    _ = timer => {}
 525            }
 526
 527            if let Some(result) = workspace
 528                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 529                .log_err()
 530            {
 531                result.await.log_err();
 532            }
 533        }));
 534    }
 535}
 536
 537pub enum Event {
 538    PaneAdded(ViewHandle<Pane>),
 539    ContactRequestedJoin(u64),
 540}
 541
 542pub struct Workspace {
 543    weak_self: WeakViewHandle<Self>,
 544    remote_entity_subscription: Option<client::Subscription>,
 545    modal: Option<ActiveModal>,
 546    zoomed: Option<AnyWeakViewHandle>,
 547    zoomed_position: Option<DockPosition>,
 548    center: PaneGroup,
 549    left_dock: ViewHandle<Dock>,
 550    bottom_dock: ViewHandle<Dock>,
 551    right_dock: ViewHandle<Dock>,
 552    panes: Vec<ViewHandle<Pane>>,
 553    panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
 554    active_pane: ViewHandle<Pane>,
 555    last_active_center_pane: Option<WeakViewHandle<Pane>>,
 556    status_bar: ViewHandle<StatusBar>,
 557    titlebar_item: Option<AnyViewHandle>,
 558    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 559    project: ModelHandle<Project>,
 560    leader_state: LeaderState,
 561    follower_states_by_leader: FollowerStatesByLeader,
 562    last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
 563    window_edited: bool,
 564    active_call: Option<(ModelHandle<ActiveCall>, Vec<Subscription>)>,
 565    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 566    database_id: WorkspaceId,
 567    app_state: Arc<AppState>,
 568    subscriptions: Vec<Subscription>,
 569    _apply_leader_updates: Task<Result<()>>,
 570    _observe_current_user: Task<Result<()>>,
 571    _schedule_serialize: Option<Task<()>>,
 572    pane_history_timestamp: Arc<AtomicUsize>,
 573}
 574
 575struct ActiveModal {
 576    view: Box<dyn ModalHandle>,
 577    previously_focused_view_id: Option<usize>,
 578}
 579
 580#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 581pub struct ViewId {
 582    pub creator: PeerId,
 583    pub id: u64,
 584}
 585
 586#[derive(Default)]
 587struct LeaderState {
 588    followers: HashSet<PeerId>,
 589}
 590
 591type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
 592
 593#[derive(Default)]
 594struct FollowerState {
 595    active_view_id: Option<ViewId>,
 596    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 597}
 598
 599enum WorkspaceBounds {}
 600
 601impl Workspace {
 602    pub fn new(
 603        workspace_id: WorkspaceId,
 604        project: ModelHandle<Project>,
 605        app_state: Arc<AppState>,
 606        cx: &mut ViewContext<Self>,
 607    ) -> Self {
 608        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 609        cx.subscribe(&project, move |this, _, event, cx| {
 610            match event {
 611                project::Event::RemoteIdChanged(remote_id) => {
 612                    this.update_window_title(cx);
 613                    this.project_remote_id_changed(*remote_id, cx);
 614                }
 615
 616                project::Event::CollaboratorLeft(peer_id) => {
 617                    this.collaborator_left(*peer_id, cx);
 618                }
 619
 620                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 621                    this.update_window_title(cx);
 622                    this.serialize_workspace(cx);
 623                }
 624
 625                project::Event::DisconnectedFromHost => {
 626                    this.update_window_edited(cx);
 627                    cx.blur();
 628                }
 629
 630                project::Event::Closed => {
 631                    cx.remove_window();
 632                }
 633
 634                project::Event::DeletedEntry(entry_id) => {
 635                    for pane in this.panes.iter() {
 636                        pane.update(cx, |pane, cx| {
 637                            pane.handle_deleted_project_item(*entry_id, cx)
 638                        });
 639                    }
 640                }
 641
 642                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
 643                    cx.add_view(|_| MessageNotification::new(message.clone()))
 644                }),
 645
 646                _ => {}
 647            }
 648            cx.notify()
 649        })
 650        .detach();
 651
 652        let weak_handle = cx.weak_handle();
 653        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 654
 655        let center_pane = cx.add_view(|cx| {
 656            Pane::new(
 657                weak_handle.clone(),
 658                project.clone(),
 659                app_state.background_actions,
 660                pane_history_timestamp.clone(),
 661                cx,
 662            )
 663        });
 664        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 665        cx.focus(&center_pane);
 666        cx.emit(Event::PaneAdded(center_pane.clone()));
 667
 668        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 669        let mut connection_status = app_state.client.status();
 670        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 671            current_user.recv().await;
 672            connection_status.recv().await;
 673            let mut stream =
 674                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 675
 676            while stream.recv().await.is_some() {
 677                this.update(&mut cx, |_, cx| cx.notify())?;
 678            }
 679            anyhow::Ok(())
 680        });
 681
 682        // All leader updates are enqueued and then processed in a single task, so
 683        // that each asynchronous operation can be run in order.
 684        let (leader_updates_tx, mut leader_updates_rx) =
 685            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 686        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 687            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 688                Self::process_leader_update(&this, leader_id, update, &mut cx)
 689                    .await
 690                    .log_err();
 691            }
 692
 693            Ok(())
 694        });
 695
 696        cx.emit_global(WorkspaceCreated(weak_handle.clone()));
 697
 698        let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
 699        let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
 700        let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
 701        let left_dock_buttons =
 702            cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
 703        let bottom_dock_buttons =
 704            cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
 705        let right_dock_buttons =
 706            cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
 707        let status_bar = cx.add_view(|cx| {
 708            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 709            status_bar.add_left_item(left_dock_buttons, cx);
 710            status_bar.add_right_item(right_dock_buttons, cx);
 711            status_bar.add_right_item(bottom_dock_buttons, cx);
 712            status_bar
 713        });
 714
 715        cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
 716            drag_and_drop.register_container(weak_handle.clone());
 717        });
 718
 719        let mut active_call = None;
 720        if cx.has_global::<ModelHandle<ActiveCall>>() {
 721            let call = cx.global::<ModelHandle<ActiveCall>>().clone();
 722            let mut subscriptions = Vec::new();
 723            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 724            active_call = Some((call, subscriptions));
 725        }
 726
 727        let subscriptions = vec![
 728            cx.observe_fullscreen(|_, _, cx| cx.notify()),
 729            cx.observe_window_activation(Self::on_window_activation_changed),
 730            cx.observe_window_bounds(move |_, mut bounds, display, cx| {
 731                // Transform fixed bounds to be stored in terms of the containing display
 732                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 733                    if let Some(screen) = cx.platform().screen_by_id(display) {
 734                        let screen_bounds = screen.bounds();
 735                        window_bounds
 736                            .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
 737                        window_bounds
 738                            .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
 739                        bounds = WindowBounds::Fixed(window_bounds);
 740                    }
 741                }
 742
 743                cx.background()
 744                    .spawn(DB.set_window_bounds(workspace_id, bounds, display))
 745                    .detach_and_log_err(cx);
 746            }),
 747            cx.observe(&left_dock, |this, _, cx| {
 748                this.serialize_workspace(cx);
 749                cx.notify();
 750            }),
 751            cx.observe(&bottom_dock, |this, _, cx| {
 752                this.serialize_workspace(cx);
 753                cx.notify();
 754            }),
 755            cx.observe(&right_dock, |this, _, cx| {
 756                this.serialize_workspace(cx);
 757                cx.notify();
 758            }),
 759        ];
 760
 761        let mut this = Workspace {
 762            weak_self: weak_handle.clone(),
 763            modal: None,
 764            zoomed: None,
 765            zoomed_position: None,
 766            center: PaneGroup::new(center_pane.clone()),
 767            panes: vec![center_pane.clone()],
 768            panes_by_item: Default::default(),
 769            active_pane: center_pane.clone(),
 770            last_active_center_pane: Some(center_pane.downgrade()),
 771            status_bar,
 772            titlebar_item: None,
 773            notifications: Default::default(),
 774            remote_entity_subscription: None,
 775            left_dock,
 776            bottom_dock,
 777            right_dock,
 778            project: project.clone(),
 779            leader_state: Default::default(),
 780            follower_states_by_leader: Default::default(),
 781            last_leaders_by_pane: Default::default(),
 782            window_edited: false,
 783            active_call,
 784            database_id: workspace_id,
 785            app_state,
 786            _observe_current_user,
 787            _apply_leader_updates,
 788            _schedule_serialize: None,
 789            leader_updates_tx,
 790            subscriptions,
 791            pane_history_timestamp,
 792        };
 793        this.project_remote_id_changed(project.read(cx).remote_id(), cx);
 794        cx.defer(|this, cx| this.update_window_title(cx));
 795        this
 796    }
 797
 798    fn new_local(
 799        abs_paths: Vec<PathBuf>,
 800        app_state: Arc<AppState>,
 801        requesting_window: Option<WindowHandle<Workspace>>,
 802        cx: &mut AppContext,
 803    ) -> Task<(
 804        WeakViewHandle<Workspace>,
 805        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 806    )> {
 807        let project_handle = Project::local(
 808            app_state.client.clone(),
 809            app_state.user_store.clone(),
 810            app_state.languages.clone(),
 811            app_state.fs.clone(),
 812            cx,
 813        );
 814
 815        cx.spawn(|mut cx| async move {
 816            let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 817
 818            let paths_to_open = Arc::new(abs_paths);
 819
 820            // Get project paths for all of the abs_paths
 821            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 822            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 823                Vec::with_capacity(paths_to_open.len());
 824            for path in paths_to_open.iter().cloned() {
 825                if let Some((worktree, project_entry)) = cx
 826                    .update(|cx| {
 827                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 828                    })
 829                    .await
 830                    .log_err()
 831                {
 832                    worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
 833                    project_paths.push((path, Some(project_entry)));
 834                } else {
 835                    project_paths.push((path, None));
 836                }
 837            }
 838
 839            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 840                serialized_workspace.id
 841            } else {
 842                DB.next_id().await.unwrap_or(0)
 843            };
 844
 845            let window = if let Some(window) = requesting_window {
 846                window.replace_root(&mut cx, |cx| {
 847                    Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 848                });
 849                window
 850            } else {
 851                {
 852                    let window_bounds_override = window_bounds_env_override(&cx);
 853                    let (bounds, display) = if let Some(bounds) = window_bounds_override {
 854                        (Some(bounds), None)
 855                    } else {
 856                        serialized_workspace
 857                            .as_ref()
 858                            .and_then(|serialized_workspace| {
 859                                let display = serialized_workspace.display?;
 860                                let mut bounds = serialized_workspace.bounds?;
 861
 862                                // Stored bounds are relative to the containing display.
 863                                // So convert back to global coordinates if that screen still exists
 864                                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 865                                    if let Some(screen) = cx.platform().screen_by_id(display) {
 866                                        let screen_bounds = screen.bounds();
 867                                        window_bounds.set_origin_x(
 868                                            window_bounds.origin_x() + screen_bounds.origin_x(),
 869                                        );
 870                                        window_bounds.set_origin_y(
 871                                            window_bounds.origin_y() + screen_bounds.origin_y(),
 872                                        );
 873                                        bounds = WindowBounds::Fixed(window_bounds);
 874                                    } else {
 875                                        // Screen no longer exists. Return none here.
 876                                        return None;
 877                                    }
 878                                }
 879
 880                                Some((bounds, display))
 881                            })
 882                            .unzip()
 883                    };
 884
 885                    // Use the serialized workspace to construct the new window
 886                    cx.add_window(
 887                        (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
 888                        |cx| {
 889                            Workspace::new(
 890                                workspace_id,
 891                                project_handle.clone(),
 892                                app_state.clone(),
 893                                cx,
 894                            )
 895                        },
 896                    )
 897                }
 898            };
 899
 900            // We haven't yielded the main thread since obtaining the window handle,
 901            // so the window exists.
 902            let workspace = window.root(&cx).unwrap();
 903
 904            (app_state.initialize_workspace)(
 905                workspace.downgrade(),
 906                serialized_workspace.is_some(),
 907                app_state.clone(),
 908                cx.clone(),
 909            )
 910            .await
 911            .log_err();
 912
 913            window.update(&mut cx, |cx| cx.activate_window());
 914
 915            let workspace = workspace.downgrade();
 916            notify_if_database_failed(&workspace, &mut cx);
 917            let opened_items = open_items(
 918                serialized_workspace,
 919                &workspace,
 920                project_paths,
 921                app_state,
 922                cx,
 923            )
 924            .await;
 925
 926            (workspace, opened_items)
 927        })
 928    }
 929
 930    pub fn weak_handle(&self) -> WeakViewHandle<Self> {
 931        self.weak_self.clone()
 932    }
 933
 934    pub fn left_dock(&self) -> &ViewHandle<Dock> {
 935        &self.left_dock
 936    }
 937
 938    pub fn bottom_dock(&self) -> &ViewHandle<Dock> {
 939        &self.bottom_dock
 940    }
 941
 942    pub fn right_dock(&self) -> &ViewHandle<Dock> {
 943        &self.right_dock
 944    }
 945
 946    pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
 947    where
 948        T::Event: std::fmt::Debug,
 949    {
 950        self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {})
 951    }
 952
 953    pub fn add_panel_with_extra_event_handler<T: Panel, F>(
 954        &mut self,
 955        panel: ViewHandle<T>,
 956        cx: &mut ViewContext<Self>,
 957        handler: F,
 958    ) where
 959        T::Event: std::fmt::Debug,
 960        F: Fn(&mut Self, &ViewHandle<T>, &T::Event, &mut ViewContext<Self>) + 'static,
 961    {
 962        let dock = match panel.position(cx) {
 963            DockPosition::Left => &self.left_dock,
 964            DockPosition::Bottom => &self.bottom_dock,
 965            DockPosition::Right => &self.right_dock,
 966        };
 967
 968        self.subscriptions.push(cx.subscribe(&panel, {
 969            let mut dock = dock.clone();
 970            let mut prev_position = panel.position(cx);
 971            move |this, panel, event, cx| {
 972                if T::should_change_position_on_event(event) {
 973                    let new_position = panel.read(cx).position(cx);
 974                    let mut was_visible = false;
 975                    dock.update(cx, |dock, cx| {
 976                        prev_position = new_position;
 977
 978                        was_visible = dock.is_open()
 979                            && dock
 980                                .visible_panel()
 981                                .map_or(false, |active_panel| active_panel.id() == panel.id());
 982                        dock.remove_panel(&panel, cx);
 983                    });
 984
 985                    if panel.is_zoomed(cx) {
 986                        this.zoomed_position = Some(new_position);
 987                    }
 988
 989                    dock = match panel.read(cx).position(cx) {
 990                        DockPosition::Left => &this.left_dock,
 991                        DockPosition::Bottom => &this.bottom_dock,
 992                        DockPosition::Right => &this.right_dock,
 993                    }
 994                    .clone();
 995                    dock.update(cx, |dock, cx| {
 996                        dock.add_panel(panel.clone(), cx);
 997                        if was_visible {
 998                            dock.set_open(true, cx);
 999                            dock.activate_panel(dock.panels_len() - 1, cx);
1000                        }
1001                    });
1002                } else if T::should_zoom_in_on_event(event) {
1003                    dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
1004                    if !panel.has_focus(cx) {
1005                        cx.focus(&panel);
1006                    }
1007                    this.zoomed = Some(panel.downgrade().into_any());
1008                    this.zoomed_position = Some(panel.read(cx).position(cx));
1009                } else if T::should_zoom_out_on_event(event) {
1010                    dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
1011                    if this.zoomed_position == Some(prev_position) {
1012                        this.zoomed = None;
1013                        this.zoomed_position = None;
1014                    }
1015                    cx.notify();
1016                } else if T::is_focus_event(event) {
1017                    let position = panel.read(cx).position(cx);
1018                    this.dismiss_zoomed_items_to_reveal(Some(position), cx);
1019                    if panel.is_zoomed(cx) {
1020                        this.zoomed = Some(panel.downgrade().into_any());
1021                        this.zoomed_position = Some(position);
1022                    } else {
1023                        this.zoomed = None;
1024                        this.zoomed_position = None;
1025                    }
1026                    this.update_active_view_for_followers(cx);
1027                    cx.notify();
1028                } else {
1029                    handler(this, &panel, event, cx)
1030                }
1031            }
1032        }));
1033
1034        dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
1035    }
1036
1037    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
1038        &self.status_bar
1039    }
1040
1041    pub fn app_state(&self) -> &Arc<AppState> {
1042        &self.app_state
1043    }
1044
1045    pub fn user_store(&self) -> &ModelHandle<UserStore> {
1046        &self.app_state.user_store
1047    }
1048
1049    pub fn project(&self) -> &ModelHandle<Project> {
1050        &self.project
1051    }
1052
1053    pub fn recent_navigation_history(
1054        &self,
1055        limit: Option<usize>,
1056        cx: &AppContext,
1057    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
1058        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
1059        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
1060        for pane in &self.panes {
1061            let pane = pane.read(cx);
1062            pane.nav_history()
1063                .for_each_entry(cx, |entry, (project_path, fs_path)| {
1064                    if let Some(fs_path) = &fs_path {
1065                        abs_paths_opened
1066                            .entry(fs_path.clone())
1067                            .or_default()
1068                            .insert(project_path.clone());
1069                    }
1070                    let timestamp = entry.timestamp;
1071                    match history.entry(project_path) {
1072                        hash_map::Entry::Occupied(mut entry) => {
1073                            let (_, old_timestamp) = entry.get();
1074                            if &timestamp > old_timestamp {
1075                                entry.insert((fs_path, timestamp));
1076                            }
1077                        }
1078                        hash_map::Entry::Vacant(entry) => {
1079                            entry.insert((fs_path, timestamp));
1080                        }
1081                    }
1082                });
1083        }
1084
1085        history
1086            .into_iter()
1087            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
1088            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
1089            .rev()
1090            .filter(|(history_path, abs_path)| {
1091                let latest_project_path_opened = abs_path
1092                    .as_ref()
1093                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
1094                    .and_then(|project_paths| {
1095                        project_paths
1096                            .iter()
1097                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
1098                    });
1099
1100                match latest_project_path_opened {
1101                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
1102                    None => true,
1103                }
1104            })
1105            .take(limit.unwrap_or(usize::MAX))
1106            .collect()
1107    }
1108
1109    fn navigate_history(
1110        &mut self,
1111        pane: WeakViewHandle<Pane>,
1112        mode: NavigationMode,
1113        cx: &mut ViewContext<Workspace>,
1114    ) -> Task<Result<()>> {
1115        let to_load = if let Some(pane) = pane.upgrade(cx) {
1116            cx.focus(&pane);
1117
1118            pane.update(cx, |pane, cx| {
1119                loop {
1120                    // Retrieve the weak item handle from the history.
1121                    let entry = pane.nav_history_mut().pop(mode, cx)?;
1122
1123                    // If the item is still present in this pane, then activate it.
1124                    if let Some(index) = entry
1125                        .item
1126                        .upgrade(cx)
1127                        .and_then(|v| pane.index_for_item(v.as_ref()))
1128                    {
1129                        let prev_active_item_index = pane.active_item_index();
1130                        pane.nav_history_mut().set_mode(mode);
1131                        pane.activate_item(index, true, true, cx);
1132                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1133
1134                        let mut navigated = prev_active_item_index != pane.active_item_index();
1135                        if let Some(data) = entry.data {
1136                            navigated |= pane.active_item()?.navigate(data, cx);
1137                        }
1138
1139                        if navigated {
1140                            break None;
1141                        }
1142                    }
1143                    // If the item is no longer present in this pane, then retrieve its
1144                    // project path in order to reopen it.
1145                    else {
1146                        break pane
1147                            .nav_history()
1148                            .path_for_item(entry.item.id())
1149                            .map(|(project_path, _)| (project_path, entry));
1150                    }
1151                }
1152            })
1153        } else {
1154            None
1155        };
1156
1157        if let Some((project_path, entry)) = to_load {
1158            // If the item was no longer present, then load it again from its previous path.
1159            let task = self.load_path(project_path, cx);
1160            cx.spawn(|workspace, mut cx| async move {
1161                let task = task.await;
1162                let mut navigated = false;
1163                if let Some((project_entry_id, build_item)) = task.log_err() {
1164                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
1165                        pane.nav_history_mut().set_mode(mode);
1166                        pane.active_item().map(|p| p.id())
1167                    })?;
1168
1169                    pane.update(&mut cx, |pane, cx| {
1170                        let item = pane.open_item(project_entry_id, true, cx, build_item);
1171                        navigated |= Some(item.id()) != prev_active_item_id;
1172                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1173                        if let Some(data) = entry.data {
1174                            navigated |= item.navigate(data, cx);
1175                        }
1176                    })?;
1177                }
1178
1179                if !navigated {
1180                    workspace
1181                        .update(&mut cx, |workspace, cx| {
1182                            Self::navigate_history(workspace, pane, mode, cx)
1183                        })?
1184                        .await?;
1185                }
1186
1187                Ok(())
1188            })
1189        } else {
1190            Task::ready(Ok(()))
1191        }
1192    }
1193
1194    pub fn go_back(
1195        &mut self,
1196        pane: WeakViewHandle<Pane>,
1197        cx: &mut ViewContext<Workspace>,
1198    ) -> Task<Result<()>> {
1199        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1200    }
1201
1202    pub fn go_forward(
1203        &mut self,
1204        pane: WeakViewHandle<Pane>,
1205        cx: &mut ViewContext<Workspace>,
1206    ) -> Task<Result<()>> {
1207        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1208    }
1209
1210    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1211        self.navigate_history(
1212            self.active_pane().downgrade(),
1213            NavigationMode::ReopeningClosedItem,
1214            cx,
1215        )
1216    }
1217
1218    pub fn client(&self) -> &Client {
1219        &self.app_state.client
1220    }
1221
1222    pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
1223        self.titlebar_item = Some(item);
1224        cx.notify();
1225    }
1226
1227    pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
1228        self.titlebar_item.clone()
1229    }
1230
1231    /// Call the given callback with a workspace whose project is local.
1232    ///
1233    /// If the given workspace has a local project, then it will be passed
1234    /// to the callback. Otherwise, a new empty window will be created.
1235    pub fn with_local_workspace<T, F>(
1236        &mut self,
1237        cx: &mut ViewContext<Self>,
1238        callback: F,
1239    ) -> Task<Result<T>>
1240    where
1241        T: 'static,
1242        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1243    {
1244        if self.project.read(cx).is_local() {
1245            Task::Ready(Some(Ok(callback(self, cx))))
1246        } else {
1247            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1248            cx.spawn(|_vh, mut cx| async move {
1249                let (workspace, _) = task.await;
1250                workspace.update(&mut cx, callback)
1251            })
1252        }
1253    }
1254
1255    pub fn worktrees<'a>(
1256        &self,
1257        cx: &'a AppContext,
1258    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1259        self.project.read(cx).worktrees(cx)
1260    }
1261
1262    pub fn visible_worktrees<'a>(
1263        &self,
1264        cx: &'a AppContext,
1265    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1266        self.project.read(cx).visible_worktrees(cx)
1267    }
1268
1269    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1270        let futures = self
1271            .worktrees(cx)
1272            .filter_map(|worktree| worktree.read(cx).as_local())
1273            .map(|worktree| worktree.scan_complete())
1274            .collect::<Vec<_>>();
1275        async move {
1276            for future in futures {
1277                future.await;
1278            }
1279        }
1280    }
1281
1282    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1283        cx.spawn(|mut cx| async move {
1284            let window = cx
1285                .windows()
1286                .into_iter()
1287                .find(|window| window.is_active(&cx).unwrap_or(false));
1288            if let Some(window) = window {
1289                //This can only get called when the window's project connection has been lost
1290                //so we don't need to prompt the user for anything and instead just close the window
1291                window.remove(&mut cx);
1292            }
1293        })
1294        .detach();
1295    }
1296
1297    pub fn close(
1298        &mut self,
1299        _: &CloseWindow,
1300        cx: &mut ViewContext<Self>,
1301    ) -> Option<Task<Result<()>>> {
1302        let window = cx.window();
1303        let prepare = self.prepare_to_close(false, cx);
1304        Some(cx.spawn(|_, mut cx| async move {
1305            if prepare.await? {
1306                window.remove(&mut cx);
1307            }
1308            Ok(())
1309        }))
1310    }
1311
1312    pub fn prepare_to_close(
1313        &mut self,
1314        quitting: bool,
1315        cx: &mut ViewContext<Self>,
1316    ) -> Task<Result<bool>> {
1317        let active_call = self.active_call().cloned();
1318        let window = cx.window();
1319
1320        cx.spawn(|this, mut cx| async move {
1321            let workspace_count = cx
1322                .windows()
1323                .into_iter()
1324                .filter(|window| window.root_is::<Workspace>())
1325                .count();
1326
1327            if let Some(active_call) = active_call {
1328                if !quitting
1329                    && workspace_count == 1
1330                    && active_call.read_with(&cx, |call, _| call.room().is_some())
1331                {
1332                    let answer = window.prompt(
1333                        PromptLevel::Warning,
1334                        "Do you want to leave the current call?",
1335                        &["Close window and hang up", "Cancel"],
1336                        &mut cx,
1337                    );
1338
1339                    if let Some(mut answer) = answer {
1340                        if answer.next().await == Some(1) {
1341                            return anyhow::Ok(false);
1342                        } else {
1343                            active_call
1344                                .update(&mut cx, |call, cx| call.hang_up(cx))
1345                                .await
1346                                .log_err();
1347                        }
1348                    }
1349                }
1350            }
1351
1352            Ok(this
1353                .update(&mut cx, |this, cx| {
1354                    this.save_all_internal(SaveIntent::Close, cx)
1355                })?
1356                .await?)
1357        })
1358    }
1359
1360    fn save_all(
1361        &mut self,
1362        action: &SaveAll,
1363        cx: &mut ViewContext<Self>,
1364    ) -> Option<Task<Result<()>>> {
1365        let save_all =
1366            self.save_all_internal(action.save_behavior.unwrap_or(SaveIntent::SaveAll), cx);
1367        Some(cx.foreground().spawn(async move {
1368            save_all.await?;
1369            Ok(())
1370        }))
1371    }
1372
1373    fn save_all_internal(
1374        &mut self,
1375        save_behaviour: SaveIntent,
1376        cx: &mut ViewContext<Self>,
1377    ) -> Task<Result<bool>> {
1378        if self.project.read(cx).is_read_only() {
1379            return Task::ready(Ok(true));
1380        }
1381
1382        let dirty_items = self
1383            .panes
1384            .iter()
1385            .flat_map(|pane| {
1386                pane.read(cx).items().filter_map(|item| {
1387                    if item.is_dirty(cx) {
1388                        Some((pane.downgrade(), item.boxed_clone()))
1389                    } else {
1390                        None
1391                    }
1392                })
1393            })
1394            .collect::<Vec<_>>();
1395
1396        let project = self.project.clone();
1397        cx.spawn(|_, mut cx| async move {
1398            for (pane, item) in dirty_items {
1399                let (singleton, project_entry_ids) =
1400                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1401                if singleton || !project_entry_ids.is_empty() {
1402                    if let Some(ix) =
1403                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
1404                    {
1405                        if !Pane::save_item(
1406                            project.clone(),
1407                            &pane,
1408                            ix,
1409                            &*item,
1410                            save_behaviour,
1411                            &mut cx,
1412                        )
1413                        .await?
1414                        {
1415                            return Ok(false);
1416                        }
1417                    }
1418                }
1419            }
1420            Ok(true)
1421        })
1422    }
1423
1424    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1425        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1426            files: true,
1427            directories: true,
1428            multiple: true,
1429        });
1430
1431        Some(cx.spawn(|this, mut cx| async move {
1432            if let Some(paths) = paths.recv().await.flatten() {
1433                if let Some(task) = this
1434                    .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1435                    .log_err()
1436                {
1437                    task.await?
1438                }
1439            }
1440            Ok(())
1441        }))
1442    }
1443
1444    pub fn open_workspace_for_paths(
1445        &mut self,
1446        paths: Vec<PathBuf>,
1447        cx: &mut ViewContext<Self>,
1448    ) -> Task<Result<()>> {
1449        let window = cx.window().downcast::<Self>();
1450        let is_remote = self.project.read(cx).is_remote();
1451        let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
1452        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1453        let close_task = if is_remote || has_worktree || has_dirty_items {
1454            None
1455        } else {
1456            Some(self.prepare_to_close(false, cx))
1457        };
1458        let app_state = self.app_state.clone();
1459
1460        cx.spawn(|_, mut cx| async move {
1461            let window_to_replace = if let Some(close_task) = close_task {
1462                if !close_task.await? {
1463                    return Ok(());
1464                }
1465                window
1466            } else {
1467                None
1468            };
1469            cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))
1470                .await?;
1471            Ok(())
1472        })
1473    }
1474
1475    #[allow(clippy::type_complexity)]
1476    pub fn open_paths(
1477        &mut self,
1478        mut abs_paths: Vec<PathBuf>,
1479        visible: bool,
1480        cx: &mut ViewContext<Self>,
1481    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1482        log::info!("open paths {:?}", abs_paths);
1483
1484        let fs = self.app_state.fs.clone();
1485
1486        // Sort the paths to ensure we add worktrees for parents before their children.
1487        abs_paths.sort_unstable();
1488        cx.spawn(|this, mut cx| async move {
1489            let mut tasks = Vec::with_capacity(abs_paths.len());
1490            for abs_path in &abs_paths {
1491                let project_path = match this
1492                    .update(&mut cx, |this, cx| {
1493                        Workspace::project_path_for_path(
1494                            this.project.clone(),
1495                            abs_path,
1496                            visible,
1497                            cx,
1498                        )
1499                    })
1500                    .log_err()
1501                {
1502                    Some(project_path) => project_path.await.log_err(),
1503                    None => None,
1504                };
1505
1506                let this = this.clone();
1507                let task = cx.spawn(|mut cx| {
1508                    let fs = fs.clone();
1509                    let abs_path = abs_path.clone();
1510                    async move {
1511                        let (worktree, project_path) = project_path?;
1512                        if fs.is_file(&abs_path).await {
1513                            Some(
1514                                this.update(&mut cx, |this, cx| {
1515                                    this.open_path(project_path, None, true, cx)
1516                                })
1517                                .log_err()?
1518                                .await,
1519                            )
1520                        } else {
1521                            this.update(&mut cx, |workspace, cx| {
1522                                let worktree = worktree.read(cx);
1523                                let worktree_abs_path = worktree.abs_path();
1524                                let entry_id = if abs_path == worktree_abs_path.as_ref() {
1525                                    worktree.root_entry()
1526                                } else {
1527                                    abs_path
1528                                        .strip_prefix(worktree_abs_path.as_ref())
1529                                        .ok()
1530                                        .and_then(|relative_path| {
1531                                            worktree.entry_for_path(relative_path)
1532                                        })
1533                                }
1534                                .map(|entry| entry.id);
1535                                if let Some(entry_id) = entry_id {
1536                                    workspace.project().update(cx, |_, cx| {
1537                                        cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
1538                                    })
1539                                }
1540                            })
1541                            .log_err()?;
1542                            None
1543                        }
1544                    }
1545                });
1546                tasks.push(task);
1547            }
1548
1549            futures::future::join_all(tasks).await
1550        })
1551    }
1552
1553    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1554        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1555            files: false,
1556            directories: true,
1557            multiple: true,
1558        });
1559        cx.spawn(|this, mut cx| async move {
1560            if let Some(paths) = paths.recv().await.flatten() {
1561                let results = this
1562                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
1563                    .await;
1564                for result in results.into_iter().flatten() {
1565                    result.log_err();
1566                }
1567            }
1568            anyhow::Ok(())
1569        })
1570        .detach_and_log_err(cx);
1571    }
1572
1573    fn project_path_for_path(
1574        project: ModelHandle<Project>,
1575        abs_path: &Path,
1576        visible: bool,
1577        cx: &mut AppContext,
1578    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1579        let entry = project.update(cx, |project, cx| {
1580            project.find_or_create_local_worktree(abs_path, visible, cx)
1581        });
1582        cx.spawn(|cx| async move {
1583            let (worktree, path) = entry.await?;
1584            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1585            Ok((
1586                worktree,
1587                ProjectPath {
1588                    worktree_id,
1589                    path: path.into(),
1590                },
1591            ))
1592        })
1593    }
1594
1595    /// Returns the modal that was toggled closed if it was open.
1596    pub fn toggle_modal<V, F>(
1597        &mut self,
1598        cx: &mut ViewContext<Self>,
1599        add_view: F,
1600    ) -> Option<ViewHandle<V>>
1601    where
1602        V: 'static + Modal,
1603        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1604    {
1605        cx.notify();
1606        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1607        // it. Otherwise, create a new modal and set it as active.
1608        if let Some(already_open_modal) = self
1609            .dismiss_modal(cx)
1610            .and_then(|modal| modal.downcast::<V>())
1611        {
1612            cx.focus_self();
1613            Some(already_open_modal)
1614        } else {
1615            let modal = add_view(self, cx);
1616            cx.subscribe(&modal, |this, _, event, cx| {
1617                if V::dismiss_on_event(event) {
1618                    this.dismiss_modal(cx);
1619                }
1620            })
1621            .detach();
1622            let previously_focused_view_id = cx.focused_view_id();
1623            cx.focus(&modal);
1624            self.modal = Some(ActiveModal {
1625                view: Box::new(modal),
1626                previously_focused_view_id,
1627            });
1628            None
1629        }
1630    }
1631
1632    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1633        self.modal
1634            .as_ref()
1635            .and_then(|modal| modal.view.as_any().clone().downcast::<V>())
1636    }
1637
1638    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) -> Option<AnyViewHandle> {
1639        if let Some(modal) = self.modal.take() {
1640            if let Some(previously_focused_view_id) = modal.previously_focused_view_id {
1641                if modal.view.has_focus(cx) {
1642                    cx.window_context().focus(Some(previously_focused_view_id));
1643                }
1644            }
1645            cx.notify();
1646            Some(modal.view.as_any().clone())
1647        } else {
1648            None
1649        }
1650    }
1651
1652    pub fn items<'a>(
1653        &'a self,
1654        cx: &'a AppContext,
1655    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1656        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1657    }
1658
1659    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1660        self.items_of_type(cx).max_by_key(|item| item.id())
1661    }
1662
1663    pub fn items_of_type<'a, T: Item>(
1664        &'a self,
1665        cx: &'a AppContext,
1666    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1667        self.panes
1668            .iter()
1669            .flat_map(|pane| pane.read(cx).items_of_type())
1670    }
1671
1672    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1673        self.active_pane().read(cx).active_item()
1674    }
1675
1676    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1677        self.active_item(cx).and_then(|item| item.project_path(cx))
1678    }
1679
1680    pub fn save_active_item(
1681        &mut self,
1682        save_behavior: SaveIntent,
1683        cx: &mut ViewContext<Self>,
1684    ) -> Task<Result<()>> {
1685        let project = self.project.clone();
1686        let pane = self.active_pane();
1687        let item_ix = pane.read(cx).active_item_index();
1688        let item = pane.read(cx).active_item();
1689        let pane = pane.downgrade();
1690
1691        cx.spawn(|_, mut cx| async move {
1692            if let Some(item) = item {
1693                Pane::save_item(
1694                    project,
1695                    &pane,
1696                    item_ix,
1697                    item.as_ref(),
1698                    save_behavior,
1699                    &mut cx,
1700                )
1701                .await
1702                .map(|_| ())
1703            } else {
1704                Ok(())
1705            }
1706        })
1707    }
1708
1709    pub fn close_inactive_items_and_panes(
1710        &mut self,
1711        _: &CloseInactiveTabsAndPanes,
1712        cx: &mut ViewContext<Self>,
1713    ) -> Option<Task<Result<()>>> {
1714        self.close_all_internal(true, SaveIntent::Close, cx)
1715    }
1716
1717    pub fn close_all_items_and_panes(
1718        &mut self,
1719        action: &CloseAllItemsAndPanes,
1720        cx: &mut ViewContext<Self>,
1721    ) -> Option<Task<Result<()>>> {
1722        self.close_all_internal(false, action.save_behavior.unwrap_or(SaveIntent::Close), cx)
1723    }
1724
1725    fn close_all_internal(
1726        &mut self,
1727        retain_active_pane: bool,
1728        save_behavior: SaveIntent,
1729        cx: &mut ViewContext<Self>,
1730    ) -> Option<Task<Result<()>>> {
1731        let current_pane = self.active_pane();
1732
1733        let mut tasks = Vec::new();
1734
1735        if retain_active_pane {
1736            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1737                pane.close_inactive_items(&CloseInactiveItems, cx)
1738            }) {
1739                tasks.push(current_pane_close);
1740            };
1741        }
1742
1743        for pane in self.panes() {
1744            if retain_active_pane && pane.id() == current_pane.id() {
1745                continue;
1746            }
1747
1748            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1749                pane.close_all_items(
1750                    &CloseAllItems {
1751                        save_behavior: Some(save_behavior),
1752                    },
1753                    cx,
1754                )
1755            }) {
1756                tasks.push(close_pane_items)
1757            }
1758        }
1759
1760        if tasks.is_empty() {
1761            None
1762        } else {
1763            Some(cx.spawn(|_, _| async move {
1764                for task in tasks {
1765                    task.await?
1766                }
1767                Ok(())
1768            }))
1769        }
1770    }
1771
1772    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1773        let dock = match dock_side {
1774            DockPosition::Left => &self.left_dock,
1775            DockPosition::Bottom => &self.bottom_dock,
1776            DockPosition::Right => &self.right_dock,
1777        };
1778        let mut focus_center = false;
1779        let mut reveal_dock = false;
1780        dock.update(cx, |dock, cx| {
1781            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1782            let was_visible = dock.is_open() && !other_is_zoomed;
1783            dock.set_open(!was_visible, cx);
1784
1785            if let Some(active_panel) = dock.active_panel() {
1786                if was_visible {
1787                    if active_panel.has_focus(cx) {
1788                        focus_center = true;
1789                    }
1790                } else {
1791                    cx.focus(active_panel.as_any());
1792                    reveal_dock = true;
1793                }
1794            }
1795        });
1796
1797        if reveal_dock {
1798            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1799        }
1800
1801        if focus_center {
1802            cx.focus_self();
1803        }
1804
1805        cx.notify();
1806        self.serialize_workspace(cx);
1807    }
1808
1809    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1810        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1811
1812        for dock in docks {
1813            dock.update(cx, |dock, cx| {
1814                dock.set_open(false, cx);
1815            });
1816        }
1817
1818        cx.focus_self();
1819        cx.notify();
1820        self.serialize_workspace(cx);
1821    }
1822
1823    /// Transfer focus to the panel of the given type.
1824    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
1825        self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
1826            .as_any()
1827            .clone()
1828            .downcast()
1829    }
1830
1831    /// Focus the panel of the given type if it isn't already focused. If it is
1832    /// already focused, then transfer focus back to the workspace center.
1833    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1834        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
1835    }
1836
1837    /// Focus or unfocus the given panel type, depending on the given callback.
1838    fn focus_or_unfocus_panel<T: Panel>(
1839        &mut self,
1840        cx: &mut ViewContext<Self>,
1841        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1842    ) -> Option<Rc<dyn PanelHandle>> {
1843        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1844            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1845                let mut focus_center = false;
1846                let mut reveal_dock = false;
1847                let panel = dock.update(cx, |dock, cx| {
1848                    dock.activate_panel(panel_index, cx);
1849
1850                    let panel = dock.active_panel().cloned();
1851                    if let Some(panel) = panel.as_ref() {
1852                        if should_focus(&**panel, cx) {
1853                            dock.set_open(true, cx);
1854                            cx.focus(panel.as_any());
1855                            reveal_dock = true;
1856                        } else {
1857                            // if panel.is_zoomed(cx) {
1858                            //     dock.set_open(false, cx);
1859                            // }
1860                            focus_center = true;
1861                        }
1862                    }
1863                    panel
1864                });
1865
1866                if focus_center {
1867                    cx.focus_self();
1868                }
1869
1870                self.serialize_workspace(cx);
1871                cx.notify();
1872                return panel;
1873            }
1874        }
1875        None
1876    }
1877
1878    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<ViewHandle<T>> {
1879        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1880            let dock = dock.read(cx);
1881            if let Some(panel) = dock.panel::<T>() {
1882                return Some(panel);
1883            }
1884        }
1885        None
1886    }
1887
1888    fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
1889        for pane in &self.panes {
1890            pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1891        }
1892
1893        self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1894        self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1895        self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1896        self.zoomed = None;
1897        self.zoomed_position = None;
1898
1899        cx.notify();
1900    }
1901
1902    #[cfg(any(test, feature = "test-support"))]
1903    pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
1904        self.zoomed.and_then(|view| view.upgrade(cx))
1905    }
1906
1907    fn dismiss_zoomed_items_to_reveal(
1908        &mut self,
1909        dock_to_reveal: Option<DockPosition>,
1910        cx: &mut ViewContext<Self>,
1911    ) {
1912        // If a center pane is zoomed, unzoom it.
1913        for pane in &self.panes {
1914            if pane != &self.active_pane || dock_to_reveal.is_some() {
1915                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1916            }
1917        }
1918
1919        // If another dock is zoomed, hide it.
1920        let mut focus_center = false;
1921        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1922            dock.update(cx, |dock, cx| {
1923                if Some(dock.position()) != dock_to_reveal {
1924                    if let Some(panel) = dock.active_panel() {
1925                        if panel.is_zoomed(cx) {
1926                            focus_center |= panel.has_focus(cx);
1927                            dock.set_open(false, cx);
1928                        }
1929                    }
1930                }
1931            });
1932        }
1933
1934        if focus_center {
1935            cx.focus_self();
1936        }
1937
1938        if self.zoomed_position != dock_to_reveal {
1939            self.zoomed = None;
1940            self.zoomed_position = None;
1941        }
1942
1943        cx.notify();
1944    }
1945
1946    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1947        let pane = cx.add_view(|cx| {
1948            Pane::new(
1949                self.weak_handle(),
1950                self.project.clone(),
1951                self.app_state.background_actions,
1952                self.pane_history_timestamp.clone(),
1953                cx,
1954            )
1955        });
1956        cx.subscribe(&pane, Self::handle_pane_event).detach();
1957        self.panes.push(pane.clone());
1958        cx.focus(&pane);
1959        cx.emit(Event::PaneAdded(pane.clone()));
1960        pane
1961    }
1962
1963    pub fn add_item_to_center(
1964        &mut self,
1965        item: Box<dyn ItemHandle>,
1966        cx: &mut ViewContext<Self>,
1967    ) -> bool {
1968        if let Some(center_pane) = self.last_active_center_pane.clone() {
1969            if let Some(center_pane) = center_pane.upgrade(cx) {
1970                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1971                true
1972            } else {
1973                false
1974            }
1975        } else {
1976            false
1977        }
1978    }
1979
1980    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1981        self.active_pane
1982            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1983    }
1984
1985    pub fn split_item(
1986        &mut self,
1987        split_direction: SplitDirection,
1988        item: Box<dyn ItemHandle>,
1989        cx: &mut ViewContext<Self>,
1990    ) {
1991        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
1992        new_pane.update(cx, move |new_pane, cx| {
1993            new_pane.add_item(item, true, true, None, cx)
1994        })
1995    }
1996
1997    pub fn open_abs_path(
1998        &mut self,
1999        abs_path: PathBuf,
2000        visible: bool,
2001        cx: &mut ViewContext<Self>,
2002    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2003        cx.spawn(|workspace, mut cx| async move {
2004            let open_paths_task_result = workspace
2005                .update(&mut cx, |workspace, cx| {
2006                    workspace.open_paths(vec![abs_path.clone()], visible, cx)
2007                })
2008                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
2009                .await;
2010            anyhow::ensure!(
2011                open_paths_task_result.len() == 1,
2012                "open abs path {abs_path:?} task returned incorrect number of results"
2013            );
2014            match open_paths_task_result
2015                .into_iter()
2016                .next()
2017                .expect("ensured single task result")
2018            {
2019                Some(open_result) => {
2020                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
2021                }
2022                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
2023            }
2024        })
2025    }
2026
2027    pub fn split_abs_path(
2028        &mut self,
2029        abs_path: PathBuf,
2030        visible: bool,
2031        cx: &mut ViewContext<Self>,
2032    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2033        let project_path_task =
2034            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
2035        cx.spawn(|this, mut cx| async move {
2036            let (_, path) = project_path_task.await?;
2037            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
2038                .await
2039        })
2040    }
2041
2042    pub fn open_path(
2043        &mut self,
2044        path: impl Into<ProjectPath>,
2045        pane: Option<WeakViewHandle<Pane>>,
2046        focus_item: bool,
2047        cx: &mut ViewContext<Self>,
2048    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2049        let pane = pane.unwrap_or_else(|| {
2050            self.last_active_center_pane.clone().unwrap_or_else(|| {
2051                self.panes
2052                    .first()
2053                    .expect("There must be an active pane")
2054                    .downgrade()
2055            })
2056        });
2057
2058        let task = self.load_path(path.into(), cx);
2059        cx.spawn(|_, mut cx| async move {
2060            let (project_entry_id, build_item) = task.await?;
2061            pane.update(&mut cx, |pane, cx| {
2062                pane.open_item(project_entry_id, focus_item, cx, build_item)
2063            })
2064        })
2065    }
2066
2067    pub fn split_path(
2068        &mut self,
2069        path: impl Into<ProjectPath>,
2070        cx: &mut ViewContext<Self>,
2071    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2072        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
2073            self.panes
2074                .first()
2075                .expect("There must be an active pane")
2076                .downgrade()
2077        });
2078
2079        if let Member::Pane(center_pane) = &self.center.root {
2080            if center_pane.read(cx).items_len() == 0 {
2081                return self.open_path(path, Some(pane), true, cx);
2082            }
2083        }
2084
2085        let task = self.load_path(path.into(), cx);
2086        cx.spawn(|this, mut cx| async move {
2087            let (project_entry_id, build_item) = task.await?;
2088            this.update(&mut cx, move |this, cx| -> Option<_> {
2089                let pane = pane.upgrade(cx)?;
2090                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
2091                new_pane.update(cx, |new_pane, cx| {
2092                    Some(new_pane.open_item(project_entry_id, true, cx, build_item))
2093                })
2094            })
2095            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
2096        })
2097    }
2098
2099    pub(crate) fn load_path(
2100        &mut self,
2101        path: ProjectPath,
2102        cx: &mut ViewContext<Self>,
2103    ) -> Task<
2104        Result<(
2105            ProjectEntryId,
2106            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
2107        )>,
2108    > {
2109        let project = self.project().clone();
2110        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
2111        cx.spawn(|_, mut cx| async move {
2112            let (project_entry_id, project_item) = project_item.await?;
2113            let build_item = cx.update(|cx| {
2114                cx.default_global::<ProjectItemBuilders>()
2115                    .get(&project_item.model_type())
2116                    .ok_or_else(|| anyhow!("no item builder for project item"))
2117                    .cloned()
2118            })?;
2119            let build_item =
2120                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
2121            Ok((project_entry_id, build_item))
2122        })
2123    }
2124
2125    pub fn open_project_item<T>(
2126        &mut self,
2127        project_item: ModelHandle<T::Item>,
2128        cx: &mut ViewContext<Self>,
2129    ) -> ViewHandle<T>
2130    where
2131        T: ProjectItem,
2132    {
2133        use project::Item as _;
2134
2135        let entry_id = project_item.read(cx).entry_id(cx);
2136        if let Some(item) = entry_id
2137            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2138            .and_then(|item| item.downcast())
2139        {
2140            self.activate_item(&item, cx);
2141            return item;
2142        }
2143
2144        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2145        self.add_item(Box::new(item.clone()), cx);
2146        item
2147    }
2148
2149    pub fn split_project_item<T>(
2150        &mut self,
2151        project_item: ModelHandle<T::Item>,
2152        cx: &mut ViewContext<Self>,
2153    ) -> ViewHandle<T>
2154    where
2155        T: ProjectItem,
2156    {
2157        use project::Item as _;
2158
2159        let entry_id = project_item.read(cx).entry_id(cx);
2160        if let Some(item) = entry_id
2161            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2162            .and_then(|item| item.downcast())
2163        {
2164            self.activate_item(&item, cx);
2165            return item;
2166        }
2167
2168        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2169        self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
2170        item
2171    }
2172
2173    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2174        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2175            self.active_pane.update(cx, |pane, cx| {
2176                pane.add_item(Box::new(shared_screen), false, true, None, cx)
2177            });
2178        }
2179    }
2180
2181    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
2182        let result = self.panes.iter().find_map(|pane| {
2183            pane.read(cx)
2184                .index_for_item(item)
2185                .map(|ix| (pane.clone(), ix))
2186        });
2187        if let Some((pane, ix)) = result {
2188            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2189            true
2190        } else {
2191            false
2192        }
2193    }
2194
2195    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2196        let panes = self.center.panes();
2197        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2198            cx.focus(&pane);
2199        } else {
2200            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2201        }
2202    }
2203
2204    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
2205        let panes = self.center.panes();
2206        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2207            let next_ix = (ix + 1) % panes.len();
2208            let next_pane = panes[next_ix].clone();
2209            cx.focus(&next_pane);
2210        }
2211    }
2212
2213    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
2214        let panes = self.center.panes();
2215        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2216            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2217            let prev_pane = panes[prev_ix].clone();
2218            cx.focus(&prev_pane);
2219        }
2220    }
2221
2222    pub fn activate_pane_in_direction(
2223        &mut self,
2224        direction: SplitDirection,
2225        cx: &mut ViewContext<Self>,
2226    ) {
2227        let bounding_box = match self.center.bounding_box_for_pane(&self.active_pane) {
2228            Some(coordinates) => coordinates,
2229            None => {
2230                return;
2231            }
2232        };
2233        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2234        let center = match cursor {
2235            Some(cursor) if bounding_box.contains_point(cursor) => cursor,
2236            _ => bounding_box.center(),
2237        };
2238
2239        let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.;
2240
2241        let target = match direction {
2242            SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()),
2243            SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()),
2244            SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next),
2245            SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next),
2246        };
2247
2248        if let Some(pane) = self.center.pane_at_pixel_position(target) {
2249            cx.focus(pane);
2250        }
2251    }
2252
2253    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2254        if self.active_pane != pane {
2255            self.active_pane = pane.clone();
2256            self.status_bar.update(cx, |status_bar, cx| {
2257                status_bar.set_active_pane(&self.active_pane, cx);
2258            });
2259            self.active_item_path_changed(cx);
2260            self.last_active_center_pane = Some(pane.downgrade());
2261        }
2262
2263        self.dismiss_zoomed_items_to_reveal(None, cx);
2264        if pane.read(cx).is_zoomed() {
2265            self.zoomed = Some(pane.downgrade().into_any());
2266        } else {
2267            self.zoomed = None;
2268        }
2269        self.zoomed_position = None;
2270        self.update_active_view_for_followers(cx);
2271
2272        cx.notify();
2273    }
2274
2275    fn handle_pane_event(
2276        &mut self,
2277        pane: ViewHandle<Pane>,
2278        event: &pane::Event,
2279        cx: &mut ViewContext<Self>,
2280    ) {
2281        match event {
2282            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2283            pane::Event::Split(direction) => {
2284                self.split_and_clone(pane, *direction, cx);
2285            }
2286            pane::Event::Remove => self.remove_pane(pane, cx),
2287            pane::Event::ActivateItem { local } => {
2288                if *local {
2289                    self.unfollow(&pane, cx);
2290                }
2291                if &pane == self.active_pane() {
2292                    self.active_item_path_changed(cx);
2293                }
2294            }
2295            pane::Event::ChangeItemTitle => {
2296                if pane == self.active_pane {
2297                    self.active_item_path_changed(cx);
2298                }
2299                self.update_window_edited(cx);
2300            }
2301            pane::Event::RemoveItem { item_id } => {
2302                self.update_window_edited(cx);
2303                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2304                    if entry.get().id() == pane.id() {
2305                        entry.remove();
2306                    }
2307                }
2308            }
2309            pane::Event::Focus => {
2310                self.handle_pane_focused(pane.clone(), cx);
2311            }
2312            pane::Event::ZoomIn => {
2313                if pane == self.active_pane {
2314                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2315                    if pane.read(cx).has_focus() {
2316                        self.zoomed = Some(pane.downgrade().into_any());
2317                        self.zoomed_position = None;
2318                    }
2319                    cx.notify();
2320                }
2321            }
2322            pane::Event::ZoomOut => {
2323                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2324                if self.zoomed_position.is_none() {
2325                    self.zoomed = None;
2326                }
2327                cx.notify();
2328            }
2329        }
2330
2331        self.serialize_workspace(cx);
2332    }
2333
2334    pub fn split_pane(
2335        &mut self,
2336        pane_to_split: ViewHandle<Pane>,
2337        split_direction: SplitDirection,
2338        cx: &mut ViewContext<Self>,
2339    ) -> ViewHandle<Pane> {
2340        let new_pane = self.add_pane(cx);
2341        self.center
2342            .split(&pane_to_split, &new_pane, split_direction)
2343            .unwrap();
2344        cx.notify();
2345        new_pane
2346    }
2347
2348    pub fn split_and_clone(
2349        &mut self,
2350        pane: ViewHandle<Pane>,
2351        direction: SplitDirection,
2352        cx: &mut ViewContext<Self>,
2353    ) -> Option<ViewHandle<Pane>> {
2354        let item = pane.read(cx).active_item()?;
2355        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2356            let new_pane = self.add_pane(cx);
2357            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2358            self.center.split(&pane, &new_pane, direction).unwrap();
2359            Some(new_pane)
2360        } else {
2361            None
2362        };
2363        cx.notify();
2364        maybe_pane_handle
2365    }
2366
2367    pub fn split_pane_with_item(
2368        &mut self,
2369        pane_to_split: WeakViewHandle<Pane>,
2370        split_direction: SplitDirection,
2371        from: WeakViewHandle<Pane>,
2372        item_id_to_move: usize,
2373        cx: &mut ViewContext<Self>,
2374    ) {
2375        let Some(pane_to_split) = pane_to_split.upgrade(cx) else {
2376            return;
2377        };
2378        let Some(from) = from.upgrade(cx) else {
2379            return;
2380        };
2381
2382        let new_pane = self.add_pane(cx);
2383        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2384        self.center
2385            .split(&pane_to_split, &new_pane, split_direction)
2386            .unwrap();
2387        cx.notify();
2388    }
2389
2390    pub fn split_pane_with_project_entry(
2391        &mut self,
2392        pane_to_split: WeakViewHandle<Pane>,
2393        split_direction: SplitDirection,
2394        project_entry: ProjectEntryId,
2395        cx: &mut ViewContext<Self>,
2396    ) -> Option<Task<Result<()>>> {
2397        let pane_to_split = pane_to_split.upgrade(cx)?;
2398        let new_pane = self.add_pane(cx);
2399        self.center
2400            .split(&pane_to_split, &new_pane, split_direction)
2401            .unwrap();
2402
2403        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2404        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2405        Some(cx.foreground().spawn(async move {
2406            task.await?;
2407            Ok(())
2408        }))
2409    }
2410
2411    pub fn move_item(
2412        &mut self,
2413        source: ViewHandle<Pane>,
2414        destination: ViewHandle<Pane>,
2415        item_id_to_move: usize,
2416        destination_index: usize,
2417        cx: &mut ViewContext<Self>,
2418    ) {
2419        let item_to_move = source
2420            .read(cx)
2421            .items()
2422            .enumerate()
2423            .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
2424
2425        if item_to_move.is_none() {
2426            log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
2427            return;
2428        }
2429        let (item_ix, item_handle) = item_to_move.unwrap();
2430        let item_handle = item_handle.clone();
2431
2432        if source != destination {
2433            // Close item from previous pane
2434            source.update(cx, |source, cx| {
2435                source.remove_item(item_ix, false, cx);
2436            });
2437        }
2438
2439        // This automatically removes duplicate items in the pane
2440        destination.update(cx, |destination, cx| {
2441            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2442            cx.focus_self();
2443        });
2444    }
2445
2446    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2447        if self.center.remove(&pane).unwrap() {
2448            self.force_remove_pane(&pane, cx);
2449            self.unfollow(&pane, cx);
2450            self.last_leaders_by_pane.remove(&pane.downgrade());
2451            for removed_item in pane.read(cx).items() {
2452                self.panes_by_item.remove(&removed_item.id());
2453            }
2454
2455            cx.notify();
2456        } else {
2457            self.active_item_path_changed(cx);
2458        }
2459    }
2460
2461    pub fn panes(&self) -> &[ViewHandle<Pane>] {
2462        &self.panes
2463    }
2464
2465    pub fn active_pane(&self) -> &ViewHandle<Pane> {
2466        &self.active_pane
2467    }
2468
2469    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
2470        if let Some(remote_id) = remote_id {
2471            self.remote_entity_subscription = Some(
2472                self.app_state
2473                    .client
2474                    .add_view_for_remote_entity(remote_id, cx),
2475            );
2476        } else {
2477            self.remote_entity_subscription.take();
2478        }
2479    }
2480
2481    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2482        self.leader_state.followers.remove(&peer_id);
2483        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
2484            for state in states_by_pane.into_values() {
2485                for item in state.items_by_leader_view_id.into_values() {
2486                    item.set_leader_replica_id(None, cx);
2487                }
2488            }
2489        }
2490        cx.notify();
2491    }
2492
2493    pub fn toggle_follow(
2494        &mut self,
2495        leader_id: PeerId,
2496        cx: &mut ViewContext<Self>,
2497    ) -> Option<Task<Result<()>>> {
2498        let pane = self.active_pane().clone();
2499
2500        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
2501            if leader_id == prev_leader_id {
2502                return None;
2503            }
2504        }
2505
2506        self.last_leaders_by_pane
2507            .insert(pane.downgrade(), leader_id);
2508        self.follower_states_by_leader
2509            .entry(leader_id)
2510            .or_default()
2511            .insert(pane.clone(), Default::default());
2512        cx.notify();
2513
2514        let project_id = self.project.read(cx).remote_id()?;
2515        let request = self.app_state.client.request(proto::Follow {
2516            project_id,
2517            leader_id: Some(leader_id),
2518        });
2519
2520        Some(cx.spawn(|this, mut cx| async move {
2521            let response = request.await?;
2522            this.update(&mut cx, |this, _| {
2523                let state = this
2524                    .follower_states_by_leader
2525                    .get_mut(&leader_id)
2526                    .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
2527                    .ok_or_else(|| anyhow!("following interrupted"))?;
2528                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2529                    Some(ViewId::from_proto(active_view_id)?)
2530                } else {
2531                    None
2532                };
2533                Ok::<_, anyhow::Error>(())
2534            })??;
2535            Self::add_views_from_leader(
2536                this.clone(),
2537                leader_id,
2538                vec![pane],
2539                response.views,
2540                &mut cx,
2541            )
2542            .await?;
2543            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2544            Ok(())
2545        }))
2546    }
2547
2548    pub fn follow_next_collaborator(
2549        &mut self,
2550        _: &FollowNextCollaborator,
2551        cx: &mut ViewContext<Self>,
2552    ) -> Option<Task<Result<()>>> {
2553        let collaborators = self.project.read(cx).collaborators();
2554        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2555            let mut collaborators = collaborators.keys().copied();
2556            for peer_id in collaborators.by_ref() {
2557                if peer_id == leader_id {
2558                    break;
2559                }
2560            }
2561            collaborators.next()
2562        } else if let Some(last_leader_id) =
2563            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2564        {
2565            if collaborators.contains_key(last_leader_id) {
2566                Some(*last_leader_id)
2567            } else {
2568                None
2569            }
2570        } else {
2571            None
2572        };
2573
2574        next_leader_id
2575            .or_else(|| collaborators.keys().copied().next())
2576            .and_then(|leader_id| self.toggle_follow(leader_id, cx))
2577    }
2578
2579    pub fn unfollow(
2580        &mut self,
2581        pane: &ViewHandle<Pane>,
2582        cx: &mut ViewContext<Self>,
2583    ) -> Option<PeerId> {
2584        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2585            let leader_id = *leader_id;
2586            if let Some(state) = states_by_pane.remove(pane) {
2587                for (_, item) in state.items_by_leader_view_id {
2588                    item.set_leader_replica_id(None, cx);
2589                }
2590
2591                if states_by_pane.is_empty() {
2592                    self.follower_states_by_leader.remove(&leader_id);
2593                    if let Some(project_id) = self.project.read(cx).remote_id() {
2594                        self.app_state
2595                            .client
2596                            .send(proto::Unfollow {
2597                                project_id,
2598                                leader_id: Some(leader_id),
2599                            })
2600                            .log_err();
2601                    }
2602                }
2603
2604                cx.notify();
2605                return Some(leader_id);
2606            }
2607        }
2608        None
2609    }
2610
2611    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2612        self.follower_states_by_leader.contains_key(&peer_id)
2613    }
2614
2615    pub fn is_followed_by(&self, peer_id: PeerId) -> bool {
2616        self.leader_state.followers.contains(&peer_id)
2617    }
2618
2619    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2620        // TODO: There should be a better system in place for this
2621        // (https://github.com/zed-industries/zed/issues/1290)
2622        let is_fullscreen = cx.window_is_fullscreen();
2623        let container_theme = if is_fullscreen {
2624            let mut container_theme = theme.titlebar.container;
2625            container_theme.padding.left = container_theme.padding.right;
2626            container_theme
2627        } else {
2628            theme.titlebar.container
2629        };
2630
2631        enum TitleBar {}
2632        MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
2633            Stack::new()
2634                .with_children(
2635                    self.titlebar_item
2636                        .as_ref()
2637                        .map(|item| ChildView::new(item, cx)),
2638                )
2639                .contained()
2640                .with_style(container_theme)
2641        })
2642        .on_click(MouseButton::Left, |event, _, cx| {
2643            if event.click_count == 2 {
2644                cx.zoom_window();
2645            }
2646        })
2647        .constrained()
2648        .with_height(theme.titlebar.height)
2649        .into_any_named("titlebar")
2650    }
2651
2652    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2653        let active_entry = self.active_project_path(cx);
2654        self.project
2655            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2656        self.update_window_title(cx);
2657    }
2658
2659    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2660        let project = self.project().read(cx);
2661        let mut title = String::new();
2662
2663        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2664            let filename = path
2665                .path
2666                .file_name()
2667                .map(|s| s.to_string_lossy())
2668                .or_else(|| {
2669                    Some(Cow::Borrowed(
2670                        project
2671                            .worktree_for_id(path.worktree_id, cx)?
2672                            .read(cx)
2673                            .root_name(),
2674                    ))
2675                });
2676
2677            if let Some(filename) = filename {
2678                title.push_str(filename.as_ref());
2679                title.push_str(" β€” ");
2680            }
2681        }
2682
2683        for (i, name) in project.worktree_root_names(cx).enumerate() {
2684            if i > 0 {
2685                title.push_str(", ");
2686            }
2687            title.push_str(name);
2688        }
2689
2690        if title.is_empty() {
2691            title = "empty project".to_string();
2692        }
2693
2694        if project.is_remote() {
2695            title.push_str(" ↙");
2696        } else if project.is_shared() {
2697            title.push_str(" β†—");
2698        }
2699
2700        cx.set_window_title(&title);
2701    }
2702
2703    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2704        let is_edited = !self.project.read(cx).is_read_only()
2705            && self
2706                .items(cx)
2707                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2708        if is_edited != self.window_edited {
2709            self.window_edited = is_edited;
2710            cx.set_window_edited(self.window_edited)
2711        }
2712    }
2713
2714    fn render_disconnected_overlay(
2715        &self,
2716        cx: &mut ViewContext<Workspace>,
2717    ) -> Option<AnyElement<Workspace>> {
2718        if self.project.read(cx).is_read_only() {
2719            enum DisconnectedOverlay {}
2720            Some(
2721                MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
2722                    let theme = &theme::current(cx);
2723                    Label::new(
2724                        "Your connection to the remote project has been lost.",
2725                        theme.workspace.disconnected_overlay.text.clone(),
2726                    )
2727                    .aligned()
2728                    .contained()
2729                    .with_style(theme.workspace.disconnected_overlay.container)
2730                })
2731                .with_cursor_style(CursorStyle::Arrow)
2732                .capture_all()
2733                .into_any_named("disconnected overlay"),
2734            )
2735        } else {
2736            None
2737        }
2738    }
2739
2740    fn render_notifications(
2741        &self,
2742        theme: &theme::Workspace,
2743        cx: &AppContext,
2744    ) -> Option<AnyElement<Workspace>> {
2745        if self.notifications.is_empty() {
2746            None
2747        } else {
2748            Some(
2749                Flex::column()
2750                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2751                        ChildView::new(notification.as_any(), cx)
2752                            .contained()
2753                            .with_style(theme.notification)
2754                    }))
2755                    .constrained()
2756                    .with_width(theme.notifications.width)
2757                    .contained()
2758                    .with_style(theme.notifications.container)
2759                    .aligned()
2760                    .bottom()
2761                    .right()
2762                    .into_any(),
2763            )
2764        }
2765    }
2766
2767    // RPC handlers
2768
2769    async fn handle_follow(
2770        this: WeakViewHandle<Self>,
2771        envelope: TypedEnvelope<proto::Follow>,
2772        _: Arc<Client>,
2773        mut cx: AsyncAppContext,
2774    ) -> Result<proto::FollowResponse> {
2775        this.update(&mut cx, |this, cx| {
2776            let client = &this.app_state.client;
2777            this.leader_state
2778                .followers
2779                .insert(envelope.original_sender_id()?);
2780
2781            let active_view_id = this.active_item(cx).and_then(|i| {
2782                Some(
2783                    i.to_followable_item_handle(cx)?
2784                        .remote_id(client, cx)?
2785                        .to_proto(),
2786                )
2787            });
2788
2789            cx.notify();
2790
2791            Ok(proto::FollowResponse {
2792                active_view_id,
2793                views: this
2794                    .panes()
2795                    .iter()
2796                    .flat_map(|pane| {
2797                        let leader_id = this.leader_for_pane(pane);
2798                        pane.read(cx).items().filter_map({
2799                            let cx = &cx;
2800                            move |item| {
2801                                let item = item.to_followable_item_handle(cx)?;
2802                                let id = item.remote_id(client, cx)?.to_proto();
2803                                let variant = item.to_state_proto(cx)?;
2804                                Some(proto::View {
2805                                    id: Some(id),
2806                                    leader_id,
2807                                    variant: Some(variant),
2808                                })
2809                            }
2810                        })
2811                    })
2812                    .collect(),
2813            })
2814        })?
2815    }
2816
2817    async fn handle_unfollow(
2818        this: WeakViewHandle<Self>,
2819        envelope: TypedEnvelope<proto::Unfollow>,
2820        _: Arc<Client>,
2821        mut cx: AsyncAppContext,
2822    ) -> Result<()> {
2823        this.update(&mut cx, |this, cx| {
2824            this.leader_state
2825                .followers
2826                .remove(&envelope.original_sender_id()?);
2827            cx.notify();
2828            Ok(())
2829        })?
2830    }
2831
2832    async fn handle_update_followers(
2833        this: WeakViewHandle<Self>,
2834        envelope: TypedEnvelope<proto::UpdateFollowers>,
2835        _: Arc<Client>,
2836        cx: AsyncAppContext,
2837    ) -> Result<()> {
2838        let leader_id = envelope.original_sender_id()?;
2839        this.read_with(&cx, |this, _| {
2840            this.leader_updates_tx
2841                .unbounded_send((leader_id, envelope.payload))
2842        })??;
2843        Ok(())
2844    }
2845
2846    async fn process_leader_update(
2847        this: &WeakViewHandle<Self>,
2848        leader_id: PeerId,
2849        update: proto::UpdateFollowers,
2850        cx: &mut AsyncAppContext,
2851    ) -> Result<()> {
2852        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2853            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2854                this.update(cx, |this, _| {
2855                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2856                        for state in state.values_mut() {
2857                            state.active_view_id =
2858                                if let Some(active_view_id) = update_active_view.id.clone() {
2859                                    Some(ViewId::from_proto(active_view_id)?)
2860                                } else {
2861                                    None
2862                                };
2863                        }
2864                    }
2865                    anyhow::Ok(())
2866                })??;
2867            }
2868            proto::update_followers::Variant::UpdateView(update_view) => {
2869                let variant = update_view
2870                    .variant
2871                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2872                let id = update_view
2873                    .id
2874                    .ok_or_else(|| anyhow!("missing update view id"))?;
2875                let mut tasks = Vec::new();
2876                this.update(cx, |this, cx| {
2877                    let project = this.project.clone();
2878                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2879                        for state in state.values_mut() {
2880                            let view_id = ViewId::from_proto(id.clone())?;
2881                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2882                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2883                            }
2884                        }
2885                    }
2886                    anyhow::Ok(())
2887                })??;
2888                try_join_all(tasks).await.log_err();
2889            }
2890            proto::update_followers::Variant::CreateView(view) => {
2891                let panes = this.read_with(cx, |this, _| {
2892                    this.follower_states_by_leader
2893                        .get(&leader_id)
2894                        .into_iter()
2895                        .flat_map(|states_by_pane| states_by_pane.keys())
2896                        .cloned()
2897                        .collect()
2898                })?;
2899                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2900            }
2901        }
2902        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2903        Ok(())
2904    }
2905
2906    async fn add_views_from_leader(
2907        this: WeakViewHandle<Self>,
2908        leader_id: PeerId,
2909        panes: Vec<ViewHandle<Pane>>,
2910        views: Vec<proto::View>,
2911        cx: &mut AsyncAppContext,
2912    ) -> Result<()> {
2913        let this = this
2914            .upgrade(cx)
2915            .ok_or_else(|| anyhow!("workspace dropped"))?;
2916        let project = this
2917            .read_with(cx, |this, _| this.project.clone())
2918            .ok_or_else(|| anyhow!("window dropped"))?;
2919
2920        let replica_id = project
2921            .read_with(cx, |project, _| {
2922                project
2923                    .collaborators()
2924                    .get(&leader_id)
2925                    .map(|c| c.replica_id)
2926            })
2927            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2928
2929        let item_builders = cx.update(|cx| {
2930            cx.default_global::<FollowableItemBuilders>()
2931                .values()
2932                .map(|b| b.0)
2933                .collect::<Vec<_>>()
2934        });
2935
2936        let mut item_tasks_by_pane = HashMap::default();
2937        for pane in panes {
2938            let mut item_tasks = Vec::new();
2939            let mut leader_view_ids = Vec::new();
2940            for view in &views {
2941                let Some(id) = &view.id else { continue };
2942                let id = ViewId::from_proto(id.clone())?;
2943                let mut variant = view.variant.clone();
2944                if variant.is_none() {
2945                    Err(anyhow!("missing view variant"))?;
2946                }
2947                for build_item in &item_builders {
2948                    let task = cx
2949                        .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx));
2950                    if let Some(task) = task {
2951                        item_tasks.push(task);
2952                        leader_view_ids.push(id);
2953                        break;
2954                    } else {
2955                        assert!(variant.is_some());
2956                    }
2957                }
2958            }
2959
2960            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2961        }
2962
2963        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2964            let items = futures::future::try_join_all(item_tasks).await?;
2965            this.update(cx, |this, cx| {
2966                let state = this
2967                    .follower_states_by_leader
2968                    .get_mut(&leader_id)?
2969                    .get_mut(&pane)?;
2970
2971                for (id, item) in leader_view_ids.into_iter().zip(items) {
2972                    item.set_leader_replica_id(Some(replica_id), cx);
2973                    state.items_by_leader_view_id.insert(id, item);
2974                }
2975
2976                Some(())
2977            });
2978        }
2979        Ok(())
2980    }
2981
2982    fn update_active_view_for_followers(&self, cx: &AppContext) {
2983        if self.active_pane.read(cx).has_focus() {
2984            self.update_followers(
2985                proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
2986                    id: self.active_item(cx).and_then(|item| {
2987                        item.to_followable_item_handle(cx)?
2988                            .remote_id(&self.app_state.client, cx)
2989                            .map(|id| id.to_proto())
2990                    }),
2991                    leader_id: self.leader_for_pane(&self.active_pane),
2992                }),
2993                cx,
2994            );
2995        } else {
2996            self.update_followers(
2997                proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
2998                    id: None,
2999                    leader_id: None,
3000                }),
3001                cx,
3002            );
3003        }
3004    }
3005
3006    fn update_followers(
3007        &self,
3008        update: proto::update_followers::Variant,
3009        cx: &AppContext,
3010    ) -> Option<()> {
3011        let project_id = self.project.read(cx).remote_id()?;
3012        if !self.leader_state.followers.is_empty() {
3013            self.app_state
3014                .client
3015                .send(proto::UpdateFollowers {
3016                    project_id,
3017                    follower_ids: self.leader_state.followers.iter().copied().collect(),
3018                    variant: Some(update),
3019                })
3020                .log_err();
3021        }
3022        None
3023    }
3024
3025    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
3026        self.follower_states_by_leader
3027            .iter()
3028            .find_map(|(leader_id, state)| {
3029                if state.contains_key(pane) {
3030                    Some(*leader_id)
3031                } else {
3032                    None
3033                }
3034            })
3035    }
3036
3037    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3038        cx.notify();
3039
3040        let call = self.active_call()?;
3041        let room = call.read(cx).room()?.read(cx);
3042        let participant = room.remote_participant_for_peer_id(leader_id)?;
3043        let mut items_to_activate = Vec::new();
3044        match participant.location {
3045            call::ParticipantLocation::SharedProject { project_id } => {
3046                if Some(project_id) == self.project.read(cx).remote_id() {
3047                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
3048                        if let Some(item) = state
3049                            .active_view_id
3050                            .and_then(|id| state.items_by_leader_view_id.get(&id))
3051                        {
3052                            items_to_activate.push((pane.clone(), item.boxed_clone()));
3053                        } else if let Some(shared_screen) =
3054                            self.shared_screen_for_peer(leader_id, pane, cx)
3055                        {
3056                            items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3057                        }
3058                    }
3059                }
3060            }
3061            call::ParticipantLocation::UnsharedProject => {}
3062            call::ParticipantLocation::External => {
3063                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
3064                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3065                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3066                    }
3067                }
3068            }
3069        }
3070
3071        for (pane, item) in items_to_activate {
3072            let pane_was_focused = pane.read(cx).has_focus();
3073            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3074                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3075            } else {
3076                pane.update(cx, |pane, cx| {
3077                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3078                });
3079            }
3080
3081            if pane_was_focused {
3082                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3083            }
3084        }
3085
3086        None
3087    }
3088
3089    fn shared_screen_for_peer(
3090        &self,
3091        peer_id: PeerId,
3092        pane: &ViewHandle<Pane>,
3093        cx: &mut ViewContext<Self>,
3094    ) -> Option<ViewHandle<SharedScreen>> {
3095        let call = self.active_call()?;
3096        let room = call.read(cx).room()?.read(cx);
3097        let participant = room.remote_participant_for_peer_id(peer_id)?;
3098        let track = participant.video_tracks.values().next()?.clone();
3099        let user = participant.user.clone();
3100
3101        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3102            if item.read(cx).peer_id == peer_id {
3103                return Some(item);
3104            }
3105        }
3106
3107        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3108    }
3109
3110    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
3111        if active {
3112            cx.background()
3113                .spawn(persistence::DB.update_timestamp(self.database_id()))
3114                .detach();
3115        } else {
3116            for pane in &self.panes {
3117                pane.update(cx, |pane, cx| {
3118                    if let Some(item) = pane.active_item() {
3119                        item.workspace_deactivated(cx);
3120                    }
3121                    if matches!(
3122                        settings::get::<WorkspaceSettings>(cx).autosave,
3123                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3124                    ) {
3125                        for item in pane.items() {
3126                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3127                                .detach_and_log_err(cx);
3128                        }
3129                    }
3130                });
3131            }
3132        }
3133    }
3134
3135    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
3136        self.active_call.as_ref().map(|(call, _)| call)
3137    }
3138
3139    fn on_active_call_event(
3140        &mut self,
3141        _: ModelHandle<ActiveCall>,
3142        event: &call::room::Event,
3143        cx: &mut ViewContext<Self>,
3144    ) {
3145        match event {
3146            call::room::Event::ParticipantLocationChanged { participant_id }
3147            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3148                self.leader_updated(*participant_id, cx);
3149            }
3150            _ => {}
3151        }
3152    }
3153
3154    pub fn database_id(&self) -> WorkspaceId {
3155        self.database_id
3156    }
3157
3158    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3159        let project = self.project().read(cx);
3160
3161        if project.is_local() {
3162            Some(
3163                project
3164                    .visible_worktrees(cx)
3165                    .map(|worktree| worktree.read(cx).abs_path())
3166                    .collect::<Vec<_>>()
3167                    .into(),
3168            )
3169        } else {
3170            None
3171        }
3172    }
3173
3174    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3175        match member {
3176            Member::Axis(PaneAxis { members, .. }) => {
3177                for child in members.iter() {
3178                    self.remove_panes(child.clone(), cx)
3179                }
3180            }
3181            Member::Pane(pane) => {
3182                self.force_remove_pane(&pane, cx);
3183            }
3184        }
3185    }
3186
3187    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
3188        self.panes.retain(|p| p != pane);
3189        cx.focus(self.panes.last().unwrap());
3190        if self.last_active_center_pane == Some(pane.downgrade()) {
3191            self.last_active_center_pane = None;
3192        }
3193        cx.notify();
3194    }
3195
3196    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3197        self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
3198            cx.background().timer(Duration::from_millis(100)).await;
3199            this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
3200                .ok();
3201        }));
3202    }
3203
3204    fn serialize_workspace(&self, cx: &ViewContext<Self>) {
3205        fn serialize_pane_handle(
3206            pane_handle: &ViewHandle<Pane>,
3207            cx: &AppContext,
3208        ) -> SerializedPane {
3209            let (items, active) = {
3210                let pane = pane_handle.read(cx);
3211                let active_item_id = pane.active_item().map(|item| item.id());
3212                (
3213                    pane.items()
3214                        .filter_map(|item_handle| {
3215                            Some(SerializedItem {
3216                                kind: Arc::from(item_handle.serialized_item_kind()?),
3217                                item_id: item_handle.id(),
3218                                active: Some(item_handle.id()) == active_item_id,
3219                            })
3220                        })
3221                        .collect::<Vec<_>>(),
3222                    pane.has_focus(),
3223                )
3224            };
3225
3226            SerializedPane::new(items, active)
3227        }
3228
3229        fn build_serialized_pane_group(
3230            pane_group: &Member,
3231            cx: &AppContext,
3232        ) -> SerializedPaneGroup {
3233            match pane_group {
3234                Member::Axis(PaneAxis {
3235                    axis,
3236                    members,
3237                    flexes,
3238                    bounding_boxes: _,
3239                }) => SerializedPaneGroup::Group {
3240                    axis: *axis,
3241                    children: members
3242                        .iter()
3243                        .map(|member| build_serialized_pane_group(member, cx))
3244                        .collect::<Vec<_>>(),
3245                    flexes: Some(flexes.borrow().clone()),
3246                },
3247                Member::Pane(pane_handle) => {
3248                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3249                }
3250            }
3251        }
3252
3253        fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
3254            let left_dock = this.left_dock.read(cx);
3255            let left_visible = left_dock.is_open();
3256            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
3257                Some(
3258                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3259                        .to_string(),
3260                )
3261            });
3262            let left_dock_zoom = left_dock
3263                .visible_panel()
3264                .map(|panel| panel.is_zoomed(cx))
3265                .unwrap_or(false);
3266
3267            let right_dock = this.right_dock.read(cx);
3268            let right_visible = right_dock.is_open();
3269            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
3270                Some(
3271                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3272                        .to_string(),
3273                )
3274            });
3275            let right_dock_zoom = right_dock
3276                .visible_panel()
3277                .map(|panel| panel.is_zoomed(cx))
3278                .unwrap_or(false);
3279
3280            let bottom_dock = this.bottom_dock.read(cx);
3281            let bottom_visible = bottom_dock.is_open();
3282            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
3283                Some(
3284                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3285                        .to_string(),
3286                )
3287            });
3288            let bottom_dock_zoom = bottom_dock
3289                .visible_panel()
3290                .map(|panel| panel.is_zoomed(cx))
3291                .unwrap_or(false);
3292
3293            DockStructure {
3294                left: DockData {
3295                    visible: left_visible,
3296                    active_panel: left_active_panel,
3297                    zoom: left_dock_zoom,
3298                },
3299                right: DockData {
3300                    visible: right_visible,
3301                    active_panel: right_active_panel,
3302                    zoom: right_dock_zoom,
3303                },
3304                bottom: DockData {
3305                    visible: bottom_visible,
3306                    active_panel: bottom_active_panel,
3307                    zoom: bottom_dock_zoom,
3308                },
3309            }
3310        }
3311
3312        if let Some(location) = self.location(cx) {
3313            // Load bearing special case:
3314            //  - with_local_workspace() relies on this to not have other stuff open
3315            //    when you open your log
3316            if !location.paths().is_empty() {
3317                let center_group = build_serialized_pane_group(&self.center.root, cx);
3318                let docks = build_serialized_docks(self, cx);
3319
3320                let serialized_workspace = SerializedWorkspace {
3321                    id: self.database_id,
3322                    location,
3323                    center_group,
3324                    bounds: Default::default(),
3325                    display: Default::default(),
3326                    docks,
3327                };
3328
3329                cx.background()
3330                    .spawn(persistence::DB.save_workspace(serialized_workspace))
3331                    .detach();
3332            }
3333        }
3334    }
3335
3336    pub(crate) fn load_workspace(
3337        workspace: WeakViewHandle<Workspace>,
3338        serialized_workspace: SerializedWorkspace,
3339        paths_to_open: Vec<Option<ProjectPath>>,
3340        cx: &mut AppContext,
3341    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
3342        cx.spawn(|mut cx| async move {
3343            let result = async_iife! {{
3344                let (project, old_center_pane) =
3345                workspace.read_with(&cx, |workspace, _| {
3346                    (
3347                        workspace.project().clone(),
3348                        workspace.last_active_center_pane.clone(),
3349                    )
3350                })?;
3351
3352                let mut center_items = None;
3353                let mut center_group = None;
3354                // Traverse the splits tree and add to things
3355                if let Some((group, active_pane, items)) = serialized_workspace
3356                        .center_group
3357                        .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
3358                        .await {
3359                    center_items = Some(items);
3360                    center_group = Some((group, active_pane))
3361                }
3362
3363                let resulting_list = cx.read(|cx| {
3364                    let mut opened_items = center_items
3365                        .unwrap_or_default()
3366                        .into_iter()
3367                        .filter_map(|item| {
3368                            let item = item?;
3369                            let project_path = item.project_path(cx)?;
3370                            Some((project_path, item))
3371                        })
3372                        .collect::<HashMap<_, _>>();
3373
3374                    paths_to_open
3375                        .into_iter()
3376                        .map(|path_to_open| {
3377                            path_to_open.map(|path_to_open| {
3378                                Ok(opened_items.remove(&path_to_open))
3379                            })
3380                            .transpose()
3381                            .map(|item| item.flatten())
3382                            .transpose()
3383                        })
3384                        .collect::<Vec<_>>()
3385                });
3386
3387                // Remove old panes from workspace panes list
3388                workspace.update(&mut cx, |workspace, cx| {
3389                    if let Some((center_group, active_pane)) = center_group {
3390                        workspace.remove_panes(workspace.center.root.clone(), cx);
3391
3392                        // Swap workspace center group
3393                        workspace.center = PaneGroup::with_root(center_group);
3394
3395                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
3396                        cx.focus_self();
3397
3398                        if let Some(active_pane) = active_pane {
3399                            cx.focus(&active_pane);
3400                        } else {
3401                            cx.focus(workspace.panes.last().unwrap());
3402                        }
3403                    } else {
3404                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3405                        if let Some(old_center_handle) = old_center_handle {
3406                            cx.focus(&old_center_handle)
3407                        } else {
3408                            cx.focus_self()
3409                        }
3410                    }
3411
3412                    let docks = serialized_workspace.docks;
3413                    workspace.left_dock.update(cx, |dock, cx| {
3414                        dock.set_open(docks.left.visible, cx);
3415                        if let Some(active_panel) = docks.left.active_panel {
3416                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3417                                dock.activate_panel(ix, cx);
3418                            }
3419                        }
3420                                dock.active_panel()
3421                                    .map(|panel| {
3422                                        panel.set_zoomed(docks.left.zoom, cx)
3423                                    });
3424                                if docks.left.visible && docks.left.zoom {
3425                                    cx.focus_self()
3426                                }
3427                    });
3428                    // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3429                    workspace.right_dock.update(cx, |dock, cx| {
3430                        dock.set_open(docks.right.visible, cx);
3431                        if let Some(active_panel) = docks.right.active_panel {
3432                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3433                                dock.activate_panel(ix, cx);
3434
3435                            }
3436                        }
3437                                dock.active_panel()
3438                                    .map(|panel| {
3439                                        panel.set_zoomed(docks.right.zoom, cx)
3440                                    });
3441
3442                                if docks.right.visible && docks.right.zoom {
3443                                    cx.focus_self()
3444                                }
3445                    });
3446                    workspace.bottom_dock.update(cx, |dock, cx| {
3447                        dock.set_open(docks.bottom.visible, cx);
3448                        if let Some(active_panel) = docks.bottom.active_panel {
3449                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3450                                dock.activate_panel(ix, cx);
3451                            }
3452                        }
3453
3454                        dock.active_panel()
3455                            .map(|panel| {
3456                                panel.set_zoomed(docks.bottom.zoom, cx)
3457                            });
3458
3459                        if docks.bottom.visible && docks.bottom.zoom {
3460                            cx.focus_self()
3461                        }
3462                    });
3463
3464
3465                    cx.notify();
3466                })?;
3467
3468                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3469                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3470
3471                Ok::<_, anyhow::Error>(resulting_list)
3472            }};
3473
3474            result.await.unwrap_or_default()
3475        })
3476    }
3477
3478    #[cfg(any(test, feature = "test-support"))]
3479    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3480        let client = project.read(cx).client();
3481        let user_store = project.read(cx).user_store();
3482
3483        let channel_store =
3484            cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
3485        let app_state = Arc::new(AppState {
3486            languages: project.read(cx).languages().clone(),
3487            client,
3488            user_store,
3489            channel_store,
3490            fs: project.read(cx).fs().clone(),
3491            build_window_options: |_, _, _| Default::default(),
3492            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3493            background_actions: || &[],
3494        });
3495        Self::new(0, project, app_state, cx)
3496    }
3497
3498    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3499        let dock = match position {
3500            DockPosition::Left => &self.left_dock,
3501            DockPosition::Right => &self.right_dock,
3502            DockPosition::Bottom => &self.bottom_dock,
3503        };
3504        let active_panel = dock.read(cx).visible_panel()?;
3505        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3506            dock.read(cx).render_placeholder(cx)
3507        } else {
3508            ChildView::new(dock, cx).into_any()
3509        };
3510
3511        Some(
3512            element
3513                .constrained()
3514                .dynamically(move |constraint, _, cx| match position {
3515                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3516                        Vector2F::new(20., constraint.min.y()),
3517                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3518                    ),
3519                    DockPosition::Bottom => SizeConstraint::new(
3520                        Vector2F::new(constraint.min.x(), 20.),
3521                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3522                    ),
3523                })
3524                .into_any(),
3525        )
3526    }
3527}
3528
3529fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3530    ZED_WINDOW_POSITION
3531        .zip(*ZED_WINDOW_SIZE)
3532        .map(|(position, size)| {
3533            WindowBounds::Fixed(RectF::new(
3534                cx.platform().screens()[0].bounds().origin() + position,
3535                size,
3536            ))
3537        })
3538}
3539
3540async fn open_items(
3541    serialized_workspace: Option<SerializedWorkspace>,
3542    workspace: &WeakViewHandle<Workspace>,
3543    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3544    app_state: Arc<AppState>,
3545    mut cx: AsyncAppContext,
3546) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
3547    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3548
3549    if let Some(serialized_workspace) = serialized_workspace {
3550        let workspace = workspace.clone();
3551        let restored_items = cx
3552            .update(|cx| {
3553                Workspace::load_workspace(
3554                    workspace,
3555                    serialized_workspace,
3556                    project_paths_to_open
3557                        .iter()
3558                        .map(|(_, project_path)| project_path)
3559                        .cloned()
3560                        .collect(),
3561                    cx,
3562                )
3563            })
3564            .await;
3565
3566        let restored_project_paths = cx.read(|cx| {
3567            restored_items
3568                .iter()
3569                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
3570                .collect::<HashSet<_>>()
3571        });
3572
3573        opened_items = restored_items;
3574        project_paths_to_open
3575            .iter_mut()
3576            .for_each(|(_, project_path)| {
3577                if let Some(project_path_to_open) = project_path {
3578                    if restored_project_paths.contains(project_path_to_open) {
3579                        *project_path = None;
3580                    }
3581                }
3582            });
3583    } else {
3584        for _ in 0..project_paths_to_open.len() {
3585            opened_items.push(None);
3586        }
3587    }
3588    assert!(opened_items.len() == project_paths_to_open.len());
3589
3590    let tasks =
3591        project_paths_to_open
3592            .into_iter()
3593            .enumerate()
3594            .map(|(i, (abs_path, project_path))| {
3595                let workspace = workspace.clone();
3596                cx.spawn(|mut cx| {
3597                    let fs = app_state.fs.clone();
3598                    async move {
3599                        let file_project_path = project_path?;
3600                        if fs.is_file(&abs_path).await {
3601                            Some((
3602                                i,
3603                                workspace
3604                                    .update(&mut cx, |workspace, cx| {
3605                                        workspace.open_path(file_project_path, None, true, cx)
3606                                    })
3607                                    .log_err()?
3608                                    .await,
3609                            ))
3610                        } else {
3611                            None
3612                        }
3613                    }
3614                })
3615            });
3616
3617    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3618        .await
3619        .into_iter()
3620    {
3621        if let Some((i, path_open_result)) = maybe_opened_path {
3622            opened_items[i] = Some(path_open_result);
3623        }
3624    }
3625
3626    opened_items
3627}
3628
3629fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3630    const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3631    const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3632    const MESSAGE_ID: usize = 2;
3633
3634    if workspace
3635        .read_with(cx, |workspace, cx| {
3636            workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3637        })
3638        .unwrap_or(false)
3639    {
3640        return;
3641    }
3642
3643    if db::kvp::KEY_VALUE_STORE
3644        .read_kvp(NEW_DOCK_HINT_KEY)
3645        .ok()
3646        .flatten()
3647        .is_some()
3648    {
3649        if !workspace
3650            .read_with(cx, |workspace, cx| {
3651                workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3652            })
3653            .unwrap_or(false)
3654        {
3655            cx.update(|cx| {
3656                cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3657                    let entry = tracker
3658                        .entry(TypeId::of::<MessageNotification>())
3659                        .or_default();
3660                    if !entry.contains(&MESSAGE_ID) {
3661                        entry.push(MESSAGE_ID);
3662                    }
3663                });
3664            });
3665        }
3666
3667        return;
3668    }
3669
3670    cx.spawn(|_| async move {
3671        db::kvp::KEY_VALUE_STORE
3672            .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3673            .await
3674            .ok();
3675    })
3676    .detach();
3677
3678    workspace
3679        .update(cx, |workspace, cx| {
3680            workspace.show_notification_once(2, cx, |cx| {
3681                cx.add_view(|_| {
3682                    MessageNotification::new_element(|text, _| {
3683                        Text::new(
3684                            "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3685                            text,
3686                        )
3687                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3688                            let code_span_background_color = settings::get::<ThemeSettings>(cx)
3689                                .theme
3690                                .editor
3691                                .document_highlight_read_background;
3692
3693                            cx.scene().push_quad(gpui::Quad {
3694                                bounds,
3695                                background: Some(code_span_background_color),
3696                                border: Default::default(),
3697                                corner_radii: (2.0).into(),
3698                            })
3699                        })
3700                        .into_any()
3701                    })
3702                    .with_click_message("Read more about the new panel system")
3703                    .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3704                })
3705            })
3706        })
3707        .ok();
3708}
3709
3710fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3711    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3712
3713    workspace
3714        .update(cx, |workspace, cx| {
3715            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3716                workspace.show_notification_once(0, cx, |cx| {
3717                    cx.add_view(|_| {
3718                        MessageNotification::new("Failed to load the database file.")
3719                            .with_click_message("Click to let us know about this error")
3720                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3721                    })
3722                });
3723            }
3724        })
3725        .log_err();
3726}
3727
3728impl Entity for Workspace {
3729    type Event = Event;
3730}
3731
3732impl View for Workspace {
3733    fn ui_name() -> &'static str {
3734        "Workspace"
3735    }
3736
3737    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3738        let theme = theme::current(cx).clone();
3739        Stack::new()
3740            .with_child(
3741                Flex::column()
3742                    .with_child(self.render_titlebar(&theme, cx))
3743                    .with_child(
3744                        Stack::new()
3745                            .with_child({
3746                                let project = self.project.clone();
3747                                Flex::row()
3748                                    .with_children(self.render_dock(DockPosition::Left, cx))
3749                                    .with_child(
3750                                        Flex::column()
3751                                            .with_child(
3752                                                FlexItem::new(
3753                                                    self.center.render(
3754                                                        &project,
3755                                                        &theme,
3756                                                        &self.follower_states_by_leader,
3757                                                        self.active_call(),
3758                                                        self.active_pane(),
3759                                                        self.zoomed
3760                                                            .as_ref()
3761                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3762                                                            .as_ref(),
3763                                                        &self.app_state,
3764                                                        cx,
3765                                                    ),
3766                                                )
3767                                                .flex(1., true),
3768                                            )
3769                                            .with_children(
3770                                                self.render_dock(DockPosition::Bottom, cx),
3771                                            )
3772                                            .flex(1., true),
3773                                    )
3774                                    .with_children(self.render_dock(DockPosition::Right, cx))
3775                            })
3776                            .with_child(Overlay::new(
3777                                Stack::new()
3778                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3779                                        enum ZoomBackground {}
3780                                        let zoomed = zoomed.upgrade(cx)?;
3781
3782                                        let mut foreground_style =
3783                                            theme.workspace.zoomed_pane_foreground;
3784                                        if let Some(zoomed_dock_position) = self.zoomed_position {
3785                                            foreground_style =
3786                                                theme.workspace.zoomed_panel_foreground;
3787                                            let margin = foreground_style.margin.top;
3788                                            let border = foreground_style.border.top;
3789
3790                                            // Only include a margin and border on the opposite side.
3791                                            foreground_style.margin.top = 0.;
3792                                            foreground_style.margin.left = 0.;
3793                                            foreground_style.margin.bottom = 0.;
3794                                            foreground_style.margin.right = 0.;
3795                                            foreground_style.border.top = false;
3796                                            foreground_style.border.left = false;
3797                                            foreground_style.border.bottom = false;
3798                                            foreground_style.border.right = false;
3799                                            match zoomed_dock_position {
3800                                                DockPosition::Left => {
3801                                                    foreground_style.margin.right = margin;
3802                                                    foreground_style.border.right = border;
3803                                                }
3804                                                DockPosition::Right => {
3805                                                    foreground_style.margin.left = margin;
3806                                                    foreground_style.border.left = border;
3807                                                }
3808                                                DockPosition::Bottom => {
3809                                                    foreground_style.margin.top = margin;
3810                                                    foreground_style.border.top = border;
3811                                                }
3812                                            }
3813                                        }
3814
3815                                        Some(
3816                                            ChildView::new(&zoomed, cx)
3817                                                .contained()
3818                                                .with_style(foreground_style)
3819                                                .aligned()
3820                                                .contained()
3821                                                .with_style(theme.workspace.zoomed_background)
3822                                                .mouse::<ZoomBackground>(0)
3823                                                .capture_all()
3824                                                .on_down(
3825                                                    MouseButton::Left,
3826                                                    |_, this: &mut Self, cx| {
3827                                                        this.zoom_out(cx);
3828                                                    },
3829                                                ),
3830                                        )
3831                                    }))
3832                                    .with_children(self.modal.as_ref().map(|modal| {
3833                                        // Prevent clicks within the modal from falling
3834                                        // through to the rest of the workspace.
3835                                        enum ModalBackground {}
3836                                        MouseEventHandler::new::<ModalBackground, _>(
3837                                            0,
3838                                            cx,
3839                                            |_, cx| ChildView::new(modal.view.as_any(), cx),
3840                                        )
3841                                        .on_click(MouseButton::Left, |_, _, _| {})
3842                                        .contained()
3843                                        .with_style(theme.workspace.modal)
3844                                        .aligned()
3845                                        .top()
3846                                    }))
3847                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3848                            ))
3849                            .provide_resize_bounds::<WorkspaceBounds>()
3850                            .flex(1.0, true),
3851                    )
3852                    .with_child(ChildView::new(&self.status_bar, cx))
3853                    .contained()
3854                    .with_background_color(theme.workspace.background),
3855            )
3856            .with_children(DragAndDrop::render(cx))
3857            .with_children(self.render_disconnected_overlay(cx))
3858            .into_any_named("workspace")
3859    }
3860
3861    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3862        if cx.is_self_focused() {
3863            cx.focus(&self.active_pane);
3864        }
3865    }
3866
3867    fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3868        DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3869    }
3870}
3871
3872impl ViewId {
3873    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3874        Ok(Self {
3875            creator: message
3876                .creator
3877                .ok_or_else(|| anyhow!("creator is missing"))?,
3878            id: message.id,
3879        })
3880    }
3881
3882    pub(crate) fn to_proto(&self) -> proto::ViewId {
3883        proto::ViewId {
3884            creator: Some(self.creator),
3885            id: self.id,
3886        }
3887    }
3888}
3889
3890pub trait WorkspaceHandle {
3891    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3892}
3893
3894impl WorkspaceHandle for ViewHandle<Workspace> {
3895    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3896        self.read(cx)
3897            .worktrees(cx)
3898            .flat_map(|worktree| {
3899                let worktree_id = worktree.read(cx).id();
3900                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3901                    worktree_id,
3902                    path: f.path.clone(),
3903                })
3904            })
3905            .collect::<Vec<_>>()
3906    }
3907}
3908
3909impl std::fmt::Debug for OpenPaths {
3910    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3911        f.debug_struct("OpenPaths")
3912            .field("paths", &self.paths)
3913            .finish()
3914    }
3915}
3916
3917pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
3918
3919pub fn activate_workspace_for_project(
3920    cx: &mut AsyncAppContext,
3921    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3922) -> Option<WeakViewHandle<Workspace>> {
3923    for window in cx.windows() {
3924        let handle = window
3925            .update(cx, |cx| {
3926                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3927                    let project = workspace_handle.read(cx).project.clone();
3928                    if project.update(cx, &predicate) {
3929                        cx.activate_window();
3930                        return Some(workspace_handle.clone());
3931                    }
3932                }
3933                None
3934            })
3935            .flatten();
3936
3937        if let Some(handle) = handle {
3938            return Some(handle.downgrade());
3939        }
3940    }
3941    None
3942}
3943
3944pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3945    DB.last_workspace().await.log_err().flatten()
3946}
3947
3948#[allow(clippy::type_complexity)]
3949pub fn open_paths(
3950    abs_paths: &[PathBuf],
3951    app_state: &Arc<AppState>,
3952    requesting_window: Option<WindowHandle<Workspace>>,
3953    cx: &mut AppContext,
3954) -> Task<
3955    Result<(
3956        WeakViewHandle<Workspace>,
3957        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3958    )>,
3959> {
3960    let app_state = app_state.clone();
3961    let abs_paths = abs_paths.to_vec();
3962    cx.spawn(|mut cx| async move {
3963        // Open paths in existing workspace if possible
3964        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3965            project.contains_paths(&abs_paths, cx)
3966        });
3967
3968        if let Some(existing) = existing {
3969            Ok((
3970                existing.clone(),
3971                existing
3972                    .update(&mut cx, |workspace, cx| {
3973                        workspace.open_paths(abs_paths, true, cx)
3974                    })?
3975                    .await,
3976            ))
3977        } else {
3978            Ok(cx
3979                .update(|cx| {
3980                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
3981                })
3982                .await)
3983        }
3984    })
3985}
3986
3987pub fn open_new(
3988    app_state: &Arc<AppState>,
3989    cx: &mut AppContext,
3990    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3991) -> Task<()> {
3992    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3993    cx.spawn(|mut cx| async move {
3994        let (workspace, opened_paths) = task.await;
3995
3996        workspace
3997            .update(&mut cx, |workspace, cx| {
3998                if opened_paths.is_empty() {
3999                    init(workspace, cx)
4000                }
4001            })
4002            .log_err();
4003    })
4004}
4005
4006pub fn create_and_open_local_file(
4007    path: &'static Path,
4008    cx: &mut ViewContext<Workspace>,
4009    default_content: impl 'static + Send + FnOnce() -> Rope,
4010) -> Task<Result<Box<dyn ItemHandle>>> {
4011    cx.spawn(|workspace, mut cx| async move {
4012        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
4013        if !fs.is_file(path).await {
4014            fs.create_file(path, Default::default()).await?;
4015            fs.save(path, &default_content(), Default::default())
4016                .await?;
4017        }
4018
4019        let mut items = workspace
4020            .update(&mut cx, |workspace, cx| {
4021                workspace.with_local_workspace(cx, |workspace, cx| {
4022                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4023                })
4024            })?
4025            .await?
4026            .await;
4027
4028        let item = items.pop().flatten();
4029        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4030    })
4031}
4032
4033pub fn join_remote_project(
4034    project_id: u64,
4035    follow_user_id: u64,
4036    app_state: Arc<AppState>,
4037    cx: &mut AppContext,
4038) -> Task<Result<()>> {
4039    cx.spawn(|mut cx| async move {
4040        let existing_workspace = cx
4041            .windows()
4042            .into_iter()
4043            .find_map(|window| {
4044                window.downcast::<Workspace>().and_then(|window| {
4045                    window.read_root_with(&cx, |workspace, cx| {
4046                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4047                            Some(cx.handle().downgrade())
4048                        } else {
4049                            None
4050                        }
4051                    })
4052                })
4053            })
4054            .flatten();
4055
4056        let workspace = if let Some(existing_workspace) = existing_workspace {
4057            existing_workspace
4058        } else {
4059            let active_call = cx.read(ActiveCall::global);
4060            let room = active_call
4061                .read_with(&cx, |call, _| call.room().cloned())
4062                .ok_or_else(|| anyhow!("not in a call"))?;
4063            let project = room
4064                .update(&mut cx, |room, cx| {
4065                    room.join_project(
4066                        project_id,
4067                        app_state.languages.clone(),
4068                        app_state.fs.clone(),
4069                        cx,
4070                    )
4071                })
4072                .await?;
4073
4074            let window_bounds_override = window_bounds_env_override(&cx);
4075            let window = cx.add_window(
4076                (app_state.build_window_options)(
4077                    window_bounds_override,
4078                    None,
4079                    cx.platform().as_ref(),
4080                ),
4081                |cx| Workspace::new(0, project, app_state.clone(), cx),
4082            );
4083            let workspace = window.root(&cx).unwrap();
4084            (app_state.initialize_workspace)(
4085                workspace.downgrade(),
4086                false,
4087                app_state.clone(),
4088                cx.clone(),
4089            )
4090            .await
4091            .log_err();
4092
4093            workspace.downgrade()
4094        };
4095
4096        workspace.window().activate(&mut cx);
4097        cx.platform().activate(true);
4098
4099        workspace.update(&mut cx, |workspace, cx| {
4100            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4101                let follow_peer_id = room
4102                    .read(cx)
4103                    .remote_participants()
4104                    .iter()
4105                    .find(|(_, participant)| participant.user.id == follow_user_id)
4106                    .map(|(_, p)| p.peer_id)
4107                    .or_else(|| {
4108                        // If we couldn't follow the given user, follow the host instead.
4109                        let collaborator = workspace
4110                            .project()
4111                            .read(cx)
4112                            .collaborators()
4113                            .values()
4114                            .find(|collaborator| collaborator.replica_id == 0)?;
4115                        Some(collaborator.peer_id)
4116                    });
4117
4118                if let Some(follow_peer_id) = follow_peer_id {
4119                    if !workspace.is_being_followed(follow_peer_id) {
4120                        workspace
4121                            .toggle_follow(follow_peer_id, cx)
4122                            .map(|follow| follow.detach_and_log_err(cx));
4123                    }
4124                }
4125            }
4126        })?;
4127
4128        anyhow::Ok(())
4129    })
4130}
4131
4132pub fn restart(_: &Restart, cx: &mut AppContext) {
4133    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4134    cx.spawn(|mut cx| async move {
4135        let mut workspace_windows = cx
4136            .windows()
4137            .into_iter()
4138            .filter_map(|window| window.downcast::<Workspace>())
4139            .collect::<Vec<_>>();
4140
4141        // If multiple windows have unsaved changes, and need a save prompt,
4142        // prompt in the active window before switching to a different window.
4143        workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4144
4145        if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4146            let answer = window.prompt(
4147                PromptLevel::Info,
4148                "Are you sure you want to restart?",
4149                &["Restart", "Cancel"],
4150                &mut cx,
4151            );
4152
4153            if let Some(mut answer) = answer {
4154                let answer = answer.next().await;
4155                if answer != Some(0) {
4156                    return Ok(());
4157                }
4158            }
4159        }
4160
4161        // If the user cancels any save prompt, then keep the app open.
4162        for window in workspace_windows {
4163            if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4164                workspace.prepare_to_close(true, cx)
4165            }) {
4166                if !should_close.await? {
4167                    return Ok(());
4168                }
4169            }
4170        }
4171        cx.platform().restart();
4172        anyhow::Ok(())
4173    })
4174    .detach_and_log_err(cx);
4175}
4176
4177fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4178    let mut parts = value.split(',');
4179    let width: usize = parts.next()?.parse().ok()?;
4180    let height: usize = parts.next()?.parse().ok()?;
4181    Some(vec2f(width as f32, height as f32))
4182}
4183
4184#[cfg(test)]
4185mod tests {
4186    use super::*;
4187    use crate::{
4188        dock::test::{TestPanel, TestPanelEvent},
4189        item::test::{TestItem, TestItemEvent, TestProjectItem},
4190    };
4191    use fs::FakeFs;
4192    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4193    use project::{Project, ProjectEntryId};
4194    use serde_json::json;
4195    use settings::SettingsStore;
4196    use std::{cell::RefCell, rc::Rc};
4197
4198    #[gpui::test]
4199    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4200        init_test(cx);
4201
4202        let fs = FakeFs::new(cx.background());
4203        let project = Project::test(fs, [], cx).await;
4204        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4205        let workspace = window.root(cx);
4206
4207        // Adding an item with no ambiguity renders the tab without detail.
4208        let item1 = window.add_view(cx, |_| {
4209            let mut item = TestItem::new();
4210            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4211            item
4212        });
4213        workspace.update(cx, |workspace, cx| {
4214            workspace.add_item(Box::new(item1.clone()), cx);
4215        });
4216        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4217
4218        // Adding an item that creates ambiguity increases the level of detail on
4219        // both tabs.
4220        let item2 = window.add_view(cx, |_| {
4221            let mut item = TestItem::new();
4222            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4223            item
4224        });
4225        workspace.update(cx, |workspace, cx| {
4226            workspace.add_item(Box::new(item2.clone()), cx);
4227        });
4228        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4229        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4230
4231        // Adding an item that creates ambiguity increases the level of detail only
4232        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4233        // we stop at the highest detail available.
4234        let item3 = window.add_view(cx, |_| {
4235            let mut item = TestItem::new();
4236            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4237            item
4238        });
4239        workspace.update(cx, |workspace, cx| {
4240            workspace.add_item(Box::new(item3.clone()), cx);
4241        });
4242        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4243        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4244        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4245    }
4246
4247    #[gpui::test]
4248    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4249        init_test(cx);
4250
4251        let fs = FakeFs::new(cx.background());
4252        fs.insert_tree(
4253            "/root1",
4254            json!({
4255                "one.txt": "",
4256                "two.txt": "",
4257            }),
4258        )
4259        .await;
4260        fs.insert_tree(
4261            "/root2",
4262            json!({
4263                "three.txt": "",
4264            }),
4265        )
4266        .await;
4267
4268        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4269        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4270        let workspace = window.root(cx);
4271        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4272        let worktree_id = project.read_with(cx, |project, cx| {
4273            project.worktrees(cx).next().unwrap().read(cx).id()
4274        });
4275
4276        let item1 = window.add_view(cx, |cx| {
4277            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4278        });
4279        let item2 = window.add_view(cx, |cx| {
4280            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4281        });
4282
4283        // Add an item to an empty pane
4284        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4285        project.read_with(cx, |project, cx| {
4286            assert_eq!(
4287                project.active_entry(),
4288                project
4289                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4290                    .map(|e| e.id)
4291            );
4292        });
4293        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4294
4295        // Add a second item to a non-empty pane
4296        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4297        assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
4298        project.read_with(cx, |project, cx| {
4299            assert_eq!(
4300                project.active_entry(),
4301                project
4302                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4303                    .map(|e| e.id)
4304            );
4305        });
4306
4307        // Close the active item
4308        pane.update(cx, |pane, cx| {
4309            pane.close_active_item(&Default::default(), cx).unwrap()
4310        })
4311        .await
4312        .unwrap();
4313        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4314        project.read_with(cx, |project, cx| {
4315            assert_eq!(
4316                project.active_entry(),
4317                project
4318                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4319                    .map(|e| e.id)
4320            );
4321        });
4322
4323        // Add a project folder
4324        project
4325            .update(cx, |project, cx| {
4326                project.find_or_create_local_worktree("/root2", true, cx)
4327            })
4328            .await
4329            .unwrap();
4330        assert_eq!(
4331            window.current_title(cx).as_deref(),
4332            Some("one.txt β€” root1, root2")
4333        );
4334
4335        // Remove a project folder
4336        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4337        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
4338    }
4339
4340    #[gpui::test]
4341    async fn test_close_window(cx: &mut TestAppContext) {
4342        init_test(cx);
4343
4344        let fs = FakeFs::new(cx.background());
4345        fs.insert_tree("/root", json!({ "one": "" })).await;
4346
4347        let project = Project::test(fs, ["root".as_ref()], cx).await;
4348        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4349        let workspace = window.root(cx);
4350
4351        // When there are no dirty items, there's nothing to do.
4352        let item1 = window.add_view(cx, |_| TestItem::new());
4353        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4354        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4355        assert!(task.await.unwrap());
4356
4357        // When there are dirty untitled items, prompt to save each one. If the user
4358        // cancels any prompt, then abort.
4359        let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
4360        let item3 = window.add_view(cx, |cx| {
4361            TestItem::new()
4362                .with_dirty(true)
4363                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4364        });
4365        workspace.update(cx, |w, cx| {
4366            w.add_item(Box::new(item2.clone()), cx);
4367            w.add_item(Box::new(item3.clone()), cx);
4368        });
4369        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4370        cx.foreground().run_until_parked();
4371        window.simulate_prompt_answer(2, cx); // cancel
4372        cx.foreground().run_until_parked();
4373        assert!(!window.has_pending_prompt(cx));
4374        assert!(!task.await.unwrap());
4375    }
4376
4377    #[gpui::test]
4378    async fn test_close_pane_items(cx: &mut TestAppContext) {
4379        init_test(cx);
4380
4381        let fs = FakeFs::new(cx.background());
4382
4383        let project = Project::test(fs, None, cx).await;
4384        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4385        let workspace = window.root(cx);
4386
4387        let item1 = window.add_view(cx, |cx| {
4388            TestItem::new()
4389                .with_dirty(true)
4390                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4391        });
4392        let item2 = window.add_view(cx, |cx| {
4393            TestItem::new()
4394                .with_dirty(true)
4395                .with_conflict(true)
4396                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4397        });
4398        let item3 = window.add_view(cx, |cx| {
4399            TestItem::new()
4400                .with_dirty(true)
4401                .with_conflict(true)
4402                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4403        });
4404        let item4 = window.add_view(cx, |cx| {
4405            TestItem::new()
4406                .with_dirty(true)
4407                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4408        });
4409        let pane = workspace.update(cx, |workspace, cx| {
4410            workspace.add_item(Box::new(item1.clone()), cx);
4411            workspace.add_item(Box::new(item2.clone()), cx);
4412            workspace.add_item(Box::new(item3.clone()), cx);
4413            workspace.add_item(Box::new(item4.clone()), cx);
4414            workspace.active_pane().clone()
4415        });
4416
4417        let close_items = pane.update(cx, |pane, cx| {
4418            pane.activate_item(1, true, true, cx);
4419            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4420            let item1_id = item1.id();
4421            let item3_id = item3.id();
4422            let item4_id = item4.id();
4423            pane.close_items(cx, SaveIntent::Close, move |id| {
4424                [item1_id, item3_id, item4_id].contains(&id)
4425            })
4426        });
4427        cx.foreground().run_until_parked();
4428
4429        // There's a prompt to save item 1.
4430        pane.read_with(cx, |pane, _| {
4431            assert_eq!(pane.items_len(), 4);
4432            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4433        });
4434        assert!(window.has_pending_prompt(cx));
4435
4436        // Confirm saving item 1.
4437        window.simulate_prompt_answer(0, cx);
4438        cx.foreground().run_until_parked();
4439
4440        // Item 1 is saved. There's a prompt to save item 3.
4441        pane.read_with(cx, |pane, cx| {
4442            assert_eq!(item1.read(cx).save_count, 1);
4443            assert_eq!(item1.read(cx).save_as_count, 0);
4444            assert_eq!(item1.read(cx).reload_count, 0);
4445            assert_eq!(pane.items_len(), 3);
4446            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4447        });
4448        assert!(window.has_pending_prompt(cx));
4449
4450        // Cancel saving item 3.
4451        window.simulate_prompt_answer(1, cx);
4452        cx.foreground().run_until_parked();
4453
4454        // Item 3 is reloaded. There's a prompt to save item 4.
4455        pane.read_with(cx, |pane, cx| {
4456            assert_eq!(item3.read(cx).save_count, 0);
4457            assert_eq!(item3.read(cx).save_as_count, 0);
4458            assert_eq!(item3.read(cx).reload_count, 1);
4459            assert_eq!(pane.items_len(), 2);
4460            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4461        });
4462        assert!(window.has_pending_prompt(cx));
4463
4464        // Confirm saving item 4.
4465        window.simulate_prompt_answer(0, cx);
4466        cx.foreground().run_until_parked();
4467
4468        // There's a prompt for a path for item 4.
4469        cx.simulate_new_path_selection(|_| Some(Default::default()));
4470        close_items.await.unwrap();
4471
4472        // The requested items are closed.
4473        pane.read_with(cx, |pane, cx| {
4474            assert_eq!(item4.read(cx).save_count, 0);
4475            assert_eq!(item4.read(cx).save_as_count, 1);
4476            assert_eq!(item4.read(cx).reload_count, 0);
4477            assert_eq!(pane.items_len(), 1);
4478            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4479        });
4480    }
4481
4482    #[gpui::test]
4483    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4484        init_test(cx);
4485
4486        let fs = FakeFs::new(cx.background());
4487
4488        let project = Project::test(fs, [], cx).await;
4489        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4490        let workspace = window.root(cx);
4491
4492        // Create several workspace items with single project entries, and two
4493        // workspace items with multiple project entries.
4494        let single_entry_items = (0..=4)
4495            .map(|project_entry_id| {
4496                window.add_view(cx, |cx| {
4497                    TestItem::new()
4498                        .with_dirty(true)
4499                        .with_project_items(&[TestProjectItem::new(
4500                            project_entry_id,
4501                            &format!("{project_entry_id}.txt"),
4502                            cx,
4503                        )])
4504                })
4505            })
4506            .collect::<Vec<_>>();
4507        let item_2_3 = window.add_view(cx, |cx| {
4508            TestItem::new()
4509                .with_dirty(true)
4510                .with_singleton(false)
4511                .with_project_items(&[
4512                    single_entry_items[2].read(cx).project_items[0].clone(),
4513                    single_entry_items[3].read(cx).project_items[0].clone(),
4514                ])
4515        });
4516        let item_3_4 = window.add_view(cx, |cx| {
4517            TestItem::new()
4518                .with_dirty(true)
4519                .with_singleton(false)
4520                .with_project_items(&[
4521                    single_entry_items[3].read(cx).project_items[0].clone(),
4522                    single_entry_items[4].read(cx).project_items[0].clone(),
4523                ])
4524        });
4525
4526        // Create two panes that contain the following project entries:
4527        //   left pane:
4528        //     multi-entry items:   (2, 3)
4529        //     single-entry items:  0, 1, 2, 3, 4
4530        //   right pane:
4531        //     single-entry items:  1
4532        //     multi-entry items:   (3, 4)
4533        let left_pane = workspace.update(cx, |workspace, cx| {
4534            let left_pane = workspace.active_pane().clone();
4535            workspace.add_item(Box::new(item_2_3.clone()), cx);
4536            for item in single_entry_items {
4537                workspace.add_item(Box::new(item), cx);
4538            }
4539            left_pane.update(cx, |pane, cx| {
4540                pane.activate_item(2, true, true, cx);
4541            });
4542
4543            workspace
4544                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4545                .unwrap();
4546
4547            left_pane
4548        });
4549
4550        //Need to cause an effect flush in order to respect new focus
4551        workspace.update(cx, |workspace, cx| {
4552            workspace.add_item(Box::new(item_3_4.clone()), cx);
4553            cx.focus(&left_pane);
4554        });
4555
4556        // When closing all of the items in the left pane, we should be prompted twice:
4557        // once for project entry 0, and once for project entry 2. After those two
4558        // prompts, the task should complete.
4559
4560        let close = left_pane.update(cx, |pane, cx| {
4561            pane.close_items(cx, SaveIntent::Close, move |_| true)
4562        });
4563        cx.foreground().run_until_parked();
4564        left_pane.read_with(cx, |pane, cx| {
4565            assert_eq!(
4566                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4567                &[ProjectEntryId::from_proto(0)]
4568            );
4569        });
4570        window.simulate_prompt_answer(0, cx);
4571
4572        cx.foreground().run_until_parked();
4573        left_pane.read_with(cx, |pane, cx| {
4574            assert_eq!(
4575                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4576                &[ProjectEntryId::from_proto(2)]
4577            );
4578        });
4579        window.simulate_prompt_answer(0, cx);
4580
4581        cx.foreground().run_until_parked();
4582        close.await.unwrap();
4583        left_pane.read_with(cx, |pane, _| {
4584            assert_eq!(pane.items_len(), 0);
4585        });
4586    }
4587
4588    #[gpui::test]
4589    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4590        init_test(cx);
4591
4592        let fs = FakeFs::new(cx.background());
4593
4594        let project = Project::test(fs, [], cx).await;
4595        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4596        let workspace = window.root(cx);
4597        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4598
4599        let item = window.add_view(cx, |cx| {
4600            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4601        });
4602        let item_id = item.id();
4603        workspace.update(cx, |workspace, cx| {
4604            workspace.add_item(Box::new(item.clone()), cx);
4605        });
4606
4607        // Autosave on window change.
4608        item.update(cx, |item, cx| {
4609            cx.update_global(|settings: &mut SettingsStore, cx| {
4610                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4611                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4612                })
4613            });
4614            item.is_dirty = true;
4615        });
4616
4617        // Deactivating the window saves the file.
4618        window.simulate_deactivation(cx);
4619        deterministic.run_until_parked();
4620        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4621
4622        // Autosave on focus change.
4623        item.update(cx, |item, cx| {
4624            cx.focus_self();
4625            cx.update_global(|settings: &mut SettingsStore, cx| {
4626                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4627                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4628                })
4629            });
4630            item.is_dirty = true;
4631        });
4632
4633        // Blurring the item saves the file.
4634        item.update(cx, |_, cx| cx.blur());
4635        deterministic.run_until_parked();
4636        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4637
4638        // Deactivating the window still saves the file.
4639        window.simulate_activation(cx);
4640        item.update(cx, |item, cx| {
4641            cx.focus_self();
4642            item.is_dirty = true;
4643        });
4644        window.simulate_deactivation(cx);
4645
4646        deterministic.run_until_parked();
4647        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4648
4649        // Autosave after delay.
4650        item.update(cx, |item, cx| {
4651            cx.update_global(|settings: &mut SettingsStore, cx| {
4652                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4653                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4654                })
4655            });
4656            item.is_dirty = true;
4657            cx.emit(TestItemEvent::Edit);
4658        });
4659
4660        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4661        deterministic.advance_clock(Duration::from_millis(250));
4662        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4663
4664        // After delay expires, the file is saved.
4665        deterministic.advance_clock(Duration::from_millis(250));
4666        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4667
4668        // Autosave on focus change, ensuring closing the tab counts as such.
4669        item.update(cx, |item, cx| {
4670            cx.update_global(|settings: &mut SettingsStore, cx| {
4671                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4672                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4673                })
4674            });
4675            item.is_dirty = true;
4676        });
4677
4678        pane.update(cx, |pane, cx| {
4679            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4680        })
4681        .await
4682        .unwrap();
4683        assert!(!window.has_pending_prompt(cx));
4684        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4685
4686        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4687        workspace.update(cx, |workspace, cx| {
4688            workspace.add_item(Box::new(item.clone()), cx);
4689        });
4690        item.update(cx, |item, cx| {
4691            item.project_items[0].update(cx, |item, _| {
4692                item.entry_id = None;
4693            });
4694            item.is_dirty = true;
4695            cx.blur();
4696        });
4697        deterministic.run_until_parked();
4698        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4699
4700        // Ensure autosave is prevented for deleted files also when closing the buffer.
4701        let _close_items = pane.update(cx, |pane, cx| {
4702            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4703        });
4704        deterministic.run_until_parked();
4705        assert!(window.has_pending_prompt(cx));
4706        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4707    }
4708
4709    #[gpui::test]
4710    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4711        init_test(cx);
4712
4713        let fs = FakeFs::new(cx.background());
4714
4715        let project = Project::test(fs, [], cx).await;
4716        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4717        let workspace = window.root(cx);
4718
4719        let item = window.add_view(cx, |cx| {
4720            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4721        });
4722        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4723        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4724        let toolbar_notify_count = Rc::new(RefCell::new(0));
4725
4726        workspace.update(cx, |workspace, cx| {
4727            workspace.add_item(Box::new(item.clone()), cx);
4728            let toolbar_notification_count = toolbar_notify_count.clone();
4729            cx.observe(&toolbar, move |_, _, _| {
4730                *toolbar_notification_count.borrow_mut() += 1
4731            })
4732            .detach();
4733        });
4734
4735        pane.read_with(cx, |pane, _| {
4736            assert!(!pane.can_navigate_backward());
4737            assert!(!pane.can_navigate_forward());
4738        });
4739
4740        item.update(cx, |item, cx| {
4741            item.set_state("one".to_string(), cx);
4742        });
4743
4744        // Toolbar must be notified to re-render the navigation buttons
4745        assert_eq!(*toolbar_notify_count.borrow(), 1);
4746
4747        pane.read_with(cx, |pane, _| {
4748            assert!(pane.can_navigate_backward());
4749            assert!(!pane.can_navigate_forward());
4750        });
4751
4752        workspace
4753            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4754            .await
4755            .unwrap();
4756
4757        assert_eq!(*toolbar_notify_count.borrow(), 3);
4758        pane.read_with(cx, |pane, _| {
4759            assert!(!pane.can_navigate_backward());
4760            assert!(pane.can_navigate_forward());
4761        });
4762    }
4763
4764    #[gpui::test]
4765    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4766        init_test(cx);
4767        let fs = FakeFs::new(cx.background());
4768
4769        let project = Project::test(fs, [], cx).await;
4770        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4771        let workspace = window.root(cx);
4772
4773        let panel = workspace.update(cx, |workspace, cx| {
4774            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4775            workspace.add_panel(panel.clone(), cx);
4776
4777            workspace
4778                .right_dock()
4779                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4780
4781            panel
4782        });
4783
4784        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4785        pane.update(cx, |pane, cx| {
4786            let item = cx.add_view(|_| TestItem::new());
4787            pane.add_item(Box::new(item), true, true, None, cx);
4788        });
4789
4790        // Transfer focus from center to panel
4791        workspace.update(cx, |workspace, cx| {
4792            workspace.toggle_panel_focus::<TestPanel>(cx);
4793        });
4794
4795        workspace.read_with(cx, |workspace, cx| {
4796            assert!(workspace.right_dock().read(cx).is_open());
4797            assert!(!panel.is_zoomed(cx));
4798            assert!(panel.has_focus(cx));
4799        });
4800
4801        // Transfer focus from panel to center
4802        workspace.update(cx, |workspace, cx| {
4803            workspace.toggle_panel_focus::<TestPanel>(cx);
4804        });
4805
4806        workspace.read_with(cx, |workspace, cx| {
4807            assert!(workspace.right_dock().read(cx).is_open());
4808            assert!(!panel.is_zoomed(cx));
4809            assert!(!panel.has_focus(cx));
4810        });
4811
4812        // Close the dock
4813        workspace.update(cx, |workspace, cx| {
4814            workspace.toggle_dock(DockPosition::Right, cx);
4815        });
4816
4817        workspace.read_with(cx, |workspace, cx| {
4818            assert!(!workspace.right_dock().read(cx).is_open());
4819            assert!(!panel.is_zoomed(cx));
4820            assert!(!panel.has_focus(cx));
4821        });
4822
4823        // Open the dock
4824        workspace.update(cx, |workspace, cx| {
4825            workspace.toggle_dock(DockPosition::Right, cx);
4826        });
4827
4828        workspace.read_with(cx, |workspace, cx| {
4829            assert!(workspace.right_dock().read(cx).is_open());
4830            assert!(!panel.is_zoomed(cx));
4831            assert!(panel.has_focus(cx));
4832        });
4833
4834        // Focus and zoom panel
4835        panel.update(cx, |panel, cx| {
4836            cx.focus_self();
4837            panel.set_zoomed(true, cx)
4838        });
4839
4840        workspace.read_with(cx, |workspace, cx| {
4841            assert!(workspace.right_dock().read(cx).is_open());
4842            assert!(panel.is_zoomed(cx));
4843            assert!(panel.has_focus(cx));
4844        });
4845
4846        // Transfer focus to the center closes the dock
4847        workspace.update(cx, |workspace, cx| {
4848            workspace.toggle_panel_focus::<TestPanel>(cx);
4849        });
4850
4851        workspace.read_with(cx, |workspace, cx| {
4852            assert!(!workspace.right_dock().read(cx).is_open());
4853            assert!(panel.is_zoomed(cx));
4854            assert!(!panel.has_focus(cx));
4855        });
4856
4857        // Transferring focus back to the panel keeps it zoomed
4858        workspace.update(cx, |workspace, cx| {
4859            workspace.toggle_panel_focus::<TestPanel>(cx);
4860        });
4861
4862        workspace.read_with(cx, |workspace, cx| {
4863            assert!(workspace.right_dock().read(cx).is_open());
4864            assert!(panel.is_zoomed(cx));
4865            assert!(panel.has_focus(cx));
4866        });
4867
4868        // Close the dock while it is zoomed
4869        workspace.update(cx, |workspace, cx| {
4870            workspace.toggle_dock(DockPosition::Right, cx)
4871        });
4872
4873        workspace.read_with(cx, |workspace, cx| {
4874            assert!(!workspace.right_dock().read(cx).is_open());
4875            assert!(panel.is_zoomed(cx));
4876            assert!(workspace.zoomed.is_none());
4877            assert!(!panel.has_focus(cx));
4878        });
4879
4880        // Opening the dock, when it's zoomed, retains focus
4881        workspace.update(cx, |workspace, cx| {
4882            workspace.toggle_dock(DockPosition::Right, cx)
4883        });
4884
4885        workspace.read_with(cx, |workspace, cx| {
4886            assert!(workspace.right_dock().read(cx).is_open());
4887            assert!(panel.is_zoomed(cx));
4888            assert!(workspace.zoomed.is_some());
4889            assert!(panel.has_focus(cx));
4890        });
4891
4892        // Unzoom and close the panel, zoom the active pane.
4893        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
4894        workspace.update(cx, |workspace, cx| {
4895            workspace.toggle_dock(DockPosition::Right, cx)
4896        });
4897        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
4898
4899        // Opening a dock unzooms the pane.
4900        workspace.update(cx, |workspace, cx| {
4901            workspace.toggle_dock(DockPosition::Right, cx)
4902        });
4903        workspace.read_with(cx, |workspace, cx| {
4904            let pane = pane.read(cx);
4905            assert!(!pane.is_zoomed());
4906            assert!(!pane.has_focus());
4907            assert!(workspace.right_dock().read(cx).is_open());
4908            assert!(workspace.zoomed.is_none());
4909        });
4910    }
4911
4912    #[gpui::test]
4913    async fn test_panels(cx: &mut gpui::TestAppContext) {
4914        init_test(cx);
4915        let fs = FakeFs::new(cx.background());
4916
4917        let project = Project::test(fs, [], cx).await;
4918        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4919        let workspace = window.root(cx);
4920
4921        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4922            // Add panel_1 on the left, panel_2 on the right.
4923            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4924            workspace.add_panel(panel_1.clone(), cx);
4925            workspace
4926                .left_dock()
4927                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4928            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4929            workspace.add_panel(panel_2.clone(), cx);
4930            workspace
4931                .right_dock()
4932                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4933
4934            let left_dock = workspace.left_dock();
4935            assert_eq!(
4936                left_dock.read(cx).visible_panel().unwrap().id(),
4937                panel_1.id()
4938            );
4939            assert_eq!(
4940                left_dock.read(cx).active_panel_size(cx).unwrap(),
4941                panel_1.size(cx)
4942            );
4943
4944            left_dock.update(cx, |left_dock, cx| {
4945                left_dock.resize_active_panel(Some(1337.), cx)
4946            });
4947            assert_eq!(
4948                workspace
4949                    .right_dock()
4950                    .read(cx)
4951                    .visible_panel()
4952                    .unwrap()
4953                    .id(),
4954                panel_2.id()
4955            );
4956
4957            (panel_1, panel_2)
4958        });
4959
4960        // Move panel_1 to the right
4961        panel_1.update(cx, |panel_1, cx| {
4962            panel_1.set_position(DockPosition::Right, cx)
4963        });
4964
4965        workspace.update(cx, |workspace, cx| {
4966            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4967            // Since it was the only panel on the left, the left dock should now be closed.
4968            assert!(!workspace.left_dock().read(cx).is_open());
4969            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
4970            let right_dock = workspace.right_dock();
4971            assert_eq!(
4972                right_dock.read(cx).visible_panel().unwrap().id(),
4973                panel_1.id()
4974            );
4975            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4976
4977            // Now we move panel_2Β to the left
4978            panel_2.set_position(DockPosition::Left, cx);
4979        });
4980
4981        workspace.update(cx, |workspace, cx| {
4982            // Since panel_2 was not visible on the right, we don't open the left dock.
4983            assert!(!workspace.left_dock().read(cx).is_open());
4984            // And the right dock is unaffected in it's displaying of panel_1
4985            assert!(workspace.right_dock().read(cx).is_open());
4986            assert_eq!(
4987                workspace
4988                    .right_dock()
4989                    .read(cx)
4990                    .visible_panel()
4991                    .unwrap()
4992                    .id(),
4993                panel_1.id()
4994            );
4995        });
4996
4997        // Move panel_1 back to the left
4998        panel_1.update(cx, |panel_1, cx| {
4999            panel_1.set_position(DockPosition::Left, cx)
5000        });
5001
5002        workspace.update(cx, |workspace, cx| {
5003            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5004            let left_dock = workspace.left_dock();
5005            assert!(left_dock.read(cx).is_open());
5006            assert_eq!(
5007                left_dock.read(cx).visible_panel().unwrap().id(),
5008                panel_1.id()
5009            );
5010            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5011            // And right the dock should be closed as it no longer has any panels.
5012            assert!(!workspace.right_dock().read(cx).is_open());
5013
5014            // Now we move panel_1 to the bottom
5015            panel_1.set_position(DockPosition::Bottom, cx);
5016        });
5017
5018        workspace.update(cx, |workspace, cx| {
5019            // Since panel_1 was visible on the left, we close the left dock.
5020            assert!(!workspace.left_dock().read(cx).is_open());
5021            // The bottom dock is sized based on the panel's default size,
5022            // since the panel orientation changed from vertical to horizontal.
5023            let bottom_dock = workspace.bottom_dock();
5024            assert_eq!(
5025                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5026                panel_1.size(cx),
5027            );
5028            // Close bottom dock and move panel_1 back to the left.
5029            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5030            panel_1.set_position(DockPosition::Left, cx);
5031        });
5032
5033        // Emit activated event on panel 1
5034        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5035
5036        // Now the left dock is open and panel_1 is active and focused.
5037        workspace.read_with(cx, |workspace, cx| {
5038            let left_dock = workspace.left_dock();
5039            assert!(left_dock.read(cx).is_open());
5040            assert_eq!(
5041                left_dock.read(cx).visible_panel().unwrap().id(),
5042                panel_1.id()
5043            );
5044            assert!(panel_1.is_focused(cx));
5045        });
5046
5047        // Emit closed event on panel 2, which is not active
5048        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5049
5050        // Wo don't close the left dock, because panel_2 wasn't the active panel
5051        workspace.read_with(cx, |workspace, cx| {
5052            let left_dock = workspace.left_dock();
5053            assert!(left_dock.read(cx).is_open());
5054            assert_eq!(
5055                left_dock.read(cx).visible_panel().unwrap().id(),
5056                panel_1.id()
5057            );
5058        });
5059
5060        // Emitting a ZoomIn event shows the panel as zoomed.
5061        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5062        workspace.read_with(cx, |workspace, _| {
5063            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5064            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5065        });
5066
5067        // Move panel to another dock while it is zoomed
5068        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5069        workspace.read_with(cx, |workspace, _| {
5070            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5071            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5072        });
5073
5074        // If focus is transferred to another view that's not a panel or another pane, we still show
5075        // the panel as zoomed.
5076        let focus_receiver = window.add_view(cx, |_| EmptyView);
5077        focus_receiver.update(cx, |_, cx| cx.focus_self());
5078        workspace.read_with(cx, |workspace, _| {
5079            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5080            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5081        });
5082
5083        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5084        workspace.update(cx, |_, cx| cx.focus_self());
5085        workspace.read_with(cx, |workspace, _| {
5086            assert_eq!(workspace.zoomed, None);
5087            assert_eq!(workspace.zoomed_position, None);
5088        });
5089
5090        // If focus is transferred again to another view that's not a panel or a pane, we won't
5091        // show the panel as zoomed because it wasn't zoomed before.
5092        focus_receiver.update(cx, |_, cx| cx.focus_self());
5093        workspace.read_with(cx, |workspace, _| {
5094            assert_eq!(workspace.zoomed, None);
5095            assert_eq!(workspace.zoomed_position, None);
5096        });
5097
5098        // When focus is transferred back to the panel, it is zoomed again.
5099        panel_1.update(cx, |_, cx| cx.focus_self());
5100        workspace.read_with(cx, |workspace, _| {
5101            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5102            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5103        });
5104
5105        // Emitting a ZoomOut event unzooms the panel.
5106        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5107        workspace.read_with(cx, |workspace, _| {
5108            assert_eq!(workspace.zoomed, None);
5109            assert_eq!(workspace.zoomed_position, None);
5110        });
5111
5112        // Emit closed event on panel 1, which is active
5113        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5114
5115        // Now the left dock is closed, because panel_1 was the active panel
5116        workspace.read_with(cx, |workspace, cx| {
5117            let right_dock = workspace.right_dock();
5118            assert!(!right_dock.read(cx).is_open());
5119        });
5120    }
5121
5122    pub fn init_test(cx: &mut TestAppContext) {
5123        cx.foreground().forbid_parking();
5124        cx.update(|cx| {
5125            cx.set_global(SettingsStore::test(cx));
5126            theme::init((), cx);
5127            language::init(cx);
5128            crate::init_settings(cx);
5129            Project::init_settings(cx);
5130        });
5131    }
5132}