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