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