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;
 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                .map(|panel| 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                .map(|panel| 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                .map(|panel| 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(|(ix, (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                                    ix,
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 (ix, path_open_result) in tasks.await.into_iter().flatten() {
3743            opened_items[ix] = Some(path_open_result);
3744        }
3745
3746        Ok(opened_items)
3747    })
3748}
3749
3750enum ActivateInDirectionTarget {
3751    Pane(View<Pane>),
3752    Dock(View<Dock>),
3753}
3754
3755fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3756    const REPORT_ISSUE_URL: &str = "https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3757
3758    workspace
3759        .update(cx, |workspace, cx| {
3760            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3761                workspace.show_notification_once(0, cx, |cx| {
3762                    cx.new_view(|_| {
3763                        MessageNotification::new("Failed to load the database file.")
3764                            .with_click_message("Click to let us know about this error")
3765                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3766                    })
3767                });
3768            }
3769        })
3770        .log_err();
3771}
3772
3773impl FocusableView for Workspace {
3774    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3775        self.active_pane.focus_handle(cx)
3776    }
3777}
3778
3779#[derive(Clone, Render)]
3780struct DraggedDock(DockPosition);
3781
3782impl Render for Workspace {
3783    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3784        let mut context = KeyContext::default();
3785        context.add("Workspace");
3786
3787        let (ui_font, ui_font_size) = {
3788            let theme_settings = ThemeSettings::get_global(cx);
3789            (
3790                theme_settings.ui_font.family.clone(),
3791                theme_settings.ui_font_size,
3792            )
3793        };
3794
3795        let theme = cx.theme().clone();
3796        let colors = theme.colors();
3797        cx.set_rem_size(ui_font_size);
3798
3799        self.actions(div(), cx)
3800            .key_context(context)
3801            .relative()
3802            .size_full()
3803            .flex()
3804            .flex_col()
3805            .font(ui_font)
3806            .gap_0()
3807            .justify_start()
3808            .items_start()
3809            .text_color(colors.text)
3810            .bg(colors.background)
3811            .border()
3812            .border_color(colors.border)
3813            .children(self.titlebar_item.clone())
3814            .child(
3815                div()
3816                    .id("workspace")
3817                    .relative()
3818                    .flex_1()
3819                    .w_full()
3820                    .flex()
3821                    .flex_col()
3822                    .overflow_hidden()
3823                    .border_t()
3824                    .border_b()
3825                    .border_color(colors.border)
3826                    .child(
3827                        canvas({
3828                            let this = cx.view().clone();
3829                            move |bounds, cx| {
3830                                this.update(cx, |this, _cx| {
3831                                    this.bounds = *bounds;
3832                                })
3833                            }
3834                        })
3835                        .absolute()
3836                        .size_full(),
3837                    )
3838                    .on_drag_move(
3839                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3840                            match e.drag(cx).0 {
3841                                DockPosition::Left => {
3842                                    let size = workspace.bounds.left() + e.event.position.x;
3843                                    workspace.left_dock.update(cx, |left_dock, cx| {
3844                                        left_dock.resize_active_panel(Some(size), cx);
3845                                    });
3846                                }
3847                                DockPosition::Right => {
3848                                    let size = workspace.bounds.right() - e.event.position.x;
3849                                    workspace.right_dock.update(cx, |right_dock, cx| {
3850                                        right_dock.resize_active_panel(Some(size), cx);
3851                                    });
3852                                }
3853                                DockPosition::Bottom => {
3854                                    let size = workspace.bounds.bottom() - e.event.position.y;
3855                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3856                                        bottom_dock.resize_active_panel(Some(size), cx);
3857                                    });
3858                                }
3859                            }
3860                        }),
3861                    )
3862                    .child(self.modal_layer.clone())
3863                    .child(
3864                        div()
3865                            .flex()
3866                            .flex_row()
3867                            .h_full()
3868                            // Left Dock
3869                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3870                                || {
3871                                    div()
3872                                        .flex()
3873                                        .flex_none()
3874                                        .overflow_hidden()
3875                                        .child(self.left_dock.clone())
3876                                },
3877                            ))
3878                            // Panes
3879                            .child(
3880                                div()
3881                                    .flex()
3882                                    .flex_col()
3883                                    .flex_1()
3884                                    .overflow_hidden()
3885                                    .child(self.center.render(
3886                                        &self.project,
3887                                        &self.follower_states,
3888                                        self.active_call(),
3889                                        &self.active_pane,
3890                                        self.zoomed.as_ref(),
3891                                        &self.app_state,
3892                                        cx,
3893                                    ))
3894                                    .children(
3895                                        self.zoomed_position
3896                                            .ne(&Some(DockPosition::Bottom))
3897                                            .then(|| self.bottom_dock.clone()),
3898                                    ),
3899                            )
3900                            // Right Dock
3901                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3902                                || {
3903                                    div()
3904                                        .flex()
3905                                        .flex_none()
3906                                        .overflow_hidden()
3907                                        .child(self.right_dock.clone())
3908                                },
3909                            )),
3910                    )
3911                    .children(self.render_notifications(cx))
3912                    .children(self.zoomed.as_ref().and_then(|view| {
3913                        let zoomed_view = view.upgrade()?;
3914                        let div = div()
3915                            .z_index(1)
3916                            .absolute()
3917                            .overflow_hidden()
3918                            .border_color(colors.border)
3919                            .bg(colors.background)
3920                            .child(zoomed_view)
3921                            .inset_0()
3922                            .shadow_lg();
3923
3924                        Some(match self.zoomed_position {
3925                            Some(DockPosition::Left) => div.right_2().border_r(),
3926                            Some(DockPosition::Right) => div.left_2().border_l(),
3927                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3928                            None => div.top_2().bottom_2().left_2().right_2().border(),
3929                        })
3930                    })),
3931            )
3932            .child(self.status_bar.clone())
3933            .children(if self.project.read(cx).is_disconnected() {
3934                Some(DisconnectedOverlay)
3935            } else {
3936                None
3937            })
3938    }
3939}
3940
3941impl WorkspaceStore {
3942    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3943        Self {
3944            workspaces: Default::default(),
3945            _subscriptions: vec![
3946                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3947                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3948            ],
3949            client,
3950        }
3951    }
3952
3953    pub fn update_followers(
3954        &self,
3955        project_id: Option<u64>,
3956        update: proto::update_followers::Variant,
3957        cx: &AppContext,
3958    ) -> Option<()> {
3959        let active_call = ActiveCall::try_global(cx)?;
3960        let room_id = active_call.read(cx).room()?.read(cx).id();
3961        self.client
3962            .send(proto::UpdateFollowers {
3963                room_id,
3964                project_id,
3965                variant: Some(update),
3966            })
3967            .log_err()
3968    }
3969
3970    pub async fn handle_follow(
3971        this: Model<Self>,
3972        envelope: TypedEnvelope<proto::Follow>,
3973        _: Arc<Client>,
3974        mut cx: AsyncAppContext,
3975    ) -> Result<proto::FollowResponse> {
3976        this.update(&mut cx, |this, cx| {
3977            let follower = Follower {
3978                project_id: envelope.payload.project_id,
3979                peer_id: envelope.original_sender_id()?,
3980            };
3981
3982            let mut response = proto::FollowResponse::default();
3983            this.workspaces.retain(|workspace| {
3984                workspace
3985                    .update(cx, |workspace, cx| {
3986                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3987                        if response.views.is_empty() {
3988                            response.views = handler_response.views;
3989                        } else {
3990                            response.views.extend_from_slice(&handler_response.views);
3991                        }
3992
3993                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3994                            if response.active_view_id.is_none()
3995                                || workspace.project.read(cx).remote_id() == follower.project_id
3996                            {
3997                                response.active_view_id = Some(active_view_id);
3998                            }
3999                        }
4000
4001                        if let Some(active_view) = handler_response.active_view.clone() {
4002                            if response.active_view_id.is_none()
4003                                || workspace.project.read(cx).remote_id() == follower.project_id
4004                            {
4005                                response.active_view = Some(active_view)
4006                            }
4007                        }
4008                    })
4009                    .is_ok()
4010            });
4011
4012            Ok(response)
4013        })?
4014    }
4015
4016    async fn handle_update_followers(
4017        this: Model<Self>,
4018        envelope: TypedEnvelope<proto::UpdateFollowers>,
4019        _: Arc<Client>,
4020        mut cx: AsyncAppContext,
4021    ) -> Result<()> {
4022        let leader_id = envelope.original_sender_id()?;
4023        let update = envelope.payload;
4024
4025        this.update(&mut cx, |this, cx| {
4026            this.workspaces.retain(|workspace| {
4027                workspace
4028                    .update(cx, |workspace, cx| {
4029                        let project_id = workspace.project.read(cx).remote_id();
4030                        if update.project_id != project_id && update.project_id.is_some() {
4031                            return;
4032                        }
4033                        workspace.handle_update_followers(leader_id, update.clone(), cx);
4034                    })
4035                    .is_ok()
4036            });
4037            Ok(())
4038        })?
4039    }
4040}
4041
4042impl ViewId {
4043    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4044        Ok(Self {
4045            creator: message
4046                .creator
4047                .ok_or_else(|| anyhow!("creator is missing"))?,
4048            id: message.id,
4049        })
4050    }
4051
4052    pub(crate) fn to_proto(&self) -> proto::ViewId {
4053        proto::ViewId {
4054            creator: Some(self.creator),
4055            id: self.id,
4056        }
4057    }
4058}
4059
4060pub trait WorkspaceHandle {
4061    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4062}
4063
4064impl WorkspaceHandle for View<Workspace> {
4065    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4066        self.read(cx)
4067            .worktrees(cx)
4068            .flat_map(|worktree| {
4069                let worktree_id = worktree.read(cx).id();
4070                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4071                    worktree_id,
4072                    path: f.path.clone(),
4073                })
4074            })
4075            .collect::<Vec<_>>()
4076    }
4077}
4078
4079impl std::fmt::Debug for OpenPaths {
4080    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4081        f.debug_struct("OpenPaths")
4082            .field("paths", &self.paths)
4083            .finish()
4084    }
4085}
4086
4087pub fn activate_workspace_for_project(
4088    cx: &mut AppContext,
4089    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4090) -> Option<WindowHandle<Workspace>> {
4091    for window in cx.windows() {
4092        let Some(workspace) = window.downcast::<Workspace>() else {
4093            continue;
4094        };
4095
4096        let predicate = workspace
4097            .update(cx, |workspace, cx| {
4098                let project = workspace.project.read(cx);
4099                if predicate(project, cx) {
4100                    cx.activate_window();
4101                    true
4102                } else {
4103                    false
4104                }
4105            })
4106            .log_err()
4107            .unwrap_or(false);
4108
4109        if predicate {
4110            return Some(workspace);
4111        }
4112    }
4113
4114    None
4115}
4116
4117pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4118    DB.last_workspace().await.log_err().flatten()
4119}
4120
4121actions!(collab, [OpenChannelNotes]);
4122
4123async fn join_channel_internal(
4124    channel_id: ChannelId,
4125    app_state: &Arc<AppState>,
4126    requesting_window: Option<WindowHandle<Workspace>>,
4127    active_call: &Model<ActiveCall>,
4128    cx: &mut AsyncAppContext,
4129) -> Result<bool> {
4130    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4131        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4132            return (false, None);
4133        };
4134
4135        let already_in_channel = room.channel_id() == Some(channel_id);
4136        let should_prompt = room.is_sharing_project()
4137            && room.remote_participants().len() > 0
4138            && !already_in_channel;
4139        let open_room = if already_in_channel {
4140            active_call.room().cloned()
4141        } else {
4142            None
4143        };
4144        (should_prompt, open_room)
4145    })?;
4146
4147    if let Some(room) = open_room {
4148        let task = room.update(cx, |room, cx| {
4149            if let Some((project, host)) = room.most_active_project(cx) {
4150                return Some(join_remote_project(project, host, app_state.clone(), cx));
4151            }
4152
4153            None
4154        })?;
4155        if let Some(task) = task {
4156            task.await?;
4157        }
4158        return anyhow::Ok(true);
4159    }
4160
4161    if should_prompt {
4162        if let Some(workspace) = requesting_window {
4163            let answer = workspace
4164                .update(cx, |_, cx| {
4165                    cx.prompt(
4166                        PromptLevel::Warning,
4167                        "Do you want to switch channels?",
4168                        Some("Leaving this call will unshare your current project."),
4169                        &["Yes, Join Channel", "Cancel"],
4170                    )
4171                })?
4172                .await;
4173
4174            if answer == Ok(1) {
4175                return Ok(false);
4176            }
4177        } else {
4178            return Ok(false); // unreachable!() hopefully
4179        }
4180    }
4181
4182    let client = cx.update(|cx| active_call.read(cx).client())?;
4183
4184    let mut client_status = client.status();
4185
4186    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4187    'outer: loop {
4188        let Some(status) = client_status.recv().await else {
4189            return Err(anyhow!("error connecting"));
4190        };
4191
4192        match status {
4193            Status::Connecting
4194            | Status::Authenticating
4195            | Status::Reconnecting
4196            | Status::Reauthenticating => continue,
4197            Status::Connected { .. } => break 'outer,
4198            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
4199            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
4200            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4201                return Err(ErrorCode::Disconnected.into());
4202            }
4203        }
4204    }
4205
4206    let room = active_call
4207        .update(cx, |active_call, cx| {
4208            active_call.join_channel(channel_id, cx)
4209        })?
4210        .await?;
4211
4212    let Some(room) = room else {
4213        return anyhow::Ok(true);
4214    };
4215
4216    room.update(cx, |room, _| room.room_update_completed())?
4217        .await;
4218
4219    let task = room.update(cx, |room, cx| {
4220        if let Some((project, host)) = room.most_active_project(cx) {
4221            return Some(join_remote_project(project, host, app_state.clone(), cx));
4222        }
4223
4224        // if you are the first to join a channel, share your project
4225        if room.remote_participants().len() == 0 && !room.local_participant_is_guest() {
4226            if let Some(workspace) = requesting_window {
4227                let project = workspace.update(cx, |workspace, cx| {
4228                    if !CallSettings::get_global(cx).share_on_join {
4229                        return None;
4230                    }
4231                    let project = workspace.project.read(cx);
4232                    if project.is_local()
4233                        && project.visible_worktrees(cx).any(|tree| {
4234                            tree.read(cx)
4235                                .root_entry()
4236                                .map_or(false, |entry| entry.is_dir())
4237                        })
4238                    {
4239                        Some(workspace.project.clone())
4240                    } else {
4241                        None
4242                    }
4243                });
4244                if let Ok(Some(project)) = project {
4245                    return Some(cx.spawn(|room, mut cx| async move {
4246                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
4247                            .await?;
4248                        Ok(())
4249                    }));
4250                }
4251            }
4252        }
4253
4254        None
4255    })?;
4256    if let Some(task) = task {
4257        task.await?;
4258        return anyhow::Ok(true);
4259    }
4260    anyhow::Ok(false)
4261}
4262
4263pub fn join_channel(
4264    channel_id: ChannelId,
4265    app_state: Arc<AppState>,
4266    requesting_window: Option<WindowHandle<Workspace>>,
4267    cx: &mut AppContext,
4268) -> Task<Result<()>> {
4269    let active_call = ActiveCall::global(cx);
4270    cx.spawn(|mut cx| async move {
4271        let result = join_channel_internal(
4272            channel_id,
4273            &app_state,
4274            requesting_window,
4275            &active_call,
4276            &mut cx,
4277        )
4278            .await;
4279
4280        // join channel succeeded, and opened a window
4281        if matches!(result, Ok(true)) {
4282            return anyhow::Ok(());
4283        }
4284
4285        // find an existing workspace to focus and show call controls
4286        let mut active_window =
4287            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4288        if active_window.is_none() {
4289            // no open workspaces, make one to show the error in (blergh)
4290            let (window_handle, _) = cx
4291                .update(|cx| {
4292                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4293                })?
4294                .await?;
4295
4296            if result.is_ok() {
4297                cx.update(|cx| {
4298                    cx.dispatch_action(&OpenChannelNotes);
4299                }).log_err();
4300            }
4301
4302            active_window = Some(window_handle);
4303        }
4304
4305        if let Err(err) = result {
4306            log::error!("failed to join channel: {}", err);
4307            if let Some(active_window) = active_window {
4308                active_window
4309                    .update(&mut cx, |_, cx| {
4310                        let detail: SharedString = match err.error_code() {
4311                            ErrorCode::SignedOut => {
4312                                "Please sign in to continue.".into()
4313                            }
4314                            ErrorCode::UpgradeRequired => {
4315                                "Your are running an unsupported version of Zed. Please update to continue.".into()
4316                            }
4317                            ErrorCode::NoSuchChannel => {
4318                                "No matching channel was found. Please check the link and try again.".into()
4319                            }
4320                            ErrorCode::Forbidden => {
4321                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
4322                            }
4323                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
4324                            _ => format!("{}\n\nPlease try again.", err).into(),
4325                        };
4326                        cx.prompt(
4327                            PromptLevel::Critical,
4328                            "Failed to join channel",
4329                            Some(&detail),
4330                            &["Ok"],
4331                        )
4332                    })?
4333                    .await
4334                    .ok();
4335            }
4336        }
4337
4338        // return ok, we showed the error to the user.
4339        return anyhow::Ok(());
4340    })
4341}
4342
4343pub async fn get_any_active_workspace(
4344    app_state: Arc<AppState>,
4345    mut cx: AsyncAppContext,
4346) -> anyhow::Result<WindowHandle<Workspace>> {
4347    // find an existing workspace to focus and show call controls
4348    let active_window = activate_any_workspace_window(&mut cx);
4349    if active_window.is_none() {
4350        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4351            .await?;
4352    }
4353    activate_any_workspace_window(&mut cx).context("could not open zed")
4354}
4355
4356fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4357    cx.update(|cx| {
4358        for window in cx.windows() {
4359            if let Some(workspace_window) = window.downcast::<Workspace>() {
4360                workspace_window
4361                    .update(cx, |_, cx| cx.activate_window())
4362                    .ok();
4363                return Some(workspace_window);
4364            }
4365        }
4366        None
4367    })
4368    .ok()
4369    .flatten()
4370}
4371
4372#[allow(clippy::type_complexity)]
4373pub fn open_paths(
4374    abs_paths: &[PathBuf],
4375    app_state: &Arc<AppState>,
4376    requesting_window: Option<WindowHandle<Workspace>>,
4377    cx: &mut AppContext,
4378) -> Task<
4379    anyhow::Result<(
4380        WindowHandle<Workspace>,
4381        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4382    )>,
4383> {
4384    let app_state = app_state.clone();
4385    let abs_paths = abs_paths.to_vec();
4386    // Open paths in existing workspace if possible
4387    let existing = activate_workspace_for_project(cx, {
4388        let abs_paths = abs_paths.clone();
4389        move |project, cx| project.contains_paths(&abs_paths, cx)
4390    });
4391    cx.spawn(move |mut cx| async move {
4392        if let Some(existing) = existing {
4393            Ok((
4394                existing,
4395                existing
4396                    .update(&mut cx, |workspace, cx| {
4397                        workspace.open_paths(abs_paths, OpenVisible::All, None, cx)
4398                    })?
4399                    .await,
4400            ))
4401        } else {
4402            cx.update(move |cx| {
4403                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4404            })?
4405            .await
4406        }
4407    })
4408}
4409
4410pub fn open_new(
4411    app_state: &Arc<AppState>,
4412    cx: &mut AppContext,
4413    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4414) -> Task<()> {
4415    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4416    cx.spawn(|mut cx| async move {
4417        if let Some((workspace, opened_paths)) = task.await.log_err() {
4418            workspace
4419                .update(&mut cx, |workspace, cx| {
4420                    if opened_paths.is_empty() {
4421                        init(workspace, cx)
4422                    }
4423                })
4424                .log_err();
4425        }
4426    })
4427}
4428
4429pub fn create_and_open_local_file(
4430    path: &'static Path,
4431    cx: &mut ViewContext<Workspace>,
4432    default_content: impl 'static + Send + FnOnce() -> Rope,
4433) -> Task<Result<Box<dyn ItemHandle>>> {
4434    cx.spawn(|workspace, mut cx| async move {
4435        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4436        if !fs.is_file(path).await {
4437            fs.create_file(path, Default::default()).await?;
4438            fs.save(path, &default_content(), Default::default())
4439                .await?;
4440        }
4441
4442        let mut items = workspace
4443            .update(&mut cx, |workspace, cx| {
4444                workspace.with_local_workspace(cx, |workspace, cx| {
4445                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4446                })
4447            })?
4448            .await?
4449            .await;
4450
4451        let item = items.pop().flatten();
4452        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4453    })
4454}
4455
4456pub fn join_remote_project(
4457    project_id: u64,
4458    follow_user_id: u64,
4459    app_state: Arc<AppState>,
4460    cx: &mut AppContext,
4461) -> Task<Result<()>> {
4462    let windows = cx.windows();
4463    cx.spawn(|mut cx| async move {
4464        let existing_workspace = windows.into_iter().find_map(|window| {
4465            window.downcast::<Workspace>().and_then(|window| {
4466                window
4467                    .update(&mut cx, |workspace, cx| {
4468                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4469                            Some(window)
4470                        } else {
4471                            None
4472                        }
4473                    })
4474                    .unwrap_or(None)
4475            })
4476        });
4477
4478        let workspace = if let Some(existing_workspace) = existing_workspace {
4479            existing_workspace
4480        } else {
4481            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4482            let room = active_call
4483                .read_with(&cx, |call, _| call.room().cloned())?
4484                .ok_or_else(|| anyhow!("not in a call"))?;
4485            let project = room
4486                .update(&mut cx, |room, cx| {
4487                    room.join_project(
4488                        project_id,
4489                        app_state.languages.clone(),
4490                        app_state.fs.clone(),
4491                        cx,
4492                    )
4493                })?
4494                .await?;
4495
4496            let window_bounds_override = window_bounds_env_override(&cx);
4497            cx.update(|cx| {
4498                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4499                cx.open_window(options, |cx| {
4500                    cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4501                })
4502            })?
4503        };
4504
4505        workspace.update(&mut cx, |workspace, cx| {
4506            cx.activate(true);
4507            cx.activate_window();
4508
4509            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4510                let follow_peer_id = room
4511                    .read(cx)
4512                    .remote_participants()
4513                    .iter()
4514                    .find(|(_, participant)| participant.user.id == follow_user_id)
4515                    .map(|(_, p)| p.peer_id)
4516                    .or_else(|| {
4517                        // If we couldn't follow the given user, follow the host instead.
4518                        let collaborator = workspace
4519                            .project()
4520                            .read(cx)
4521                            .collaborators()
4522                            .values()
4523                            .find(|collaborator| collaborator.replica_id == 0)?;
4524                        Some(collaborator.peer_id)
4525                    });
4526
4527                if let Some(follow_peer_id) = follow_peer_id {
4528                    workspace.follow(follow_peer_id, cx);
4529                }
4530            }
4531        })?;
4532
4533        anyhow::Ok(())
4534    })
4535}
4536
4537pub fn restart(_: &Restart, cx: &mut AppContext) {
4538    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4539    let mut workspace_windows = cx
4540        .windows()
4541        .into_iter()
4542        .filter_map(|window| window.downcast::<Workspace>())
4543        .collect::<Vec<_>>();
4544
4545    // If multiple windows have unsaved changes, and need a save prompt,
4546    // prompt in the active window before switching to a different window.
4547    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
4548
4549    let mut prompt = None;
4550    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4551        prompt = window
4552            .update(cx, |_, cx| {
4553                cx.prompt(
4554                    PromptLevel::Info,
4555                    "Are you sure you want to restart?",
4556                    None,
4557                    &["Restart", "Cancel"],
4558                )
4559            })
4560            .ok();
4561    }
4562
4563    cx.spawn(|mut cx| async move {
4564        if let Some(prompt) = prompt {
4565            let answer = prompt.await?;
4566            if answer != 0 {
4567                return Ok(());
4568            }
4569        }
4570
4571        // If the user cancels any save prompt, then keep the app open.
4572        for window in workspace_windows {
4573            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4574                workspace.prepare_to_close(true, cx)
4575            }) {
4576                if !should_close.await? {
4577                    return Ok(());
4578                }
4579            }
4580        }
4581
4582        cx.update(|cx| cx.restart())
4583    })
4584    .detach_and_log_err(cx);
4585}
4586
4587fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4588    let mut parts = value.split(',');
4589    let x: usize = parts.next()?.parse().ok()?;
4590    let y: usize = parts.next()?.parse().ok()?;
4591    Some(point((x as f64).into(), (y as f64).into()))
4592}
4593
4594fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4595    let mut parts = value.split(',');
4596    let width: usize = parts.next()?.parse().ok()?;
4597    let height: usize = parts.next()?.parse().ok()?;
4598    Some(size((width as f64).into(), (height as f64).into()))
4599}
4600
4601pub fn titlebar_height(cx: &mut WindowContext) -> Pixels {
4602    (1.75 * cx.rem_size()).max(px(32.))
4603}
4604
4605struct DisconnectedOverlay;
4606
4607impl Element for DisconnectedOverlay {
4608    type State = AnyElement;
4609
4610    fn request_layout(
4611        &mut self,
4612        _: Option<Self::State>,
4613        cx: &mut ElementContext,
4614    ) -> (LayoutId, Self::State) {
4615        let mut background = cx.theme().colors().elevated_surface_background;
4616        background.fade_out(0.2);
4617        let mut overlay = div()
4618            .bg(background)
4619            .absolute()
4620            .left_0()
4621            .top(titlebar_height(cx))
4622            .size_full()
4623            .flex()
4624            .items_center()
4625            .justify_center()
4626            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4627            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4628            .child(Label::new(
4629                "Your connection to the remote project has been lost.",
4630            ))
4631            .into_any();
4632        (overlay.request_layout(cx), overlay)
4633    }
4634
4635    fn paint(
4636        &mut self,
4637        bounds: Bounds<Pixels>,
4638        overlay: &mut Self::State,
4639        cx: &mut ElementContext,
4640    ) {
4641        cx.with_z_index(u16::MAX, |cx| {
4642            cx.add_opaque_layer(bounds);
4643            overlay.paint(cx);
4644        })
4645    }
4646}
4647
4648impl IntoElement for DisconnectedOverlay {
4649    type Element = Self;
4650
4651    fn element_id(&self) -> Option<ui::prelude::ElementId> {
4652        None
4653    }
4654
4655    fn into_element(self) -> Self::Element {
4656        self
4657    }
4658}
4659
4660#[cfg(test)]
4661mod tests {
4662    use std::{cell::RefCell, rc::Rc};
4663
4664    use super::*;
4665    use crate::{
4666        dock::{test::TestPanel, PanelEvent},
4667        item::{
4668            test::{TestItem, TestProjectItem},
4669            ItemEvent,
4670        },
4671    };
4672    use fs::FakeFs;
4673    use gpui::{px, DismissEvent, TestAppContext, VisualTestContext};
4674    use project::{Project, ProjectEntryId};
4675    use serde_json::json;
4676    use settings::SettingsStore;
4677
4678    #[gpui::test]
4679    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4680        init_test(cx);
4681
4682        let fs = FakeFs::new(cx.executor());
4683        let project = Project::test(fs, [], cx).await;
4684        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4685
4686        // Adding an item with no ambiguity renders the tab without detail.
4687        let item1 = cx.new_view(|cx| {
4688            let mut item = TestItem::new(cx);
4689            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4690            item
4691        });
4692        workspace.update(cx, |workspace, cx| {
4693            workspace.add_item_to_active_pane(Box::new(item1.clone()), cx);
4694        });
4695        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4696
4697        // Adding an item that creates ambiguity increases the level of detail on
4698        // both tabs.
4699        let item2 = cx.new_view(|cx| {
4700            let mut item = TestItem::new(cx);
4701            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4702            item
4703        });
4704        workspace.update(cx, |workspace, cx| {
4705            workspace.add_item_to_active_pane(Box::new(item2.clone()), cx);
4706        });
4707        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4708        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4709
4710        // Adding an item that creates ambiguity increases the level of detail only
4711        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4712        // we stop at the highest detail available.
4713        let item3 = cx.new_view(|cx| {
4714            let mut item = TestItem::new(cx);
4715            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4716            item
4717        });
4718        workspace.update(cx, |workspace, cx| {
4719            workspace.add_item_to_active_pane(Box::new(item3.clone()), cx);
4720        });
4721        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4722        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4723        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4724    }
4725
4726    #[gpui::test]
4727    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4728        init_test(cx);
4729
4730        let fs = FakeFs::new(cx.executor());
4731        fs.insert_tree(
4732            "/root1",
4733            json!({
4734                "one.txt": "",
4735                "two.txt": "",
4736            }),
4737        )
4738        .await;
4739        fs.insert_tree(
4740            "/root2",
4741            json!({
4742                "three.txt": "",
4743            }),
4744        )
4745        .await;
4746
4747        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4748        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4749        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4750        let worktree_id = project.update(cx, |project, cx| {
4751            project.worktrees().next().unwrap().read(cx).id()
4752        });
4753
4754        let item1 = cx.new_view(|cx| {
4755            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4756        });
4757        let item2 = cx.new_view(|cx| {
4758            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4759        });
4760
4761        // Add an item to an empty pane
4762        workspace.update(cx, |workspace, cx| {
4763            workspace.add_item_to_active_pane(Box::new(item1), cx)
4764        });
4765        project.update(cx, |project, cx| {
4766            assert_eq!(
4767                project.active_entry(),
4768                project
4769                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4770                    .map(|e| e.id)
4771            );
4772        });
4773        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4774
4775        // Add a second item to a non-empty pane
4776        workspace.update(cx, |workspace, cx| {
4777            workspace.add_item_to_active_pane(Box::new(item2), cx)
4778        });
4779        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
4780        project.update(cx, |project, cx| {
4781            assert_eq!(
4782                project.active_entry(),
4783                project
4784                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4785                    .map(|e| e.id)
4786            );
4787        });
4788
4789        // Close the active item
4790        pane.update(cx, |pane, cx| {
4791            pane.close_active_item(&Default::default(), cx).unwrap()
4792        })
4793        .await
4794        .unwrap();
4795        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4796        project.update(cx, |project, cx| {
4797            assert_eq!(
4798                project.active_entry(),
4799                project
4800                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4801                    .map(|e| e.id)
4802            );
4803        });
4804
4805        // Add a project folder
4806        project
4807            .update(cx, |project, cx| {
4808                project.find_or_create_local_worktree("/root2", true, cx)
4809            })
4810            .await
4811            .unwrap();
4812        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
4813
4814        // Remove a project folder
4815        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4816        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
4817    }
4818
4819    #[gpui::test]
4820    async fn test_close_window(cx: &mut TestAppContext) {
4821        init_test(cx);
4822
4823        let fs = FakeFs::new(cx.executor());
4824        fs.insert_tree("/root", json!({ "one": "" })).await;
4825
4826        let project = Project::test(fs, ["root".as_ref()], cx).await;
4827        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4828
4829        // When there are no dirty items, there's nothing to do.
4830        let item1 = cx.new_view(|cx| TestItem::new(cx));
4831        workspace.update(cx, |w, cx| {
4832            w.add_item_to_active_pane(Box::new(item1.clone()), cx)
4833        });
4834        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4835        assert!(task.await.unwrap());
4836
4837        // When there are dirty untitled items, prompt to save each one. If the user
4838        // cancels any prompt, then abort.
4839        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
4840        let item3 = cx.new_view(|cx| {
4841            TestItem::new(cx)
4842                .with_dirty(true)
4843                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4844        });
4845        workspace.update(cx, |w, cx| {
4846            w.add_item_to_active_pane(Box::new(item2.clone()), cx);
4847            w.add_item_to_active_pane(Box::new(item3.clone()), cx);
4848        });
4849        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4850        cx.executor().run_until_parked();
4851        cx.simulate_prompt_answer(2); // cancel save all
4852        cx.executor().run_until_parked();
4853        cx.simulate_prompt_answer(2); // cancel save all
4854        cx.executor().run_until_parked();
4855        assert!(!cx.has_pending_prompt());
4856        assert!(!task.await.unwrap());
4857    }
4858
4859    #[gpui::test]
4860    async fn test_close_pane_items(cx: &mut TestAppContext) {
4861        init_test(cx);
4862
4863        let fs = FakeFs::new(cx.executor());
4864
4865        let project = Project::test(fs, None, cx).await;
4866        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4867
4868        let item1 = cx.new_view(|cx| {
4869            TestItem::new(cx)
4870                .with_dirty(true)
4871                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4872        });
4873        let item2 = cx.new_view(|cx| {
4874            TestItem::new(cx)
4875                .with_dirty(true)
4876                .with_conflict(true)
4877                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4878        });
4879        let item3 = cx.new_view(|cx| {
4880            TestItem::new(cx)
4881                .with_dirty(true)
4882                .with_conflict(true)
4883                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4884        });
4885        let item4 = cx.new_view(|cx| {
4886            TestItem::new(cx)
4887                .with_dirty(true)
4888                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4889        });
4890        let pane = workspace.update(cx, |workspace, cx| {
4891            workspace.add_item_to_active_pane(Box::new(item1.clone()), cx);
4892            workspace.add_item_to_active_pane(Box::new(item2.clone()), cx);
4893            workspace.add_item_to_active_pane(Box::new(item3.clone()), cx);
4894            workspace.add_item_to_active_pane(Box::new(item4.clone()), cx);
4895            workspace.active_pane().clone()
4896        });
4897
4898        let close_items = pane.update(cx, |pane, cx| {
4899            pane.activate_item(1, true, true, cx);
4900            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4901            let item1_id = item1.item_id();
4902            let item3_id = item3.item_id();
4903            let item4_id = item4.item_id();
4904            pane.close_items(cx, SaveIntent::Close, move |id| {
4905                [item1_id, item3_id, item4_id].contains(&id)
4906            })
4907        });
4908        cx.executor().run_until_parked();
4909
4910        assert!(cx.has_pending_prompt());
4911        // Ignore "Save all" prompt
4912        cx.simulate_prompt_answer(2);
4913        cx.executor().run_until_parked();
4914        // There's a prompt to save item 1.
4915        pane.update(cx, |pane, _| {
4916            assert_eq!(pane.items_len(), 4);
4917            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4918        });
4919        // Confirm saving item 1.
4920        cx.simulate_prompt_answer(0);
4921        cx.executor().run_until_parked();
4922
4923        // Item 1 is saved. There's a prompt to save item 3.
4924        pane.update(cx, |pane, cx| {
4925            assert_eq!(item1.read(cx).save_count, 1);
4926            assert_eq!(item1.read(cx).save_as_count, 0);
4927            assert_eq!(item1.read(cx).reload_count, 0);
4928            assert_eq!(pane.items_len(), 3);
4929            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4930        });
4931        assert!(cx.has_pending_prompt());
4932
4933        // Cancel saving item 3.
4934        cx.simulate_prompt_answer(1);
4935        cx.executor().run_until_parked();
4936
4937        // Item 3 is reloaded. There's a prompt to save item 4.
4938        pane.update(cx, |pane, cx| {
4939            assert_eq!(item3.read(cx).save_count, 0);
4940            assert_eq!(item3.read(cx).save_as_count, 0);
4941            assert_eq!(item3.read(cx).reload_count, 1);
4942            assert_eq!(pane.items_len(), 2);
4943            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4944        });
4945        assert!(cx.has_pending_prompt());
4946
4947        // Confirm saving item 4.
4948        cx.simulate_prompt_answer(0);
4949        cx.executor().run_until_parked();
4950
4951        // There's a prompt for a path for item 4.
4952        cx.simulate_new_path_selection(|_| Some(Default::default()));
4953        close_items.await.unwrap();
4954
4955        // The requested items are closed.
4956        pane.update(cx, |pane, cx| {
4957            assert_eq!(item4.read(cx).save_count, 0);
4958            assert_eq!(item4.read(cx).save_as_count, 1);
4959            assert_eq!(item4.read(cx).reload_count, 0);
4960            assert_eq!(pane.items_len(), 1);
4961            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4962        });
4963    }
4964
4965    #[gpui::test]
4966    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4967        init_test(cx);
4968
4969        let fs = FakeFs::new(cx.executor());
4970        let project = Project::test(fs, [], cx).await;
4971        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4972
4973        // Create several workspace items with single project entries, and two
4974        // workspace items with multiple project entries.
4975        let single_entry_items = (0..=4)
4976            .map(|project_entry_id| {
4977                cx.new_view(|cx| {
4978                    TestItem::new(cx)
4979                        .with_dirty(true)
4980                        .with_project_items(&[TestProjectItem::new(
4981                            project_entry_id,
4982                            &format!("{project_entry_id}.txt"),
4983                            cx,
4984                        )])
4985                })
4986            })
4987            .collect::<Vec<_>>();
4988        let item_2_3 = cx.new_view(|cx| {
4989            TestItem::new(cx)
4990                .with_dirty(true)
4991                .with_singleton(false)
4992                .with_project_items(&[
4993                    single_entry_items[2].read(cx).project_items[0].clone(),
4994                    single_entry_items[3].read(cx).project_items[0].clone(),
4995                ])
4996        });
4997        let item_3_4 = cx.new_view(|cx| {
4998            TestItem::new(cx)
4999                .with_dirty(true)
5000                .with_singleton(false)
5001                .with_project_items(&[
5002                    single_entry_items[3].read(cx).project_items[0].clone(),
5003                    single_entry_items[4].read(cx).project_items[0].clone(),
5004                ])
5005        });
5006
5007        // Create two panes that contain the following project entries:
5008        //   left pane:
5009        //     multi-entry items:   (2, 3)
5010        //     single-entry items:  0, 1, 2, 3, 4
5011        //   right pane:
5012        //     single-entry items:  1
5013        //     multi-entry items:   (3, 4)
5014        let left_pane = workspace.update(cx, |workspace, cx| {
5015            let left_pane = workspace.active_pane().clone();
5016            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), cx);
5017            for item in single_entry_items {
5018                workspace.add_item_to_active_pane(Box::new(item), cx);
5019            }
5020            left_pane.update(cx, |pane, cx| {
5021                pane.activate_item(2, true, true, cx);
5022            });
5023
5024            let right_pane = workspace
5025                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
5026                .unwrap();
5027
5028            right_pane.update(cx, |pane, cx| {
5029                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
5030            });
5031
5032            left_pane
5033        });
5034
5035        cx.focus_view(&left_pane);
5036
5037        // When closing all of the items in the left pane, we should be prompted twice:
5038        // once for project entry 0, and once for project entry 2. Project entries 1,
5039        // 3, and 4 are all still open in the other paten. After those two
5040        // prompts, the task should complete.
5041
5042        let close = left_pane.update(cx, |pane, cx| {
5043            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
5044        });
5045        cx.executor().run_until_parked();
5046
5047        // Discard "Save all" prompt
5048        cx.simulate_prompt_answer(2);
5049
5050        cx.executor().run_until_parked();
5051        left_pane.update(cx, |pane, cx| {
5052            assert_eq!(
5053                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5054                &[ProjectEntryId::from_proto(0)]
5055            );
5056        });
5057        cx.simulate_prompt_answer(0);
5058
5059        cx.executor().run_until_parked();
5060        left_pane.update(cx, |pane, cx| {
5061            assert_eq!(
5062                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5063                &[ProjectEntryId::from_proto(2)]
5064            );
5065        });
5066        cx.simulate_prompt_answer(0);
5067
5068        cx.executor().run_until_parked();
5069        close.await.unwrap();
5070        left_pane.update(cx, |pane, _| {
5071            assert_eq!(pane.items_len(), 0);
5072        });
5073    }
5074
5075    #[gpui::test]
5076    async fn test_autosave(cx: &mut gpui::TestAppContext) {
5077        init_test(cx);
5078
5079        let fs = FakeFs::new(cx.executor());
5080        let project = Project::test(fs, [], cx).await;
5081        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5082        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5083
5084        let item = cx.new_view(|cx| {
5085            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5086        });
5087        let item_id = item.entity_id();
5088        workspace.update(cx, |workspace, cx| {
5089            workspace.add_item_to_active_pane(Box::new(item.clone()), cx);
5090        });
5091
5092        // Autosave on window change.
5093        item.update(cx, |item, cx| {
5094            cx.update_global(|settings: &mut SettingsStore, cx| {
5095                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5096                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
5097                })
5098            });
5099            item.is_dirty = true;
5100        });
5101
5102        // Deactivating the window saves the file.
5103        cx.deactivate_window();
5104        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5105
5106        // Autosave on focus change.
5107        item.update(cx, |item, cx| {
5108            cx.focus_self();
5109            cx.update_global(|settings: &mut SettingsStore, cx| {
5110                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5111                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5112                })
5113            });
5114            item.is_dirty = true;
5115        });
5116
5117        // Blurring the item saves the file.
5118        item.update(cx, |_, cx| cx.blur());
5119        cx.executor().run_until_parked();
5120        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
5121
5122        // Deactivating the window still saves the file.
5123        cx.update(|cx| cx.activate_window());
5124        item.update(cx, |item, cx| {
5125            cx.focus_self();
5126            item.is_dirty = true;
5127        });
5128        cx.deactivate_window();
5129
5130        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5131
5132        // Autosave after delay.
5133        item.update(cx, |item, cx| {
5134            cx.update_global(|settings: &mut SettingsStore, cx| {
5135                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5136                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5137                })
5138            });
5139            item.is_dirty = true;
5140            cx.emit(ItemEvent::Edit);
5141        });
5142
5143        // Delay hasn't fully expired, so the file is still dirty and unsaved.
5144        cx.executor().advance_clock(Duration::from_millis(250));
5145        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5146
5147        // After delay expires, the file is saved.
5148        cx.executor().advance_clock(Duration::from_millis(250));
5149        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
5150
5151        // Autosave on focus change, ensuring closing the tab counts as such.
5152        item.update(cx, |item, cx| {
5153            cx.update_global(|settings: &mut SettingsStore, cx| {
5154                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5155                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5156                })
5157            });
5158            item.is_dirty = true;
5159        });
5160
5161        pane.update(cx, |pane, cx| {
5162            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5163        })
5164        .await
5165        .unwrap();
5166        assert!(!cx.has_pending_prompt());
5167        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5168
5169        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5170        workspace.update(cx, |workspace, cx| {
5171            workspace.add_item_to_active_pane(Box::new(item.clone()), cx);
5172        });
5173        item.update(cx, |item, cx| {
5174            item.project_items[0].update(cx, |item, _| {
5175                item.entry_id = None;
5176            });
5177            item.is_dirty = true;
5178            cx.blur();
5179        });
5180        cx.run_until_parked();
5181        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5182
5183        // Ensure autosave is prevented for deleted files also when closing the buffer.
5184        let _close_items = pane.update(cx, |pane, cx| {
5185            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5186        });
5187        cx.run_until_parked();
5188        assert!(cx.has_pending_prompt());
5189        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5190    }
5191
5192    #[gpui::test]
5193    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5194        init_test(cx);
5195
5196        let fs = FakeFs::new(cx.executor());
5197
5198        let project = Project::test(fs, [], cx).await;
5199        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5200
5201        let item = cx.new_view(|cx| {
5202            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5203        });
5204        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5205        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
5206        let toolbar_notify_count = Rc::new(RefCell::new(0));
5207
5208        workspace.update(cx, |workspace, cx| {
5209            workspace.add_item_to_active_pane(Box::new(item.clone()), cx);
5210            let toolbar_notification_count = toolbar_notify_count.clone();
5211            cx.observe(&toolbar, move |_, _, _| {
5212                *toolbar_notification_count.borrow_mut() += 1
5213            })
5214            .detach();
5215        });
5216
5217        pane.update(cx, |pane, _| {
5218            assert!(!pane.can_navigate_backward());
5219            assert!(!pane.can_navigate_forward());
5220        });
5221
5222        item.update(cx, |item, cx| {
5223            item.set_state("one".to_string(), cx);
5224        });
5225
5226        // Toolbar must be notified to re-render the navigation buttons
5227        assert_eq!(*toolbar_notify_count.borrow(), 1);
5228
5229        pane.update(cx, |pane, _| {
5230            assert!(pane.can_navigate_backward());
5231            assert!(!pane.can_navigate_forward());
5232        });
5233
5234        workspace
5235            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5236            .await
5237            .unwrap();
5238
5239        assert_eq!(*toolbar_notify_count.borrow(), 2);
5240        pane.update(cx, |pane, _| {
5241            assert!(!pane.can_navigate_backward());
5242            assert!(pane.can_navigate_forward());
5243        });
5244    }
5245
5246    #[gpui::test]
5247    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5248        init_test(cx);
5249        let fs = FakeFs::new(cx.executor());
5250
5251        let project = Project::test(fs, [], cx).await;
5252        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5253
5254        let panel = workspace.update(cx, |workspace, cx| {
5255            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5256            workspace.add_panel(panel.clone(), cx);
5257
5258            workspace
5259                .right_dock()
5260                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5261
5262            panel
5263        });
5264
5265        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5266        pane.update(cx, |pane, cx| {
5267            let item = cx.new_view(|cx| TestItem::new(cx));
5268            pane.add_item(Box::new(item), true, true, None, cx);
5269        });
5270
5271        // Transfer focus from center to panel
5272        workspace.update(cx, |workspace, cx| {
5273            workspace.toggle_panel_focus::<TestPanel>(cx);
5274        });
5275
5276        workspace.update(cx, |workspace, cx| {
5277            assert!(workspace.right_dock().read(cx).is_open());
5278            assert!(!panel.is_zoomed(cx));
5279            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5280        });
5281
5282        // Transfer focus from panel to center
5283        workspace.update(cx, |workspace, cx| {
5284            workspace.toggle_panel_focus::<TestPanel>(cx);
5285        });
5286
5287        workspace.update(cx, |workspace, cx| {
5288            assert!(workspace.right_dock().read(cx).is_open());
5289            assert!(!panel.is_zoomed(cx));
5290            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5291        });
5292
5293        // Close the dock
5294        workspace.update(cx, |workspace, cx| {
5295            workspace.toggle_dock(DockPosition::Right, cx);
5296        });
5297
5298        workspace.update(cx, |workspace, cx| {
5299            assert!(!workspace.right_dock().read(cx).is_open());
5300            assert!(!panel.is_zoomed(cx));
5301            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5302        });
5303
5304        // Open the dock
5305        workspace.update(cx, |workspace, cx| {
5306            workspace.toggle_dock(DockPosition::Right, cx);
5307        });
5308
5309        workspace.update(cx, |workspace, cx| {
5310            assert!(workspace.right_dock().read(cx).is_open());
5311            assert!(!panel.is_zoomed(cx));
5312            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5313        });
5314
5315        // Focus and zoom panel
5316        panel.update(cx, |panel, cx| {
5317            cx.focus_self();
5318            panel.set_zoomed(true, cx)
5319        });
5320
5321        workspace.update(cx, |workspace, cx| {
5322            assert!(workspace.right_dock().read(cx).is_open());
5323            assert!(panel.is_zoomed(cx));
5324            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5325        });
5326
5327        // Transfer focus to the center closes the dock
5328        workspace.update(cx, |workspace, cx| {
5329            workspace.toggle_panel_focus::<TestPanel>(cx);
5330        });
5331
5332        workspace.update(cx, |workspace, cx| {
5333            assert!(!workspace.right_dock().read(cx).is_open());
5334            assert!(panel.is_zoomed(cx));
5335            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5336        });
5337
5338        // Transferring focus back to the panel keeps it zoomed
5339        workspace.update(cx, |workspace, cx| {
5340            workspace.toggle_panel_focus::<TestPanel>(cx);
5341        });
5342
5343        workspace.update(cx, |workspace, cx| {
5344            assert!(workspace.right_dock().read(cx).is_open());
5345            assert!(panel.is_zoomed(cx));
5346            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5347        });
5348
5349        // Close the dock while it is zoomed
5350        workspace.update(cx, |workspace, cx| {
5351            workspace.toggle_dock(DockPosition::Right, cx)
5352        });
5353
5354        workspace.update(cx, |workspace, cx| {
5355            assert!(!workspace.right_dock().read(cx).is_open());
5356            assert!(panel.is_zoomed(cx));
5357            assert!(workspace.zoomed.is_none());
5358            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5359        });
5360
5361        // Opening the dock, when it's zoomed, retains focus
5362        workspace.update(cx, |workspace, cx| {
5363            workspace.toggle_dock(DockPosition::Right, cx)
5364        });
5365
5366        workspace.update(cx, |workspace, cx| {
5367            assert!(workspace.right_dock().read(cx).is_open());
5368            assert!(panel.is_zoomed(cx));
5369            assert!(workspace.zoomed.is_some());
5370            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5371        });
5372
5373        // Unzoom and close the panel, zoom the active pane.
5374        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5375        workspace.update(cx, |workspace, cx| {
5376            workspace.toggle_dock(DockPosition::Right, cx)
5377        });
5378        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5379
5380        // Opening a dock unzooms the pane.
5381        workspace.update(cx, |workspace, cx| {
5382            workspace.toggle_dock(DockPosition::Right, cx)
5383        });
5384        workspace.update(cx, |workspace, cx| {
5385            let pane = pane.read(cx);
5386            assert!(!pane.is_zoomed());
5387            assert!(!pane.focus_handle(cx).is_focused(cx));
5388            assert!(workspace.right_dock().read(cx).is_open());
5389            assert!(workspace.zoomed.is_none());
5390        });
5391    }
5392
5393    struct TestModal(FocusHandle);
5394
5395    impl TestModal {
5396        fn new(cx: &mut ViewContext<Self>) -> Self {
5397            Self(cx.focus_handle())
5398        }
5399    }
5400
5401    impl EventEmitter<DismissEvent> for TestModal {}
5402
5403    impl FocusableView for TestModal {
5404        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5405            self.0.clone()
5406        }
5407    }
5408
5409    impl ModalView for TestModal {}
5410
5411    impl Render for TestModal {
5412        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5413            div().track_focus(&self.0)
5414        }
5415    }
5416
5417    #[gpui::test]
5418    async fn test_panels(cx: &mut gpui::TestAppContext) {
5419        init_test(cx);
5420        let fs = FakeFs::new(cx.executor());
5421
5422        let project = Project::test(fs, [], cx).await;
5423        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5424
5425        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5426            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5427            workspace.add_panel(panel_1.clone(), cx);
5428            workspace
5429                .left_dock()
5430                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5431            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5432            workspace.add_panel(panel_2.clone(), cx);
5433            workspace
5434                .right_dock()
5435                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5436
5437            let left_dock = workspace.left_dock();
5438            assert_eq!(
5439                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5440                panel_1.panel_id()
5441            );
5442            assert_eq!(
5443                left_dock.read(cx).active_panel_size(cx).unwrap(),
5444                panel_1.size(cx)
5445            );
5446
5447            left_dock.update(cx, |left_dock, cx| {
5448                left_dock.resize_active_panel(Some(px(1337.)), cx)
5449            });
5450            assert_eq!(
5451                workspace
5452                    .right_dock()
5453                    .read(cx)
5454                    .visible_panel()
5455                    .unwrap()
5456                    .panel_id(),
5457                panel_2.panel_id(),
5458            );
5459
5460            (panel_1, panel_2)
5461        });
5462
5463        // Move panel_1 to the right
5464        panel_1.update(cx, |panel_1, cx| {
5465            panel_1.set_position(DockPosition::Right, cx)
5466        });
5467
5468        workspace.update(cx, |workspace, cx| {
5469            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5470            // Since it was the only panel on the left, the left dock should now be closed.
5471            assert!(!workspace.left_dock().read(cx).is_open());
5472            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5473            let right_dock = workspace.right_dock();
5474            assert_eq!(
5475                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5476                panel_1.panel_id()
5477            );
5478            assert_eq!(
5479                right_dock.read(cx).active_panel_size(cx).unwrap(),
5480                px(1337.)
5481            );
5482
5483            // Now we move panel_2 to the left
5484            panel_2.set_position(DockPosition::Left, cx);
5485        });
5486
5487        workspace.update(cx, |workspace, cx| {
5488            // Since panel_2 was not visible on the right, we don't open the left dock.
5489            assert!(!workspace.left_dock().read(cx).is_open());
5490            // And the right dock is unaffected in its displaying of panel_1
5491            assert!(workspace.right_dock().read(cx).is_open());
5492            assert_eq!(
5493                workspace
5494                    .right_dock()
5495                    .read(cx)
5496                    .visible_panel()
5497                    .unwrap()
5498                    .panel_id(),
5499                panel_1.panel_id(),
5500            );
5501        });
5502
5503        // Move panel_1 back to the left
5504        panel_1.update(cx, |panel_1, cx| {
5505            panel_1.set_position(DockPosition::Left, cx)
5506        });
5507
5508        workspace.update(cx, |workspace, cx| {
5509            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5510            let left_dock = workspace.left_dock();
5511            assert!(left_dock.read(cx).is_open());
5512            assert_eq!(
5513                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5514                panel_1.panel_id()
5515            );
5516            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5517            // And the right dock should be closed as it no longer has any panels.
5518            assert!(!workspace.right_dock().read(cx).is_open());
5519
5520            // Now we move panel_1 to the bottom
5521            panel_1.set_position(DockPosition::Bottom, cx);
5522        });
5523
5524        workspace.update(cx, |workspace, cx| {
5525            // Since panel_1 was visible on the left, we close the left dock.
5526            assert!(!workspace.left_dock().read(cx).is_open());
5527            // The bottom dock is sized based on the panel's default size,
5528            // since the panel orientation changed from vertical to horizontal.
5529            let bottom_dock = workspace.bottom_dock();
5530            assert_eq!(
5531                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5532                panel_1.size(cx),
5533            );
5534            // Close bottom dock and move panel_1 back to the left.
5535            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5536            panel_1.set_position(DockPosition::Left, cx);
5537        });
5538
5539        // Emit activated event on panel 1
5540        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5541
5542        // Now the left dock is open and panel_1 is active and focused.
5543        workspace.update(cx, |workspace, cx| {
5544            let left_dock = workspace.left_dock();
5545            assert!(left_dock.read(cx).is_open());
5546            assert_eq!(
5547                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5548                panel_1.panel_id(),
5549            );
5550            assert!(panel_1.focus_handle(cx).is_focused(cx));
5551        });
5552
5553        // Emit closed event on panel 2, which is not active
5554        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5555
5556        // Wo don't close the left dock, because panel_2 wasn't the active panel
5557        workspace.update(cx, |workspace, cx| {
5558            let left_dock = workspace.left_dock();
5559            assert!(left_dock.read(cx).is_open());
5560            assert_eq!(
5561                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5562                panel_1.panel_id(),
5563            );
5564        });
5565
5566        // Emitting a ZoomIn event shows the panel as zoomed.
5567        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5568        workspace.update(cx, |workspace, _| {
5569            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5570            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5571        });
5572
5573        // Move panel to another dock while it is zoomed
5574        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5575        workspace.update(cx, |workspace, _| {
5576            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5577
5578            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5579        });
5580
5581        // This is a helper for getting a:
5582        // - valid focus on an element,
5583        // - that isn't a part of the panes and panels system of the Workspace,
5584        // - and doesn't trigger the 'on_focus_lost' API.
5585        let focus_other_view = {
5586            let workspace = workspace.clone();
5587            move |cx: &mut VisualTestContext| {
5588                workspace.update(cx, |workspace, cx| {
5589                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5590                        workspace.toggle_modal(cx, TestModal::new);
5591                        workspace.toggle_modal(cx, TestModal::new);
5592                    } else {
5593                        workspace.toggle_modal(cx, TestModal::new);
5594                    }
5595                })
5596            }
5597        };
5598
5599        // If focus is transferred to another view that's not a panel or another pane, we still show
5600        // the panel as zoomed.
5601        focus_other_view(cx);
5602        workspace.update(cx, |workspace, _| {
5603            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5604            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5605        });
5606
5607        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5608        workspace.update(cx, |_, cx| cx.focus_self());
5609        workspace.update(cx, |workspace, _| {
5610            assert_eq!(workspace.zoomed, None);
5611            assert_eq!(workspace.zoomed_position, None);
5612        });
5613
5614        // If focus is transferred again to another view that's not a panel or a pane, we won't
5615        // show the panel as zoomed because it wasn't zoomed before.
5616        focus_other_view(cx);
5617        workspace.update(cx, |workspace, _| {
5618            assert_eq!(workspace.zoomed, None);
5619            assert_eq!(workspace.zoomed_position, None);
5620        });
5621
5622        // When the panel is activated, it is zoomed again.
5623        cx.dispatch_action(ToggleRightDock);
5624        workspace.update(cx, |workspace, _| {
5625            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5626            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5627        });
5628
5629        // Emitting a ZoomOut event unzooms the panel.
5630        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5631        workspace.update(cx, |workspace, _| {
5632            assert_eq!(workspace.zoomed, None);
5633            assert_eq!(workspace.zoomed_position, None);
5634        });
5635
5636        // Emit closed event on panel 1, which is active
5637        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5638
5639        // Now the left dock is closed, because panel_1 was the active panel
5640        workspace.update(cx, |workspace, cx| {
5641            let right_dock = workspace.right_dock();
5642            assert!(!right_dock.read(cx).is_open());
5643        });
5644    }
5645
5646    pub fn init_test(cx: &mut TestAppContext) {
5647        cx.update(|cx| {
5648            let settings_store = SettingsStore::test(cx);
5649            cx.set_global(settings_store);
5650            theme::init(theme::LoadThemes::JustBase, cx);
5651            language::init(cx);
5652            crate::init_settings(cx);
5653            Project::init_settings(cx);
5654        });
5655    }
5656}