workspace.rs

   1pub mod dock;
   2pub mod item;
   3mod modal_layer;
   4pub mod notifications;
   5pub mod pane;
   6pub mod pane_group;
   7mod persistence;
   8pub mod searchable;
   9pub mod shared_screen;
  10mod status_bar;
  11mod toolbar;
  12mod workspace_settings;
  13
  14use anyhow::{anyhow, Context as _, Result};
  15use call::{call_settings::CallSettings, ActiveCall};
  16use client::{
  17    proto::{self, ErrorCode, PeerId},
  18    ChannelId, Client, ErrorExt, Status, TypedEnvelope, UserStore,
  19};
  20use collections::{hash_map, HashMap, HashSet};
  21use derive_more::{Deref, DerefMut};
  22use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  23use futures::{
  24    channel::{mpsc, oneshot},
  25    future::try_join_all,
  26    Future, FutureExt, StreamExt,
  27};
  28use gpui::{
  29    actions, canvas, div, impl_actions, point, px, size, Action, AnyElement, AnyModel, AnyView,
  30    AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div,
  31    DragMoveEvent, Element, ElementContext, Entity, EntityId, EventEmitter, FocusHandle,
  32    FocusableView, Global, GlobalPixels, InteractiveElement, IntoElement, KeyContext, Keystroke,
  33    LayoutId, ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point,
  34    PromptLevel, Render, SharedString, Size, Styled, Subscription, Task, View, ViewContext,
  35    VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions,
  36};
  37use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
  38use itertools::Itertools;
  39use language::{LanguageRegistry, Rope};
  40use lazy_static::lazy_static;
  41pub use modal_layer::*;
  42use node_runtime::NodeRuntime;
  43use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
  44pub use pane::*;
  45pub use pane_group::*;
  46use persistence::{model::SerializedWorkspace, SerializedWindowsBounds, DB};
  47pub use persistence::{
  48    model::{ItemId, WorkspaceLocation},
  49    WorkspaceDb, DB as WORKSPACE_DB,
  50};
  51use postage::stream::Stream;
  52use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  53use serde::Deserialize;
  54use settings::Settings;
  55use shared_screen::SharedScreen;
  56use status_bar::StatusBar;
  57pub use status_bar::StatusItemView;
  58use std::{
  59    any::TypeId,
  60    borrow::Cow,
  61    cell::RefCell,
  62    cmp,
  63    collections::hash_map::DefaultHasher,
  64    env,
  65    hash::{Hash, Hasher},
  66    path::{Path, PathBuf},
  67    rc::Rc,
  68    sync::{atomic::AtomicUsize, Arc, Weak},
  69    time::Duration,
  70};
  71use task::SpawnInTerminal;
  72use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
  73pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
  74pub use ui;
  75use ui::Label;
  76use util::ResultExt;
  77use uuid::Uuid;
  78pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
  79
  80use crate::persistence::{
  81    model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
  82    SerializedAxis,
  83};
  84
  85lazy_static! {
  86    static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
  87        .ok()
  88        .as_deref()
  89        .and_then(parse_pixel_size_env_var);
  90    static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
  91        .ok()
  92        .as_deref()
  93        .and_then(parse_pixel_position_env_var);
  94}
  95
  96#[derive(Clone, PartialEq)]
  97pub struct RemoveWorktreeFromProject(pub WorktreeId);
  98
  99actions!(
 100    workspace,
 101    [
 102        Open,
 103        NewFile,
 104        NewWindow,
 105        CloseWindow,
 106        AddFolderToProject,
 107        Unfollow,
 108        SaveAs,
 109        ReloadActiveItem,
 110        ActivatePreviousPane,
 111        ActivateNextPane,
 112        FollowNextCollaborator,
 113        NewTerminal,
 114        NewCenterTerminal,
 115        NewSearch,
 116        Feedback,
 117        Restart,
 118        Welcome,
 119        ToggleZoom,
 120        ToggleLeftDock,
 121        ToggleRightDock,
 122        ToggleBottomDock,
 123        CloseAllDocks,
 124    ]
 125);
 126
 127#[derive(Clone, PartialEq)]
 128pub struct OpenPaths {
 129    pub paths: Vec<PathBuf>,
 130}
 131
 132#[derive(Clone, Deserialize, PartialEq)]
 133pub struct ActivatePane(pub usize);
 134
 135#[derive(Clone, Deserialize, PartialEq)]
 136pub struct ActivatePaneInDirection(pub SplitDirection);
 137
 138#[derive(Clone, Deserialize, PartialEq)]
 139pub struct SwapPaneInDirection(pub SplitDirection);
 140
 141#[derive(Clone, Deserialize, PartialEq)]
 142pub struct NewFileInDirection(pub SplitDirection);
 143
 144#[derive(Clone, PartialEq, Debug, Deserialize)]
 145#[serde(rename_all = "camelCase")]
 146pub struct SaveAll {
 147    pub save_intent: Option<SaveIntent>,
 148}
 149
 150#[derive(Clone, PartialEq, Debug, Deserialize)]
 151#[serde(rename_all = "camelCase")]
 152pub struct Save {
 153    pub save_intent: Option<SaveIntent>,
 154}
 155
 156#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 157#[serde(rename_all = "camelCase")]
 158pub struct CloseAllItemsAndPanes {
 159    pub save_intent: Option<SaveIntent>,
 160}
 161
 162#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 163#[serde(rename_all = "camelCase")]
 164pub struct CloseInactiveTabsAndPanes {
 165    pub save_intent: Option<SaveIntent>,
 166}
 167
 168#[derive(Clone, Deserialize, PartialEq)]
 169pub struct SendKeystrokes(pub String);
 170
 171impl_actions!(
 172    workspace,
 173    [
 174        ActivatePane,
 175        ActivatePaneInDirection,
 176        CloseAllItemsAndPanes,
 177        CloseInactiveTabsAndPanes,
 178        NewFileInDirection,
 179        OpenTerminal,
 180        Save,
 181        SaveAll,
 182        SwapPaneInDirection,
 183        SendKeystrokes,
 184    ]
 185);
 186
 187#[derive(Deserialize)]
 188pub struct Toast {
 189    id: usize,
 190    msg: Cow<'static, str>,
 191    #[serde(skip)]
 192    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 193}
 194
 195impl Toast {
 196    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 197        Toast {
 198            id,
 199            msg: msg.into(),
 200            on_click: None,
 201        }
 202    }
 203
 204    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 205    where
 206        M: Into<Cow<'static, str>>,
 207        F: Fn(&mut WindowContext) + 'static,
 208    {
 209        self.on_click = Some((message.into(), Arc::new(on_click)));
 210        self
 211    }
 212}
 213
 214impl PartialEq for Toast {
 215    fn eq(&self, other: &Self) -> bool {
 216        self.id == other.id
 217            && self.msg == other.msg
 218            && self.on_click.is_some() == other.on_click.is_some()
 219    }
 220}
 221
 222impl Clone for Toast {
 223    fn clone(&self) -> Self {
 224        Toast {
 225            id: self.id,
 226            msg: self.msg.to_owned(),
 227            on_click: self.on_click.clone(),
 228        }
 229    }
 230}
 231
 232#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
 233pub struct OpenTerminal {
 234    pub working_directory: PathBuf,
 235}
 236
 237pub type WorkspaceId = i64;
 238
 239pub fn init_settings(cx: &mut AppContext) {
 240    WorkspaceSettings::register(cx);
 241    ItemSettings::register(cx);
 242}
 243
 244pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 245    init_settings(cx);
 246    notifications::init(cx);
 247
 248    cx.on_action(Workspace::close_global);
 249    cx.on_action(restart);
 250
 251    cx.on_action({
 252        let app_state = Arc::downgrade(&app_state);
 253        move |_: &Open, cx: &mut AppContext| {
 254            let paths = cx.prompt_for_paths(PathPromptOptions {
 255                files: true,
 256                directories: true,
 257                multiple: true,
 258            });
 259
 260            if let Some(app_state) = app_state.upgrade() {
 261                cx.spawn(move |cx| async move {
 262                    if let Some(paths) = paths.await.log_err().flatten() {
 263                        cx.update(|cx| {
 264                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 265                        })
 266                        .ok();
 267                    }
 268                })
 269                .detach();
 270            }
 271        }
 272    });
 273}
 274
 275#[derive(Clone, Default, Deref, DerefMut)]
 276struct ProjectItemBuilders(
 277    HashMap<TypeId, fn(Model<Project>, AnyModel, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>,
 278);
 279
 280impl Global for ProjectItemBuilders {}
 281
 282pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 283    let builders = cx.default_global::<ProjectItemBuilders>();
 284    builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 285        let item = model.downcast::<I::Item>().unwrap();
 286        Box::new(cx.new_view(|cx| I::for_project_item(project, item, cx)))
 287    });
 288}
 289
 290type FollowableItemBuilder = fn(
 291    View<Pane>,
 292    View<Workspace>,
 293    ViewId,
 294    &mut Option<proto::view::Variant>,
 295    &mut WindowContext,
 296) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 297
 298#[derive(Default, Deref, DerefMut)]
 299struct FollowableItemBuilders(
 300    HashMap<
 301        TypeId,
 302        (
 303            FollowableItemBuilder,
 304            fn(&AnyView) -> Box<dyn FollowableItemHandle>,
 305        ),
 306    >,
 307);
 308
 309impl Global for FollowableItemBuilders {}
 310
 311pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 312    let builders = cx.default_global::<FollowableItemBuilders>();
 313    builders.insert(
 314        TypeId::of::<I>(),
 315        (
 316            |pane, workspace, id, state, cx| {
 317                I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
 318                    cx.foreground_executor()
 319                        .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 320                })
 321            },
 322            |this| Box::new(this.clone().downcast::<I>().unwrap()),
 323        ),
 324    );
 325}
 326
 327#[derive(Default, Deref, DerefMut)]
 328struct ItemDeserializers(
 329    HashMap<
 330        Arc<str>,
 331        fn(
 332            Model<Project>,
 333            WeakView<Workspace>,
 334            WorkspaceId,
 335            ItemId,
 336            &mut ViewContext<Pane>,
 337        ) -> Task<Result<Box<dyn ItemHandle>>>,
 338    >,
 339);
 340
 341impl Global for ItemDeserializers {}
 342
 343pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 344    if let Some(serialized_item_kind) = I::serialized_item_kind() {
 345        let deserializers = cx.default_global::<ItemDeserializers>();
 346        deserializers.insert(
 347            Arc::from(serialized_item_kind),
 348            |project, workspace, workspace_id, item_id, cx| {
 349                let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 350                cx.foreground_executor()
 351                    .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 352            },
 353        );
 354    }
 355}
 356
 357pub struct AppState {
 358    pub languages: Arc<LanguageRegistry>,
 359    pub client: Arc<Client>,
 360    pub user_store: Model<UserStore>,
 361    pub workspace_store: Model<WorkspaceStore>,
 362    pub fs: Arc<dyn fs::Fs>,
 363    pub build_window_options:
 364        fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
 365    pub node_runtime: Arc<dyn NodeRuntime>,
 366}
 367
 368struct GlobalAppState(Weak<AppState>);
 369
 370impl Global for GlobalAppState {}
 371
 372pub struct WorkspaceStore {
 373    workspaces: HashSet<WindowHandle<Workspace>>,
 374    client: Arc<Client>,
 375    _subscriptions: Vec<client::Subscription>,
 376}
 377
 378#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 379struct Follower {
 380    project_id: Option<u64>,
 381    peer_id: PeerId,
 382}
 383
 384impl AppState {
 385    pub fn global(cx: &AppContext) -> Weak<Self> {
 386        cx.global::<GlobalAppState>().0.clone()
 387    }
 388    pub fn try_global(cx: &AppContext) -> Option<Weak<Self>> {
 389        cx.try_global::<GlobalAppState>()
 390            .map(|state| state.0.clone())
 391    }
 392    pub fn set_global(state: Weak<AppState>, cx: &mut AppContext) {
 393        cx.set_global(GlobalAppState(state));
 394    }
 395
 396    #[cfg(any(test, feature = "test-support"))]
 397    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 398        use node_runtime::FakeNodeRuntime;
 399        use settings::SettingsStore;
 400
 401        if !cx.has_global::<SettingsStore>() {
 402            let settings_store = SettingsStore::test(cx);
 403            cx.set_global(settings_store);
 404        }
 405
 406        let fs = fs::FakeFs::new(cx.background_executor().clone());
 407        let languages = Arc::new(LanguageRegistry::test());
 408        let clock = Arc::new(clock::FakeSystemClock::default());
 409        let http_client = util::http::FakeHttpClient::with_404_response();
 410        let client = Client::new(clock, http_client.clone(), cx);
 411        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
 412        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
 413
 414        theme::init(theme::LoadThemes::JustBase, cx);
 415        client::init(&client, cx);
 416        crate::init_settings(cx);
 417
 418        Arc::new(Self {
 419            client,
 420            fs,
 421            languages,
 422            user_store,
 423            workspace_store,
 424            node_runtime: FakeNodeRuntime::new(),
 425            build_window_options: |_, _, _| Default::default(),
 426        })
 427    }
 428}
 429
 430struct DelayedDebouncedEditAction {
 431    task: Option<Task<()>>,
 432    cancel_channel: Option<oneshot::Sender<()>>,
 433}
 434
 435impl DelayedDebouncedEditAction {
 436    fn new() -> DelayedDebouncedEditAction {
 437        DelayedDebouncedEditAction {
 438            task: None,
 439            cancel_channel: None,
 440        }
 441    }
 442
 443    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 444    where
 445        F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 446    {
 447        if let Some(channel) = self.cancel_channel.take() {
 448            _ = channel.send(());
 449        }
 450
 451        let (sender, mut receiver) = oneshot::channel::<()>();
 452        self.cancel_channel = Some(sender);
 453
 454        let previous_task = self.task.take();
 455        self.task = Some(cx.spawn(move |workspace, mut cx| async move {
 456            let mut timer = cx.background_executor().timer(delay).fuse();
 457            if let Some(previous_task) = previous_task {
 458                previous_task.await;
 459            }
 460
 461            futures::select_biased! {
 462                _ = receiver => return,
 463                    _ = timer => {}
 464            }
 465
 466            if let Some(result) = workspace
 467                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 468                .log_err()
 469            {
 470                result.await.log_err();
 471            }
 472        }));
 473    }
 474}
 475
 476pub enum Event {
 477    PaneAdded(View<Pane>),
 478    ContactRequestedJoin(u64),
 479    WorkspaceCreated(WeakView<Workspace>),
 480    SpawnTask(SpawnInTerminal),
 481}
 482
 483pub enum OpenVisible {
 484    All,
 485    None,
 486    OnlyFiles,
 487    OnlyDirectories,
 488}
 489
 490pub struct Workspace {
 491    weak_self: WeakView<Self>,
 492    workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
 493    zoomed: Option<AnyWeakView>,
 494    zoomed_position: Option<DockPosition>,
 495    center: PaneGroup,
 496    left_dock: View<Dock>,
 497    bottom_dock: View<Dock>,
 498    right_dock: View<Dock>,
 499    panes: Vec<View<Pane>>,
 500    panes_by_item: HashMap<EntityId, WeakView<Pane>>,
 501    active_pane: View<Pane>,
 502    last_active_center_pane: Option<WeakView<Pane>>,
 503    last_active_view_id: Option<proto::ViewId>,
 504    status_bar: View<StatusBar>,
 505    modal_layer: View<ModalLayer>,
 506    titlebar_item: Option<AnyView>,
 507    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 508    project: Model<Project>,
 509    follower_states: HashMap<View<Pane>, FollowerState>,
 510    last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
 511    window_edited: bool,
 512    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
 513    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 514    database_id: WorkspaceId,
 515    app_state: Arc<AppState>,
 516    dispatching_keystrokes: Rc<RefCell<Vec<Keystroke>>>,
 517    _subscriptions: Vec<Subscription>,
 518    _apply_leader_updates: Task<Result<()>>,
 519    _observe_current_user: Task<Result<()>>,
 520    _schedule_serialize: Option<Task<()>>,
 521    pane_history_timestamp: Arc<AtomicUsize>,
 522    bounds: Bounds<Pixels>,
 523}
 524
 525impl EventEmitter<Event> for Workspace {}
 526
 527#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 528pub struct ViewId {
 529    pub creator: PeerId,
 530    pub id: u64,
 531}
 532
 533#[derive(Default)]
 534struct FollowerState {
 535    leader_id: PeerId,
 536    active_view_id: Option<ViewId>,
 537    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 538}
 539
 540impl Workspace {
 541    pub fn new(
 542        workspace_id: WorkspaceId,
 543        project: Model<Project>,
 544        app_state: Arc<AppState>,
 545        cx: &mut ViewContext<Self>,
 546    ) -> Self {
 547        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 548        cx.subscribe(&project, move |this, _, event, cx| {
 549            match event {
 550                project::Event::RemoteIdChanged(_) => {
 551                    this.update_window_title(cx);
 552                }
 553
 554                project::Event::CollaboratorLeft(peer_id) => {
 555                    this.collaborator_left(*peer_id, cx);
 556                }
 557
 558                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 559                    this.update_window_title(cx);
 560                    this.serialize_workspace(cx);
 561                }
 562
 563                project::Event::DisconnectedFromHost => {
 564                    this.update_window_edited(cx);
 565                    let panes_to_unfollow: Vec<View<Pane>> =
 566                        this.follower_states.keys().map(|k| k.clone()).collect();
 567                    for pane in panes_to_unfollow {
 568                        this.unfollow(&pane, cx);
 569                    }
 570                    cx.disable_focus();
 571                }
 572
 573                project::Event::Closed => {
 574                    cx.remove_window();
 575                }
 576
 577                project::Event::DeletedEntry(entry_id) => {
 578                    for pane in this.panes.iter() {
 579                        pane.update(cx, |pane, cx| {
 580                            pane.handle_deleted_project_item(*entry_id, cx)
 581                        });
 582                    }
 583                }
 584
 585                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
 586                    cx.new_view(|_| MessageNotification::new(message.clone()))
 587                }),
 588
 589                project::Event::LanguageServerPrompt(request) => {
 590                    let mut hasher = DefaultHasher::new();
 591                    request.message.as_str().hash(&mut hasher);
 592                    let id = hasher.finish();
 593
 594                    this.show_notification(id as usize, cx, |cx| {
 595                        cx.new_view(|_| notifications::LanguageServerPrompt::new(request.clone()))
 596                    });
 597                }
 598
 599                _ => {}
 600            }
 601            cx.notify()
 602        })
 603        .detach();
 604
 605        cx.on_focus_lost(|this, cx| {
 606            let focus_handle = this.focus_handle(cx);
 607            cx.focus(&focus_handle);
 608        })
 609        .detach();
 610
 611        let weak_handle = cx.view().downgrade();
 612        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 613
 614        let center_pane = cx.new_view(|cx| {
 615            Pane::new(
 616                weak_handle.clone(),
 617                project.clone(),
 618                pane_history_timestamp.clone(),
 619                None,
 620                cx,
 621            )
 622        });
 623        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 624
 625        cx.focus_view(&center_pane);
 626        cx.emit(Event::PaneAdded(center_pane.clone()));
 627
 628        let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
 629        app_state.workspace_store.update(cx, |store, _| {
 630            store.workspaces.insert(window_handle);
 631        });
 632
 633        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 634        let mut connection_status = app_state.client.status();
 635        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 636            current_user.next().await;
 637            connection_status.next().await;
 638            let mut stream =
 639                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 640
 641            while stream.recv().await.is_some() {
 642                this.update(&mut cx, |_, cx| cx.notify())?;
 643            }
 644            anyhow::Ok(())
 645        });
 646
 647        // All leader updates are enqueued and then processed in a single task, so
 648        // that each asynchronous operation can be run in order.
 649        let (leader_updates_tx, mut leader_updates_rx) =
 650            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 651        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 652            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 653                Self::process_leader_update(&this, leader_id, update, &mut cx)
 654                    .await
 655                    .log_err();
 656            }
 657
 658            Ok(())
 659        });
 660
 661        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
 662
 663        let left_dock = Dock::new(DockPosition::Left, cx);
 664        let bottom_dock = Dock::new(DockPosition::Bottom, cx);
 665        let right_dock = Dock::new(DockPosition::Right, cx);
 666        let left_dock_buttons = cx.new_view(|cx| PanelButtons::new(left_dock.clone(), cx));
 667        let bottom_dock_buttons = cx.new_view(|cx| PanelButtons::new(bottom_dock.clone(), cx));
 668        let right_dock_buttons = cx.new_view(|cx| PanelButtons::new(right_dock.clone(), cx));
 669        let status_bar = cx.new_view(|cx| {
 670            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 671            status_bar.add_left_item(left_dock_buttons, cx);
 672            status_bar.add_right_item(right_dock_buttons, cx);
 673            status_bar.add_right_item(bottom_dock_buttons, cx);
 674            status_bar
 675        });
 676
 677        let modal_layer = cx.new_view(|_| ModalLayer::new());
 678
 679        let mut active_call = None;
 680        if let Some(call) = ActiveCall::try_global(cx) {
 681            let call = call.clone();
 682            let mut subscriptions = Vec::new();
 683            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 684            active_call = Some((call, subscriptions));
 685        }
 686
 687        let subscriptions = vec![
 688            cx.observe_window_activation(Self::on_window_activation_changed),
 689            cx.observe_window_bounds(move |_, cx| {
 690                if let Some(display) = cx.display() {
 691                    // Transform fixed bounds to be stored in terms of the containing display
 692                    let mut bounds = cx.window_bounds();
 693                    if let WindowBounds::Fixed(window_bounds) = &mut bounds {
 694                        let display_bounds = display.bounds();
 695                        window_bounds.origin.x -= display_bounds.origin.x;
 696                        window_bounds.origin.y -= display_bounds.origin.y;
 697                    }
 698
 699                    if let Some(display_uuid) = display.uuid().log_err() {
 700                        cx.background_executor()
 701                            .spawn(DB.set_window_bounds(
 702                                workspace_id,
 703                                SerializedWindowsBounds(bounds),
 704                                display_uuid,
 705                            ))
 706                            .detach_and_log_err(cx);
 707                    }
 708                }
 709                cx.notify();
 710            }),
 711            cx.observe_window_appearance(|_, cx| {
 712                let window_appearance = cx.appearance();
 713
 714                *SystemAppearance::global_mut(cx) = SystemAppearance(window_appearance.into());
 715
 716                ThemeSettings::reload_current_theme(cx);
 717            }),
 718            cx.observe(&left_dock, |this, _, cx| {
 719                this.serialize_workspace(cx);
 720                cx.notify();
 721            }),
 722            cx.observe(&bottom_dock, |this, _, cx| {
 723                this.serialize_workspace(cx);
 724                cx.notify();
 725            }),
 726            cx.observe(&right_dock, |this, _, cx| {
 727                this.serialize_workspace(cx);
 728                cx.notify();
 729            }),
 730            cx.on_release(|this, window, cx| {
 731                this.app_state.workspace_store.update(cx, |store, _| {
 732                    let window = window.downcast::<Self>().unwrap();
 733                    store.workspaces.remove(&window);
 734                })
 735            }),
 736        ];
 737
 738        cx.defer(|this, cx| {
 739            this.update_window_title(cx);
 740        });
 741        Workspace {
 742            weak_self: weak_handle.clone(),
 743            zoomed: None,
 744            zoomed_position: None,
 745            center: PaneGroup::new(center_pane.clone()),
 746            panes: vec![center_pane.clone()],
 747            panes_by_item: Default::default(),
 748            active_pane: center_pane.clone(),
 749            last_active_center_pane: Some(center_pane.downgrade()),
 750            last_active_view_id: None,
 751            status_bar,
 752            modal_layer,
 753            titlebar_item: None,
 754            notifications: Default::default(),
 755            left_dock,
 756            bottom_dock,
 757            right_dock,
 758            project: project.clone(),
 759            follower_states: Default::default(),
 760            last_leaders_by_pane: Default::default(),
 761            dispatching_keystrokes: Default::default(),
 762            window_edited: false,
 763            active_call,
 764            database_id: workspace_id,
 765            app_state,
 766            _observe_current_user,
 767            _apply_leader_updates,
 768            _schedule_serialize: None,
 769            leader_updates_tx,
 770            _subscriptions: subscriptions,
 771            pane_history_timestamp,
 772            workspace_actions: Default::default(),
 773            // This data will be incorrect, but it will be overwritten by the time it needs to be used.
 774            bounds: Default::default(),
 775        }
 776    }
 777
 778    fn new_local(
 779        abs_paths: Vec<PathBuf>,
 780        app_state: Arc<AppState>,
 781        requesting_window: Option<WindowHandle<Workspace>>,
 782        cx: &mut AppContext,
 783    ) -> Task<
 784        anyhow::Result<(
 785            WindowHandle<Workspace>,
 786            Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 787        )>,
 788    > {
 789        let project_handle = Project::local(
 790            app_state.client.clone(),
 791            app_state.node_runtime.clone(),
 792            app_state.user_store.clone(),
 793            app_state.languages.clone(),
 794            app_state.fs.clone(),
 795            cx,
 796        );
 797
 798        cx.spawn(|mut cx| async move {
 799            let serialized_workspace: Option<SerializedWorkspace> =
 800                persistence::DB.workspace_for_roots(abs_paths.as_slice());
 801
 802            let paths_to_open = Arc::new(abs_paths);
 803
 804            // Get project paths for all of the abs_paths
 805            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 806            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 807                Vec::with_capacity(paths_to_open.len());
 808            for path in paths_to_open.iter().cloned() {
 809                if let Some((worktree, project_entry)) = cx
 810                    .update(|cx| {
 811                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 812                    })?
 813                    .await
 814                    .log_err()
 815                {
 816                    worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
 817                    project_paths.push((path, Some(project_entry)));
 818                } else {
 819                    project_paths.push((path, None));
 820                }
 821            }
 822
 823            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 824                serialized_workspace.id
 825            } else {
 826                DB.next_id().await.unwrap_or(0)
 827            };
 828
 829            let window = if let Some(window) = requesting_window {
 830                cx.update_window(window.into(), |_, cx| {
 831                    cx.replace_root_view(|cx| {
 832                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 833                    });
 834                })?;
 835                window
 836            } else {
 837                let window_bounds_override = window_bounds_env_override(&cx);
 838                let (bounds, display) = if let Some(bounds) = window_bounds_override {
 839                    (Some(bounds), None)
 840                } else {
 841                    serialized_workspace
 842                        .as_ref()
 843                        .and_then(|serialized_workspace| {
 844                            let serialized_display = serialized_workspace.display?;
 845                            let mut bounds = serialized_workspace.bounds?;
 846
 847                            // Stored bounds are relative to the containing display.
 848                            // So convert back to global coordinates if that screen still exists
 849                            if let WindowBounds::Fixed(mut window_bounds) = bounds {
 850                                let screen = cx
 851                                    .update(|cx| {
 852                                        cx.displays().into_iter().find(|display| {
 853                                            display.uuid().ok() == Some(serialized_display)
 854                                        })
 855                                    })
 856                                    .ok()??;
 857                                let screen_bounds = screen.bounds();
 858                                window_bounds.origin.x += screen_bounds.origin.x;
 859                                window_bounds.origin.y += screen_bounds.origin.y;
 860                                bounds = WindowBounds::Fixed(window_bounds);
 861                            }
 862
 863                            Some((bounds, serialized_display))
 864                        })
 865                        .unzip()
 866                };
 867
 868                // Use the serialized workspace to construct the new window
 869                let options =
 870                    cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
 871
 872                cx.open_window(options, {
 873                    let app_state = app_state.clone();
 874                    let workspace_id = workspace_id.clone();
 875                    let project_handle = project_handle.clone();
 876                    move |cx| {
 877                        cx.new_view(|cx| {
 878                            Workspace::new(workspace_id, project_handle, app_state, cx)
 879                        })
 880                    }
 881                })?
 882            };
 883
 884            window
 885                .update(&mut cx, |_, cx| cx.activate_window())
 886                .log_err();
 887
 888            notify_if_database_failed(window, &mut cx);
 889            let opened_items = window
 890                .update(&mut cx, |_workspace, cx| {
 891                    open_items(serialized_workspace, project_paths, app_state, cx)
 892                })?
 893                .await
 894                .unwrap_or_default();
 895
 896            Ok((window, opened_items))
 897        })
 898    }
 899
 900    pub fn weak_handle(&self) -> WeakView<Self> {
 901        self.weak_self.clone()
 902    }
 903
 904    pub fn left_dock(&self) -> &View<Dock> {
 905        &self.left_dock
 906    }
 907
 908    pub fn bottom_dock(&self) -> &View<Dock> {
 909        &self.bottom_dock
 910    }
 911
 912    pub fn right_dock(&self) -> &View<Dock> {
 913        &self.right_dock
 914    }
 915
 916    pub fn is_edited(&self) -> bool {
 917        self.window_edited
 918    }
 919
 920    pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut WindowContext) {
 921        let dock = match panel.position(cx) {
 922            DockPosition::Left => &self.left_dock,
 923            DockPosition::Bottom => &self.bottom_dock,
 924            DockPosition::Right => &self.right_dock,
 925        };
 926
 927        dock.update(cx, |dock, cx| {
 928            dock.add_panel(panel, self.weak_self.clone(), cx)
 929        });
 930    }
 931
 932    pub fn status_bar(&self) -> &View<StatusBar> {
 933        &self.status_bar
 934    }
 935
 936    pub fn app_state(&self) -> &Arc<AppState> {
 937        &self.app_state
 938    }
 939
 940    pub fn user_store(&self) -> &Model<UserStore> {
 941        &self.app_state.user_store
 942    }
 943
 944    pub fn project(&self) -> &Model<Project> {
 945        &self.project
 946    }
 947
 948    pub fn recent_navigation_history(
 949        &self,
 950        limit: Option<usize>,
 951        cx: &AppContext,
 952    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
 953        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
 954        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
 955        for pane in &self.panes {
 956            let pane = pane.read(cx);
 957            pane.nav_history()
 958                .for_each_entry(cx, |entry, (project_path, fs_path)| {
 959                    if let Some(fs_path) = &fs_path {
 960                        abs_paths_opened
 961                            .entry(fs_path.clone())
 962                            .or_default()
 963                            .insert(project_path.clone());
 964                    }
 965                    let timestamp = entry.timestamp;
 966                    match history.entry(project_path) {
 967                        hash_map::Entry::Occupied(mut entry) => {
 968                            let (_, old_timestamp) = entry.get();
 969                            if &timestamp > old_timestamp {
 970                                entry.insert((fs_path, timestamp));
 971                            }
 972                        }
 973                        hash_map::Entry::Vacant(entry) => {
 974                            entry.insert((fs_path, timestamp));
 975                        }
 976                    }
 977                });
 978        }
 979
 980        history
 981            .into_iter()
 982            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
 983            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
 984            .rev()
 985            .filter(|(history_path, abs_path)| {
 986                let latest_project_path_opened = abs_path
 987                    .as_ref()
 988                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
 989                    .and_then(|project_paths| {
 990                        project_paths
 991                            .iter()
 992                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
 993                    });
 994
 995                match latest_project_path_opened {
 996                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
 997                    None => true,
 998                }
 999            })
1000            .take(limit.unwrap_or(usize::MAX))
1001            .collect()
1002    }
1003
1004    fn navigate_history(
1005        &mut self,
1006        pane: WeakView<Pane>,
1007        mode: NavigationMode,
1008        cx: &mut ViewContext<Workspace>,
1009    ) -> Task<Result<()>> {
1010        let to_load = if let Some(pane) = pane.upgrade() {
1011            pane.update(cx, |pane, cx| {
1012                pane.focus(cx);
1013                loop {
1014                    // Retrieve the weak item handle from the history.
1015                    let entry = pane.nav_history_mut().pop(mode, cx)?;
1016
1017                    // If the item is still present in this pane, then activate it.
1018                    if let Some(index) = entry
1019                        .item
1020                        .upgrade()
1021                        .and_then(|v| pane.index_for_item(v.as_ref()))
1022                    {
1023                        let prev_active_item_index = pane.active_item_index();
1024                        pane.nav_history_mut().set_mode(mode);
1025                        pane.activate_item(index, true, true, cx);
1026                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1027
1028                        let mut navigated = prev_active_item_index != pane.active_item_index();
1029                        if let Some(data) = entry.data {
1030                            navigated |= pane.active_item()?.navigate(data, cx);
1031                        }
1032
1033                        if navigated {
1034                            break None;
1035                        }
1036                    }
1037                    // If the item is no longer present in this pane, then retrieve its
1038                    // project path in order to reopen it.
1039                    else {
1040                        break pane
1041                            .nav_history()
1042                            .path_for_item(entry.item.id())
1043                            .map(|(project_path, _)| (project_path, entry));
1044                    }
1045                }
1046            })
1047        } else {
1048            None
1049        };
1050
1051        if let Some((project_path, entry)) = to_load {
1052            // If the item was no longer present, then load it again from its previous path.
1053            let task = self.load_path(project_path, cx);
1054            cx.spawn(|workspace, mut cx| async move {
1055                let task = task.await;
1056                let mut navigated = false;
1057                if let Some((project_entry_id, build_item)) = task.log_err() {
1058                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
1059                        pane.nav_history_mut().set_mode(mode);
1060                        pane.active_item().map(|p| p.item_id())
1061                    })?;
1062
1063                    pane.update(&mut cx, |pane, cx| {
1064                        let item = pane.open_item(project_entry_id, true, cx, build_item);
1065                        navigated |= Some(item.item_id()) != prev_active_item_id;
1066                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1067                        if let Some(data) = entry.data {
1068                            navigated |= item.navigate(data, cx);
1069                        }
1070                    })?;
1071                }
1072
1073                if !navigated {
1074                    workspace
1075                        .update(&mut cx, |workspace, cx| {
1076                            Self::navigate_history(workspace, pane, mode, cx)
1077                        })?
1078                        .await?;
1079                }
1080
1081                Ok(())
1082            })
1083        } else {
1084            Task::ready(Ok(()))
1085        }
1086    }
1087
1088    pub fn go_back(
1089        &mut self,
1090        pane: WeakView<Pane>,
1091        cx: &mut ViewContext<Workspace>,
1092    ) -> Task<Result<()>> {
1093        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1094    }
1095
1096    pub fn go_forward(
1097        &mut self,
1098        pane: WeakView<Pane>,
1099        cx: &mut ViewContext<Workspace>,
1100    ) -> Task<Result<()>> {
1101        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1102    }
1103
1104    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1105        self.navigate_history(
1106            self.active_pane().downgrade(),
1107            NavigationMode::ReopeningClosedItem,
1108            cx,
1109        )
1110    }
1111
1112    pub fn client(&self) -> &Arc<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(&mut self, item: Box<dyn ItemHandle>, cx: &mut WindowContext) {
1889        if let Some(text) = item.telemetry_event_text(cx) {
1890            self.client()
1891                .telemetry()
1892                .report_app_event(format!("{}: open", text));
1893        }
1894
1895        self.active_pane
1896            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1897    }
1898
1899    pub fn split_item(
1900        &mut self,
1901        split_direction: SplitDirection,
1902        item: Box<dyn ItemHandle>,
1903        cx: &mut ViewContext<Self>,
1904    ) {
1905        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
1906        new_pane.update(cx, move |new_pane, cx| {
1907            new_pane.add_item(item, true, true, None, cx)
1908        })
1909    }
1910
1911    pub fn open_abs_path(
1912        &mut self,
1913        abs_path: PathBuf,
1914        visible: bool,
1915        cx: &mut ViewContext<Self>,
1916    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1917        cx.spawn(|workspace, mut cx| async move {
1918            let open_paths_task_result = workspace
1919                .update(&mut cx, |workspace, cx| {
1920                    workspace.open_paths(
1921                        vec![abs_path.clone()],
1922                        if visible {
1923                            OpenVisible::All
1924                        } else {
1925                            OpenVisible::None
1926                        },
1927                        None,
1928                        cx,
1929                    )
1930                })
1931                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1932                .await;
1933            anyhow::ensure!(
1934                open_paths_task_result.len() == 1,
1935                "open abs path {abs_path:?} task returned incorrect number of results"
1936            );
1937            match open_paths_task_result
1938                .into_iter()
1939                .next()
1940                .expect("ensured single task result")
1941            {
1942                Some(open_result) => {
1943                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1944                }
1945                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1946            }
1947        })
1948    }
1949
1950    pub fn split_abs_path(
1951        &mut self,
1952        abs_path: PathBuf,
1953        visible: bool,
1954        cx: &mut ViewContext<Self>,
1955    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1956        let project_path_task =
1957            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
1958        cx.spawn(|this, mut cx| async move {
1959            let (_, path) = project_path_task.await?;
1960            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
1961                .await
1962        })
1963    }
1964
1965    pub fn open_path(
1966        &mut self,
1967        path: impl Into<ProjectPath>,
1968        pane: Option<WeakView<Pane>>,
1969        focus_item: bool,
1970        cx: &mut WindowContext,
1971    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1972        let pane = pane.unwrap_or_else(|| {
1973            self.last_active_center_pane.clone().unwrap_or_else(|| {
1974                self.panes
1975                    .first()
1976                    .expect("There must be an active pane")
1977                    .downgrade()
1978            })
1979        });
1980
1981        let task = self.load_path(path.into(), cx);
1982        cx.spawn(move |mut cx| async move {
1983            let (project_entry_id, build_item) = task.await?;
1984            pane.update(&mut cx, |pane, cx| {
1985                pane.open_item(project_entry_id, focus_item, cx, build_item)
1986            })
1987        })
1988    }
1989
1990    pub fn split_path(
1991        &mut self,
1992        path: impl Into<ProjectPath>,
1993        cx: &mut ViewContext<Self>,
1994    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1995        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
1996            self.panes
1997                .first()
1998                .expect("There must be an active pane")
1999                .downgrade()
2000        });
2001
2002        if let Member::Pane(center_pane) = &self.center.root {
2003            if center_pane.read(cx).items_len() == 0 {
2004                return self.open_path(path, Some(pane), true, cx);
2005            }
2006        }
2007
2008        let task = self.load_path(path.into(), cx);
2009        cx.spawn(|this, mut cx| async move {
2010            let (project_entry_id, build_item) = task.await?;
2011            this.update(&mut cx, move |this, cx| -> Option<_> {
2012                let pane = pane.upgrade()?;
2013                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
2014                new_pane.update(cx, |new_pane, cx| {
2015                    Some(new_pane.open_item(project_entry_id, true, cx, build_item))
2016                })
2017            })
2018            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
2019        })
2020    }
2021
2022    fn load_path(
2023        &mut self,
2024        path: ProjectPath,
2025        cx: &mut WindowContext,
2026    ) -> Task<
2027        Result<(
2028            Option<ProjectEntryId>,
2029            impl 'static + Send + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
2030        )>,
2031    > {
2032        let project = self.project().clone();
2033        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
2034        cx.spawn(|mut cx| async move {
2035            let (project_entry_id, project_item) = project_item.await?;
2036            let build_item = cx.update(|cx| {
2037                cx.default_global::<ProjectItemBuilders>()
2038                    .get(&project_item.entity_type())
2039                    .ok_or_else(|| anyhow!("no item builder for project item"))
2040                    .cloned()
2041            })??;
2042            let build_item =
2043                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
2044            Ok((project_entry_id, build_item))
2045        })
2046    }
2047
2048    pub fn open_project_item<T>(
2049        &mut self,
2050        project_item: Model<T::Item>,
2051        cx: &mut ViewContext<Self>,
2052    ) -> View<T>
2053    where
2054        T: ProjectItem,
2055    {
2056        use project::Item as _;
2057
2058        let entry_id = project_item.read(cx).entry_id(cx);
2059        if let Some(item) = entry_id
2060            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2061            .and_then(|item| item.downcast())
2062        {
2063            self.activate_item(&item, cx);
2064            return item;
2065        }
2066
2067        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2068        self.add_item(Box::new(item.clone()), cx);
2069        item
2070    }
2071
2072    pub fn split_project_item<T>(
2073        &mut self,
2074        project_item: Model<T::Item>,
2075        cx: &mut ViewContext<Self>,
2076    ) -> View<T>
2077    where
2078        T: ProjectItem,
2079    {
2080        use project::Item as _;
2081
2082        let entry_id = project_item.read(cx).entry_id(cx);
2083        if let Some(item) = entry_id
2084            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2085            .and_then(|item| item.downcast())
2086        {
2087            self.activate_item(&item, cx);
2088            return item;
2089        }
2090
2091        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2092        self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
2093        item
2094    }
2095
2096    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2097        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2098            self.active_pane.update(cx, |pane, cx| {
2099                pane.add_item(Box::new(shared_screen), false, true, None, cx)
2100            });
2101        }
2102    }
2103
2104    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut WindowContext) -> bool {
2105        let result = self.panes.iter().find_map(|pane| {
2106            pane.read(cx)
2107                .index_for_item(item)
2108                .map(|ix| (pane.clone(), ix))
2109        });
2110        if let Some((pane, ix)) = result {
2111            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2112            true
2113        } else {
2114            false
2115        }
2116    }
2117
2118    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2119        let panes = self.center.panes();
2120        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2121            cx.focus_view(&pane);
2122        } else {
2123            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2124        }
2125    }
2126
2127    pub fn activate_next_pane(&mut self, cx: &mut WindowContext) {
2128        let panes = self.center.panes();
2129        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2130            let next_ix = (ix + 1) % panes.len();
2131            let next_pane = panes[next_ix].clone();
2132            cx.focus_view(&next_pane);
2133        }
2134    }
2135
2136    pub fn activate_previous_pane(&mut self, cx: &mut WindowContext) {
2137        let panes = self.center.panes();
2138        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2139            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2140            let prev_pane = panes[prev_ix].clone();
2141            cx.focus_view(&prev_pane);
2142        }
2143    }
2144
2145    pub fn activate_pane_in_direction(
2146        &mut self,
2147        direction: SplitDirection,
2148        cx: &mut WindowContext,
2149    ) {
2150        use ActivateInDirectionTarget as Target;
2151        enum Origin {
2152            LeftDock,
2153            RightDock,
2154            BottomDock,
2155            Center,
2156        }
2157
2158        let origin: Origin = [
2159            (&self.left_dock, Origin::LeftDock),
2160            (&self.right_dock, Origin::RightDock),
2161            (&self.bottom_dock, Origin::BottomDock),
2162        ]
2163        .into_iter()
2164        .find_map(|(dock, origin)| {
2165            if dock.focus_handle(cx).contains_focused(cx) && dock.read(cx).is_open() {
2166                Some(origin)
2167            } else {
2168                None
2169            }
2170        })
2171        .unwrap_or(Origin::Center);
2172
2173        let get_last_active_pane = || {
2174            self.last_active_center_pane.as_ref().and_then(|p| {
2175                let p = p.upgrade()?;
2176                (p.read(cx).items_len() != 0).then_some(p)
2177            })
2178        };
2179
2180        let try_dock =
2181            |dock: &View<Dock>| dock.read(cx).is_open().then(|| Target::Dock(dock.clone()));
2182
2183        let target = match (origin, direction) {
2184            // We're in the center, so we first try to go to a different pane,
2185            // otherwise try to go to a dock.
2186            (Origin::Center, direction) => {
2187                if let Some(pane) = self.find_pane_in_direction(direction, cx) {
2188                    Some(Target::Pane(pane))
2189                } else {
2190                    match direction {
2191                        SplitDirection::Up => None,
2192                        SplitDirection::Down => try_dock(&self.bottom_dock),
2193                        SplitDirection::Left => try_dock(&self.left_dock),
2194                        SplitDirection::Right => try_dock(&self.right_dock),
2195                    }
2196                }
2197            }
2198
2199            (Origin::LeftDock, SplitDirection::Right) => {
2200                if let Some(last_active_pane) = get_last_active_pane() {
2201                    Some(Target::Pane(last_active_pane))
2202                } else {
2203                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock))
2204                }
2205            }
2206
2207            (Origin::LeftDock, SplitDirection::Down)
2208            | (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock),
2209
2210            (Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane),
2211            (Origin::BottomDock, SplitDirection::Left) => try_dock(&self.left_dock),
2212            (Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock),
2213
2214            (Origin::RightDock, SplitDirection::Left) => {
2215                if let Some(last_active_pane) = get_last_active_pane() {
2216                    Some(Target::Pane(last_active_pane))
2217                } else {
2218                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock))
2219                }
2220            }
2221
2222            _ => None,
2223        };
2224
2225        match target {
2226            Some(ActivateInDirectionTarget::Pane(pane)) => cx.focus_view(&pane),
2227            Some(ActivateInDirectionTarget::Dock(dock)) => {
2228                if let Some(panel) = dock.read(cx).active_panel() {
2229                    panel.focus_handle(cx).focus(cx);
2230                } else {
2231                    log::error!("Could not find a focus target when in switching focus in {direction} direction for a {:?} dock", dock.read(cx).position());
2232                }
2233            }
2234            None => {}
2235        }
2236    }
2237
2238    fn find_pane_in_direction(
2239        &mut self,
2240        direction: SplitDirection,
2241        cx: &WindowContext,
2242    ) -> Option<View<Pane>> {
2243        let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
2244            return None;
2245        };
2246        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2247        let center = match cursor {
2248            Some(cursor) if bounding_box.contains(&cursor) => cursor,
2249            _ => bounding_box.center(),
2250        };
2251
2252        let distance_to_next = pane_group::HANDLE_HITBOX_SIZE;
2253
2254        let target = match direction {
2255            SplitDirection::Left => {
2256                Point::new(bounding_box.left() - distance_to_next.into(), center.y)
2257            }
2258            SplitDirection::Right => {
2259                Point::new(bounding_box.right() + distance_to_next.into(), center.y)
2260            }
2261            SplitDirection::Up => {
2262                Point::new(center.x, bounding_box.top() - distance_to_next.into())
2263            }
2264            SplitDirection::Down => {
2265                Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
2266            }
2267        };
2268        self.center.pane_at_pixel_position(target).cloned()
2269    }
2270
2271    pub fn swap_pane_in_direction(
2272        &mut self,
2273        direction: SplitDirection,
2274        cx: &mut ViewContext<Self>,
2275    ) {
2276        if let Some(to) = self
2277            .find_pane_in_direction(direction, cx)
2278            .map(|pane| pane.clone())
2279        {
2280            self.center.swap(&self.active_pane.clone(), &to);
2281            cx.notify();
2282        }
2283    }
2284
2285    fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2286        if self.active_pane != pane {
2287            self.active_pane = pane.clone();
2288            self.status_bar.update(cx, |status_bar, cx| {
2289                status_bar.set_active_pane(&self.active_pane, cx);
2290            });
2291            self.active_item_path_changed(cx);
2292            self.last_active_center_pane = Some(pane.downgrade());
2293        }
2294
2295        self.dismiss_zoomed_items_to_reveal(None, cx);
2296        if pane.read(cx).is_zoomed() {
2297            self.zoomed = Some(pane.downgrade().into());
2298        } else {
2299            self.zoomed = None;
2300        }
2301        self.zoomed_position = None;
2302        self.update_active_view_for_followers(cx);
2303
2304        cx.notify();
2305    }
2306
2307    fn handle_pane_event(
2308        &mut self,
2309        pane: View<Pane>,
2310        event: &pane::Event,
2311        cx: &mut ViewContext<Self>,
2312    ) {
2313        match event {
2314            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2315            pane::Event::Split(direction) => {
2316                self.split_and_clone(pane, *direction, cx);
2317            }
2318            pane::Event::Remove => self.remove_pane(pane, cx),
2319            pane::Event::ActivateItem { local } => {
2320                if *local {
2321                    self.unfollow(&pane, cx);
2322                }
2323                if &pane == self.active_pane() {
2324                    self.active_item_path_changed(cx);
2325                    self.update_active_view_for_followers(cx);
2326                }
2327            }
2328            pane::Event::ChangeItemTitle => {
2329                if pane == self.active_pane {
2330                    self.active_item_path_changed(cx);
2331                }
2332                self.update_window_edited(cx);
2333            }
2334            pane::Event::RemoveItem { item_id } => {
2335                self.update_window_edited(cx);
2336                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2337                    if entry.get().entity_id() == pane.entity_id() {
2338                        entry.remove();
2339                    }
2340                }
2341            }
2342            pane::Event::Focus => {
2343                self.handle_pane_focused(pane.clone(), cx);
2344            }
2345            pane::Event::ZoomIn => {
2346                if pane == self.active_pane {
2347                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2348                    if pane.read(cx).has_focus(cx) {
2349                        self.zoomed = Some(pane.downgrade().into());
2350                        self.zoomed_position = None;
2351                    }
2352                    cx.notify();
2353                }
2354            }
2355            pane::Event::ZoomOut => {
2356                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2357                if self.zoomed_position.is_none() {
2358                    self.zoomed = None;
2359                }
2360                cx.notify();
2361            }
2362        }
2363
2364        self.serialize_workspace(cx);
2365    }
2366
2367    pub fn split_pane(
2368        &mut self,
2369        pane_to_split: View<Pane>,
2370        split_direction: SplitDirection,
2371        cx: &mut ViewContext<Self>,
2372    ) -> View<Pane> {
2373        let new_pane = self.add_pane(cx);
2374        self.center
2375            .split(&pane_to_split, &new_pane, split_direction)
2376            .unwrap();
2377        cx.notify();
2378        new_pane
2379    }
2380
2381    pub fn split_and_clone(
2382        &mut self,
2383        pane: View<Pane>,
2384        direction: SplitDirection,
2385        cx: &mut ViewContext<Self>,
2386    ) -> Option<View<Pane>> {
2387        let item = pane.read(cx).active_item()?;
2388        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2389            let new_pane = self.add_pane(cx);
2390            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2391            self.center.split(&pane, &new_pane, direction).unwrap();
2392            Some(new_pane)
2393        } else {
2394            None
2395        };
2396        cx.notify();
2397        maybe_pane_handle
2398    }
2399
2400    pub fn split_pane_with_item(
2401        &mut self,
2402        pane_to_split: WeakView<Pane>,
2403        split_direction: SplitDirection,
2404        from: WeakView<Pane>,
2405        item_id_to_move: EntityId,
2406        cx: &mut ViewContext<Self>,
2407    ) {
2408        let Some(pane_to_split) = pane_to_split.upgrade() else {
2409            return;
2410        };
2411        let Some(from) = from.upgrade() else {
2412            return;
2413        };
2414
2415        let new_pane = self.add_pane(cx);
2416        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2417        self.center
2418            .split(&pane_to_split, &new_pane, split_direction)
2419            .unwrap();
2420        cx.notify();
2421    }
2422
2423    pub fn split_pane_with_project_entry(
2424        &mut self,
2425        pane_to_split: WeakView<Pane>,
2426        split_direction: SplitDirection,
2427        project_entry: ProjectEntryId,
2428        cx: &mut ViewContext<Self>,
2429    ) -> Option<Task<Result<()>>> {
2430        let pane_to_split = pane_to_split.upgrade()?;
2431        let new_pane = self.add_pane(cx);
2432        self.center
2433            .split(&pane_to_split, &new_pane, split_direction)
2434            .unwrap();
2435
2436        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2437        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2438        Some(cx.foreground_executor().spawn(async move {
2439            task.await?;
2440            Ok(())
2441        }))
2442    }
2443
2444    pub fn move_item(
2445        &mut self,
2446        source: View<Pane>,
2447        destination: View<Pane>,
2448        item_id_to_move: EntityId,
2449        destination_index: usize,
2450        cx: &mut ViewContext<Self>,
2451    ) {
2452        let Some((item_ix, item_handle)) = source
2453            .read(cx)
2454            .items()
2455            .enumerate()
2456            .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
2457        else {
2458            // Tab was closed during drag
2459            return;
2460        };
2461
2462        let item_handle = item_handle.clone();
2463
2464        if source != destination {
2465            // Close item from previous pane
2466            source.update(cx, |source, cx| {
2467                source.remove_item(item_ix, false, cx);
2468            });
2469        }
2470
2471        // This automatically removes duplicate items in the pane
2472        destination.update(cx, |destination, cx| {
2473            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2474            destination.focus(cx)
2475        });
2476    }
2477
2478    fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2479        if self.center.remove(&pane).unwrap() {
2480            self.force_remove_pane(&pane, cx);
2481            self.unfollow(&pane, cx);
2482            self.last_leaders_by_pane.remove(&pane.downgrade());
2483            for removed_item in pane.read(cx).items() {
2484                self.panes_by_item.remove(&removed_item.item_id());
2485            }
2486
2487            cx.notify();
2488        } else {
2489            self.active_item_path_changed(cx);
2490        }
2491    }
2492
2493    pub fn panes(&self) -> &[View<Pane>] {
2494        &self.panes
2495    }
2496
2497    pub fn active_pane(&self) -> &View<Pane> {
2498        &self.active_pane
2499    }
2500
2501    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
2502        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
2503        weak_pane.upgrade()
2504    }
2505
2506    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2507        self.follower_states.retain(|_, state| {
2508            if state.leader_id == peer_id {
2509                for item in state.items_by_leader_view_id.values() {
2510                    item.set_leader_peer_id(None, cx);
2511                }
2512                false
2513            } else {
2514                true
2515            }
2516        });
2517        cx.notify();
2518    }
2519
2520    pub fn start_following(
2521        &mut self,
2522        leader_id: PeerId,
2523        cx: &mut ViewContext<Self>,
2524    ) -> Option<Task<Result<()>>> {
2525        let pane = self.active_pane().clone();
2526
2527        self.last_leaders_by_pane
2528            .insert(pane.downgrade(), leader_id);
2529        self.unfollow(&pane, cx);
2530        self.follower_states.insert(
2531            pane.clone(),
2532            FollowerState {
2533                leader_id,
2534                active_view_id: None,
2535                items_by_leader_view_id: Default::default(),
2536            },
2537        );
2538        cx.notify();
2539
2540        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2541        let project_id = self.project.read(cx).remote_id();
2542        let request = self.app_state.client.request(proto::Follow {
2543            room_id,
2544            project_id,
2545            leader_id: Some(leader_id),
2546        });
2547
2548        Some(cx.spawn(|this, mut cx| async move {
2549            let response = request.await?;
2550            this.update(&mut cx, |this, _| {
2551                let state = this
2552                    .follower_states
2553                    .get_mut(&pane)
2554                    .ok_or_else(|| anyhow!("following interrupted"))?;
2555                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2556                    Some(ViewId::from_proto(active_view_id)?)
2557                } else {
2558                    None
2559                };
2560                Ok::<_, anyhow::Error>(())
2561            })??;
2562            if let Some(view) = response.active_view {
2563                Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, &mut cx)
2564                    .await?;
2565            }
2566            Self::add_views_from_leader(
2567                this.clone(),
2568                leader_id,
2569                vec![pane],
2570                response.views,
2571                &mut cx,
2572            )
2573            .await?;
2574            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2575            Ok(())
2576        }))
2577    }
2578
2579    pub fn follow_next_collaborator(
2580        &mut self,
2581        _: &FollowNextCollaborator,
2582        cx: &mut ViewContext<Self>,
2583    ) {
2584        let collaborators = self.project.read(cx).collaborators();
2585        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2586            let mut collaborators = collaborators.keys().copied();
2587            for peer_id in collaborators.by_ref() {
2588                if peer_id == leader_id {
2589                    break;
2590                }
2591            }
2592            collaborators.next()
2593        } else if let Some(last_leader_id) =
2594            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2595        {
2596            if collaborators.contains_key(last_leader_id) {
2597                Some(*last_leader_id)
2598            } else {
2599                None
2600            }
2601        } else {
2602            None
2603        };
2604
2605        let pane = self.active_pane.clone();
2606        let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
2607        else {
2608            return;
2609        };
2610        if Some(leader_id) == self.unfollow(&pane, cx) {
2611            return;
2612        }
2613        self.start_following(leader_id, cx)
2614            .map(|task| task.detach_and_log_err(cx));
2615    }
2616
2617    pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) {
2618        let Some(room) = ActiveCall::global(cx).read(cx).room() else {
2619            return;
2620        };
2621        let room = room.read(cx);
2622        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2623            return;
2624        };
2625
2626        let project = self.project.read(cx);
2627
2628        let other_project_id = match remote_participant.location {
2629            call::ParticipantLocation::External => None,
2630            call::ParticipantLocation::UnsharedProject => None,
2631            call::ParticipantLocation::SharedProject { project_id } => {
2632                if Some(project_id) == project.remote_id() {
2633                    None
2634                } else {
2635                    Some(project_id)
2636                }
2637            }
2638        };
2639
2640        // if they are active in another project, follow there.
2641        if let Some(project_id) = other_project_id {
2642            let app_state = self.app_state.clone();
2643            crate::join_remote_project(project_id, remote_participant.user.id, app_state, cx)
2644                .detach_and_log_err(cx);
2645        }
2646
2647        // if you're already following, find the right pane and focus it.
2648        for (pane, state) in &self.follower_states {
2649            if leader_id == state.leader_id {
2650                cx.focus_view(pane);
2651                return;
2652            }
2653        }
2654
2655        // Otherwise, follow.
2656        self.start_following(leader_id, cx)
2657            .map(|task| task.detach_and_log_err(cx));
2658    }
2659
2660    pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
2661        let state = self.follower_states.remove(pane)?;
2662        let leader_id = state.leader_id;
2663        for (_, item) in state.items_by_leader_view_id {
2664            item.set_leader_peer_id(None, cx);
2665        }
2666
2667        if self
2668            .follower_states
2669            .values()
2670            .all(|state| state.leader_id != state.leader_id)
2671        {
2672            let project_id = self.project.read(cx).remote_id();
2673            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2674            self.app_state
2675                .client
2676                .send(proto::Unfollow {
2677                    room_id,
2678                    project_id,
2679                    leader_id: Some(leader_id),
2680                })
2681                .log_err();
2682        }
2683
2684        cx.notify();
2685        Some(leader_id)
2686    }
2687
2688    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2689        self.follower_states
2690            .values()
2691            .any(|state| state.leader_id == peer_id)
2692    }
2693
2694    fn active_item_path_changed(&mut self, cx: &mut WindowContext) {
2695        let active_entry = self.active_project_path(cx);
2696        self.project
2697            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2698        self.update_window_title(cx);
2699    }
2700
2701    fn update_window_title(&mut self, cx: &mut WindowContext) {
2702        let project = self.project().read(cx);
2703        let mut title = String::new();
2704
2705        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2706            let filename = path
2707                .path
2708                .file_name()
2709                .map(|s| s.to_string_lossy())
2710                .or_else(|| {
2711                    Some(Cow::Borrowed(
2712                        project
2713                            .worktree_for_id(path.worktree_id, cx)?
2714                            .read(cx)
2715                            .root_name(),
2716                    ))
2717                });
2718
2719            if let Some(filename) = filename {
2720                title.push_str(filename.as_ref());
2721                title.push_str("");
2722            }
2723        }
2724
2725        for (i, name) in project.worktree_root_names(cx).enumerate() {
2726            if i > 0 {
2727                title.push_str(", ");
2728            }
2729            title.push_str(name);
2730        }
2731
2732        if title.is_empty() {
2733            title = "empty project".to_string();
2734        }
2735
2736        if project.is_remote() {
2737            title.push_str("");
2738        } else if project.is_shared() {
2739            title.push_str("");
2740        }
2741
2742        cx.set_window_title(&title);
2743    }
2744
2745    fn update_window_edited(&mut self, cx: &mut WindowContext) {
2746        let is_edited = !self.project.read(cx).is_disconnected()
2747            && self
2748                .items(cx)
2749                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2750        if is_edited != self.window_edited {
2751            self.window_edited = is_edited;
2752            cx.set_window_edited(self.window_edited)
2753        }
2754    }
2755
2756    fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
2757        if self.notifications.is_empty() {
2758            None
2759        } else {
2760            Some(
2761                div()
2762                    .absolute()
2763                    .z_index(100)
2764                    .right_3()
2765                    .bottom_3()
2766                    .w_112()
2767                    .h_full()
2768                    .flex()
2769                    .flex_col()
2770                    .justify_end()
2771                    .gap_2()
2772                    .children(
2773                        self.notifications
2774                            .iter()
2775                            .map(|(_, _, notification)| notification.to_any()),
2776                    ),
2777            )
2778        }
2779    }
2780
2781    // RPC handlers
2782
2783    fn active_view_for_follower(&self, cx: &mut ViewContext<Self>) -> Option<proto::View> {
2784        let item = self.active_item(cx)?;
2785        let leader_id = self
2786            .pane_for(&*item)
2787            .and_then(|pane| self.leader_for_pane(&pane));
2788
2789        let item_handle = item.to_followable_item_handle(cx)?;
2790        let id = item_handle.remote_id(&self.app_state.client, cx)?;
2791        let variant = item_handle.to_state_proto(cx)?;
2792
2793        Some(proto::View {
2794            id: Some(id.to_proto()),
2795            leader_id,
2796            variant: Some(variant),
2797        })
2798    }
2799
2800    fn handle_follow(
2801        &mut self,
2802        follower_project_id: Option<u64>,
2803        cx: &mut ViewContext<Self>,
2804    ) -> proto::FollowResponse {
2805        let client = &self.app_state.client;
2806        let project_id = self.project.read(cx).remote_id();
2807
2808        let active_view = self.active_view_for_follower(cx);
2809        let active_view_id = active_view.as_ref().and_then(|view| view.id.clone());
2810
2811        cx.notify();
2812
2813        proto::FollowResponse {
2814            active_view,
2815            // TODO: once v0.124.0 is retired we can stop sending these
2816            active_view_id,
2817            views: self
2818                .panes()
2819                .iter()
2820                .flat_map(|pane| {
2821                    let leader_id = self.leader_for_pane(pane);
2822                    pane.read(cx).items().filter_map({
2823                        let cx = &cx;
2824                        move |item| {
2825                            let item = item.to_followable_item_handle(cx)?;
2826
2827                            // If the item belongs to a particular project, then it should
2828                            // only be included if this project is shared, and the follower
2829                            // is in the project.
2830                            //
2831                            // Some items, like channel notes, do not belong to a particular
2832                            // project, so they should be included regardless of whether the
2833                            // current project is shared, or what project the follower is in.
2834                            if item.is_project_item(cx)
2835                                && (project_id.is_none() || project_id != follower_project_id)
2836                            {
2837                                return None;
2838                            }
2839
2840                            let id = item.remote_id(client, cx)?.to_proto();
2841                            let variant = item.to_state_proto(cx)?;
2842                            Some(proto::View {
2843                                id: Some(id),
2844                                leader_id,
2845                                variant: Some(variant),
2846                            })
2847                        }
2848                    })
2849                })
2850                .collect(),
2851        }
2852    }
2853
2854    fn handle_update_followers(
2855        &mut self,
2856        leader_id: PeerId,
2857        message: proto::UpdateFollowers,
2858        _cx: &mut ViewContext<Self>,
2859    ) {
2860        self.leader_updates_tx
2861            .unbounded_send((leader_id, message))
2862            .ok();
2863    }
2864
2865    async fn process_leader_update(
2866        this: &WeakView<Self>,
2867        leader_id: PeerId,
2868        update: proto::UpdateFollowers,
2869        cx: &mut AsyncWindowContext,
2870    ) -> Result<()> {
2871        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2872            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2873                let panes_missing_view = this.update(cx, |this, _| {
2874                    let mut panes = vec![];
2875                    for (pane, state) in &mut this.follower_states {
2876                        if state.leader_id != leader_id {
2877                            continue;
2878                        }
2879
2880                        state.active_view_id =
2881                            if let Some(active_view_id) = update_active_view.id.clone() {
2882                                Some(ViewId::from_proto(active_view_id)?)
2883                            } else {
2884                                None
2885                            };
2886
2887                        if state.active_view_id.is_some_and(|view_id| {
2888                            !state.items_by_leader_view_id.contains_key(&view_id)
2889                        }) {
2890                            panes.push(pane.clone())
2891                        }
2892                    }
2893                    anyhow::Ok(panes)
2894                })??;
2895
2896                if let Some(view) = update_active_view.view {
2897                    for pane in panes_missing_view {
2898                        Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, cx)
2899                            .await?
2900                    }
2901                }
2902            }
2903            proto::update_followers::Variant::UpdateView(update_view) => {
2904                let variant = update_view
2905                    .variant
2906                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2907                let id = update_view
2908                    .id
2909                    .ok_or_else(|| anyhow!("missing update view id"))?;
2910                let mut tasks = Vec::new();
2911                this.update(cx, |this, cx| {
2912                    let project = this.project.clone();
2913                    for (_, state) in &mut this.follower_states {
2914                        if state.leader_id == leader_id {
2915                            let view_id = ViewId::from_proto(id.clone())?;
2916                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2917                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2918                            }
2919                        }
2920                    }
2921                    anyhow::Ok(())
2922                })??;
2923                try_join_all(tasks).await.log_err();
2924            }
2925            proto::update_followers::Variant::CreateView(view) => {
2926                let panes = this.update(cx, |this, _| {
2927                    this.follower_states
2928                        .iter()
2929                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2930                        .cloned()
2931                        .collect()
2932                })?;
2933                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2934            }
2935        }
2936        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2937        Ok(())
2938    }
2939
2940    async fn add_view_from_leader(
2941        this: WeakView<Self>,
2942        leader_id: PeerId,
2943        pane: View<Pane>,
2944        view: &proto::View,
2945        cx: &mut AsyncWindowContext,
2946    ) -> Result<()> {
2947        let this = this.upgrade().context("workspace dropped")?;
2948
2949        let item_builders = cx.update(|cx| {
2950            cx.default_global::<FollowableItemBuilders>()
2951                .values()
2952                .map(|b| b.0)
2953                .collect::<Vec<_>>()
2954        })?;
2955
2956        let Some(id) = view.id.clone() else {
2957            return Err(anyhow!("no id for view")).into();
2958        };
2959        let id = ViewId::from_proto(id)?;
2960
2961        let mut variant = view.variant.clone();
2962        if variant.is_none() {
2963            Err(anyhow!("missing view variant"))?;
2964        }
2965
2966        let task = item_builders.iter().find_map(|build_item| {
2967            cx.update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx))
2968                .log_err()
2969                .flatten()
2970        });
2971        let Some(task) = task else {
2972            return Err(anyhow!(
2973                "failed to construct view from leader (maybe from a different version of zed?)"
2974            ));
2975        };
2976
2977        let item = task.await?;
2978
2979        this.update(cx, |this, cx| {
2980            let state = this.follower_states.get_mut(&pane)?;
2981            item.set_leader_peer_id(Some(leader_id), cx);
2982            state.items_by_leader_view_id.insert(id, item);
2983
2984            Some(())
2985        })?;
2986
2987        Ok(())
2988    }
2989
2990    async fn add_views_from_leader(
2991        this: WeakView<Self>,
2992        leader_id: PeerId,
2993        panes: Vec<View<Pane>>,
2994        views: Vec<proto::View>,
2995        cx: &mut AsyncWindowContext,
2996    ) -> Result<()> {
2997        let this = this.upgrade().context("workspace dropped")?;
2998
2999        let item_builders = cx.update(|cx| {
3000            cx.default_global::<FollowableItemBuilders>()
3001                .values()
3002                .map(|b| b.0)
3003                .collect::<Vec<_>>()
3004        })?;
3005
3006        let mut item_tasks_by_pane = HashMap::default();
3007        for pane in panes {
3008            let mut item_tasks = Vec::new();
3009            let mut leader_view_ids = Vec::new();
3010            for view in &views {
3011                let Some(id) = &view.id else {
3012                    continue;
3013                };
3014                let id = ViewId::from_proto(id.clone())?;
3015                let mut variant = view.variant.clone();
3016                if variant.is_none() {
3017                    Err(anyhow!("missing view variant"))?;
3018                }
3019                for build_item in &item_builders {
3020                    let task = cx.update(|cx| {
3021                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
3022                    })?;
3023                    if let Some(task) = task {
3024                        item_tasks.push(task);
3025                        leader_view_ids.push(id);
3026                        break;
3027                    } else if variant.is_none() {
3028                        Err(anyhow!(
3029                            "failed to construct view from leader (maybe from a different version of zed?)"
3030                        ))?;
3031                    }
3032                }
3033            }
3034
3035            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
3036        }
3037
3038        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
3039            let items = futures::future::try_join_all(item_tasks).await?;
3040            this.update(cx, |this, cx| {
3041                let state = this.follower_states.get_mut(&pane)?;
3042                for (id, item) in leader_view_ids.into_iter().zip(items) {
3043                    item.set_leader_peer_id(Some(leader_id), cx);
3044                    state.items_by_leader_view_id.insert(id, item);
3045                }
3046
3047                Some(())
3048            })?;
3049        }
3050        Ok(())
3051    }
3052
3053    pub fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
3054        let mut is_project_item = true;
3055        let mut update = proto::UpdateActiveView::default();
3056        if cx.is_window_active() {
3057            if let Some(item) = self.active_item(cx) {
3058                if item.focus_handle(cx).contains_focused(cx) {
3059                    let leader_id = self
3060                        .pane_for(&*item)
3061                        .and_then(|pane| self.leader_for_pane(&pane));
3062
3063                    if let Some(item) = item.to_followable_item_handle(cx) {
3064                        let id = item
3065                            .remote_id(&self.app_state.client, cx)
3066                            .map(|id| id.to_proto());
3067
3068                        if let Some(id) = id.clone() {
3069                            if let Some(variant) = item.to_state_proto(cx) {
3070                                let view = Some(proto::View {
3071                                    id: Some(id.clone()),
3072                                    leader_id,
3073                                    variant: Some(variant),
3074                                });
3075
3076                                is_project_item = item.is_project_item(cx);
3077                                update = proto::UpdateActiveView {
3078                                    view,
3079                                    // TODO: once v0.124.0 is retired we can stop sending these
3080                                    id: Some(id),
3081                                    leader_id,
3082                                };
3083                            }
3084                        };
3085                    }
3086                }
3087            }
3088        }
3089
3090        if &update.id != &self.last_active_view_id {
3091            self.last_active_view_id = update.id.clone();
3092            self.update_followers(
3093                is_project_item,
3094                proto::update_followers::Variant::UpdateActiveView(update),
3095                cx,
3096            );
3097        }
3098    }
3099
3100    fn update_followers(
3101        &self,
3102        project_only: bool,
3103        update: proto::update_followers::Variant,
3104        cx: &mut WindowContext,
3105    ) -> Option<()> {
3106        // If this update only applies to for followers in the current project,
3107        // then skip it unless this project is shared. If it applies to all
3108        // followers, regardless of project, then set `project_id` to none,
3109        // indicating that it goes to all followers.
3110        let project_id = if project_only {
3111            Some(self.project.read(cx).remote_id()?)
3112        } else {
3113            None
3114        };
3115        self.app_state().workspace_store.update(cx, |store, cx| {
3116            store.update_followers(project_id, update, cx)
3117        })
3118    }
3119
3120    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
3121        self.follower_states.get(pane).map(|state| state.leader_id)
3122    }
3123
3124    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3125        cx.notify();
3126
3127        let call = self.active_call()?;
3128        let room = call.read(cx).room()?.read(cx);
3129        let participant = room.remote_participant_for_peer_id(leader_id)?;
3130        let mut items_to_activate = Vec::new();
3131
3132        let leader_in_this_app;
3133        let leader_in_this_project;
3134        match participant.location {
3135            call::ParticipantLocation::SharedProject { project_id } => {
3136                leader_in_this_app = true;
3137                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3138            }
3139            call::ParticipantLocation::UnsharedProject => {
3140                leader_in_this_app = true;
3141                leader_in_this_project = false;
3142            }
3143            call::ParticipantLocation::External => {
3144                leader_in_this_app = false;
3145                leader_in_this_project = false;
3146            }
3147        };
3148
3149        for (pane, state) in &self.follower_states {
3150            if state.leader_id != leader_id {
3151                continue;
3152            }
3153            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
3154                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
3155                    if leader_in_this_project || !item.is_project_item(cx) {
3156                        items_to_activate.push((pane.clone(), item.boxed_clone()));
3157                    }
3158                }
3159                continue;
3160            }
3161
3162            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3163                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3164            }
3165        }
3166
3167        for (pane, item) in items_to_activate {
3168            let pane_was_focused = pane.read(cx).has_focus(cx);
3169            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3170                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3171            } else {
3172                pane.update(cx, |pane, cx| {
3173                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3174                });
3175            }
3176
3177            if pane_was_focused {
3178                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3179            }
3180        }
3181
3182        None
3183    }
3184
3185    fn shared_screen_for_peer(
3186        &self,
3187        peer_id: PeerId,
3188        pane: &View<Pane>,
3189        cx: &mut WindowContext,
3190    ) -> Option<View<SharedScreen>> {
3191        let call = self.active_call()?;
3192        let room = call.read(cx).room()?.read(cx);
3193        let participant = room.remote_participant_for_peer_id(peer_id)?;
3194        let track = participant.video_tracks.values().next()?.clone();
3195        let user = participant.user.clone();
3196
3197        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3198            if item.read(cx).peer_id == peer_id {
3199                return Some(item);
3200            }
3201        }
3202
3203        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3204    }
3205
3206    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
3207        if cx.is_window_active() {
3208            self.update_active_view_for_followers(cx);
3209            cx.background_executor()
3210                .spawn(persistence::DB.update_timestamp(self.database_id()))
3211                .detach();
3212        } else {
3213            for pane in &self.panes {
3214                pane.update(cx, |pane, cx| {
3215                    if let Some(item) = pane.active_item() {
3216                        item.workspace_deactivated(cx);
3217                    }
3218                    if matches!(
3219                        WorkspaceSettings::get_global(cx).autosave,
3220                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3221                    ) {
3222                        for item in pane.items() {
3223                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3224                                .detach_and_log_err(cx);
3225                        }
3226                    }
3227                });
3228            }
3229        }
3230    }
3231
3232    fn active_call(&self) -> Option<&Model<ActiveCall>> {
3233        self.active_call.as_ref().map(|(call, _)| call)
3234    }
3235
3236    fn on_active_call_event(
3237        &mut self,
3238        _: Model<ActiveCall>,
3239        event: &call::room::Event,
3240        cx: &mut ViewContext<Self>,
3241    ) {
3242        match event {
3243            call::room::Event::ParticipantLocationChanged { participant_id }
3244            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3245                self.leader_updated(*participant_id, cx);
3246            }
3247            _ => {}
3248        }
3249    }
3250
3251    pub fn database_id(&self) -> WorkspaceId {
3252        self.database_id
3253    }
3254
3255    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3256        let project = self.project().read(cx);
3257
3258        if project.is_local() {
3259            Some(
3260                project
3261                    .visible_worktrees(cx)
3262                    .map(|worktree| worktree.read(cx).abs_path())
3263                    .collect::<Vec<_>>()
3264                    .into(),
3265            )
3266        } else {
3267            None
3268        }
3269    }
3270
3271    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3272        match member {
3273            Member::Axis(PaneAxis { members, .. }) => {
3274                for child in members.iter() {
3275                    self.remove_panes(child.clone(), cx)
3276                }
3277            }
3278            Member::Pane(pane) => {
3279                self.force_remove_pane(&pane, cx);
3280            }
3281        }
3282    }
3283
3284    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
3285        self.panes.retain(|p| p != pane);
3286        self.panes
3287            .last()
3288            .unwrap()
3289            .update(cx, |pane, cx| pane.focus(cx));
3290        if self.last_active_center_pane == Some(pane.downgrade()) {
3291            self.last_active_center_pane = None;
3292        }
3293        cx.notify();
3294    }
3295
3296    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3297        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3298            cx.background_executor()
3299                .timer(Duration::from_millis(100))
3300                .await;
3301            this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
3302                .log_err();
3303        }));
3304    }
3305
3306    fn serialize_workspace(&self, cx: &mut WindowContext) {
3307        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3308            let (items, active) = {
3309                let pane = pane_handle.read(cx);
3310                let active_item_id = pane.active_item().map(|item| item.item_id());
3311                (
3312                    pane.items()
3313                        .filter_map(|item_handle| {
3314                            Some(SerializedItem {
3315                                kind: Arc::from(item_handle.serialized_item_kind()?),
3316                                item_id: item_handle.item_id().as_u64(),
3317                                active: Some(item_handle.item_id()) == active_item_id,
3318                            })
3319                        })
3320                        .collect::<Vec<_>>(),
3321                    pane.has_focus(cx),
3322                )
3323            };
3324
3325            SerializedPane::new(items, active)
3326        }
3327
3328        fn build_serialized_pane_group(
3329            pane_group: &Member,
3330            cx: &WindowContext,
3331        ) -> SerializedPaneGroup {
3332            match pane_group {
3333                Member::Axis(PaneAxis {
3334                    axis,
3335                    members,
3336                    flexes,
3337                    bounding_boxes: _,
3338                }) => SerializedPaneGroup::Group {
3339                    axis: SerializedAxis(*axis),
3340                    children: members
3341                        .iter()
3342                        .map(|member| build_serialized_pane_group(member, cx))
3343                        .collect::<Vec<_>>(),
3344                    flexes: Some(flexes.lock().clone()),
3345                },
3346                Member::Pane(pane_handle) => {
3347                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
3348                }
3349            }
3350        }
3351
3352        fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
3353            let left_dock = this.left_dock.read(cx);
3354            let left_visible = left_dock.is_open();
3355            let left_active_panel = left_dock
3356                .visible_panel()
3357                .and_then(|panel| Some(panel.persistent_name().to_string()));
3358            let left_dock_zoom = left_dock
3359                .visible_panel()
3360                .map(|panel| panel.is_zoomed(cx))
3361                .unwrap_or(false);
3362
3363            let right_dock = this.right_dock.read(cx);
3364            let right_visible = right_dock.is_open();
3365            let right_active_panel = right_dock
3366                .visible_panel()
3367                .and_then(|panel| Some(panel.persistent_name().to_string()));
3368            let right_dock_zoom = right_dock
3369                .visible_panel()
3370                .map(|panel| panel.is_zoomed(cx))
3371                .unwrap_or(false);
3372
3373            let bottom_dock = this.bottom_dock.read(cx);
3374            let bottom_visible = bottom_dock.is_open();
3375            let bottom_active_panel = bottom_dock
3376                .visible_panel()
3377                .and_then(|panel| Some(panel.persistent_name().to_string()));
3378            let bottom_dock_zoom = bottom_dock
3379                .visible_panel()
3380                .map(|panel| panel.is_zoomed(cx))
3381                .unwrap_or(false);
3382
3383            DockStructure {
3384                left: DockData {
3385                    visible: left_visible,
3386                    active_panel: left_active_panel,
3387                    zoom: left_dock_zoom,
3388                },
3389                right: DockData {
3390                    visible: right_visible,
3391                    active_panel: right_active_panel,
3392                    zoom: right_dock_zoom,
3393                },
3394                bottom: DockData {
3395                    visible: bottom_visible,
3396                    active_panel: bottom_active_panel,
3397                    zoom: bottom_dock_zoom,
3398                },
3399            }
3400        }
3401
3402        if let Some(location) = self.location(cx) {
3403            // Load bearing special case:
3404            //  - with_local_workspace() relies on this to not have other stuff open
3405            //    when you open your log
3406            if !location.paths().is_empty() {
3407                let center_group = build_serialized_pane_group(&self.center.root, cx);
3408                let docks = build_serialized_docks(self, cx);
3409
3410                let serialized_workspace = SerializedWorkspace {
3411                    id: self.database_id,
3412                    location,
3413                    center_group,
3414                    bounds: Default::default(),
3415                    display: Default::default(),
3416                    docks,
3417                };
3418
3419                cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace))
3420                    .detach();
3421            }
3422        }
3423    }
3424
3425    pub(crate) fn load_workspace(
3426        serialized_workspace: SerializedWorkspace,
3427        paths_to_open: Vec<Option<ProjectPath>>,
3428        cx: &mut ViewContext<Workspace>,
3429    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3430        cx.spawn(|workspace, mut cx| async move {
3431            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3432
3433            let mut center_group = None;
3434            let mut center_items = None;
3435
3436            // Traverse the splits tree and add to things
3437            if let Some((group, active_pane, items)) = serialized_workspace
3438                .center_group
3439                .deserialize(
3440                    &project,
3441                    serialized_workspace.id,
3442                    workspace.clone(),
3443                    &mut cx,
3444                )
3445                .await
3446            {
3447                center_items = Some(items);
3448                center_group = Some((group, active_pane))
3449            }
3450
3451            let mut items_by_project_path = cx.update(|cx| {
3452                center_items
3453                    .unwrap_or_default()
3454                    .into_iter()
3455                    .filter_map(|item| {
3456                        let item = item?;
3457                        let project_path = item.project_path(cx)?;
3458                        Some((project_path, item))
3459                    })
3460                    .collect::<HashMap<_, _>>()
3461            })?;
3462
3463            let opened_items = paths_to_open
3464                .into_iter()
3465                .map(|path_to_open| {
3466                    path_to_open
3467                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3468                })
3469                .collect::<Vec<_>>();
3470
3471            // Remove old panes from workspace panes list
3472            workspace.update(&mut cx, |workspace, cx| {
3473                if let Some((center_group, active_pane)) = center_group {
3474                    workspace.remove_panes(workspace.center.root.clone(), cx);
3475
3476                    // Swap workspace center group
3477                    workspace.center = PaneGroup::with_root(center_group);
3478                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3479                    if let Some(active_pane) = active_pane {
3480                        workspace.active_pane = active_pane;
3481                        cx.focus_self();
3482                    } else {
3483                        workspace.active_pane = workspace.center.first_pane().clone();
3484                    }
3485                }
3486
3487                let docks = serialized_workspace.docks;
3488
3489                let right = docks.right.clone();
3490                workspace
3491                    .right_dock
3492                    .update(cx, |dock, _| dock.serialized_dock = Some(right));
3493                let left = docks.left.clone();
3494                workspace
3495                    .left_dock
3496                    .update(cx, |dock, _| dock.serialized_dock = Some(left));
3497                let bottom = docks.bottom.clone();
3498                workspace
3499                    .bottom_dock
3500                    .update(cx, |dock, _| dock.serialized_dock = Some(bottom));
3501
3502                cx.notify();
3503            })?;
3504
3505            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3506            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3507
3508            Ok(opened_items)
3509        })
3510    }
3511
3512    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3513        self.add_workspace_actions_listeners(div, cx)
3514            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3515            .on_action(cx.listener(Self::close_all_items_and_panes))
3516            .on_action(cx.listener(Self::save_all))
3517            .on_action(cx.listener(Self::send_keystrokes))
3518            .on_action(cx.listener(Self::add_folder_to_project))
3519            .on_action(cx.listener(Self::follow_next_collaborator))
3520            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3521                let pane = workspace.active_pane().clone();
3522                workspace.unfollow(&pane, cx);
3523            }))
3524            .on_action(cx.listener(|workspace, action: &Save, cx| {
3525                workspace
3526                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3527                    .detach_and_log_err(cx);
3528            }))
3529            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3530                workspace
3531                    .save_active_item(SaveIntent::SaveAs, cx)
3532                    .detach_and_log_err(cx);
3533            }))
3534            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3535                workspace.activate_previous_pane(cx)
3536            }))
3537            .on_action(
3538                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3539            )
3540            .on_action(
3541                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3542                    workspace.activate_pane_in_direction(action.0, cx)
3543                }),
3544            )
3545            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3546                workspace.swap_pane_in_direction(action.0, cx)
3547            }))
3548            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3549                this.toggle_dock(DockPosition::Left, cx);
3550            }))
3551            .on_action(
3552                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3553                    workspace.toggle_dock(DockPosition::Right, cx);
3554                }),
3555            )
3556            .on_action(
3557                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3558                    workspace.toggle_dock(DockPosition::Bottom, cx);
3559                }),
3560            )
3561            .on_action(
3562                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3563                    workspace.close_all_docks(cx);
3564                }),
3565            )
3566            .on_action(cx.listener(Workspace::open))
3567            .on_action(cx.listener(Workspace::close_window))
3568            .on_action(cx.listener(Workspace::activate_pane_at_index))
3569            .on_action(
3570                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3571                    workspace.reopen_closed_item(cx).detach();
3572                }),
3573            )
3574    }
3575
3576    #[cfg(any(test, feature = "test-support"))]
3577    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3578        use node_runtime::FakeNodeRuntime;
3579
3580        let client = project.read(cx).client();
3581        let user_store = project.read(cx).user_store();
3582
3583        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
3584        cx.activate_window();
3585        let app_state = Arc::new(AppState {
3586            languages: project.read(cx).languages().clone(),
3587            workspace_store,
3588            client,
3589            user_store,
3590            fs: project.read(cx).fs().clone(),
3591            build_window_options: |_, _, _| Default::default(),
3592            node_runtime: FakeNodeRuntime::new(),
3593        });
3594        let workspace = Self::new(0, project, app_state, cx);
3595        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3596        workspace
3597    }
3598
3599    pub fn register_action<A: Action>(
3600        &mut self,
3601        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3602    ) -> &mut Self {
3603        let callback = Arc::new(callback);
3604
3605        self.workspace_actions.push(Box::new(move |div, cx| {
3606            let callback = callback.clone();
3607            div.on_action(
3608                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3609            )
3610        }));
3611        self
3612    }
3613
3614    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3615        let mut div = div
3616            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3617            .on_action(cx.listener(Self::close_all_items_and_panes))
3618            .on_action(cx.listener(Self::add_folder_to_project))
3619            .on_action(cx.listener(Self::save_all))
3620            .on_action(cx.listener(Self::open));
3621        for action in self.workspace_actions.iter() {
3622            div = (action)(div, cx)
3623        }
3624        div
3625    }
3626
3627    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3628        self.modal_layer.read(cx).has_active_modal()
3629    }
3630
3631    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
3632        self.modal_layer.read(cx).active_modal()
3633    }
3634
3635    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
3636    where
3637        B: FnOnce(&mut ViewContext<V>) -> V,
3638    {
3639        self.modal_layer
3640            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3641    }
3642}
3643
3644fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3645    let display_origin = cx
3646        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3647        .ok()??;
3648    ZED_WINDOW_POSITION
3649        .zip(*ZED_WINDOW_SIZE)
3650        .map(|(position, size)| {
3651            WindowBounds::Fixed(Bounds {
3652                origin: display_origin + position,
3653                size,
3654            })
3655        })
3656}
3657
3658fn open_items(
3659    serialized_workspace: Option<SerializedWorkspace>,
3660    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3661    app_state: Arc<AppState>,
3662    cx: &mut ViewContext<Workspace>,
3663) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3664    let restored_items = serialized_workspace.map(|serialized_workspace| {
3665        Workspace::load_workspace(
3666            serialized_workspace,
3667            project_paths_to_open
3668                .iter()
3669                .map(|(_, project_path)| project_path)
3670                .cloned()
3671                .collect(),
3672            cx,
3673        )
3674    });
3675
3676    cx.spawn(|workspace, mut cx| async move {
3677        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3678
3679        if let Some(restored_items) = restored_items {
3680            let restored_items = restored_items.await?;
3681
3682            let restored_project_paths = restored_items
3683                .iter()
3684                .filter_map(|item| {
3685                    cx.update(|cx| item.as_ref()?.project_path(cx))
3686                        .ok()
3687                        .flatten()
3688                })
3689                .collect::<HashSet<_>>();
3690
3691            for restored_item in restored_items {
3692                opened_items.push(restored_item.map(Ok));
3693            }
3694
3695            project_paths_to_open
3696                .iter_mut()
3697                .for_each(|(_, project_path)| {
3698                    if let Some(project_path_to_open) = project_path {
3699                        if restored_project_paths.contains(project_path_to_open) {
3700                            *project_path = None;
3701                        }
3702                    }
3703                });
3704        } else {
3705            for _ in 0..project_paths_to_open.len() {
3706                opened_items.push(None);
3707            }
3708        }
3709        assert!(opened_items.len() == project_paths_to_open.len());
3710
3711        let tasks =
3712            project_paths_to_open
3713                .into_iter()
3714                .enumerate()
3715                .map(|(i, (abs_path, project_path))| {
3716                    let workspace = workspace.clone();
3717                    cx.spawn(|mut cx| {
3718                        let fs = app_state.fs.clone();
3719                        async move {
3720                            let file_project_path = project_path?;
3721                            if fs.is_file(&abs_path).await {
3722                                Some((
3723                                    i,
3724                                    workspace
3725                                        .update(&mut cx, |workspace, cx| {
3726                                            workspace.open_path(file_project_path, None, true, cx)
3727                                        })
3728                                        .log_err()?
3729                                        .await,
3730                                ))
3731                            } else {
3732                                None
3733                            }
3734                        }
3735                    })
3736                });
3737
3738        let tasks = tasks.collect::<Vec<_>>();
3739
3740        let tasks = futures::future::join_all(tasks.into_iter());
3741        for maybe_opened_path in tasks.await.into_iter() {
3742            if let Some((i, path_open_result)) = maybe_opened_path {
3743                opened_items[i] = Some(path_open_result);
3744            }
3745        }
3746
3747        Ok(opened_items)
3748    })
3749}
3750
3751enum ActivateInDirectionTarget {
3752    Pane(View<Pane>),
3753    Dock(View<Dock>),
3754}
3755
3756fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3757    const REPORT_ISSUE_URL: &str = "https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3758
3759    workspace
3760        .update(cx, |workspace, cx| {
3761            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3762                workspace.show_notification_once(0, cx, |cx| {
3763                    cx.new_view(|_| {
3764                        MessageNotification::new("Failed to load the database file.")
3765                            .with_click_message("Click to let us know about this error")
3766                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3767                    })
3768                });
3769            }
3770        })
3771        .log_err();
3772}
3773
3774impl FocusableView for Workspace {
3775    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3776        self.active_pane.focus_handle(cx)
3777    }
3778}
3779
3780#[derive(Clone, Render)]
3781struct DraggedDock(DockPosition);
3782
3783impl Render for Workspace {
3784    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3785        let mut context = KeyContext::default();
3786        context.add("Workspace");
3787
3788        let (ui_font, ui_font_size) = {
3789            let theme_settings = ThemeSettings::get_global(cx);
3790            (
3791                theme_settings.ui_font.family.clone(),
3792                theme_settings.ui_font_size.clone(),
3793            )
3794        };
3795
3796        let theme = cx.theme().clone();
3797        let colors = theme.colors();
3798        cx.set_rem_size(ui_font_size);
3799
3800        self.actions(div(), cx)
3801            .key_context(context)
3802            .relative()
3803            .size_full()
3804            .flex()
3805            .flex_col()
3806            .font(ui_font)
3807            .gap_0()
3808            .justify_start()
3809            .items_start()
3810            .text_color(colors.text)
3811            .bg(colors.background)
3812            .border()
3813            .border_color(colors.border)
3814            .children(self.titlebar_item.clone())
3815            .child(
3816                div()
3817                    .id("workspace")
3818                    .relative()
3819                    .flex_1()
3820                    .w_full()
3821                    .flex()
3822                    .flex_col()
3823                    .overflow_hidden()
3824                    .border_t()
3825                    .border_b()
3826                    .border_color(colors.border)
3827                    .child(
3828                        canvas({
3829                            let this = cx.view().clone();
3830                            move |bounds, cx| {
3831                                this.update(cx, |this, _cx| {
3832                                    this.bounds = *bounds;
3833                                })
3834                            }
3835                        })
3836                        .absolute()
3837                        .size_full(),
3838                    )
3839                    .on_drag_move(
3840                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3841                            match e.drag(cx).0 {
3842                                DockPosition::Left => {
3843                                    let size = workspace.bounds.left() + e.event.position.x;
3844                                    workspace.left_dock.update(cx, |left_dock, cx| {
3845                                        left_dock.resize_active_panel(Some(size), cx);
3846                                    });
3847                                }
3848                                DockPosition::Right => {
3849                                    let size = workspace.bounds.right() - e.event.position.x;
3850                                    workspace.right_dock.update(cx, |right_dock, cx| {
3851                                        right_dock.resize_active_panel(Some(size), cx);
3852                                    });
3853                                }
3854                                DockPosition::Bottom => {
3855                                    let size = workspace.bounds.bottom() - e.event.position.y;
3856                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3857                                        bottom_dock.resize_active_panel(Some(size), cx);
3858                                    });
3859                                }
3860                            }
3861                        }),
3862                    )
3863                    .child(self.modal_layer.clone())
3864                    .child(
3865                        div()
3866                            .flex()
3867                            .flex_row()
3868                            .h_full()
3869                            // Left Dock
3870                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3871                                || {
3872                                    div()
3873                                        .flex()
3874                                        .flex_none()
3875                                        .overflow_hidden()
3876                                        .child(self.left_dock.clone())
3877                                },
3878                            ))
3879                            // Panes
3880                            .child(
3881                                div()
3882                                    .flex()
3883                                    .flex_col()
3884                                    .flex_1()
3885                                    .overflow_hidden()
3886                                    .child(self.center.render(
3887                                        &self.project,
3888                                        &self.follower_states,
3889                                        self.active_call(),
3890                                        &self.active_pane,
3891                                        self.zoomed.as_ref(),
3892                                        &self.app_state,
3893                                        cx,
3894                                    ))
3895                                    .children(
3896                                        self.zoomed_position
3897                                            .ne(&Some(DockPosition::Bottom))
3898                                            .then(|| self.bottom_dock.clone()),
3899                                    ),
3900                            )
3901                            // Right Dock
3902                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3903                                || {
3904                                    div()
3905                                        .flex()
3906                                        .flex_none()
3907                                        .overflow_hidden()
3908                                        .child(self.right_dock.clone())
3909                                },
3910                            )),
3911                    )
3912                    .children(self.render_notifications(cx))
3913                    .children(self.zoomed.as_ref().and_then(|view| {
3914                        let zoomed_view = view.upgrade()?;
3915                        let div = div()
3916                            .z_index(1)
3917                            .absolute()
3918                            .overflow_hidden()
3919                            .border_color(colors.border)
3920                            .bg(colors.background)
3921                            .child(zoomed_view)
3922                            .inset_0()
3923                            .shadow_lg();
3924
3925                        Some(match self.zoomed_position {
3926                            Some(DockPosition::Left) => div.right_2().border_r(),
3927                            Some(DockPosition::Right) => div.left_2().border_l(),
3928                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3929                            None => div.top_2().bottom_2().left_2().right_2().border(),
3930                        })
3931                    })),
3932            )
3933            .child(self.status_bar.clone())
3934            .children(if self.project.read(cx).is_disconnected() {
3935                Some(DisconnectedOverlay)
3936            } else {
3937                None
3938            })
3939    }
3940}
3941
3942impl WorkspaceStore {
3943    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3944        Self {
3945            workspaces: Default::default(),
3946            _subscriptions: vec![
3947                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3948                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3949            ],
3950            client,
3951        }
3952    }
3953
3954    pub fn update_followers(
3955        &self,
3956        project_id: Option<u64>,
3957        update: proto::update_followers::Variant,
3958        cx: &AppContext,
3959    ) -> Option<()> {
3960        let active_call = ActiveCall::try_global(cx)?;
3961        let room_id = active_call.read(cx).room()?.read(cx).id();
3962        self.client
3963            .send(proto::UpdateFollowers {
3964                room_id,
3965                project_id,
3966                variant: Some(update),
3967            })
3968            .log_err()
3969    }
3970
3971    pub async fn handle_follow(
3972        this: Model<Self>,
3973        envelope: TypedEnvelope<proto::Follow>,
3974        _: Arc<Client>,
3975        mut cx: AsyncAppContext,
3976    ) -> Result<proto::FollowResponse> {
3977        this.update(&mut cx, |this, cx| {
3978            let follower = Follower {
3979                project_id: envelope.payload.project_id,
3980                peer_id: envelope.original_sender_id()?,
3981            };
3982            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3983
3984            let mut response = proto::FollowResponse::default();
3985            this.workspaces.retain(|workspace| {
3986                workspace
3987                    .update(cx, |workspace, cx| {
3988                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3989                        if response.views.is_empty() {
3990                            response.views = handler_response.views;
3991                        } else {
3992                            response.views.extend_from_slice(&handler_response.views);
3993                        }
3994
3995                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3996                            if response.active_view_id.is_none()
3997                                || Some(workspace.project.downgrade()) == active_project
3998                            {
3999                                response.active_view_id = Some(active_view_id);
4000                            }
4001                        }
4002
4003                        if let Some(active_view) = handler_response.active_view.clone() {
4004                            if workspace.project.read(cx).remote_id() == follower.project_id {
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.clone(),
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(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(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(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| workspace.add_item(Box::new(item1), cx));
4763        project.update(cx, |project, cx| {
4764            assert_eq!(
4765                project.active_entry(),
4766                project
4767                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4768                    .map(|e| e.id)
4769            );
4770        });
4771        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4772
4773        // Add a second item to a non-empty pane
4774        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4775        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
4776        project.update(cx, |project, cx| {
4777            assert_eq!(
4778                project.active_entry(),
4779                project
4780                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4781                    .map(|e| e.id)
4782            );
4783        });
4784
4785        // Close the active item
4786        pane.update(cx, |pane, cx| {
4787            pane.close_active_item(&Default::default(), cx).unwrap()
4788        })
4789        .await
4790        .unwrap();
4791        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4792        project.update(cx, |project, cx| {
4793            assert_eq!(
4794                project.active_entry(),
4795                project
4796                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4797                    .map(|e| e.id)
4798            );
4799        });
4800
4801        // Add a project folder
4802        project
4803            .update(cx, |project, cx| {
4804                project.find_or_create_local_worktree("/root2", true, cx)
4805            })
4806            .await
4807            .unwrap();
4808        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
4809
4810        // Remove a project folder
4811        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4812        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
4813    }
4814
4815    #[gpui::test]
4816    async fn test_close_window(cx: &mut TestAppContext) {
4817        init_test(cx);
4818
4819        let fs = FakeFs::new(cx.executor());
4820        fs.insert_tree("/root", json!({ "one": "" })).await;
4821
4822        let project = Project::test(fs, ["root".as_ref()], cx).await;
4823        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4824
4825        // When there are no dirty items, there's nothing to do.
4826        let item1 = cx.new_view(|cx| TestItem::new(cx));
4827        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4828        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4829        assert!(task.await.unwrap());
4830
4831        // When there are dirty untitled items, prompt to save each one. If the user
4832        // cancels any prompt, then abort.
4833        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
4834        let item3 = cx.new_view(|cx| {
4835            TestItem::new(cx)
4836                .with_dirty(true)
4837                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4838        });
4839        workspace.update(cx, |w, cx| {
4840            w.add_item(Box::new(item2.clone()), cx);
4841            w.add_item(Box::new(item3.clone()), cx);
4842        });
4843        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4844        cx.executor().run_until_parked();
4845        cx.simulate_prompt_answer(2); // cancel save all
4846        cx.executor().run_until_parked();
4847        cx.simulate_prompt_answer(2); // cancel save all
4848        cx.executor().run_until_parked();
4849        assert!(!cx.has_pending_prompt());
4850        assert!(!task.await.unwrap());
4851    }
4852
4853    #[gpui::test]
4854    async fn test_close_pane_items(cx: &mut TestAppContext) {
4855        init_test(cx);
4856
4857        let fs = FakeFs::new(cx.executor());
4858
4859        let project = Project::test(fs, None, cx).await;
4860        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4861
4862        let item1 = cx.new_view(|cx| {
4863            TestItem::new(cx)
4864                .with_dirty(true)
4865                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4866        });
4867        let item2 = cx.new_view(|cx| {
4868            TestItem::new(cx)
4869                .with_dirty(true)
4870                .with_conflict(true)
4871                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4872        });
4873        let item3 = cx.new_view(|cx| {
4874            TestItem::new(cx)
4875                .with_dirty(true)
4876                .with_conflict(true)
4877                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4878        });
4879        let item4 = cx.new_view(|cx| {
4880            TestItem::new(cx)
4881                .with_dirty(true)
4882                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4883        });
4884        let pane = workspace.update(cx, |workspace, cx| {
4885            workspace.add_item(Box::new(item1.clone()), cx);
4886            workspace.add_item(Box::new(item2.clone()), cx);
4887            workspace.add_item(Box::new(item3.clone()), cx);
4888            workspace.add_item(Box::new(item4.clone()), cx);
4889            workspace.active_pane().clone()
4890        });
4891
4892        let close_items = pane.update(cx, |pane, cx| {
4893            pane.activate_item(1, true, true, cx);
4894            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4895            let item1_id = item1.item_id();
4896            let item3_id = item3.item_id();
4897            let item4_id = item4.item_id();
4898            pane.close_items(cx, SaveIntent::Close, move |id| {
4899                [item1_id, item3_id, item4_id].contains(&id)
4900            })
4901        });
4902        cx.executor().run_until_parked();
4903
4904        assert!(cx.has_pending_prompt());
4905        // Ignore "Save all" prompt
4906        cx.simulate_prompt_answer(2);
4907        cx.executor().run_until_parked();
4908        // There's a prompt to save item 1.
4909        pane.update(cx, |pane, _| {
4910            assert_eq!(pane.items_len(), 4);
4911            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4912        });
4913        // Confirm saving item 1.
4914        cx.simulate_prompt_answer(0);
4915        cx.executor().run_until_parked();
4916
4917        // Item 1 is saved. There's a prompt to save item 3.
4918        pane.update(cx, |pane, cx| {
4919            assert_eq!(item1.read(cx).save_count, 1);
4920            assert_eq!(item1.read(cx).save_as_count, 0);
4921            assert_eq!(item1.read(cx).reload_count, 0);
4922            assert_eq!(pane.items_len(), 3);
4923            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4924        });
4925        assert!(cx.has_pending_prompt());
4926
4927        // Cancel saving item 3.
4928        cx.simulate_prompt_answer(1);
4929        cx.executor().run_until_parked();
4930
4931        // Item 3 is reloaded. There's a prompt to save item 4.
4932        pane.update(cx, |pane, cx| {
4933            assert_eq!(item3.read(cx).save_count, 0);
4934            assert_eq!(item3.read(cx).save_as_count, 0);
4935            assert_eq!(item3.read(cx).reload_count, 1);
4936            assert_eq!(pane.items_len(), 2);
4937            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4938        });
4939        assert!(cx.has_pending_prompt());
4940
4941        // Confirm saving item 4.
4942        cx.simulate_prompt_answer(0);
4943        cx.executor().run_until_parked();
4944
4945        // There's a prompt for a path for item 4.
4946        cx.simulate_new_path_selection(|_| Some(Default::default()));
4947        close_items.await.unwrap();
4948
4949        // The requested items are closed.
4950        pane.update(cx, |pane, cx| {
4951            assert_eq!(item4.read(cx).save_count, 0);
4952            assert_eq!(item4.read(cx).save_as_count, 1);
4953            assert_eq!(item4.read(cx).reload_count, 0);
4954            assert_eq!(pane.items_len(), 1);
4955            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4956        });
4957    }
4958
4959    #[gpui::test]
4960    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4961        init_test(cx);
4962
4963        let fs = FakeFs::new(cx.executor());
4964        let project = Project::test(fs, [], cx).await;
4965        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4966
4967        // Create several workspace items with single project entries, and two
4968        // workspace items with multiple project entries.
4969        let single_entry_items = (0..=4)
4970            .map(|project_entry_id| {
4971                cx.new_view(|cx| {
4972                    TestItem::new(cx)
4973                        .with_dirty(true)
4974                        .with_project_items(&[TestProjectItem::new(
4975                            project_entry_id,
4976                            &format!("{project_entry_id}.txt"),
4977                            cx,
4978                        )])
4979                })
4980            })
4981            .collect::<Vec<_>>();
4982        let item_2_3 = cx.new_view(|cx| {
4983            TestItem::new(cx)
4984                .with_dirty(true)
4985                .with_singleton(false)
4986                .with_project_items(&[
4987                    single_entry_items[2].read(cx).project_items[0].clone(),
4988                    single_entry_items[3].read(cx).project_items[0].clone(),
4989                ])
4990        });
4991        let item_3_4 = cx.new_view(|cx| {
4992            TestItem::new(cx)
4993                .with_dirty(true)
4994                .with_singleton(false)
4995                .with_project_items(&[
4996                    single_entry_items[3].read(cx).project_items[0].clone(),
4997                    single_entry_items[4].read(cx).project_items[0].clone(),
4998                ])
4999        });
5000
5001        // Create two panes that contain the following project entries:
5002        //   left pane:
5003        //     multi-entry items:   (2, 3)
5004        //     single-entry items:  0, 1, 2, 3, 4
5005        //   right pane:
5006        //     single-entry items:  1
5007        //     multi-entry items:   (3, 4)
5008        let left_pane = workspace.update(cx, |workspace, cx| {
5009            let left_pane = workspace.active_pane().clone();
5010            workspace.add_item(Box::new(item_2_3.clone()), cx);
5011            for item in single_entry_items {
5012                workspace.add_item(Box::new(item), cx);
5013            }
5014            left_pane.update(cx, |pane, cx| {
5015                pane.activate_item(2, true, true, cx);
5016            });
5017
5018            let right_pane = workspace
5019                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
5020                .unwrap();
5021
5022            right_pane.update(cx, |pane, cx| {
5023                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
5024            });
5025
5026            left_pane
5027        });
5028
5029        cx.focus_view(&left_pane);
5030
5031        // When closing all of the items in the left pane, we should be prompted twice:
5032        // once for project entry 0, and once for project entry 2. Project entries 1,
5033        // 3, and 4 are all still open in the other paten. After those two
5034        // prompts, the task should complete.
5035
5036        let close = left_pane.update(cx, |pane, cx| {
5037            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
5038        });
5039        cx.executor().run_until_parked();
5040
5041        // Discard "Save all" prompt
5042        cx.simulate_prompt_answer(2);
5043
5044        cx.executor().run_until_parked();
5045        left_pane.update(cx, |pane, cx| {
5046            assert_eq!(
5047                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5048                &[ProjectEntryId::from_proto(0)]
5049            );
5050        });
5051        cx.simulate_prompt_answer(0);
5052
5053        cx.executor().run_until_parked();
5054        left_pane.update(cx, |pane, cx| {
5055            assert_eq!(
5056                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5057                &[ProjectEntryId::from_proto(2)]
5058            );
5059        });
5060        cx.simulate_prompt_answer(0);
5061
5062        cx.executor().run_until_parked();
5063        close.await.unwrap();
5064        left_pane.update(cx, |pane, _| {
5065            assert_eq!(pane.items_len(), 0);
5066        });
5067    }
5068
5069    #[gpui::test]
5070    async fn test_autosave(cx: &mut gpui::TestAppContext) {
5071        init_test(cx);
5072
5073        let fs = FakeFs::new(cx.executor());
5074        let project = Project::test(fs, [], cx).await;
5075        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5076        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5077
5078        let item = cx.new_view(|cx| {
5079            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5080        });
5081        let item_id = item.entity_id();
5082        workspace.update(cx, |workspace, cx| {
5083            workspace.add_item(Box::new(item.clone()), cx);
5084        });
5085
5086        // Autosave on window change.
5087        item.update(cx, |item, cx| {
5088            cx.update_global(|settings: &mut SettingsStore, cx| {
5089                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5090                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
5091                })
5092            });
5093            item.is_dirty = true;
5094        });
5095
5096        // Deactivating the window saves the file.
5097        cx.deactivate_window();
5098        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5099
5100        // Autosave on focus change.
5101        item.update(cx, |item, cx| {
5102            cx.focus_self();
5103            cx.update_global(|settings: &mut SettingsStore, cx| {
5104                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5105                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5106                })
5107            });
5108            item.is_dirty = true;
5109        });
5110
5111        // Blurring the item saves the file.
5112        item.update(cx, |_, cx| cx.blur());
5113        cx.executor().run_until_parked();
5114        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
5115
5116        // Deactivating the window still saves the file.
5117        cx.update(|cx| cx.activate_window());
5118        item.update(cx, |item, cx| {
5119            cx.focus_self();
5120            item.is_dirty = true;
5121        });
5122        cx.deactivate_window();
5123
5124        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5125
5126        // Autosave after delay.
5127        item.update(cx, |item, cx| {
5128            cx.update_global(|settings: &mut SettingsStore, cx| {
5129                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5130                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5131                })
5132            });
5133            item.is_dirty = true;
5134            cx.emit(ItemEvent::Edit);
5135        });
5136
5137        // Delay hasn't fully expired, so the file is still dirty and unsaved.
5138        cx.executor().advance_clock(Duration::from_millis(250));
5139        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5140
5141        // After delay expires, the file is saved.
5142        cx.executor().advance_clock(Duration::from_millis(250));
5143        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
5144
5145        // Autosave on focus change, ensuring closing the tab counts as such.
5146        item.update(cx, |item, cx| {
5147            cx.update_global(|settings: &mut SettingsStore, cx| {
5148                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5149                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5150                })
5151            });
5152            item.is_dirty = true;
5153        });
5154
5155        pane.update(cx, |pane, cx| {
5156            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5157        })
5158        .await
5159        .unwrap();
5160        assert!(!cx.has_pending_prompt());
5161        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5162
5163        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5164        workspace.update(cx, |workspace, cx| {
5165            workspace.add_item(Box::new(item.clone()), cx);
5166        });
5167        item.update(cx, |item, cx| {
5168            item.project_items[0].update(cx, |item, _| {
5169                item.entry_id = None;
5170            });
5171            item.is_dirty = true;
5172            cx.blur();
5173        });
5174        cx.run_until_parked();
5175        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5176
5177        // Ensure autosave is prevented for deleted files also when closing the buffer.
5178        let _close_items = pane.update(cx, |pane, cx| {
5179            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5180        });
5181        cx.run_until_parked();
5182        assert!(cx.has_pending_prompt());
5183        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5184    }
5185
5186    #[gpui::test]
5187    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5188        init_test(cx);
5189
5190        let fs = FakeFs::new(cx.executor());
5191
5192        let project = Project::test(fs, [], cx).await;
5193        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5194
5195        let item = cx.new_view(|cx| {
5196            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5197        });
5198        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5199        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
5200        let toolbar_notify_count = Rc::new(RefCell::new(0));
5201
5202        workspace.update(cx, |workspace, cx| {
5203            workspace.add_item(Box::new(item.clone()), cx);
5204            let toolbar_notification_count = toolbar_notify_count.clone();
5205            cx.observe(&toolbar, move |_, _, _| {
5206                *toolbar_notification_count.borrow_mut() += 1
5207            })
5208            .detach();
5209        });
5210
5211        pane.update(cx, |pane, _| {
5212            assert!(!pane.can_navigate_backward());
5213            assert!(!pane.can_navigate_forward());
5214        });
5215
5216        item.update(cx, |item, cx| {
5217            item.set_state("one".to_string(), cx);
5218        });
5219
5220        // Toolbar must be notified to re-render the navigation buttons
5221        assert_eq!(*toolbar_notify_count.borrow(), 1);
5222
5223        pane.update(cx, |pane, _| {
5224            assert!(pane.can_navigate_backward());
5225            assert!(!pane.can_navigate_forward());
5226        });
5227
5228        workspace
5229            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5230            .await
5231            .unwrap();
5232
5233        assert_eq!(*toolbar_notify_count.borrow(), 2);
5234        pane.update(cx, |pane, _| {
5235            assert!(!pane.can_navigate_backward());
5236            assert!(pane.can_navigate_forward());
5237        });
5238    }
5239
5240    #[gpui::test]
5241    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5242        init_test(cx);
5243        let fs = FakeFs::new(cx.executor());
5244
5245        let project = Project::test(fs, [], cx).await;
5246        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5247
5248        let panel = workspace.update(cx, |workspace, cx| {
5249            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5250            workspace.add_panel(panel.clone(), cx);
5251
5252            workspace
5253                .right_dock()
5254                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5255
5256            panel
5257        });
5258
5259        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5260        pane.update(cx, |pane, cx| {
5261            let item = cx.new_view(|cx| TestItem::new(cx));
5262            pane.add_item(Box::new(item), true, true, None, cx);
5263        });
5264
5265        // Transfer focus from center to panel
5266        workspace.update(cx, |workspace, cx| {
5267            workspace.toggle_panel_focus::<TestPanel>(cx);
5268        });
5269
5270        workspace.update(cx, |workspace, cx| {
5271            assert!(workspace.right_dock().read(cx).is_open());
5272            assert!(!panel.is_zoomed(cx));
5273            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5274        });
5275
5276        // Transfer focus from panel to center
5277        workspace.update(cx, |workspace, cx| {
5278            workspace.toggle_panel_focus::<TestPanel>(cx);
5279        });
5280
5281        workspace.update(cx, |workspace, cx| {
5282            assert!(workspace.right_dock().read(cx).is_open());
5283            assert!(!panel.is_zoomed(cx));
5284            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5285        });
5286
5287        // Close the dock
5288        workspace.update(cx, |workspace, cx| {
5289            workspace.toggle_dock(DockPosition::Right, cx);
5290        });
5291
5292        workspace.update(cx, |workspace, cx| {
5293            assert!(!workspace.right_dock().read(cx).is_open());
5294            assert!(!panel.is_zoomed(cx));
5295            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5296        });
5297
5298        // Open the dock
5299        workspace.update(cx, |workspace, cx| {
5300            workspace.toggle_dock(DockPosition::Right, cx);
5301        });
5302
5303        workspace.update(cx, |workspace, cx| {
5304            assert!(workspace.right_dock().read(cx).is_open());
5305            assert!(!panel.is_zoomed(cx));
5306            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5307        });
5308
5309        // Focus and zoom panel
5310        panel.update(cx, |panel, cx| {
5311            cx.focus_self();
5312            panel.set_zoomed(true, cx)
5313        });
5314
5315        workspace.update(cx, |workspace, cx| {
5316            assert!(workspace.right_dock().read(cx).is_open());
5317            assert!(panel.is_zoomed(cx));
5318            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5319        });
5320
5321        // Transfer focus to the center closes the dock
5322        workspace.update(cx, |workspace, cx| {
5323            workspace.toggle_panel_focus::<TestPanel>(cx);
5324        });
5325
5326        workspace.update(cx, |workspace, cx| {
5327            assert!(!workspace.right_dock().read(cx).is_open());
5328            assert!(panel.is_zoomed(cx));
5329            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5330        });
5331
5332        // Transferring focus back to the panel keeps it zoomed
5333        workspace.update(cx, |workspace, cx| {
5334            workspace.toggle_panel_focus::<TestPanel>(cx);
5335        });
5336
5337        workspace.update(cx, |workspace, cx| {
5338            assert!(workspace.right_dock().read(cx).is_open());
5339            assert!(panel.is_zoomed(cx));
5340            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5341        });
5342
5343        // Close the dock while it is zoomed
5344        workspace.update(cx, |workspace, cx| {
5345            workspace.toggle_dock(DockPosition::Right, cx)
5346        });
5347
5348        workspace.update(cx, |workspace, cx| {
5349            assert!(!workspace.right_dock().read(cx).is_open());
5350            assert!(panel.is_zoomed(cx));
5351            assert!(workspace.zoomed.is_none());
5352            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5353        });
5354
5355        // Opening the dock, when it's zoomed, retains focus
5356        workspace.update(cx, |workspace, cx| {
5357            workspace.toggle_dock(DockPosition::Right, cx)
5358        });
5359
5360        workspace.update(cx, |workspace, cx| {
5361            assert!(workspace.right_dock().read(cx).is_open());
5362            assert!(panel.is_zoomed(cx));
5363            assert!(workspace.zoomed.is_some());
5364            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5365        });
5366
5367        // Unzoom and close the panel, zoom the active pane.
5368        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5369        workspace.update(cx, |workspace, cx| {
5370            workspace.toggle_dock(DockPosition::Right, cx)
5371        });
5372        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5373
5374        // Opening a dock unzooms the pane.
5375        workspace.update(cx, |workspace, cx| {
5376            workspace.toggle_dock(DockPosition::Right, cx)
5377        });
5378        workspace.update(cx, |workspace, cx| {
5379            let pane = pane.read(cx);
5380            assert!(!pane.is_zoomed());
5381            assert!(!pane.focus_handle(cx).is_focused(cx));
5382            assert!(workspace.right_dock().read(cx).is_open());
5383            assert!(workspace.zoomed.is_none());
5384        });
5385    }
5386
5387    struct TestModal(FocusHandle);
5388
5389    impl TestModal {
5390        fn new(cx: &mut ViewContext<Self>) -> Self {
5391            Self(cx.focus_handle())
5392        }
5393    }
5394
5395    impl EventEmitter<DismissEvent> for TestModal {}
5396
5397    impl FocusableView for TestModal {
5398        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5399            self.0.clone()
5400        }
5401    }
5402
5403    impl ModalView for TestModal {}
5404
5405    impl Render for TestModal {
5406        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5407            div().track_focus(&self.0)
5408        }
5409    }
5410
5411    #[gpui::test]
5412    async fn test_panels(cx: &mut gpui::TestAppContext) {
5413        init_test(cx);
5414        let fs = FakeFs::new(cx.executor());
5415
5416        let project = Project::test(fs, [], cx).await;
5417        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5418
5419        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5420            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5421            workspace.add_panel(panel_1.clone(), cx);
5422            workspace
5423                .left_dock()
5424                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5425            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5426            workspace.add_panel(panel_2.clone(), cx);
5427            workspace
5428                .right_dock()
5429                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5430
5431            let left_dock = workspace.left_dock();
5432            assert_eq!(
5433                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5434                panel_1.panel_id()
5435            );
5436            assert_eq!(
5437                left_dock.read(cx).active_panel_size(cx).unwrap(),
5438                panel_1.size(cx)
5439            );
5440
5441            left_dock.update(cx, |left_dock, cx| {
5442                left_dock.resize_active_panel(Some(px(1337.)), cx)
5443            });
5444            assert_eq!(
5445                workspace
5446                    .right_dock()
5447                    .read(cx)
5448                    .visible_panel()
5449                    .unwrap()
5450                    .panel_id(),
5451                panel_2.panel_id(),
5452            );
5453
5454            (panel_1, panel_2)
5455        });
5456
5457        // Move panel_1 to the right
5458        panel_1.update(cx, |panel_1, cx| {
5459            panel_1.set_position(DockPosition::Right, cx)
5460        });
5461
5462        workspace.update(cx, |workspace, cx| {
5463            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5464            // Since it was the only panel on the left, the left dock should now be closed.
5465            assert!(!workspace.left_dock().read(cx).is_open());
5466            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5467            let right_dock = workspace.right_dock();
5468            assert_eq!(
5469                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5470                panel_1.panel_id()
5471            );
5472            assert_eq!(
5473                right_dock.read(cx).active_panel_size(cx).unwrap(),
5474                px(1337.)
5475            );
5476
5477            // Now we move panel_2 to the left
5478            panel_2.set_position(DockPosition::Left, cx);
5479        });
5480
5481        workspace.update(cx, |workspace, cx| {
5482            // Since panel_2 was not visible on the right, we don't open the left dock.
5483            assert!(!workspace.left_dock().read(cx).is_open());
5484            // And the right dock is unaffected in it's displaying of panel_1
5485            assert!(workspace.right_dock().read(cx).is_open());
5486            assert_eq!(
5487                workspace
5488                    .right_dock()
5489                    .read(cx)
5490                    .visible_panel()
5491                    .unwrap()
5492                    .panel_id(),
5493                panel_1.panel_id(),
5494            );
5495        });
5496
5497        // Move panel_1 back to the left
5498        panel_1.update(cx, |panel_1, cx| {
5499            panel_1.set_position(DockPosition::Left, cx)
5500        });
5501
5502        workspace.update(cx, |workspace, cx| {
5503            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5504            let left_dock = workspace.left_dock();
5505            assert!(left_dock.read(cx).is_open());
5506            assert_eq!(
5507                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5508                panel_1.panel_id()
5509            );
5510            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5511            // And the right dock should be closed as it no longer has any panels.
5512            assert!(!workspace.right_dock().read(cx).is_open());
5513
5514            // Now we move panel_1 to the bottom
5515            panel_1.set_position(DockPosition::Bottom, cx);
5516        });
5517
5518        workspace.update(cx, |workspace, cx| {
5519            // Since panel_1 was visible on the left, we close the left dock.
5520            assert!(!workspace.left_dock().read(cx).is_open());
5521            // The bottom dock is sized based on the panel's default size,
5522            // since the panel orientation changed from vertical to horizontal.
5523            let bottom_dock = workspace.bottom_dock();
5524            assert_eq!(
5525                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5526                panel_1.size(cx),
5527            );
5528            // Close bottom dock and move panel_1 back to the left.
5529            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5530            panel_1.set_position(DockPosition::Left, cx);
5531        });
5532
5533        // Emit activated event on panel 1
5534        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5535
5536        // Now the left dock is open and panel_1 is active and focused.
5537        workspace.update(cx, |workspace, cx| {
5538            let left_dock = workspace.left_dock();
5539            assert!(left_dock.read(cx).is_open());
5540            assert_eq!(
5541                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5542                panel_1.panel_id(),
5543            );
5544            assert!(panel_1.focus_handle(cx).is_focused(cx));
5545        });
5546
5547        // Emit closed event on panel 2, which is not active
5548        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5549
5550        // Wo don't close the left dock, because panel_2 wasn't the active panel
5551        workspace.update(cx, |workspace, cx| {
5552            let left_dock = workspace.left_dock();
5553            assert!(left_dock.read(cx).is_open());
5554            assert_eq!(
5555                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5556                panel_1.panel_id(),
5557            );
5558        });
5559
5560        // Emitting a ZoomIn event shows the panel as zoomed.
5561        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5562        workspace.update(cx, |workspace, _| {
5563            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5564            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5565        });
5566
5567        // Move panel to another dock while it is zoomed
5568        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5569        workspace.update(cx, |workspace, _| {
5570            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5571
5572            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5573        });
5574
5575        // This is a helper for getting a:
5576        // - valid focus on an element,
5577        // - that isn't a part of the panes and panels system of the Workspace,
5578        // - and doesn't trigger the 'on_focus_lost' API.
5579        let focus_other_view = {
5580            let workspace = workspace.clone();
5581            move |cx: &mut VisualTestContext| {
5582                workspace.update(cx, |workspace, cx| {
5583                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5584                        workspace.toggle_modal(cx, TestModal::new);
5585                        workspace.toggle_modal(cx, TestModal::new);
5586                    } else {
5587                        workspace.toggle_modal(cx, TestModal::new);
5588                    }
5589                })
5590            }
5591        };
5592
5593        // If focus is transferred to another view that's not a panel or another pane, we still show
5594        // the panel as zoomed.
5595        focus_other_view(cx);
5596        workspace.update(cx, |workspace, _| {
5597            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5598            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5599        });
5600
5601        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5602        workspace.update(cx, |_, cx| cx.focus_self());
5603        workspace.update(cx, |workspace, _| {
5604            assert_eq!(workspace.zoomed, None);
5605            assert_eq!(workspace.zoomed_position, None);
5606        });
5607
5608        // If focus is transferred again to another view that's not a panel or a pane, we won't
5609        // show the panel as zoomed because it wasn't zoomed before.
5610        focus_other_view(cx);
5611        workspace.update(cx, |workspace, _| {
5612            assert_eq!(workspace.zoomed, None);
5613            assert_eq!(workspace.zoomed_position, None);
5614        });
5615
5616        // When the panel is activated, it is zoomed again.
5617        cx.dispatch_action(ToggleRightDock);
5618        workspace.update(cx, |workspace, _| {
5619            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5620            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5621        });
5622
5623        // Emitting a ZoomOut event unzooms the panel.
5624        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5625        workspace.update(cx, |workspace, _| {
5626            assert_eq!(workspace.zoomed, None);
5627            assert_eq!(workspace.zoomed_position, None);
5628        });
5629
5630        // Emit closed event on panel 1, which is active
5631        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5632
5633        // Now the left dock is closed, because panel_1 was the active panel
5634        workspace.update(cx, |workspace, cx| {
5635            let right_dock = workspace.right_dock();
5636            assert!(!right_dock.read(cx).is_open());
5637        });
5638    }
5639
5640    pub fn init_test(cx: &mut TestAppContext) {
5641        cx.update(|cx| {
5642            let settings_store = SettingsStore::test(cx);
5643            cx.set_global(settings_store);
5644            theme::init(theme::LoadThemes::JustBase, cx);
5645            language::init(cx);
5646            crate::init_settings(cx);
5647            Project::init_settings(cx);
5648        });
5649    }
5650}