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