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