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