workspace.rs

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