workspace.rs

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