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