workspace.rs

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