workspace.rs

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