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