workspace.rs

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