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