workspace.rs

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