workspace.rs

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