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