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_intent: Option<SaveIntent>,
 167}
 168
 169#[derive(Clone, PartialEq, Debug, Deserialize)]
 170#[serde(rename_all = "camelCase")]
 171pub struct Save {
 172    pub save_intent: Option<SaveIntent>,
 173}
 174
 175#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 176#[serde(rename_all = "camelCase")]
 177pub struct CloseAllItemsAndPanes {
 178    pub save_intent: 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_intent.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_intent.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_intent: 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_intent,
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_intent: 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(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
1694                    .await
1695                    .map(|_| ())
1696            } else {
1697                Ok(())
1698            }
1699        })
1700    }
1701
1702    pub fn close_inactive_items_and_panes(
1703        &mut self,
1704        _: &CloseInactiveTabsAndPanes,
1705        cx: &mut ViewContext<Self>,
1706    ) -> Option<Task<Result<()>>> {
1707        self.close_all_internal(true, SaveIntent::Close, cx)
1708    }
1709
1710    pub fn close_all_items_and_panes(
1711        &mut self,
1712        action: &CloseAllItemsAndPanes,
1713        cx: &mut ViewContext<Self>,
1714    ) -> Option<Task<Result<()>>> {
1715        self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1716    }
1717
1718    fn close_all_internal(
1719        &mut self,
1720        retain_active_pane: bool,
1721        save_intent: SaveIntent,
1722        cx: &mut ViewContext<Self>,
1723    ) -> Option<Task<Result<()>>> {
1724        let current_pane = self.active_pane();
1725
1726        let mut tasks = Vec::new();
1727
1728        if retain_active_pane {
1729            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1730                pane.close_inactive_items(&CloseInactiveItems, cx)
1731            }) {
1732                tasks.push(current_pane_close);
1733            };
1734        }
1735
1736        for pane in self.panes() {
1737            if retain_active_pane && pane.id() == current_pane.id() {
1738                continue;
1739            }
1740
1741            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1742                pane.close_all_items(
1743                    &CloseAllItems {
1744                        save_intent: Some(save_intent),
1745                    },
1746                    cx,
1747                )
1748            }) {
1749                tasks.push(close_pane_items)
1750            }
1751        }
1752
1753        if tasks.is_empty() {
1754            None
1755        } else {
1756            Some(cx.spawn(|_, _| async move {
1757                for task in tasks {
1758                    task.await?
1759                }
1760                Ok(())
1761            }))
1762        }
1763    }
1764
1765    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1766        let dock = match dock_side {
1767            DockPosition::Left => &self.left_dock,
1768            DockPosition::Bottom => &self.bottom_dock,
1769            DockPosition::Right => &self.right_dock,
1770        };
1771        let mut focus_center = false;
1772        let mut reveal_dock = false;
1773        dock.update(cx, |dock, cx| {
1774            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1775            let was_visible = dock.is_open() && !other_is_zoomed;
1776            dock.set_open(!was_visible, cx);
1777
1778            if let Some(active_panel) = dock.active_panel() {
1779                if was_visible {
1780                    if active_panel.has_focus(cx) {
1781                        focus_center = true;
1782                    }
1783                } else {
1784                    cx.focus(active_panel.as_any());
1785                    reveal_dock = true;
1786                }
1787            }
1788        });
1789
1790        if reveal_dock {
1791            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1792        }
1793
1794        if focus_center {
1795            cx.focus_self();
1796        }
1797
1798        cx.notify();
1799        self.serialize_workspace(cx);
1800    }
1801
1802    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1803        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1804
1805        for dock in docks {
1806            dock.update(cx, |dock, cx| {
1807                dock.set_open(false, cx);
1808            });
1809        }
1810
1811        cx.focus_self();
1812        cx.notify();
1813        self.serialize_workspace(cx);
1814    }
1815
1816    /// Transfer focus to the panel of the given type.
1817    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
1818        self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
1819            .as_any()
1820            .clone()
1821            .downcast()
1822    }
1823
1824    /// Focus the panel of the given type if it isn't already focused. If it is
1825    /// already focused, then transfer focus back to the workspace center.
1826    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1827        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
1828    }
1829
1830    /// Focus or unfocus the given panel type, depending on the given callback.
1831    fn focus_or_unfocus_panel<T: Panel>(
1832        &mut self,
1833        cx: &mut ViewContext<Self>,
1834        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1835    ) -> Option<Rc<dyn PanelHandle>> {
1836        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1837            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1838                let mut focus_center = false;
1839                let mut reveal_dock = false;
1840                let panel = dock.update(cx, |dock, cx| {
1841                    dock.activate_panel(panel_index, cx);
1842
1843                    let panel = dock.active_panel().cloned();
1844                    if let Some(panel) = panel.as_ref() {
1845                        if should_focus(&**panel, cx) {
1846                            dock.set_open(true, cx);
1847                            cx.focus(panel.as_any());
1848                            reveal_dock = true;
1849                        } else {
1850                            // if panel.is_zoomed(cx) {
1851                            //     dock.set_open(false, cx);
1852                            // }
1853                            focus_center = true;
1854                        }
1855                    }
1856                    panel
1857                });
1858
1859                if focus_center {
1860                    cx.focus_self();
1861                }
1862
1863                self.serialize_workspace(cx);
1864                cx.notify();
1865                return panel;
1866            }
1867        }
1868        None
1869    }
1870
1871    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<ViewHandle<T>> {
1872        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1873            let dock = dock.read(cx);
1874            if let Some(panel) = dock.panel::<T>() {
1875                return Some(panel);
1876            }
1877        }
1878        None
1879    }
1880
1881    fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
1882        for pane in &self.panes {
1883            pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1884        }
1885
1886        self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1887        self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1888        self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1889        self.zoomed = None;
1890        self.zoomed_position = None;
1891
1892        cx.notify();
1893    }
1894
1895    #[cfg(any(test, feature = "test-support"))]
1896    pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
1897        self.zoomed.and_then(|view| view.upgrade(cx))
1898    }
1899
1900    fn dismiss_zoomed_items_to_reveal(
1901        &mut self,
1902        dock_to_reveal: Option<DockPosition>,
1903        cx: &mut ViewContext<Self>,
1904    ) {
1905        // If a center pane is zoomed, unzoom it.
1906        for pane in &self.panes {
1907            if pane != &self.active_pane || dock_to_reveal.is_some() {
1908                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1909            }
1910        }
1911
1912        // If another dock is zoomed, hide it.
1913        let mut focus_center = false;
1914        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1915            dock.update(cx, |dock, cx| {
1916                if Some(dock.position()) != dock_to_reveal {
1917                    if let Some(panel) = dock.active_panel() {
1918                        if panel.is_zoomed(cx) {
1919                            focus_center |= panel.has_focus(cx);
1920                            dock.set_open(false, cx);
1921                        }
1922                    }
1923                }
1924            });
1925        }
1926
1927        if focus_center {
1928            cx.focus_self();
1929        }
1930
1931        if self.zoomed_position != dock_to_reveal {
1932            self.zoomed = None;
1933            self.zoomed_position = None;
1934        }
1935
1936        cx.notify();
1937    }
1938
1939    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1940        let pane = cx.add_view(|cx| {
1941            Pane::new(
1942                self.weak_handle(),
1943                self.project.clone(),
1944                self.app_state.background_actions,
1945                self.pane_history_timestamp.clone(),
1946                cx,
1947            )
1948        });
1949        cx.subscribe(&pane, Self::handle_pane_event).detach();
1950        self.panes.push(pane.clone());
1951        cx.focus(&pane);
1952        cx.emit(Event::PaneAdded(pane.clone()));
1953        pane
1954    }
1955
1956    pub fn add_item_to_center(
1957        &mut self,
1958        item: Box<dyn ItemHandle>,
1959        cx: &mut ViewContext<Self>,
1960    ) -> bool {
1961        if let Some(center_pane) = self.last_active_center_pane.clone() {
1962            if let Some(center_pane) = center_pane.upgrade(cx) {
1963                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1964                true
1965            } else {
1966                false
1967            }
1968        } else {
1969            false
1970        }
1971    }
1972
1973    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1974        self.active_pane
1975            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1976    }
1977
1978    pub fn split_item(
1979        &mut self,
1980        split_direction: SplitDirection,
1981        item: Box<dyn ItemHandle>,
1982        cx: &mut ViewContext<Self>,
1983    ) {
1984        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
1985        new_pane.update(cx, move |new_pane, cx| {
1986            new_pane.add_item(item, true, true, None, cx)
1987        })
1988    }
1989
1990    pub fn open_abs_path(
1991        &mut self,
1992        abs_path: PathBuf,
1993        visible: bool,
1994        cx: &mut ViewContext<Self>,
1995    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1996        cx.spawn(|workspace, mut cx| async move {
1997            let open_paths_task_result = workspace
1998                .update(&mut cx, |workspace, cx| {
1999                    workspace.open_paths(vec![abs_path.clone()], visible, cx)
2000                })
2001                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
2002                .await;
2003            anyhow::ensure!(
2004                open_paths_task_result.len() == 1,
2005                "open abs path {abs_path:?} task returned incorrect number of results"
2006            );
2007            match open_paths_task_result
2008                .into_iter()
2009                .next()
2010                .expect("ensured single task result")
2011            {
2012                Some(open_result) => {
2013                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
2014                }
2015                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
2016            }
2017        })
2018    }
2019
2020    pub fn split_abs_path(
2021        &mut self,
2022        abs_path: PathBuf,
2023        visible: bool,
2024        cx: &mut ViewContext<Self>,
2025    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2026        let project_path_task =
2027            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
2028        cx.spawn(|this, mut cx| async move {
2029            let (_, path) = project_path_task.await?;
2030            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
2031                .await
2032        })
2033    }
2034
2035    pub fn open_path(
2036        &mut self,
2037        path: impl Into<ProjectPath>,
2038        pane: Option<WeakViewHandle<Pane>>,
2039        focus_item: bool,
2040        cx: &mut ViewContext<Self>,
2041    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2042        let pane = pane.unwrap_or_else(|| {
2043            self.last_active_center_pane.clone().unwrap_or_else(|| {
2044                self.panes
2045                    .first()
2046                    .expect("There must be an active pane")
2047                    .downgrade()
2048            })
2049        });
2050
2051        let task = self.load_path(path.into(), cx);
2052        cx.spawn(|_, mut cx| async move {
2053            let (project_entry_id, build_item) = task.await?;
2054            pane.update(&mut cx, |pane, cx| {
2055                pane.open_item(project_entry_id, focus_item, cx, build_item)
2056            })
2057        })
2058    }
2059
2060    pub fn split_path(
2061        &mut self,
2062        path: impl Into<ProjectPath>,
2063        cx: &mut ViewContext<Self>,
2064    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2065        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
2066            self.panes
2067                .first()
2068                .expect("There must be an active pane")
2069                .downgrade()
2070        });
2071
2072        if let Member::Pane(center_pane) = &self.center.root {
2073            if center_pane.read(cx).items_len() == 0 {
2074                return self.open_path(path, Some(pane), true, cx);
2075            }
2076        }
2077
2078        let task = self.load_path(path.into(), cx);
2079        cx.spawn(|this, mut cx| async move {
2080            let (project_entry_id, build_item) = task.await?;
2081            this.update(&mut cx, move |this, cx| -> Option<_> {
2082                let pane = pane.upgrade(cx)?;
2083                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
2084                new_pane.update(cx, |new_pane, cx| {
2085                    Some(new_pane.open_item(project_entry_id, true, cx, build_item))
2086                })
2087            })
2088            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
2089        })
2090    }
2091
2092    pub(crate) fn load_path(
2093        &mut self,
2094        path: ProjectPath,
2095        cx: &mut ViewContext<Self>,
2096    ) -> Task<
2097        Result<(
2098            ProjectEntryId,
2099            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
2100        )>,
2101    > {
2102        let project = self.project().clone();
2103        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
2104        cx.spawn(|_, mut cx| async move {
2105            let (project_entry_id, project_item) = project_item.await?;
2106            let build_item = cx.update(|cx| {
2107                cx.default_global::<ProjectItemBuilders>()
2108                    .get(&project_item.model_type())
2109                    .ok_or_else(|| anyhow!("no item builder for project item"))
2110                    .cloned()
2111            })?;
2112            let build_item =
2113                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
2114            Ok((project_entry_id, build_item))
2115        })
2116    }
2117
2118    pub fn open_project_item<T>(
2119        &mut self,
2120        project_item: ModelHandle<T::Item>,
2121        cx: &mut ViewContext<Self>,
2122    ) -> ViewHandle<T>
2123    where
2124        T: ProjectItem,
2125    {
2126        use project::Item as _;
2127
2128        let entry_id = project_item.read(cx).entry_id(cx);
2129        if let Some(item) = entry_id
2130            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2131            .and_then(|item| item.downcast())
2132        {
2133            self.activate_item(&item, cx);
2134            return item;
2135        }
2136
2137        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2138        self.add_item(Box::new(item.clone()), cx);
2139        item
2140    }
2141
2142    pub fn split_project_item<T>(
2143        &mut self,
2144        project_item: ModelHandle<T::Item>,
2145        cx: &mut ViewContext<Self>,
2146    ) -> ViewHandle<T>
2147    where
2148        T: ProjectItem,
2149    {
2150        use project::Item as _;
2151
2152        let entry_id = project_item.read(cx).entry_id(cx);
2153        if let Some(item) = entry_id
2154            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2155            .and_then(|item| item.downcast())
2156        {
2157            self.activate_item(&item, cx);
2158            return item;
2159        }
2160
2161        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2162        self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
2163        item
2164    }
2165
2166    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2167        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2168            self.active_pane.update(cx, |pane, cx| {
2169                pane.add_item(Box::new(shared_screen), false, true, None, cx)
2170            });
2171        }
2172    }
2173
2174    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
2175        let result = self.panes.iter().find_map(|pane| {
2176            pane.read(cx)
2177                .index_for_item(item)
2178                .map(|ix| (pane.clone(), ix))
2179        });
2180        if let Some((pane, ix)) = result {
2181            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2182            true
2183        } else {
2184            false
2185        }
2186    }
2187
2188    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2189        let panes = self.center.panes();
2190        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2191            cx.focus(&pane);
2192        } else {
2193            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2194        }
2195    }
2196
2197    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
2198        let panes = self.center.panes();
2199        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2200            let next_ix = (ix + 1) % panes.len();
2201            let next_pane = panes[next_ix].clone();
2202            cx.focus(&next_pane);
2203        }
2204    }
2205
2206    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
2207        let panes = self.center.panes();
2208        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2209            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2210            let prev_pane = panes[prev_ix].clone();
2211            cx.focus(&prev_pane);
2212        }
2213    }
2214
2215    pub fn activate_pane_in_direction(
2216        &mut self,
2217        direction: SplitDirection,
2218        cx: &mut ViewContext<Self>,
2219    ) {
2220        let bounding_box = match self.center.bounding_box_for_pane(&self.active_pane) {
2221            Some(coordinates) => coordinates,
2222            None => {
2223                return;
2224            }
2225        };
2226        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2227        let center = match cursor {
2228            Some(cursor) if bounding_box.contains_point(cursor) => cursor,
2229            _ => bounding_box.center(),
2230        };
2231
2232        let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.;
2233
2234        let target = match direction {
2235            SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()),
2236            SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()),
2237            SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next),
2238            SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next),
2239        };
2240
2241        if let Some(pane) = self.center.pane_at_pixel_position(target) {
2242            cx.focus(pane);
2243        }
2244    }
2245
2246    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2247        if self.active_pane != pane {
2248            self.active_pane = pane.clone();
2249            self.status_bar.update(cx, |status_bar, cx| {
2250                status_bar.set_active_pane(&self.active_pane, cx);
2251            });
2252            self.active_item_path_changed(cx);
2253            self.last_active_center_pane = Some(pane.downgrade());
2254        }
2255
2256        self.dismiss_zoomed_items_to_reveal(None, cx);
2257        if pane.read(cx).is_zoomed() {
2258            self.zoomed = Some(pane.downgrade().into_any());
2259        } else {
2260            self.zoomed = None;
2261        }
2262        self.zoomed_position = None;
2263        self.update_active_view_for_followers(cx);
2264
2265        cx.notify();
2266    }
2267
2268    fn handle_pane_event(
2269        &mut self,
2270        pane: ViewHandle<Pane>,
2271        event: &pane::Event,
2272        cx: &mut ViewContext<Self>,
2273    ) {
2274        match event {
2275            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2276            pane::Event::Split(direction) => {
2277                self.split_and_clone(pane, *direction, cx);
2278            }
2279            pane::Event::Remove => self.remove_pane(pane, cx),
2280            pane::Event::ActivateItem { local } => {
2281                if *local {
2282                    self.unfollow(&pane, cx);
2283                }
2284                if &pane == self.active_pane() {
2285                    self.active_item_path_changed(cx);
2286                }
2287            }
2288            pane::Event::ChangeItemTitle => {
2289                if pane == self.active_pane {
2290                    self.active_item_path_changed(cx);
2291                }
2292                self.update_window_edited(cx);
2293            }
2294            pane::Event::RemoveItem { item_id } => {
2295                self.update_window_edited(cx);
2296                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2297                    if entry.get().id() == pane.id() {
2298                        entry.remove();
2299                    }
2300                }
2301            }
2302            pane::Event::Focus => {
2303                self.handle_pane_focused(pane.clone(), cx);
2304            }
2305            pane::Event::ZoomIn => {
2306                if pane == self.active_pane {
2307                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2308                    if pane.read(cx).has_focus() {
2309                        self.zoomed = Some(pane.downgrade().into_any());
2310                        self.zoomed_position = None;
2311                    }
2312                    cx.notify();
2313                }
2314            }
2315            pane::Event::ZoomOut => {
2316                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2317                if self.zoomed_position.is_none() {
2318                    self.zoomed = None;
2319                }
2320                cx.notify();
2321            }
2322        }
2323
2324        self.serialize_workspace(cx);
2325    }
2326
2327    pub fn split_pane(
2328        &mut self,
2329        pane_to_split: ViewHandle<Pane>,
2330        split_direction: SplitDirection,
2331        cx: &mut ViewContext<Self>,
2332    ) -> ViewHandle<Pane> {
2333        let new_pane = self.add_pane(cx);
2334        self.center
2335            .split(&pane_to_split, &new_pane, split_direction)
2336            .unwrap();
2337        cx.notify();
2338        new_pane
2339    }
2340
2341    pub fn split_and_clone(
2342        &mut self,
2343        pane: ViewHandle<Pane>,
2344        direction: SplitDirection,
2345        cx: &mut ViewContext<Self>,
2346    ) -> Option<ViewHandle<Pane>> {
2347        let item = pane.read(cx).active_item()?;
2348        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2349            let new_pane = self.add_pane(cx);
2350            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2351            self.center.split(&pane, &new_pane, direction).unwrap();
2352            Some(new_pane)
2353        } else {
2354            None
2355        };
2356        cx.notify();
2357        maybe_pane_handle
2358    }
2359
2360    pub fn split_pane_with_item(
2361        &mut self,
2362        pane_to_split: WeakViewHandle<Pane>,
2363        split_direction: SplitDirection,
2364        from: WeakViewHandle<Pane>,
2365        item_id_to_move: usize,
2366        cx: &mut ViewContext<Self>,
2367    ) {
2368        let Some(pane_to_split) = pane_to_split.upgrade(cx) else {
2369            return;
2370        };
2371        let Some(from) = from.upgrade(cx) else {
2372            return;
2373        };
2374
2375        let new_pane = self.add_pane(cx);
2376        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2377        self.center
2378            .split(&pane_to_split, &new_pane, split_direction)
2379            .unwrap();
2380        cx.notify();
2381    }
2382
2383    pub fn split_pane_with_project_entry(
2384        &mut self,
2385        pane_to_split: WeakViewHandle<Pane>,
2386        split_direction: SplitDirection,
2387        project_entry: ProjectEntryId,
2388        cx: &mut ViewContext<Self>,
2389    ) -> Option<Task<Result<()>>> {
2390        let pane_to_split = pane_to_split.upgrade(cx)?;
2391        let new_pane = self.add_pane(cx);
2392        self.center
2393            .split(&pane_to_split, &new_pane, split_direction)
2394            .unwrap();
2395
2396        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2397        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2398        Some(cx.foreground().spawn(async move {
2399            task.await?;
2400            Ok(())
2401        }))
2402    }
2403
2404    pub fn move_item(
2405        &mut self,
2406        source: ViewHandle<Pane>,
2407        destination: ViewHandle<Pane>,
2408        item_id_to_move: usize,
2409        destination_index: usize,
2410        cx: &mut ViewContext<Self>,
2411    ) {
2412        let item_to_move = source
2413            .read(cx)
2414            .items()
2415            .enumerate()
2416            .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
2417
2418        if item_to_move.is_none() {
2419            log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
2420            return;
2421        }
2422        let (item_ix, item_handle) = item_to_move.unwrap();
2423        let item_handle = item_handle.clone();
2424
2425        if source != destination {
2426            // Close item from previous pane
2427            source.update(cx, |source, cx| {
2428                source.remove_item(item_ix, false, cx);
2429            });
2430        }
2431
2432        // This automatically removes duplicate items in the pane
2433        destination.update(cx, |destination, cx| {
2434            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2435            cx.focus_self();
2436        });
2437    }
2438
2439    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2440        if self.center.remove(&pane).unwrap() {
2441            self.force_remove_pane(&pane, cx);
2442            self.unfollow(&pane, cx);
2443            self.last_leaders_by_pane.remove(&pane.downgrade());
2444            for removed_item in pane.read(cx).items() {
2445                self.panes_by_item.remove(&removed_item.id());
2446            }
2447
2448            cx.notify();
2449        } else {
2450            self.active_item_path_changed(cx);
2451        }
2452    }
2453
2454    pub fn panes(&self) -> &[ViewHandle<Pane>] {
2455        &self.panes
2456    }
2457
2458    pub fn active_pane(&self) -> &ViewHandle<Pane> {
2459        &self.active_pane
2460    }
2461
2462    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
2463        if let Some(remote_id) = remote_id {
2464            self.remote_entity_subscription = Some(
2465                self.app_state
2466                    .client
2467                    .add_view_for_remote_entity(remote_id, cx),
2468            );
2469        } else {
2470            self.remote_entity_subscription.take();
2471        }
2472    }
2473
2474    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2475        self.leader_state.followers.remove(&peer_id);
2476        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
2477            for state in states_by_pane.into_values() {
2478                for item in state.items_by_leader_view_id.into_values() {
2479                    item.set_leader_replica_id(None, cx);
2480                }
2481            }
2482        }
2483        cx.notify();
2484    }
2485
2486    pub fn toggle_follow(
2487        &mut self,
2488        leader_id: PeerId,
2489        cx: &mut ViewContext<Self>,
2490    ) -> Option<Task<Result<()>>> {
2491        let pane = self.active_pane().clone();
2492
2493        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
2494            if leader_id == prev_leader_id {
2495                return None;
2496            }
2497        }
2498
2499        self.last_leaders_by_pane
2500            .insert(pane.downgrade(), leader_id);
2501        self.follower_states_by_leader
2502            .entry(leader_id)
2503            .or_default()
2504            .insert(pane.clone(), Default::default());
2505        cx.notify();
2506
2507        let project_id = self.project.read(cx).remote_id()?;
2508        let request = self.app_state.client.request(proto::Follow {
2509            project_id,
2510            leader_id: Some(leader_id),
2511        });
2512
2513        Some(cx.spawn(|this, mut cx| async move {
2514            let response = request.await?;
2515            this.update(&mut cx, |this, _| {
2516                let state = this
2517                    .follower_states_by_leader
2518                    .get_mut(&leader_id)
2519                    .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
2520                    .ok_or_else(|| anyhow!("following interrupted"))?;
2521                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2522                    Some(ViewId::from_proto(active_view_id)?)
2523                } else {
2524                    None
2525                };
2526                Ok::<_, anyhow::Error>(())
2527            })??;
2528            Self::add_views_from_leader(
2529                this.clone(),
2530                leader_id,
2531                vec![pane],
2532                response.views,
2533                &mut cx,
2534            )
2535            .await?;
2536            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2537            Ok(())
2538        }))
2539    }
2540
2541    pub fn follow_next_collaborator(
2542        &mut self,
2543        _: &FollowNextCollaborator,
2544        cx: &mut ViewContext<Self>,
2545    ) -> Option<Task<Result<()>>> {
2546        let collaborators = self.project.read(cx).collaborators();
2547        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2548            let mut collaborators = collaborators.keys().copied();
2549            for peer_id in collaborators.by_ref() {
2550                if peer_id == leader_id {
2551                    break;
2552                }
2553            }
2554            collaborators.next()
2555        } else if let Some(last_leader_id) =
2556            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2557        {
2558            if collaborators.contains_key(last_leader_id) {
2559                Some(*last_leader_id)
2560            } else {
2561                None
2562            }
2563        } else {
2564            None
2565        };
2566
2567        next_leader_id
2568            .or_else(|| collaborators.keys().copied().next())
2569            .and_then(|leader_id| self.toggle_follow(leader_id, cx))
2570    }
2571
2572    pub fn unfollow(
2573        &mut self,
2574        pane: &ViewHandle<Pane>,
2575        cx: &mut ViewContext<Self>,
2576    ) -> Option<PeerId> {
2577        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2578            let leader_id = *leader_id;
2579            if let Some(state) = states_by_pane.remove(pane) {
2580                for (_, item) in state.items_by_leader_view_id {
2581                    item.set_leader_replica_id(None, cx);
2582                }
2583
2584                if states_by_pane.is_empty() {
2585                    self.follower_states_by_leader.remove(&leader_id);
2586                    if let Some(project_id) = self.project.read(cx).remote_id() {
2587                        self.app_state
2588                            .client
2589                            .send(proto::Unfollow {
2590                                project_id,
2591                                leader_id: Some(leader_id),
2592                            })
2593                            .log_err();
2594                    }
2595                }
2596
2597                cx.notify();
2598                return Some(leader_id);
2599            }
2600        }
2601        None
2602    }
2603
2604    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2605        self.follower_states_by_leader.contains_key(&peer_id)
2606    }
2607
2608    pub fn is_followed_by(&self, peer_id: PeerId) -> bool {
2609        self.leader_state.followers.contains(&peer_id)
2610    }
2611
2612    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2613        // TODO: There should be a better system in place for this
2614        // (https://github.com/zed-industries/zed/issues/1290)
2615        let is_fullscreen = cx.window_is_fullscreen();
2616        let container_theme = if is_fullscreen {
2617            let mut container_theme = theme.titlebar.container;
2618            container_theme.padding.left = container_theme.padding.right;
2619            container_theme
2620        } else {
2621            theme.titlebar.container
2622        };
2623
2624        enum TitleBar {}
2625        MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
2626            Stack::new()
2627                .with_children(
2628                    self.titlebar_item
2629                        .as_ref()
2630                        .map(|item| ChildView::new(item, cx)),
2631                )
2632                .contained()
2633                .with_style(container_theme)
2634        })
2635        .on_click(MouseButton::Left, |event, _, cx| {
2636            if event.click_count == 2 {
2637                cx.zoom_window();
2638            }
2639        })
2640        .constrained()
2641        .with_height(theme.titlebar.height)
2642        .into_any_named("titlebar")
2643    }
2644
2645    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2646        let active_entry = self.active_project_path(cx);
2647        self.project
2648            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2649        self.update_window_title(cx);
2650    }
2651
2652    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2653        let project = self.project().read(cx);
2654        let mut title = String::new();
2655
2656        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2657            let filename = path
2658                .path
2659                .file_name()
2660                .map(|s| s.to_string_lossy())
2661                .or_else(|| {
2662                    Some(Cow::Borrowed(
2663                        project
2664                            .worktree_for_id(path.worktree_id, cx)?
2665                            .read(cx)
2666                            .root_name(),
2667                    ))
2668                });
2669
2670            if let Some(filename) = filename {
2671                title.push_str(filename.as_ref());
2672                title.push_str(" β€” ");
2673            }
2674        }
2675
2676        for (i, name) in project.worktree_root_names(cx).enumerate() {
2677            if i > 0 {
2678                title.push_str(", ");
2679            }
2680            title.push_str(name);
2681        }
2682
2683        if title.is_empty() {
2684            title = "empty project".to_string();
2685        }
2686
2687        if project.is_remote() {
2688            title.push_str(" ↙");
2689        } else if project.is_shared() {
2690            title.push_str(" β†—");
2691        }
2692
2693        cx.set_window_title(&title);
2694    }
2695
2696    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2697        let is_edited = !self.project.read(cx).is_read_only()
2698            && self
2699                .items(cx)
2700                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2701        if is_edited != self.window_edited {
2702            self.window_edited = is_edited;
2703            cx.set_window_edited(self.window_edited)
2704        }
2705    }
2706
2707    fn render_disconnected_overlay(
2708        &self,
2709        cx: &mut ViewContext<Workspace>,
2710    ) -> Option<AnyElement<Workspace>> {
2711        if self.project.read(cx).is_read_only() {
2712            enum DisconnectedOverlay {}
2713            Some(
2714                MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
2715                    let theme = &theme::current(cx);
2716                    Label::new(
2717                        "Your connection to the remote project has been lost.",
2718                        theme.workspace.disconnected_overlay.text.clone(),
2719                    )
2720                    .aligned()
2721                    .contained()
2722                    .with_style(theme.workspace.disconnected_overlay.container)
2723                })
2724                .with_cursor_style(CursorStyle::Arrow)
2725                .capture_all()
2726                .into_any_named("disconnected overlay"),
2727            )
2728        } else {
2729            None
2730        }
2731    }
2732
2733    fn render_notifications(
2734        &self,
2735        theme: &theme::Workspace,
2736        cx: &AppContext,
2737    ) -> Option<AnyElement<Workspace>> {
2738        if self.notifications.is_empty() {
2739            None
2740        } else {
2741            Some(
2742                Flex::column()
2743                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2744                        ChildView::new(notification.as_any(), cx)
2745                            .contained()
2746                            .with_style(theme.notification)
2747                    }))
2748                    .constrained()
2749                    .with_width(theme.notifications.width)
2750                    .contained()
2751                    .with_style(theme.notifications.container)
2752                    .aligned()
2753                    .bottom()
2754                    .right()
2755                    .into_any(),
2756            )
2757        }
2758    }
2759
2760    // RPC handlers
2761
2762    async fn handle_follow(
2763        this: WeakViewHandle<Self>,
2764        envelope: TypedEnvelope<proto::Follow>,
2765        _: Arc<Client>,
2766        mut cx: AsyncAppContext,
2767    ) -> Result<proto::FollowResponse> {
2768        this.update(&mut cx, |this, cx| {
2769            let client = &this.app_state.client;
2770            this.leader_state
2771                .followers
2772                .insert(envelope.original_sender_id()?);
2773
2774            let active_view_id = this.active_item(cx).and_then(|i| {
2775                Some(
2776                    i.to_followable_item_handle(cx)?
2777                        .remote_id(client, cx)?
2778                        .to_proto(),
2779                )
2780            });
2781
2782            cx.notify();
2783
2784            Ok(proto::FollowResponse {
2785                active_view_id,
2786                views: this
2787                    .panes()
2788                    .iter()
2789                    .flat_map(|pane| {
2790                        let leader_id = this.leader_for_pane(pane);
2791                        pane.read(cx).items().filter_map({
2792                            let cx = &cx;
2793                            move |item| {
2794                                let item = item.to_followable_item_handle(cx)?;
2795                                let id = item.remote_id(client, cx)?.to_proto();
2796                                let variant = item.to_state_proto(cx)?;
2797                                Some(proto::View {
2798                                    id: Some(id),
2799                                    leader_id,
2800                                    variant: Some(variant),
2801                                })
2802                            }
2803                        })
2804                    })
2805                    .collect(),
2806            })
2807        })?
2808    }
2809
2810    async fn handle_unfollow(
2811        this: WeakViewHandle<Self>,
2812        envelope: TypedEnvelope<proto::Unfollow>,
2813        _: Arc<Client>,
2814        mut cx: AsyncAppContext,
2815    ) -> Result<()> {
2816        this.update(&mut cx, |this, cx| {
2817            this.leader_state
2818                .followers
2819                .remove(&envelope.original_sender_id()?);
2820            cx.notify();
2821            Ok(())
2822        })?
2823    }
2824
2825    async fn handle_update_followers(
2826        this: WeakViewHandle<Self>,
2827        envelope: TypedEnvelope<proto::UpdateFollowers>,
2828        _: Arc<Client>,
2829        cx: AsyncAppContext,
2830    ) -> Result<()> {
2831        let leader_id = envelope.original_sender_id()?;
2832        this.read_with(&cx, |this, _| {
2833            this.leader_updates_tx
2834                .unbounded_send((leader_id, envelope.payload))
2835        })??;
2836        Ok(())
2837    }
2838
2839    async fn process_leader_update(
2840        this: &WeakViewHandle<Self>,
2841        leader_id: PeerId,
2842        update: proto::UpdateFollowers,
2843        cx: &mut AsyncAppContext,
2844    ) -> Result<()> {
2845        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2846            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2847                this.update(cx, |this, _| {
2848                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2849                        for state in state.values_mut() {
2850                            state.active_view_id =
2851                                if let Some(active_view_id) = update_active_view.id.clone() {
2852                                    Some(ViewId::from_proto(active_view_id)?)
2853                                } else {
2854                                    None
2855                                };
2856                        }
2857                    }
2858                    anyhow::Ok(())
2859                })??;
2860            }
2861            proto::update_followers::Variant::UpdateView(update_view) => {
2862                let variant = update_view
2863                    .variant
2864                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2865                let id = update_view
2866                    .id
2867                    .ok_or_else(|| anyhow!("missing update view id"))?;
2868                let mut tasks = Vec::new();
2869                this.update(cx, |this, cx| {
2870                    let project = this.project.clone();
2871                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2872                        for state in state.values_mut() {
2873                            let view_id = ViewId::from_proto(id.clone())?;
2874                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2875                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2876                            }
2877                        }
2878                    }
2879                    anyhow::Ok(())
2880                })??;
2881                try_join_all(tasks).await.log_err();
2882            }
2883            proto::update_followers::Variant::CreateView(view) => {
2884                let panes = this.read_with(cx, |this, _| {
2885                    this.follower_states_by_leader
2886                        .get(&leader_id)
2887                        .into_iter()
2888                        .flat_map(|states_by_pane| states_by_pane.keys())
2889                        .cloned()
2890                        .collect()
2891                })?;
2892                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2893            }
2894        }
2895        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2896        Ok(())
2897    }
2898
2899    async fn add_views_from_leader(
2900        this: WeakViewHandle<Self>,
2901        leader_id: PeerId,
2902        panes: Vec<ViewHandle<Pane>>,
2903        views: Vec<proto::View>,
2904        cx: &mut AsyncAppContext,
2905    ) -> Result<()> {
2906        let this = this
2907            .upgrade(cx)
2908            .ok_or_else(|| anyhow!("workspace dropped"))?;
2909        let project = this
2910            .read_with(cx, |this, _| this.project.clone())
2911            .ok_or_else(|| anyhow!("window dropped"))?;
2912
2913        let replica_id = project
2914            .read_with(cx, |project, _| {
2915                project
2916                    .collaborators()
2917                    .get(&leader_id)
2918                    .map(|c| c.replica_id)
2919            })
2920            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2921
2922        let item_builders = cx.update(|cx| {
2923            cx.default_global::<FollowableItemBuilders>()
2924                .values()
2925                .map(|b| b.0)
2926                .collect::<Vec<_>>()
2927        });
2928
2929        let mut item_tasks_by_pane = HashMap::default();
2930        for pane in panes {
2931            let mut item_tasks = Vec::new();
2932            let mut leader_view_ids = Vec::new();
2933            for view in &views {
2934                let Some(id) = &view.id else { continue };
2935                let id = ViewId::from_proto(id.clone())?;
2936                let mut variant = view.variant.clone();
2937                if variant.is_none() {
2938                    Err(anyhow!("missing view variant"))?;
2939                }
2940                for build_item in &item_builders {
2941                    let task = cx
2942                        .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx));
2943                    if let Some(task) = task {
2944                        item_tasks.push(task);
2945                        leader_view_ids.push(id);
2946                        break;
2947                    } else {
2948                        assert!(variant.is_some());
2949                    }
2950                }
2951            }
2952
2953            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2954        }
2955
2956        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2957            let items = futures::future::try_join_all(item_tasks).await?;
2958            this.update(cx, |this, cx| {
2959                let state = this
2960                    .follower_states_by_leader
2961                    .get_mut(&leader_id)?
2962                    .get_mut(&pane)?;
2963
2964                for (id, item) in leader_view_ids.into_iter().zip(items) {
2965                    item.set_leader_replica_id(Some(replica_id), cx);
2966                    state.items_by_leader_view_id.insert(id, item);
2967                }
2968
2969                Some(())
2970            });
2971        }
2972        Ok(())
2973    }
2974
2975    fn update_active_view_for_followers(&self, cx: &AppContext) {
2976        if self.active_pane.read(cx).has_focus() {
2977            self.update_followers(
2978                proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
2979                    id: self.active_item(cx).and_then(|item| {
2980                        item.to_followable_item_handle(cx)?
2981                            .remote_id(&self.app_state.client, cx)
2982                            .map(|id| id.to_proto())
2983                    }),
2984                    leader_id: self.leader_for_pane(&self.active_pane),
2985                }),
2986                cx,
2987            );
2988        } else {
2989            self.update_followers(
2990                proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
2991                    id: None,
2992                    leader_id: None,
2993                }),
2994                cx,
2995            );
2996        }
2997    }
2998
2999    fn update_followers(
3000        &self,
3001        update: proto::update_followers::Variant,
3002        cx: &AppContext,
3003    ) -> Option<()> {
3004        let project_id = self.project.read(cx).remote_id()?;
3005        if !self.leader_state.followers.is_empty() {
3006            self.app_state
3007                .client
3008                .send(proto::UpdateFollowers {
3009                    project_id,
3010                    follower_ids: self.leader_state.followers.iter().copied().collect(),
3011                    variant: Some(update),
3012                })
3013                .log_err();
3014        }
3015        None
3016    }
3017
3018    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
3019        self.follower_states_by_leader
3020            .iter()
3021            .find_map(|(leader_id, state)| {
3022                if state.contains_key(pane) {
3023                    Some(*leader_id)
3024                } else {
3025                    None
3026                }
3027            })
3028    }
3029
3030    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3031        cx.notify();
3032
3033        let call = self.active_call()?;
3034        let room = call.read(cx).room()?.read(cx);
3035        let participant = room.remote_participant_for_peer_id(leader_id)?;
3036        let mut items_to_activate = Vec::new();
3037        match participant.location {
3038            call::ParticipantLocation::SharedProject { project_id } => {
3039                if Some(project_id) == self.project.read(cx).remote_id() {
3040                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
3041                        if let Some(item) = state
3042                            .active_view_id
3043                            .and_then(|id| state.items_by_leader_view_id.get(&id))
3044                        {
3045                            items_to_activate.push((pane.clone(), item.boxed_clone()));
3046                        } else if let Some(shared_screen) =
3047                            self.shared_screen_for_peer(leader_id, pane, cx)
3048                        {
3049                            items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3050                        }
3051                    }
3052                }
3053            }
3054            call::ParticipantLocation::UnsharedProject => {}
3055            call::ParticipantLocation::External => {
3056                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
3057                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3058                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3059                    }
3060                }
3061            }
3062        }
3063
3064        for (pane, item) in items_to_activate {
3065            let pane_was_focused = pane.read(cx).has_focus();
3066            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3067                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3068            } else {
3069                pane.update(cx, |pane, cx| {
3070                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3071                });
3072            }
3073
3074            if pane_was_focused {
3075                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3076            }
3077        }
3078
3079        None
3080    }
3081
3082    fn shared_screen_for_peer(
3083        &self,
3084        peer_id: PeerId,
3085        pane: &ViewHandle<Pane>,
3086        cx: &mut ViewContext<Self>,
3087    ) -> Option<ViewHandle<SharedScreen>> {
3088        let call = self.active_call()?;
3089        let room = call.read(cx).room()?.read(cx);
3090        let participant = room.remote_participant_for_peer_id(peer_id)?;
3091        let track = participant.video_tracks.values().next()?.clone();
3092        let user = participant.user.clone();
3093
3094        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3095            if item.read(cx).peer_id == peer_id {
3096                return Some(item);
3097            }
3098        }
3099
3100        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3101    }
3102
3103    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
3104        if active {
3105            cx.background()
3106                .spawn(persistence::DB.update_timestamp(self.database_id()))
3107                .detach();
3108        } else {
3109            for pane in &self.panes {
3110                pane.update(cx, |pane, cx| {
3111                    if let Some(item) = pane.active_item() {
3112                        item.workspace_deactivated(cx);
3113                    }
3114                    if matches!(
3115                        settings::get::<WorkspaceSettings>(cx).autosave,
3116                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3117                    ) {
3118                        for item in pane.items() {
3119                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3120                                .detach_and_log_err(cx);
3121                        }
3122                    }
3123                });
3124            }
3125        }
3126    }
3127
3128    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
3129        self.active_call.as_ref().map(|(call, _)| call)
3130    }
3131
3132    fn on_active_call_event(
3133        &mut self,
3134        _: ModelHandle<ActiveCall>,
3135        event: &call::room::Event,
3136        cx: &mut ViewContext<Self>,
3137    ) {
3138        match event {
3139            call::room::Event::ParticipantLocationChanged { participant_id }
3140            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3141                self.leader_updated(*participant_id, cx);
3142            }
3143            _ => {}
3144        }
3145    }
3146
3147    pub fn database_id(&self) -> WorkspaceId {
3148        self.database_id
3149    }
3150
3151    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3152        let project = self.project().read(cx);
3153
3154        if project.is_local() {
3155            Some(
3156                project
3157                    .visible_worktrees(cx)
3158                    .map(|worktree| worktree.read(cx).abs_path())
3159                    .collect::<Vec<_>>()
3160                    .into(),
3161            )
3162        } else {
3163            None
3164        }
3165    }
3166
3167    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3168        match member {
3169            Member::Axis(PaneAxis { members, .. }) => {
3170                for child in members.iter() {
3171                    self.remove_panes(child.clone(), cx)
3172                }
3173            }
3174            Member::Pane(pane) => {
3175                self.force_remove_pane(&pane, cx);
3176            }
3177        }
3178    }
3179
3180    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
3181        self.panes.retain(|p| p != pane);
3182        cx.focus(self.panes.last().unwrap());
3183        if self.last_active_center_pane == Some(pane.downgrade()) {
3184            self.last_active_center_pane = None;
3185        }
3186        cx.notify();
3187    }
3188
3189    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3190        self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
3191            cx.background().timer(Duration::from_millis(100)).await;
3192            this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
3193                .ok();
3194        }));
3195    }
3196
3197    fn serialize_workspace(&self, cx: &ViewContext<Self>) {
3198        fn serialize_pane_handle(
3199            pane_handle: &ViewHandle<Pane>,
3200            cx: &AppContext,
3201        ) -> SerializedPane {
3202            let (items, active) = {
3203                let pane = pane_handle.read(cx);
3204                let active_item_id = pane.active_item().map(|item| item.id());
3205                (
3206                    pane.items()
3207                        .filter_map(|item_handle| {
3208                            Some(SerializedItem {
3209                                kind: Arc::from(item_handle.serialized_item_kind()?),
3210                                item_id: item_handle.id(),
3211                                active: Some(item_handle.id()) == active_item_id,
3212                            })
3213                        })
3214                        .collect::<Vec<_>>(),
3215                    pane.has_focus(),
3216                )
3217            };
3218
3219            SerializedPane::new(items, active)
3220        }
3221
3222        fn build_serialized_pane_group(
3223            pane_group: &Member,
3224            cx: &AppContext,
3225        ) -> SerializedPaneGroup {
3226            match pane_group {
3227                Member::Axis(PaneAxis {
3228                    axis,
3229                    members,
3230                    flexes,
3231                    bounding_boxes: _,
3232                }) => SerializedPaneGroup::Group {
3233                    axis: *axis,
3234                    children: members
3235                        .iter()
3236                        .map(|member| build_serialized_pane_group(member, cx))
3237                        .collect::<Vec<_>>(),
3238                    flexes: Some(flexes.borrow().clone()),
3239                },
3240                Member::Pane(pane_handle) => {
3241                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3242                }
3243            }
3244        }
3245
3246        fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
3247            let left_dock = this.left_dock.read(cx);
3248            let left_visible = left_dock.is_open();
3249            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
3250                Some(
3251                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3252                        .to_string(),
3253                )
3254            });
3255            let left_dock_zoom = left_dock
3256                .visible_panel()
3257                .map(|panel| panel.is_zoomed(cx))
3258                .unwrap_or(false);
3259
3260            let right_dock = this.right_dock.read(cx);
3261            let right_visible = right_dock.is_open();
3262            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
3263                Some(
3264                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3265                        .to_string(),
3266                )
3267            });
3268            let right_dock_zoom = right_dock
3269                .visible_panel()
3270                .map(|panel| panel.is_zoomed(cx))
3271                .unwrap_or(false);
3272
3273            let bottom_dock = this.bottom_dock.read(cx);
3274            let bottom_visible = bottom_dock.is_open();
3275            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
3276                Some(
3277                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3278                        .to_string(),
3279                )
3280            });
3281            let bottom_dock_zoom = bottom_dock
3282                .visible_panel()
3283                .map(|panel| panel.is_zoomed(cx))
3284                .unwrap_or(false);
3285
3286            DockStructure {
3287                left: DockData {
3288                    visible: left_visible,
3289                    active_panel: left_active_panel,
3290                    zoom: left_dock_zoom,
3291                },
3292                right: DockData {
3293                    visible: right_visible,
3294                    active_panel: right_active_panel,
3295                    zoom: right_dock_zoom,
3296                },
3297                bottom: DockData {
3298                    visible: bottom_visible,
3299                    active_panel: bottom_active_panel,
3300                    zoom: bottom_dock_zoom,
3301                },
3302            }
3303        }
3304
3305        if let Some(location) = self.location(cx) {
3306            // Load bearing special case:
3307            //  - with_local_workspace() relies on this to not have other stuff open
3308            //    when you open your log
3309            if !location.paths().is_empty() {
3310                let center_group = build_serialized_pane_group(&self.center.root, cx);
3311                let docks = build_serialized_docks(self, cx);
3312
3313                let serialized_workspace = SerializedWorkspace {
3314                    id: self.database_id,
3315                    location,
3316                    center_group,
3317                    bounds: Default::default(),
3318                    display: Default::default(),
3319                    docks,
3320                };
3321
3322                cx.background()
3323                    .spawn(persistence::DB.save_workspace(serialized_workspace))
3324                    .detach();
3325            }
3326        }
3327    }
3328
3329    pub(crate) fn load_workspace(
3330        workspace: WeakViewHandle<Workspace>,
3331        serialized_workspace: SerializedWorkspace,
3332        paths_to_open: Vec<Option<ProjectPath>>,
3333        cx: &mut AppContext,
3334    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
3335        cx.spawn(|mut cx| async move {
3336            let result = async_iife! {{
3337                let (project, old_center_pane) =
3338                workspace.read_with(&cx, |workspace, _| {
3339                    (
3340                        workspace.project().clone(),
3341                        workspace.last_active_center_pane.clone(),
3342                    )
3343                })?;
3344
3345                let mut center_items = None;
3346                let mut center_group = None;
3347                // Traverse the splits tree and add to things
3348                if let Some((group, active_pane, items)) = serialized_workspace
3349                        .center_group
3350                        .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
3351                        .await {
3352                    center_items = Some(items);
3353                    center_group = Some((group, active_pane))
3354                }
3355
3356                let resulting_list = cx.read(|cx| {
3357                    let mut opened_items = center_items
3358                        .unwrap_or_default()
3359                        .into_iter()
3360                        .filter_map(|item| {
3361                            let item = item?;
3362                            let project_path = item.project_path(cx)?;
3363                            Some((project_path, item))
3364                        })
3365                        .collect::<HashMap<_, _>>();
3366
3367                    paths_to_open
3368                        .into_iter()
3369                        .map(|path_to_open| {
3370                            path_to_open.map(|path_to_open| {
3371                                Ok(opened_items.remove(&path_to_open))
3372                            })
3373                            .transpose()
3374                            .map(|item| item.flatten())
3375                            .transpose()
3376                        })
3377                        .collect::<Vec<_>>()
3378                });
3379
3380                // Remove old panes from workspace panes list
3381                workspace.update(&mut cx, |workspace, cx| {
3382                    if let Some((center_group, active_pane)) = center_group {
3383                        workspace.remove_panes(workspace.center.root.clone(), cx);
3384
3385                        // Swap workspace center group
3386                        workspace.center = PaneGroup::with_root(center_group);
3387
3388                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
3389                        cx.focus_self();
3390
3391                        if let Some(active_pane) = active_pane {
3392                            cx.focus(&active_pane);
3393                        } else {
3394                            cx.focus(workspace.panes.last().unwrap());
3395                        }
3396                    } else {
3397                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3398                        if let Some(old_center_handle) = old_center_handle {
3399                            cx.focus(&old_center_handle)
3400                        } else {
3401                            cx.focus_self()
3402                        }
3403                    }
3404
3405                    let docks = serialized_workspace.docks;
3406                    workspace.left_dock.update(cx, |dock, cx| {
3407                        dock.set_open(docks.left.visible, cx);
3408                        if let Some(active_panel) = docks.left.active_panel {
3409                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3410                                dock.activate_panel(ix, cx);
3411                            }
3412                        }
3413                                dock.active_panel()
3414                                    .map(|panel| {
3415                                        panel.set_zoomed(docks.left.zoom, cx)
3416                                    });
3417                                if docks.left.visible && docks.left.zoom {
3418                                    cx.focus_self()
3419                                }
3420                    });
3421                    // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3422                    workspace.right_dock.update(cx, |dock, cx| {
3423                        dock.set_open(docks.right.visible, cx);
3424                        if let Some(active_panel) = docks.right.active_panel {
3425                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3426                                dock.activate_panel(ix, cx);
3427
3428                            }
3429                        }
3430                                dock.active_panel()
3431                                    .map(|panel| {
3432                                        panel.set_zoomed(docks.right.zoom, cx)
3433                                    });
3434
3435                                if docks.right.visible && docks.right.zoom {
3436                                    cx.focus_self()
3437                                }
3438                    });
3439                    workspace.bottom_dock.update(cx, |dock, cx| {
3440                        dock.set_open(docks.bottom.visible, cx);
3441                        if let Some(active_panel) = docks.bottom.active_panel {
3442                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3443                                dock.activate_panel(ix, cx);
3444                            }
3445                        }
3446
3447                        dock.active_panel()
3448                            .map(|panel| {
3449                                panel.set_zoomed(docks.bottom.zoom, cx)
3450                            });
3451
3452                        if docks.bottom.visible && docks.bottom.zoom {
3453                            cx.focus_self()
3454                        }
3455                    });
3456
3457
3458                    cx.notify();
3459                })?;
3460
3461                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3462                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3463
3464                Ok::<_, anyhow::Error>(resulting_list)
3465            }};
3466
3467            result.await.unwrap_or_default()
3468        })
3469    }
3470
3471    #[cfg(any(test, feature = "test-support"))]
3472    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3473        let client = project.read(cx).client();
3474        let user_store = project.read(cx).user_store();
3475
3476        let channel_store =
3477            cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
3478        let app_state = Arc::new(AppState {
3479            languages: project.read(cx).languages().clone(),
3480            client,
3481            user_store,
3482            channel_store,
3483            fs: project.read(cx).fs().clone(),
3484            build_window_options: |_, _, _| Default::default(),
3485            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3486            background_actions: || &[],
3487        });
3488        Self::new(0, project, app_state, cx)
3489    }
3490
3491    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3492        let dock = match position {
3493            DockPosition::Left => &self.left_dock,
3494            DockPosition::Right => &self.right_dock,
3495            DockPosition::Bottom => &self.bottom_dock,
3496        };
3497        let active_panel = dock.read(cx).visible_panel()?;
3498        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3499            dock.read(cx).render_placeholder(cx)
3500        } else {
3501            ChildView::new(dock, cx).into_any()
3502        };
3503
3504        Some(
3505            element
3506                .constrained()
3507                .dynamically(move |constraint, _, cx| match position {
3508                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3509                        Vector2F::new(20., constraint.min.y()),
3510                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3511                    ),
3512                    DockPosition::Bottom => SizeConstraint::new(
3513                        Vector2F::new(constraint.min.x(), 20.),
3514                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3515                    ),
3516                })
3517                .into_any(),
3518        )
3519    }
3520}
3521
3522fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3523    ZED_WINDOW_POSITION
3524        .zip(*ZED_WINDOW_SIZE)
3525        .map(|(position, size)| {
3526            WindowBounds::Fixed(RectF::new(
3527                cx.platform().screens()[0].bounds().origin() + position,
3528                size,
3529            ))
3530        })
3531}
3532
3533async fn open_items(
3534    serialized_workspace: Option<SerializedWorkspace>,
3535    workspace: &WeakViewHandle<Workspace>,
3536    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3537    app_state: Arc<AppState>,
3538    mut cx: AsyncAppContext,
3539) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
3540    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3541
3542    if let Some(serialized_workspace) = serialized_workspace {
3543        let workspace = workspace.clone();
3544        let restored_items = cx
3545            .update(|cx| {
3546                Workspace::load_workspace(
3547                    workspace,
3548                    serialized_workspace,
3549                    project_paths_to_open
3550                        .iter()
3551                        .map(|(_, project_path)| project_path)
3552                        .cloned()
3553                        .collect(),
3554                    cx,
3555                )
3556            })
3557            .await;
3558
3559        let restored_project_paths = cx.read(|cx| {
3560            restored_items
3561                .iter()
3562                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
3563                .collect::<HashSet<_>>()
3564        });
3565
3566        opened_items = restored_items;
3567        project_paths_to_open
3568            .iter_mut()
3569            .for_each(|(_, project_path)| {
3570                if let Some(project_path_to_open) = project_path {
3571                    if restored_project_paths.contains(project_path_to_open) {
3572                        *project_path = None;
3573                    }
3574                }
3575            });
3576    } else {
3577        for _ in 0..project_paths_to_open.len() {
3578            opened_items.push(None);
3579        }
3580    }
3581    assert!(opened_items.len() == project_paths_to_open.len());
3582
3583    let tasks =
3584        project_paths_to_open
3585            .into_iter()
3586            .enumerate()
3587            .map(|(i, (abs_path, project_path))| {
3588                let workspace = workspace.clone();
3589                cx.spawn(|mut cx| {
3590                    let fs = app_state.fs.clone();
3591                    async move {
3592                        let file_project_path = project_path?;
3593                        if fs.is_file(&abs_path).await {
3594                            Some((
3595                                i,
3596                                workspace
3597                                    .update(&mut cx, |workspace, cx| {
3598                                        workspace.open_path(file_project_path, None, true, cx)
3599                                    })
3600                                    .log_err()?
3601                                    .await,
3602                            ))
3603                        } else {
3604                            None
3605                        }
3606                    }
3607                })
3608            });
3609
3610    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3611        .await
3612        .into_iter()
3613    {
3614        if let Some((i, path_open_result)) = maybe_opened_path {
3615            opened_items[i] = Some(path_open_result);
3616        }
3617    }
3618
3619    opened_items
3620}
3621
3622fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3623    const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3624    const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3625    const MESSAGE_ID: usize = 2;
3626
3627    if workspace
3628        .read_with(cx, |workspace, cx| {
3629            workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3630        })
3631        .unwrap_or(false)
3632    {
3633        return;
3634    }
3635
3636    if db::kvp::KEY_VALUE_STORE
3637        .read_kvp(NEW_DOCK_HINT_KEY)
3638        .ok()
3639        .flatten()
3640        .is_some()
3641    {
3642        if !workspace
3643            .read_with(cx, |workspace, cx| {
3644                workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3645            })
3646            .unwrap_or(false)
3647        {
3648            cx.update(|cx| {
3649                cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3650                    let entry = tracker
3651                        .entry(TypeId::of::<MessageNotification>())
3652                        .or_default();
3653                    if !entry.contains(&MESSAGE_ID) {
3654                        entry.push(MESSAGE_ID);
3655                    }
3656                });
3657            });
3658        }
3659
3660        return;
3661    }
3662
3663    cx.spawn(|_| async move {
3664        db::kvp::KEY_VALUE_STORE
3665            .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3666            .await
3667            .ok();
3668    })
3669    .detach();
3670
3671    workspace
3672        .update(cx, |workspace, cx| {
3673            workspace.show_notification_once(2, cx, |cx| {
3674                cx.add_view(|_| {
3675                    MessageNotification::new_element(|text, _| {
3676                        Text::new(
3677                            "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3678                            text,
3679                        )
3680                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3681                            let code_span_background_color = settings::get::<ThemeSettings>(cx)
3682                                .theme
3683                                .editor
3684                                .document_highlight_read_background;
3685
3686                            cx.scene().push_quad(gpui::Quad {
3687                                bounds,
3688                                background: Some(code_span_background_color),
3689                                border: Default::default(),
3690                                corner_radii: (2.0).into(),
3691                            })
3692                        })
3693                        .into_any()
3694                    })
3695                    .with_click_message("Read more about the new panel system")
3696                    .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3697                })
3698            })
3699        })
3700        .ok();
3701}
3702
3703fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3704    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3705
3706    workspace
3707        .update(cx, |workspace, cx| {
3708            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3709                workspace.show_notification_once(0, cx, |cx| {
3710                    cx.add_view(|_| {
3711                        MessageNotification::new("Failed to load the database file.")
3712                            .with_click_message("Click to let us know about this error")
3713                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3714                    })
3715                });
3716            }
3717        })
3718        .log_err();
3719}
3720
3721impl Entity for Workspace {
3722    type Event = Event;
3723}
3724
3725impl View for Workspace {
3726    fn ui_name() -> &'static str {
3727        "Workspace"
3728    }
3729
3730    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3731        let theme = theme::current(cx).clone();
3732        Stack::new()
3733            .with_child(
3734                Flex::column()
3735                    .with_child(self.render_titlebar(&theme, cx))
3736                    .with_child(
3737                        Stack::new()
3738                            .with_child({
3739                                let project = self.project.clone();
3740                                Flex::row()
3741                                    .with_children(self.render_dock(DockPosition::Left, cx))
3742                                    .with_child(
3743                                        Flex::column()
3744                                            .with_child(
3745                                                FlexItem::new(
3746                                                    self.center.render(
3747                                                        &project,
3748                                                        &theme,
3749                                                        &self.follower_states_by_leader,
3750                                                        self.active_call(),
3751                                                        self.active_pane(),
3752                                                        self.zoomed
3753                                                            .as_ref()
3754                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3755                                                            .as_ref(),
3756                                                        &self.app_state,
3757                                                        cx,
3758                                                    ),
3759                                                )
3760                                                .flex(1., true),
3761                                            )
3762                                            .with_children(
3763                                                self.render_dock(DockPosition::Bottom, cx),
3764                                            )
3765                                            .flex(1., true),
3766                                    )
3767                                    .with_children(self.render_dock(DockPosition::Right, cx))
3768                            })
3769                            .with_child(Overlay::new(
3770                                Stack::new()
3771                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3772                                        enum ZoomBackground {}
3773                                        let zoomed = zoomed.upgrade(cx)?;
3774
3775                                        let mut foreground_style =
3776                                            theme.workspace.zoomed_pane_foreground;
3777                                        if let Some(zoomed_dock_position) = self.zoomed_position {
3778                                            foreground_style =
3779                                                theme.workspace.zoomed_panel_foreground;
3780                                            let margin = foreground_style.margin.top;
3781                                            let border = foreground_style.border.top;
3782
3783                                            // Only include a margin and border on the opposite side.
3784                                            foreground_style.margin.top = 0.;
3785                                            foreground_style.margin.left = 0.;
3786                                            foreground_style.margin.bottom = 0.;
3787                                            foreground_style.margin.right = 0.;
3788                                            foreground_style.border.top = false;
3789                                            foreground_style.border.left = false;
3790                                            foreground_style.border.bottom = false;
3791                                            foreground_style.border.right = false;
3792                                            match zoomed_dock_position {
3793                                                DockPosition::Left => {
3794                                                    foreground_style.margin.right = margin;
3795                                                    foreground_style.border.right = border;
3796                                                }
3797                                                DockPosition::Right => {
3798                                                    foreground_style.margin.left = margin;
3799                                                    foreground_style.border.left = border;
3800                                                }
3801                                                DockPosition::Bottom => {
3802                                                    foreground_style.margin.top = margin;
3803                                                    foreground_style.border.top = border;
3804                                                }
3805                                            }
3806                                        }
3807
3808                                        Some(
3809                                            ChildView::new(&zoomed, cx)
3810                                                .contained()
3811                                                .with_style(foreground_style)
3812                                                .aligned()
3813                                                .contained()
3814                                                .with_style(theme.workspace.zoomed_background)
3815                                                .mouse::<ZoomBackground>(0)
3816                                                .capture_all()
3817                                                .on_down(
3818                                                    MouseButton::Left,
3819                                                    |_, this: &mut Self, cx| {
3820                                                        this.zoom_out(cx);
3821                                                    },
3822                                                ),
3823                                        )
3824                                    }))
3825                                    .with_children(self.modal.as_ref().map(|modal| {
3826                                        // Prevent clicks within the modal from falling
3827                                        // through to the rest of the workspace.
3828                                        enum ModalBackground {}
3829                                        MouseEventHandler::new::<ModalBackground, _>(
3830                                            0,
3831                                            cx,
3832                                            |_, cx| ChildView::new(modal.view.as_any(), cx),
3833                                        )
3834                                        .on_click(MouseButton::Left, |_, _, _| {})
3835                                        .contained()
3836                                        .with_style(theme.workspace.modal)
3837                                        .aligned()
3838                                        .top()
3839                                    }))
3840                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3841                            ))
3842                            .provide_resize_bounds::<WorkspaceBounds>()
3843                            .flex(1.0, true),
3844                    )
3845                    .with_child(ChildView::new(&self.status_bar, cx))
3846                    .contained()
3847                    .with_background_color(theme.workspace.background),
3848            )
3849            .with_children(DragAndDrop::render(cx))
3850            .with_children(self.render_disconnected_overlay(cx))
3851            .into_any_named("workspace")
3852    }
3853
3854    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3855        if cx.is_self_focused() {
3856            cx.focus(&self.active_pane);
3857        }
3858    }
3859
3860    fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3861        DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3862    }
3863}
3864
3865impl ViewId {
3866    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3867        Ok(Self {
3868            creator: message
3869                .creator
3870                .ok_or_else(|| anyhow!("creator is missing"))?,
3871            id: message.id,
3872        })
3873    }
3874
3875    pub(crate) fn to_proto(&self) -> proto::ViewId {
3876        proto::ViewId {
3877            creator: Some(self.creator),
3878            id: self.id,
3879        }
3880    }
3881}
3882
3883pub trait WorkspaceHandle {
3884    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3885}
3886
3887impl WorkspaceHandle for ViewHandle<Workspace> {
3888    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3889        self.read(cx)
3890            .worktrees(cx)
3891            .flat_map(|worktree| {
3892                let worktree_id = worktree.read(cx).id();
3893                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3894                    worktree_id,
3895                    path: f.path.clone(),
3896                })
3897            })
3898            .collect::<Vec<_>>()
3899    }
3900}
3901
3902impl std::fmt::Debug for OpenPaths {
3903    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3904        f.debug_struct("OpenPaths")
3905            .field("paths", &self.paths)
3906            .finish()
3907    }
3908}
3909
3910pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
3911
3912pub fn activate_workspace_for_project(
3913    cx: &mut AsyncAppContext,
3914    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3915) -> Option<WeakViewHandle<Workspace>> {
3916    for window in cx.windows() {
3917        let handle = window
3918            .update(cx, |cx| {
3919                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3920                    let project = workspace_handle.read(cx).project.clone();
3921                    if project.update(cx, &predicate) {
3922                        cx.activate_window();
3923                        return Some(workspace_handle.clone());
3924                    }
3925                }
3926                None
3927            })
3928            .flatten();
3929
3930        if let Some(handle) = handle {
3931            return Some(handle.downgrade());
3932        }
3933    }
3934    None
3935}
3936
3937pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3938    DB.last_workspace().await.log_err().flatten()
3939}
3940
3941#[allow(clippy::type_complexity)]
3942pub fn open_paths(
3943    abs_paths: &[PathBuf],
3944    app_state: &Arc<AppState>,
3945    requesting_window: Option<WindowHandle<Workspace>>,
3946    cx: &mut AppContext,
3947) -> Task<
3948    Result<(
3949        WeakViewHandle<Workspace>,
3950        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3951    )>,
3952> {
3953    let app_state = app_state.clone();
3954    let abs_paths = abs_paths.to_vec();
3955    cx.spawn(|mut cx| async move {
3956        // Open paths in existing workspace if possible
3957        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3958            project.contains_paths(&abs_paths, cx)
3959        });
3960
3961        if let Some(existing) = existing {
3962            Ok((
3963                existing.clone(),
3964                existing
3965                    .update(&mut cx, |workspace, cx| {
3966                        workspace.open_paths(abs_paths, true, cx)
3967                    })?
3968                    .await,
3969            ))
3970        } else {
3971            Ok(cx
3972                .update(|cx| {
3973                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
3974                })
3975                .await)
3976        }
3977    })
3978}
3979
3980pub fn open_new(
3981    app_state: &Arc<AppState>,
3982    cx: &mut AppContext,
3983    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3984) -> Task<()> {
3985    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3986    cx.spawn(|mut cx| async move {
3987        let (workspace, opened_paths) = task.await;
3988
3989        workspace
3990            .update(&mut cx, |workspace, cx| {
3991                if opened_paths.is_empty() {
3992                    init(workspace, cx)
3993                }
3994            })
3995            .log_err();
3996    })
3997}
3998
3999pub fn create_and_open_local_file(
4000    path: &'static Path,
4001    cx: &mut ViewContext<Workspace>,
4002    default_content: impl 'static + Send + FnOnce() -> Rope,
4003) -> Task<Result<Box<dyn ItemHandle>>> {
4004    cx.spawn(|workspace, mut cx| async move {
4005        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
4006        if !fs.is_file(path).await {
4007            fs.create_file(path, Default::default()).await?;
4008            fs.save(path, &default_content(), Default::default())
4009                .await?;
4010        }
4011
4012        let mut items = workspace
4013            .update(&mut cx, |workspace, cx| {
4014                workspace.with_local_workspace(cx, |workspace, cx| {
4015                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4016                })
4017            })?
4018            .await?
4019            .await;
4020
4021        let item = items.pop().flatten();
4022        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4023    })
4024}
4025
4026pub fn join_remote_project(
4027    project_id: u64,
4028    follow_user_id: u64,
4029    app_state: Arc<AppState>,
4030    cx: &mut AppContext,
4031) -> Task<Result<()>> {
4032    cx.spawn(|mut cx| async move {
4033        let existing_workspace = cx
4034            .windows()
4035            .into_iter()
4036            .find_map(|window| {
4037                window.downcast::<Workspace>().and_then(|window| {
4038                    window.read_root_with(&cx, |workspace, cx| {
4039                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4040                            Some(cx.handle().downgrade())
4041                        } else {
4042                            None
4043                        }
4044                    })
4045                })
4046            })
4047            .flatten();
4048
4049        let workspace = if let Some(existing_workspace) = existing_workspace {
4050            existing_workspace
4051        } else {
4052            let active_call = cx.read(ActiveCall::global);
4053            let room = active_call
4054                .read_with(&cx, |call, _| call.room().cloned())
4055                .ok_or_else(|| anyhow!("not in a call"))?;
4056            let project = room
4057                .update(&mut cx, |room, cx| {
4058                    room.join_project(
4059                        project_id,
4060                        app_state.languages.clone(),
4061                        app_state.fs.clone(),
4062                        cx,
4063                    )
4064                })
4065                .await?;
4066
4067            let window_bounds_override = window_bounds_env_override(&cx);
4068            let window = cx.add_window(
4069                (app_state.build_window_options)(
4070                    window_bounds_override,
4071                    None,
4072                    cx.platform().as_ref(),
4073                ),
4074                |cx| Workspace::new(0, project, app_state.clone(), cx),
4075            );
4076            let workspace = window.root(&cx).unwrap();
4077            (app_state.initialize_workspace)(
4078                workspace.downgrade(),
4079                false,
4080                app_state.clone(),
4081                cx.clone(),
4082            )
4083            .await
4084            .log_err();
4085
4086            workspace.downgrade()
4087        };
4088
4089        workspace.window().activate(&mut cx);
4090        cx.platform().activate(true);
4091
4092        workspace.update(&mut cx, |workspace, cx| {
4093            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4094                let follow_peer_id = room
4095                    .read(cx)
4096                    .remote_participants()
4097                    .iter()
4098                    .find(|(_, participant)| participant.user.id == follow_user_id)
4099                    .map(|(_, p)| p.peer_id)
4100                    .or_else(|| {
4101                        // If we couldn't follow the given user, follow the host instead.
4102                        let collaborator = workspace
4103                            .project()
4104                            .read(cx)
4105                            .collaborators()
4106                            .values()
4107                            .find(|collaborator| collaborator.replica_id == 0)?;
4108                        Some(collaborator.peer_id)
4109                    });
4110
4111                if let Some(follow_peer_id) = follow_peer_id {
4112                    if !workspace.is_being_followed(follow_peer_id) {
4113                        workspace
4114                            .toggle_follow(follow_peer_id, cx)
4115                            .map(|follow| follow.detach_and_log_err(cx));
4116                    }
4117                }
4118            }
4119        })?;
4120
4121        anyhow::Ok(())
4122    })
4123}
4124
4125pub fn restart(_: &Restart, cx: &mut AppContext) {
4126    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4127    cx.spawn(|mut cx| async move {
4128        let mut workspace_windows = cx
4129            .windows()
4130            .into_iter()
4131            .filter_map(|window| window.downcast::<Workspace>())
4132            .collect::<Vec<_>>();
4133
4134        // If multiple windows have unsaved changes, and need a save prompt,
4135        // prompt in the active window before switching to a different window.
4136        workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4137
4138        if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4139            let answer = window.prompt(
4140                PromptLevel::Info,
4141                "Are you sure you want to restart?",
4142                &["Restart", "Cancel"],
4143                &mut cx,
4144            );
4145
4146            if let Some(mut answer) = answer {
4147                let answer = answer.next().await;
4148                if answer != Some(0) {
4149                    return Ok(());
4150                }
4151            }
4152        }
4153
4154        // If the user cancels any save prompt, then keep the app open.
4155        for window in workspace_windows {
4156            if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4157                workspace.prepare_to_close(true, cx)
4158            }) {
4159                if !should_close.await? {
4160                    return Ok(());
4161                }
4162            }
4163        }
4164        cx.platform().restart();
4165        anyhow::Ok(())
4166    })
4167    .detach_and_log_err(cx);
4168}
4169
4170fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4171    let mut parts = value.split(',');
4172    let width: usize = parts.next()?.parse().ok()?;
4173    let height: usize = parts.next()?.parse().ok()?;
4174    Some(vec2f(width as f32, height as f32))
4175}
4176
4177#[cfg(test)]
4178mod tests {
4179    use super::*;
4180    use crate::{
4181        dock::test::{TestPanel, TestPanelEvent},
4182        item::test::{TestItem, TestItemEvent, TestProjectItem},
4183    };
4184    use fs::FakeFs;
4185    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4186    use project::{Project, ProjectEntryId};
4187    use serde_json::json;
4188    use settings::SettingsStore;
4189    use std::{cell::RefCell, rc::Rc};
4190
4191    #[gpui::test]
4192    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4193        init_test(cx);
4194
4195        let fs = FakeFs::new(cx.background());
4196        let project = Project::test(fs, [], cx).await;
4197        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4198        let workspace = window.root(cx);
4199
4200        // Adding an item with no ambiguity renders the tab without detail.
4201        let item1 = window.add_view(cx, |_| {
4202            let mut item = TestItem::new();
4203            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4204            item
4205        });
4206        workspace.update(cx, |workspace, cx| {
4207            workspace.add_item(Box::new(item1.clone()), cx);
4208        });
4209        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4210
4211        // Adding an item that creates ambiguity increases the level of detail on
4212        // both tabs.
4213        let item2 = window.add_view(cx, |_| {
4214            let mut item = TestItem::new();
4215            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4216            item
4217        });
4218        workspace.update(cx, |workspace, cx| {
4219            workspace.add_item(Box::new(item2.clone()), cx);
4220        });
4221        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4222        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4223
4224        // Adding an item that creates ambiguity increases the level of detail only
4225        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4226        // we stop at the highest detail available.
4227        let item3 = window.add_view(cx, |_| {
4228            let mut item = TestItem::new();
4229            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4230            item
4231        });
4232        workspace.update(cx, |workspace, cx| {
4233            workspace.add_item(Box::new(item3.clone()), cx);
4234        });
4235        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4236        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4237        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4238    }
4239
4240    #[gpui::test]
4241    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4242        init_test(cx);
4243
4244        let fs = FakeFs::new(cx.background());
4245        fs.insert_tree(
4246            "/root1",
4247            json!({
4248                "one.txt": "",
4249                "two.txt": "",
4250            }),
4251        )
4252        .await;
4253        fs.insert_tree(
4254            "/root2",
4255            json!({
4256                "three.txt": "",
4257            }),
4258        )
4259        .await;
4260
4261        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4262        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4263        let workspace = window.root(cx);
4264        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4265        let worktree_id = project.read_with(cx, |project, cx| {
4266            project.worktrees(cx).next().unwrap().read(cx).id()
4267        });
4268
4269        let item1 = window.add_view(cx, |cx| {
4270            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4271        });
4272        let item2 = window.add_view(cx, |cx| {
4273            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4274        });
4275
4276        // Add an item to an empty pane
4277        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4278        project.read_with(cx, |project, cx| {
4279            assert_eq!(
4280                project.active_entry(),
4281                project
4282                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4283                    .map(|e| e.id)
4284            );
4285        });
4286        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4287
4288        // Add a second item to a non-empty pane
4289        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4290        assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
4291        project.read_with(cx, |project, cx| {
4292            assert_eq!(
4293                project.active_entry(),
4294                project
4295                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4296                    .map(|e| e.id)
4297            );
4298        });
4299
4300        // Close the active item
4301        pane.update(cx, |pane, cx| {
4302            pane.close_active_item(&Default::default(), cx).unwrap()
4303        })
4304        .await
4305        .unwrap();
4306        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4307        project.read_with(cx, |project, cx| {
4308            assert_eq!(
4309                project.active_entry(),
4310                project
4311                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4312                    .map(|e| e.id)
4313            );
4314        });
4315
4316        // Add a project folder
4317        project
4318            .update(cx, |project, cx| {
4319                project.find_or_create_local_worktree("/root2", true, cx)
4320            })
4321            .await
4322            .unwrap();
4323        assert_eq!(
4324            window.current_title(cx).as_deref(),
4325            Some("one.txt β€” root1, root2")
4326        );
4327
4328        // Remove a project folder
4329        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4330        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
4331    }
4332
4333    #[gpui::test]
4334    async fn test_close_window(cx: &mut TestAppContext) {
4335        init_test(cx);
4336
4337        let fs = FakeFs::new(cx.background());
4338        fs.insert_tree("/root", json!({ "one": "" })).await;
4339
4340        let project = Project::test(fs, ["root".as_ref()], cx).await;
4341        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4342        let workspace = window.root(cx);
4343
4344        // When there are no dirty items, there's nothing to do.
4345        let item1 = window.add_view(cx, |_| TestItem::new());
4346        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4347        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4348        assert!(task.await.unwrap());
4349
4350        // When there are dirty untitled items, prompt to save each one. If the user
4351        // cancels any prompt, then abort.
4352        let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
4353        let item3 = window.add_view(cx, |cx| {
4354            TestItem::new()
4355                .with_dirty(true)
4356                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4357        });
4358        workspace.update(cx, |w, cx| {
4359            w.add_item(Box::new(item2.clone()), cx);
4360            w.add_item(Box::new(item3.clone()), cx);
4361        });
4362        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4363        cx.foreground().run_until_parked();
4364        window.simulate_prompt_answer(2, cx); // cancel
4365        cx.foreground().run_until_parked();
4366        assert!(!window.has_pending_prompt(cx));
4367        assert!(!task.await.unwrap());
4368    }
4369
4370    #[gpui::test]
4371    async fn test_close_pane_items(cx: &mut TestAppContext) {
4372        init_test(cx);
4373
4374        let fs = FakeFs::new(cx.background());
4375
4376        let project = Project::test(fs, None, cx).await;
4377        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4378        let workspace = window.root(cx);
4379
4380        let item1 = window.add_view(cx, |cx| {
4381            TestItem::new()
4382                .with_dirty(true)
4383                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4384        });
4385        let item2 = window.add_view(cx, |cx| {
4386            TestItem::new()
4387                .with_dirty(true)
4388                .with_conflict(true)
4389                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4390        });
4391        let item3 = window.add_view(cx, |cx| {
4392            TestItem::new()
4393                .with_dirty(true)
4394                .with_conflict(true)
4395                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4396        });
4397        let item4 = window.add_view(cx, |cx| {
4398            TestItem::new()
4399                .with_dirty(true)
4400                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4401        });
4402        let pane = workspace.update(cx, |workspace, cx| {
4403            workspace.add_item(Box::new(item1.clone()), cx);
4404            workspace.add_item(Box::new(item2.clone()), cx);
4405            workspace.add_item(Box::new(item3.clone()), cx);
4406            workspace.add_item(Box::new(item4.clone()), cx);
4407            workspace.active_pane().clone()
4408        });
4409
4410        let close_items = pane.update(cx, |pane, cx| {
4411            pane.activate_item(1, true, true, cx);
4412            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4413            let item1_id = item1.id();
4414            let item3_id = item3.id();
4415            let item4_id = item4.id();
4416            pane.close_items(cx, SaveIntent::Close, move |id| {
4417                [item1_id, item3_id, item4_id].contains(&id)
4418            })
4419        });
4420        cx.foreground().run_until_parked();
4421
4422        // There's a prompt to save item 1.
4423        pane.read_with(cx, |pane, _| {
4424            assert_eq!(pane.items_len(), 4);
4425            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4426        });
4427        assert!(window.has_pending_prompt(cx));
4428
4429        // Confirm saving item 1.
4430        window.simulate_prompt_answer(0, cx);
4431        cx.foreground().run_until_parked();
4432
4433        // Item 1 is saved. There's a prompt to save item 3.
4434        pane.read_with(cx, |pane, cx| {
4435            assert_eq!(item1.read(cx).save_count, 1);
4436            assert_eq!(item1.read(cx).save_as_count, 0);
4437            assert_eq!(item1.read(cx).reload_count, 0);
4438            assert_eq!(pane.items_len(), 3);
4439            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4440        });
4441        assert!(window.has_pending_prompt(cx));
4442
4443        // Cancel saving item 3.
4444        window.simulate_prompt_answer(1, cx);
4445        cx.foreground().run_until_parked();
4446
4447        // Item 3 is reloaded. There's a prompt to save item 4.
4448        pane.read_with(cx, |pane, cx| {
4449            assert_eq!(item3.read(cx).save_count, 0);
4450            assert_eq!(item3.read(cx).save_as_count, 0);
4451            assert_eq!(item3.read(cx).reload_count, 1);
4452            assert_eq!(pane.items_len(), 2);
4453            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4454        });
4455        assert!(window.has_pending_prompt(cx));
4456
4457        // Confirm saving item 4.
4458        window.simulate_prompt_answer(0, cx);
4459        cx.foreground().run_until_parked();
4460
4461        // There's a prompt for a path for item 4.
4462        cx.simulate_new_path_selection(|_| Some(Default::default()));
4463        close_items.await.unwrap();
4464
4465        // The requested items are closed.
4466        pane.read_with(cx, |pane, cx| {
4467            assert_eq!(item4.read(cx).save_count, 0);
4468            assert_eq!(item4.read(cx).save_as_count, 1);
4469            assert_eq!(item4.read(cx).reload_count, 0);
4470            assert_eq!(pane.items_len(), 1);
4471            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4472        });
4473    }
4474
4475    #[gpui::test]
4476    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4477        init_test(cx);
4478
4479        let fs = FakeFs::new(cx.background());
4480
4481        let project = Project::test(fs, [], cx).await;
4482        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4483        let workspace = window.root(cx);
4484
4485        // Create several workspace items with single project entries, and two
4486        // workspace items with multiple project entries.
4487        let single_entry_items = (0..=4)
4488            .map(|project_entry_id| {
4489                window.add_view(cx, |cx| {
4490                    TestItem::new()
4491                        .with_dirty(true)
4492                        .with_project_items(&[TestProjectItem::new(
4493                            project_entry_id,
4494                            &format!("{project_entry_id}.txt"),
4495                            cx,
4496                        )])
4497                })
4498            })
4499            .collect::<Vec<_>>();
4500        let item_2_3 = window.add_view(cx, |cx| {
4501            TestItem::new()
4502                .with_dirty(true)
4503                .with_singleton(false)
4504                .with_project_items(&[
4505                    single_entry_items[2].read(cx).project_items[0].clone(),
4506                    single_entry_items[3].read(cx).project_items[0].clone(),
4507                ])
4508        });
4509        let item_3_4 = window.add_view(cx, |cx| {
4510            TestItem::new()
4511                .with_dirty(true)
4512                .with_singleton(false)
4513                .with_project_items(&[
4514                    single_entry_items[3].read(cx).project_items[0].clone(),
4515                    single_entry_items[4].read(cx).project_items[0].clone(),
4516                ])
4517        });
4518
4519        // Create two panes that contain the following project entries:
4520        //   left pane:
4521        //     multi-entry items:   (2, 3)
4522        //     single-entry items:  0, 1, 2, 3, 4
4523        //   right pane:
4524        //     single-entry items:  1
4525        //     multi-entry items:   (3, 4)
4526        let left_pane = workspace.update(cx, |workspace, cx| {
4527            let left_pane = workspace.active_pane().clone();
4528            workspace.add_item(Box::new(item_2_3.clone()), cx);
4529            for item in single_entry_items {
4530                workspace.add_item(Box::new(item), cx);
4531            }
4532            left_pane.update(cx, |pane, cx| {
4533                pane.activate_item(2, true, true, cx);
4534            });
4535
4536            workspace
4537                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4538                .unwrap();
4539
4540            left_pane
4541        });
4542
4543        //Need to cause an effect flush in order to respect new focus
4544        workspace.update(cx, |workspace, cx| {
4545            workspace.add_item(Box::new(item_3_4.clone()), cx);
4546            cx.focus(&left_pane);
4547        });
4548
4549        // When closing all of the items in the left pane, we should be prompted twice:
4550        // once for project entry 0, and once for project entry 2. After those two
4551        // prompts, the task should complete.
4552
4553        let close = left_pane.update(cx, |pane, cx| {
4554            pane.close_items(cx, SaveIntent::Close, move |_| true)
4555        });
4556        cx.foreground().run_until_parked();
4557        left_pane.read_with(cx, |pane, cx| {
4558            assert_eq!(
4559                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4560                &[ProjectEntryId::from_proto(0)]
4561            );
4562        });
4563        window.simulate_prompt_answer(0, cx);
4564
4565        cx.foreground().run_until_parked();
4566        left_pane.read_with(cx, |pane, cx| {
4567            assert_eq!(
4568                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4569                &[ProjectEntryId::from_proto(2)]
4570            );
4571        });
4572        window.simulate_prompt_answer(0, cx);
4573
4574        cx.foreground().run_until_parked();
4575        close.await.unwrap();
4576        left_pane.read_with(cx, |pane, _| {
4577            assert_eq!(pane.items_len(), 0);
4578        });
4579    }
4580
4581    #[gpui::test]
4582    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4583        init_test(cx);
4584
4585        let fs = FakeFs::new(cx.background());
4586
4587        let project = Project::test(fs, [], cx).await;
4588        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4589        let workspace = window.root(cx);
4590        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4591
4592        let item = window.add_view(cx, |cx| {
4593            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4594        });
4595        let item_id = item.id();
4596        workspace.update(cx, |workspace, cx| {
4597            workspace.add_item(Box::new(item.clone()), cx);
4598        });
4599
4600        // Autosave on window change.
4601        item.update(cx, |item, cx| {
4602            cx.update_global(|settings: &mut SettingsStore, cx| {
4603                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4604                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4605                })
4606            });
4607            item.is_dirty = true;
4608        });
4609
4610        // Deactivating the window saves the file.
4611        window.simulate_deactivation(cx);
4612        deterministic.run_until_parked();
4613        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4614
4615        // Autosave on focus change.
4616        item.update(cx, |item, cx| {
4617            cx.focus_self();
4618            cx.update_global(|settings: &mut SettingsStore, cx| {
4619                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4620                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4621                })
4622            });
4623            item.is_dirty = true;
4624        });
4625
4626        // Blurring the item saves the file.
4627        item.update(cx, |_, cx| cx.blur());
4628        deterministic.run_until_parked();
4629        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4630
4631        // Deactivating the window still saves the file.
4632        window.simulate_activation(cx);
4633        item.update(cx, |item, cx| {
4634            cx.focus_self();
4635            item.is_dirty = true;
4636        });
4637        window.simulate_deactivation(cx);
4638
4639        deterministic.run_until_parked();
4640        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4641
4642        // Autosave after delay.
4643        item.update(cx, |item, cx| {
4644            cx.update_global(|settings: &mut SettingsStore, cx| {
4645                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4646                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4647                })
4648            });
4649            item.is_dirty = true;
4650            cx.emit(TestItemEvent::Edit);
4651        });
4652
4653        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4654        deterministic.advance_clock(Duration::from_millis(250));
4655        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4656
4657        // After delay expires, the file is saved.
4658        deterministic.advance_clock(Duration::from_millis(250));
4659        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4660
4661        // Autosave on focus change, ensuring closing the tab counts as such.
4662        item.update(cx, |item, cx| {
4663            cx.update_global(|settings: &mut SettingsStore, cx| {
4664                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4665                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4666                })
4667            });
4668            item.is_dirty = true;
4669        });
4670
4671        pane.update(cx, |pane, cx| {
4672            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4673        })
4674        .await
4675        .unwrap();
4676        assert!(!window.has_pending_prompt(cx));
4677        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4678
4679        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4680        workspace.update(cx, |workspace, cx| {
4681            workspace.add_item(Box::new(item.clone()), cx);
4682        });
4683        item.update(cx, |item, cx| {
4684            item.project_items[0].update(cx, |item, _| {
4685                item.entry_id = None;
4686            });
4687            item.is_dirty = true;
4688            cx.blur();
4689        });
4690        deterministic.run_until_parked();
4691        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4692
4693        // Ensure autosave is prevented for deleted files also when closing the buffer.
4694        let _close_items = pane.update(cx, |pane, cx| {
4695            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4696        });
4697        deterministic.run_until_parked();
4698        assert!(window.has_pending_prompt(cx));
4699        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4700    }
4701
4702    #[gpui::test]
4703    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4704        init_test(cx);
4705
4706        let fs = FakeFs::new(cx.background());
4707
4708        let project = Project::test(fs, [], cx).await;
4709        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4710        let workspace = window.root(cx);
4711
4712        let item = window.add_view(cx, |cx| {
4713            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4714        });
4715        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4716        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4717        let toolbar_notify_count = Rc::new(RefCell::new(0));
4718
4719        workspace.update(cx, |workspace, cx| {
4720            workspace.add_item(Box::new(item.clone()), cx);
4721            let toolbar_notification_count = toolbar_notify_count.clone();
4722            cx.observe(&toolbar, move |_, _, _| {
4723                *toolbar_notification_count.borrow_mut() += 1
4724            })
4725            .detach();
4726        });
4727
4728        pane.read_with(cx, |pane, _| {
4729            assert!(!pane.can_navigate_backward());
4730            assert!(!pane.can_navigate_forward());
4731        });
4732
4733        item.update(cx, |item, cx| {
4734            item.set_state("one".to_string(), cx);
4735        });
4736
4737        // Toolbar must be notified to re-render the navigation buttons
4738        assert_eq!(*toolbar_notify_count.borrow(), 1);
4739
4740        pane.read_with(cx, |pane, _| {
4741            assert!(pane.can_navigate_backward());
4742            assert!(!pane.can_navigate_forward());
4743        });
4744
4745        workspace
4746            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4747            .await
4748            .unwrap();
4749
4750        assert_eq!(*toolbar_notify_count.borrow(), 3);
4751        pane.read_with(cx, |pane, _| {
4752            assert!(!pane.can_navigate_backward());
4753            assert!(pane.can_navigate_forward());
4754        });
4755    }
4756
4757    #[gpui::test]
4758    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4759        init_test(cx);
4760        let fs = FakeFs::new(cx.background());
4761
4762        let project = Project::test(fs, [], cx).await;
4763        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4764        let workspace = window.root(cx);
4765
4766        let panel = workspace.update(cx, |workspace, cx| {
4767            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4768            workspace.add_panel(panel.clone(), cx);
4769
4770            workspace
4771                .right_dock()
4772                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4773
4774            panel
4775        });
4776
4777        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4778        pane.update(cx, |pane, cx| {
4779            let item = cx.add_view(|_| TestItem::new());
4780            pane.add_item(Box::new(item), true, true, None, cx);
4781        });
4782
4783        // Transfer focus from center to panel
4784        workspace.update(cx, |workspace, cx| {
4785            workspace.toggle_panel_focus::<TestPanel>(cx);
4786        });
4787
4788        workspace.read_with(cx, |workspace, cx| {
4789            assert!(workspace.right_dock().read(cx).is_open());
4790            assert!(!panel.is_zoomed(cx));
4791            assert!(panel.has_focus(cx));
4792        });
4793
4794        // Transfer focus from panel to center
4795        workspace.update(cx, |workspace, cx| {
4796            workspace.toggle_panel_focus::<TestPanel>(cx);
4797        });
4798
4799        workspace.read_with(cx, |workspace, cx| {
4800            assert!(workspace.right_dock().read(cx).is_open());
4801            assert!(!panel.is_zoomed(cx));
4802            assert!(!panel.has_focus(cx));
4803        });
4804
4805        // Close the dock
4806        workspace.update(cx, |workspace, cx| {
4807            workspace.toggle_dock(DockPosition::Right, cx);
4808        });
4809
4810        workspace.read_with(cx, |workspace, cx| {
4811            assert!(!workspace.right_dock().read(cx).is_open());
4812            assert!(!panel.is_zoomed(cx));
4813            assert!(!panel.has_focus(cx));
4814        });
4815
4816        // Open the dock
4817        workspace.update(cx, |workspace, cx| {
4818            workspace.toggle_dock(DockPosition::Right, cx);
4819        });
4820
4821        workspace.read_with(cx, |workspace, cx| {
4822            assert!(workspace.right_dock().read(cx).is_open());
4823            assert!(!panel.is_zoomed(cx));
4824            assert!(panel.has_focus(cx));
4825        });
4826
4827        // Focus and zoom panel
4828        panel.update(cx, |panel, cx| {
4829            cx.focus_self();
4830            panel.set_zoomed(true, cx)
4831        });
4832
4833        workspace.read_with(cx, |workspace, cx| {
4834            assert!(workspace.right_dock().read(cx).is_open());
4835            assert!(panel.is_zoomed(cx));
4836            assert!(panel.has_focus(cx));
4837        });
4838
4839        // Transfer focus to the center closes the dock
4840        workspace.update(cx, |workspace, cx| {
4841            workspace.toggle_panel_focus::<TestPanel>(cx);
4842        });
4843
4844        workspace.read_with(cx, |workspace, cx| {
4845            assert!(!workspace.right_dock().read(cx).is_open());
4846            assert!(panel.is_zoomed(cx));
4847            assert!(!panel.has_focus(cx));
4848        });
4849
4850        // Transferring focus back to the panel keeps it zoomed
4851        workspace.update(cx, |workspace, cx| {
4852            workspace.toggle_panel_focus::<TestPanel>(cx);
4853        });
4854
4855        workspace.read_with(cx, |workspace, cx| {
4856            assert!(workspace.right_dock().read(cx).is_open());
4857            assert!(panel.is_zoomed(cx));
4858            assert!(panel.has_focus(cx));
4859        });
4860
4861        // Close the dock while it is zoomed
4862        workspace.update(cx, |workspace, cx| {
4863            workspace.toggle_dock(DockPosition::Right, cx)
4864        });
4865
4866        workspace.read_with(cx, |workspace, cx| {
4867            assert!(!workspace.right_dock().read(cx).is_open());
4868            assert!(panel.is_zoomed(cx));
4869            assert!(workspace.zoomed.is_none());
4870            assert!(!panel.has_focus(cx));
4871        });
4872
4873        // Opening the dock, when it's zoomed, retains focus
4874        workspace.update(cx, |workspace, cx| {
4875            workspace.toggle_dock(DockPosition::Right, cx)
4876        });
4877
4878        workspace.read_with(cx, |workspace, cx| {
4879            assert!(workspace.right_dock().read(cx).is_open());
4880            assert!(panel.is_zoomed(cx));
4881            assert!(workspace.zoomed.is_some());
4882            assert!(panel.has_focus(cx));
4883        });
4884
4885        // Unzoom and close the panel, zoom the active pane.
4886        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
4887        workspace.update(cx, |workspace, cx| {
4888            workspace.toggle_dock(DockPosition::Right, cx)
4889        });
4890        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
4891
4892        // Opening a dock unzooms the pane.
4893        workspace.update(cx, |workspace, cx| {
4894            workspace.toggle_dock(DockPosition::Right, cx)
4895        });
4896        workspace.read_with(cx, |workspace, cx| {
4897            let pane = pane.read(cx);
4898            assert!(!pane.is_zoomed());
4899            assert!(!pane.has_focus());
4900            assert!(workspace.right_dock().read(cx).is_open());
4901            assert!(workspace.zoomed.is_none());
4902        });
4903    }
4904
4905    #[gpui::test]
4906    async fn test_panels(cx: &mut gpui::TestAppContext) {
4907        init_test(cx);
4908        let fs = FakeFs::new(cx.background());
4909
4910        let project = Project::test(fs, [], cx).await;
4911        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4912        let workspace = window.root(cx);
4913
4914        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4915            // Add panel_1 on the left, panel_2 on the right.
4916            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4917            workspace.add_panel(panel_1.clone(), cx);
4918            workspace
4919                .left_dock()
4920                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4921            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4922            workspace.add_panel(panel_2.clone(), cx);
4923            workspace
4924                .right_dock()
4925                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4926
4927            let left_dock = workspace.left_dock();
4928            assert_eq!(
4929                left_dock.read(cx).visible_panel().unwrap().id(),
4930                panel_1.id()
4931            );
4932            assert_eq!(
4933                left_dock.read(cx).active_panel_size(cx).unwrap(),
4934                panel_1.size(cx)
4935            );
4936
4937            left_dock.update(cx, |left_dock, cx| {
4938                left_dock.resize_active_panel(Some(1337.), cx)
4939            });
4940            assert_eq!(
4941                workspace
4942                    .right_dock()
4943                    .read(cx)
4944                    .visible_panel()
4945                    .unwrap()
4946                    .id(),
4947                panel_2.id()
4948            );
4949
4950            (panel_1, panel_2)
4951        });
4952
4953        // Move panel_1 to the right
4954        panel_1.update(cx, |panel_1, cx| {
4955            panel_1.set_position(DockPosition::Right, cx)
4956        });
4957
4958        workspace.update(cx, |workspace, cx| {
4959            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4960            // Since it was the only panel on the left, the left dock should now be closed.
4961            assert!(!workspace.left_dock().read(cx).is_open());
4962            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
4963            let right_dock = workspace.right_dock();
4964            assert_eq!(
4965                right_dock.read(cx).visible_panel().unwrap().id(),
4966                panel_1.id()
4967            );
4968            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4969
4970            // Now we move panel_2Β to the left
4971            panel_2.set_position(DockPosition::Left, cx);
4972        });
4973
4974        workspace.update(cx, |workspace, cx| {
4975            // Since panel_2 was not visible on the right, we don't open the left dock.
4976            assert!(!workspace.left_dock().read(cx).is_open());
4977            // And the right dock is unaffected in it's displaying of panel_1
4978            assert!(workspace.right_dock().read(cx).is_open());
4979            assert_eq!(
4980                workspace
4981                    .right_dock()
4982                    .read(cx)
4983                    .visible_panel()
4984                    .unwrap()
4985                    .id(),
4986                panel_1.id()
4987            );
4988        });
4989
4990        // Move panel_1 back to the left
4991        panel_1.update(cx, |panel_1, cx| {
4992            panel_1.set_position(DockPosition::Left, cx)
4993        });
4994
4995        workspace.update(cx, |workspace, cx| {
4996            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4997            let left_dock = workspace.left_dock();
4998            assert!(left_dock.read(cx).is_open());
4999            assert_eq!(
5000                left_dock.read(cx).visible_panel().unwrap().id(),
5001                panel_1.id()
5002            );
5003            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5004            // And right the dock should be closed as it no longer has any panels.
5005            assert!(!workspace.right_dock().read(cx).is_open());
5006
5007            // Now we move panel_1 to the bottom
5008            panel_1.set_position(DockPosition::Bottom, cx);
5009        });
5010
5011        workspace.update(cx, |workspace, cx| {
5012            // Since panel_1 was visible on the left, we close the left dock.
5013            assert!(!workspace.left_dock().read(cx).is_open());
5014            // The bottom dock is sized based on the panel's default size,
5015            // since the panel orientation changed from vertical to horizontal.
5016            let bottom_dock = workspace.bottom_dock();
5017            assert_eq!(
5018                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5019                panel_1.size(cx),
5020            );
5021            // Close bottom dock and move panel_1 back to the left.
5022            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5023            panel_1.set_position(DockPosition::Left, cx);
5024        });
5025
5026        // Emit activated event on panel 1
5027        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5028
5029        // Now the left dock is open and panel_1 is active and focused.
5030        workspace.read_with(cx, |workspace, cx| {
5031            let left_dock = workspace.left_dock();
5032            assert!(left_dock.read(cx).is_open());
5033            assert_eq!(
5034                left_dock.read(cx).visible_panel().unwrap().id(),
5035                panel_1.id()
5036            );
5037            assert!(panel_1.is_focused(cx));
5038        });
5039
5040        // Emit closed event on panel 2, which is not active
5041        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5042
5043        // Wo don't close the left dock, because panel_2 wasn't the active panel
5044        workspace.read_with(cx, |workspace, cx| {
5045            let left_dock = workspace.left_dock();
5046            assert!(left_dock.read(cx).is_open());
5047            assert_eq!(
5048                left_dock.read(cx).visible_panel().unwrap().id(),
5049                panel_1.id()
5050            );
5051        });
5052
5053        // Emitting a ZoomIn event shows the panel as zoomed.
5054        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5055        workspace.read_with(cx, |workspace, _| {
5056            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5057            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5058        });
5059
5060        // Move panel to another dock while it is zoomed
5061        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
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::Right));
5065        });
5066
5067        // If focus is transferred to another view that's not a panel or another pane, we still show
5068        // the panel as zoomed.
5069        let focus_receiver = window.add_view(cx, |_| EmptyView);
5070        focus_receiver.update(cx, |_, cx| cx.focus_self());
5071        workspace.read_with(cx, |workspace, _| {
5072            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5073            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5074        });
5075
5076        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5077        workspace.update(cx, |_, cx| cx.focus_self());
5078        workspace.read_with(cx, |workspace, _| {
5079            assert_eq!(workspace.zoomed, None);
5080            assert_eq!(workspace.zoomed_position, None);
5081        });
5082
5083        // If focus is transferred again to another view that's not a panel or a pane, we won't
5084        // show the panel as zoomed because it wasn't zoomed before.
5085        focus_receiver.update(cx, |_, cx| cx.focus_self());
5086        workspace.read_with(cx, |workspace, _| {
5087            assert_eq!(workspace.zoomed, None);
5088            assert_eq!(workspace.zoomed_position, None);
5089        });
5090
5091        // When focus is transferred back to the panel, it is zoomed again.
5092        panel_1.update(cx, |_, cx| cx.focus_self());
5093        workspace.read_with(cx, |workspace, _| {
5094            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5095            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5096        });
5097
5098        // Emitting a ZoomOut event unzooms the panel.
5099        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5100        workspace.read_with(cx, |workspace, _| {
5101            assert_eq!(workspace.zoomed, None);
5102            assert_eq!(workspace.zoomed_position, None);
5103        });
5104
5105        // Emit closed event on panel 1, which is active
5106        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5107
5108        // Now the left dock is closed, because panel_1 was the active panel
5109        workspace.read_with(cx, |workspace, cx| {
5110            let right_dock = workspace.right_dock();
5111            assert!(!right_dock.read(cx).is_open());
5112        });
5113    }
5114
5115    pub fn init_test(cx: &mut TestAppContext) {
5116        cx.foreground().forbid_parking();
5117        cx.update(|cx| {
5118            cx.set_global(SettingsStore::test(cx));
5119            theme::init((), cx);
5120            language::init(cx);
5121            crate::init_settings(cx);
5122            Project::init_settings(cx);
5123        });
5124    }
5125}