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