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