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_some()
2882                                && project_id != follower_project_id
2883                                && item.is_project_item(cx)
2884                            {
2885                                return None;
2886                            }
2887                            let id = item.remote_id(client, cx)?.to_proto();
2888                            let variant = item.to_state_proto(cx)?;
2889                            Some(proto::View {
2890                                id: Some(id),
2891                                leader_id,
2892                                variant: Some(variant),
2893                            })
2894                        }
2895                    })
2896                })
2897                .collect(),
2898        }
2899    }
2900
2901    fn handle_update_followers(
2902        &mut self,
2903        leader_id: PeerId,
2904        message: proto::UpdateFollowers,
2905        _cx: &mut ViewContext<Self>,
2906    ) {
2907        self.leader_updates_tx
2908            .unbounded_send((leader_id, message))
2909            .ok();
2910    }
2911
2912    async fn process_leader_update(
2913        this: &WeakViewHandle<Self>,
2914        leader_id: PeerId,
2915        update: proto::UpdateFollowers,
2916        cx: &mut AsyncAppContext,
2917    ) -> Result<()> {
2918        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2919            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2920                this.update(cx, |this, _| {
2921                    for (_, state) in &mut this.follower_states {
2922                        if state.leader_id == leader_id {
2923                            state.active_view_id =
2924                                if let Some(active_view_id) = update_active_view.id.clone() {
2925                                    Some(ViewId::from_proto(active_view_id)?)
2926                                } else {
2927                                    None
2928                                };
2929                        }
2930                    }
2931                    anyhow::Ok(())
2932                })??;
2933            }
2934            proto::update_followers::Variant::UpdateView(update_view) => {
2935                let variant = update_view
2936                    .variant
2937                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2938                let id = update_view
2939                    .id
2940                    .ok_or_else(|| anyhow!("missing update view id"))?;
2941                let mut tasks = Vec::new();
2942                this.update(cx, |this, cx| {
2943                    let project = this.project.clone();
2944                    for (_, state) in &mut this.follower_states {
2945                        if state.leader_id == leader_id {
2946                            let view_id = ViewId::from_proto(id.clone())?;
2947                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2948                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2949                            }
2950                        }
2951                    }
2952                    anyhow::Ok(())
2953                })??;
2954                try_join_all(tasks).await.log_err();
2955            }
2956            proto::update_followers::Variant::CreateView(view) => {
2957                let panes = this.read_with(cx, |this, _| {
2958                    this.follower_states
2959                        .iter()
2960                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2961                        .cloned()
2962                        .collect()
2963                })?;
2964                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2965            }
2966        }
2967        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2968        Ok(())
2969    }
2970
2971    async fn add_views_from_leader(
2972        this: WeakViewHandle<Self>,
2973        leader_id: PeerId,
2974        panes: Vec<ViewHandle<Pane>>,
2975        views: Vec<proto::View>,
2976        cx: &mut AsyncAppContext,
2977    ) -> Result<()> {
2978        let this = this
2979            .upgrade(cx)
2980            .ok_or_else(|| anyhow!("workspace dropped"))?;
2981
2982        let item_builders = cx.update(|cx| {
2983            cx.default_global::<FollowableItemBuilders>()
2984                .values()
2985                .map(|b| b.0)
2986                .collect::<Vec<_>>()
2987        });
2988
2989        let mut item_tasks_by_pane = HashMap::default();
2990        for pane in panes {
2991            let mut item_tasks = Vec::new();
2992            let mut leader_view_ids = Vec::new();
2993            for view in &views {
2994                let Some(id) = &view.id else { continue };
2995                let id = ViewId::from_proto(id.clone())?;
2996                let mut variant = view.variant.clone();
2997                if variant.is_none() {
2998                    Err(anyhow!("missing view variant"))?;
2999                }
3000                for build_item in &item_builders {
3001                    let task = cx
3002                        .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx));
3003                    if let Some(task) = task {
3004                        item_tasks.push(task);
3005                        leader_view_ids.push(id);
3006                        break;
3007                    } else {
3008                        assert!(variant.is_some());
3009                    }
3010                }
3011            }
3012
3013            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
3014        }
3015
3016        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
3017            let items = futures::future::try_join_all(item_tasks).await?;
3018            this.update(cx, |this, cx| {
3019                let state = this.follower_states.get_mut(&pane)?;
3020                for (id, item) in leader_view_ids.into_iter().zip(items) {
3021                    item.set_leader_peer_id(Some(leader_id), cx);
3022                    state.items_by_leader_view_id.insert(id, item);
3023                }
3024
3025                Some(())
3026            });
3027        }
3028        Ok(())
3029    }
3030
3031    fn update_active_view_for_followers(&mut self, cx: &AppContext) {
3032        let mut is_project_item = true;
3033        let mut update = proto::UpdateActiveView::default();
3034        if self.active_pane.read(cx).has_focus() {
3035            let item = self
3036                .active_item(cx)
3037                .and_then(|item| item.to_followable_item_handle(cx));
3038            if let Some(item) = item {
3039                is_project_item = item.is_project_item(cx);
3040                update = proto::UpdateActiveView {
3041                    id: item
3042                        .remote_id(&self.app_state.client, cx)
3043                        .map(|id| id.to_proto()),
3044                    leader_id: self.leader_for_pane(&self.active_pane),
3045                };
3046            }
3047        }
3048
3049        if update.id != self.last_active_view_id {
3050            self.last_active_view_id = update.id.clone();
3051            self.update_followers(
3052                is_project_item,
3053                proto::update_followers::Variant::UpdateActiveView(update),
3054                cx,
3055            );
3056        }
3057    }
3058
3059    fn update_followers(
3060        &self,
3061        project_only: bool,
3062        update: proto::update_followers::Variant,
3063        cx: &AppContext,
3064    ) -> Option<()> {
3065        let project_id = if project_only {
3066            self.project.read(cx).remote_id()
3067        } else {
3068            None
3069        };
3070        self.app_state().workspace_store.read_with(cx, |store, cx| {
3071            store.update_followers(project_id, update, cx)
3072        })
3073    }
3074
3075    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
3076        self.follower_states.get(pane).map(|state| state.leader_id)
3077    }
3078
3079    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3080        cx.notify();
3081
3082        let call = self.active_call()?;
3083        let room = call.read(cx).room()?.read(cx);
3084        let participant = room.remote_participant_for_peer_id(leader_id)?;
3085        let mut items_to_activate = Vec::new();
3086
3087        let leader_in_this_app;
3088        let leader_in_this_project;
3089        match participant.location {
3090            call::ParticipantLocation::SharedProject { project_id } => {
3091                leader_in_this_app = true;
3092                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3093            }
3094            call::ParticipantLocation::UnsharedProject => {
3095                leader_in_this_app = true;
3096                leader_in_this_project = false;
3097            }
3098            call::ParticipantLocation::External => {
3099                leader_in_this_app = false;
3100                leader_in_this_project = false;
3101            }
3102        };
3103
3104        for (pane, state) in &self.follower_states {
3105            if state.leader_id != leader_id {
3106                continue;
3107            }
3108            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
3109                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
3110                    if leader_in_this_project || !item.is_project_item(cx) {
3111                        items_to_activate.push((pane.clone(), item.boxed_clone()));
3112                    }
3113                } else {
3114                    log::warn!(
3115                        "unknown view id {:?} for leader {:?}",
3116                        active_view_id,
3117                        leader_id
3118                    );
3119                }
3120                continue;
3121            }
3122            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3123                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3124            }
3125        }
3126
3127        for (pane, item) in items_to_activate {
3128            let pane_was_focused = pane.read(cx).has_focus();
3129            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3130                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3131            } else {
3132                pane.update(cx, |pane, cx| {
3133                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3134                });
3135            }
3136
3137            if pane_was_focused {
3138                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3139            }
3140        }
3141
3142        None
3143    }
3144
3145    fn shared_screen_for_peer(
3146        &self,
3147        peer_id: PeerId,
3148        pane: &ViewHandle<Pane>,
3149        cx: &mut ViewContext<Self>,
3150    ) -> Option<ViewHandle<SharedScreen>> {
3151        let call = self.active_call()?;
3152        let room = call.read(cx).room()?.read(cx);
3153        let participant = room.remote_participant_for_peer_id(peer_id)?;
3154        let track = participant.video_tracks.values().next()?.clone();
3155        let user = participant.user.clone();
3156
3157        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3158            if item.read(cx).peer_id == peer_id {
3159                return Some(item);
3160            }
3161        }
3162
3163        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3164    }
3165
3166    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
3167        if active {
3168            self.update_active_view_for_followers(cx);
3169            cx.background()
3170                .spawn(persistence::DB.update_timestamp(self.database_id()))
3171                .detach();
3172        } else {
3173            for pane in &self.panes {
3174                pane.update(cx, |pane, cx| {
3175                    if let Some(item) = pane.active_item() {
3176                        item.workspace_deactivated(cx);
3177                    }
3178                    if matches!(
3179                        settings::get::<WorkspaceSettings>(cx).autosave,
3180                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3181                    ) {
3182                        for item in pane.items() {
3183                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3184                                .detach_and_log_err(cx);
3185                        }
3186                    }
3187                });
3188            }
3189        }
3190    }
3191
3192    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
3193        self.active_call.as_ref().map(|(call, _)| call)
3194    }
3195
3196    fn on_active_call_event(
3197        &mut self,
3198        _: ModelHandle<ActiveCall>,
3199        event: &call::room::Event,
3200        cx: &mut ViewContext<Self>,
3201    ) {
3202        match event {
3203            call::room::Event::ParticipantLocationChanged { participant_id }
3204            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3205                self.leader_updated(*participant_id, cx);
3206            }
3207            _ => {}
3208        }
3209    }
3210
3211    pub fn database_id(&self) -> WorkspaceId {
3212        self.database_id
3213    }
3214
3215    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3216        let project = self.project().read(cx);
3217
3218        if project.is_local() {
3219            Some(
3220                project
3221                    .visible_worktrees(cx)
3222                    .map(|worktree| worktree.read(cx).abs_path())
3223                    .collect::<Vec<_>>()
3224                    .into(),
3225            )
3226        } else {
3227            None
3228        }
3229    }
3230
3231    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3232        match member {
3233            Member::Axis(PaneAxis { members, .. }) => {
3234                for child in members.iter() {
3235                    self.remove_panes(child.clone(), cx)
3236                }
3237            }
3238            Member::Pane(pane) => {
3239                self.force_remove_pane(&pane, cx);
3240            }
3241        }
3242    }
3243
3244    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
3245        self.panes.retain(|p| p != pane);
3246        cx.focus(self.panes.last().unwrap());
3247        if self.last_active_center_pane == Some(pane.downgrade()) {
3248            self.last_active_center_pane = None;
3249        }
3250        cx.notify();
3251    }
3252
3253    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3254        self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
3255            cx.background().timer(Duration::from_millis(100)).await;
3256            this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
3257                .ok();
3258        }));
3259    }
3260
3261    fn serialize_workspace(&self, cx: &ViewContext<Self>) {
3262        fn serialize_pane_handle(
3263            pane_handle: &ViewHandle<Pane>,
3264            cx: &AppContext,
3265        ) -> SerializedPane {
3266            let (items, active) = {
3267                let pane = pane_handle.read(cx);
3268                let active_item_id = pane.active_item().map(|item| item.id());
3269                (
3270                    pane.items()
3271                        .filter_map(|item_handle| {
3272                            Some(SerializedItem {
3273                                kind: Arc::from(item_handle.serialized_item_kind()?),
3274                                item_id: item_handle.id(),
3275                                active: Some(item_handle.id()) == active_item_id,
3276                            })
3277                        })
3278                        .collect::<Vec<_>>(),
3279                    pane.has_focus(),
3280                )
3281            };
3282
3283            SerializedPane::new(items, active)
3284        }
3285
3286        fn build_serialized_pane_group(
3287            pane_group: &Member,
3288            cx: &AppContext,
3289        ) -> SerializedPaneGroup {
3290            match pane_group {
3291                Member::Axis(PaneAxis {
3292                    axis,
3293                    members,
3294                    flexes,
3295                    bounding_boxes: _,
3296                }) => SerializedPaneGroup::Group {
3297                    axis: *axis,
3298                    children: members
3299                        .iter()
3300                        .map(|member| build_serialized_pane_group(member, cx))
3301                        .collect::<Vec<_>>(),
3302                    flexes: Some(flexes.borrow().clone()),
3303                },
3304                Member::Pane(pane_handle) => {
3305                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3306                }
3307            }
3308        }
3309
3310        fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
3311            let left_dock = this.left_dock.read(cx);
3312            let left_visible = left_dock.is_open();
3313            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
3314                Some(
3315                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3316                        .to_string(),
3317                )
3318            });
3319            let left_dock_zoom = left_dock
3320                .visible_panel()
3321                .map(|panel| panel.is_zoomed(cx))
3322                .unwrap_or(false);
3323
3324            let right_dock = this.right_dock.read(cx);
3325            let right_visible = right_dock.is_open();
3326            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
3327                Some(
3328                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3329                        .to_string(),
3330                )
3331            });
3332            let right_dock_zoom = right_dock
3333                .visible_panel()
3334                .map(|panel| panel.is_zoomed(cx))
3335                .unwrap_or(false);
3336
3337            let bottom_dock = this.bottom_dock.read(cx);
3338            let bottom_visible = bottom_dock.is_open();
3339            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
3340                Some(
3341                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3342                        .to_string(),
3343                )
3344            });
3345            let bottom_dock_zoom = bottom_dock
3346                .visible_panel()
3347                .map(|panel| panel.is_zoomed(cx))
3348                .unwrap_or(false);
3349
3350            DockStructure {
3351                left: DockData {
3352                    visible: left_visible,
3353                    active_panel: left_active_panel,
3354                    zoom: left_dock_zoom,
3355                },
3356                right: DockData {
3357                    visible: right_visible,
3358                    active_panel: right_active_panel,
3359                    zoom: right_dock_zoom,
3360                },
3361                bottom: DockData {
3362                    visible: bottom_visible,
3363                    active_panel: bottom_active_panel,
3364                    zoom: bottom_dock_zoom,
3365                },
3366            }
3367        }
3368
3369        if let Some(location) = self.location(cx) {
3370            // Load bearing special case:
3371            //  - with_local_workspace() relies on this to not have other stuff open
3372            //    when you open your log
3373            if !location.paths().is_empty() {
3374                let center_group = build_serialized_pane_group(&self.center.root, cx);
3375                let docks = build_serialized_docks(self, cx);
3376
3377                let serialized_workspace = SerializedWorkspace {
3378                    id: self.database_id,
3379                    location,
3380                    center_group,
3381                    bounds: Default::default(),
3382                    display: Default::default(),
3383                    docks,
3384                };
3385
3386                cx.background()
3387                    .spawn(persistence::DB.save_workspace(serialized_workspace))
3388                    .detach();
3389            }
3390        }
3391    }
3392
3393    pub(crate) fn load_workspace(
3394        workspace: WeakViewHandle<Workspace>,
3395        serialized_workspace: SerializedWorkspace,
3396        paths_to_open: Vec<Option<ProjectPath>>,
3397        cx: &mut AppContext,
3398    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3399        cx.spawn(|mut cx| async move {
3400            let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| {
3401                (
3402                    workspace.project().clone(),
3403                    workspace.last_active_center_pane.clone(),
3404                )
3405            })?;
3406
3407            let mut center_group = None;
3408            let mut center_items = None;
3409            // Traverse the splits tree and add to things
3410            if let Some((group, active_pane, items)) = serialized_workspace
3411                .center_group
3412                .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
3413                .await
3414            {
3415                center_items = Some(items);
3416                center_group = Some((group, active_pane))
3417            }
3418
3419            let mut items_by_project_path = cx.read(|cx| {
3420                center_items
3421                    .unwrap_or_default()
3422                    .into_iter()
3423                    .filter_map(|item| {
3424                        let item = item?;
3425                        let project_path = item.project_path(cx)?;
3426                        Some((project_path, item))
3427                    })
3428                    .collect::<HashMap<_, _>>()
3429            });
3430
3431            let opened_items = paths_to_open
3432                .into_iter()
3433                .map(|path_to_open| {
3434                    path_to_open
3435                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3436                })
3437                .collect::<Vec<_>>();
3438
3439            // Remove old panes from workspace panes list
3440            workspace.update(&mut cx, |workspace, cx| {
3441                if let Some((center_group, active_pane)) = center_group {
3442                    workspace.remove_panes(workspace.center.root.clone(), cx);
3443
3444                    // Swap workspace center group
3445                    workspace.center = PaneGroup::with_root(center_group);
3446
3447                    // Change the focus to the workspace first so that we retrigger focus in on the pane.
3448                    cx.focus_self();
3449
3450                    if let Some(active_pane) = active_pane {
3451                        cx.focus(&active_pane);
3452                    } else {
3453                        cx.focus(workspace.panes.last().unwrap());
3454                    }
3455                } else {
3456                    let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3457                    if let Some(old_center_handle) = old_center_handle {
3458                        cx.focus(&old_center_handle)
3459                    } else {
3460                        cx.focus_self()
3461                    }
3462                }
3463
3464                let docks = serialized_workspace.docks;
3465                workspace.left_dock.update(cx, |dock, cx| {
3466                    dock.set_open(docks.left.visible, cx);
3467                    if let Some(active_panel) = docks.left.active_panel {
3468                        if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3469                            dock.activate_panel(ix, cx);
3470                        }
3471                    }
3472                    dock.active_panel()
3473                        .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
3474                    if docks.left.visible && docks.left.zoom {
3475                        cx.focus_self()
3476                    }
3477                });
3478                // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3479                workspace.right_dock.update(cx, |dock, cx| {
3480                    dock.set_open(docks.right.visible, cx);
3481                    if let Some(active_panel) = docks.right.active_panel {
3482                        if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3483                            dock.activate_panel(ix, cx);
3484                        }
3485                    }
3486                    dock.active_panel()
3487                        .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
3488
3489                    if docks.right.visible && docks.right.zoom {
3490                        cx.focus_self()
3491                    }
3492                });
3493                workspace.bottom_dock.update(cx, |dock, cx| {
3494                    dock.set_open(docks.bottom.visible, cx);
3495                    if let Some(active_panel) = docks.bottom.active_panel {
3496                        if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3497                            dock.activate_panel(ix, cx);
3498                        }
3499                    }
3500
3501                    dock.active_panel()
3502                        .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
3503
3504                    if docks.bottom.visible && docks.bottom.zoom {
3505                        cx.focus_self()
3506                    }
3507                });
3508
3509                cx.notify();
3510            })?;
3511
3512            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3513            workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3514
3515            Ok(opened_items)
3516        })
3517    }
3518
3519    #[cfg(any(test, feature = "test-support"))]
3520    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3521        let client = project.read(cx).client();
3522        let user_store = project.read(cx).user_store();
3523
3524        let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
3525        let app_state = Arc::new(AppState {
3526            languages: project.read(cx).languages().clone(),
3527            workspace_store,
3528            client,
3529            user_store,
3530            fs: project.read(cx).fs().clone(),
3531            build_window_options: |_, _, _| Default::default(),
3532            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3533            background_actions: || &[],
3534        });
3535        Self::new(0, project, app_state, cx)
3536    }
3537
3538    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3539        let dock = match position {
3540            DockPosition::Left => &self.left_dock,
3541            DockPosition::Right => &self.right_dock,
3542            DockPosition::Bottom => &self.bottom_dock,
3543        };
3544        let active_panel = dock.read(cx).visible_panel()?;
3545        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3546            dock.read(cx).render_placeholder(cx)
3547        } else {
3548            ChildView::new(dock, cx).into_any()
3549        };
3550
3551        Some(
3552            element
3553                .constrained()
3554                .dynamically(move |constraint, _, cx| match position {
3555                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3556                        Vector2F::new(20., constraint.min.y()),
3557                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3558                    ),
3559                    DockPosition::Bottom => SizeConstraint::new(
3560                        Vector2F::new(constraint.min.x(), 20.),
3561                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3562                    ),
3563                })
3564                .into_any(),
3565        )
3566    }
3567}
3568
3569fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3570    ZED_WINDOW_POSITION
3571        .zip(*ZED_WINDOW_SIZE)
3572        .map(|(position, size)| {
3573            WindowBounds::Fixed(RectF::new(
3574                cx.platform().screens()[0].bounds().origin() + position,
3575                size,
3576            ))
3577        })
3578}
3579
3580async fn open_items(
3581    serialized_workspace: Option<SerializedWorkspace>,
3582    workspace: &WeakViewHandle<Workspace>,
3583    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3584    app_state: Arc<AppState>,
3585    mut cx: AsyncAppContext,
3586) -> Result<Vec<Option<Result<Box<dyn ItemHandle>>>>> {
3587    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3588
3589    if let Some(serialized_workspace) = serialized_workspace {
3590        let workspace = workspace.clone();
3591        let restored_items = cx
3592            .update(|cx| {
3593                Workspace::load_workspace(
3594                    workspace,
3595                    serialized_workspace,
3596                    project_paths_to_open
3597                        .iter()
3598                        .map(|(_, project_path)| project_path)
3599                        .cloned()
3600                        .collect(),
3601                    cx,
3602                )
3603            })
3604            .await?;
3605
3606        let restored_project_paths = cx.read(|cx| {
3607            restored_items
3608                .iter()
3609                .filter_map(|item| item.as_ref()?.project_path(cx))
3610                .collect::<HashSet<_>>()
3611        });
3612
3613        for restored_item in restored_items {
3614            opened_items.push(restored_item.map(Ok));
3615        }
3616
3617        project_paths_to_open
3618            .iter_mut()
3619            .for_each(|(_, project_path)| {
3620                if let Some(project_path_to_open) = project_path {
3621                    if restored_project_paths.contains(project_path_to_open) {
3622                        *project_path = None;
3623                    }
3624                }
3625            });
3626    } else {
3627        for _ in 0..project_paths_to_open.len() {
3628            opened_items.push(None);
3629        }
3630    }
3631    assert!(opened_items.len() == project_paths_to_open.len());
3632
3633    let tasks =
3634        project_paths_to_open
3635            .into_iter()
3636            .enumerate()
3637            .map(|(i, (abs_path, project_path))| {
3638                let workspace = workspace.clone();
3639                cx.spawn(|mut cx| {
3640                    let fs = app_state.fs.clone();
3641                    async move {
3642                        let file_project_path = project_path?;
3643                        if fs.is_file(&abs_path).await {
3644                            Some((
3645                                i,
3646                                workspace
3647                                    .update(&mut cx, |workspace, cx| {
3648                                        workspace.open_path(file_project_path, None, true, cx)
3649                                    })
3650                                    .log_err()?
3651                                    .await,
3652                            ))
3653                        } else {
3654                            None
3655                        }
3656                    }
3657                })
3658            });
3659
3660    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3661        .await
3662        .into_iter()
3663    {
3664        if let Some((i, path_open_result)) = maybe_opened_path {
3665            opened_items[i] = Some(path_open_result);
3666        }
3667    }
3668
3669    Ok(opened_items)
3670}
3671
3672fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3673    const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3674    const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3675    const MESSAGE_ID: usize = 2;
3676
3677    if workspace
3678        .read_with(cx, |workspace, cx| {
3679            workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3680        })
3681        .unwrap_or(false)
3682    {
3683        return;
3684    }
3685
3686    if db::kvp::KEY_VALUE_STORE
3687        .read_kvp(NEW_DOCK_HINT_KEY)
3688        .ok()
3689        .flatten()
3690        .is_some()
3691    {
3692        if !workspace
3693            .read_with(cx, |workspace, cx| {
3694                workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3695            })
3696            .unwrap_or(false)
3697        {
3698            cx.update(|cx| {
3699                cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3700                    let entry = tracker
3701                        .entry(TypeId::of::<MessageNotification>())
3702                        .or_default();
3703                    if !entry.contains(&MESSAGE_ID) {
3704                        entry.push(MESSAGE_ID);
3705                    }
3706                });
3707            });
3708        }
3709
3710        return;
3711    }
3712
3713    cx.spawn(|_| async move {
3714        db::kvp::KEY_VALUE_STORE
3715            .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3716            .await
3717            .ok();
3718    })
3719    .detach();
3720
3721    workspace
3722        .update(cx, |workspace, cx| {
3723            workspace.show_notification_once(2, cx, |cx| {
3724                cx.add_view(|_| {
3725                    MessageNotification::new_element(|text, _| {
3726                        Text::new(
3727                            "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3728                            text,
3729                        )
3730                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3731                            let code_span_background_color = settings::get::<ThemeSettings>(cx)
3732                                .theme
3733                                .editor
3734                                .document_highlight_read_background;
3735
3736                            cx.scene().push_quad(gpui::Quad {
3737                                bounds,
3738                                background: Some(code_span_background_color),
3739                                border: Default::default(),
3740                                corner_radii: (2.0).into(),
3741                            })
3742                        })
3743                        .into_any()
3744                    })
3745                    .with_click_message("Read more about the new panel system")
3746                    .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3747                })
3748            })
3749        })
3750        .ok();
3751}
3752
3753fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3754    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3755
3756    workspace
3757        .update(cx, |workspace, cx| {
3758            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3759                workspace.show_notification_once(0, cx, |cx| {
3760                    cx.add_view(|_| {
3761                        MessageNotification::new("Failed to load the database file.")
3762                            .with_click_message("Click to let us know about this error")
3763                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3764                    })
3765                });
3766            }
3767        })
3768        .log_err();
3769}
3770
3771impl Entity for Workspace {
3772    type Event = Event;
3773
3774    fn release(&mut self, cx: &mut AppContext) {
3775        self.app_state.workspace_store.update(cx, |store, _| {
3776            store.workspaces.remove(&self.weak_self);
3777        })
3778    }
3779}
3780
3781impl View for Workspace {
3782    fn ui_name() -> &'static str {
3783        "Workspace"
3784    }
3785
3786    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3787        let theme = theme::current(cx).clone();
3788        Stack::new()
3789            .with_child(
3790                Flex::column()
3791                    .with_child(self.render_titlebar(&theme, cx))
3792                    .with_child(
3793                        Stack::new()
3794                            .with_child({
3795                                let project = self.project.clone();
3796                                Flex::row()
3797                                    .with_children(self.render_dock(DockPosition::Left, cx))
3798                                    .with_child(
3799                                        Flex::column()
3800                                            .with_child(
3801                                                FlexItem::new(
3802                                                    self.center.render(
3803                                                        &project,
3804                                                        &theme,
3805                                                        &self.follower_states,
3806                                                        self.active_call(),
3807                                                        self.active_pane(),
3808                                                        self.zoomed
3809                                                            .as_ref()
3810                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3811                                                            .as_ref(),
3812                                                        &self.app_state,
3813                                                        cx,
3814                                                    ),
3815                                                )
3816                                                .flex(1., true),
3817                                            )
3818                                            .with_children(
3819                                                self.render_dock(DockPosition::Bottom, cx),
3820                                            )
3821                                            .flex(1., true),
3822                                    )
3823                                    .with_children(self.render_dock(DockPosition::Right, cx))
3824                            })
3825                            .with_child(Overlay::new(
3826                                Stack::new()
3827                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3828                                        enum ZoomBackground {}
3829                                        let zoomed = zoomed.upgrade(cx)?;
3830
3831                                        let mut foreground_style =
3832                                            theme.workspace.zoomed_pane_foreground;
3833                                        if let Some(zoomed_dock_position) = self.zoomed_position {
3834                                            foreground_style =
3835                                                theme.workspace.zoomed_panel_foreground;
3836                                            let margin = foreground_style.margin.top;
3837                                            let border = foreground_style.border.top;
3838
3839                                            // Only include a margin and border on the opposite side.
3840                                            foreground_style.margin.top = 0.;
3841                                            foreground_style.margin.left = 0.;
3842                                            foreground_style.margin.bottom = 0.;
3843                                            foreground_style.margin.right = 0.;
3844                                            foreground_style.border.top = false;
3845                                            foreground_style.border.left = false;
3846                                            foreground_style.border.bottom = false;
3847                                            foreground_style.border.right = false;
3848                                            match zoomed_dock_position {
3849                                                DockPosition::Left => {
3850                                                    foreground_style.margin.right = margin;
3851                                                    foreground_style.border.right = border;
3852                                                }
3853                                                DockPosition::Right => {
3854                                                    foreground_style.margin.left = margin;
3855                                                    foreground_style.border.left = border;
3856                                                }
3857                                                DockPosition::Bottom => {
3858                                                    foreground_style.margin.top = margin;
3859                                                    foreground_style.border.top = border;
3860                                                }
3861                                            }
3862                                        }
3863
3864                                        Some(
3865                                            ChildView::new(&zoomed, cx)
3866                                                .contained()
3867                                                .with_style(foreground_style)
3868                                                .aligned()
3869                                                .contained()
3870                                                .with_style(theme.workspace.zoomed_background)
3871                                                .mouse::<ZoomBackground>(0)
3872                                                .capture_all()
3873                                                .on_down(
3874                                                    MouseButton::Left,
3875                                                    |_, this: &mut Self, cx| {
3876                                                        this.zoom_out(cx);
3877                                                    },
3878                                                ),
3879                                        )
3880                                    }))
3881                                    .with_children(self.modal.as_ref().map(|modal| {
3882                                        // Prevent clicks within the modal from falling
3883                                        // through to the rest of the workspace.
3884                                        enum ModalBackground {}
3885                                        MouseEventHandler::new::<ModalBackground, _>(
3886                                            0,
3887                                            cx,
3888                                            |_, cx| ChildView::new(modal.view.as_any(), cx),
3889                                        )
3890                                        .on_click(MouseButton::Left, |_, _, _| {})
3891                                        .contained()
3892                                        .with_style(theme.workspace.modal)
3893                                        .aligned()
3894                                        .top()
3895                                    }))
3896                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3897                            ))
3898                            .provide_resize_bounds::<WorkspaceBounds>()
3899                            .flex(1.0, true),
3900                    )
3901                    .with_child(ChildView::new(&self.status_bar, cx))
3902                    .contained()
3903                    .with_background_color(theme.workspace.background),
3904            )
3905            .with_children(DragAndDrop::render(cx))
3906            .with_children(self.render_disconnected_overlay(cx))
3907            .into_any_named("workspace")
3908    }
3909
3910    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3911        if cx.is_self_focused() {
3912            cx.focus(&self.active_pane);
3913        }
3914    }
3915
3916    fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3917        DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3918    }
3919}
3920
3921impl WorkspaceStore {
3922    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3923        Self {
3924            workspaces: Default::default(),
3925            followers: Default::default(),
3926            _subscriptions: vec![
3927                client.add_request_handler(cx.handle(), Self::handle_follow),
3928                client.add_message_handler(cx.handle(), Self::handle_unfollow),
3929                client.add_message_handler(cx.handle(), Self::handle_update_followers),
3930            ],
3931            client,
3932        }
3933    }
3934
3935    pub fn update_followers(
3936        &self,
3937        project_id: Option<u64>,
3938        update: proto::update_followers::Variant,
3939        cx: &AppContext,
3940    ) -> Option<()> {
3941        if !cx.has_global::<ModelHandle<ActiveCall>>() {
3942            return None;
3943        }
3944
3945        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3946        let follower_ids: Vec<_> = self
3947            .followers
3948            .iter()
3949            .filter_map(|follower| {
3950                if follower.project_id == project_id || project_id.is_none() {
3951                    Some(follower.peer_id.into())
3952                } else {
3953                    None
3954                }
3955            })
3956            .collect();
3957        if follower_ids.is_empty() {
3958            return None;
3959        }
3960        self.client
3961            .send(proto::UpdateFollowers {
3962                room_id,
3963                project_id,
3964                follower_ids,
3965                variant: Some(update),
3966            })
3967            .log_err()
3968    }
3969
3970    async fn handle_follow(
3971        this: ModelHandle<Self>,
3972        envelope: TypedEnvelope<proto::Follow>,
3973        _: Arc<Client>,
3974        mut cx: AsyncAppContext,
3975    ) -> Result<proto::FollowResponse> {
3976        this.update(&mut cx, |this, cx| {
3977            let follower = Follower {
3978                project_id: envelope.payload.project_id,
3979                peer_id: envelope.original_sender_id()?,
3980            };
3981            let active_project = ActiveCall::global(cx)
3982                .read(cx)
3983                .location()
3984                .map(|project| project.id());
3985
3986            let mut response = proto::FollowResponse::default();
3987            for workspace in &this.workspaces {
3988                let Some(workspace) = workspace.upgrade(cx) else {
3989                    continue;
3990                };
3991
3992                workspace.update(cx.as_mut(), |workspace, cx| {
3993                    let handler_response = workspace.handle_follow(follower.project_id, cx);
3994                    if response.views.is_empty() {
3995                        response.views = handler_response.views;
3996                    } else {
3997                        response.views.extend_from_slice(&handler_response.views);
3998                    }
3999
4000                    if let Some(active_view_id) = handler_response.active_view_id.clone() {
4001                        if response.active_view_id.is_none()
4002                            || Some(workspace.project.id()) == active_project
4003                        {
4004                            response.active_view_id = Some(active_view_id);
4005                        }
4006                    }
4007                });
4008            }
4009
4010            if let Err(ix) = this.followers.binary_search(&follower) {
4011                this.followers.insert(ix, follower);
4012            }
4013
4014            Ok(response)
4015        })
4016    }
4017
4018    async fn handle_unfollow(
4019        this: ModelHandle<Self>,
4020        envelope: TypedEnvelope<proto::Unfollow>,
4021        _: Arc<Client>,
4022        mut cx: AsyncAppContext,
4023    ) -> Result<()> {
4024        this.update(&mut cx, |this, _| {
4025            let follower = Follower {
4026                project_id: envelope.payload.project_id,
4027                peer_id: envelope.original_sender_id()?,
4028            };
4029            if let Ok(ix) = this.followers.binary_search(&follower) {
4030                this.followers.remove(ix);
4031            }
4032            Ok(())
4033        })
4034    }
4035
4036    async fn handle_update_followers(
4037        this: ModelHandle<Self>,
4038        envelope: TypedEnvelope<proto::UpdateFollowers>,
4039        _: Arc<Client>,
4040        mut cx: AsyncAppContext,
4041    ) -> Result<()> {
4042        let leader_id = envelope.original_sender_id()?;
4043        let update = envelope.payload;
4044        this.update(&mut cx, |this, cx| {
4045            for workspace in &this.workspaces {
4046                let Some(workspace) = workspace.upgrade(cx) else {
4047                    continue;
4048                };
4049                workspace.update(cx.as_mut(), |workspace, cx| {
4050                    let project_id = workspace.project.read(cx).remote_id();
4051                    if update.project_id != project_id && update.project_id.is_some() {
4052                        return;
4053                    }
4054                    workspace.handle_update_followers(leader_id, update.clone(), cx);
4055                });
4056            }
4057            Ok(())
4058        })
4059    }
4060}
4061
4062impl Entity for WorkspaceStore {
4063    type Event = ();
4064}
4065
4066impl ViewId {
4067    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4068        Ok(Self {
4069            creator: message
4070                .creator
4071                .ok_or_else(|| anyhow!("creator is missing"))?,
4072            id: message.id,
4073        })
4074    }
4075
4076    pub(crate) fn to_proto(&self) -> proto::ViewId {
4077        proto::ViewId {
4078            creator: Some(self.creator),
4079            id: self.id,
4080        }
4081    }
4082}
4083
4084pub trait WorkspaceHandle {
4085    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4086}
4087
4088impl WorkspaceHandle for ViewHandle<Workspace> {
4089    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4090        self.read(cx)
4091            .worktrees(cx)
4092            .flat_map(|worktree| {
4093                let worktree_id = worktree.read(cx).id();
4094                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4095                    worktree_id,
4096                    path: f.path.clone(),
4097                })
4098            })
4099            .collect::<Vec<_>>()
4100    }
4101}
4102
4103impl std::fmt::Debug for OpenPaths {
4104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4105        f.debug_struct("OpenPaths")
4106            .field("paths", &self.paths)
4107            .finish()
4108    }
4109}
4110
4111pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
4112
4113pub fn activate_workspace_for_project(
4114    cx: &mut AsyncAppContext,
4115    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
4116) -> Option<WeakViewHandle<Workspace>> {
4117    for window in cx.windows() {
4118        let handle = window
4119            .update(cx, |cx| {
4120                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
4121                    let project = workspace_handle.read(cx).project.clone();
4122                    if project.update(cx, &predicate) {
4123                        cx.activate_window();
4124                        return Some(workspace_handle.clone());
4125                    }
4126                }
4127                None
4128            })
4129            .flatten();
4130
4131        if let Some(handle) = handle {
4132            return Some(handle.downgrade());
4133        }
4134    }
4135    None
4136}
4137
4138pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4139    DB.last_workspace().await.log_err().flatten()
4140}
4141
4142async fn join_channel_internal(
4143    channel_id: u64,
4144    app_state: &Arc<AppState>,
4145    requesting_window: Option<WindowHandle<Workspace>>,
4146    active_call: &ModelHandle<ActiveCall>,
4147    cx: &mut AsyncAppContext,
4148) -> Result<bool> {
4149    let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
4150        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4151            return (false, None);
4152        };
4153
4154        let already_in_channel = room.channel_id() == Some(channel_id);
4155        let should_prompt = room.is_sharing_project()
4156            && room.remote_participants().len() > 0
4157            && !already_in_channel;
4158        let open_room = if already_in_channel {
4159            active_call.room().cloned()
4160        } else {
4161            None
4162        };
4163        (should_prompt, open_room)
4164    });
4165
4166    if let Some(room) = open_room {
4167        let task = room.update(cx, |room, cx| {
4168            if let Some((project, host)) = room.most_active_project(cx) {
4169                return Some(join_remote_project(project, host, app_state.clone(), cx));
4170            }
4171
4172            None
4173        });
4174        if let Some(task) = task {
4175            task.await?;
4176        }
4177        return anyhow::Ok(true);
4178    }
4179
4180    if should_prompt {
4181        if let Some(workspace) = requesting_window {
4182            if let Some(window) = workspace.update(cx, |cx| cx.window()) {
4183                let answer = window.prompt(
4184                    PromptLevel::Warning,
4185                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4186                    &["Yes, Join Channel", "Cancel"],
4187                    cx,
4188                );
4189
4190                if let Some(mut answer) = answer {
4191                    if answer.next().await == Some(1) {
4192                        return Ok(false);
4193                    }
4194                }
4195            } else {
4196                return Ok(false); // unreachable!() hopefully
4197            }
4198        } else {
4199            return Ok(false); // unreachable!() hopefully
4200        }
4201    }
4202
4203    let client = cx.read(|cx| active_call.read(cx).client());
4204
4205    let mut client_status = client.status();
4206
4207    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4208    'outer: loop {
4209        let Some(status) = client_status.recv().await else {
4210            return Err(anyhow!("error connecting"));
4211        };
4212
4213        match status {
4214            Status::Connecting
4215            | Status::Authenticating
4216            | Status::Reconnecting
4217            | Status::Reauthenticating => continue,
4218            Status::Connected { .. } => break 'outer,
4219            Status::SignedOut => return Err(anyhow!("not signed in")),
4220            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4221            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4222                return Err(anyhow!("zed is offline"))
4223            }
4224        }
4225    }
4226
4227    let room = active_call
4228        .update(cx, |active_call, cx| {
4229            active_call.join_channel(channel_id, cx)
4230        })
4231        .await?;
4232
4233    room.update(cx, |room, _| room.room_update_completed())
4234        .await;
4235
4236    let task = room.update(cx, |room, cx| {
4237        if let Some((project, host)) = room.most_active_project(cx) {
4238            return Some(join_remote_project(project, host, app_state.clone(), cx));
4239        }
4240
4241        None
4242    });
4243    if let Some(task) = task {
4244        task.await?;
4245        return anyhow::Ok(true);
4246    }
4247    anyhow::Ok(false)
4248}
4249
4250pub fn join_channel(
4251    channel_id: u64,
4252    app_state: Arc<AppState>,
4253    requesting_window: Option<WindowHandle<Workspace>>,
4254    cx: &mut AppContext,
4255) -> Task<Result<()>> {
4256    let active_call = ActiveCall::global(cx);
4257    cx.spawn(|mut cx| async move {
4258        let result = join_channel_internal(
4259            channel_id,
4260            &app_state,
4261            requesting_window,
4262            &active_call,
4263            &mut cx,
4264        )
4265        .await;
4266
4267        // join channel succeeded, and opened a window
4268        if matches!(result, Ok(true)) {
4269            return anyhow::Ok(());
4270        }
4271
4272        if requesting_window.is_some() {
4273            return anyhow::Ok(());
4274        }
4275
4276        // find an existing workspace to focus and show call controls
4277        let mut active_window = activate_any_workspace_window(&mut cx);
4278        if active_window.is_none() {
4279            // no open workspaces, make one to show the error in (blergh)
4280            cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))
4281                .await;
4282        }
4283
4284        active_window = activate_any_workspace_window(&mut cx);
4285        if active_window.is_none() {
4286            return result.map(|_| ()); // unreachable!() assuming new_local always opens a window
4287        }
4288
4289        if let Err(err) = result {
4290            let prompt = active_window.unwrap().prompt(
4291                PromptLevel::Critical,
4292                &format!("Failed to join channel: {}", err),
4293                &["Ok"],
4294                &mut cx,
4295            );
4296            if let Some(mut prompt) = prompt {
4297                prompt.next().await;
4298            } else {
4299                return Err(err);
4300            }
4301        }
4302
4303        // return ok, we showed the error to the user.
4304        return anyhow::Ok(());
4305    })
4306}
4307
4308pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4309    for window in cx.windows() {
4310        let found = window.update(cx, |cx| {
4311            let is_workspace = cx.root_view().clone().downcast::<Workspace>().is_some();
4312            if is_workspace {
4313                cx.activate_window();
4314            }
4315            is_workspace
4316        });
4317        if found == Some(true) {
4318            return Some(window);
4319        }
4320    }
4321    None
4322}
4323
4324#[allow(clippy::type_complexity)]
4325pub fn open_paths(
4326    abs_paths: &[PathBuf],
4327    app_state: &Arc<AppState>,
4328    requesting_window: Option<WindowHandle<Workspace>>,
4329    cx: &mut AppContext,
4330) -> Task<
4331    Result<(
4332        WeakViewHandle<Workspace>,
4333        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4334    )>,
4335> {
4336    let app_state = app_state.clone();
4337    let abs_paths = abs_paths.to_vec();
4338    cx.spawn(|mut cx| async move {
4339        // Open paths in existing workspace if possible
4340        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
4341            project.contains_paths(&abs_paths, cx)
4342        });
4343
4344        if let Some(existing) = existing {
4345            Ok((
4346                existing.clone(),
4347                existing
4348                    .update(&mut cx, |workspace, cx| {
4349                        workspace.open_paths(abs_paths, true, cx)
4350                    })?
4351                    .await,
4352            ))
4353        } else {
4354            Ok(cx
4355                .update(|cx| {
4356                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4357                })
4358                .await)
4359        }
4360    })
4361}
4362
4363pub fn open_new(
4364    app_state: &Arc<AppState>,
4365    cx: &mut AppContext,
4366    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
4367) -> Task<()> {
4368    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4369    cx.spawn(|mut cx| async move {
4370        let (workspace, opened_paths) = task.await;
4371
4372        workspace
4373            .update(&mut cx, |workspace, cx| {
4374                if opened_paths.is_empty() {
4375                    init(workspace, cx)
4376                }
4377            })
4378            .log_err();
4379    })
4380}
4381
4382pub fn create_and_open_local_file(
4383    path: &'static Path,
4384    cx: &mut ViewContext<Workspace>,
4385    default_content: impl 'static + Send + FnOnce() -> Rope,
4386) -> Task<Result<Box<dyn ItemHandle>>> {
4387    cx.spawn(|workspace, mut cx| async move {
4388        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
4389        if !fs.is_file(path).await {
4390            fs.create_file(path, Default::default()).await?;
4391            fs.save(path, &default_content(), Default::default())
4392                .await?;
4393        }
4394
4395        let mut items = workspace
4396            .update(&mut cx, |workspace, cx| {
4397                workspace.with_local_workspace(cx, |workspace, cx| {
4398                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4399                })
4400            })?
4401            .await?
4402            .await;
4403
4404        let item = items.pop().flatten();
4405        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4406    })
4407}
4408
4409pub fn join_remote_project(
4410    project_id: u64,
4411    follow_user_id: u64,
4412    app_state: Arc<AppState>,
4413    cx: &mut AppContext,
4414) -> Task<Result<()>> {
4415    cx.spawn(|mut cx| async move {
4416        let windows = cx.windows();
4417        let existing_workspace = windows.into_iter().find_map(|window| {
4418            window.downcast::<Workspace>().and_then(|window| {
4419                window
4420                    .read_root_with(&cx, |workspace, cx| {
4421                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4422                            Some(cx.handle().downgrade())
4423                        } else {
4424                            None
4425                        }
4426                    })
4427                    .unwrap_or(None)
4428            })
4429        });
4430
4431        let workspace = if let Some(existing_workspace) = existing_workspace {
4432            existing_workspace
4433        } else {
4434            let active_call = cx.read(ActiveCall::global);
4435            let room = active_call
4436                .read_with(&cx, |call, _| call.room().cloned())
4437                .ok_or_else(|| anyhow!("not in a call"))?;
4438            let project = room
4439                .update(&mut cx, |room, cx| {
4440                    room.join_project(
4441                        project_id,
4442                        app_state.languages.clone(),
4443                        app_state.fs.clone(),
4444                        cx,
4445                    )
4446                })
4447                .await?;
4448
4449            let window_bounds_override = window_bounds_env_override(&cx);
4450            let window = cx.add_window(
4451                (app_state.build_window_options)(
4452                    window_bounds_override,
4453                    None,
4454                    cx.platform().as_ref(),
4455                ),
4456                |cx| Workspace::new(0, project, app_state.clone(), cx),
4457            );
4458            let workspace = window.root(&cx).unwrap();
4459            (app_state.initialize_workspace)(
4460                workspace.downgrade(),
4461                false,
4462                app_state.clone(),
4463                cx.clone(),
4464            )
4465            .await
4466            .log_err();
4467
4468            workspace.downgrade()
4469        };
4470
4471        workspace.window().activate(&mut cx);
4472        cx.platform().activate(true);
4473
4474        workspace.update(&mut cx, |workspace, cx| {
4475            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4476                let follow_peer_id = room
4477                    .read(cx)
4478                    .remote_participants()
4479                    .iter()
4480                    .find(|(_, participant)| participant.user.id == follow_user_id)
4481                    .map(|(_, p)| p.peer_id)
4482                    .or_else(|| {
4483                        // If we couldn't follow the given user, follow the host instead.
4484                        let collaborator = workspace
4485                            .project()
4486                            .read(cx)
4487                            .collaborators()
4488                            .values()
4489                            .find(|collaborator| collaborator.replica_id == 0)?;
4490                        Some(collaborator.peer_id)
4491                    });
4492
4493                if let Some(follow_peer_id) = follow_peer_id {
4494                    workspace
4495                        .follow(follow_peer_id, cx)
4496                        .map(|follow| follow.detach_and_log_err(cx));
4497                }
4498            }
4499        })?;
4500
4501        anyhow::Ok(())
4502    })
4503}
4504
4505pub fn restart(_: &Restart, cx: &mut AppContext) {
4506    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4507    cx.spawn(|mut cx| async move {
4508        let mut workspace_windows = cx
4509            .windows()
4510            .into_iter()
4511            .filter_map(|window| window.downcast::<Workspace>())
4512            .collect::<Vec<_>>();
4513
4514        // If multiple windows have unsaved changes, and need a save prompt,
4515        // prompt in the active window before switching to a different window.
4516        workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4517
4518        if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4519            let answer = window.prompt(
4520                PromptLevel::Info,
4521                "Are you sure you want to restart?",
4522                &["Restart", "Cancel"],
4523                &mut cx,
4524            );
4525
4526            if let Some(mut answer) = answer {
4527                let answer = answer.next().await;
4528                if answer != Some(0) {
4529                    return Ok(());
4530                }
4531            }
4532        }
4533
4534        // If the user cancels any save prompt, then keep the app open.
4535        for window in workspace_windows {
4536            if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4537                workspace.prepare_to_close(true, cx)
4538            }) {
4539                if !should_close.await? {
4540                    return Ok(());
4541                }
4542            }
4543        }
4544        cx.platform().restart();
4545        anyhow::Ok(())
4546    })
4547    .detach_and_log_err(cx);
4548}
4549
4550fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4551    let mut parts = value.split(',');
4552    let width: usize = parts.next()?.parse().ok()?;
4553    let height: usize = parts.next()?.parse().ok()?;
4554    Some(vec2f(width as f32, height as f32))
4555}
4556
4557#[cfg(test)]
4558mod tests {
4559    use super::*;
4560    use crate::{
4561        dock::test::{TestPanel, TestPanelEvent},
4562        item::test::{TestItem, TestItemEvent, TestProjectItem},
4563    };
4564    use fs::FakeFs;
4565    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4566    use project::{Project, ProjectEntryId};
4567    use serde_json::json;
4568    use settings::SettingsStore;
4569    use std::{cell::RefCell, rc::Rc};
4570
4571    #[gpui::test]
4572    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4573        init_test(cx);
4574
4575        let fs = FakeFs::new(cx.background());
4576        let project = Project::test(fs, [], cx).await;
4577        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4578        let workspace = window.root(cx);
4579
4580        // Adding an item with no ambiguity renders the tab without detail.
4581        let item1 = window.add_view(cx, |_| {
4582            let mut item = TestItem::new();
4583            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4584            item
4585        });
4586        workspace.update(cx, |workspace, cx| {
4587            workspace.add_item(Box::new(item1.clone()), cx);
4588        });
4589        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4590
4591        // Adding an item that creates ambiguity increases the level of detail on
4592        // both tabs.
4593        let item2 = window.add_view(cx, |_| {
4594            let mut item = TestItem::new();
4595            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4596            item
4597        });
4598        workspace.update(cx, |workspace, cx| {
4599            workspace.add_item(Box::new(item2.clone()), cx);
4600        });
4601        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4602        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4603
4604        // Adding an item that creates ambiguity increases the level of detail only
4605        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4606        // we stop at the highest detail available.
4607        let item3 = window.add_view(cx, |_| {
4608            let mut item = TestItem::new();
4609            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4610            item
4611        });
4612        workspace.update(cx, |workspace, cx| {
4613            workspace.add_item(Box::new(item3.clone()), cx);
4614        });
4615        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4616        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4617        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4618    }
4619
4620    #[gpui::test]
4621    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4622        init_test(cx);
4623
4624        let fs = FakeFs::new(cx.background());
4625        fs.insert_tree(
4626            "/root1",
4627            json!({
4628                "one.txt": "",
4629                "two.txt": "",
4630            }),
4631        )
4632        .await;
4633        fs.insert_tree(
4634            "/root2",
4635            json!({
4636                "three.txt": "",
4637            }),
4638        )
4639        .await;
4640
4641        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4642        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4643        let workspace = window.root(cx);
4644        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4645        let worktree_id = project.read_with(cx, |project, cx| {
4646            project.worktrees(cx).next().unwrap().read(cx).id()
4647        });
4648
4649        let item1 = window.add_view(cx, |cx| {
4650            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4651        });
4652        let item2 = window.add_view(cx, |cx| {
4653            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4654        });
4655
4656        // Add an item to an empty pane
4657        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4658        project.read_with(cx, |project, cx| {
4659            assert_eq!(
4660                project.active_entry(),
4661                project
4662                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4663                    .map(|e| e.id)
4664            );
4665        });
4666        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4667
4668        // Add a second item to a non-empty pane
4669        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4670        assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
4671        project.read_with(cx, |project, cx| {
4672            assert_eq!(
4673                project.active_entry(),
4674                project
4675                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4676                    .map(|e| e.id)
4677            );
4678        });
4679
4680        // Close the active item
4681        pane.update(cx, |pane, cx| {
4682            pane.close_active_item(&Default::default(), cx).unwrap()
4683        })
4684        .await
4685        .unwrap();
4686        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4687        project.read_with(cx, |project, cx| {
4688            assert_eq!(
4689                project.active_entry(),
4690                project
4691                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4692                    .map(|e| e.id)
4693            );
4694        });
4695
4696        // Add a project folder
4697        project
4698            .update(cx, |project, cx| {
4699                project.find_or_create_local_worktree("/root2", true, cx)
4700            })
4701            .await
4702            .unwrap();
4703        assert_eq!(
4704            window.current_title(cx).as_deref(),
4705            Some("one.txt β€” root1, root2")
4706        );
4707
4708        // Remove a project folder
4709        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4710        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
4711    }
4712
4713    #[gpui::test]
4714    async fn test_close_window(cx: &mut TestAppContext) {
4715        init_test(cx);
4716
4717        let fs = FakeFs::new(cx.background());
4718        fs.insert_tree("/root", json!({ "one": "" })).await;
4719
4720        let project = Project::test(fs, ["root".as_ref()], cx).await;
4721        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4722        let workspace = window.root(cx);
4723
4724        // When there are no dirty items, there's nothing to do.
4725        let item1 = window.add_view(cx, |_| TestItem::new());
4726        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4727        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4728        assert!(task.await.unwrap());
4729
4730        // When there are dirty untitled items, prompt to save each one. If the user
4731        // cancels any prompt, then abort.
4732        let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
4733        let item3 = window.add_view(cx, |cx| {
4734            TestItem::new()
4735                .with_dirty(true)
4736                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4737        });
4738        workspace.update(cx, |w, cx| {
4739            w.add_item(Box::new(item2.clone()), cx);
4740            w.add_item(Box::new(item3.clone()), cx);
4741        });
4742        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4743        cx.foreground().run_until_parked();
4744        window.simulate_prompt_answer(2, cx); // cancel save all
4745        cx.foreground().run_until_parked();
4746        window.simulate_prompt_answer(2, cx); // cancel save all
4747        cx.foreground().run_until_parked();
4748        assert!(!window.has_pending_prompt(cx));
4749        assert!(!task.await.unwrap());
4750    }
4751
4752    #[gpui::test]
4753    async fn test_close_pane_items(cx: &mut TestAppContext) {
4754        init_test(cx);
4755
4756        let fs = FakeFs::new(cx.background());
4757
4758        let project = Project::test(fs, None, cx).await;
4759        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4760        let workspace = window.root(cx);
4761
4762        let item1 = window.add_view(cx, |cx| {
4763            TestItem::new()
4764                .with_dirty(true)
4765                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4766        });
4767        let item2 = window.add_view(cx, |cx| {
4768            TestItem::new()
4769                .with_dirty(true)
4770                .with_conflict(true)
4771                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4772        });
4773        let item3 = window.add_view(cx, |cx| {
4774            TestItem::new()
4775                .with_dirty(true)
4776                .with_conflict(true)
4777                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4778        });
4779        let item4 = window.add_view(cx, |cx| {
4780            TestItem::new()
4781                .with_dirty(true)
4782                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4783        });
4784        let pane = workspace.update(cx, |workspace, cx| {
4785            workspace.add_item(Box::new(item1.clone()), cx);
4786            workspace.add_item(Box::new(item2.clone()), cx);
4787            workspace.add_item(Box::new(item3.clone()), cx);
4788            workspace.add_item(Box::new(item4.clone()), cx);
4789            workspace.active_pane().clone()
4790        });
4791
4792        let close_items = pane.update(cx, |pane, cx| {
4793            pane.activate_item(1, true, true, cx);
4794            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4795            let item1_id = item1.id();
4796            let item3_id = item3.id();
4797            let item4_id = item4.id();
4798            pane.close_items(cx, SaveIntent::Close, move |id| {
4799                [item1_id, item3_id, item4_id].contains(&id)
4800            })
4801        });
4802        cx.foreground().run_until_parked();
4803
4804        assert!(window.has_pending_prompt(cx));
4805        // Ignore "Save all" prompt
4806        window.simulate_prompt_answer(2, cx);
4807        cx.foreground().run_until_parked();
4808        // There's a prompt to save item 1.
4809        pane.read_with(cx, |pane, _| {
4810            assert_eq!(pane.items_len(), 4);
4811            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4812        });
4813        // Confirm saving item 1.
4814        window.simulate_prompt_answer(0, cx);
4815        cx.foreground().run_until_parked();
4816
4817        // Item 1 is saved. There's a prompt to save item 3.
4818        pane.read_with(cx, |pane, cx| {
4819            assert_eq!(item1.read(cx).save_count, 1);
4820            assert_eq!(item1.read(cx).save_as_count, 0);
4821            assert_eq!(item1.read(cx).reload_count, 0);
4822            assert_eq!(pane.items_len(), 3);
4823            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4824        });
4825        assert!(window.has_pending_prompt(cx));
4826
4827        // Cancel saving item 3.
4828        window.simulate_prompt_answer(1, cx);
4829        cx.foreground().run_until_parked();
4830
4831        // Item 3 is reloaded. There's a prompt to save item 4.
4832        pane.read_with(cx, |pane, cx| {
4833            assert_eq!(item3.read(cx).save_count, 0);
4834            assert_eq!(item3.read(cx).save_as_count, 0);
4835            assert_eq!(item3.read(cx).reload_count, 1);
4836            assert_eq!(pane.items_len(), 2);
4837            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4838        });
4839        assert!(window.has_pending_prompt(cx));
4840
4841        // Confirm saving item 4.
4842        window.simulate_prompt_answer(0, cx);
4843        cx.foreground().run_until_parked();
4844
4845        // There's a prompt for a path for item 4.
4846        cx.simulate_new_path_selection(|_| Some(Default::default()));
4847        close_items.await.unwrap();
4848
4849        // The requested items are closed.
4850        pane.read_with(cx, |pane, cx| {
4851            assert_eq!(item4.read(cx).save_count, 0);
4852            assert_eq!(item4.read(cx).save_as_count, 1);
4853            assert_eq!(item4.read(cx).reload_count, 0);
4854            assert_eq!(pane.items_len(), 1);
4855            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4856        });
4857    }
4858
4859    #[gpui::test]
4860    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4861        init_test(cx);
4862
4863        let fs = FakeFs::new(cx.background());
4864
4865        let project = Project::test(fs, [], cx).await;
4866        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4867        let workspace = window.root(cx);
4868
4869        // Create several workspace items with single project entries, and two
4870        // workspace items with multiple project entries.
4871        let single_entry_items = (0..=4)
4872            .map(|project_entry_id| {
4873                window.add_view(cx, |cx| {
4874                    TestItem::new()
4875                        .with_dirty(true)
4876                        .with_project_items(&[TestProjectItem::new(
4877                            project_entry_id,
4878                            &format!("{project_entry_id}.txt"),
4879                            cx,
4880                        )])
4881                })
4882            })
4883            .collect::<Vec<_>>();
4884        let item_2_3 = window.add_view(cx, |cx| {
4885            TestItem::new()
4886                .with_dirty(true)
4887                .with_singleton(false)
4888                .with_project_items(&[
4889                    single_entry_items[2].read(cx).project_items[0].clone(),
4890                    single_entry_items[3].read(cx).project_items[0].clone(),
4891                ])
4892        });
4893        let item_3_4 = window.add_view(cx, |cx| {
4894            TestItem::new()
4895                .with_dirty(true)
4896                .with_singleton(false)
4897                .with_project_items(&[
4898                    single_entry_items[3].read(cx).project_items[0].clone(),
4899                    single_entry_items[4].read(cx).project_items[0].clone(),
4900                ])
4901        });
4902
4903        // Create two panes that contain the following project entries:
4904        //   left pane:
4905        //     multi-entry items:   (2, 3)
4906        //     single-entry items:  0, 1, 2, 3, 4
4907        //   right pane:
4908        //     single-entry items:  1
4909        //     multi-entry items:   (3, 4)
4910        let left_pane = workspace.update(cx, |workspace, cx| {
4911            let left_pane = workspace.active_pane().clone();
4912            workspace.add_item(Box::new(item_2_3.clone()), cx);
4913            for item in single_entry_items {
4914                workspace.add_item(Box::new(item), cx);
4915            }
4916            left_pane.update(cx, |pane, cx| {
4917                pane.activate_item(2, true, true, cx);
4918            });
4919
4920            workspace
4921                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4922                .unwrap();
4923
4924            left_pane
4925        });
4926
4927        //Need to cause an effect flush in order to respect new focus
4928        workspace.update(cx, |workspace, cx| {
4929            workspace.add_item(Box::new(item_3_4.clone()), cx);
4930            cx.focus(&left_pane);
4931        });
4932
4933        // When closing all of the items in the left pane, we should be prompted twice:
4934        // once for project entry 0, and once for project entry 2. After those two
4935        // prompts, the task should complete.
4936
4937        let close = left_pane.update(cx, |pane, cx| {
4938            pane.close_items(cx, SaveIntent::Close, move |_| true)
4939        });
4940        cx.foreground().run_until_parked();
4941        // Discard "Save all" prompt
4942        window.simulate_prompt_answer(2, cx);
4943
4944        cx.foreground().run_until_parked();
4945        left_pane.read_with(cx, |pane, cx| {
4946            assert_eq!(
4947                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4948                &[ProjectEntryId::from_proto(0)]
4949            );
4950        });
4951        window.simulate_prompt_answer(0, cx);
4952
4953        cx.foreground().run_until_parked();
4954        left_pane.read_with(cx, |pane, cx| {
4955            assert_eq!(
4956                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4957                &[ProjectEntryId::from_proto(2)]
4958            );
4959        });
4960        window.simulate_prompt_answer(0, cx);
4961
4962        cx.foreground().run_until_parked();
4963        close.await.unwrap();
4964        left_pane.read_with(cx, |pane, _| {
4965            assert_eq!(pane.items_len(), 0);
4966        });
4967    }
4968
4969    #[gpui::test]
4970    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4971        init_test(cx);
4972
4973        let fs = FakeFs::new(cx.background());
4974
4975        let project = Project::test(fs, [], cx).await;
4976        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4977        let workspace = window.root(cx);
4978        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4979
4980        let item = window.add_view(cx, |cx| {
4981            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4982        });
4983        let item_id = item.id();
4984        workspace.update(cx, |workspace, cx| {
4985            workspace.add_item(Box::new(item.clone()), cx);
4986        });
4987
4988        // Autosave on window change.
4989        item.update(cx, |item, cx| {
4990            cx.update_global(|settings: &mut SettingsStore, cx| {
4991                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4992                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4993                })
4994            });
4995            item.is_dirty = true;
4996        });
4997
4998        // Deactivating the window saves the file.
4999        window.simulate_deactivation(cx);
5000        deterministic.run_until_parked();
5001        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
5002
5003        // Autosave on focus change.
5004        item.update(cx, |item, cx| {
5005            cx.focus_self();
5006            cx.update_global(|settings: &mut SettingsStore, cx| {
5007                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5008                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5009                })
5010            });
5011            item.is_dirty = true;
5012        });
5013
5014        // Blurring the item saves the file.
5015        item.update(cx, |_, cx| cx.blur());
5016        deterministic.run_until_parked();
5017        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
5018
5019        // Deactivating the window still saves the file.
5020        window.simulate_activation(cx);
5021        item.update(cx, |item, cx| {
5022            cx.focus_self();
5023            item.is_dirty = true;
5024        });
5025        window.simulate_deactivation(cx);
5026
5027        deterministic.run_until_parked();
5028        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5029
5030        // Autosave after delay.
5031        item.update(cx, |item, cx| {
5032            cx.update_global(|settings: &mut SettingsStore, cx| {
5033                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5034                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5035                })
5036            });
5037            item.is_dirty = true;
5038            cx.emit(TestItemEvent::Edit);
5039        });
5040
5041        // Delay hasn't fully expired, so the file is still dirty and unsaved.
5042        deterministic.advance_clock(Duration::from_millis(250));
5043        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5044
5045        // After delay expires, the file is saved.
5046        deterministic.advance_clock(Duration::from_millis(250));
5047        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
5048
5049        // Autosave on focus change, ensuring closing the tab counts as such.
5050        item.update(cx, |item, cx| {
5051            cx.update_global(|settings: &mut SettingsStore, cx| {
5052                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5053                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5054                })
5055            });
5056            item.is_dirty = true;
5057        });
5058
5059        pane.update(cx, |pane, cx| {
5060            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5061        })
5062        .await
5063        .unwrap();
5064        assert!(!window.has_pending_prompt(cx));
5065        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5066
5067        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5068        workspace.update(cx, |workspace, cx| {
5069            workspace.add_item(Box::new(item.clone()), cx);
5070        });
5071        item.update(cx, |item, cx| {
5072            item.project_items[0].update(cx, |item, _| {
5073                item.entry_id = None;
5074            });
5075            item.is_dirty = true;
5076            cx.blur();
5077        });
5078        deterministic.run_until_parked();
5079        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5080
5081        // Ensure autosave is prevented for deleted files also when closing the buffer.
5082        let _close_items = pane.update(cx, |pane, cx| {
5083            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5084        });
5085        deterministic.run_until_parked();
5086        assert!(window.has_pending_prompt(cx));
5087        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5088    }
5089
5090    #[gpui::test]
5091    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5092        init_test(cx);
5093
5094        let fs = FakeFs::new(cx.background());
5095
5096        let project = Project::test(fs, [], cx).await;
5097        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5098        let workspace = window.root(cx);
5099
5100        let item = window.add_view(cx, |cx| {
5101            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5102        });
5103        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5104        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
5105        let toolbar_notify_count = Rc::new(RefCell::new(0));
5106
5107        workspace.update(cx, |workspace, cx| {
5108            workspace.add_item(Box::new(item.clone()), cx);
5109            let toolbar_notification_count = toolbar_notify_count.clone();
5110            cx.observe(&toolbar, move |_, _, _| {
5111                *toolbar_notification_count.borrow_mut() += 1
5112            })
5113            .detach();
5114        });
5115
5116        pane.read_with(cx, |pane, _| {
5117            assert!(!pane.can_navigate_backward());
5118            assert!(!pane.can_navigate_forward());
5119        });
5120
5121        item.update(cx, |item, cx| {
5122            item.set_state("one".to_string(), cx);
5123        });
5124
5125        // Toolbar must be notified to re-render the navigation buttons
5126        assert_eq!(*toolbar_notify_count.borrow(), 1);
5127
5128        pane.read_with(cx, |pane, _| {
5129            assert!(pane.can_navigate_backward());
5130            assert!(!pane.can_navigate_forward());
5131        });
5132
5133        workspace
5134            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5135            .await
5136            .unwrap();
5137
5138        assert_eq!(*toolbar_notify_count.borrow(), 3);
5139        pane.read_with(cx, |pane, _| {
5140            assert!(!pane.can_navigate_backward());
5141            assert!(pane.can_navigate_forward());
5142        });
5143    }
5144
5145    #[gpui::test]
5146    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5147        init_test(cx);
5148        let fs = FakeFs::new(cx.background());
5149
5150        let project = Project::test(fs, [], cx).await;
5151        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5152        let workspace = window.root(cx);
5153
5154        let panel = workspace.update(cx, |workspace, cx| {
5155            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
5156            workspace.add_panel(panel.clone(), cx);
5157
5158            workspace
5159                .right_dock()
5160                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5161
5162            panel
5163        });
5164
5165        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5166        pane.update(cx, |pane, cx| {
5167            let item = cx.add_view(|_| TestItem::new());
5168            pane.add_item(Box::new(item), true, true, None, cx);
5169        });
5170
5171        // Transfer focus from center to panel
5172        workspace.update(cx, |workspace, cx| {
5173            workspace.toggle_panel_focus::<TestPanel>(cx);
5174        });
5175
5176        workspace.read_with(cx, |workspace, cx| {
5177            assert!(workspace.right_dock().read(cx).is_open());
5178            assert!(!panel.is_zoomed(cx));
5179            assert!(panel.has_focus(cx));
5180        });
5181
5182        // Transfer focus from panel to center
5183        workspace.update(cx, |workspace, cx| {
5184            workspace.toggle_panel_focus::<TestPanel>(cx);
5185        });
5186
5187        workspace.read_with(cx, |workspace, cx| {
5188            assert!(workspace.right_dock().read(cx).is_open());
5189            assert!(!panel.is_zoomed(cx));
5190            assert!(!panel.has_focus(cx));
5191        });
5192
5193        // Close the dock
5194        workspace.update(cx, |workspace, cx| {
5195            workspace.toggle_dock(DockPosition::Right, cx);
5196        });
5197
5198        workspace.read_with(cx, |workspace, cx| {
5199            assert!(!workspace.right_dock().read(cx).is_open());
5200            assert!(!panel.is_zoomed(cx));
5201            assert!(!panel.has_focus(cx));
5202        });
5203
5204        // Open the dock
5205        workspace.update(cx, |workspace, cx| {
5206            workspace.toggle_dock(DockPosition::Right, cx);
5207        });
5208
5209        workspace.read_with(cx, |workspace, cx| {
5210            assert!(workspace.right_dock().read(cx).is_open());
5211            assert!(!panel.is_zoomed(cx));
5212            assert!(panel.has_focus(cx));
5213        });
5214
5215        // Focus and zoom panel
5216        panel.update(cx, |panel, cx| {
5217            cx.focus_self();
5218            panel.set_zoomed(true, cx)
5219        });
5220
5221        workspace.read_with(cx, |workspace, cx| {
5222            assert!(workspace.right_dock().read(cx).is_open());
5223            assert!(panel.is_zoomed(cx));
5224            assert!(panel.has_focus(cx));
5225        });
5226
5227        // Transfer focus to the center closes the dock
5228        workspace.update(cx, |workspace, cx| {
5229            workspace.toggle_panel_focus::<TestPanel>(cx);
5230        });
5231
5232        workspace.read_with(cx, |workspace, cx| {
5233            assert!(!workspace.right_dock().read(cx).is_open());
5234            assert!(panel.is_zoomed(cx));
5235            assert!(!panel.has_focus(cx));
5236        });
5237
5238        // Transferring focus back to the panel keeps it zoomed
5239        workspace.update(cx, |workspace, cx| {
5240            workspace.toggle_panel_focus::<TestPanel>(cx);
5241        });
5242
5243        workspace.read_with(cx, |workspace, cx| {
5244            assert!(workspace.right_dock().read(cx).is_open());
5245            assert!(panel.is_zoomed(cx));
5246            assert!(panel.has_focus(cx));
5247        });
5248
5249        // Close the dock while it is zoomed
5250        workspace.update(cx, |workspace, cx| {
5251            workspace.toggle_dock(DockPosition::Right, cx)
5252        });
5253
5254        workspace.read_with(cx, |workspace, cx| {
5255            assert!(!workspace.right_dock().read(cx).is_open());
5256            assert!(panel.is_zoomed(cx));
5257            assert!(workspace.zoomed.is_none());
5258            assert!(!panel.has_focus(cx));
5259        });
5260
5261        // Opening the dock, when it's zoomed, retains focus
5262        workspace.update(cx, |workspace, cx| {
5263            workspace.toggle_dock(DockPosition::Right, cx)
5264        });
5265
5266        workspace.read_with(cx, |workspace, cx| {
5267            assert!(workspace.right_dock().read(cx).is_open());
5268            assert!(panel.is_zoomed(cx));
5269            assert!(workspace.zoomed.is_some());
5270            assert!(panel.has_focus(cx));
5271        });
5272
5273        // Unzoom and close the panel, zoom the active pane.
5274        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5275        workspace.update(cx, |workspace, cx| {
5276            workspace.toggle_dock(DockPosition::Right, cx)
5277        });
5278        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5279
5280        // Opening a dock unzooms the pane.
5281        workspace.update(cx, |workspace, cx| {
5282            workspace.toggle_dock(DockPosition::Right, cx)
5283        });
5284        workspace.read_with(cx, |workspace, cx| {
5285            let pane = pane.read(cx);
5286            assert!(!pane.is_zoomed());
5287            assert!(!pane.has_focus());
5288            assert!(workspace.right_dock().read(cx).is_open());
5289            assert!(workspace.zoomed.is_none());
5290        });
5291    }
5292
5293    #[gpui::test]
5294    async fn test_panels(cx: &mut gpui::TestAppContext) {
5295        init_test(cx);
5296        let fs = FakeFs::new(cx.background());
5297
5298        let project = Project::test(fs, [], cx).await;
5299        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5300        let workspace = window.root(cx);
5301
5302        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5303            // Add panel_1 on the left, panel_2 on the right.
5304            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
5305            workspace.add_panel(panel_1.clone(), cx);
5306            workspace
5307                .left_dock()
5308                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5309            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
5310            workspace.add_panel(panel_2.clone(), cx);
5311            workspace
5312                .right_dock()
5313                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5314
5315            let left_dock = workspace.left_dock();
5316            assert_eq!(
5317                left_dock.read(cx).visible_panel().unwrap().id(),
5318                panel_1.id()
5319            );
5320            assert_eq!(
5321                left_dock.read(cx).active_panel_size(cx).unwrap(),
5322                panel_1.size(cx)
5323            );
5324
5325            left_dock.update(cx, |left_dock, cx| {
5326                left_dock.resize_active_panel(Some(1337.), cx)
5327            });
5328            assert_eq!(
5329                workspace
5330                    .right_dock()
5331                    .read(cx)
5332                    .visible_panel()
5333                    .unwrap()
5334                    .id(),
5335                panel_2.id()
5336            );
5337
5338            (panel_1, panel_2)
5339        });
5340
5341        // Move panel_1 to the right
5342        panel_1.update(cx, |panel_1, cx| {
5343            panel_1.set_position(DockPosition::Right, cx)
5344        });
5345
5346        workspace.update(cx, |workspace, cx| {
5347            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5348            // Since it was the only panel on the left, the left dock should now be closed.
5349            assert!(!workspace.left_dock().read(cx).is_open());
5350            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5351            let right_dock = workspace.right_dock();
5352            assert_eq!(
5353                right_dock.read(cx).visible_panel().unwrap().id(),
5354                panel_1.id()
5355            );
5356            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5357
5358            // Now we move panel_2Β to the left
5359            panel_2.set_position(DockPosition::Left, cx);
5360        });
5361
5362        workspace.update(cx, |workspace, cx| {
5363            // Since panel_2 was not visible on the right, we don't open the left dock.
5364            assert!(!workspace.left_dock().read(cx).is_open());
5365            // And the right dock is unaffected in it's displaying of panel_1
5366            assert!(workspace.right_dock().read(cx).is_open());
5367            assert_eq!(
5368                workspace
5369                    .right_dock()
5370                    .read(cx)
5371                    .visible_panel()
5372                    .unwrap()
5373                    .id(),
5374                panel_1.id()
5375            );
5376        });
5377
5378        // Move panel_1 back to the left
5379        panel_1.update(cx, |panel_1, cx| {
5380            panel_1.set_position(DockPosition::Left, cx)
5381        });
5382
5383        workspace.update(cx, |workspace, cx| {
5384            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5385            let left_dock = workspace.left_dock();
5386            assert!(left_dock.read(cx).is_open());
5387            assert_eq!(
5388                left_dock.read(cx).visible_panel().unwrap().id(),
5389                panel_1.id()
5390            );
5391            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5392            // And right the dock should be closed as it no longer has any panels.
5393            assert!(!workspace.right_dock().read(cx).is_open());
5394
5395            // Now we move panel_1 to the bottom
5396            panel_1.set_position(DockPosition::Bottom, cx);
5397        });
5398
5399        workspace.update(cx, |workspace, cx| {
5400            // Since panel_1 was visible on the left, we close the left dock.
5401            assert!(!workspace.left_dock().read(cx).is_open());
5402            // The bottom dock is sized based on the panel's default size,
5403            // since the panel orientation changed from vertical to horizontal.
5404            let bottom_dock = workspace.bottom_dock();
5405            assert_eq!(
5406                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5407                panel_1.size(cx),
5408            );
5409            // Close bottom dock and move panel_1 back to the left.
5410            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5411            panel_1.set_position(DockPosition::Left, cx);
5412        });
5413
5414        // Emit activated event on panel 1
5415        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5416
5417        // Now the left dock is open and panel_1 is active and focused.
5418        workspace.read_with(cx, |workspace, cx| {
5419            let left_dock = workspace.left_dock();
5420            assert!(left_dock.read(cx).is_open());
5421            assert_eq!(
5422                left_dock.read(cx).visible_panel().unwrap().id(),
5423                panel_1.id()
5424            );
5425            assert!(panel_1.is_focused(cx));
5426        });
5427
5428        // Emit closed event on panel 2, which is not active
5429        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5430
5431        // Wo don't close the left dock, because panel_2 wasn't the active panel
5432        workspace.read_with(cx, |workspace, cx| {
5433            let left_dock = workspace.left_dock();
5434            assert!(left_dock.read(cx).is_open());
5435            assert_eq!(
5436                left_dock.read(cx).visible_panel().unwrap().id(),
5437                panel_1.id()
5438            );
5439        });
5440
5441        // Emitting a ZoomIn event shows the panel as zoomed.
5442        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5443        workspace.read_with(cx, |workspace, _| {
5444            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5445            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5446        });
5447
5448        // Move panel to another dock while it is zoomed
5449        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5450        workspace.read_with(cx, |workspace, _| {
5451            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5452            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5453        });
5454
5455        // If focus is transferred to another view that's not a panel or another pane, we still show
5456        // the panel as zoomed.
5457        let focus_receiver = window.add_view(cx, |_| EmptyView);
5458        focus_receiver.update(cx, |_, cx| cx.focus_self());
5459        workspace.read_with(cx, |workspace, _| {
5460            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5461            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5462        });
5463
5464        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5465        workspace.update(cx, |_, cx| cx.focus_self());
5466        workspace.read_with(cx, |workspace, _| {
5467            assert_eq!(workspace.zoomed, None);
5468            assert_eq!(workspace.zoomed_position, None);
5469        });
5470
5471        // If focus is transferred again to another view that's not a panel or a pane, we won't
5472        // show the panel as zoomed because it wasn't zoomed before.
5473        focus_receiver.update(cx, |_, cx| cx.focus_self());
5474        workspace.read_with(cx, |workspace, _| {
5475            assert_eq!(workspace.zoomed, None);
5476            assert_eq!(workspace.zoomed_position, None);
5477        });
5478
5479        // When focus is transferred back to the panel, it is zoomed again.
5480        panel_1.update(cx, |_, cx| cx.focus_self());
5481        workspace.read_with(cx, |workspace, _| {
5482            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5483            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5484        });
5485
5486        // Emitting a ZoomOut event unzooms the panel.
5487        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5488        workspace.read_with(cx, |workspace, _| {
5489            assert_eq!(workspace.zoomed, None);
5490            assert_eq!(workspace.zoomed_position, None);
5491        });
5492
5493        // Emit closed event on panel 1, which is active
5494        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5495
5496        // Now the left dock is closed, because panel_1 was the active panel
5497        workspace.read_with(cx, |workspace, cx| {
5498            let right_dock = workspace.right_dock();
5499            assert!(!right_dock.read(cx).is_open());
5500        });
5501    }
5502
5503    pub fn init_test(cx: &mut TestAppContext) {
5504        cx.foreground().forbid_parking();
5505        cx.update(|cx| {
5506            cx.set_global(SettingsStore::test(cx));
5507            theme::init((), cx);
5508            language::init(cx);
5509            crate::init_settings(cx);
5510            Project::init_settings(cx);
5511        });
5512    }
5513}