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