workspace.rs

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