workspace.rs

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