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    dbg!("join channel internal");
4166    let should_prompt = active_call.read_with(cx, |active_call, cx| {
4167        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4168            return false;
4169        };
4170
4171        room.is_sharing_project()
4172            && room.remote_participants().len() > 0
4173            && room.channel_id() != Some(channel_id)
4174    });
4175
4176    if should_prompt {
4177        if let Some(workspace) = requesting_window {
4178            if let Some(window) = workspace.update(cx, |cx| cx.window()) {
4179                let answer = window.prompt(
4180                    PromptLevel::Warning,
4181                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4182                    &["Yes, Join Channel", "Cancel"],
4183                    cx,
4184                );
4185
4186                if let Some(mut answer) = answer {
4187                    if answer.next().await == Some(1) {
4188                        return Ok(false);
4189                    }
4190                }
4191            } else {
4192                return Ok(false); // unreachable!() hopefully
4193            }
4194        } else {
4195            return Ok(false); // unreachable!() hopefully
4196        }
4197    }
4198    dbg!("asdajdkjkasd");
4199
4200    let client = cx.read(|cx| active_call.read(cx).client());
4201
4202    let mut client_status = client.status();
4203
4204    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4205    'outer: loop {
4206        let Some(status) = client_status.recv().await else {
4207            return Err(anyhow!("error connecting"));
4208        };
4209
4210        match status {
4211            Status::Connecting
4212            | Status::Authenticating
4213            | Status::Reconnecting
4214            | Status::Reauthenticating => continue,
4215            Status::Connected { .. } => break 'outer,
4216            Status::SignedOut => return Err(anyhow!("not signed in")),
4217            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4218            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4219                return Err(anyhow!("zed is offline"))
4220            }
4221        }
4222    }
4223
4224    dbg!("past here");
4225
4226    let room = active_call
4227        .update(cx, |active_call, cx| {
4228            active_call.join_channel(channel_id, cx)
4229        })
4230        .await?;
4231
4232    room.update(cx, |room, _| room.next_room_update()).await;
4233
4234    dbg!("wow");
4235
4236    let task = room.update(cx, |room, cx| {
4237        if let Some((project, host)) = room.most_active_project() {
4238            return Some(join_remote_project(project, host, app_state.clone(), cx));
4239        }
4240
4241        None
4242    });
4243    if let Some(task) = task {
4244        task.await?;
4245        return anyhow::Ok(true);
4246    }
4247    use std::io::Write;
4248    writeln!(
4249        OpenOptions::new()
4250            .write(true)
4251            .append(true)
4252            .open("/Users/conrad/dbg")
4253            .unwrap(),
4254        "no jokes"
4255    )
4256    .unwrap();
4257    anyhow::Ok(false)
4258}
4259
4260pub fn join_channel(
4261    channel_id: u64,
4262    app_state: Arc<AppState>,
4263    requesting_window: Option<WindowHandle<Workspace>>,
4264    cx: &mut AppContext,
4265) -> Task<Result<()>> {
4266    let active_call = ActiveCall::global(cx);
4267    cx.spawn(|mut cx| async move {
4268        let result = join_channel_internal(
4269            channel_id,
4270            &app_state,
4271            requesting_window,
4272            &active_call,
4273            &mut cx,
4274        )
4275        .await;
4276        dbg!("joined!");
4277
4278        // join channel succeeded, and opened a window
4279        if matches!(result, Ok(true)) {
4280            return anyhow::Ok(());
4281        }
4282
4283        if requesting_window.is_some() {
4284            return anyhow::Ok(());
4285        }
4286
4287        // find an existing workspace to focus and show call controls
4288        let mut active_window = activate_any_workspace_window(&mut cx);
4289        if active_window.is_none() {
4290            // no open workspaces, make one to show the error in (blergh)
4291            cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))
4292                .await;
4293        }
4294
4295        active_window = activate_any_workspace_window(&mut cx);
4296        if active_window.is_none() {
4297            return result.map(|_| ()); // unreachable!() assuming new_local always opens a window
4298        }
4299
4300        if let Err(err) = result {
4301            let prompt = active_window.unwrap().prompt(
4302                PromptLevel::Critical,
4303                &format!("Failed to join channel: {}", err),
4304                &["Ok"],
4305                &mut cx,
4306            );
4307            if let Some(mut prompt) = prompt {
4308                prompt.next().await;
4309            } else {
4310                return Err(err);
4311            }
4312        }
4313
4314        // return ok, we showed the error to the user.
4315        return anyhow::Ok(());
4316    })
4317}
4318
4319pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4320    for window in cx.windows() {
4321        let found = window.update(cx, |cx| {
4322            let is_workspace = cx.root_view().clone().downcast::<Workspace>().is_some();
4323            if is_workspace {
4324                cx.activate_window();
4325            }
4326            is_workspace
4327        });
4328        if found == Some(true) {
4329            return Some(window);
4330        }
4331    }
4332    None
4333}
4334
4335#[allow(clippy::type_complexity)]
4336pub fn open_paths(
4337    abs_paths: &[PathBuf],
4338    app_state: &Arc<AppState>,
4339    requesting_window: Option<WindowHandle<Workspace>>,
4340    cx: &mut AppContext,
4341) -> Task<
4342    Result<(
4343        WeakViewHandle<Workspace>,
4344        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4345    )>,
4346> {
4347    let app_state = app_state.clone();
4348    let abs_paths = abs_paths.to_vec();
4349    cx.spawn(|mut cx| async move {
4350        // Open paths in existing workspace if possible
4351        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
4352            project.contains_paths(&abs_paths, cx)
4353        });
4354
4355        if let Some(existing) = existing {
4356            Ok((
4357                existing.clone(),
4358                existing
4359                    .update(&mut cx, |workspace, cx| {
4360                        workspace.open_paths(abs_paths, true, cx)
4361                    })?
4362                    .await,
4363            ))
4364        } else {
4365            Ok(cx
4366                .update(|cx| {
4367                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4368                })
4369                .await)
4370        }
4371    })
4372}
4373
4374pub fn open_new(
4375    app_state: &Arc<AppState>,
4376    cx: &mut AppContext,
4377    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
4378) -> Task<()> {
4379    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4380    cx.spawn(|mut cx| async move {
4381        let (workspace, opened_paths) = task.await;
4382
4383        workspace
4384            .update(&mut cx, |workspace, cx| {
4385                if opened_paths.is_empty() {
4386                    init(workspace, cx)
4387                }
4388            })
4389            .log_err();
4390    })
4391}
4392
4393pub fn create_and_open_local_file(
4394    path: &'static Path,
4395    cx: &mut ViewContext<Workspace>,
4396    default_content: impl 'static + Send + FnOnce() -> Rope,
4397) -> Task<Result<Box<dyn ItemHandle>>> {
4398    cx.spawn(|workspace, mut cx| async move {
4399        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
4400        if !fs.is_file(path).await {
4401            fs.create_file(path, Default::default()).await?;
4402            fs.save(path, &default_content(), Default::default())
4403                .await?;
4404        }
4405
4406        let mut items = workspace
4407            .update(&mut cx, |workspace, cx| {
4408                workspace.with_local_workspace(cx, |workspace, cx| {
4409                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4410                })
4411            })?
4412            .await?
4413            .await;
4414
4415        let item = items.pop().flatten();
4416        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4417    })
4418}
4419
4420pub fn join_remote_project(
4421    project_id: u64,
4422    follow_user_id: u64,
4423    app_state: Arc<AppState>,
4424    cx: &mut AppContext,
4425) -> Task<Result<()>> {
4426    cx.spawn(|mut cx| async move {
4427        let windows = cx.windows();
4428        let existing_workspace = windows.into_iter().find_map(|window| {
4429            window.downcast::<Workspace>().and_then(|window| {
4430                window
4431                    .read_root_with(&cx, |workspace, cx| {
4432                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4433                            Some(cx.handle().downgrade())
4434                        } else {
4435                            None
4436                        }
4437                    })
4438                    .unwrap_or(None)
4439            })
4440        });
4441
4442        let workspace = if let Some(existing_workspace) = existing_workspace {
4443            existing_workspace
4444        } else {
4445            let active_call = cx.read(ActiveCall::global);
4446            let room = active_call
4447                .read_with(&cx, |call, _| call.room().cloned())
4448                .ok_or_else(|| anyhow!("not in a call"))?;
4449            let project = room
4450                .update(&mut cx, |room, cx| {
4451                    room.join_project(
4452                        project_id,
4453                        app_state.languages.clone(),
4454                        app_state.fs.clone(),
4455                        cx,
4456                    )
4457                })
4458                .await?;
4459
4460            let window_bounds_override = window_bounds_env_override(&cx);
4461            let window = cx.add_window(
4462                (app_state.build_window_options)(
4463                    window_bounds_override,
4464                    None,
4465                    cx.platform().as_ref(),
4466                ),
4467                |cx| Workspace::new(0, project, app_state.clone(), cx),
4468            );
4469            let workspace = window.root(&cx).unwrap();
4470            (app_state.initialize_workspace)(
4471                workspace.downgrade(),
4472                false,
4473                app_state.clone(),
4474                cx.clone(),
4475            )
4476            .await
4477            .log_err();
4478
4479            workspace.downgrade()
4480        };
4481
4482        workspace.window().activate(&mut cx);
4483        cx.platform().activate(true);
4484
4485        workspace.update(&mut cx, |workspace, cx| {
4486            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4487                let follow_peer_id = room
4488                    .read(cx)
4489                    .remote_participants()
4490                    .iter()
4491                    .find(|(_, participant)| participant.user.id == follow_user_id)
4492                    .map(|(_, p)| p.peer_id)
4493                    .or_else(|| {
4494                        // If we couldn't follow the given user, follow the host instead.
4495                        let collaborator = workspace
4496                            .project()
4497                            .read(cx)
4498                            .collaborators()
4499                            .values()
4500                            .find(|collaborator| collaborator.replica_id == 0)?;
4501                        Some(collaborator.peer_id)
4502                    });
4503
4504                if let Some(follow_peer_id) = follow_peer_id {
4505                    workspace
4506                        .follow(follow_peer_id, cx)
4507                        .map(|follow| follow.detach_and_log_err(cx));
4508                }
4509            }
4510        })?;
4511
4512        anyhow::Ok(())
4513    })
4514}
4515
4516pub fn restart(_: &Restart, cx: &mut AppContext) {
4517    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4518    cx.spawn(|mut cx| async move {
4519        let mut workspace_windows = cx
4520            .windows()
4521            .into_iter()
4522            .filter_map(|window| window.downcast::<Workspace>())
4523            .collect::<Vec<_>>();
4524
4525        // If multiple windows have unsaved changes, and need a save prompt,
4526        // prompt in the active window before switching to a different window.
4527        workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4528
4529        if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4530            let answer = window.prompt(
4531                PromptLevel::Info,
4532                "Are you sure you want to restart?",
4533                &["Restart", "Cancel"],
4534                &mut cx,
4535            );
4536
4537            if let Some(mut answer) = answer {
4538                let answer = answer.next().await;
4539                if answer != Some(0) {
4540                    return Ok(());
4541                }
4542            }
4543        }
4544
4545        // If the user cancels any save prompt, then keep the app open.
4546        for window in workspace_windows {
4547            if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4548                workspace.prepare_to_close(true, cx)
4549            }) {
4550                if !should_close.await? {
4551                    return Ok(());
4552                }
4553            }
4554        }
4555        cx.platform().restart();
4556        anyhow::Ok(())
4557    })
4558    .detach_and_log_err(cx);
4559}
4560
4561fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4562    let mut parts = value.split(',');
4563    let width: usize = parts.next()?.parse().ok()?;
4564    let height: usize = parts.next()?.parse().ok()?;
4565    Some(vec2f(width as f32, height as f32))
4566}
4567
4568#[cfg(test)]
4569mod tests {
4570    use super::*;
4571    use crate::{
4572        dock::test::{TestPanel, TestPanelEvent},
4573        item::test::{TestItem, TestItemEvent, TestProjectItem},
4574    };
4575    use fs::FakeFs;
4576    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4577    use project::{Project, ProjectEntryId};
4578    use serde_json::json;
4579    use settings::SettingsStore;
4580    use std::{cell::RefCell, rc::Rc};
4581
4582    #[gpui::test]
4583    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4584        init_test(cx);
4585
4586        let fs = FakeFs::new(cx.background());
4587        let project = Project::test(fs, [], cx).await;
4588        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4589        let workspace = window.root(cx);
4590
4591        // Adding an item with no ambiguity renders the tab without detail.
4592        let item1 = window.add_view(cx, |_| {
4593            let mut item = TestItem::new();
4594            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4595            item
4596        });
4597        workspace.update(cx, |workspace, cx| {
4598            workspace.add_item(Box::new(item1.clone()), cx);
4599        });
4600        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4601
4602        // Adding an item that creates ambiguity increases the level of detail on
4603        // both tabs.
4604        let item2 = window.add_view(cx, |_| {
4605            let mut item = TestItem::new();
4606            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4607            item
4608        });
4609        workspace.update(cx, |workspace, cx| {
4610            workspace.add_item(Box::new(item2.clone()), cx);
4611        });
4612        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4613        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4614
4615        // Adding an item that creates ambiguity increases the level of detail only
4616        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4617        // we stop at the highest detail available.
4618        let item3 = window.add_view(cx, |_| {
4619            let mut item = TestItem::new();
4620            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4621            item
4622        });
4623        workspace.update(cx, |workspace, cx| {
4624            workspace.add_item(Box::new(item3.clone()), cx);
4625        });
4626        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4627        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4628        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4629    }
4630
4631    #[gpui::test]
4632    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4633        init_test(cx);
4634
4635        let fs = FakeFs::new(cx.background());
4636        fs.insert_tree(
4637            "/root1",
4638            json!({
4639                "one.txt": "",
4640                "two.txt": "",
4641            }),
4642        )
4643        .await;
4644        fs.insert_tree(
4645            "/root2",
4646            json!({
4647                "three.txt": "",
4648            }),
4649        )
4650        .await;
4651
4652        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4653        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4654        let workspace = window.root(cx);
4655        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4656        let worktree_id = project.read_with(cx, |project, cx| {
4657            project.worktrees(cx).next().unwrap().read(cx).id()
4658        });
4659
4660        let item1 = window.add_view(cx, |cx| {
4661            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4662        });
4663        let item2 = window.add_view(cx, |cx| {
4664            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4665        });
4666
4667        // Add an item to an empty pane
4668        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4669        project.read_with(cx, |project, cx| {
4670            assert_eq!(
4671                project.active_entry(),
4672                project
4673                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4674                    .map(|e| e.id)
4675            );
4676        });
4677        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4678
4679        // Add a second item to a non-empty pane
4680        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4681        assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
4682        project.read_with(cx, |project, cx| {
4683            assert_eq!(
4684                project.active_entry(),
4685                project
4686                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4687                    .map(|e| e.id)
4688            );
4689        });
4690
4691        // Close the active item
4692        pane.update(cx, |pane, cx| {
4693            pane.close_active_item(&Default::default(), cx).unwrap()
4694        })
4695        .await
4696        .unwrap();
4697        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4698        project.read_with(cx, |project, cx| {
4699            assert_eq!(
4700                project.active_entry(),
4701                project
4702                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4703                    .map(|e| e.id)
4704            );
4705        });
4706
4707        // Add a project folder
4708        project
4709            .update(cx, |project, cx| {
4710                project.find_or_create_local_worktree("/root2", true, cx)
4711            })
4712            .await
4713            .unwrap();
4714        assert_eq!(
4715            window.current_title(cx).as_deref(),
4716            Some("one.txt β€” root1, root2")
4717        );
4718
4719        // Remove a project folder
4720        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4721        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
4722    }
4723
4724    #[gpui::test]
4725    async fn test_close_window(cx: &mut TestAppContext) {
4726        init_test(cx);
4727
4728        let fs = FakeFs::new(cx.background());
4729        fs.insert_tree("/root", json!({ "one": "" })).await;
4730
4731        let project = Project::test(fs, ["root".as_ref()], cx).await;
4732        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4733        let workspace = window.root(cx);
4734
4735        // When there are no dirty items, there's nothing to do.
4736        let item1 = window.add_view(cx, |_| TestItem::new());
4737        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4738        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4739        assert!(task.await.unwrap());
4740
4741        // When there are dirty untitled items, prompt to save each one. If the user
4742        // cancels any prompt, then abort.
4743        let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
4744        let item3 = window.add_view(cx, |cx| {
4745            TestItem::new()
4746                .with_dirty(true)
4747                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4748        });
4749        workspace.update(cx, |w, cx| {
4750            w.add_item(Box::new(item2.clone()), cx);
4751            w.add_item(Box::new(item3.clone()), cx);
4752        });
4753        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4754        cx.foreground().run_until_parked();
4755        window.simulate_prompt_answer(2, cx); // cancel save all
4756        cx.foreground().run_until_parked();
4757        window.simulate_prompt_answer(2, cx); // cancel save all
4758        cx.foreground().run_until_parked();
4759        assert!(!window.has_pending_prompt(cx));
4760        assert!(!task.await.unwrap());
4761    }
4762
4763    #[gpui::test]
4764    async fn test_close_pane_items(cx: &mut TestAppContext) {
4765        init_test(cx);
4766
4767        let fs = FakeFs::new(cx.background());
4768
4769        let project = Project::test(fs, None, cx).await;
4770        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4771        let workspace = window.root(cx);
4772
4773        let item1 = window.add_view(cx, |cx| {
4774            TestItem::new()
4775                .with_dirty(true)
4776                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4777        });
4778        let item2 = window.add_view(cx, |cx| {
4779            TestItem::new()
4780                .with_dirty(true)
4781                .with_conflict(true)
4782                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4783        });
4784        let item3 = window.add_view(cx, |cx| {
4785            TestItem::new()
4786                .with_dirty(true)
4787                .with_conflict(true)
4788                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4789        });
4790        let item4 = window.add_view(cx, |cx| {
4791            TestItem::new()
4792                .with_dirty(true)
4793                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4794        });
4795        let pane = workspace.update(cx, |workspace, cx| {
4796            workspace.add_item(Box::new(item1.clone()), cx);
4797            workspace.add_item(Box::new(item2.clone()), cx);
4798            workspace.add_item(Box::new(item3.clone()), cx);
4799            workspace.add_item(Box::new(item4.clone()), cx);
4800            workspace.active_pane().clone()
4801        });
4802
4803        let close_items = pane.update(cx, |pane, cx| {
4804            pane.activate_item(1, true, true, cx);
4805            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4806            let item1_id = item1.id();
4807            let item3_id = item3.id();
4808            let item4_id = item4.id();
4809            pane.close_items(cx, SaveIntent::Close, move |id| {
4810                [item1_id, item3_id, item4_id].contains(&id)
4811            })
4812        });
4813        cx.foreground().run_until_parked();
4814
4815        assert!(window.has_pending_prompt(cx));
4816        // Ignore "Save all" prompt
4817        window.simulate_prompt_answer(2, cx);
4818        cx.foreground().run_until_parked();
4819        // There's a prompt to save item 1.
4820        pane.read_with(cx, |pane, _| {
4821            assert_eq!(pane.items_len(), 4);
4822            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4823        });
4824        // Confirm saving item 1.
4825        window.simulate_prompt_answer(0, cx);
4826        cx.foreground().run_until_parked();
4827
4828        // Item 1 is saved. There's a prompt to save item 3.
4829        pane.read_with(cx, |pane, cx| {
4830            assert_eq!(item1.read(cx).save_count, 1);
4831            assert_eq!(item1.read(cx).save_as_count, 0);
4832            assert_eq!(item1.read(cx).reload_count, 0);
4833            assert_eq!(pane.items_len(), 3);
4834            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4835        });
4836        assert!(window.has_pending_prompt(cx));
4837
4838        // Cancel saving item 3.
4839        window.simulate_prompt_answer(1, cx);
4840        cx.foreground().run_until_parked();
4841
4842        // Item 3 is reloaded. There's a prompt to save item 4.
4843        pane.read_with(cx, |pane, cx| {
4844            assert_eq!(item3.read(cx).save_count, 0);
4845            assert_eq!(item3.read(cx).save_as_count, 0);
4846            assert_eq!(item3.read(cx).reload_count, 1);
4847            assert_eq!(pane.items_len(), 2);
4848            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4849        });
4850        assert!(window.has_pending_prompt(cx));
4851
4852        // Confirm saving item 4.
4853        window.simulate_prompt_answer(0, cx);
4854        cx.foreground().run_until_parked();
4855
4856        // There's a prompt for a path for item 4.
4857        cx.simulate_new_path_selection(|_| Some(Default::default()));
4858        close_items.await.unwrap();
4859
4860        // The requested items are closed.
4861        pane.read_with(cx, |pane, cx| {
4862            assert_eq!(item4.read(cx).save_count, 0);
4863            assert_eq!(item4.read(cx).save_as_count, 1);
4864            assert_eq!(item4.read(cx).reload_count, 0);
4865            assert_eq!(pane.items_len(), 1);
4866            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4867        });
4868    }
4869
4870    #[gpui::test]
4871    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4872        init_test(cx);
4873
4874        let fs = FakeFs::new(cx.background());
4875
4876        let project = Project::test(fs, [], cx).await;
4877        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4878        let workspace = window.root(cx);
4879
4880        // Create several workspace items with single project entries, and two
4881        // workspace items with multiple project entries.
4882        let single_entry_items = (0..=4)
4883            .map(|project_entry_id| {
4884                window.add_view(cx, |cx| {
4885                    TestItem::new()
4886                        .with_dirty(true)
4887                        .with_project_items(&[TestProjectItem::new(
4888                            project_entry_id,
4889                            &format!("{project_entry_id}.txt"),
4890                            cx,
4891                        )])
4892                })
4893            })
4894            .collect::<Vec<_>>();
4895        let item_2_3 = window.add_view(cx, |cx| {
4896            TestItem::new()
4897                .with_dirty(true)
4898                .with_singleton(false)
4899                .with_project_items(&[
4900                    single_entry_items[2].read(cx).project_items[0].clone(),
4901                    single_entry_items[3].read(cx).project_items[0].clone(),
4902                ])
4903        });
4904        let item_3_4 = window.add_view(cx, |cx| {
4905            TestItem::new()
4906                .with_dirty(true)
4907                .with_singleton(false)
4908                .with_project_items(&[
4909                    single_entry_items[3].read(cx).project_items[0].clone(),
4910                    single_entry_items[4].read(cx).project_items[0].clone(),
4911                ])
4912        });
4913
4914        // Create two panes that contain the following project entries:
4915        //   left pane:
4916        //     multi-entry items:   (2, 3)
4917        //     single-entry items:  0, 1, 2, 3, 4
4918        //   right pane:
4919        //     single-entry items:  1
4920        //     multi-entry items:   (3, 4)
4921        let left_pane = workspace.update(cx, |workspace, cx| {
4922            let left_pane = workspace.active_pane().clone();
4923            workspace.add_item(Box::new(item_2_3.clone()), cx);
4924            for item in single_entry_items {
4925                workspace.add_item(Box::new(item), cx);
4926            }
4927            left_pane.update(cx, |pane, cx| {
4928                pane.activate_item(2, true, true, cx);
4929            });
4930
4931            workspace
4932                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4933                .unwrap();
4934
4935            left_pane
4936        });
4937
4938        //Need to cause an effect flush in order to respect new focus
4939        workspace.update(cx, |workspace, cx| {
4940            workspace.add_item(Box::new(item_3_4.clone()), cx);
4941            cx.focus(&left_pane);
4942        });
4943
4944        // When closing all of the items in the left pane, we should be prompted twice:
4945        // once for project entry 0, and once for project entry 2. After those two
4946        // prompts, the task should complete.
4947
4948        let close = left_pane.update(cx, |pane, cx| {
4949            pane.close_items(cx, SaveIntent::Close, move |_| true)
4950        });
4951        cx.foreground().run_until_parked();
4952        // Discard "Save all" prompt
4953        window.simulate_prompt_answer(2, cx);
4954
4955        cx.foreground().run_until_parked();
4956        left_pane.read_with(cx, |pane, cx| {
4957            assert_eq!(
4958                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4959                &[ProjectEntryId::from_proto(0)]
4960            );
4961        });
4962        window.simulate_prompt_answer(0, cx);
4963
4964        cx.foreground().run_until_parked();
4965        left_pane.read_with(cx, |pane, cx| {
4966            assert_eq!(
4967                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4968                &[ProjectEntryId::from_proto(2)]
4969            );
4970        });
4971        window.simulate_prompt_answer(0, cx);
4972
4973        cx.foreground().run_until_parked();
4974        close.await.unwrap();
4975        left_pane.read_with(cx, |pane, _| {
4976            assert_eq!(pane.items_len(), 0);
4977        });
4978    }
4979
4980    #[gpui::test]
4981    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4982        init_test(cx);
4983
4984        let fs = FakeFs::new(cx.background());
4985
4986        let project = Project::test(fs, [], cx).await;
4987        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4988        let workspace = window.root(cx);
4989        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4990
4991        let item = window.add_view(cx, |cx| {
4992            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4993        });
4994        let item_id = item.id();
4995        workspace.update(cx, |workspace, cx| {
4996            workspace.add_item(Box::new(item.clone()), cx);
4997        });
4998
4999        // Autosave on window change.
5000        item.update(cx, |item, cx| {
5001            cx.update_global(|settings: &mut SettingsStore, cx| {
5002                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5003                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
5004                })
5005            });
5006            item.is_dirty = true;
5007        });
5008
5009        // Deactivating the window saves the file.
5010        window.simulate_deactivation(cx);
5011        deterministic.run_until_parked();
5012        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
5013
5014        // Autosave on focus change.
5015        item.update(cx, |item, cx| {
5016            cx.focus_self();
5017            cx.update_global(|settings: &mut SettingsStore, cx| {
5018                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5019                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5020                })
5021            });
5022            item.is_dirty = true;
5023        });
5024
5025        // Blurring the item saves the file.
5026        item.update(cx, |_, cx| cx.blur());
5027        deterministic.run_until_parked();
5028        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
5029
5030        // Deactivating the window still saves the file.
5031        window.simulate_activation(cx);
5032        item.update(cx, |item, cx| {
5033            cx.focus_self();
5034            item.is_dirty = true;
5035        });
5036        window.simulate_deactivation(cx);
5037
5038        deterministic.run_until_parked();
5039        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5040
5041        // Autosave after delay.
5042        item.update(cx, |item, cx| {
5043            cx.update_global(|settings: &mut SettingsStore, cx| {
5044                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5045                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5046                })
5047            });
5048            item.is_dirty = true;
5049            cx.emit(TestItemEvent::Edit);
5050        });
5051
5052        // Delay hasn't fully expired, so the file is still dirty and unsaved.
5053        deterministic.advance_clock(Duration::from_millis(250));
5054        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5055
5056        // After delay expires, the file is saved.
5057        deterministic.advance_clock(Duration::from_millis(250));
5058        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
5059
5060        // Autosave on focus change, ensuring closing the tab counts as such.
5061        item.update(cx, |item, cx| {
5062            cx.update_global(|settings: &mut SettingsStore, cx| {
5063                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5064                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5065                })
5066            });
5067            item.is_dirty = true;
5068        });
5069
5070        pane.update(cx, |pane, cx| {
5071            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5072        })
5073        .await
5074        .unwrap();
5075        assert!(!window.has_pending_prompt(cx));
5076        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5077
5078        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5079        workspace.update(cx, |workspace, cx| {
5080            workspace.add_item(Box::new(item.clone()), cx);
5081        });
5082        item.update(cx, |item, cx| {
5083            item.project_items[0].update(cx, |item, _| {
5084                item.entry_id = None;
5085            });
5086            item.is_dirty = true;
5087            cx.blur();
5088        });
5089        deterministic.run_until_parked();
5090        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5091
5092        // Ensure autosave is prevented for deleted files also when closing the buffer.
5093        let _close_items = pane.update(cx, |pane, cx| {
5094            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5095        });
5096        deterministic.run_until_parked();
5097        assert!(window.has_pending_prompt(cx));
5098        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5099    }
5100
5101    #[gpui::test]
5102    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5103        init_test(cx);
5104
5105        let fs = FakeFs::new(cx.background());
5106
5107        let project = Project::test(fs, [], cx).await;
5108        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5109        let workspace = window.root(cx);
5110
5111        let item = window.add_view(cx, |cx| {
5112            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5113        });
5114        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5115        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
5116        let toolbar_notify_count = Rc::new(RefCell::new(0));
5117
5118        workspace.update(cx, |workspace, cx| {
5119            workspace.add_item(Box::new(item.clone()), cx);
5120            let toolbar_notification_count = toolbar_notify_count.clone();
5121            cx.observe(&toolbar, move |_, _, _| {
5122                *toolbar_notification_count.borrow_mut() += 1
5123            })
5124            .detach();
5125        });
5126
5127        pane.read_with(cx, |pane, _| {
5128            assert!(!pane.can_navigate_backward());
5129            assert!(!pane.can_navigate_forward());
5130        });
5131
5132        item.update(cx, |item, cx| {
5133            item.set_state("one".to_string(), cx);
5134        });
5135
5136        // Toolbar must be notified to re-render the navigation buttons
5137        assert_eq!(*toolbar_notify_count.borrow(), 1);
5138
5139        pane.read_with(cx, |pane, _| {
5140            assert!(pane.can_navigate_backward());
5141            assert!(!pane.can_navigate_forward());
5142        });
5143
5144        workspace
5145            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5146            .await
5147            .unwrap();
5148
5149        assert_eq!(*toolbar_notify_count.borrow(), 3);
5150        pane.read_with(cx, |pane, _| {
5151            assert!(!pane.can_navigate_backward());
5152            assert!(pane.can_navigate_forward());
5153        });
5154    }
5155
5156    #[gpui::test]
5157    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5158        init_test(cx);
5159        let fs = FakeFs::new(cx.background());
5160
5161        let project = Project::test(fs, [], cx).await;
5162        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5163        let workspace = window.root(cx);
5164
5165        let panel = workspace.update(cx, |workspace, cx| {
5166            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
5167            workspace.add_panel(panel.clone(), cx);
5168
5169            workspace
5170                .right_dock()
5171                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5172
5173            panel
5174        });
5175
5176        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5177        pane.update(cx, |pane, cx| {
5178            let item = cx.add_view(|_| TestItem::new());
5179            pane.add_item(Box::new(item), true, true, None, cx);
5180        });
5181
5182        // Transfer focus from center to panel
5183        workspace.update(cx, |workspace, cx| {
5184            workspace.toggle_panel_focus::<TestPanel>(cx);
5185        });
5186
5187        workspace.read_with(cx, |workspace, cx| {
5188            assert!(workspace.right_dock().read(cx).is_open());
5189            assert!(!panel.is_zoomed(cx));
5190            assert!(panel.has_focus(cx));
5191        });
5192
5193        // Transfer focus from panel to center
5194        workspace.update(cx, |workspace, cx| {
5195            workspace.toggle_panel_focus::<TestPanel>(cx);
5196        });
5197
5198        workspace.read_with(cx, |workspace, cx| {
5199            assert!(workspace.right_dock().read(cx).is_open());
5200            assert!(!panel.is_zoomed(cx));
5201            assert!(!panel.has_focus(cx));
5202        });
5203
5204        // Close the dock
5205        workspace.update(cx, |workspace, cx| {
5206            workspace.toggle_dock(DockPosition::Right, cx);
5207        });
5208
5209        workspace.read_with(cx, |workspace, cx| {
5210            assert!(!workspace.right_dock().read(cx).is_open());
5211            assert!(!panel.is_zoomed(cx));
5212            assert!(!panel.has_focus(cx));
5213        });
5214
5215        // Open the dock
5216        workspace.update(cx, |workspace, cx| {
5217            workspace.toggle_dock(DockPosition::Right, cx);
5218        });
5219
5220        workspace.read_with(cx, |workspace, cx| {
5221            assert!(workspace.right_dock().read(cx).is_open());
5222            assert!(!panel.is_zoomed(cx));
5223            assert!(panel.has_focus(cx));
5224        });
5225
5226        // Focus and zoom panel
5227        panel.update(cx, |panel, cx| {
5228            cx.focus_self();
5229            panel.set_zoomed(true, cx)
5230        });
5231
5232        workspace.read_with(cx, |workspace, cx| {
5233            assert!(workspace.right_dock().read(cx).is_open());
5234            assert!(panel.is_zoomed(cx));
5235            assert!(panel.has_focus(cx));
5236        });
5237
5238        // Transfer focus to the center closes the dock
5239        workspace.update(cx, |workspace, cx| {
5240            workspace.toggle_panel_focus::<TestPanel>(cx);
5241        });
5242
5243        workspace.read_with(cx, |workspace, cx| {
5244            assert!(!workspace.right_dock().read(cx).is_open());
5245            assert!(panel.is_zoomed(cx));
5246            assert!(!panel.has_focus(cx));
5247        });
5248
5249        // Transferring focus back to the panel keeps it zoomed
5250        workspace.update(cx, |workspace, cx| {
5251            workspace.toggle_panel_focus::<TestPanel>(cx);
5252        });
5253
5254        workspace.read_with(cx, |workspace, cx| {
5255            assert!(workspace.right_dock().read(cx).is_open());
5256            assert!(panel.is_zoomed(cx));
5257            assert!(panel.has_focus(cx));
5258        });
5259
5260        // Close the dock while it is zoomed
5261        workspace.update(cx, |workspace, cx| {
5262            workspace.toggle_dock(DockPosition::Right, cx)
5263        });
5264
5265        workspace.read_with(cx, |workspace, cx| {
5266            assert!(!workspace.right_dock().read(cx).is_open());
5267            assert!(panel.is_zoomed(cx));
5268            assert!(workspace.zoomed.is_none());
5269            assert!(!panel.has_focus(cx));
5270        });
5271
5272        // Opening the dock, when it's zoomed, retains focus
5273        workspace.update(cx, |workspace, cx| {
5274            workspace.toggle_dock(DockPosition::Right, cx)
5275        });
5276
5277        workspace.read_with(cx, |workspace, cx| {
5278            assert!(workspace.right_dock().read(cx).is_open());
5279            assert!(panel.is_zoomed(cx));
5280            assert!(workspace.zoomed.is_some());
5281            assert!(panel.has_focus(cx));
5282        });
5283
5284        // Unzoom and close the panel, zoom the active pane.
5285        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5286        workspace.update(cx, |workspace, cx| {
5287            workspace.toggle_dock(DockPosition::Right, cx)
5288        });
5289        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5290
5291        // Opening a dock unzooms the pane.
5292        workspace.update(cx, |workspace, cx| {
5293            workspace.toggle_dock(DockPosition::Right, cx)
5294        });
5295        workspace.read_with(cx, |workspace, cx| {
5296            let pane = pane.read(cx);
5297            assert!(!pane.is_zoomed());
5298            assert!(!pane.has_focus());
5299            assert!(workspace.right_dock().read(cx).is_open());
5300            assert!(workspace.zoomed.is_none());
5301        });
5302    }
5303
5304    #[gpui::test]
5305    async fn test_panels(cx: &mut gpui::TestAppContext) {
5306        init_test(cx);
5307        let fs = FakeFs::new(cx.background());
5308
5309        let project = Project::test(fs, [], cx).await;
5310        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5311        let workspace = window.root(cx);
5312
5313        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5314            // Add panel_1 on the left, panel_2 on the right.
5315            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
5316            workspace.add_panel(panel_1.clone(), cx);
5317            workspace
5318                .left_dock()
5319                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5320            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
5321            workspace.add_panel(panel_2.clone(), cx);
5322            workspace
5323                .right_dock()
5324                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5325
5326            let left_dock = workspace.left_dock();
5327            assert_eq!(
5328                left_dock.read(cx).visible_panel().unwrap().id(),
5329                panel_1.id()
5330            );
5331            assert_eq!(
5332                left_dock.read(cx).active_panel_size(cx).unwrap(),
5333                panel_1.size(cx)
5334            );
5335
5336            left_dock.update(cx, |left_dock, cx| {
5337                left_dock.resize_active_panel(Some(1337.), cx)
5338            });
5339            assert_eq!(
5340                workspace
5341                    .right_dock()
5342                    .read(cx)
5343                    .visible_panel()
5344                    .unwrap()
5345                    .id(),
5346                panel_2.id()
5347            );
5348
5349            (panel_1, panel_2)
5350        });
5351
5352        // Move panel_1 to the right
5353        panel_1.update(cx, |panel_1, cx| {
5354            panel_1.set_position(DockPosition::Right, cx)
5355        });
5356
5357        workspace.update(cx, |workspace, cx| {
5358            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5359            // Since it was the only panel on the left, the left dock should now be closed.
5360            assert!(!workspace.left_dock().read(cx).is_open());
5361            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5362            let right_dock = workspace.right_dock();
5363            assert_eq!(
5364                right_dock.read(cx).visible_panel().unwrap().id(),
5365                panel_1.id()
5366            );
5367            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5368
5369            // Now we move panel_2Β to the left
5370            panel_2.set_position(DockPosition::Left, cx);
5371        });
5372
5373        workspace.update(cx, |workspace, cx| {
5374            // Since panel_2 was not visible on the right, we don't open the left dock.
5375            assert!(!workspace.left_dock().read(cx).is_open());
5376            // And the right dock is unaffected in it's displaying of panel_1
5377            assert!(workspace.right_dock().read(cx).is_open());
5378            assert_eq!(
5379                workspace
5380                    .right_dock()
5381                    .read(cx)
5382                    .visible_panel()
5383                    .unwrap()
5384                    .id(),
5385                panel_1.id()
5386            );
5387        });
5388
5389        // Move panel_1 back to the left
5390        panel_1.update(cx, |panel_1, cx| {
5391            panel_1.set_position(DockPosition::Left, cx)
5392        });
5393
5394        workspace.update(cx, |workspace, cx| {
5395            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5396            let left_dock = workspace.left_dock();
5397            assert!(left_dock.read(cx).is_open());
5398            assert_eq!(
5399                left_dock.read(cx).visible_panel().unwrap().id(),
5400                panel_1.id()
5401            );
5402            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5403            // And right the dock should be closed as it no longer has any panels.
5404            assert!(!workspace.right_dock().read(cx).is_open());
5405
5406            // Now we move panel_1 to the bottom
5407            panel_1.set_position(DockPosition::Bottom, cx);
5408        });
5409
5410        workspace.update(cx, |workspace, cx| {
5411            // Since panel_1 was visible on the left, we close the left dock.
5412            assert!(!workspace.left_dock().read(cx).is_open());
5413            // The bottom dock is sized based on the panel's default size,
5414            // since the panel orientation changed from vertical to horizontal.
5415            let bottom_dock = workspace.bottom_dock();
5416            assert_eq!(
5417                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5418                panel_1.size(cx),
5419            );
5420            // Close bottom dock and move panel_1 back to the left.
5421            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5422            panel_1.set_position(DockPosition::Left, cx);
5423        });
5424
5425        // Emit activated event on panel 1
5426        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5427
5428        // Now the left dock is open and panel_1 is active and focused.
5429        workspace.read_with(cx, |workspace, cx| {
5430            let left_dock = workspace.left_dock();
5431            assert!(left_dock.read(cx).is_open());
5432            assert_eq!(
5433                left_dock.read(cx).visible_panel().unwrap().id(),
5434                panel_1.id()
5435            );
5436            assert!(panel_1.is_focused(cx));
5437        });
5438
5439        // Emit closed event on panel 2, which is not active
5440        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5441
5442        // Wo don't close the left dock, because panel_2 wasn't the active panel
5443        workspace.read_with(cx, |workspace, cx| {
5444            let left_dock = workspace.left_dock();
5445            assert!(left_dock.read(cx).is_open());
5446            assert_eq!(
5447                left_dock.read(cx).visible_panel().unwrap().id(),
5448                panel_1.id()
5449            );
5450        });
5451
5452        // Emitting a ZoomIn event shows the panel as zoomed.
5453        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
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::Left));
5457        });
5458
5459        // Move panel to another dock while it is zoomed
5460        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5461        workspace.read_with(cx, |workspace, _| {
5462            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5463            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5464        });
5465
5466        // If focus is transferred to another view that's not a panel or another pane, we still show
5467        // the panel as zoomed.
5468        let focus_receiver = window.add_view(cx, |_| EmptyView);
5469        focus_receiver.update(cx, |_, cx| cx.focus_self());
5470        workspace.read_with(cx, |workspace, _| {
5471            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5472            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5473        });
5474
5475        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5476        workspace.update(cx, |_, cx| cx.focus_self());
5477        workspace.read_with(cx, |workspace, _| {
5478            assert_eq!(workspace.zoomed, None);
5479            assert_eq!(workspace.zoomed_position, None);
5480        });
5481
5482        // If focus is transferred again to another view that's not a panel or a pane, we won't
5483        // show the panel as zoomed because it wasn't zoomed before.
5484        focus_receiver.update(cx, |_, cx| cx.focus_self());
5485        workspace.read_with(cx, |workspace, _| {
5486            assert_eq!(workspace.zoomed, None);
5487            assert_eq!(workspace.zoomed_position, None);
5488        });
5489
5490        // When focus is transferred back to the panel, it is zoomed again.
5491        panel_1.update(cx, |_, cx| cx.focus_self());
5492        workspace.read_with(cx, |workspace, _| {
5493            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5494            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5495        });
5496
5497        // Emitting a ZoomOut event unzooms the panel.
5498        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5499        workspace.read_with(cx, |workspace, _| {
5500            assert_eq!(workspace.zoomed, None);
5501            assert_eq!(workspace.zoomed_position, None);
5502        });
5503
5504        // Emit closed event on panel 1, which is active
5505        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5506
5507        // Now the left dock is closed, because panel_1 was the active panel
5508        workspace.read_with(cx, |workspace, cx| {
5509            let right_dock = workspace.right_dock();
5510            assert!(!right_dock.read(cx).is_open());
5511        });
5512    }
5513
5514    pub fn init_test(cx: &mut TestAppContext) {
5515        cx.foreground().forbid_parking();
5516        cx.update(|cx| {
5517            cx.set_global(SettingsStore::test(cx));
5518            theme::init((), cx);
5519            language::init(cx);
5520            crate::init_settings(cx);
5521            Project::init_settings(cx);
5522        });
5523    }
5524}