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