workspace.rs

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