workspace.rs

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