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