workspace.rs

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