workspace.rs

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