workspace.rs

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