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 any 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            } else {
3511                let backup_path = (*db::BACKUP_DB_PATH).read();
3512                if let Some(backup_path) = backup_path.clone() {
3513                    workspace.show_notification_once(1, cx, move |cx| {
3514                        cx.add_view(move |_| {
3515                            MessageNotification::new(format!(
3516                                "Database file was corrupted. Old database backed up to {}",
3517                                backup_path.display()
3518                            ))
3519                            .with_click_message("Click to show old database in finder")
3520                            .on_click(move |cx| {
3521                                cx.platform().open_url(&backup_path.to_string_lossy())
3522                            })
3523                        })
3524                    });
3525                }
3526            }
3527        })
3528        .log_err();
3529}
3530
3531impl Entity for Workspace {
3532    type Event = Event;
3533}
3534
3535impl View for Workspace {
3536    fn ui_name() -> &'static str {
3537        "Workspace"
3538    }
3539
3540    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3541        let theme = theme::current(cx).clone();
3542        Stack::new()
3543            .with_child(
3544                Flex::column()
3545                    .with_child(self.render_titlebar(&theme, cx))
3546                    .with_child(
3547                        Stack::new()
3548                            .with_child({
3549                                let project = self.project.clone();
3550                                Flex::row()
3551                                    .with_children(self.render_dock(DockPosition::Left, cx))
3552                                    .with_child(
3553                                        Flex::column()
3554                                            .with_child(
3555                                                FlexItem::new(
3556                                                    self.center.render(
3557                                                        &project,
3558                                                        &theme,
3559                                                        &self.follower_states_by_leader,
3560                                                        self.active_call(),
3561                                                        self.active_pane(),
3562                                                        self.zoomed
3563                                                            .as_ref()
3564                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3565                                                            .as_ref(),
3566                                                        &self.app_state,
3567                                                        cx,
3568                                                    ),
3569                                                )
3570                                                .flex(1., true),
3571                                            )
3572                                            .with_children(
3573                                                self.render_dock(DockPosition::Bottom, cx),
3574                                            )
3575                                            .flex(1., true),
3576                                    )
3577                                    .with_children(self.render_dock(DockPosition::Right, cx))
3578                            })
3579                            .with_child(Overlay::new(
3580                                Stack::new()
3581                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3582                                        enum ZoomBackground {}
3583                                        let zoomed = zoomed.upgrade(cx)?;
3584
3585                                        let mut foreground_style =
3586                                            theme.workspace.zoomed_pane_foreground;
3587                                        if let Some(zoomed_dock_position) = self.zoomed_position {
3588                                            foreground_style =
3589                                                theme.workspace.zoomed_panel_foreground;
3590                                            let margin = foreground_style.margin.top;
3591                                            let border = foreground_style.border.top;
3592
3593                                            // Only include a margin and border on the opposite side.
3594                                            foreground_style.margin.top = 0.;
3595                                            foreground_style.margin.left = 0.;
3596                                            foreground_style.margin.bottom = 0.;
3597                                            foreground_style.margin.right = 0.;
3598                                            foreground_style.border.top = false;
3599                                            foreground_style.border.left = false;
3600                                            foreground_style.border.bottom = false;
3601                                            foreground_style.border.right = false;
3602                                            match zoomed_dock_position {
3603                                                DockPosition::Left => {
3604                                                    foreground_style.margin.right = margin;
3605                                                    foreground_style.border.right = border;
3606                                                }
3607                                                DockPosition::Right => {
3608                                                    foreground_style.margin.left = margin;
3609                                                    foreground_style.border.left = border;
3610                                                }
3611                                                DockPosition::Bottom => {
3612                                                    foreground_style.margin.top = margin;
3613                                                    foreground_style.border.top = border;
3614                                                }
3615                                            }
3616                                        }
3617
3618                                        Some(
3619                                            ChildView::new(&zoomed, cx)
3620                                                .contained()
3621                                                .with_style(foreground_style)
3622                                                .aligned()
3623                                                .contained()
3624                                                .with_style(theme.workspace.zoomed_background)
3625                                                .mouse::<ZoomBackground>(0)
3626                                                .capture_all()
3627                                                .on_down(
3628                                                    MouseButton::Left,
3629                                                    |_, this: &mut Self, cx| {
3630                                                        this.zoom_out(cx);
3631                                                    },
3632                                                ),
3633                                        )
3634                                    }))
3635                                    .with_children(self.modal.as_ref().map(|modal| {
3636                                        ChildView::new(modal.view.as_any(), cx)
3637                                            .contained()
3638                                            .with_style(theme.workspace.modal)
3639                                            .aligned()
3640                                            .top()
3641                                    }))
3642                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3643                            ))
3644                            .flex(1.0, true),
3645                    )
3646                    .with_child(ChildView::new(&self.status_bar, cx))
3647                    .contained()
3648                    .with_background_color(theme.workspace.background),
3649            )
3650            .with_children(DragAndDrop::render(cx))
3651            .with_children(self.render_disconnected_overlay(cx))
3652            .into_any_named("workspace")
3653    }
3654
3655    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3656        if cx.is_self_focused() {
3657            cx.focus(&self.active_pane);
3658        }
3659    }
3660}
3661
3662impl ViewId {
3663    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3664        Ok(Self {
3665            creator: message
3666                .creator
3667                .ok_or_else(|| anyhow!("creator is missing"))?,
3668            id: message.id,
3669        })
3670    }
3671
3672    pub(crate) fn to_proto(&self) -> proto::ViewId {
3673        proto::ViewId {
3674            creator: Some(self.creator),
3675            id: self.id,
3676        }
3677    }
3678}
3679
3680pub trait WorkspaceHandle {
3681    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3682}
3683
3684impl WorkspaceHandle for ViewHandle<Workspace> {
3685    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3686        self.read(cx)
3687            .worktrees(cx)
3688            .flat_map(|worktree| {
3689                let worktree_id = worktree.read(cx).id();
3690                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3691                    worktree_id,
3692                    path: f.path.clone(),
3693                })
3694            })
3695            .collect::<Vec<_>>()
3696    }
3697}
3698
3699impl std::fmt::Debug for OpenPaths {
3700    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3701        f.debug_struct("OpenPaths")
3702            .field("paths", &self.paths)
3703            .finish()
3704    }
3705}
3706
3707pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
3708
3709pub fn activate_workspace_for_project(
3710    cx: &mut AsyncAppContext,
3711    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3712) -> Option<WeakViewHandle<Workspace>> {
3713    for window_id in cx.window_ids() {
3714        let handle = cx
3715            .update_window(window_id, |cx| {
3716                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3717                    let project = workspace_handle.read(cx).project.clone();
3718                    if project.update(cx, &predicate) {
3719                        cx.activate_window();
3720                        return Some(workspace_handle.clone());
3721                    }
3722                }
3723                None
3724            })
3725            .flatten();
3726
3727        if let Some(handle) = handle {
3728            return Some(handle.downgrade());
3729        }
3730    }
3731    None
3732}
3733
3734pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3735    DB.last_workspace().await.log_err().flatten()
3736}
3737
3738#[allow(clippy::type_complexity)]
3739pub fn open_paths(
3740    abs_paths: &[PathBuf],
3741    app_state: &Arc<AppState>,
3742    requesting_window_id: Option<usize>,
3743    cx: &mut AppContext,
3744) -> Task<
3745    Result<(
3746        WeakViewHandle<Workspace>,
3747        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3748    )>,
3749> {
3750    let app_state = app_state.clone();
3751    let abs_paths = abs_paths.to_vec();
3752    cx.spawn(|mut cx| async move {
3753        // Open paths in existing workspace if possible
3754        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3755            project.contains_paths(&abs_paths, cx)
3756        });
3757
3758        if let Some(existing) = existing {
3759            Ok((
3760                existing.clone(),
3761                existing
3762                    .update(&mut cx, |workspace, cx| {
3763                        workspace.open_paths(abs_paths, true, cx)
3764                    })?
3765                    .await,
3766            ))
3767        } else {
3768            Ok(cx
3769                .update(|cx| {
3770                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx)
3771                })
3772                .await)
3773        }
3774    })
3775}
3776
3777pub fn open_new(
3778    app_state: &Arc<AppState>,
3779    cx: &mut AppContext,
3780    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3781) -> Task<()> {
3782    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3783    cx.spawn(|mut cx| async move {
3784        let (workspace, opened_paths) = task.await;
3785
3786        workspace
3787            .update(&mut cx, |workspace, cx| {
3788                if opened_paths.is_empty() {
3789                    init(workspace, cx)
3790                }
3791            })
3792            .log_err();
3793    })
3794}
3795
3796pub fn create_and_open_local_file(
3797    path: &'static Path,
3798    cx: &mut ViewContext<Workspace>,
3799    default_content: impl 'static + Send + FnOnce() -> Rope,
3800) -> Task<Result<Box<dyn ItemHandle>>> {
3801    cx.spawn(|workspace, mut cx| async move {
3802        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
3803        if !fs.is_file(path).await {
3804            fs.create_file(path, Default::default()).await?;
3805            fs.save(path, &default_content(), Default::default())
3806                .await?;
3807        }
3808
3809        let mut items = workspace
3810            .update(&mut cx, |workspace, cx| {
3811                workspace.with_local_workspace(cx, |workspace, cx| {
3812                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
3813                })
3814            })?
3815            .await?
3816            .await;
3817
3818        let item = items.pop().flatten();
3819        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
3820    })
3821}
3822
3823pub fn join_remote_project(
3824    project_id: u64,
3825    follow_user_id: u64,
3826    app_state: Arc<AppState>,
3827    cx: &mut AppContext,
3828) -> Task<Result<()>> {
3829    cx.spawn(|mut cx| async move {
3830        let existing_workspace = cx
3831            .window_ids()
3832            .into_iter()
3833            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
3834            .find(|workspace| {
3835                cx.read_window(workspace.window_id(), |cx| {
3836                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
3837                })
3838                .unwrap_or(false)
3839            });
3840
3841        let workspace = if let Some(existing_workspace) = existing_workspace {
3842            existing_workspace.downgrade()
3843        } else {
3844            let active_call = cx.read(ActiveCall::global);
3845            let room = active_call
3846                .read_with(&cx, |call, _| call.room().cloned())
3847                .ok_or_else(|| anyhow!("not in a call"))?;
3848            let project = room
3849                .update(&mut cx, |room, cx| {
3850                    room.join_project(
3851                        project_id,
3852                        app_state.languages.clone(),
3853                        app_state.fs.clone(),
3854                        cx,
3855                    )
3856                })
3857                .await?;
3858
3859            let window_bounds_override = window_bounds_env_override(&cx);
3860            let (_, workspace) = cx.add_window(
3861                (app_state.build_window_options)(
3862                    window_bounds_override,
3863                    None,
3864                    cx.platform().as_ref(),
3865                ),
3866                |cx| Workspace::new(0, project, app_state.clone(), cx),
3867            );
3868            (app_state.initialize_workspace)(
3869                workspace.downgrade(),
3870                false,
3871                app_state.clone(),
3872                cx.clone(),
3873            )
3874            .await
3875            .log_err();
3876
3877            workspace.downgrade()
3878        };
3879
3880        cx.activate_window(workspace.window_id());
3881        cx.platform().activate(true);
3882
3883        workspace.update(&mut cx, |workspace, cx| {
3884            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
3885                let follow_peer_id = room
3886                    .read(cx)
3887                    .remote_participants()
3888                    .iter()
3889                    .find(|(_, participant)| participant.user.id == follow_user_id)
3890                    .map(|(_, p)| p.peer_id)
3891                    .or_else(|| {
3892                        // If we couldn't follow the given user, follow the host instead.
3893                        let collaborator = workspace
3894                            .project()
3895                            .read(cx)
3896                            .collaborators()
3897                            .values()
3898                            .find(|collaborator| collaborator.replica_id == 0)?;
3899                        Some(collaborator.peer_id)
3900                    });
3901
3902                if let Some(follow_peer_id) = follow_peer_id {
3903                    if !workspace.is_being_followed(follow_peer_id) {
3904                        workspace
3905                            .toggle_follow(follow_peer_id, cx)
3906                            .map(|follow| follow.detach_and_log_err(cx));
3907                    }
3908                }
3909            }
3910        })?;
3911
3912        anyhow::Ok(())
3913    })
3914}
3915
3916pub fn restart(_: &Restart, cx: &mut AppContext) {
3917    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
3918    cx.spawn(|mut cx| async move {
3919        let mut workspaces = cx
3920            .window_ids()
3921            .into_iter()
3922            .filter_map(|window_id| {
3923                Some(
3924                    cx.root_view(window_id)?
3925                        .clone()
3926                        .downcast::<Workspace>()?
3927                        .downgrade(),
3928                )
3929            })
3930            .collect::<Vec<_>>();
3931
3932        // If multiple windows have unsaved changes, and need a save prompt,
3933        // prompt in the active window before switching to a different window.
3934        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
3935
3936        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
3937            let answer = cx.prompt(
3938                workspace.window_id(),
3939                PromptLevel::Info,
3940                "Are you sure you want to restart?",
3941                &["Restart", "Cancel"],
3942            );
3943
3944            if let Some(mut answer) = answer {
3945                let answer = answer.next().await;
3946                if answer != Some(0) {
3947                    return Ok(());
3948                }
3949            }
3950        }
3951
3952        // If the user cancels any save prompt, then keep the app open.
3953        for workspace in workspaces {
3954            if !workspace
3955                .update(&mut cx, |workspace, cx| {
3956                    workspace.prepare_to_close(true, cx)
3957                })?
3958                .await?
3959            {
3960                return Ok(());
3961            }
3962        }
3963        cx.platform().restart();
3964        anyhow::Ok(())
3965    })
3966    .detach_and_log_err(cx);
3967}
3968
3969fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
3970    let mut parts = value.split(',');
3971    let width: usize = parts.next()?.parse().ok()?;
3972    let height: usize = parts.next()?.parse().ok()?;
3973    Some(vec2f(width as f32, height as f32))
3974}
3975
3976#[cfg(test)]
3977mod tests {
3978    use super::*;
3979    use crate::{
3980        dock::test::{TestPanel, TestPanelEvent},
3981        item::test::{TestItem, TestItemEvent, TestProjectItem},
3982    };
3983    use fs::FakeFs;
3984    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
3985    use project::{Project, ProjectEntryId};
3986    use serde_json::json;
3987    use settings::SettingsStore;
3988    use std::{cell::RefCell, rc::Rc};
3989
3990    #[gpui::test]
3991    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3992        init_test(cx);
3993
3994        let fs = FakeFs::new(cx.background());
3995        let project = Project::test(fs, [], cx).await;
3996        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3997
3998        // Adding an item with no ambiguity renders the tab without detail.
3999        let item1 = cx.add_view(window_id, |_| {
4000            let mut item = TestItem::new();
4001            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4002            item
4003        });
4004        workspace.update(cx, |workspace, cx| {
4005            workspace.add_item(Box::new(item1.clone()), cx);
4006        });
4007        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4008
4009        // Adding an item that creates ambiguity increases the level of detail on
4010        // both tabs.
4011        let item2 = cx.add_view(window_id, |_| {
4012            let mut item = TestItem::new();
4013            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4014            item
4015        });
4016        workspace.update(cx, |workspace, cx| {
4017            workspace.add_item(Box::new(item2.clone()), cx);
4018        });
4019        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4020        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4021
4022        // Adding an item that creates ambiguity increases the level of detail only
4023        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4024        // we stop at the highest detail available.
4025        let item3 = cx.add_view(window_id, |_| {
4026            let mut item = TestItem::new();
4027            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4028            item
4029        });
4030        workspace.update(cx, |workspace, cx| {
4031            workspace.add_item(Box::new(item3.clone()), cx);
4032        });
4033        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4034        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4035        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4036    }
4037
4038    #[gpui::test]
4039    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4040        init_test(cx);
4041
4042        let fs = FakeFs::new(cx.background());
4043        fs.insert_tree(
4044            "/root1",
4045            json!({
4046                "one.txt": "",
4047                "two.txt": "",
4048            }),
4049        )
4050        .await;
4051        fs.insert_tree(
4052            "/root2",
4053            json!({
4054                "three.txt": "",
4055            }),
4056        )
4057        .await;
4058
4059        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4060        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4061        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4062        let worktree_id = project.read_with(cx, |project, cx| {
4063            project.worktrees(cx).next().unwrap().read(cx).id()
4064        });
4065
4066        let item1 = cx.add_view(window_id, |cx| {
4067            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4068        });
4069        let item2 = cx.add_view(window_id, |cx| {
4070            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4071        });
4072
4073        // Add an item to an empty pane
4074        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4075        project.read_with(cx, |project, cx| {
4076            assert_eq!(
4077                project.active_entry(),
4078                project
4079                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4080                    .map(|e| e.id)
4081            );
4082        });
4083        assert_eq!(
4084            cx.current_window_title(window_id).as_deref(),
4085            Some("one.txt β€” root1")
4086        );
4087
4088        // Add a second item to a non-empty pane
4089        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4090        assert_eq!(
4091            cx.current_window_title(window_id).as_deref(),
4092            Some("two.txt β€” root1")
4093        );
4094        project.read_with(cx, |project, cx| {
4095            assert_eq!(
4096                project.active_entry(),
4097                project
4098                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4099                    .map(|e| e.id)
4100            );
4101        });
4102
4103        // Close the active item
4104        pane.update(cx, |pane, cx| {
4105            pane.close_active_item(&Default::default(), cx).unwrap()
4106        })
4107        .await
4108        .unwrap();
4109        assert_eq!(
4110            cx.current_window_title(window_id).as_deref(),
4111            Some("one.txt β€” root1")
4112        );
4113        project.read_with(cx, |project, cx| {
4114            assert_eq!(
4115                project.active_entry(),
4116                project
4117                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4118                    .map(|e| e.id)
4119            );
4120        });
4121
4122        // Add a project folder
4123        project
4124            .update(cx, |project, cx| {
4125                project.find_or_create_local_worktree("/root2", true, cx)
4126            })
4127            .await
4128            .unwrap();
4129        assert_eq!(
4130            cx.current_window_title(window_id).as_deref(),
4131            Some("one.txt β€” root1, root2")
4132        );
4133
4134        // Remove a project folder
4135        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4136        assert_eq!(
4137            cx.current_window_title(window_id).as_deref(),
4138            Some("one.txt β€” root2")
4139        );
4140    }
4141
4142    #[gpui::test]
4143    async fn test_close_window(cx: &mut TestAppContext) {
4144        init_test(cx);
4145
4146        let fs = FakeFs::new(cx.background());
4147        fs.insert_tree("/root", json!({ "one": "" })).await;
4148
4149        let project = Project::test(fs, ["root".as_ref()], cx).await;
4150        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4151
4152        // When there are no dirty items, there's nothing to do.
4153        let item1 = cx.add_view(window_id, |_| TestItem::new());
4154        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4155        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4156        assert!(task.await.unwrap());
4157
4158        // When there are dirty untitled items, prompt to save each one. If the user
4159        // cancels any prompt, then abort.
4160        let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
4161        let item3 = cx.add_view(window_id, |cx| {
4162            TestItem::new()
4163                .with_dirty(true)
4164                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4165        });
4166        workspace.update(cx, |w, cx| {
4167            w.add_item(Box::new(item2.clone()), cx);
4168            w.add_item(Box::new(item3.clone()), cx);
4169        });
4170        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4171        cx.foreground().run_until_parked();
4172        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
4173        cx.foreground().run_until_parked();
4174        assert!(!cx.has_pending_prompt(window_id));
4175        assert!(!task.await.unwrap());
4176    }
4177
4178    #[gpui::test]
4179    async fn test_close_pane_items(cx: &mut TestAppContext) {
4180        init_test(cx);
4181
4182        let fs = FakeFs::new(cx.background());
4183
4184        let project = Project::test(fs, None, cx).await;
4185        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4186
4187        let item1 = cx.add_view(window_id, |cx| {
4188            TestItem::new()
4189                .with_dirty(true)
4190                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4191        });
4192        let item2 = cx.add_view(window_id, |cx| {
4193            TestItem::new()
4194                .with_dirty(true)
4195                .with_conflict(true)
4196                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4197        });
4198        let item3 = cx.add_view(window_id, |cx| {
4199            TestItem::new()
4200                .with_dirty(true)
4201                .with_conflict(true)
4202                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4203        });
4204        let item4 = cx.add_view(window_id, |cx| {
4205            TestItem::new()
4206                .with_dirty(true)
4207                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4208        });
4209        let pane = workspace.update(cx, |workspace, cx| {
4210            workspace.add_item(Box::new(item1.clone()), cx);
4211            workspace.add_item(Box::new(item2.clone()), cx);
4212            workspace.add_item(Box::new(item3.clone()), cx);
4213            workspace.add_item(Box::new(item4.clone()), cx);
4214            workspace.active_pane().clone()
4215        });
4216
4217        let close_items = pane.update(cx, |pane, cx| {
4218            pane.activate_item(1, true, true, cx);
4219            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4220            let item1_id = item1.id();
4221            let item3_id = item3.id();
4222            let item4_id = item4.id();
4223            pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id))
4224        });
4225        cx.foreground().run_until_parked();
4226
4227        // There's a prompt to save item 1.
4228        pane.read_with(cx, |pane, _| {
4229            assert_eq!(pane.items_len(), 4);
4230            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4231        });
4232        assert!(cx.has_pending_prompt(window_id));
4233
4234        // Confirm saving item 1.
4235        cx.simulate_prompt_answer(window_id, 0);
4236        cx.foreground().run_until_parked();
4237
4238        // Item 1 is saved. There's a prompt to save item 3.
4239        pane.read_with(cx, |pane, cx| {
4240            assert_eq!(item1.read(cx).save_count, 1);
4241            assert_eq!(item1.read(cx).save_as_count, 0);
4242            assert_eq!(item1.read(cx).reload_count, 0);
4243            assert_eq!(pane.items_len(), 3);
4244            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4245        });
4246        assert!(cx.has_pending_prompt(window_id));
4247
4248        // Cancel saving item 3.
4249        cx.simulate_prompt_answer(window_id, 1);
4250        cx.foreground().run_until_parked();
4251
4252        // Item 3 is reloaded. There's a prompt to save item 4.
4253        pane.read_with(cx, |pane, cx| {
4254            assert_eq!(item3.read(cx).save_count, 0);
4255            assert_eq!(item3.read(cx).save_as_count, 0);
4256            assert_eq!(item3.read(cx).reload_count, 1);
4257            assert_eq!(pane.items_len(), 2);
4258            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4259        });
4260        assert!(cx.has_pending_prompt(window_id));
4261
4262        // Confirm saving item 4.
4263        cx.simulate_prompt_answer(window_id, 0);
4264        cx.foreground().run_until_parked();
4265
4266        // There's a prompt for a path for item 4.
4267        cx.simulate_new_path_selection(|_| Some(Default::default()));
4268        close_items.await.unwrap();
4269
4270        // The requested items are closed.
4271        pane.read_with(cx, |pane, cx| {
4272            assert_eq!(item4.read(cx).save_count, 0);
4273            assert_eq!(item4.read(cx).save_as_count, 1);
4274            assert_eq!(item4.read(cx).reload_count, 0);
4275            assert_eq!(pane.items_len(), 1);
4276            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4277        });
4278    }
4279
4280    #[gpui::test]
4281    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4282        init_test(cx);
4283
4284        let fs = FakeFs::new(cx.background());
4285
4286        let project = Project::test(fs, [], cx).await;
4287        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4288
4289        // Create several workspace items with single project entries, and two
4290        // workspace items with multiple project entries.
4291        let single_entry_items = (0..=4)
4292            .map(|project_entry_id| {
4293                cx.add_view(window_id, |cx| {
4294                    TestItem::new()
4295                        .with_dirty(true)
4296                        .with_project_items(&[TestProjectItem::new(
4297                            project_entry_id,
4298                            &format!("{project_entry_id}.txt"),
4299                            cx,
4300                        )])
4301                })
4302            })
4303            .collect::<Vec<_>>();
4304        let item_2_3 = cx.add_view(window_id, |cx| {
4305            TestItem::new()
4306                .with_dirty(true)
4307                .with_singleton(false)
4308                .with_project_items(&[
4309                    single_entry_items[2].read(cx).project_items[0].clone(),
4310                    single_entry_items[3].read(cx).project_items[0].clone(),
4311                ])
4312        });
4313        let item_3_4 = cx.add_view(window_id, |cx| {
4314            TestItem::new()
4315                .with_dirty(true)
4316                .with_singleton(false)
4317                .with_project_items(&[
4318                    single_entry_items[3].read(cx).project_items[0].clone(),
4319                    single_entry_items[4].read(cx).project_items[0].clone(),
4320                ])
4321        });
4322
4323        // Create two panes that contain the following project entries:
4324        //   left pane:
4325        //     multi-entry items:   (2, 3)
4326        //     single-entry items:  0, 1, 2, 3, 4
4327        //   right pane:
4328        //     single-entry items:  1
4329        //     multi-entry items:   (3, 4)
4330        let left_pane = workspace.update(cx, |workspace, cx| {
4331            let left_pane = workspace.active_pane().clone();
4332            workspace.add_item(Box::new(item_2_3.clone()), cx);
4333            for item in single_entry_items {
4334                workspace.add_item(Box::new(item), cx);
4335            }
4336            left_pane.update(cx, |pane, cx| {
4337                pane.activate_item(2, true, true, cx);
4338            });
4339
4340            workspace
4341                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4342                .unwrap();
4343
4344            left_pane
4345        });
4346
4347        //Need to cause an effect flush in order to respect new focus
4348        workspace.update(cx, |workspace, cx| {
4349            workspace.add_item(Box::new(item_3_4.clone()), cx);
4350            cx.focus(&left_pane);
4351        });
4352
4353        // When closing all of the items in the left pane, we should be prompted twice:
4354        // once for project entry 0, and once for project entry 2. After those two
4355        // prompts, the task should complete.
4356
4357        let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true));
4358        cx.foreground().run_until_parked();
4359        left_pane.read_with(cx, |pane, cx| {
4360            assert_eq!(
4361                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4362                &[ProjectEntryId::from_proto(0)]
4363            );
4364        });
4365        cx.simulate_prompt_answer(window_id, 0);
4366
4367        cx.foreground().run_until_parked();
4368        left_pane.read_with(cx, |pane, cx| {
4369            assert_eq!(
4370                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4371                &[ProjectEntryId::from_proto(2)]
4372            );
4373        });
4374        cx.simulate_prompt_answer(window_id, 0);
4375
4376        cx.foreground().run_until_parked();
4377        close.await.unwrap();
4378        left_pane.read_with(cx, |pane, _| {
4379            assert_eq!(pane.items_len(), 0);
4380        });
4381    }
4382
4383    #[gpui::test]
4384    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4385        init_test(cx);
4386
4387        let fs = FakeFs::new(cx.background());
4388
4389        let project = Project::test(fs, [], cx).await;
4390        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4391        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4392
4393        let item = cx.add_view(window_id, |cx| {
4394            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4395        });
4396        let item_id = item.id();
4397        workspace.update(cx, |workspace, cx| {
4398            workspace.add_item(Box::new(item.clone()), cx);
4399        });
4400
4401        // Autosave on window change.
4402        item.update(cx, |item, cx| {
4403            cx.update_global(|settings: &mut SettingsStore, cx| {
4404                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4405                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4406                })
4407            });
4408            item.is_dirty = true;
4409        });
4410
4411        // Deactivating the window saves the file.
4412        cx.simulate_window_activation(None);
4413        deterministic.run_until_parked();
4414        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4415
4416        // Autosave on focus change.
4417        item.update(cx, |item, cx| {
4418            cx.focus_self();
4419            cx.update_global(|settings: &mut SettingsStore, cx| {
4420                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4421                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4422                })
4423            });
4424            item.is_dirty = true;
4425        });
4426
4427        // Blurring the item saves the file.
4428        item.update(cx, |_, cx| cx.blur());
4429        deterministic.run_until_parked();
4430        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4431
4432        // Deactivating the window still saves the file.
4433        cx.simulate_window_activation(Some(window_id));
4434        item.update(cx, |item, cx| {
4435            cx.focus_self();
4436            item.is_dirty = true;
4437        });
4438        cx.simulate_window_activation(None);
4439
4440        deterministic.run_until_parked();
4441        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4442
4443        // Autosave after delay.
4444        item.update(cx, |item, cx| {
4445            cx.update_global(|settings: &mut SettingsStore, cx| {
4446                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4447                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4448                })
4449            });
4450            item.is_dirty = true;
4451            cx.emit(TestItemEvent::Edit);
4452        });
4453
4454        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4455        deterministic.advance_clock(Duration::from_millis(250));
4456        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4457
4458        // After delay expires, the file is saved.
4459        deterministic.advance_clock(Duration::from_millis(250));
4460        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4461
4462        // Autosave on focus change, ensuring closing the tab counts as such.
4463        item.update(cx, |item, cx| {
4464            cx.update_global(|settings: &mut SettingsStore, cx| {
4465                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4466                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4467                })
4468            });
4469            item.is_dirty = true;
4470        });
4471
4472        pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
4473            .await
4474            .unwrap();
4475        assert!(!cx.has_pending_prompt(window_id));
4476        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4477
4478        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4479        workspace.update(cx, |workspace, cx| {
4480            workspace.add_item(Box::new(item.clone()), cx);
4481        });
4482        item.update(cx, |item, cx| {
4483            item.project_items[0].update(cx, |item, _| {
4484                item.entry_id = None;
4485            });
4486            item.is_dirty = true;
4487            cx.blur();
4488        });
4489        deterministic.run_until_parked();
4490        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4491
4492        // Ensure autosave is prevented for deleted files also when closing the buffer.
4493        let _close_items =
4494            pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
4495        deterministic.run_until_parked();
4496        assert!(cx.has_pending_prompt(window_id));
4497        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4498    }
4499
4500    #[gpui::test]
4501    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4502        init_test(cx);
4503
4504        let fs = FakeFs::new(cx.background());
4505
4506        let project = Project::test(fs, [], cx).await;
4507        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4508
4509        let item = cx.add_view(window_id, |cx| {
4510            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4511        });
4512        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4513        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4514        let toolbar_notify_count = Rc::new(RefCell::new(0));
4515
4516        workspace.update(cx, |workspace, cx| {
4517            workspace.add_item(Box::new(item.clone()), cx);
4518            let toolbar_notification_count = toolbar_notify_count.clone();
4519            cx.observe(&toolbar, move |_, _, _| {
4520                *toolbar_notification_count.borrow_mut() += 1
4521            })
4522            .detach();
4523        });
4524
4525        pane.read_with(cx, |pane, _| {
4526            assert!(!pane.can_navigate_backward());
4527            assert!(!pane.can_navigate_forward());
4528        });
4529
4530        item.update(cx, |item, cx| {
4531            item.set_state("one".to_string(), cx);
4532        });
4533
4534        // Toolbar must be notified to re-render the navigation buttons
4535        assert_eq!(*toolbar_notify_count.borrow(), 1);
4536
4537        pane.read_with(cx, |pane, _| {
4538            assert!(pane.can_navigate_backward());
4539            assert!(!pane.can_navigate_forward());
4540        });
4541
4542        workspace
4543            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4544            .await
4545            .unwrap();
4546
4547        assert_eq!(*toolbar_notify_count.borrow(), 3);
4548        pane.read_with(cx, |pane, _| {
4549            assert!(!pane.can_navigate_backward());
4550            assert!(pane.can_navigate_forward());
4551        });
4552    }
4553
4554    #[gpui::test]
4555    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4556        init_test(cx);
4557        let fs = FakeFs::new(cx.background());
4558
4559        let project = Project::test(fs, [], cx).await;
4560        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4561
4562        let panel = workspace.update(cx, |workspace, cx| {
4563            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4564            workspace.add_panel(panel.clone(), cx);
4565
4566            workspace
4567                .right_dock()
4568                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4569
4570            panel
4571        });
4572
4573        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4574        pane.update(cx, |pane, cx| {
4575            let item = cx.add_view(|_| TestItem::new());
4576            pane.add_item(Box::new(item), true, true, None, cx);
4577        });
4578
4579        // Transfer focus from center to panel
4580        workspace.update(cx, |workspace, cx| {
4581            workspace.toggle_panel_focus::<TestPanel>(cx);
4582        });
4583
4584        workspace.read_with(cx, |workspace, cx| {
4585            assert!(workspace.right_dock().read(cx).is_open());
4586            assert!(!panel.is_zoomed(cx));
4587            assert!(panel.has_focus(cx));
4588        });
4589
4590        // Transfer focus from panel to center
4591        workspace.update(cx, |workspace, cx| {
4592            workspace.toggle_panel_focus::<TestPanel>(cx);
4593        });
4594
4595        workspace.read_with(cx, |workspace, cx| {
4596            assert!(workspace.right_dock().read(cx).is_open());
4597            assert!(!panel.is_zoomed(cx));
4598            assert!(!panel.has_focus(cx));
4599        });
4600
4601        // Close the dock
4602        workspace.update(cx, |workspace, cx| {
4603            workspace.toggle_dock(DockPosition::Right, cx);
4604        });
4605
4606        workspace.read_with(cx, |workspace, cx| {
4607            assert!(!workspace.right_dock().read(cx).is_open());
4608            assert!(!panel.is_zoomed(cx));
4609            assert!(!panel.has_focus(cx));
4610        });
4611
4612        // Open the dock
4613        workspace.update(cx, |workspace, cx| {
4614            workspace.toggle_dock(DockPosition::Right, cx);
4615        });
4616
4617        workspace.read_with(cx, |workspace, cx| {
4618            assert!(workspace.right_dock().read(cx).is_open());
4619            assert!(!panel.is_zoomed(cx));
4620            assert!(panel.has_focus(cx));
4621        });
4622
4623        // Focus and zoom panel
4624        panel.update(cx, |panel, cx| {
4625            cx.focus_self();
4626            panel.set_zoomed(true, cx)
4627        });
4628
4629        workspace.read_with(cx, |workspace, cx| {
4630            assert!(workspace.right_dock().read(cx).is_open());
4631            assert!(panel.is_zoomed(cx));
4632            assert!(panel.has_focus(cx));
4633        });
4634
4635        // Transfer focus to the center closes the dock
4636        workspace.update(cx, |workspace, cx| {
4637            workspace.toggle_panel_focus::<TestPanel>(cx);
4638        });
4639
4640        workspace.read_with(cx, |workspace, cx| {
4641            assert!(!workspace.right_dock().read(cx).is_open());
4642            assert!(panel.is_zoomed(cx));
4643            assert!(!panel.has_focus(cx));
4644        });
4645
4646        // Transferring focus back to the panel keeps it zoomed
4647        workspace.update(cx, |workspace, cx| {
4648            workspace.toggle_panel_focus::<TestPanel>(cx);
4649        });
4650
4651        workspace.read_with(cx, |workspace, cx| {
4652            assert!(workspace.right_dock().read(cx).is_open());
4653            assert!(panel.is_zoomed(cx));
4654            assert!(panel.has_focus(cx));
4655        });
4656
4657        // Close the dock while it is zoomed
4658        workspace.update(cx, |workspace, cx| {
4659            workspace.toggle_dock(DockPosition::Right, cx)
4660        });
4661
4662        workspace.read_with(cx, |workspace, cx| {
4663            assert!(!workspace.right_dock().read(cx).is_open());
4664            assert!(panel.is_zoomed(cx));
4665            assert!(workspace.zoomed.is_none());
4666            assert!(!panel.has_focus(cx));
4667        });
4668
4669        // Opening the dock, when it's zoomed, retains focus
4670        workspace.update(cx, |workspace, cx| {
4671            workspace.toggle_dock(DockPosition::Right, cx)
4672        });
4673
4674        workspace.read_with(cx, |workspace, cx| {
4675            assert!(workspace.right_dock().read(cx).is_open());
4676            assert!(panel.is_zoomed(cx));
4677            assert!(workspace.zoomed.is_some());
4678            assert!(panel.has_focus(cx));
4679        });
4680
4681        // Unzoom and close the panel, zoom the active pane.
4682        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
4683        workspace.update(cx, |workspace, cx| {
4684            workspace.toggle_dock(DockPosition::Right, cx)
4685        });
4686        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
4687
4688        // Opening a dock unzooms the pane.
4689        workspace.update(cx, |workspace, cx| {
4690            workspace.toggle_dock(DockPosition::Right, cx)
4691        });
4692        workspace.read_with(cx, |workspace, cx| {
4693            let pane = pane.read(cx);
4694            assert!(!pane.is_zoomed());
4695            assert!(!pane.has_focus());
4696            assert!(workspace.right_dock().read(cx).is_open());
4697            assert!(workspace.zoomed.is_none());
4698        });
4699    }
4700
4701    #[gpui::test]
4702    async fn test_panels(cx: &mut gpui::TestAppContext) {
4703        init_test(cx);
4704        let fs = FakeFs::new(cx.background());
4705
4706        let project = Project::test(fs, [], cx).await;
4707        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4708
4709        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4710            // Add panel_1 on the left, panel_2 on the right.
4711            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4712            workspace.add_panel(panel_1.clone(), cx);
4713            workspace
4714                .left_dock()
4715                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4716            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4717            workspace.add_panel(panel_2.clone(), cx);
4718            workspace
4719                .right_dock()
4720                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4721
4722            let left_dock = workspace.left_dock();
4723            assert_eq!(
4724                left_dock.read(cx).visible_panel().unwrap().id(),
4725                panel_1.id()
4726            );
4727            assert_eq!(
4728                left_dock.read(cx).active_panel_size(cx).unwrap(),
4729                panel_1.size(cx)
4730            );
4731
4732            left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx));
4733            assert_eq!(
4734                workspace
4735                    .right_dock()
4736                    .read(cx)
4737                    .visible_panel()
4738                    .unwrap()
4739                    .id(),
4740                panel_2.id()
4741            );
4742
4743            (panel_1, panel_2)
4744        });
4745
4746        // Move panel_1 to the right
4747        panel_1.update(cx, |panel_1, cx| {
4748            panel_1.set_position(DockPosition::Right, cx)
4749        });
4750
4751        workspace.update(cx, |workspace, cx| {
4752            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4753            // Since it was the only panel on the left, the left dock should now be closed.
4754            assert!(!workspace.left_dock().read(cx).is_open());
4755            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
4756            let right_dock = workspace.right_dock();
4757            assert_eq!(
4758                right_dock.read(cx).visible_panel().unwrap().id(),
4759                panel_1.id()
4760            );
4761            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4762
4763            // Now we move panel_2Β to the left
4764            panel_2.set_position(DockPosition::Left, cx);
4765        });
4766
4767        workspace.update(cx, |workspace, cx| {
4768            // Since panel_2 was not visible on the right, we don't open the left dock.
4769            assert!(!workspace.left_dock().read(cx).is_open());
4770            // And the right dock is unaffected in it's displaying of panel_1
4771            assert!(workspace.right_dock().read(cx).is_open());
4772            assert_eq!(
4773                workspace
4774                    .right_dock()
4775                    .read(cx)
4776                    .visible_panel()
4777                    .unwrap()
4778                    .id(),
4779                panel_1.id()
4780            );
4781        });
4782
4783        // Move panel_1 back to the left
4784        panel_1.update(cx, |panel_1, cx| {
4785            panel_1.set_position(DockPosition::Left, cx)
4786        });
4787
4788        workspace.update(cx, |workspace, cx| {
4789            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4790            let left_dock = workspace.left_dock();
4791            assert!(left_dock.read(cx).is_open());
4792            assert_eq!(
4793                left_dock.read(cx).visible_panel().unwrap().id(),
4794                panel_1.id()
4795            );
4796            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4797            // And right the dock should be closed as it no longer has any panels.
4798            assert!(!workspace.right_dock().read(cx).is_open());
4799
4800            // Now we move panel_1 to the bottom
4801            panel_1.set_position(DockPosition::Bottom, cx);
4802        });
4803
4804        workspace.update(cx, |workspace, cx| {
4805            // Since panel_1 was visible on the left, we close the left dock.
4806            assert!(!workspace.left_dock().read(cx).is_open());
4807            // The bottom dock is sized based on the panel's default size,
4808            // since the panel orientation changed from vertical to horizontal.
4809            let bottom_dock = workspace.bottom_dock();
4810            assert_eq!(
4811                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
4812                panel_1.size(cx),
4813            );
4814            // Close bottom dock and move panel_1 back to the left.
4815            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
4816            panel_1.set_position(DockPosition::Left, cx);
4817        });
4818
4819        // Emit activated event on panel 1
4820        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
4821
4822        // Now the left dock is open and panel_1 is active and focused.
4823        workspace.read_with(cx, |workspace, cx| {
4824            let left_dock = workspace.left_dock();
4825            assert!(left_dock.read(cx).is_open());
4826            assert_eq!(
4827                left_dock.read(cx).visible_panel().unwrap().id(),
4828                panel_1.id()
4829            );
4830            assert!(panel_1.is_focused(cx));
4831        });
4832
4833        // Emit closed event on panel 2, which is not active
4834        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4835
4836        // Wo don't close the left dock, because panel_2 wasn't the active panel
4837        workspace.read_with(cx, |workspace, cx| {
4838            let left_dock = workspace.left_dock();
4839            assert!(left_dock.read(cx).is_open());
4840            assert_eq!(
4841                left_dock.read(cx).visible_panel().unwrap().id(),
4842                panel_1.id()
4843            );
4844        });
4845
4846        // Emitting a ZoomIn event shows the panel as zoomed.
4847        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
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::Left));
4851        });
4852
4853        // Move panel to another dock while it is zoomed
4854        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
4855        workspace.read_with(cx, |workspace, _| {
4856            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4857            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4858        });
4859
4860        // If focus is transferred to another view that's not a panel or another pane, we still show
4861        // the panel as zoomed.
4862        let focus_receiver = cx.add_view(window_id, |_| EmptyView);
4863        focus_receiver.update(cx, |_, cx| cx.focus_self());
4864        workspace.read_with(cx, |workspace, _| {
4865            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4866            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4867        });
4868
4869        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
4870        workspace.update(cx, |_, cx| cx.focus_self());
4871        workspace.read_with(cx, |workspace, _| {
4872            assert_eq!(workspace.zoomed, None);
4873            assert_eq!(workspace.zoomed_position, None);
4874        });
4875
4876        // If focus is transferred again to another view that's not a panel or a pane, we won't
4877        // show the panel as zoomed because it wasn't zoomed before.
4878        focus_receiver.update(cx, |_, cx| cx.focus_self());
4879        workspace.read_with(cx, |workspace, _| {
4880            assert_eq!(workspace.zoomed, None);
4881            assert_eq!(workspace.zoomed_position, None);
4882        });
4883
4884        // When focus is transferred back to the panel, it is zoomed again.
4885        panel_1.update(cx, |_, cx| cx.focus_self());
4886        workspace.read_with(cx, |workspace, _| {
4887            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4888            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4889        });
4890
4891        // Emitting a ZoomOut event unzooms the panel.
4892        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
4893        workspace.read_with(cx, |workspace, _| {
4894            assert_eq!(workspace.zoomed, None);
4895            assert_eq!(workspace.zoomed_position, None);
4896        });
4897
4898        // Emit closed event on panel 1, which is active
4899        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4900
4901        // Now the left dock is closed, because panel_1 was the active panel
4902        workspace.read_with(cx, |workspace, cx| {
4903            let right_dock = workspace.right_dock();
4904            assert!(!right_dock.read(cx).is_open());
4905        });
4906    }
4907
4908    pub fn init_test(cx: &mut TestAppContext) {
4909        cx.foreground().forbid_parking();
4910        cx.update(|cx| {
4911            cx.set_global(SettingsStore::test(cx));
4912            theme::init((), cx);
4913            language::init(cx);
4914            crate::init_settings(cx);
4915            Project::init_settings(cx);
4916        });
4917    }
4918}