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