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<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    pub fn push_subscription(&mut self, subscription: Subscription) {
3013        self.subscriptions.push(subscription)
3014    }
3015
3016    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3017        let project = self.project().read(cx);
3018
3019        if project.is_local() {
3020            Some(
3021                project
3022                    .visible_worktrees(cx)
3023                    .map(|worktree| worktree.read(cx).abs_path())
3024                    .collect::<Vec<_>>()
3025                    .into(),
3026            )
3027        } else {
3028            None
3029        }
3030    }
3031
3032    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3033        match member {
3034            Member::Axis(PaneAxis { members, .. }) => {
3035                for child in members.iter() {
3036                    self.remove_panes(child.clone(), cx)
3037                }
3038            }
3039            Member::Pane(pane) => {
3040                self.force_remove_pane(&pane, cx);
3041            }
3042        }
3043    }
3044
3045    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
3046        self.panes.retain(|p| p != pane);
3047        cx.focus(self.panes.last().unwrap());
3048        if self.last_active_center_pane == Some(pane.downgrade()) {
3049            self.last_active_center_pane = None;
3050        }
3051        cx.notify();
3052    }
3053
3054    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3055        self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
3056            cx.background().timer(Duration::from_millis(100)).await;
3057            this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
3058                .ok();
3059        }));
3060    }
3061
3062    fn serialize_workspace(&self, cx: &ViewContext<Self>) {
3063        fn serialize_pane_handle(
3064            pane_handle: &ViewHandle<Pane>,
3065            cx: &AppContext,
3066        ) -> SerializedPane {
3067            let (items, active) = {
3068                let pane = pane_handle.read(cx);
3069                let active_item_id = pane.active_item().map(|item| item.id());
3070                (
3071                    pane.items()
3072                        .filter_map(|item_handle| {
3073                            Some(SerializedItem {
3074                                kind: Arc::from(item_handle.serialized_item_kind()?),
3075                                item_id: item_handle.id(),
3076                                active: Some(item_handle.id()) == active_item_id,
3077                            })
3078                        })
3079                        .collect::<Vec<_>>(),
3080                    pane.has_focus(),
3081                )
3082            };
3083
3084            SerializedPane::new(items, active)
3085        }
3086
3087        fn build_serialized_pane_group(
3088            pane_group: &Member,
3089            cx: &AppContext,
3090        ) -> SerializedPaneGroup {
3091            match pane_group {
3092                Member::Axis(PaneAxis {
3093                    axis,
3094                    members,
3095                    flexes,
3096                    bounding_boxes: _,
3097                }) => SerializedPaneGroup::Group {
3098                    axis: *axis,
3099                    children: members
3100                        .iter()
3101                        .map(|member| build_serialized_pane_group(member, cx))
3102                        .collect::<Vec<_>>(),
3103                    flexes: Some(flexes.borrow().clone()),
3104                },
3105                Member::Pane(pane_handle) => {
3106                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3107                }
3108            }
3109        }
3110
3111        fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
3112            let left_dock = this.left_dock.read(cx);
3113            let left_visible = left_dock.is_open();
3114            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
3115                Some(
3116                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
3117                        .to_string(),
3118                )
3119            });
3120            let left_dock_zoom = left_dock
3121                .visible_panel()
3122                .map(|panel| panel.is_zoomed(cx))
3123                .unwrap_or(false);
3124
3125            let right_dock = this.right_dock.read(cx);
3126            let right_visible = right_dock.is_open();
3127            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
3128                Some(
3129                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
3130                        .to_string(),
3131                )
3132            });
3133            let right_dock_zoom = right_dock
3134                .visible_panel()
3135                .map(|panel| panel.is_zoomed(cx))
3136                .unwrap_or(false);
3137
3138            let bottom_dock = this.bottom_dock.read(cx);
3139            let bottom_visible = bottom_dock.is_open();
3140            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
3141                Some(
3142                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
3143                        .to_string(),
3144                )
3145            });
3146            let bottom_dock_zoom = bottom_dock
3147                .visible_panel()
3148                .map(|panel| panel.is_zoomed(cx))
3149                .unwrap_or(false);
3150
3151            DockStructure {
3152                left: DockData {
3153                    visible: left_visible,
3154                    active_panel: left_active_panel,
3155                    zoom: left_dock_zoom,
3156                },
3157                right: DockData {
3158                    visible: right_visible,
3159                    active_panel: right_active_panel,
3160                    zoom: right_dock_zoom,
3161                },
3162                bottom: DockData {
3163                    visible: bottom_visible,
3164                    active_panel: bottom_active_panel,
3165                    zoom: bottom_dock_zoom,
3166                },
3167            }
3168        }
3169
3170        if let Some(location) = self.location(cx) {
3171            // Load bearing special case:
3172            //  - with_local_workspace() relies on this to not have other stuff open
3173            //    when you open your log
3174            if !location.paths().is_empty() {
3175                let center_group = build_serialized_pane_group(&self.center.root, cx);
3176                let docks = build_serialized_docks(self, cx);
3177
3178                let serialized_workspace = SerializedWorkspace {
3179                    id: self.database_id,
3180                    location,
3181                    center_group,
3182                    bounds: Default::default(),
3183                    display: Default::default(),
3184                    docks,
3185                };
3186
3187                cx.background()
3188                    .spawn(persistence::DB.save_workspace(serialized_workspace))
3189                    .detach();
3190            }
3191        }
3192    }
3193
3194    pub(crate) fn load_workspace(
3195        workspace: WeakViewHandle<Workspace>,
3196        serialized_workspace: SerializedWorkspace,
3197        paths_to_open: Vec<Option<ProjectPath>>,
3198        cx: &mut AppContext,
3199    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
3200        cx.spawn(|mut cx| async move {
3201            let result = async_iife! {{
3202                let (project, old_center_pane) =
3203                workspace.read_with(&cx, |workspace, _| {
3204                    (
3205                        workspace.project().clone(),
3206                        workspace.last_active_center_pane.clone(),
3207                    )
3208                })?;
3209
3210                let mut center_items = None;
3211                let mut center_group = None;
3212                // Traverse the splits tree and add to things
3213                if let Some((group, active_pane, items)) = serialized_workspace
3214                        .center_group
3215                        .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
3216                        .await {
3217                    center_items = Some(items);
3218                    center_group = Some((group, active_pane))
3219                }
3220
3221                let resulting_list = cx.read(|cx| {
3222                    let mut opened_items = center_items
3223                        .unwrap_or_default()
3224                        .into_iter()
3225                        .filter_map(|item| {
3226                            let item = item?;
3227                            let project_path = item.project_path(cx)?;
3228                            Some((project_path, item))
3229                        })
3230                        .collect::<HashMap<_, _>>();
3231
3232                    paths_to_open
3233                        .into_iter()
3234                        .map(|path_to_open| {
3235                            path_to_open.map(|path_to_open| {
3236                                Ok(opened_items.remove(&path_to_open))
3237                            })
3238                            .transpose()
3239                            .map(|item| item.flatten())
3240                            .transpose()
3241                        })
3242                        .collect::<Vec<_>>()
3243                });
3244
3245                // Remove old panes from workspace panes list
3246                workspace.update(&mut cx, |workspace, cx| {
3247                    if let Some((center_group, active_pane)) = center_group {
3248                        workspace.remove_panes(workspace.center.root.clone(), cx);
3249
3250                        // Swap workspace center group
3251                        workspace.center = PaneGroup::with_root(center_group);
3252
3253                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
3254                        cx.focus_self();
3255
3256                        if let Some(active_pane) = active_pane {
3257                            cx.focus(&active_pane);
3258                        } else {
3259                            cx.focus(workspace.panes.last().unwrap());
3260                        }
3261                    } else {
3262                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3263                        if let Some(old_center_handle) = old_center_handle {
3264                            cx.focus(&old_center_handle)
3265                        } else {
3266                            cx.focus_self()
3267                        }
3268                    }
3269
3270                    let docks = serialized_workspace.docks;
3271                    workspace.left_dock.update(cx, |dock, cx| {
3272                        dock.set_open(docks.left.visible, cx);
3273                        if let Some(active_panel) = docks.left.active_panel {
3274                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3275                                dock.activate_panel(ix, cx);
3276                            }
3277                        }
3278                                dock.active_panel()
3279                                    .map(|panel| {
3280                                        panel.set_zoomed(docks.left.zoom, cx)
3281                                    });
3282                                if docks.left.visible && docks.left.zoom {
3283                                    cx.focus_self()
3284                                }
3285                    });
3286                    // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3287                    workspace.right_dock.update(cx, |dock, cx| {
3288                        dock.set_open(docks.right.visible, cx);
3289                        if let Some(active_panel) = docks.right.active_panel {
3290                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3291                                dock.activate_panel(ix, cx);
3292
3293                            }
3294                        }
3295                                dock.active_panel()
3296                                    .map(|panel| {
3297                                        panel.set_zoomed(docks.right.zoom, cx)
3298                                    });
3299
3300                                if docks.right.visible && docks.right.zoom {
3301                                    cx.focus_self()
3302                                }
3303                    });
3304                    workspace.bottom_dock.update(cx, |dock, cx| {
3305                        dock.set_open(docks.bottom.visible, cx);
3306                        if let Some(active_panel) = docks.bottom.active_panel {
3307                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3308                                dock.activate_panel(ix, cx);
3309                            }
3310                        }
3311
3312                        dock.active_panel()
3313                            .map(|panel| {
3314                                panel.set_zoomed(docks.bottom.zoom, cx)
3315                            });
3316
3317                        if docks.bottom.visible && docks.bottom.zoom {
3318                            cx.focus_self()
3319                        }
3320                    });
3321
3322
3323                    cx.notify();
3324                })?;
3325
3326                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3327                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3328
3329                Ok::<_, anyhow::Error>(resulting_list)
3330            }};
3331
3332            result.await.unwrap_or_default()
3333        })
3334    }
3335
3336    #[cfg(any(test, feature = "test-support"))]
3337    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3338        let app_state = Arc::new(AppState {
3339            languages: project.read(cx).languages().clone(),
3340            client: project.read(cx).client(),
3341            user_store: project.read(cx).user_store(),
3342            fs: project.read(cx).fs().clone(),
3343            build_window_options: |_, _, _| Default::default(),
3344            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3345            background_actions: || &[],
3346        });
3347        Self::new(0, project, app_state, cx)
3348    }
3349
3350    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3351        let dock = match position {
3352            DockPosition::Left => &self.left_dock,
3353            DockPosition::Right => &self.right_dock,
3354            DockPosition::Bottom => &self.bottom_dock,
3355        };
3356        let active_panel = dock.read(cx).visible_panel()?;
3357        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3358            dock.read(cx).render_placeholder(cx)
3359        } else {
3360            ChildView::new(dock, cx).into_any()
3361        };
3362
3363        Some(
3364            element
3365                .constrained()
3366                .dynamically(move |constraint, _, cx| match position {
3367                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3368                        Vector2F::new(20., constraint.min.y()),
3369                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3370                    ),
3371                    DockPosition::Bottom => SizeConstraint::new(
3372                        Vector2F::new(constraint.min.x(), 20.),
3373                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3374                    ),
3375                })
3376                .into_any(),
3377        )
3378    }
3379}
3380
3381fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3382    ZED_WINDOW_POSITION
3383        .zip(*ZED_WINDOW_SIZE)
3384        .map(|(position, size)| {
3385            WindowBounds::Fixed(RectF::new(
3386                cx.platform().screens()[0].bounds().origin() + position,
3387                size,
3388            ))
3389        })
3390}
3391
3392async fn open_items(
3393    serialized_workspace: Option<SerializedWorkspace>,
3394    workspace: &WeakViewHandle<Workspace>,
3395    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3396    app_state: Arc<AppState>,
3397    mut cx: AsyncAppContext,
3398) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
3399    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3400
3401    if let Some(serialized_workspace) = serialized_workspace {
3402        let workspace = workspace.clone();
3403        let restored_items = cx
3404            .update(|cx| {
3405                Workspace::load_workspace(
3406                    workspace,
3407                    serialized_workspace,
3408                    project_paths_to_open
3409                        .iter()
3410                        .map(|(_, project_path)| project_path)
3411                        .cloned()
3412                        .collect(),
3413                    cx,
3414                )
3415            })
3416            .await;
3417
3418        let restored_project_paths = cx.read(|cx| {
3419            restored_items
3420                .iter()
3421                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
3422                .collect::<HashSet<_>>()
3423        });
3424
3425        opened_items = restored_items;
3426        project_paths_to_open
3427            .iter_mut()
3428            .for_each(|(_, project_path)| {
3429                if let Some(project_path_to_open) = project_path {
3430                    if restored_project_paths.contains(project_path_to_open) {
3431                        *project_path = None;
3432                    }
3433                }
3434            });
3435    } else {
3436        for _ in 0..project_paths_to_open.len() {
3437            opened_items.push(None);
3438        }
3439    }
3440    assert!(opened_items.len() == project_paths_to_open.len());
3441
3442    let tasks =
3443        project_paths_to_open
3444            .into_iter()
3445            .enumerate()
3446            .map(|(i, (abs_path, project_path))| {
3447                let workspace = workspace.clone();
3448                cx.spawn(|mut cx| {
3449                    let fs = app_state.fs.clone();
3450                    async move {
3451                        let file_project_path = project_path?;
3452                        if fs.is_file(&abs_path).await {
3453                            Some((
3454                                i,
3455                                workspace
3456                                    .update(&mut cx, |workspace, cx| {
3457                                        workspace.open_path(file_project_path, None, true, cx)
3458                                    })
3459                                    .log_err()?
3460                                    .await,
3461                            ))
3462                        } else {
3463                            None
3464                        }
3465                    }
3466                })
3467            });
3468
3469    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3470        .await
3471        .into_iter()
3472    {
3473        if let Some((i, path_open_result)) = maybe_opened_path {
3474            opened_items[i] = Some(path_open_result);
3475        }
3476    }
3477
3478    opened_items
3479}
3480
3481fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3482    const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3483    const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3484    const MESSAGE_ID: usize = 2;
3485
3486    if workspace
3487        .read_with(cx, |workspace, cx| {
3488            workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3489        })
3490        .unwrap_or(false)
3491    {
3492        return;
3493    }
3494
3495    if db::kvp::KEY_VALUE_STORE
3496        .read_kvp(NEW_DOCK_HINT_KEY)
3497        .ok()
3498        .flatten()
3499        .is_some()
3500    {
3501        if !workspace
3502            .read_with(cx, |workspace, cx| {
3503                workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3504            })
3505            .unwrap_or(false)
3506        {
3507            cx.update(|cx| {
3508                cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3509                    let entry = tracker
3510                        .entry(TypeId::of::<MessageNotification>())
3511                        .or_default();
3512                    if !entry.contains(&MESSAGE_ID) {
3513                        entry.push(MESSAGE_ID);
3514                    }
3515                });
3516            });
3517        }
3518
3519        return;
3520    }
3521
3522    cx.spawn(|_| async move {
3523        db::kvp::KEY_VALUE_STORE
3524            .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3525            .await
3526            .ok();
3527    })
3528    .detach();
3529
3530    workspace
3531        .update(cx, |workspace, cx| {
3532            workspace.show_notification_once(2, cx, |cx| {
3533                cx.add_view(|_| {
3534                    MessageNotification::new_element(|text, _| {
3535                        Text::new(
3536                            "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3537                            text,
3538                        )
3539                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, scene, cx| {
3540                            let code_span_background_color = settings::get::<ThemeSettings>(cx)
3541                                .theme
3542                                .editor
3543                                .document_highlight_read_background;
3544
3545                            scene.push_quad(gpui::Quad {
3546                                bounds,
3547                                background: Some(code_span_background_color),
3548                                border: Default::default(),
3549                                corner_radius: 2.0,
3550                            })
3551                        })
3552                        .into_any()
3553                    })
3554                    .with_click_message("Read more about the new panel system")
3555                    .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3556                })
3557            })
3558        })
3559        .ok();
3560}
3561
3562fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3563    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3564
3565    workspace
3566        .update(cx, |workspace, cx| {
3567            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3568                workspace.show_notification_once(0, cx, |cx| {
3569                    cx.add_view(|_| {
3570                        MessageNotification::new("Failed to load the database file.")
3571                            .with_click_message("Click to let us know about this error")
3572                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3573                    })
3574                });
3575            }
3576        })
3577        .log_err();
3578}
3579
3580impl Entity for Workspace {
3581    type Event = Event;
3582}
3583
3584impl View for Workspace {
3585    fn ui_name() -> &'static str {
3586        "Workspace"
3587    }
3588
3589    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3590        let theme = theme::current(cx).clone();
3591        Stack::new()
3592            .with_child(
3593                Flex::column()
3594                    .with_child(self.render_titlebar(&theme, cx))
3595                    .with_child(
3596                        Stack::new()
3597                            .with_child({
3598                                let project = self.project.clone();
3599                                Flex::row()
3600                                    .with_children(self.render_dock(DockPosition::Left, cx))
3601                                    .with_child(
3602                                        Flex::column()
3603                                            .with_child(
3604                                                FlexItem::new(
3605                                                    self.center.render(
3606                                                        &project,
3607                                                        &theme,
3608                                                        &self.follower_states_by_leader,
3609                                                        self.active_call(),
3610                                                        self.active_pane(),
3611                                                        self.zoomed
3612                                                            .as_ref()
3613                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3614                                                            .as_ref(),
3615                                                        &self.app_state,
3616                                                        cx,
3617                                                    ),
3618                                                )
3619                                                .flex(1., true),
3620                                            )
3621                                            .with_children(
3622                                                self.render_dock(DockPosition::Bottom, cx),
3623                                            )
3624                                            .flex(1., true),
3625                                    )
3626                                    .with_children(self.render_dock(DockPosition::Right, cx))
3627                            })
3628                            .with_child(Overlay::new(
3629                                Stack::new()
3630                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3631                                        enum ZoomBackground {}
3632                                        let zoomed = zoomed.upgrade(cx)?;
3633
3634                                        let mut foreground_style =
3635                                            theme.workspace.zoomed_pane_foreground;
3636                                        if let Some(zoomed_dock_position) = self.zoomed_position {
3637                                            foreground_style =
3638                                                theme.workspace.zoomed_panel_foreground;
3639                                            let margin = foreground_style.margin.top;
3640                                            let border = foreground_style.border.top;
3641
3642                                            // Only include a margin and border on the opposite side.
3643                                            foreground_style.margin.top = 0.;
3644                                            foreground_style.margin.left = 0.;
3645                                            foreground_style.margin.bottom = 0.;
3646                                            foreground_style.margin.right = 0.;
3647                                            foreground_style.border.top = false;
3648                                            foreground_style.border.left = false;
3649                                            foreground_style.border.bottom = false;
3650                                            foreground_style.border.right = false;
3651                                            match zoomed_dock_position {
3652                                                DockPosition::Left => {
3653                                                    foreground_style.margin.right = margin;
3654                                                    foreground_style.border.right = border;
3655                                                }
3656                                                DockPosition::Right => {
3657                                                    foreground_style.margin.left = margin;
3658                                                    foreground_style.border.left = border;
3659                                                }
3660                                                DockPosition::Bottom => {
3661                                                    foreground_style.margin.top = margin;
3662                                                    foreground_style.border.top = border;
3663                                                }
3664                                            }
3665                                        }
3666
3667                                        Some(
3668                                            ChildView::new(&zoomed, cx)
3669                                                .contained()
3670                                                .with_style(foreground_style)
3671                                                .aligned()
3672                                                .contained()
3673                                                .with_style(theme.workspace.zoomed_background)
3674                                                .mouse::<ZoomBackground>(0)
3675                                                .capture_all()
3676                                                .on_down(
3677                                                    MouseButton::Left,
3678                                                    |_, this: &mut Self, cx| {
3679                                                        this.zoom_out(cx);
3680                                                    },
3681                                                ),
3682                                        )
3683                                    }))
3684                                    .with_children(self.modal.as_ref().map(|modal| {
3685                                        ChildView::new(modal.view.as_any(), cx)
3686                                            .contained()
3687                                            .with_style(theme.workspace.modal)
3688                                            .aligned()
3689                                            .top()
3690                                    }))
3691                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3692                            ))
3693                            .flex(1.0, true),
3694                    )
3695                    .with_child(ChildView::new(&self.status_bar, cx))
3696                    .contained()
3697                    .with_background_color(theme.workspace.background),
3698            )
3699            .with_children(DragAndDrop::render(cx))
3700            .with_children(self.render_disconnected_overlay(cx))
3701            .into_any_named("workspace")
3702    }
3703
3704    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3705        if cx.is_self_focused() {
3706            cx.focus(&self.active_pane);
3707        }
3708    }
3709}
3710
3711impl ViewId {
3712    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3713        Ok(Self {
3714            creator: message
3715                .creator
3716                .ok_or_else(|| anyhow!("creator is missing"))?,
3717            id: message.id,
3718        })
3719    }
3720
3721    pub(crate) fn to_proto(&self) -> proto::ViewId {
3722        proto::ViewId {
3723            creator: Some(self.creator),
3724            id: self.id,
3725        }
3726    }
3727}
3728
3729pub trait WorkspaceHandle {
3730    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3731}
3732
3733impl WorkspaceHandle for ViewHandle<Workspace> {
3734    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3735        self.read(cx)
3736            .worktrees(cx)
3737            .flat_map(|worktree| {
3738                let worktree_id = worktree.read(cx).id();
3739                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3740                    worktree_id,
3741                    path: f.path.clone(),
3742                })
3743            })
3744            .collect::<Vec<_>>()
3745    }
3746}
3747
3748impl std::fmt::Debug for OpenPaths {
3749    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3750        f.debug_struct("OpenPaths")
3751            .field("paths", &self.paths)
3752            .finish()
3753    }
3754}
3755
3756pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
3757
3758pub fn activate_workspace_for_project(
3759    cx: &mut AsyncAppContext,
3760    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3761) -> Option<WeakViewHandle<Workspace>> {
3762    for window_id in cx.window_ids() {
3763        let handle = cx
3764            .update_window(window_id, |cx| {
3765                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3766                    let project = workspace_handle.read(cx).project.clone();
3767                    if project.update(cx, &predicate) {
3768                        cx.activate_window();
3769                        return Some(workspace_handle.clone());
3770                    }
3771                }
3772                None
3773            })
3774            .flatten();
3775
3776        if let Some(handle) = handle {
3777            return Some(handle.downgrade());
3778        }
3779    }
3780    None
3781}
3782
3783pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3784    DB.last_workspace().await.log_err().flatten()
3785}
3786
3787#[allow(clippy::type_complexity)]
3788pub fn open_paths(
3789    abs_paths: &[PathBuf],
3790    app_state: &Arc<AppState>,
3791    requesting_window_id: Option<usize>,
3792    cx: &mut AppContext,
3793) -> Task<
3794    Result<(
3795        WeakViewHandle<Workspace>,
3796        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3797    )>,
3798> {
3799    let app_state = app_state.clone();
3800    let abs_paths = abs_paths.to_vec();
3801    cx.spawn(|mut cx| async move {
3802        // Open paths in existing workspace if possible
3803        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3804            project.contains_paths(&abs_paths, cx)
3805        });
3806
3807        if let Some(existing) = existing {
3808            Ok((
3809                existing.clone(),
3810                existing
3811                    .update(&mut cx, |workspace, cx| {
3812                        workspace.open_paths(abs_paths, true, cx)
3813                    })?
3814                    .await,
3815            ))
3816        } else {
3817            Ok(cx
3818                .update(|cx| {
3819                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx)
3820                })
3821                .await)
3822        }
3823    })
3824}
3825
3826pub fn open_new(
3827    app_state: &Arc<AppState>,
3828    cx: &mut AppContext,
3829    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3830) -> Task<()> {
3831    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3832    cx.spawn(|mut cx| async move {
3833        let (workspace, opened_paths) = task.await;
3834
3835        workspace
3836            .update(&mut cx, |workspace, cx| {
3837                if opened_paths.is_empty() {
3838                    init(workspace, cx)
3839                }
3840            })
3841            .log_err();
3842    })
3843}
3844
3845pub fn create_and_open_local_file(
3846    path: &'static Path,
3847    cx: &mut ViewContext<Workspace>,
3848    default_content: impl 'static + Send + FnOnce() -> Rope,
3849) -> Task<Result<Box<dyn ItemHandle>>> {
3850    cx.spawn(|workspace, mut cx| async move {
3851        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
3852        if !fs.is_file(path).await {
3853            fs.create_file(path, Default::default()).await?;
3854            fs.save(path, &default_content(), Default::default())
3855                .await?;
3856        }
3857
3858        let mut items = workspace
3859            .update(&mut cx, |workspace, cx| {
3860                workspace.with_local_workspace(cx, |workspace, cx| {
3861                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
3862                })
3863            })?
3864            .await?
3865            .await;
3866
3867        let item = items.pop().flatten();
3868        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
3869    })
3870}
3871
3872pub fn join_remote_project(
3873    project_id: u64,
3874    follow_user_id: u64,
3875    app_state: Arc<AppState>,
3876    cx: &mut AppContext,
3877) -> Task<Result<()>> {
3878    cx.spawn(|mut cx| async move {
3879        let existing_workspace = cx
3880            .window_ids()
3881            .into_iter()
3882            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
3883            .find(|workspace| {
3884                cx.read_window(workspace.window_id(), |cx| {
3885                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
3886                })
3887                .unwrap_or(false)
3888            });
3889
3890        let workspace = if let Some(existing_workspace) = existing_workspace {
3891            existing_workspace.downgrade()
3892        } else {
3893            let active_call = cx.read(ActiveCall::global);
3894            let room = active_call
3895                .read_with(&cx, |call, _| call.room().cloned())
3896                .ok_or_else(|| anyhow!("not in a call"))?;
3897            let project = room
3898                .update(&mut cx, |room, cx| {
3899                    room.join_project(
3900                        project_id,
3901                        app_state.languages.clone(),
3902                        app_state.fs.clone(),
3903                        cx,
3904                    )
3905                })
3906                .await?;
3907
3908            let window_bounds_override = window_bounds_env_override(&cx);
3909            let (_, workspace) = cx.add_window(
3910                (app_state.build_window_options)(
3911                    window_bounds_override,
3912                    None,
3913                    cx.platform().as_ref(),
3914                ),
3915                |cx| Workspace::new(0, project, app_state.clone(), cx),
3916            );
3917            (app_state.initialize_workspace)(
3918                workspace.downgrade(),
3919                false,
3920                app_state.clone(),
3921                cx.clone(),
3922            )
3923            .await
3924            .log_err();
3925
3926            workspace.downgrade()
3927        };
3928
3929        cx.activate_window(workspace.window_id());
3930        cx.platform().activate(true);
3931
3932        workspace.update(&mut cx, |workspace, cx| {
3933            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
3934                let follow_peer_id = room
3935                    .read(cx)
3936                    .remote_participants()
3937                    .iter()
3938                    .find(|(_, participant)| participant.user.id == follow_user_id)
3939                    .map(|(_, p)| p.peer_id)
3940                    .or_else(|| {
3941                        // If we couldn't follow the given user, follow the host instead.
3942                        let collaborator = workspace
3943                            .project()
3944                            .read(cx)
3945                            .collaborators()
3946                            .values()
3947                            .find(|collaborator| collaborator.replica_id == 0)?;
3948                        Some(collaborator.peer_id)
3949                    });
3950
3951                if let Some(follow_peer_id) = follow_peer_id {
3952                    if !workspace.is_being_followed(follow_peer_id) {
3953                        workspace
3954                            .toggle_follow(follow_peer_id, cx)
3955                            .map(|follow| follow.detach_and_log_err(cx));
3956                    }
3957                }
3958            }
3959        })?;
3960
3961        anyhow::Ok(())
3962    })
3963}
3964
3965pub fn restart(_: &Restart, cx: &mut AppContext) {
3966    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
3967    cx.spawn(|mut cx| async move {
3968        let mut workspaces = cx
3969            .window_ids()
3970            .into_iter()
3971            .filter_map(|window_id| {
3972                Some(
3973                    cx.root_view(window_id)?
3974                        .clone()
3975                        .downcast::<Workspace>()?
3976                        .downgrade(),
3977                )
3978            })
3979            .collect::<Vec<_>>();
3980
3981        // If multiple windows have unsaved changes, and need a save prompt,
3982        // prompt in the active window before switching to a different window.
3983        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
3984
3985        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
3986            let answer = cx.prompt(
3987                workspace.window_id(),
3988                PromptLevel::Info,
3989                "Are you sure you want to restart?",
3990                &["Restart", "Cancel"],
3991            );
3992
3993            if let Some(mut answer) = answer {
3994                let answer = answer.next().await;
3995                if answer != Some(0) {
3996                    return Ok(());
3997                }
3998            }
3999        }
4000
4001        // If the user cancels any save prompt, then keep the app open.
4002        for workspace in workspaces {
4003            if !workspace
4004                .update(&mut cx, |workspace, cx| {
4005                    workspace.prepare_to_close(true, cx)
4006                })?
4007                .await?
4008            {
4009                return Ok(());
4010            }
4011        }
4012        cx.platform().restart();
4013        anyhow::Ok(())
4014    })
4015    .detach_and_log_err(cx);
4016}
4017
4018fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4019    let mut parts = value.split(',');
4020    let width: usize = parts.next()?.parse().ok()?;
4021    let height: usize = parts.next()?.parse().ok()?;
4022    Some(vec2f(width as f32, height as f32))
4023}
4024
4025#[cfg(test)]
4026mod tests {
4027    use super::*;
4028    use crate::{
4029        dock::test::{TestPanel, TestPanelEvent},
4030        item::test::{TestItem, TestItemEvent, TestProjectItem},
4031    };
4032    use fs::FakeFs;
4033    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4034    use project::{Project, ProjectEntryId};
4035    use serde_json::json;
4036    use settings::SettingsStore;
4037    use std::{cell::RefCell, rc::Rc};
4038
4039    #[gpui::test]
4040    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4041        init_test(cx);
4042
4043        let fs = FakeFs::new(cx.background());
4044        let project = Project::test(fs, [], cx).await;
4045        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4046
4047        // Adding an item with no ambiguity renders the tab without detail.
4048        let item1 = cx.add_view(window_id, |_| {
4049            let mut item = TestItem::new();
4050            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4051            item
4052        });
4053        workspace.update(cx, |workspace, cx| {
4054            workspace.add_item(Box::new(item1.clone()), cx);
4055        });
4056        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4057
4058        // Adding an item that creates ambiguity increases the level of detail on
4059        // both tabs.
4060        let item2 = cx.add_view(window_id, |_| {
4061            let mut item = TestItem::new();
4062            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4063            item
4064        });
4065        workspace.update(cx, |workspace, cx| {
4066            workspace.add_item(Box::new(item2.clone()), cx);
4067        });
4068        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4069        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4070
4071        // Adding an item that creates ambiguity increases the level of detail only
4072        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4073        // we stop at the highest detail available.
4074        let item3 = cx.add_view(window_id, |_| {
4075            let mut item = TestItem::new();
4076            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4077            item
4078        });
4079        workspace.update(cx, |workspace, cx| {
4080            workspace.add_item(Box::new(item3.clone()), cx);
4081        });
4082        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4083        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4084        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4085    }
4086
4087    #[gpui::test]
4088    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4089        init_test(cx);
4090
4091        let fs = FakeFs::new(cx.background());
4092        fs.insert_tree(
4093            "/root1",
4094            json!({
4095                "one.txt": "",
4096                "two.txt": "",
4097            }),
4098        )
4099        .await;
4100        fs.insert_tree(
4101            "/root2",
4102            json!({
4103                "three.txt": "",
4104            }),
4105        )
4106        .await;
4107
4108        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4109        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4110        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4111        let worktree_id = project.read_with(cx, |project, cx| {
4112            project.worktrees(cx).next().unwrap().read(cx).id()
4113        });
4114
4115        let item1 = cx.add_view(window_id, |cx| {
4116            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4117        });
4118        let item2 = cx.add_view(window_id, |cx| {
4119            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4120        });
4121
4122        // Add an item to an empty pane
4123        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4124        project.read_with(cx, |project, cx| {
4125            assert_eq!(
4126                project.active_entry(),
4127                project
4128                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4129                    .map(|e| e.id)
4130            );
4131        });
4132        assert_eq!(
4133            cx.current_window_title(window_id).as_deref(),
4134            Some("one.txt β€” root1")
4135        );
4136
4137        // Add a second item to a non-empty pane
4138        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4139        assert_eq!(
4140            cx.current_window_title(window_id).as_deref(),
4141            Some("two.txt β€” root1")
4142        );
4143        project.read_with(cx, |project, cx| {
4144            assert_eq!(
4145                project.active_entry(),
4146                project
4147                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4148                    .map(|e| e.id)
4149            );
4150        });
4151
4152        // Close the active item
4153        pane.update(cx, |pane, cx| {
4154            pane.close_active_item(&Default::default(), cx).unwrap()
4155        })
4156        .await
4157        .unwrap();
4158        assert_eq!(
4159            cx.current_window_title(window_id).as_deref(),
4160            Some("one.txt β€” root1")
4161        );
4162        project.read_with(cx, |project, cx| {
4163            assert_eq!(
4164                project.active_entry(),
4165                project
4166                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4167                    .map(|e| e.id)
4168            );
4169        });
4170
4171        // Add a project folder
4172        project
4173            .update(cx, |project, cx| {
4174                project.find_or_create_local_worktree("/root2", true, cx)
4175            })
4176            .await
4177            .unwrap();
4178        assert_eq!(
4179            cx.current_window_title(window_id).as_deref(),
4180            Some("one.txt β€” root1, root2")
4181        );
4182
4183        // Remove a project folder
4184        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4185        assert_eq!(
4186            cx.current_window_title(window_id).as_deref(),
4187            Some("one.txt β€” root2")
4188        );
4189    }
4190
4191    #[gpui::test]
4192    async fn test_close_window(cx: &mut TestAppContext) {
4193        init_test(cx);
4194
4195        let fs = FakeFs::new(cx.background());
4196        fs.insert_tree("/root", json!({ "one": "" })).await;
4197
4198        let project = Project::test(fs, ["root".as_ref()], cx).await;
4199        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4200
4201        // When there are no dirty items, there's nothing to do.
4202        let item1 = cx.add_view(window_id, |_| TestItem::new());
4203        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4204        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4205        assert!(task.await.unwrap());
4206
4207        // When there are dirty untitled items, prompt to save each one. If the user
4208        // cancels any prompt, then abort.
4209        let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
4210        let item3 = cx.add_view(window_id, |cx| {
4211            TestItem::new()
4212                .with_dirty(true)
4213                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4214        });
4215        workspace.update(cx, |w, cx| {
4216            w.add_item(Box::new(item2.clone()), cx);
4217            w.add_item(Box::new(item3.clone()), cx);
4218        });
4219        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4220        cx.foreground().run_until_parked();
4221        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
4222        cx.foreground().run_until_parked();
4223        assert!(!cx.has_pending_prompt(window_id));
4224        assert!(!task.await.unwrap());
4225    }
4226
4227    #[gpui::test]
4228    async fn test_close_pane_items(cx: &mut TestAppContext) {
4229        init_test(cx);
4230
4231        let fs = FakeFs::new(cx.background());
4232
4233        let project = Project::test(fs, None, cx).await;
4234        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4235
4236        let item1 = cx.add_view(window_id, |cx| {
4237            TestItem::new()
4238                .with_dirty(true)
4239                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4240        });
4241        let item2 = cx.add_view(window_id, |cx| {
4242            TestItem::new()
4243                .with_dirty(true)
4244                .with_conflict(true)
4245                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4246        });
4247        let item3 = cx.add_view(window_id, |cx| {
4248            TestItem::new()
4249                .with_dirty(true)
4250                .with_conflict(true)
4251                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4252        });
4253        let item4 = cx.add_view(window_id, |cx| {
4254            TestItem::new()
4255                .with_dirty(true)
4256                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4257        });
4258        let pane = workspace.update(cx, |workspace, cx| {
4259            workspace.add_item(Box::new(item1.clone()), cx);
4260            workspace.add_item(Box::new(item2.clone()), cx);
4261            workspace.add_item(Box::new(item3.clone()), cx);
4262            workspace.add_item(Box::new(item4.clone()), cx);
4263            workspace.active_pane().clone()
4264        });
4265
4266        let close_items = pane.update(cx, |pane, cx| {
4267            pane.activate_item(1, true, true, cx);
4268            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4269            let item1_id = item1.id();
4270            let item3_id = item3.id();
4271            let item4_id = item4.id();
4272            pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id))
4273        });
4274        cx.foreground().run_until_parked();
4275
4276        // There's a prompt to save item 1.
4277        pane.read_with(cx, |pane, _| {
4278            assert_eq!(pane.items_len(), 4);
4279            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4280        });
4281        assert!(cx.has_pending_prompt(window_id));
4282
4283        // Confirm saving item 1.
4284        cx.simulate_prompt_answer(window_id, 0);
4285        cx.foreground().run_until_parked();
4286
4287        // Item 1 is saved. There's a prompt to save item 3.
4288        pane.read_with(cx, |pane, cx| {
4289            assert_eq!(item1.read(cx).save_count, 1);
4290            assert_eq!(item1.read(cx).save_as_count, 0);
4291            assert_eq!(item1.read(cx).reload_count, 0);
4292            assert_eq!(pane.items_len(), 3);
4293            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4294        });
4295        assert!(cx.has_pending_prompt(window_id));
4296
4297        // Cancel saving item 3.
4298        cx.simulate_prompt_answer(window_id, 1);
4299        cx.foreground().run_until_parked();
4300
4301        // Item 3 is reloaded. There's a prompt to save item 4.
4302        pane.read_with(cx, |pane, cx| {
4303            assert_eq!(item3.read(cx).save_count, 0);
4304            assert_eq!(item3.read(cx).save_as_count, 0);
4305            assert_eq!(item3.read(cx).reload_count, 1);
4306            assert_eq!(pane.items_len(), 2);
4307            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4308        });
4309        assert!(cx.has_pending_prompt(window_id));
4310
4311        // Confirm saving item 4.
4312        cx.simulate_prompt_answer(window_id, 0);
4313        cx.foreground().run_until_parked();
4314
4315        // There's a prompt for a path for item 4.
4316        cx.simulate_new_path_selection(|_| Some(Default::default()));
4317        close_items.await.unwrap();
4318
4319        // The requested items are closed.
4320        pane.read_with(cx, |pane, cx| {
4321            assert_eq!(item4.read(cx).save_count, 0);
4322            assert_eq!(item4.read(cx).save_as_count, 1);
4323            assert_eq!(item4.read(cx).reload_count, 0);
4324            assert_eq!(pane.items_len(), 1);
4325            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4326        });
4327    }
4328
4329    #[gpui::test]
4330    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4331        init_test(cx);
4332
4333        let fs = FakeFs::new(cx.background());
4334
4335        let project = Project::test(fs, [], cx).await;
4336        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4337
4338        // Create several workspace items with single project entries, and two
4339        // workspace items with multiple project entries.
4340        let single_entry_items = (0..=4)
4341            .map(|project_entry_id| {
4342                cx.add_view(window_id, |cx| {
4343                    TestItem::new()
4344                        .with_dirty(true)
4345                        .with_project_items(&[TestProjectItem::new(
4346                            project_entry_id,
4347                            &format!("{project_entry_id}.txt"),
4348                            cx,
4349                        )])
4350                })
4351            })
4352            .collect::<Vec<_>>();
4353        let item_2_3 = cx.add_view(window_id, |cx| {
4354            TestItem::new()
4355                .with_dirty(true)
4356                .with_singleton(false)
4357                .with_project_items(&[
4358                    single_entry_items[2].read(cx).project_items[0].clone(),
4359                    single_entry_items[3].read(cx).project_items[0].clone(),
4360                ])
4361        });
4362        let item_3_4 = cx.add_view(window_id, |cx| {
4363            TestItem::new()
4364                .with_dirty(true)
4365                .with_singleton(false)
4366                .with_project_items(&[
4367                    single_entry_items[3].read(cx).project_items[0].clone(),
4368                    single_entry_items[4].read(cx).project_items[0].clone(),
4369                ])
4370        });
4371
4372        // Create two panes that contain the following project entries:
4373        //   left pane:
4374        //     multi-entry items:   (2, 3)
4375        //     single-entry items:  0, 1, 2, 3, 4
4376        //   right pane:
4377        //     single-entry items:  1
4378        //     multi-entry items:   (3, 4)
4379        let left_pane = workspace.update(cx, |workspace, cx| {
4380            let left_pane = workspace.active_pane().clone();
4381            workspace.add_item(Box::new(item_2_3.clone()), cx);
4382            for item in single_entry_items {
4383                workspace.add_item(Box::new(item), cx);
4384            }
4385            left_pane.update(cx, |pane, cx| {
4386                pane.activate_item(2, true, true, cx);
4387            });
4388
4389            workspace
4390                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4391                .unwrap();
4392
4393            left_pane
4394        });
4395
4396        //Need to cause an effect flush in order to respect new focus
4397        workspace.update(cx, |workspace, cx| {
4398            workspace.add_item(Box::new(item_3_4.clone()), cx);
4399            cx.focus(&left_pane);
4400        });
4401
4402        // When closing all of the items in the left pane, we should be prompted twice:
4403        // once for project entry 0, and once for project entry 2. After those two
4404        // prompts, the task should complete.
4405
4406        let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true));
4407        cx.foreground().run_until_parked();
4408        left_pane.read_with(cx, |pane, cx| {
4409            assert_eq!(
4410                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4411                &[ProjectEntryId::from_proto(0)]
4412            );
4413        });
4414        cx.simulate_prompt_answer(window_id, 0);
4415
4416        cx.foreground().run_until_parked();
4417        left_pane.read_with(cx, |pane, cx| {
4418            assert_eq!(
4419                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4420                &[ProjectEntryId::from_proto(2)]
4421            );
4422        });
4423        cx.simulate_prompt_answer(window_id, 0);
4424
4425        cx.foreground().run_until_parked();
4426        close.await.unwrap();
4427        left_pane.read_with(cx, |pane, _| {
4428            assert_eq!(pane.items_len(), 0);
4429        });
4430    }
4431
4432    #[gpui::test]
4433    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4434        init_test(cx);
4435
4436        let fs = FakeFs::new(cx.background());
4437
4438        let project = Project::test(fs, [], cx).await;
4439        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4440        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4441
4442        let item = cx.add_view(window_id, |cx| {
4443            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4444        });
4445        let item_id = item.id();
4446        workspace.update(cx, |workspace, cx| {
4447            workspace.add_item(Box::new(item.clone()), cx);
4448        });
4449
4450        // Autosave on window change.
4451        item.update(cx, |item, cx| {
4452            cx.update_global(|settings: &mut SettingsStore, cx| {
4453                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4454                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4455                })
4456            });
4457            item.is_dirty = true;
4458        });
4459
4460        // Deactivating the window saves the file.
4461        cx.simulate_window_activation(None);
4462        deterministic.run_until_parked();
4463        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4464
4465        // Autosave on focus change.
4466        item.update(cx, |item, cx| {
4467            cx.focus_self();
4468            cx.update_global(|settings: &mut SettingsStore, cx| {
4469                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4470                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4471                })
4472            });
4473            item.is_dirty = true;
4474        });
4475
4476        // Blurring the item saves the file.
4477        item.update(cx, |_, cx| cx.blur());
4478        deterministic.run_until_parked();
4479        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4480
4481        // Deactivating the window still saves the file.
4482        cx.simulate_window_activation(Some(window_id));
4483        item.update(cx, |item, cx| {
4484            cx.focus_self();
4485            item.is_dirty = true;
4486        });
4487        cx.simulate_window_activation(None);
4488
4489        deterministic.run_until_parked();
4490        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4491
4492        // Autosave after delay.
4493        item.update(cx, |item, cx| {
4494            cx.update_global(|settings: &mut SettingsStore, cx| {
4495                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4496                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4497                })
4498            });
4499            item.is_dirty = true;
4500            cx.emit(TestItemEvent::Edit);
4501        });
4502
4503        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4504        deterministic.advance_clock(Duration::from_millis(250));
4505        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4506
4507        // After delay expires, the file is saved.
4508        deterministic.advance_clock(Duration::from_millis(250));
4509        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4510
4511        // Autosave on focus change, ensuring closing the tab counts as such.
4512        item.update(cx, |item, cx| {
4513            cx.update_global(|settings: &mut SettingsStore, cx| {
4514                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4515                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4516                })
4517            });
4518            item.is_dirty = true;
4519        });
4520
4521        pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
4522            .await
4523            .unwrap();
4524        assert!(!cx.has_pending_prompt(window_id));
4525        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4526
4527        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4528        workspace.update(cx, |workspace, cx| {
4529            workspace.add_item(Box::new(item.clone()), cx);
4530        });
4531        item.update(cx, |item, cx| {
4532            item.project_items[0].update(cx, |item, _| {
4533                item.entry_id = None;
4534            });
4535            item.is_dirty = true;
4536            cx.blur();
4537        });
4538        deterministic.run_until_parked();
4539        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4540
4541        // Ensure autosave is prevented for deleted files also when closing the buffer.
4542        let _close_items =
4543            pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
4544        deterministic.run_until_parked();
4545        assert!(cx.has_pending_prompt(window_id));
4546        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4547    }
4548
4549    #[gpui::test]
4550    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4551        init_test(cx);
4552
4553        let fs = FakeFs::new(cx.background());
4554
4555        let project = Project::test(fs, [], cx).await;
4556        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4557
4558        let item = cx.add_view(window_id, |cx| {
4559            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4560        });
4561        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4562        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4563        let toolbar_notify_count = Rc::new(RefCell::new(0));
4564
4565        workspace.update(cx, |workspace, cx| {
4566            workspace.add_item(Box::new(item.clone()), cx);
4567            let toolbar_notification_count = toolbar_notify_count.clone();
4568            cx.observe(&toolbar, move |_, _, _| {
4569                *toolbar_notification_count.borrow_mut() += 1
4570            })
4571            .detach();
4572        });
4573
4574        pane.read_with(cx, |pane, _| {
4575            assert!(!pane.can_navigate_backward());
4576            assert!(!pane.can_navigate_forward());
4577        });
4578
4579        item.update(cx, |item, cx| {
4580            item.set_state("one".to_string(), cx);
4581        });
4582
4583        // Toolbar must be notified to re-render the navigation buttons
4584        assert_eq!(*toolbar_notify_count.borrow(), 1);
4585
4586        pane.read_with(cx, |pane, _| {
4587            assert!(pane.can_navigate_backward());
4588            assert!(!pane.can_navigate_forward());
4589        });
4590
4591        workspace
4592            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4593            .await
4594            .unwrap();
4595
4596        assert_eq!(*toolbar_notify_count.borrow(), 3);
4597        pane.read_with(cx, |pane, _| {
4598            assert!(!pane.can_navigate_backward());
4599            assert!(pane.can_navigate_forward());
4600        });
4601    }
4602
4603    #[gpui::test]
4604    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4605        init_test(cx);
4606        let fs = FakeFs::new(cx.background());
4607
4608        let project = Project::test(fs, [], cx).await;
4609        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4610
4611        let panel = workspace.update(cx, |workspace, cx| {
4612            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4613            workspace.add_panel(panel.clone(), cx);
4614
4615            workspace
4616                .right_dock()
4617                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4618
4619            panel
4620        });
4621
4622        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4623        pane.update(cx, |pane, cx| {
4624            let item = cx.add_view(|_| TestItem::new());
4625            pane.add_item(Box::new(item), true, true, None, cx);
4626        });
4627
4628        // Transfer focus from center to panel
4629        workspace.update(cx, |workspace, cx| {
4630            workspace.toggle_panel_focus::<TestPanel>(cx);
4631        });
4632
4633        workspace.read_with(cx, |workspace, cx| {
4634            assert!(workspace.right_dock().read(cx).is_open());
4635            assert!(!panel.is_zoomed(cx));
4636            assert!(panel.has_focus(cx));
4637        });
4638
4639        // Transfer focus from panel to center
4640        workspace.update(cx, |workspace, cx| {
4641            workspace.toggle_panel_focus::<TestPanel>(cx);
4642        });
4643
4644        workspace.read_with(cx, |workspace, cx| {
4645            assert!(workspace.right_dock().read(cx).is_open());
4646            assert!(!panel.is_zoomed(cx));
4647            assert!(!panel.has_focus(cx));
4648        });
4649
4650        // Close the dock
4651        workspace.update(cx, |workspace, cx| {
4652            workspace.toggle_dock(DockPosition::Right, cx);
4653        });
4654
4655        workspace.read_with(cx, |workspace, cx| {
4656            assert!(!workspace.right_dock().read(cx).is_open());
4657            assert!(!panel.is_zoomed(cx));
4658            assert!(!panel.has_focus(cx));
4659        });
4660
4661        // Open the dock
4662        workspace.update(cx, |workspace, cx| {
4663            workspace.toggle_dock(DockPosition::Right, cx);
4664        });
4665
4666        workspace.read_with(cx, |workspace, cx| {
4667            assert!(workspace.right_dock().read(cx).is_open());
4668            assert!(!panel.is_zoomed(cx));
4669            assert!(panel.has_focus(cx));
4670        });
4671
4672        // Focus and zoom panel
4673        panel.update(cx, |panel, cx| {
4674            cx.focus_self();
4675            panel.set_zoomed(true, cx)
4676        });
4677
4678        workspace.read_with(cx, |workspace, cx| {
4679            assert!(workspace.right_dock().read(cx).is_open());
4680            assert!(panel.is_zoomed(cx));
4681            assert!(panel.has_focus(cx));
4682        });
4683
4684        // Transfer focus to the center closes the dock
4685        workspace.update(cx, |workspace, cx| {
4686            workspace.toggle_panel_focus::<TestPanel>(cx);
4687        });
4688
4689        workspace.read_with(cx, |workspace, cx| {
4690            assert!(!workspace.right_dock().read(cx).is_open());
4691            assert!(panel.is_zoomed(cx));
4692            assert!(!panel.has_focus(cx));
4693        });
4694
4695        // Transferring focus back to the panel keeps it zoomed
4696        workspace.update(cx, |workspace, cx| {
4697            workspace.toggle_panel_focus::<TestPanel>(cx);
4698        });
4699
4700        workspace.read_with(cx, |workspace, cx| {
4701            assert!(workspace.right_dock().read(cx).is_open());
4702            assert!(panel.is_zoomed(cx));
4703            assert!(panel.has_focus(cx));
4704        });
4705
4706        // Close the dock while it is zoomed
4707        workspace.update(cx, |workspace, cx| {
4708            workspace.toggle_dock(DockPosition::Right, cx)
4709        });
4710
4711        workspace.read_with(cx, |workspace, cx| {
4712            assert!(!workspace.right_dock().read(cx).is_open());
4713            assert!(panel.is_zoomed(cx));
4714            assert!(workspace.zoomed.is_none());
4715            assert!(!panel.has_focus(cx));
4716        });
4717
4718        // Opening the dock, when it's zoomed, retains focus
4719        workspace.update(cx, |workspace, cx| {
4720            workspace.toggle_dock(DockPosition::Right, cx)
4721        });
4722
4723        workspace.read_with(cx, |workspace, cx| {
4724            assert!(workspace.right_dock().read(cx).is_open());
4725            assert!(panel.is_zoomed(cx));
4726            assert!(workspace.zoomed.is_some());
4727            assert!(panel.has_focus(cx));
4728        });
4729
4730        // Unzoom and close the panel, zoom the active pane.
4731        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
4732        workspace.update(cx, |workspace, cx| {
4733            workspace.toggle_dock(DockPosition::Right, cx)
4734        });
4735        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
4736
4737        // Opening a dock unzooms the pane.
4738        workspace.update(cx, |workspace, cx| {
4739            workspace.toggle_dock(DockPosition::Right, cx)
4740        });
4741        workspace.read_with(cx, |workspace, cx| {
4742            let pane = pane.read(cx);
4743            assert!(!pane.is_zoomed());
4744            assert!(!pane.has_focus());
4745            assert!(workspace.right_dock().read(cx).is_open());
4746            assert!(workspace.zoomed.is_none());
4747        });
4748    }
4749
4750    #[gpui::test]
4751    async fn test_panels(cx: &mut gpui::TestAppContext) {
4752        init_test(cx);
4753        let fs = FakeFs::new(cx.background());
4754
4755        let project = Project::test(fs, [], cx).await;
4756        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4757
4758        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4759            // Add panel_1 on the left, panel_2 on the right.
4760            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4761            workspace.add_panel(panel_1.clone(), cx);
4762            workspace
4763                .left_dock()
4764                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4765            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4766            workspace.add_panel(panel_2.clone(), cx);
4767            workspace
4768                .right_dock()
4769                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4770
4771            let left_dock = workspace.left_dock();
4772            assert_eq!(
4773                left_dock.read(cx).visible_panel().unwrap().id(),
4774                panel_1.id()
4775            );
4776            assert_eq!(
4777                left_dock.read(cx).active_panel_size(cx).unwrap(),
4778                panel_1.size(cx)
4779            );
4780
4781            left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx));
4782            assert_eq!(
4783                workspace
4784                    .right_dock()
4785                    .read(cx)
4786                    .visible_panel()
4787                    .unwrap()
4788                    .id(),
4789                panel_2.id()
4790            );
4791
4792            (panel_1, panel_2)
4793        });
4794
4795        // Move panel_1 to the right
4796        panel_1.update(cx, |panel_1, cx| {
4797            panel_1.set_position(DockPosition::Right, cx)
4798        });
4799
4800        workspace.update(cx, |workspace, cx| {
4801            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4802            // Since it was the only panel on the left, the left dock should now be closed.
4803            assert!(!workspace.left_dock().read(cx).is_open());
4804            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
4805            let right_dock = workspace.right_dock();
4806            assert_eq!(
4807                right_dock.read(cx).visible_panel().unwrap().id(),
4808                panel_1.id()
4809            );
4810            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4811
4812            // Now we move panel_2Β to the left
4813            panel_2.set_position(DockPosition::Left, cx);
4814        });
4815
4816        workspace.update(cx, |workspace, cx| {
4817            // Since panel_2 was not visible on the right, we don't open the left dock.
4818            assert!(!workspace.left_dock().read(cx).is_open());
4819            // And the right dock is unaffected in it's displaying of panel_1
4820            assert!(workspace.right_dock().read(cx).is_open());
4821            assert_eq!(
4822                workspace
4823                    .right_dock()
4824                    .read(cx)
4825                    .visible_panel()
4826                    .unwrap()
4827                    .id(),
4828                panel_1.id()
4829            );
4830        });
4831
4832        // Move panel_1 back to the left
4833        panel_1.update(cx, |panel_1, cx| {
4834            panel_1.set_position(DockPosition::Left, cx)
4835        });
4836
4837        workspace.update(cx, |workspace, cx| {
4838            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4839            let left_dock = workspace.left_dock();
4840            assert!(left_dock.read(cx).is_open());
4841            assert_eq!(
4842                left_dock.read(cx).visible_panel().unwrap().id(),
4843                panel_1.id()
4844            );
4845            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4846            // And right the dock should be closed as it no longer has any panels.
4847            assert!(!workspace.right_dock().read(cx).is_open());
4848
4849            // Now we move panel_1 to the bottom
4850            panel_1.set_position(DockPosition::Bottom, cx);
4851        });
4852
4853        workspace.update(cx, |workspace, cx| {
4854            // Since panel_1 was visible on the left, we close the left dock.
4855            assert!(!workspace.left_dock().read(cx).is_open());
4856            // The bottom dock is sized based on the panel's default size,
4857            // since the panel orientation changed from vertical to horizontal.
4858            let bottom_dock = workspace.bottom_dock();
4859            assert_eq!(
4860                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
4861                panel_1.size(cx),
4862            );
4863            // Close bottom dock and move panel_1 back to the left.
4864            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
4865            panel_1.set_position(DockPosition::Left, cx);
4866        });
4867
4868        // Emit activated event on panel 1
4869        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
4870
4871        // Now the left dock is open and panel_1 is active and focused.
4872        workspace.read_with(cx, |workspace, cx| {
4873            let left_dock = workspace.left_dock();
4874            assert!(left_dock.read(cx).is_open());
4875            assert_eq!(
4876                left_dock.read(cx).visible_panel().unwrap().id(),
4877                panel_1.id()
4878            );
4879            assert!(panel_1.is_focused(cx));
4880        });
4881
4882        // Emit closed event on panel 2, which is not active
4883        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4884
4885        // Wo don't close the left dock, because panel_2 wasn't the active panel
4886        workspace.read_with(cx, |workspace, cx| {
4887            let left_dock = workspace.left_dock();
4888            assert!(left_dock.read(cx).is_open());
4889            assert_eq!(
4890                left_dock.read(cx).visible_panel().unwrap().id(),
4891                panel_1.id()
4892            );
4893        });
4894
4895        // Emitting a ZoomIn event shows the panel as zoomed.
4896        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
4897        workspace.read_with(cx, |workspace, _| {
4898            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4899            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
4900        });
4901
4902        // Move panel to another dock while it is zoomed
4903        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
4904        workspace.read_with(cx, |workspace, _| {
4905            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4906            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4907        });
4908
4909        // If focus is transferred to another view that's not a panel or another pane, we still show
4910        // the panel as zoomed.
4911        let focus_receiver = cx.add_view(window_id, |_| EmptyView);
4912        focus_receiver.update(cx, |_, cx| cx.focus_self());
4913        workspace.read_with(cx, |workspace, _| {
4914            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4915            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4916        });
4917
4918        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
4919        workspace.update(cx, |_, cx| cx.focus_self());
4920        workspace.read_with(cx, |workspace, _| {
4921            assert_eq!(workspace.zoomed, None);
4922            assert_eq!(workspace.zoomed_position, None);
4923        });
4924
4925        // If focus is transferred again to another view that's not a panel or a pane, we won't
4926        // show the panel as zoomed because it wasn't zoomed before.
4927        focus_receiver.update(cx, |_, cx| cx.focus_self());
4928        workspace.read_with(cx, |workspace, _| {
4929            assert_eq!(workspace.zoomed, None);
4930            assert_eq!(workspace.zoomed_position, None);
4931        });
4932
4933        // When focus is transferred back to the panel, it is zoomed again.
4934        panel_1.update(cx, |_, cx| cx.focus_self());
4935        workspace.read_with(cx, |workspace, _| {
4936            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4937            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4938        });
4939
4940        // Emitting a ZoomOut event unzooms the panel.
4941        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
4942        workspace.read_with(cx, |workspace, _| {
4943            assert_eq!(workspace.zoomed, None);
4944            assert_eq!(workspace.zoomed_position, None);
4945        });
4946
4947        // Emit closed event on panel 1, which is active
4948        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4949
4950        // Now the left dock is closed, because panel_1 was the active panel
4951        workspace.read_with(cx, |workspace, cx| {
4952            let right_dock = workspace.right_dock();
4953            assert!(!right_dock.read(cx).is_open());
4954        });
4955    }
4956
4957    pub fn init_test(cx: &mut TestAppContext) {
4958        cx.foreground().forbid_parking();
4959        cx.update(|cx| {
4960            cx.set_global(SettingsStore::test(cx));
4961            theme::init((), cx);
4962            language::init(cx);
4963            crate::init_settings(cx);
4964            Project::init_settings(cx);
4965        });
4966    }
4967}