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::new::<TitleBar, _>(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::new::<DisconnectedOverlay, _>(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_radii: (2.0).into(),
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                                        // Prevent clicks within the modal from falling
3764                                        // through to the rest of the workspace.
3765                                        enum ModalBackground {}
3766                                        MouseEventHandler::new::<ModalBackground, _>(
3767                                            0,
3768                                            cx,
3769                                            |_, cx| ChildView::new(modal.view.as_any(), cx),
3770                                        )
3771                                        .on_click(MouseButton::Left, |_, _, _| {})
3772                                        .contained()
3773                                        .with_style(theme.workspace.modal)
3774                                        .aligned()
3775                                        .top()
3776                                    }))
3777                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3778                            ))
3779                            .flex(1.0, true),
3780                    )
3781                    .with_child(ChildView::new(&self.status_bar, cx))
3782                    .contained()
3783                    .with_background_color(theme.workspace.background),
3784            )
3785            .with_children(DragAndDrop::render(cx))
3786            .with_children(self.render_disconnected_overlay(cx))
3787            .into_any_named("workspace")
3788    }
3789
3790    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3791        if cx.is_self_focused() {
3792            cx.focus(&self.active_pane);
3793        }
3794    }
3795}
3796
3797impl ViewId {
3798    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3799        Ok(Self {
3800            creator: message
3801                .creator
3802                .ok_or_else(|| anyhow!("creator is missing"))?,
3803            id: message.id,
3804        })
3805    }
3806
3807    pub(crate) fn to_proto(&self) -> proto::ViewId {
3808        proto::ViewId {
3809            creator: Some(self.creator),
3810            id: self.id,
3811        }
3812    }
3813}
3814
3815pub trait WorkspaceHandle {
3816    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3817}
3818
3819impl WorkspaceHandle for ViewHandle<Workspace> {
3820    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3821        self.read(cx)
3822            .worktrees(cx)
3823            .flat_map(|worktree| {
3824                let worktree_id = worktree.read(cx).id();
3825                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3826                    worktree_id,
3827                    path: f.path.clone(),
3828                })
3829            })
3830            .collect::<Vec<_>>()
3831    }
3832}
3833
3834impl std::fmt::Debug for OpenPaths {
3835    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3836        f.debug_struct("OpenPaths")
3837            .field("paths", &self.paths)
3838            .finish()
3839    }
3840}
3841
3842pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
3843
3844pub fn activate_workspace_for_project(
3845    cx: &mut AsyncAppContext,
3846    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3847) -> Option<WeakViewHandle<Workspace>> {
3848    for window in cx.windows() {
3849        let handle = window
3850            .update(cx, |cx| {
3851                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3852                    let project = workspace_handle.read(cx).project.clone();
3853                    if project.update(cx, &predicate) {
3854                        cx.activate_window();
3855                        return Some(workspace_handle.clone());
3856                    }
3857                }
3858                None
3859            })
3860            .flatten();
3861
3862        if let Some(handle) = handle {
3863            return Some(handle.downgrade());
3864        }
3865    }
3866    None
3867}
3868
3869pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3870    DB.last_workspace().await.log_err().flatten()
3871}
3872
3873#[allow(clippy::type_complexity)]
3874pub fn open_paths(
3875    abs_paths: &[PathBuf],
3876    app_state: &Arc<AppState>,
3877    requesting_window: Option<WindowHandle<Workspace>>,
3878    cx: &mut AppContext,
3879) -> Task<
3880    Result<(
3881        WeakViewHandle<Workspace>,
3882        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3883    )>,
3884> {
3885    let app_state = app_state.clone();
3886    let abs_paths = abs_paths.to_vec();
3887    cx.spawn(|mut cx| async move {
3888        // Open paths in existing workspace if possible
3889        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3890            project.contains_paths(&abs_paths, cx)
3891        });
3892
3893        if let Some(existing) = existing {
3894            Ok((
3895                existing.clone(),
3896                existing
3897                    .update(&mut cx, |workspace, cx| {
3898                        workspace.open_paths(abs_paths, true, cx)
3899                    })?
3900                    .await,
3901            ))
3902        } else {
3903            Ok(cx
3904                .update(|cx| {
3905                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
3906                })
3907                .await)
3908        }
3909    })
3910}
3911
3912pub fn open_new(
3913    app_state: &Arc<AppState>,
3914    cx: &mut AppContext,
3915    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3916) -> Task<()> {
3917    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3918    cx.spawn(|mut cx| async move {
3919        let (workspace, opened_paths) = task.await;
3920
3921        workspace
3922            .update(&mut cx, |workspace, cx| {
3923                if opened_paths.is_empty() {
3924                    init(workspace, cx)
3925                }
3926            })
3927            .log_err();
3928    })
3929}
3930
3931pub fn create_and_open_local_file(
3932    path: &'static Path,
3933    cx: &mut ViewContext<Workspace>,
3934    default_content: impl 'static + Send + FnOnce() -> Rope,
3935) -> Task<Result<Box<dyn ItemHandle>>> {
3936    cx.spawn(|workspace, mut cx| async move {
3937        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
3938        if !fs.is_file(path).await {
3939            fs.create_file(path, Default::default()).await?;
3940            fs.save(path, &default_content(), Default::default())
3941                .await?;
3942        }
3943
3944        let mut items = workspace
3945            .update(&mut cx, |workspace, cx| {
3946                workspace.with_local_workspace(cx, |workspace, cx| {
3947                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
3948                })
3949            })?
3950            .await?
3951            .await;
3952
3953        let item = items.pop().flatten();
3954        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
3955    })
3956}
3957
3958pub fn join_remote_project(
3959    project_id: u64,
3960    follow_user_id: u64,
3961    app_state: Arc<AppState>,
3962    cx: &mut AppContext,
3963) -> Task<Result<()>> {
3964    cx.spawn(|mut cx| async move {
3965        let existing_workspace = cx
3966            .windows()
3967            .into_iter()
3968            .find_map(|window| {
3969                window.downcast::<Workspace>().and_then(|window| {
3970                    window.read_root_with(&cx, |workspace, cx| {
3971                        if workspace.project().read(cx).remote_id() == Some(project_id) {
3972                            Some(cx.handle().downgrade())
3973                        } else {
3974                            None
3975                        }
3976                    })
3977                })
3978            })
3979            .flatten();
3980
3981        let workspace = if let Some(existing_workspace) = existing_workspace {
3982            existing_workspace
3983        } else {
3984            let active_call = cx.read(ActiveCall::global);
3985            let room = active_call
3986                .read_with(&cx, |call, _| call.room().cloned())
3987                .ok_or_else(|| anyhow!("not in a call"))?;
3988            let project = room
3989                .update(&mut cx, |room, cx| {
3990                    room.join_project(
3991                        project_id,
3992                        app_state.languages.clone(),
3993                        app_state.fs.clone(),
3994                        cx,
3995                    )
3996                })
3997                .await?;
3998
3999            let window_bounds_override = window_bounds_env_override(&cx);
4000            let window = cx.add_window(
4001                (app_state.build_window_options)(
4002                    window_bounds_override,
4003                    None,
4004                    cx.platform().as_ref(),
4005                ),
4006                |cx| Workspace::new(0, project, app_state.clone(), cx),
4007            );
4008            let workspace = window.root(&cx).unwrap();
4009            (app_state.initialize_workspace)(
4010                workspace.downgrade(),
4011                false,
4012                app_state.clone(),
4013                cx.clone(),
4014            )
4015            .await
4016            .log_err();
4017
4018            workspace.downgrade()
4019        };
4020
4021        workspace.window().activate(&mut cx);
4022        cx.platform().activate(true);
4023
4024        workspace.update(&mut cx, |workspace, cx| {
4025            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4026                let follow_peer_id = room
4027                    .read(cx)
4028                    .remote_participants()
4029                    .iter()
4030                    .find(|(_, participant)| participant.user.id == follow_user_id)
4031                    .map(|(_, p)| p.peer_id)
4032                    .or_else(|| {
4033                        // If we couldn't follow the given user, follow the host instead.
4034                        let collaborator = workspace
4035                            .project()
4036                            .read(cx)
4037                            .collaborators()
4038                            .values()
4039                            .find(|collaborator| collaborator.replica_id == 0)?;
4040                        Some(collaborator.peer_id)
4041                    });
4042
4043                if let Some(follow_peer_id) = follow_peer_id {
4044                    if !workspace.is_being_followed(follow_peer_id) {
4045                        workspace
4046                            .toggle_follow(follow_peer_id, cx)
4047                            .map(|follow| follow.detach_and_log_err(cx));
4048                    }
4049                }
4050            }
4051        })?;
4052
4053        anyhow::Ok(())
4054    })
4055}
4056
4057pub fn restart(_: &Restart, cx: &mut AppContext) {
4058    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4059    cx.spawn(|mut cx| async move {
4060        let mut workspace_windows = cx
4061            .windows()
4062            .into_iter()
4063            .filter_map(|window| window.downcast::<Workspace>())
4064            .collect::<Vec<_>>();
4065
4066        // If multiple windows have unsaved changes, and need a save prompt,
4067        // prompt in the active window before switching to a different window.
4068        workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4069
4070        if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4071            let answer = window.prompt(
4072                PromptLevel::Info,
4073                "Are you sure you want to restart?",
4074                &["Restart", "Cancel"],
4075                &mut cx,
4076            );
4077
4078            if let Some(mut answer) = answer {
4079                let answer = answer.next().await;
4080                if answer != Some(0) {
4081                    return Ok(());
4082                }
4083            }
4084        }
4085
4086        // If the user cancels any save prompt, then keep the app open.
4087        for window in workspace_windows {
4088            if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4089                workspace.prepare_to_close(true, cx)
4090            }) {
4091                if !should_close.await? {
4092                    return Ok(());
4093                }
4094            }
4095        }
4096        cx.platform().restart();
4097        anyhow::Ok(())
4098    })
4099    .detach_and_log_err(cx);
4100}
4101
4102fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4103    let mut parts = value.split(',');
4104    let width: usize = parts.next()?.parse().ok()?;
4105    let height: usize = parts.next()?.parse().ok()?;
4106    Some(vec2f(width as f32, height as f32))
4107}
4108
4109#[cfg(test)]
4110mod tests {
4111    use super::*;
4112    use crate::{
4113        dock::test::{TestPanel, TestPanelEvent},
4114        item::test::{TestItem, TestItemEvent, TestProjectItem},
4115    };
4116    use fs::FakeFs;
4117    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4118    use project::{Project, ProjectEntryId};
4119    use serde_json::json;
4120    use settings::SettingsStore;
4121    use std::{cell::RefCell, rc::Rc};
4122
4123    #[gpui::test]
4124    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4125        init_test(cx);
4126
4127        let fs = FakeFs::new(cx.background());
4128        let project = Project::test(fs, [], cx).await;
4129        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4130        let workspace = window.root(cx);
4131
4132        // Adding an item with no ambiguity renders the tab without detail.
4133        let item1 = window.add_view(cx, |_| {
4134            let mut item = TestItem::new();
4135            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4136            item
4137        });
4138        workspace.update(cx, |workspace, cx| {
4139            workspace.add_item(Box::new(item1.clone()), cx);
4140        });
4141        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4142
4143        // Adding an item that creates ambiguity increases the level of detail on
4144        // both tabs.
4145        let item2 = window.add_view(cx, |_| {
4146            let mut item = TestItem::new();
4147            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4148            item
4149        });
4150        workspace.update(cx, |workspace, cx| {
4151            workspace.add_item(Box::new(item2.clone()), cx);
4152        });
4153        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4154        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4155
4156        // Adding an item that creates ambiguity increases the level of detail only
4157        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4158        // we stop at the highest detail available.
4159        let item3 = window.add_view(cx, |_| {
4160            let mut item = TestItem::new();
4161            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4162            item
4163        });
4164        workspace.update(cx, |workspace, cx| {
4165            workspace.add_item(Box::new(item3.clone()), cx);
4166        });
4167        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4168        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4169        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4170    }
4171
4172    #[gpui::test]
4173    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4174        init_test(cx);
4175
4176        let fs = FakeFs::new(cx.background());
4177        fs.insert_tree(
4178            "/root1",
4179            json!({
4180                "one.txt": "",
4181                "two.txt": "",
4182            }),
4183        )
4184        .await;
4185        fs.insert_tree(
4186            "/root2",
4187            json!({
4188                "three.txt": "",
4189            }),
4190        )
4191        .await;
4192
4193        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4194        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4195        let workspace = window.root(cx);
4196        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4197        let worktree_id = project.read_with(cx, |project, cx| {
4198            project.worktrees(cx).next().unwrap().read(cx).id()
4199        });
4200
4201        let item1 = window.add_view(cx, |cx| {
4202            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4203        });
4204        let item2 = window.add_view(cx, |cx| {
4205            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4206        });
4207
4208        // Add an item to an empty pane
4209        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4210        project.read_with(cx, |project, cx| {
4211            assert_eq!(
4212                project.active_entry(),
4213                project
4214                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4215                    .map(|e| e.id)
4216            );
4217        });
4218        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4219
4220        // Add a second item to a non-empty pane
4221        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4222        assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
4223        project.read_with(cx, |project, cx| {
4224            assert_eq!(
4225                project.active_entry(),
4226                project
4227                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4228                    .map(|e| e.id)
4229            );
4230        });
4231
4232        // Close the active item
4233        pane.update(cx, |pane, cx| {
4234            pane.close_active_item(&Default::default(), cx).unwrap()
4235        })
4236        .await
4237        .unwrap();
4238        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4239        project.read_with(cx, |project, cx| {
4240            assert_eq!(
4241                project.active_entry(),
4242                project
4243                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4244                    .map(|e| e.id)
4245            );
4246        });
4247
4248        // Add a project folder
4249        project
4250            .update(cx, |project, cx| {
4251                project.find_or_create_local_worktree("/root2", true, cx)
4252            })
4253            .await
4254            .unwrap();
4255        assert_eq!(
4256            window.current_title(cx).as_deref(),
4257            Some("one.txt β€” root1, root2")
4258        );
4259
4260        // Remove a project folder
4261        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4262        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
4263    }
4264
4265    #[gpui::test]
4266    async fn test_close_window(cx: &mut TestAppContext) {
4267        init_test(cx);
4268
4269        let fs = FakeFs::new(cx.background());
4270        fs.insert_tree("/root", json!({ "one": "" })).await;
4271
4272        let project = Project::test(fs, ["root".as_ref()], cx).await;
4273        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4274        let workspace = window.root(cx);
4275
4276        // When there are no dirty items, there's nothing to do.
4277        let item1 = window.add_view(cx, |_| TestItem::new());
4278        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4279        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4280        assert!(task.await.unwrap());
4281
4282        // When there are dirty untitled items, prompt to save each one. If the user
4283        // cancels any prompt, then abort.
4284        let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
4285        let item3 = window.add_view(cx, |cx| {
4286            TestItem::new()
4287                .with_dirty(true)
4288                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4289        });
4290        workspace.update(cx, |w, cx| {
4291            w.add_item(Box::new(item2.clone()), cx);
4292            w.add_item(Box::new(item3.clone()), cx);
4293        });
4294        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4295        cx.foreground().run_until_parked();
4296        window.simulate_prompt_answer(2, cx); // cancel
4297        cx.foreground().run_until_parked();
4298        assert!(!window.has_pending_prompt(cx));
4299        assert!(!task.await.unwrap());
4300    }
4301
4302    #[gpui::test]
4303    async fn test_close_pane_items(cx: &mut TestAppContext) {
4304        init_test(cx);
4305
4306        let fs = FakeFs::new(cx.background());
4307
4308        let project = Project::test(fs, None, cx).await;
4309        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4310        let workspace = window.root(cx);
4311
4312        let item1 = window.add_view(cx, |cx| {
4313            TestItem::new()
4314                .with_dirty(true)
4315                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4316        });
4317        let item2 = window.add_view(cx, |cx| {
4318            TestItem::new()
4319                .with_dirty(true)
4320                .with_conflict(true)
4321                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4322        });
4323        let item3 = window.add_view(cx, |cx| {
4324            TestItem::new()
4325                .with_dirty(true)
4326                .with_conflict(true)
4327                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4328        });
4329        let item4 = window.add_view(cx, |cx| {
4330            TestItem::new()
4331                .with_dirty(true)
4332                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4333        });
4334        let pane = workspace.update(cx, |workspace, cx| {
4335            workspace.add_item(Box::new(item1.clone()), cx);
4336            workspace.add_item(Box::new(item2.clone()), cx);
4337            workspace.add_item(Box::new(item3.clone()), cx);
4338            workspace.add_item(Box::new(item4.clone()), cx);
4339            workspace.active_pane().clone()
4340        });
4341
4342        let close_items = pane.update(cx, |pane, cx| {
4343            pane.activate_item(1, true, true, cx);
4344            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4345            let item1_id = item1.id();
4346            let item3_id = item3.id();
4347            let item4_id = item4.id();
4348            pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id))
4349        });
4350        cx.foreground().run_until_parked();
4351
4352        // There's a prompt to save item 1.
4353        pane.read_with(cx, |pane, _| {
4354            assert_eq!(pane.items_len(), 4);
4355            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4356        });
4357        assert!(window.has_pending_prompt(cx));
4358
4359        // Confirm saving item 1.
4360        window.simulate_prompt_answer(0, cx);
4361        cx.foreground().run_until_parked();
4362
4363        // Item 1 is saved. There's a prompt to save item 3.
4364        pane.read_with(cx, |pane, cx| {
4365            assert_eq!(item1.read(cx).save_count, 1);
4366            assert_eq!(item1.read(cx).save_as_count, 0);
4367            assert_eq!(item1.read(cx).reload_count, 0);
4368            assert_eq!(pane.items_len(), 3);
4369            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4370        });
4371        assert!(window.has_pending_prompt(cx));
4372
4373        // Cancel saving item 3.
4374        window.simulate_prompt_answer(1, cx);
4375        cx.foreground().run_until_parked();
4376
4377        // Item 3 is reloaded. There's a prompt to save item 4.
4378        pane.read_with(cx, |pane, cx| {
4379            assert_eq!(item3.read(cx).save_count, 0);
4380            assert_eq!(item3.read(cx).save_as_count, 0);
4381            assert_eq!(item3.read(cx).reload_count, 1);
4382            assert_eq!(pane.items_len(), 2);
4383            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4384        });
4385        assert!(window.has_pending_prompt(cx));
4386
4387        // Confirm saving item 4.
4388        window.simulate_prompt_answer(0, cx);
4389        cx.foreground().run_until_parked();
4390
4391        // There's a prompt for a path for item 4.
4392        cx.simulate_new_path_selection(|_| Some(Default::default()));
4393        close_items.await.unwrap();
4394
4395        // The requested items are closed.
4396        pane.read_with(cx, |pane, cx| {
4397            assert_eq!(item4.read(cx).save_count, 0);
4398            assert_eq!(item4.read(cx).save_as_count, 1);
4399            assert_eq!(item4.read(cx).reload_count, 0);
4400            assert_eq!(pane.items_len(), 1);
4401            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4402        });
4403    }
4404
4405    #[gpui::test]
4406    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4407        init_test(cx);
4408
4409        let fs = FakeFs::new(cx.background());
4410
4411        let project = Project::test(fs, [], cx).await;
4412        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4413        let workspace = window.root(cx);
4414
4415        // Create several workspace items with single project entries, and two
4416        // workspace items with multiple project entries.
4417        let single_entry_items = (0..=4)
4418            .map(|project_entry_id| {
4419                window.add_view(cx, |cx| {
4420                    TestItem::new()
4421                        .with_dirty(true)
4422                        .with_project_items(&[TestProjectItem::new(
4423                            project_entry_id,
4424                            &format!("{project_entry_id}.txt"),
4425                            cx,
4426                        )])
4427                })
4428            })
4429            .collect::<Vec<_>>();
4430        let item_2_3 = window.add_view(cx, |cx| {
4431            TestItem::new()
4432                .with_dirty(true)
4433                .with_singleton(false)
4434                .with_project_items(&[
4435                    single_entry_items[2].read(cx).project_items[0].clone(),
4436                    single_entry_items[3].read(cx).project_items[0].clone(),
4437                ])
4438        });
4439        let item_3_4 = window.add_view(cx, |cx| {
4440            TestItem::new()
4441                .with_dirty(true)
4442                .with_singleton(false)
4443                .with_project_items(&[
4444                    single_entry_items[3].read(cx).project_items[0].clone(),
4445                    single_entry_items[4].read(cx).project_items[0].clone(),
4446                ])
4447        });
4448
4449        // Create two panes that contain the following project entries:
4450        //   left pane:
4451        //     multi-entry items:   (2, 3)
4452        //     single-entry items:  0, 1, 2, 3, 4
4453        //   right pane:
4454        //     single-entry items:  1
4455        //     multi-entry items:   (3, 4)
4456        let left_pane = workspace.update(cx, |workspace, cx| {
4457            let left_pane = workspace.active_pane().clone();
4458            workspace.add_item(Box::new(item_2_3.clone()), cx);
4459            for item in single_entry_items {
4460                workspace.add_item(Box::new(item), cx);
4461            }
4462            left_pane.update(cx, |pane, cx| {
4463                pane.activate_item(2, true, true, cx);
4464            });
4465
4466            workspace
4467                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4468                .unwrap();
4469
4470            left_pane
4471        });
4472
4473        //Need to cause an effect flush in order to respect new focus
4474        workspace.update(cx, |workspace, cx| {
4475            workspace.add_item(Box::new(item_3_4.clone()), cx);
4476            cx.focus(&left_pane);
4477        });
4478
4479        // When closing all of the items in the left pane, we should be prompted twice:
4480        // once for project entry 0, and once for project entry 2. After those two
4481        // prompts, the task should complete.
4482
4483        let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true));
4484        cx.foreground().run_until_parked();
4485        left_pane.read_with(cx, |pane, cx| {
4486            assert_eq!(
4487                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4488                &[ProjectEntryId::from_proto(0)]
4489            );
4490        });
4491        window.simulate_prompt_answer(0, cx);
4492
4493        cx.foreground().run_until_parked();
4494        left_pane.read_with(cx, |pane, cx| {
4495            assert_eq!(
4496                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4497                &[ProjectEntryId::from_proto(2)]
4498            );
4499        });
4500        window.simulate_prompt_answer(0, cx);
4501
4502        cx.foreground().run_until_parked();
4503        close.await.unwrap();
4504        left_pane.read_with(cx, |pane, _| {
4505            assert_eq!(pane.items_len(), 0);
4506        });
4507    }
4508
4509    #[gpui::test]
4510    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4511        init_test(cx);
4512
4513        let fs = FakeFs::new(cx.background());
4514
4515        let project = Project::test(fs, [], cx).await;
4516        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4517        let workspace = window.root(cx);
4518        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4519
4520        let item = window.add_view(cx, |cx| {
4521            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4522        });
4523        let item_id = item.id();
4524        workspace.update(cx, |workspace, cx| {
4525            workspace.add_item(Box::new(item.clone()), cx);
4526        });
4527
4528        // Autosave on window change.
4529        item.update(cx, |item, cx| {
4530            cx.update_global(|settings: &mut SettingsStore, cx| {
4531                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4532                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4533                })
4534            });
4535            item.is_dirty = true;
4536        });
4537
4538        // Deactivating the window saves the file.
4539        window.simulate_deactivation(cx);
4540        deterministic.run_until_parked();
4541        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4542
4543        // Autosave on focus change.
4544        item.update(cx, |item, cx| {
4545            cx.focus_self();
4546            cx.update_global(|settings: &mut SettingsStore, cx| {
4547                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4548                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4549                })
4550            });
4551            item.is_dirty = true;
4552        });
4553
4554        // Blurring the item saves the file.
4555        item.update(cx, |_, cx| cx.blur());
4556        deterministic.run_until_parked();
4557        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4558
4559        // Deactivating the window still saves the file.
4560        window.simulate_activation(cx);
4561        item.update(cx, |item, cx| {
4562            cx.focus_self();
4563            item.is_dirty = true;
4564        });
4565        window.simulate_deactivation(cx);
4566
4567        deterministic.run_until_parked();
4568        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4569
4570        // Autosave after delay.
4571        item.update(cx, |item, cx| {
4572            cx.update_global(|settings: &mut SettingsStore, cx| {
4573                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4574                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4575                })
4576            });
4577            item.is_dirty = true;
4578            cx.emit(TestItemEvent::Edit);
4579        });
4580
4581        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4582        deterministic.advance_clock(Duration::from_millis(250));
4583        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4584
4585        // After delay expires, the file is saved.
4586        deterministic.advance_clock(Duration::from_millis(250));
4587        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4588
4589        // Autosave on focus change, ensuring closing the tab counts as such.
4590        item.update(cx, |item, cx| {
4591            cx.update_global(|settings: &mut SettingsStore, cx| {
4592                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4593                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4594                })
4595            });
4596            item.is_dirty = true;
4597        });
4598
4599        pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
4600            .await
4601            .unwrap();
4602        assert!(!window.has_pending_prompt(cx));
4603        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4604
4605        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4606        workspace.update(cx, |workspace, cx| {
4607            workspace.add_item(Box::new(item.clone()), cx);
4608        });
4609        item.update(cx, |item, cx| {
4610            item.project_items[0].update(cx, |item, _| {
4611                item.entry_id = None;
4612            });
4613            item.is_dirty = true;
4614            cx.blur();
4615        });
4616        deterministic.run_until_parked();
4617        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4618
4619        // Ensure autosave is prevented for deleted files also when closing the buffer.
4620        let _close_items =
4621            pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
4622        deterministic.run_until_parked();
4623        assert!(window.has_pending_prompt(cx));
4624        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4625    }
4626
4627    #[gpui::test]
4628    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4629        init_test(cx);
4630
4631        let fs = FakeFs::new(cx.background());
4632
4633        let project = Project::test(fs, [], cx).await;
4634        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4635        let workspace = window.root(cx);
4636
4637        let item = window.add_view(cx, |cx| {
4638            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4639        });
4640        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4641        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4642        let toolbar_notify_count = Rc::new(RefCell::new(0));
4643
4644        workspace.update(cx, |workspace, cx| {
4645            workspace.add_item(Box::new(item.clone()), cx);
4646            let toolbar_notification_count = toolbar_notify_count.clone();
4647            cx.observe(&toolbar, move |_, _, _| {
4648                *toolbar_notification_count.borrow_mut() += 1
4649            })
4650            .detach();
4651        });
4652
4653        pane.read_with(cx, |pane, _| {
4654            assert!(!pane.can_navigate_backward());
4655            assert!(!pane.can_navigate_forward());
4656        });
4657
4658        item.update(cx, |item, cx| {
4659            item.set_state("one".to_string(), cx);
4660        });
4661
4662        // Toolbar must be notified to re-render the navigation buttons
4663        assert_eq!(*toolbar_notify_count.borrow(), 1);
4664
4665        pane.read_with(cx, |pane, _| {
4666            assert!(pane.can_navigate_backward());
4667            assert!(!pane.can_navigate_forward());
4668        });
4669
4670        workspace
4671            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4672            .await
4673            .unwrap();
4674
4675        assert_eq!(*toolbar_notify_count.borrow(), 3);
4676        pane.read_with(cx, |pane, _| {
4677            assert!(!pane.can_navigate_backward());
4678            assert!(pane.can_navigate_forward());
4679        });
4680    }
4681
4682    #[gpui::test]
4683    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4684        init_test(cx);
4685        let fs = FakeFs::new(cx.background());
4686
4687        let project = Project::test(fs, [], cx).await;
4688        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4689        let workspace = window.root(cx);
4690
4691        let panel = workspace.update(cx, |workspace, cx| {
4692            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4693            workspace.add_panel(panel.clone(), cx);
4694
4695            workspace
4696                .right_dock()
4697                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4698
4699            panel
4700        });
4701
4702        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4703        pane.update(cx, |pane, cx| {
4704            let item = cx.add_view(|_| TestItem::new());
4705            pane.add_item(Box::new(item), true, true, None, cx);
4706        });
4707
4708        // Transfer focus from center to panel
4709        workspace.update(cx, |workspace, cx| {
4710            workspace.toggle_panel_focus::<TestPanel>(cx);
4711        });
4712
4713        workspace.read_with(cx, |workspace, cx| {
4714            assert!(workspace.right_dock().read(cx).is_open());
4715            assert!(!panel.is_zoomed(cx));
4716            assert!(panel.has_focus(cx));
4717        });
4718
4719        // Transfer focus from panel to center
4720        workspace.update(cx, |workspace, cx| {
4721            workspace.toggle_panel_focus::<TestPanel>(cx);
4722        });
4723
4724        workspace.read_with(cx, |workspace, cx| {
4725            assert!(workspace.right_dock().read(cx).is_open());
4726            assert!(!panel.is_zoomed(cx));
4727            assert!(!panel.has_focus(cx));
4728        });
4729
4730        // Close the dock
4731        workspace.update(cx, |workspace, cx| {
4732            workspace.toggle_dock(DockPosition::Right, cx);
4733        });
4734
4735        workspace.read_with(cx, |workspace, cx| {
4736            assert!(!workspace.right_dock().read(cx).is_open());
4737            assert!(!panel.is_zoomed(cx));
4738            assert!(!panel.has_focus(cx));
4739        });
4740
4741        // Open the dock
4742        workspace.update(cx, |workspace, cx| {
4743            workspace.toggle_dock(DockPosition::Right, cx);
4744        });
4745
4746        workspace.read_with(cx, |workspace, cx| {
4747            assert!(workspace.right_dock().read(cx).is_open());
4748            assert!(!panel.is_zoomed(cx));
4749            assert!(panel.has_focus(cx));
4750        });
4751
4752        // Focus and zoom panel
4753        panel.update(cx, |panel, cx| {
4754            cx.focus_self();
4755            panel.set_zoomed(true, cx)
4756        });
4757
4758        workspace.read_with(cx, |workspace, cx| {
4759            assert!(workspace.right_dock().read(cx).is_open());
4760            assert!(panel.is_zoomed(cx));
4761            assert!(panel.has_focus(cx));
4762        });
4763
4764        // Transfer focus to the center closes the dock
4765        workspace.update(cx, |workspace, cx| {
4766            workspace.toggle_panel_focus::<TestPanel>(cx);
4767        });
4768
4769        workspace.read_with(cx, |workspace, cx| {
4770            assert!(!workspace.right_dock().read(cx).is_open());
4771            assert!(panel.is_zoomed(cx));
4772            assert!(!panel.has_focus(cx));
4773        });
4774
4775        // Transferring focus back to the panel keeps it zoomed
4776        workspace.update(cx, |workspace, cx| {
4777            workspace.toggle_panel_focus::<TestPanel>(cx);
4778        });
4779
4780        workspace.read_with(cx, |workspace, cx| {
4781            assert!(workspace.right_dock().read(cx).is_open());
4782            assert!(panel.is_zoomed(cx));
4783            assert!(panel.has_focus(cx));
4784        });
4785
4786        // Close the dock while it is zoomed
4787        workspace.update(cx, |workspace, cx| {
4788            workspace.toggle_dock(DockPosition::Right, cx)
4789        });
4790
4791        workspace.read_with(cx, |workspace, cx| {
4792            assert!(!workspace.right_dock().read(cx).is_open());
4793            assert!(panel.is_zoomed(cx));
4794            assert!(workspace.zoomed.is_none());
4795            assert!(!panel.has_focus(cx));
4796        });
4797
4798        // Opening the dock, when it's zoomed, retains focus
4799        workspace.update(cx, |workspace, cx| {
4800            workspace.toggle_dock(DockPosition::Right, cx)
4801        });
4802
4803        workspace.read_with(cx, |workspace, cx| {
4804            assert!(workspace.right_dock().read(cx).is_open());
4805            assert!(panel.is_zoomed(cx));
4806            assert!(workspace.zoomed.is_some());
4807            assert!(panel.has_focus(cx));
4808        });
4809
4810        // Unzoom and close the panel, zoom the active pane.
4811        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
4812        workspace.update(cx, |workspace, cx| {
4813            workspace.toggle_dock(DockPosition::Right, cx)
4814        });
4815        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
4816
4817        // Opening a dock unzooms the pane.
4818        workspace.update(cx, |workspace, cx| {
4819            workspace.toggle_dock(DockPosition::Right, cx)
4820        });
4821        workspace.read_with(cx, |workspace, cx| {
4822            let pane = pane.read(cx);
4823            assert!(!pane.is_zoomed());
4824            assert!(!pane.has_focus());
4825            assert!(workspace.right_dock().read(cx).is_open());
4826            assert!(workspace.zoomed.is_none());
4827        });
4828    }
4829
4830    #[gpui::test]
4831    async fn test_panels(cx: &mut gpui::TestAppContext) {
4832        init_test(cx);
4833        let fs = FakeFs::new(cx.background());
4834
4835        let project = Project::test(fs, [], cx).await;
4836        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4837        let workspace = window.root(cx);
4838
4839        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4840            // Add panel_1 on the left, panel_2 on the right.
4841            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4842            workspace.add_panel(panel_1.clone(), cx);
4843            workspace
4844                .left_dock()
4845                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4846            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4847            workspace.add_panel(panel_2.clone(), cx);
4848            workspace
4849                .right_dock()
4850                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4851
4852            let left_dock = workspace.left_dock();
4853            assert_eq!(
4854                left_dock.read(cx).visible_panel().unwrap().id(),
4855                panel_1.id()
4856            );
4857            assert_eq!(
4858                left_dock.read(cx).active_panel_size(cx).unwrap(),
4859                panel_1.size(cx)
4860            );
4861
4862            left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx));
4863            assert_eq!(
4864                workspace
4865                    .right_dock()
4866                    .read(cx)
4867                    .visible_panel()
4868                    .unwrap()
4869                    .id(),
4870                panel_2.id()
4871            );
4872
4873            (panel_1, panel_2)
4874        });
4875
4876        // Move panel_1 to the right
4877        panel_1.update(cx, |panel_1, cx| {
4878            panel_1.set_position(DockPosition::Right, cx)
4879        });
4880
4881        workspace.update(cx, |workspace, cx| {
4882            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4883            // Since it was the only panel on the left, the left dock should now be closed.
4884            assert!(!workspace.left_dock().read(cx).is_open());
4885            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
4886            let right_dock = workspace.right_dock();
4887            assert_eq!(
4888                right_dock.read(cx).visible_panel().unwrap().id(),
4889                panel_1.id()
4890            );
4891            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4892
4893            // Now we move panel_2Β to the left
4894            panel_2.set_position(DockPosition::Left, cx);
4895        });
4896
4897        workspace.update(cx, |workspace, cx| {
4898            // Since panel_2 was not visible on the right, we don't open the left dock.
4899            assert!(!workspace.left_dock().read(cx).is_open());
4900            // And the right dock is unaffected in it's displaying of panel_1
4901            assert!(workspace.right_dock().read(cx).is_open());
4902            assert_eq!(
4903                workspace
4904                    .right_dock()
4905                    .read(cx)
4906                    .visible_panel()
4907                    .unwrap()
4908                    .id(),
4909                panel_1.id()
4910            );
4911        });
4912
4913        // Move panel_1 back to the left
4914        panel_1.update(cx, |panel_1, cx| {
4915            panel_1.set_position(DockPosition::Left, cx)
4916        });
4917
4918        workspace.update(cx, |workspace, cx| {
4919            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4920            let left_dock = workspace.left_dock();
4921            assert!(left_dock.read(cx).is_open());
4922            assert_eq!(
4923                left_dock.read(cx).visible_panel().unwrap().id(),
4924                panel_1.id()
4925            );
4926            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4927            // And right the dock should be closed as it no longer has any panels.
4928            assert!(!workspace.right_dock().read(cx).is_open());
4929
4930            // Now we move panel_1 to the bottom
4931            panel_1.set_position(DockPosition::Bottom, cx);
4932        });
4933
4934        workspace.update(cx, |workspace, cx| {
4935            // Since panel_1 was visible on the left, we close the left dock.
4936            assert!(!workspace.left_dock().read(cx).is_open());
4937            // The bottom dock is sized based on the panel's default size,
4938            // since the panel orientation changed from vertical to horizontal.
4939            let bottom_dock = workspace.bottom_dock();
4940            assert_eq!(
4941                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
4942                panel_1.size(cx),
4943            );
4944            // Close bottom dock and move panel_1 back to the left.
4945            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
4946            panel_1.set_position(DockPosition::Left, cx);
4947        });
4948
4949        // Emit activated event on panel 1
4950        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
4951
4952        // Now the left dock is open and panel_1 is active and focused.
4953        workspace.read_with(cx, |workspace, cx| {
4954            let left_dock = workspace.left_dock();
4955            assert!(left_dock.read(cx).is_open());
4956            assert_eq!(
4957                left_dock.read(cx).visible_panel().unwrap().id(),
4958                panel_1.id()
4959            );
4960            assert!(panel_1.is_focused(cx));
4961        });
4962
4963        // Emit closed event on panel 2, which is not active
4964        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4965
4966        // Wo don't close the left dock, because panel_2 wasn't the active panel
4967        workspace.read_with(cx, |workspace, cx| {
4968            let left_dock = workspace.left_dock();
4969            assert!(left_dock.read(cx).is_open());
4970            assert_eq!(
4971                left_dock.read(cx).visible_panel().unwrap().id(),
4972                panel_1.id()
4973            );
4974        });
4975
4976        // Emitting a ZoomIn event shows the panel as zoomed.
4977        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
4978        workspace.read_with(cx, |workspace, _| {
4979            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4980            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
4981        });
4982
4983        // Move panel to another dock while it is zoomed
4984        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
4985        workspace.read_with(cx, |workspace, _| {
4986            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4987            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4988        });
4989
4990        // If focus is transferred to another view that's not a panel or another pane, we still show
4991        // the panel as zoomed.
4992        let focus_receiver = window.add_view(cx, |_| EmptyView);
4993        focus_receiver.update(cx, |_, cx| cx.focus_self());
4994        workspace.read_with(cx, |workspace, _| {
4995            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4996            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4997        });
4998
4999        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5000        workspace.update(cx, |_, cx| cx.focus_self());
5001        workspace.read_with(cx, |workspace, _| {
5002            assert_eq!(workspace.zoomed, None);
5003            assert_eq!(workspace.zoomed_position, None);
5004        });
5005
5006        // If focus is transferred again to another view that's not a panel or a pane, we won't
5007        // show the panel as zoomed because it wasn't zoomed before.
5008        focus_receiver.update(cx, |_, cx| cx.focus_self());
5009        workspace.read_with(cx, |workspace, _| {
5010            assert_eq!(workspace.zoomed, None);
5011            assert_eq!(workspace.zoomed_position, None);
5012        });
5013
5014        // When focus is transferred back to the panel, it is zoomed again.
5015        panel_1.update(cx, |_, cx| cx.focus_self());
5016        workspace.read_with(cx, |workspace, _| {
5017            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5018            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5019        });
5020
5021        // Emitting a ZoomOut event unzooms the panel.
5022        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5023        workspace.read_with(cx, |workspace, _| {
5024            assert_eq!(workspace.zoomed, None);
5025            assert_eq!(workspace.zoomed_position, None);
5026        });
5027
5028        // Emit closed event on panel 1, which is active
5029        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5030
5031        // Now the left dock is closed, because panel_1 was the active panel
5032        workspace.read_with(cx, |workspace, cx| {
5033            let right_dock = workspace.right_dock();
5034            assert!(!right_dock.read(cx).is_open());
5035        });
5036    }
5037
5038    pub fn init_test(cx: &mut TestAppContext) {
5039        cx.foreground().forbid_parking();
5040        cx.update(|cx| {
5041            cx.set_global(SettingsStore::test(cx));
5042            theme::init((), cx);
5043            language::init(cx);
5044            crate::init_settings(cx);
5045            Project::init_settings(cx);
5046        });
5047    }
5048}