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    pub 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        self.start_following(leader_id, cx)
2385            .map(|task| task.detach_and_log_err(cx));
2386    }
2387
2388    pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) {
2389        let Some(room) = ActiveCall::global(cx).read(cx).room() else {
2390            return;
2391        };
2392        let room = room.read(cx);
2393        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2394            return;
2395        };
2396
2397        let project = self.project.read(cx);
2398
2399        let other_project_id = match remote_participant.location {
2400            call::ParticipantLocation::External => None,
2401            call::ParticipantLocation::UnsharedProject => None,
2402            call::ParticipantLocation::SharedProject { project_id } => {
2403                if Some(project_id) == project.remote_id() {
2404                    None
2405                } else {
2406                    Some(project_id)
2407                }
2408            }
2409        };
2410
2411        // if they are active in another project, follow there.
2412        if let Some(project_id) = other_project_id {
2413            let app_state = self.app_state.clone();
2414            crate::join_remote_project(project_id, remote_participant.user.id, app_state, cx)
2415                .detach_and_log_err(cx);
2416        }
2417
2418        // if you're already following, find the right pane and focus it.
2419        for (pane, state) in &self.follower_states {
2420            if leader_id == state.leader_id {
2421                cx.focus_view(pane);
2422                return;
2423            }
2424        }
2425
2426        // Otherwise, follow.
2427        self.start_following(leader_id, cx)
2428            .map(|task| task.detach_and_log_err(cx));
2429    }
2430
2431    pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
2432        let state = self.follower_states.remove(pane)?;
2433        let leader_id = state.leader_id;
2434        for (_, item) in state.items_by_leader_view_id {
2435            item.set_leader_peer_id(None, cx);
2436        }
2437
2438        if self
2439            .follower_states
2440            .values()
2441            .all(|state| state.leader_id != state.leader_id)
2442        {
2443            let project_id = self.project.read(cx).remote_id();
2444            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2445            self.app_state
2446                .client
2447                .send(proto::Unfollow {
2448                    room_id,
2449                    project_id,
2450                    leader_id: Some(leader_id),
2451                })
2452                .log_err();
2453        }
2454
2455        cx.notify();
2456        Some(leader_id)
2457    }
2458
2459    //     pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2460    //         self.follower_states
2461    //             .values()
2462    //             .any(|state| state.leader_id == peer_id)
2463    //     }
2464
2465    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2466        let active_entry = self.active_project_path(cx);
2467        self.project
2468            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2469        self.update_window_title(cx);
2470    }
2471
2472    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2473        let project = self.project().read(cx);
2474        let mut title = String::new();
2475
2476        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2477            let filename = path
2478                .path
2479                .file_name()
2480                .map(|s| s.to_string_lossy())
2481                .or_else(|| {
2482                    Some(Cow::Borrowed(
2483                        project
2484                            .worktree_for_id(path.worktree_id, cx)?
2485                            .read(cx)
2486                            .root_name(),
2487                    ))
2488                });
2489
2490            if let Some(filename) = filename {
2491                title.push_str(filename.as_ref());
2492                title.push_str(" β€” ");
2493            }
2494        }
2495
2496        for (i, name) in project.worktree_root_names(cx).enumerate() {
2497            if i > 0 {
2498                title.push_str(", ");
2499            }
2500            title.push_str(name);
2501        }
2502
2503        if title.is_empty() {
2504            title = "empty project".to_string();
2505        }
2506
2507        if project.is_remote() {
2508            title.push_str(" ↙");
2509        } else if project.is_shared() {
2510            title.push_str(" β†—");
2511        }
2512
2513        cx.set_window_title(&title);
2514    }
2515
2516    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2517        let is_edited = !self.project.read(cx).is_read_only()
2518            && self
2519                .items(cx)
2520                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2521        if is_edited != self.window_edited {
2522            self.window_edited = is_edited;
2523            cx.set_window_edited(self.window_edited)
2524        }
2525    }
2526
2527    //     fn render_disconnected_overlay(
2528    //         &self,
2529    //         cx: &mut ViewContext<Workspace>,
2530    //     ) -> Option<AnyElement<Workspace>> {
2531    //         if self.project.read(cx).is_read_only() {
2532    //             enum DisconnectedOverlay {}
2533    //             Some(
2534    //                 MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
2535    //                     let theme = &theme::current(cx);
2536    //                     Label::new(
2537    //                         "Your connection to the remote project has been lost.",
2538    //                         theme.workspace.disconnected_overlay.text.clone(),
2539    //                     )
2540    //                     .aligned()
2541    //                     .contained()
2542    //                     .with_style(theme.workspace.disconnected_overlay.container)
2543    //                 })
2544    //                 .with_cursor_style(CursorStyle::Arrow)
2545    //                 .capture_all()
2546    //                 .into_any_named("disconnected overlay"),
2547    //             )
2548    //         } else {
2549    //             None
2550    //         }
2551    //     }
2552
2553    fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
2554        if self.notifications.is_empty() {
2555            None
2556        } else {
2557            Some(
2558                div()
2559                    .absolute()
2560                    .z_index(100)
2561                    .right_3()
2562                    .bottom_3()
2563                    .w_96()
2564                    .h_full()
2565                    .flex()
2566                    .flex_col()
2567                    .justify_end()
2568                    .gap_2()
2569                    .children(
2570                        self.notifications
2571                            .iter()
2572                            .map(|(_, _, notification)| notification.to_any()),
2573                    ),
2574            )
2575        }
2576    }
2577
2578    // RPC handlers
2579
2580    fn handle_follow(
2581        &mut self,
2582        follower_project_id: Option<u64>,
2583        cx: &mut ViewContext<Self>,
2584    ) -> proto::FollowResponse {
2585        let client = &self.app_state.client;
2586        let project_id = self.project.read(cx).remote_id();
2587
2588        let active_view_id = self.active_item(cx).and_then(|i| {
2589            Some(
2590                i.to_followable_item_handle(cx)?
2591                    .remote_id(client, cx)?
2592                    .to_proto(),
2593            )
2594        });
2595
2596        cx.notify();
2597
2598        self.last_active_view_id = active_view_id.clone();
2599        proto::FollowResponse {
2600            active_view_id,
2601            views: self
2602                .panes()
2603                .iter()
2604                .flat_map(|pane| {
2605                    let leader_id = self.leader_for_pane(pane);
2606                    pane.read(cx).items().filter_map({
2607                        let cx = &cx;
2608                        move |item| {
2609                            let item = item.to_followable_item_handle(cx)?;
2610                            if (project_id.is_none() || project_id != follower_project_id)
2611                                && item.is_project_item(cx)
2612                            {
2613                                return None;
2614                            }
2615                            let id = item.remote_id(client, cx)?.to_proto();
2616                            let variant = item.to_state_proto(cx)?;
2617                            Some(proto::View {
2618                                id: Some(id),
2619                                leader_id,
2620                                variant: Some(variant),
2621                            })
2622                        }
2623                    })
2624                })
2625                .collect(),
2626        }
2627    }
2628
2629    fn handle_update_followers(
2630        &mut self,
2631        leader_id: PeerId,
2632        message: proto::UpdateFollowers,
2633        _cx: &mut ViewContext<Self>,
2634    ) {
2635        self.leader_updates_tx
2636            .unbounded_send((leader_id, message))
2637            .ok();
2638    }
2639
2640    async fn process_leader_update(
2641        this: &WeakView<Self>,
2642        leader_id: PeerId,
2643        update: proto::UpdateFollowers,
2644        cx: &mut AsyncWindowContext,
2645    ) -> Result<()> {
2646        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2647            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2648                this.update(cx, |this, _| {
2649                    for (_, state) in &mut this.follower_states {
2650                        if state.leader_id == leader_id {
2651                            state.active_view_id =
2652                                if let Some(active_view_id) = update_active_view.id.clone() {
2653                                    Some(ViewId::from_proto(active_view_id)?)
2654                                } else {
2655                                    None
2656                                };
2657                        }
2658                    }
2659                    anyhow::Ok(())
2660                })??;
2661            }
2662            proto::update_followers::Variant::UpdateView(update_view) => {
2663                let variant = update_view
2664                    .variant
2665                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2666                let id = update_view
2667                    .id
2668                    .ok_or_else(|| anyhow!("missing update view id"))?;
2669                let mut tasks = Vec::new();
2670                this.update(cx, |this, cx| {
2671                    let project = this.project.clone();
2672                    for (_, state) in &mut this.follower_states {
2673                        if state.leader_id == leader_id {
2674                            let view_id = ViewId::from_proto(id.clone())?;
2675                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2676                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2677                            }
2678                        }
2679                    }
2680                    anyhow::Ok(())
2681                })??;
2682                try_join_all(tasks).await.log_err();
2683            }
2684            proto::update_followers::Variant::CreateView(view) => {
2685                let panes = this.update(cx, |this, _| {
2686                    this.follower_states
2687                        .iter()
2688                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2689                        .cloned()
2690                        .collect()
2691                })?;
2692                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2693            }
2694        }
2695        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2696        Ok(())
2697    }
2698
2699    async fn add_views_from_leader(
2700        this: WeakView<Self>,
2701        leader_id: PeerId,
2702        panes: Vec<View<Pane>>,
2703        views: Vec<proto::View>,
2704        cx: &mut AsyncWindowContext,
2705    ) -> Result<()> {
2706        let this = this.upgrade().context("workspace dropped")?;
2707
2708        let item_builders = cx.update(|_, cx| {
2709            cx.default_global::<FollowableItemBuilders>()
2710                .values()
2711                .map(|b| b.0)
2712                .collect::<Vec<_>>()
2713        })?;
2714
2715        let mut item_tasks_by_pane = HashMap::default();
2716        for pane in panes {
2717            let mut item_tasks = Vec::new();
2718            let mut leader_view_ids = Vec::new();
2719            for view in &views {
2720                let Some(id) = &view.id else { continue };
2721                let id = ViewId::from_proto(id.clone())?;
2722                let mut variant = view.variant.clone();
2723                if variant.is_none() {
2724                    Err(anyhow!("missing view variant"))?;
2725                }
2726                for build_item in &item_builders {
2727                    let task = cx.update(|_, cx| {
2728                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
2729                    })?;
2730                    if let Some(task) = task {
2731                        item_tasks.push(task);
2732                        leader_view_ids.push(id);
2733                        break;
2734                    } else {
2735                        assert!(variant.is_some());
2736                    }
2737                }
2738            }
2739
2740            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2741        }
2742
2743        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2744            let items = futures::future::try_join_all(item_tasks).await?;
2745            this.update(cx, |this, cx| {
2746                let state = this.follower_states.get_mut(&pane)?;
2747                for (id, item) in leader_view_ids.into_iter().zip(items) {
2748                    item.set_leader_peer_id(Some(leader_id), cx);
2749                    state.items_by_leader_view_id.insert(id, item);
2750                }
2751
2752                Some(())
2753            })?;
2754        }
2755        Ok(())
2756    }
2757
2758    fn update_active_view_for_followers(&mut self, cx: &mut ViewContext<Self>) {
2759        let mut is_project_item = true;
2760        let mut update = proto::UpdateActiveView::default();
2761
2762        if let Some(item) = self.active_item(cx) {
2763            if item.focus_handle(cx).contains_focused(cx) {
2764                if let Some(item) = item.to_followable_item_handle(cx) {
2765                    is_project_item = item.is_project_item(cx);
2766                    update = proto::UpdateActiveView {
2767                        id: item
2768                            .remote_id(&self.app_state.client, cx)
2769                            .map(|id| id.to_proto()),
2770                        leader_id: self.leader_for_pane(&self.active_pane),
2771                    };
2772                }
2773            }
2774        }
2775
2776        if update.id != self.last_active_view_id {
2777            self.last_active_view_id = update.id.clone();
2778            self.update_followers(
2779                is_project_item,
2780                proto::update_followers::Variant::UpdateActiveView(update),
2781                cx,
2782            );
2783        }
2784    }
2785
2786    fn update_followers(
2787        &self,
2788        project_only: bool,
2789        update: proto::update_followers::Variant,
2790        cx: &mut WindowContext,
2791    ) -> Option<()> {
2792        let project_id = if project_only {
2793            self.project.read(cx).remote_id()
2794        } else {
2795            None
2796        };
2797        self.app_state().workspace_store.update(cx, |store, cx| {
2798            store.update_followers(project_id, update, cx)
2799        })
2800    }
2801
2802    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
2803        self.follower_states.get(pane).map(|state| state.leader_id)
2804    }
2805
2806    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2807        cx.notify();
2808
2809        let call = self.active_call()?;
2810        let room = call.read(cx).room()?.read(cx);
2811        let participant = room.remote_participant_for_peer_id(leader_id)?;
2812        let mut items_to_activate = Vec::new();
2813
2814        let leader_in_this_app;
2815        let leader_in_this_project;
2816        match participant.location {
2817            call::ParticipantLocation::SharedProject { project_id } => {
2818                leader_in_this_app = true;
2819                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
2820            }
2821            call::ParticipantLocation::UnsharedProject => {
2822                leader_in_this_app = true;
2823                leader_in_this_project = false;
2824            }
2825            call::ParticipantLocation::External => {
2826                leader_in_this_app = false;
2827                leader_in_this_project = false;
2828            }
2829        };
2830
2831        for (pane, state) in &self.follower_states {
2832            if state.leader_id != leader_id {
2833                continue;
2834            }
2835            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
2836                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
2837                    if leader_in_this_project || !item.is_project_item(cx) {
2838                        items_to_activate.push((pane.clone(), item.boxed_clone()));
2839                    }
2840                }
2841                continue;
2842            }
2843
2844            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2845                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2846            }
2847        }
2848
2849        for (pane, item) in items_to_activate {
2850            let pane_was_focused = pane.read(cx).has_focus(cx);
2851            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2852                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2853            } else {
2854                pane.update(cx, |pane, cx| {
2855                    pane.add_item(item.boxed_clone(), false, false, None, cx)
2856                });
2857            }
2858
2859            if pane_was_focused {
2860                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2861            }
2862        }
2863
2864        None
2865    }
2866
2867    fn shared_screen_for_peer(
2868        &self,
2869        peer_id: PeerId,
2870        pane: &View<Pane>,
2871        cx: &mut ViewContext<Self>,
2872    ) -> Option<View<SharedScreen>> {
2873        let call = self.active_call()?;
2874        let room = call.read(cx).room()?.read(cx);
2875        let participant = room.remote_participant_for_peer_id(peer_id)?;
2876        let track = participant.video_tracks.values().next()?.clone();
2877        let user = participant.user.clone();
2878
2879        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2880            if item.read(cx).peer_id == peer_id {
2881                return Some(item);
2882            }
2883        }
2884
2885        Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2886    }
2887
2888    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
2889        if cx.is_window_active() {
2890            self.update_active_view_for_followers(cx);
2891            cx.background_executor()
2892                .spawn(persistence::DB.update_timestamp(self.database_id()))
2893                .detach();
2894        } else {
2895            for pane in &self.panes {
2896                pane.update(cx, |pane, cx| {
2897                    if let Some(item) = pane.active_item() {
2898                        item.workspace_deactivated(cx);
2899                    }
2900                    if matches!(
2901                        WorkspaceSettings::get_global(cx).autosave,
2902                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2903                    ) {
2904                        for item in pane.items() {
2905                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2906                                .detach_and_log_err(cx);
2907                        }
2908                    }
2909                });
2910            }
2911        }
2912    }
2913
2914    fn active_call(&self) -> Option<&Model<ActiveCall>> {
2915        self.active_call.as_ref().map(|(call, _)| call)
2916    }
2917
2918    fn on_active_call_event(
2919        &mut self,
2920        _: Model<ActiveCall>,
2921        event: &call::room::Event,
2922        cx: &mut ViewContext<Self>,
2923    ) {
2924        match event {
2925            call::room::Event::ParticipantLocationChanged { participant_id }
2926            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2927                self.leader_updated(*participant_id, cx);
2928            }
2929            _ => {}
2930        }
2931    }
2932
2933    pub fn database_id(&self) -> WorkspaceId {
2934        self.database_id
2935    }
2936
2937    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2938        let project = self.project().read(cx);
2939
2940        if project.is_local() {
2941            Some(
2942                project
2943                    .visible_worktrees(cx)
2944                    .map(|worktree| worktree.read(cx).abs_path())
2945                    .collect::<Vec<_>>()
2946                    .into(),
2947            )
2948        } else {
2949            None
2950        }
2951    }
2952
2953    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2954        match member {
2955            Member::Axis(PaneAxis { members, .. }) => {
2956                for child in members.iter() {
2957                    self.remove_panes(child.clone(), cx)
2958                }
2959            }
2960            Member::Pane(pane) => {
2961                self.force_remove_pane(&pane, cx);
2962            }
2963        }
2964    }
2965
2966    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
2967        self.panes.retain(|p| p != pane);
2968        self.panes
2969            .last()
2970            .unwrap()
2971            .update(cx, |pane, cx| pane.focus(cx));
2972        if self.last_active_center_pane == Some(pane.downgrade()) {
2973            self.last_active_center_pane = None;
2974        }
2975        cx.notify();
2976    }
2977
2978    #[allow(unused)]
2979    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
2980        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
2981            cx.background_executor()
2982                .timer(Duration::from_millis(100))
2983                .await;
2984            this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
2985                .log_err();
2986        }));
2987    }
2988
2989    fn serialize_workspace(&self, cx: &mut ViewContext<Self>) {
2990        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
2991            let (items, active) = {
2992                let pane = pane_handle.read(cx);
2993                let active_item_id = pane.active_item().map(|item| item.item_id());
2994                (
2995                    pane.items()
2996                        .filter_map(|item_handle| {
2997                            Some(SerializedItem {
2998                                kind: Arc::from(item_handle.serialized_item_kind()?),
2999                                item_id: item_handle.item_id().as_u64(),
3000                                active: Some(item_handle.item_id()) == active_item_id,
3001                            })
3002                        })
3003                        .collect::<Vec<_>>(),
3004                    pane.has_focus(cx),
3005                )
3006            };
3007
3008            SerializedPane::new(items, active)
3009        }
3010
3011        fn build_serialized_pane_group(
3012            pane_group: &Member,
3013            cx: &WindowContext,
3014        ) -> SerializedPaneGroup {
3015            match pane_group {
3016                Member::Axis(PaneAxis {
3017                    axis,
3018                    members,
3019                    flexes,
3020                    bounding_boxes: _,
3021                }) => SerializedPaneGroup::Group {
3022                    axis: *axis,
3023                    children: members
3024                        .iter()
3025                        .map(|member| build_serialized_pane_group(member, cx))
3026                        .collect::<Vec<_>>(),
3027                    flexes: Some(flexes.lock().clone()),
3028                },
3029                Member::Pane(pane_handle) => {
3030                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3031                }
3032            }
3033        }
3034
3035        fn build_serialized_docks(
3036            this: &Workspace,
3037            cx: &mut ViewContext<Workspace>,
3038        ) -> DockStructure {
3039            let left_dock = this.left_dock.read(cx);
3040            let left_visible = left_dock.is_open();
3041            let left_active_panel = left_dock
3042                .visible_panel()
3043                .and_then(|panel| Some(panel.persistent_name().to_string()));
3044            let left_dock_zoom = left_dock
3045                .visible_panel()
3046                .map(|panel| panel.is_zoomed(cx))
3047                .unwrap_or(false);
3048
3049            let right_dock = this.right_dock.read(cx);
3050            let right_visible = right_dock.is_open();
3051            let right_active_panel = right_dock
3052                .visible_panel()
3053                .and_then(|panel| Some(panel.persistent_name().to_string()));
3054            let right_dock_zoom = right_dock
3055                .visible_panel()
3056                .map(|panel| panel.is_zoomed(cx))
3057                .unwrap_or(false);
3058
3059            let bottom_dock = this.bottom_dock.read(cx);
3060            let bottom_visible = bottom_dock.is_open();
3061            let bottom_active_panel = bottom_dock
3062                .visible_panel()
3063                .and_then(|panel| Some(panel.persistent_name().to_string()));
3064            let bottom_dock_zoom = bottom_dock
3065                .visible_panel()
3066                .map(|panel| panel.is_zoomed(cx))
3067                .unwrap_or(false);
3068
3069            DockStructure {
3070                left: DockData {
3071                    visible: left_visible,
3072                    active_panel: left_active_panel,
3073                    zoom: left_dock_zoom,
3074                },
3075                right: DockData {
3076                    visible: right_visible,
3077                    active_panel: right_active_panel,
3078                    zoom: right_dock_zoom,
3079                },
3080                bottom: DockData {
3081                    visible: bottom_visible,
3082                    active_panel: bottom_active_panel,
3083                    zoom: bottom_dock_zoom,
3084                },
3085            }
3086        }
3087
3088        if let Some(location) = self.location(cx) {
3089            // Load bearing special case:
3090            //  - with_local_workspace() relies on this to not have other stuff open
3091            //    when you open your log
3092            if !location.paths().is_empty() {
3093                let center_group = build_serialized_pane_group(&self.center.root, cx);
3094                let docks = build_serialized_docks(self, cx);
3095
3096                let serialized_workspace = SerializedWorkspace {
3097                    id: self.database_id,
3098                    location,
3099                    center_group,
3100                    bounds: Default::default(),
3101                    display: Default::default(),
3102                    docks,
3103                };
3104
3105                cx.spawn(|_, _| persistence::DB.save_workspace(serialized_workspace))
3106                    .detach();
3107            }
3108        }
3109    }
3110
3111    pub(crate) fn load_workspace(
3112        serialized_workspace: SerializedWorkspace,
3113        paths_to_open: Vec<Option<ProjectPath>>,
3114        cx: &mut ViewContext<Workspace>,
3115    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3116        cx.spawn(|workspace, mut cx| async move {
3117            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3118
3119            let mut center_group = None;
3120            let mut center_items = None;
3121
3122            // Traverse the splits tree and add to things
3123            if let Some((group, active_pane, items)) = serialized_workspace
3124                .center_group
3125                .deserialize(
3126                    &project,
3127                    serialized_workspace.id,
3128                    workspace.clone(),
3129                    &mut cx,
3130                )
3131                .await
3132            {
3133                center_items = Some(items);
3134                center_group = Some((group, active_pane))
3135            }
3136
3137            let mut items_by_project_path = cx.update(|_, cx| {
3138                center_items
3139                    .unwrap_or_default()
3140                    .into_iter()
3141                    .filter_map(|item| {
3142                        let item = item?;
3143                        let project_path = item.project_path(cx)?;
3144                        Some((project_path, item))
3145                    })
3146                    .collect::<HashMap<_, _>>()
3147            })?;
3148
3149            let opened_items = paths_to_open
3150                .into_iter()
3151                .map(|path_to_open| {
3152                    path_to_open
3153                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3154                })
3155                .collect::<Vec<_>>();
3156
3157            // Remove old panes from workspace panes list
3158            workspace.update(&mut cx, |workspace, cx| {
3159                if let Some((center_group, active_pane)) = center_group {
3160                    workspace.remove_panes(workspace.center.root.clone(), cx);
3161
3162                    // Swap workspace center group
3163                    workspace.center = PaneGroup::with_root(center_group);
3164                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3165                    if let Some(active_pane) = active_pane {
3166                        workspace.active_pane = active_pane;
3167                        cx.focus_self();
3168                    } else {
3169                        workspace.active_pane = workspace.center.first_pane().clone();
3170                    }
3171                }
3172
3173                let docks = serialized_workspace.docks;
3174                workspace.left_dock.update(cx, |dock, cx| {
3175                    dock.set_open(docks.left.visible, cx);
3176                    if let Some(active_panel) = docks.left.active_panel {
3177                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3178                            dock.activate_panel(ix, cx);
3179                        }
3180                    }
3181                    dock.active_panel()
3182                        .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
3183                    if docks.left.visible && docks.left.zoom {
3184                        cx.focus_self()
3185                    }
3186                });
3187                // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3188                workspace.right_dock.update(cx, |dock, cx| {
3189                    dock.set_open(docks.right.visible, cx);
3190                    if let Some(active_panel) = docks.right.active_panel {
3191                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3192                            dock.activate_panel(ix, cx);
3193                        }
3194                    }
3195                    dock.active_panel()
3196                        .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
3197
3198                    if docks.right.visible && docks.right.zoom {
3199                        cx.focus_self()
3200                    }
3201                });
3202                workspace.bottom_dock.update(cx, |dock, cx| {
3203                    dock.set_open(docks.bottom.visible, cx);
3204                    if let Some(active_panel) = docks.bottom.active_panel {
3205                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3206                            dock.activate_panel(ix, cx);
3207                        }
3208                    }
3209
3210                    dock.active_panel()
3211                        .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
3212
3213                    if docks.bottom.visible && docks.bottom.zoom {
3214                        cx.focus_self()
3215                    }
3216                });
3217
3218                cx.notify();
3219            })?;
3220
3221            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3222            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3223
3224            Ok(opened_items)
3225        })
3226    }
3227
3228    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3229        self.add_workspace_actions_listeners(div, cx)
3230            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3231            .on_action(cx.listener(Self::close_all_items_and_panes))
3232            .on_action(cx.listener(Self::save_all))
3233            .on_action(cx.listener(Self::add_folder_to_project))
3234            .on_action(cx.listener(Self::follow_next_collaborator))
3235            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3236                let pane = workspace.active_pane().clone();
3237                workspace.unfollow(&pane, cx);
3238            }))
3239            .on_action(cx.listener(|workspace, action: &Save, cx| {
3240                workspace
3241                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3242                    .detach_and_log_err(cx);
3243            }))
3244            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3245                workspace
3246                    .save_active_item(SaveIntent::SaveAs, cx)
3247                    .detach_and_log_err(cx);
3248            }))
3249            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3250                workspace.activate_previous_pane(cx)
3251            }))
3252            .on_action(
3253                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3254            )
3255            .on_action(
3256                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3257                    workspace.activate_pane_in_direction(action.0, cx)
3258                }),
3259            )
3260            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3261                workspace.swap_pane_in_direction(action.0, cx)
3262            }))
3263            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3264                this.toggle_dock(DockPosition::Left, cx);
3265            }))
3266            .on_action(
3267                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3268                    workspace.toggle_dock(DockPosition::Right, cx);
3269                }),
3270            )
3271            .on_action(
3272                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3273                    workspace.toggle_dock(DockPosition::Bottom, cx);
3274                }),
3275            )
3276            .on_action(
3277                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3278                    workspace.close_all_docks(cx);
3279                }),
3280            )
3281            .on_action(cx.listener(Workspace::open))
3282            .on_action(cx.listener(Workspace::close_window))
3283            .on_action(cx.listener(Workspace::activate_pane_at_index))
3284            .on_action(
3285                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3286                    workspace.reopen_closed_item(cx).detach();
3287                }),
3288            )
3289    }
3290
3291    #[cfg(any(test, feature = "test-support"))]
3292    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3293        use node_runtime::FakeNodeRuntime;
3294
3295        let client = project.read(cx).client();
3296        let user_store = project.read(cx).user_store();
3297
3298        let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
3299        let app_state = Arc::new(AppState {
3300            languages: project.read(cx).languages().clone(),
3301            workspace_store,
3302            client,
3303            user_store,
3304            fs: project.read(cx).fs().clone(),
3305            build_window_options: |_, _, _| Default::default(),
3306            node_runtime: FakeNodeRuntime::new(),
3307        });
3308        let workspace = Self::new(0, project, app_state, cx);
3309        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3310        workspace
3311    }
3312
3313    //     fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3314    //         let dock = match position {
3315    //             DockPosition::Left => &self.left_dock,
3316    //             DockPosition::Right => &self.right_dock,
3317    //             DockPosition::Bottom => &self.bottom_dock,
3318    //         };
3319    //         let active_panel = dock.read(cx).visible_panel()?;
3320    //         let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3321    //             dock.read(cx).render_placeholder(cx)
3322    //         } else {
3323    //             ChildView::new(dock, cx).into_any()
3324    //         };
3325
3326    //         Some(
3327    //             element
3328    //                 .constrained()
3329    //                 .dynamically(move |constraint, _, cx| match position {
3330    //                     DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3331    //                         Vector2F::new(20., constraint.min.y()),
3332    //                         Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3333    //                     ),
3334    //                     DockPosition::Bottom => SizeConstraint::new(
3335    //                         Vector2F::new(constraint.min.x(), 20.),
3336    //                         Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3337    //                     ),
3338    //                 })
3339    //                 .into_any(),
3340    //         )
3341    //     }
3342    // }
3343    pub fn register_action<A: Action>(
3344        &mut self,
3345        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3346    ) -> &mut Self {
3347        let callback = Arc::new(callback);
3348
3349        self.workspace_actions.push(Box::new(move |div, cx| {
3350            let callback = callback.clone();
3351            div.on_action(
3352                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3353            )
3354        }));
3355        self
3356    }
3357
3358    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3359        let mut div = div
3360            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3361            .on_action(cx.listener(Self::close_all_items_and_panes))
3362            .on_action(cx.listener(Self::add_folder_to_project))
3363            .on_action(cx.listener(Self::save_all))
3364            .on_action(cx.listener(Self::open));
3365        for action in self.workspace_actions.iter() {
3366            div = (action)(div, cx)
3367        }
3368        div
3369    }
3370
3371    pub fn active_modal<V: ManagedView + 'static>(
3372        &mut self,
3373        cx: &ViewContext<Self>,
3374    ) -> Option<View<V>> {
3375        self.modal_layer.read(cx).active_modal()
3376    }
3377
3378    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3379    where
3380        B: FnOnce(&mut ViewContext<V>) -> V,
3381    {
3382        self.modal_layer
3383            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3384    }
3385}
3386
3387fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3388    let display_origin = cx
3389        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3390        .ok()??;
3391    ZED_WINDOW_POSITION
3392        .zip(*ZED_WINDOW_SIZE)
3393        .map(|(position, size)| {
3394            WindowBounds::Fixed(Bounds {
3395                origin: display_origin + position,
3396                size,
3397            })
3398        })
3399}
3400
3401fn open_items(
3402    serialized_workspace: Option<SerializedWorkspace>,
3403    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3404    app_state: Arc<AppState>,
3405    cx: &mut ViewContext<Workspace>,
3406) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3407    let restored_items = serialized_workspace.map(|serialized_workspace| {
3408        Workspace::load_workspace(
3409            serialized_workspace,
3410            project_paths_to_open
3411                .iter()
3412                .map(|(_, project_path)| project_path)
3413                .cloned()
3414                .collect(),
3415            cx,
3416        )
3417    });
3418
3419    cx.spawn(|workspace, mut cx| async move {
3420        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3421
3422        if let Some(restored_items) = restored_items {
3423            let restored_items = restored_items.await?;
3424
3425            let restored_project_paths = restored_items
3426                .iter()
3427                .filter_map(|item| {
3428                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
3429                        .ok()
3430                        .flatten()
3431                })
3432                .collect::<HashSet<_>>();
3433
3434            for restored_item in restored_items {
3435                opened_items.push(restored_item.map(Ok));
3436            }
3437
3438            project_paths_to_open
3439                .iter_mut()
3440                .for_each(|(_, project_path)| {
3441                    if let Some(project_path_to_open) = project_path {
3442                        if restored_project_paths.contains(project_path_to_open) {
3443                            *project_path = None;
3444                        }
3445                    }
3446                });
3447        } else {
3448            for _ in 0..project_paths_to_open.len() {
3449                opened_items.push(None);
3450            }
3451        }
3452        assert!(opened_items.len() == project_paths_to_open.len());
3453
3454        let tasks =
3455            project_paths_to_open
3456                .into_iter()
3457                .enumerate()
3458                .map(|(i, (abs_path, project_path))| {
3459                    let workspace = workspace.clone();
3460                    cx.spawn(|mut cx| {
3461                        let fs = app_state.fs.clone();
3462                        async move {
3463                            let file_project_path = project_path?;
3464                            if fs.is_file(&abs_path).await {
3465                                Some((
3466                                    i,
3467                                    workspace
3468                                        .update(&mut cx, |workspace, cx| {
3469                                            workspace.open_path(file_project_path, None, true, cx)
3470                                        })
3471                                        .log_err()?
3472                                        .await,
3473                                ))
3474                            } else {
3475                                None
3476                            }
3477                        }
3478                    })
3479                });
3480
3481        let tasks = tasks.collect::<Vec<_>>();
3482
3483        let tasks = futures::future::join_all(tasks.into_iter());
3484        for maybe_opened_path in tasks.await.into_iter() {
3485            if let Some((i, path_open_result)) = maybe_opened_path {
3486                opened_items[i] = Some(path_open_result);
3487            }
3488        }
3489
3490        Ok(opened_items)
3491    })
3492}
3493
3494fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3495    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3496
3497    workspace
3498        .update(cx, |workspace, cx| {
3499            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3500                workspace.show_notification_once(0, cx, |cx| {
3501                    cx.build_view(|_| {
3502                        MessageNotification::new("Failed to load the database file.")
3503                            .with_click_message("Click to let us know about this error")
3504                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3505                    })
3506                });
3507            }
3508        })
3509        .log_err();
3510}
3511
3512impl FocusableView for Workspace {
3513    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3514        self.active_pane.focus_handle(cx)
3515    }
3516}
3517
3518#[derive(Clone, Render)]
3519struct DraggedDock(DockPosition);
3520
3521impl Render for Workspace {
3522    type Element = Div;
3523
3524    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3525        let mut context = KeyContext::default();
3526        context.add("Workspace");
3527
3528        let (ui_font, ui_font_size) = {
3529            let theme_settings = ThemeSettings::get_global(cx);
3530            (
3531                theme_settings.ui_font.family.clone(),
3532                theme_settings.ui_font_size.clone(),
3533            )
3534        };
3535
3536        let theme = cx.theme().clone();
3537        let colors = theme.colors();
3538        cx.set_rem_size(ui_font_size);
3539
3540        self.actions(div(), cx)
3541            .key_context(context)
3542            .relative()
3543            .size_full()
3544            .flex()
3545            .flex_col()
3546            .font(ui_font)
3547            .gap_0()
3548            .justify_start()
3549            .items_start()
3550            .text_color(colors.text)
3551            .bg(colors.background)
3552            .border()
3553            .border_color(colors.border)
3554            .children(self.titlebar_item.clone())
3555            .child(
3556                div()
3557                    .id("workspace")
3558                    .relative()
3559                    .flex_1()
3560                    .w_full()
3561                    .flex()
3562                    .flex_col()
3563                    .overflow_hidden()
3564                    .border_t()
3565                    .border_b()
3566                    .border_color(colors.border)
3567                    .child(
3568                        canvas(cx.listener(|workspace, bounds, _| {
3569                            workspace.bounds = *bounds;
3570                        }))
3571                        .absolute()
3572                        .size_full(),
3573                    )
3574                    .on_drag_move(
3575                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3576                            match e.drag(cx).0 {
3577                                DockPosition::Left => {
3578                                    let size = workspace.bounds.left() + e.event.position.x;
3579                                    workspace.left_dock.update(cx, |left_dock, cx| {
3580                                        left_dock.resize_active_panel(Some(size.0), cx);
3581                                    });
3582                                }
3583                                DockPosition::Right => {
3584                                    let size = workspace.bounds.right() - e.event.position.x;
3585                                    workspace.right_dock.update(cx, |right_dock, cx| {
3586                                        right_dock.resize_active_panel(Some(size.0), cx);
3587                                    });
3588                                }
3589                                DockPosition::Bottom => {
3590                                    let size = workspace.bounds.bottom() - e.event.position.y;
3591                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3592                                        bottom_dock.resize_active_panel(Some(size.0), cx);
3593                                    });
3594                                }
3595                            }
3596                        }),
3597                    )
3598                    .child(self.modal_layer.clone())
3599                    .child(
3600                        div()
3601                            .flex()
3602                            .flex_row()
3603                            .h_full()
3604                            // Left Dock
3605                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3606                                || {
3607                                    div()
3608                                        .flex()
3609                                        .flex_none()
3610                                        .overflow_hidden()
3611                                        .child(self.left_dock.clone())
3612                                },
3613                            ))
3614                            // Panes
3615                            .child(
3616                                div()
3617                                    .flex()
3618                                    .flex_col()
3619                                    .flex_1()
3620                                    .overflow_hidden()
3621                                    .child(self.center.render(
3622                                        &self.project,
3623                                        &self.follower_states,
3624                                        self.active_call(),
3625                                        &self.active_pane,
3626                                        self.zoomed.as_ref(),
3627                                        &self.app_state,
3628                                        cx,
3629                                    ))
3630                                    .children(
3631                                        self.zoomed_position
3632                                            .ne(&Some(DockPosition::Bottom))
3633                                            .then(|| self.bottom_dock.clone()),
3634                                    ),
3635                            )
3636                            // Right Dock
3637                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3638                                || {
3639                                    div()
3640                                        .flex()
3641                                        .flex_none()
3642                                        .overflow_hidden()
3643                                        .child(self.right_dock.clone())
3644                                },
3645                            )),
3646                    )
3647                    .children(self.render_notifications(cx))
3648                    .children(self.zoomed.as_ref().and_then(|view| {
3649                        let zoomed_view = view.upgrade()?;
3650                        let div = div()
3651                            .z_index(1)
3652                            .absolute()
3653                            .overflow_hidden()
3654                            .border_color(colors.border)
3655                            .bg(colors.background)
3656                            .child(zoomed_view)
3657                            .inset_0()
3658                            .shadow_lg();
3659
3660                        Some(match self.zoomed_position {
3661                            Some(DockPosition::Left) => div.right_2().border_r(),
3662                            Some(DockPosition::Right) => div.left_2().border_l(),
3663                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3664                            None => div.top_2().bottom_2().left_2().right_2().border(),
3665                        })
3666                    })),
3667            )
3668            .child(self.status_bar.clone())
3669    }
3670}
3671
3672impl WorkspaceStore {
3673    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3674        Self {
3675            workspaces: Default::default(),
3676            followers: Default::default(),
3677            _subscriptions: vec![
3678                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3679                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3680                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3681            ],
3682            client,
3683        }
3684    }
3685
3686    pub fn update_followers(
3687        &self,
3688        project_id: Option<u64>,
3689        update: proto::update_followers::Variant,
3690        cx: &AppContext,
3691    ) -> Option<()> {
3692        if !cx.has_global::<Model<ActiveCall>>() {
3693            return None;
3694        }
3695
3696        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3697        let follower_ids: Vec<_> = self
3698            .followers
3699            .iter()
3700            .filter_map(|follower| {
3701                if follower.project_id == project_id || project_id.is_none() {
3702                    Some(follower.peer_id.into())
3703                } else {
3704                    None
3705                }
3706            })
3707            .collect();
3708        if follower_ids.is_empty() {
3709            return None;
3710        }
3711        self.client
3712            .send(proto::UpdateFollowers {
3713                room_id,
3714                project_id,
3715                follower_ids,
3716                variant: Some(update),
3717            })
3718            .log_err()
3719    }
3720
3721    pub async fn handle_follow(
3722        this: Model<Self>,
3723        envelope: TypedEnvelope<proto::Follow>,
3724        _: Arc<Client>,
3725        mut cx: AsyncAppContext,
3726    ) -> Result<proto::FollowResponse> {
3727        this.update(&mut cx, |this, cx| {
3728            let follower = Follower {
3729                project_id: envelope.payload.project_id,
3730                peer_id: envelope.original_sender_id()?,
3731            };
3732            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3733
3734            let mut response = proto::FollowResponse::default();
3735            for workspace in &this.workspaces {
3736                workspace
3737                    .update(cx, |workspace, cx| {
3738                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3739                        if response.views.is_empty() {
3740                            response.views = handler_response.views;
3741                        } else {
3742                            response.views.extend_from_slice(&handler_response.views);
3743                        }
3744
3745                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3746                            if response.active_view_id.is_none()
3747                                || Some(workspace.project.downgrade()) == active_project
3748                            {
3749                                response.active_view_id = Some(active_view_id);
3750                            }
3751                        }
3752                    })
3753                    .ok();
3754            }
3755
3756            if let Err(ix) = this.followers.binary_search(&follower) {
3757                this.followers.insert(ix, follower);
3758            }
3759
3760            Ok(response)
3761        })?
3762    }
3763
3764    async fn handle_unfollow(
3765        model: Model<Self>,
3766        envelope: TypedEnvelope<proto::Unfollow>,
3767        _: Arc<Client>,
3768        mut cx: AsyncAppContext,
3769    ) -> Result<()> {
3770        model.update(&mut cx, |this, _| {
3771            let follower = Follower {
3772                project_id: envelope.payload.project_id,
3773                peer_id: envelope.original_sender_id()?,
3774            };
3775            if let Ok(ix) = this.followers.binary_search(&follower) {
3776                this.followers.remove(ix);
3777            }
3778            Ok(())
3779        })?
3780    }
3781
3782    async fn handle_update_followers(
3783        this: Model<Self>,
3784        envelope: TypedEnvelope<proto::UpdateFollowers>,
3785        _: Arc<Client>,
3786        mut cx: AsyncAppContext,
3787    ) -> Result<()> {
3788        let leader_id = envelope.original_sender_id()?;
3789        let update = envelope.payload;
3790
3791        this.update(&mut cx, |this, cx| {
3792            for workspace in &this.workspaces {
3793                workspace.update(cx, |workspace, cx| {
3794                    let project_id = workspace.project.read(cx).remote_id();
3795                    if update.project_id != project_id && update.project_id.is_some() {
3796                        return;
3797                    }
3798                    workspace.handle_update_followers(leader_id, update.clone(), cx);
3799                })?;
3800            }
3801            Ok(())
3802        })?
3803    }
3804}
3805
3806impl ViewId {
3807    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3808        Ok(Self {
3809            creator: message
3810                .creator
3811                .ok_or_else(|| anyhow!("creator is missing"))?,
3812            id: message.id,
3813        })
3814    }
3815
3816    pub(crate) fn to_proto(&self) -> proto::ViewId {
3817        proto::ViewId {
3818            creator: Some(self.creator),
3819            id: self.id,
3820        }
3821    }
3822}
3823
3824pub trait WorkspaceHandle {
3825    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3826}
3827
3828impl WorkspaceHandle for View<Workspace> {
3829    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3830        self.read(cx)
3831            .worktrees(cx)
3832            .flat_map(|worktree| {
3833                let worktree_id = worktree.read(cx).id();
3834                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3835                    worktree_id,
3836                    path: f.path.clone(),
3837                })
3838            })
3839            .collect::<Vec<_>>()
3840    }
3841}
3842
3843impl std::fmt::Debug for OpenPaths {
3844    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3845        f.debug_struct("OpenPaths")
3846            .field("paths", &self.paths)
3847            .finish()
3848    }
3849}
3850
3851pub fn activate_workspace_for_project(
3852    cx: &mut AppContext,
3853    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
3854) -> Option<WindowHandle<Workspace>> {
3855    for window in cx.windows() {
3856        let Some(workspace) = window.downcast::<Workspace>() else {
3857            continue;
3858        };
3859
3860        let predicate = workspace
3861            .update(cx, |workspace, cx| {
3862                let project = workspace.project.read(cx);
3863                if predicate(project, cx) {
3864                    cx.activate_window();
3865                    true
3866                } else {
3867                    false
3868                }
3869            })
3870            .log_err()
3871            .unwrap_or(false);
3872
3873        if predicate {
3874            return Some(workspace);
3875        }
3876    }
3877
3878    None
3879}
3880
3881pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3882    DB.last_workspace().await.log_err().flatten()
3883}
3884
3885async fn join_channel_internal(
3886    channel_id: u64,
3887    app_state: &Arc<AppState>,
3888    requesting_window: Option<WindowHandle<Workspace>>,
3889    active_call: &Model<ActiveCall>,
3890    cx: &mut AsyncAppContext,
3891) -> Result<bool> {
3892    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
3893        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
3894            return (false, None);
3895        };
3896
3897        let already_in_channel = room.channel_id() == Some(channel_id);
3898        let should_prompt = room.is_sharing_project()
3899            && room.remote_participants().len() > 0
3900            && !already_in_channel;
3901        let open_room = if already_in_channel {
3902            active_call.room().cloned()
3903        } else {
3904            None
3905        };
3906        (should_prompt, open_room)
3907    })?;
3908
3909    if let Some(room) = open_room {
3910        let task = room.update(cx, |room, cx| {
3911            if let Some((project, host)) = room.most_active_project(cx) {
3912                return Some(join_remote_project(project, host, app_state.clone(), cx));
3913            }
3914
3915            None
3916        })?;
3917        if let Some(task) = task {
3918            task.await?;
3919        }
3920        return anyhow::Ok(true);
3921    }
3922
3923    if should_prompt {
3924        if let Some(workspace) = requesting_window {
3925            let answer  = workspace.update(cx, |_, cx| {
3926                cx.prompt(
3927                    PromptLevel::Warning,
3928                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
3929                    &["Yes, Join Channel", "Cancel"],
3930                )
3931            })?.await;
3932
3933            if answer == Ok(1) {
3934                return Ok(false);
3935            }
3936        } else {
3937            return Ok(false); // unreachable!() hopefully
3938        }
3939    }
3940
3941    let client = cx.update(|cx| active_call.read(cx).client())?;
3942
3943    let mut client_status = client.status();
3944
3945    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
3946    'outer: loop {
3947        let Some(status) = client_status.recv().await else {
3948            return Err(anyhow!("error connecting"));
3949        };
3950
3951        match status {
3952            Status::Connecting
3953            | Status::Authenticating
3954            | Status::Reconnecting
3955            | Status::Reauthenticating => continue,
3956            Status::Connected { .. } => break 'outer,
3957            Status::SignedOut => return Err(anyhow!("not signed in")),
3958            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
3959            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
3960                return Err(anyhow!("zed is offline"))
3961            }
3962        }
3963    }
3964
3965    let room = active_call
3966        .update(cx, |active_call, cx| {
3967            active_call.join_channel(channel_id, cx)
3968        })?
3969        .await?;
3970
3971    let Some(room) = room else {
3972        return anyhow::Ok(true);
3973    };
3974
3975    room.update(cx, |room, _| room.room_update_completed())?
3976        .await;
3977
3978    let task = room.update(cx, |room, cx| {
3979        if let Some((project, host)) = room.most_active_project(cx) {
3980            return Some(join_remote_project(project, host, app_state.clone(), cx));
3981        }
3982
3983        None
3984    })?;
3985    if let Some(task) = task {
3986        task.await?;
3987        return anyhow::Ok(true);
3988    }
3989    anyhow::Ok(false)
3990}
3991
3992pub fn join_channel(
3993    channel_id: u64,
3994    app_state: Arc<AppState>,
3995    requesting_window: Option<WindowHandle<Workspace>>,
3996    cx: &mut AppContext,
3997) -> Task<Result<()>> {
3998    let active_call = ActiveCall::global(cx);
3999    cx.spawn(|mut cx| async move {
4000        let result = join_channel_internal(
4001            channel_id,
4002            &app_state,
4003            requesting_window,
4004            &active_call,
4005            &mut cx,
4006        )
4007        .await;
4008
4009        // join channel succeeded, and opened a window
4010        if matches!(result, Ok(true)) {
4011            return anyhow::Ok(());
4012        }
4013
4014        if requesting_window.is_some() {
4015            return anyhow::Ok(());
4016        }
4017
4018        // find an existing workspace to focus and show call controls
4019        let mut active_window = activate_any_workspace_window(&mut cx);
4020        if active_window.is_none() {
4021            // no open workspaces, make one to show the error in (blergh)
4022            cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))?
4023                .await?;
4024        }
4025
4026        active_window = activate_any_workspace_window(&mut cx);
4027        let Some(active_window) = active_window else {
4028            return anyhow::Ok(());
4029        };
4030
4031        if let Err(err) = result {
4032            active_window
4033                .update(&mut cx, |_, cx| {
4034                    cx.prompt(
4035                        PromptLevel::Critical,
4036                        &format!("Failed to join channel: {}", err),
4037                        &["Ok"],
4038                    )
4039                })?
4040                .await
4041                .ok();
4042        }
4043
4044        // return ok, we showed the error to the user.
4045        return anyhow::Ok(());
4046    })
4047}
4048
4049pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4050    cx.update(|cx| {
4051        for window in cx.windows() {
4052            let is_workspace = window.downcast::<Workspace>().is_some();
4053            if is_workspace {
4054                window.update(cx, |_, cx| cx.activate_window()).ok();
4055                return Some(window);
4056            }
4057        }
4058        None
4059    })
4060    .ok()
4061    .flatten()
4062}
4063
4064#[allow(clippy::type_complexity)]
4065pub fn open_paths(
4066    abs_paths: &[PathBuf],
4067    app_state: &Arc<AppState>,
4068    requesting_window: Option<WindowHandle<Workspace>>,
4069    cx: &mut AppContext,
4070) -> Task<
4071    anyhow::Result<(
4072        WindowHandle<Workspace>,
4073        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4074    )>,
4075> {
4076    let app_state = app_state.clone();
4077    let abs_paths = abs_paths.to_vec();
4078    // Open paths in existing workspace if possible
4079    let existing = activate_workspace_for_project(cx, {
4080        let abs_paths = abs_paths.clone();
4081        move |project, cx| project.contains_paths(&abs_paths, cx)
4082    });
4083    cx.spawn(move |mut cx| async move {
4084        if let Some(existing) = existing {
4085            Ok((
4086                existing.clone(),
4087                existing
4088                    .update(&mut cx, |workspace, cx| {
4089                        workspace.open_paths(abs_paths, true, cx)
4090                    })?
4091                    .await,
4092            ))
4093        } else {
4094            cx.update(move |cx| {
4095                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4096            })?
4097            .await
4098        }
4099    })
4100}
4101
4102pub fn open_new(
4103    app_state: &Arc<AppState>,
4104    cx: &mut AppContext,
4105    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4106) -> Task<()> {
4107    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4108    cx.spawn(|mut cx| async move {
4109        if let Some((workspace, opened_paths)) = task.await.log_err() {
4110            workspace
4111                .update(&mut cx, |workspace, cx| {
4112                    if opened_paths.is_empty() {
4113                        init(workspace, cx)
4114                    }
4115                })
4116                .log_err();
4117        }
4118    })
4119}
4120
4121pub fn create_and_open_local_file(
4122    path: &'static Path,
4123    cx: &mut ViewContext<Workspace>,
4124    default_content: impl 'static + Send + FnOnce() -> Rope,
4125) -> Task<Result<Box<dyn ItemHandle>>> {
4126    cx.spawn(|workspace, mut cx| async move {
4127        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4128        if !fs.is_file(path).await {
4129            fs.create_file(path, Default::default()).await?;
4130            fs.save(path, &default_content(), Default::default())
4131                .await?;
4132        }
4133
4134        let mut items = workspace
4135            .update(&mut cx, |workspace, cx| {
4136                workspace.with_local_workspace(cx, |workspace, cx| {
4137                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4138                })
4139            })?
4140            .await?
4141            .await;
4142
4143        let item = items.pop().flatten();
4144        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4145    })
4146}
4147
4148pub fn join_remote_project(
4149    project_id: u64,
4150    follow_user_id: u64,
4151    app_state: Arc<AppState>,
4152    cx: &mut AppContext,
4153) -> Task<Result<()>> {
4154    let windows = cx.windows();
4155    cx.spawn(|mut cx| async move {
4156        let existing_workspace = windows.into_iter().find_map(|window| {
4157            window.downcast::<Workspace>().and_then(|window| {
4158                window
4159                    .update(&mut cx, |workspace, cx| {
4160                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4161                            Some(window)
4162                        } else {
4163                            None
4164                        }
4165                    })
4166                    .unwrap_or(None)
4167            })
4168        });
4169
4170        let workspace = if let Some(existing_workspace) = existing_workspace {
4171            existing_workspace
4172        } else {
4173            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4174            let room = active_call
4175                .read_with(&cx, |call, _| call.room().cloned())?
4176                .ok_or_else(|| anyhow!("not in a call"))?;
4177            let project = room
4178                .update(&mut cx, |room, cx| {
4179                    room.join_project(
4180                        project_id,
4181                        app_state.languages.clone(),
4182                        app_state.fs.clone(),
4183                        cx,
4184                    )
4185                })?
4186                .await?;
4187
4188            let window_bounds_override = window_bounds_env_override(&cx);
4189            cx.update(|cx| {
4190                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4191                cx.open_window(options, |cx| {
4192                    cx.build_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4193                })
4194            })?
4195        };
4196
4197        workspace.update(&mut cx, |workspace, cx| {
4198            cx.activate(true);
4199            cx.activate_window();
4200
4201            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4202                let follow_peer_id = room
4203                    .read(cx)
4204                    .remote_participants()
4205                    .iter()
4206                    .find(|(_, participant)| participant.user.id == follow_user_id)
4207                    .map(|(_, p)| p.peer_id)
4208                    .or_else(|| {
4209                        // If we couldn't follow the given user, follow the host instead.
4210                        let collaborator = workspace
4211                            .project()
4212                            .read(cx)
4213                            .collaborators()
4214                            .values()
4215                            .find(|collaborator| collaborator.replica_id == 0)?;
4216                        Some(collaborator.peer_id)
4217                    });
4218
4219                if let Some(follow_peer_id) = follow_peer_id {
4220                    workspace.follow(follow_peer_id, cx);
4221                }
4222            }
4223        })?;
4224
4225        anyhow::Ok(())
4226    })
4227}
4228
4229pub fn restart(_: &Restart, cx: &mut AppContext) {
4230    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4231    let mut workspace_windows = cx
4232        .windows()
4233        .into_iter()
4234        .filter_map(|window| window.downcast::<Workspace>())
4235        .collect::<Vec<_>>();
4236
4237    // If multiple windows have unsaved changes, and need a save prompt,
4238    // prompt in the active window before switching to a different window.
4239    workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4240
4241    let mut prompt = None;
4242    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4243        prompt = window
4244            .update(cx, |_, cx| {
4245                cx.prompt(
4246                    PromptLevel::Info,
4247                    "Are you sure you want to restart?",
4248                    &["Restart", "Cancel"],
4249                )
4250            })
4251            .ok();
4252    }
4253
4254    cx.spawn(|mut cx| async move {
4255        if let Some(prompt) = prompt {
4256            let answer = prompt.await?;
4257            if answer != 0 {
4258                return Ok(());
4259            }
4260        }
4261
4262        // If the user cancels any save prompt, then keep the app open.
4263        for window in workspace_windows {
4264            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4265                workspace.prepare_to_close(true, cx)
4266            }) {
4267                if !should_close.await? {
4268                    return Ok(());
4269                }
4270            }
4271        }
4272
4273        cx.update(|cx| cx.restart())
4274    })
4275    .detach_and_log_err(cx);
4276}
4277
4278fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4279    let mut parts = value.split(',');
4280    let x: usize = parts.next()?.parse().ok()?;
4281    let y: usize = parts.next()?.parse().ok()?;
4282    Some(point((x as f64).into(), (y as f64).into()))
4283}
4284
4285fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4286    let mut parts = value.split(',');
4287    let width: usize = parts.next()?.parse().ok()?;
4288    let height: usize = parts.next()?.parse().ok()?;
4289    Some(size((width as f64).into(), (height as f64).into()))
4290}
4291
4292#[cfg(test)]
4293mod tests {
4294    use std::{cell::RefCell, rc::Rc};
4295
4296    use super::*;
4297    use crate::item::{
4298        test::{TestItem, TestProjectItem},
4299        ItemEvent,
4300    };
4301    use fs::FakeFs;
4302    use gpui::TestAppContext;
4303    use project::{Project, ProjectEntryId};
4304    use serde_json::json;
4305    use settings::SettingsStore;
4306
4307    #[gpui::test]
4308    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4309        init_test(cx);
4310
4311        let fs = FakeFs::new(cx.executor());
4312        let project = Project::test(fs, [], cx).await;
4313        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4314
4315        // Adding an item with no ambiguity renders the tab without detail.
4316        let item1 = cx.build_view(|cx| {
4317            let mut item = TestItem::new(cx);
4318            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4319            item
4320        });
4321        workspace.update(cx, |workspace, cx| {
4322            workspace.add_item(Box::new(item1.clone()), cx);
4323        });
4324        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4325
4326        // Adding an item that creates ambiguity increases the level of detail on
4327        // both tabs.
4328        let item2 = cx.build_view(|cx| {
4329            let mut item = TestItem::new(cx);
4330            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4331            item
4332        });
4333        workspace.update(cx, |workspace, cx| {
4334            workspace.add_item(Box::new(item2.clone()), cx);
4335        });
4336        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4337        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4338
4339        // Adding an item that creates ambiguity increases the level of detail only
4340        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4341        // we stop at the highest detail available.
4342        let item3 = cx.build_view(|cx| {
4343            let mut item = TestItem::new(cx);
4344            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4345            item
4346        });
4347        workspace.update(cx, |workspace, cx| {
4348            workspace.add_item(Box::new(item3.clone()), cx);
4349        });
4350        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4351        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4352        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4353    }
4354
4355    #[gpui::test]
4356    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4357        init_test(cx);
4358
4359        let fs = FakeFs::new(cx.executor());
4360        fs.insert_tree(
4361            "/root1",
4362            json!({
4363                "one.txt": "",
4364                "two.txt": "",
4365            }),
4366        )
4367        .await;
4368        fs.insert_tree(
4369            "/root2",
4370            json!({
4371                "three.txt": "",
4372            }),
4373        )
4374        .await;
4375
4376        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4377        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4378        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4379        let worktree_id = project.update(cx, |project, cx| {
4380            project.worktrees().next().unwrap().read(cx).id()
4381        });
4382
4383        let item1 = cx.build_view(|cx| {
4384            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4385        });
4386        let item2 = cx.build_view(|cx| {
4387            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4388        });
4389
4390        // Add an item to an empty pane
4391        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4392        project.update(cx, |project, cx| {
4393            assert_eq!(
4394                project.active_entry(),
4395                project
4396                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4397                    .map(|e| e.id)
4398            );
4399        });
4400        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4401
4402        // Add a second item to a non-empty pane
4403        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4404        assert_eq!(cx.window_title().as_deref(), Some("two.txt β€” root1"));
4405        project.update(cx, |project, cx| {
4406            assert_eq!(
4407                project.active_entry(),
4408                project
4409                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4410                    .map(|e| e.id)
4411            );
4412        });
4413
4414        // Close the active item
4415        pane.update(cx, |pane, cx| {
4416            pane.close_active_item(&Default::default(), cx).unwrap()
4417        })
4418        .await
4419        .unwrap();
4420        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4421        project.update(cx, |project, cx| {
4422            assert_eq!(
4423                project.active_entry(),
4424                project
4425                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4426                    .map(|e| e.id)
4427            );
4428        });
4429
4430        // Add a project folder
4431        project
4432            .update(cx, |project, cx| {
4433                project.find_or_create_local_worktree("/root2", true, cx)
4434            })
4435            .await
4436            .unwrap();
4437        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1, root2"));
4438
4439        // Remove a project folder
4440        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4441        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root2"));
4442    }
4443
4444    #[gpui::test]
4445    async fn test_close_window(cx: &mut TestAppContext) {
4446        init_test(cx);
4447
4448        let fs = FakeFs::new(cx.executor());
4449        fs.insert_tree("/root", json!({ "one": "" })).await;
4450
4451        let project = Project::test(fs, ["root".as_ref()], cx).await;
4452        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4453
4454        // When there are no dirty items, there's nothing to do.
4455        let item1 = cx.build_view(|cx| TestItem::new(cx));
4456        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4457        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4458        assert!(task.await.unwrap());
4459
4460        // When there are dirty untitled items, prompt to save each one. If the user
4461        // cancels any prompt, then abort.
4462        let item2 = cx.build_view(|cx| TestItem::new(cx).with_dirty(true));
4463        let item3 = cx.build_view(|cx| {
4464            TestItem::new(cx)
4465                .with_dirty(true)
4466                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4467        });
4468        workspace.update(cx, |w, cx| {
4469            w.add_item(Box::new(item2.clone()), cx);
4470            w.add_item(Box::new(item3.clone()), cx);
4471        });
4472        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4473        cx.executor().run_until_parked();
4474        cx.simulate_prompt_answer(2); // cancel save all
4475        cx.executor().run_until_parked();
4476        cx.simulate_prompt_answer(2); // cancel save all
4477        cx.executor().run_until_parked();
4478        assert!(!cx.has_pending_prompt());
4479        assert!(!task.await.unwrap());
4480    }
4481
4482    #[gpui::test]
4483    async fn test_close_pane_items(cx: &mut TestAppContext) {
4484        init_test(cx);
4485
4486        let fs = FakeFs::new(cx.executor());
4487
4488        let project = Project::test(fs, None, cx).await;
4489        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4490
4491        let item1 = cx.build_view(|cx| {
4492            TestItem::new(cx)
4493                .with_dirty(true)
4494                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4495        });
4496        let item2 = cx.build_view(|cx| {
4497            TestItem::new(cx)
4498                .with_dirty(true)
4499                .with_conflict(true)
4500                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4501        });
4502        let item3 = cx.build_view(|cx| {
4503            TestItem::new(cx)
4504                .with_dirty(true)
4505                .with_conflict(true)
4506                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4507        });
4508        let item4 = cx.build_view(|cx| {
4509            TestItem::new(cx)
4510                .with_dirty(true)
4511                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4512        });
4513        let pane = workspace.update(cx, |workspace, cx| {
4514            workspace.add_item(Box::new(item1.clone()), cx);
4515            workspace.add_item(Box::new(item2.clone()), cx);
4516            workspace.add_item(Box::new(item3.clone()), cx);
4517            workspace.add_item(Box::new(item4.clone()), cx);
4518            workspace.active_pane().clone()
4519        });
4520
4521        let close_items = pane.update(cx, |pane, cx| {
4522            pane.activate_item(1, true, true, cx);
4523            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4524            let item1_id = item1.item_id();
4525            let item3_id = item3.item_id();
4526            let item4_id = item4.item_id();
4527            pane.close_items(cx, SaveIntent::Close, move |id| {
4528                [item1_id, item3_id, item4_id].contains(&id)
4529            })
4530        });
4531        cx.executor().run_until_parked();
4532
4533        assert!(cx.has_pending_prompt());
4534        // Ignore "Save all" prompt
4535        cx.simulate_prompt_answer(2);
4536        cx.executor().run_until_parked();
4537        // There's a prompt to save item 1.
4538        pane.update(cx, |pane, _| {
4539            assert_eq!(pane.items_len(), 4);
4540            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4541        });
4542        // Confirm saving item 1.
4543        cx.simulate_prompt_answer(0);
4544        cx.executor().run_until_parked();
4545
4546        // Item 1 is saved. There's a prompt to save item 3.
4547        pane.update(cx, |pane, cx| {
4548            assert_eq!(item1.read(cx).save_count, 1);
4549            assert_eq!(item1.read(cx).save_as_count, 0);
4550            assert_eq!(item1.read(cx).reload_count, 0);
4551            assert_eq!(pane.items_len(), 3);
4552            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4553        });
4554        assert!(cx.has_pending_prompt());
4555
4556        // Cancel saving item 3.
4557        cx.simulate_prompt_answer(1);
4558        cx.executor().run_until_parked();
4559
4560        // Item 3 is reloaded. There's a prompt to save item 4.
4561        pane.update(cx, |pane, cx| {
4562            assert_eq!(item3.read(cx).save_count, 0);
4563            assert_eq!(item3.read(cx).save_as_count, 0);
4564            assert_eq!(item3.read(cx).reload_count, 1);
4565            assert_eq!(pane.items_len(), 2);
4566            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4567        });
4568        assert!(cx.has_pending_prompt());
4569
4570        // Confirm saving item 4.
4571        cx.simulate_prompt_answer(0);
4572        cx.executor().run_until_parked();
4573
4574        // There's a prompt for a path for item 4.
4575        cx.simulate_new_path_selection(|_| Some(Default::default()));
4576        close_items.await.unwrap();
4577
4578        // The requested items are closed.
4579        pane.update(cx, |pane, cx| {
4580            assert_eq!(item4.read(cx).save_count, 0);
4581            assert_eq!(item4.read(cx).save_as_count, 1);
4582            assert_eq!(item4.read(cx).reload_count, 0);
4583            assert_eq!(pane.items_len(), 1);
4584            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4585        });
4586    }
4587
4588    #[gpui::test]
4589    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4590        init_test(cx);
4591
4592        let fs = FakeFs::new(cx.executor());
4593        let project = Project::test(fs, [], cx).await;
4594        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4595
4596        // Create several workspace items with single project entries, and two
4597        // workspace items with multiple project entries.
4598        let single_entry_items = (0..=4)
4599            .map(|project_entry_id| {
4600                cx.build_view(|cx| {
4601                    TestItem::new(cx)
4602                        .with_dirty(true)
4603                        .with_project_items(&[TestProjectItem::new(
4604                            project_entry_id,
4605                            &format!("{project_entry_id}.txt"),
4606                            cx,
4607                        )])
4608                })
4609            })
4610            .collect::<Vec<_>>();
4611        let item_2_3 = cx.build_view(|cx| {
4612            TestItem::new(cx)
4613                .with_dirty(true)
4614                .with_singleton(false)
4615                .with_project_items(&[
4616                    single_entry_items[2].read(cx).project_items[0].clone(),
4617                    single_entry_items[3].read(cx).project_items[0].clone(),
4618                ])
4619        });
4620        let item_3_4 = cx.build_view(|cx| {
4621            TestItem::new(cx)
4622                .with_dirty(true)
4623                .with_singleton(false)
4624                .with_project_items(&[
4625                    single_entry_items[3].read(cx).project_items[0].clone(),
4626                    single_entry_items[4].read(cx).project_items[0].clone(),
4627                ])
4628        });
4629
4630        // Create two panes that contain the following project entries:
4631        //   left pane:
4632        //     multi-entry items:   (2, 3)
4633        //     single-entry items:  0, 1, 2, 3, 4
4634        //   right pane:
4635        //     single-entry items:  1
4636        //     multi-entry items:   (3, 4)
4637        let left_pane = workspace.update(cx, |workspace, cx| {
4638            let left_pane = workspace.active_pane().clone();
4639            workspace.add_item(Box::new(item_2_3.clone()), cx);
4640            for item in single_entry_items {
4641                workspace.add_item(Box::new(item), cx);
4642            }
4643            left_pane.update(cx, |pane, cx| {
4644                pane.activate_item(2, true, true, cx);
4645            });
4646
4647            let right_pane = workspace
4648                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4649                .unwrap();
4650
4651            right_pane.update(cx, |pane, cx| {
4652                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4653            });
4654
4655            left_pane
4656        });
4657
4658        cx.focus_view(&left_pane);
4659
4660        // When closing all of the items in the left pane, we should be prompted twice:
4661        // once for project entry 0, and once for project entry 2. Project entries 1,
4662        // 3, and 4 are all still open in the other paten. After those two
4663        // prompts, the task should complete.
4664
4665        let close = left_pane.update(cx, |pane, cx| {
4666            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4667        });
4668        cx.executor().run_until_parked();
4669
4670        // Discard "Save all" prompt
4671        cx.simulate_prompt_answer(2);
4672
4673        cx.executor().run_until_parked();
4674        left_pane.update(cx, |pane, cx| {
4675            assert_eq!(
4676                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4677                &[ProjectEntryId::from_proto(0)]
4678            );
4679        });
4680        cx.simulate_prompt_answer(0);
4681
4682        cx.executor().run_until_parked();
4683        left_pane.update(cx, |pane, cx| {
4684            assert_eq!(
4685                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4686                &[ProjectEntryId::from_proto(2)]
4687            );
4688        });
4689        cx.simulate_prompt_answer(0);
4690
4691        cx.executor().run_until_parked();
4692        close.await.unwrap();
4693        left_pane.update(cx, |pane, _| {
4694            assert_eq!(pane.items_len(), 0);
4695        });
4696    }
4697
4698    #[gpui::test]
4699    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4700        init_test(cx);
4701
4702        let fs = FakeFs::new(cx.executor());
4703        let project = Project::test(fs, [], cx).await;
4704        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4705        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4706
4707        let item = cx.build_view(|cx| {
4708            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4709        });
4710        let item_id = item.entity_id();
4711        workspace.update(cx, |workspace, cx| {
4712            workspace.add_item(Box::new(item.clone()), cx);
4713        });
4714
4715        // Autosave on window change.
4716        item.update(cx, |item, cx| {
4717            cx.update_global(|settings: &mut SettingsStore, cx| {
4718                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4719                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4720                })
4721            });
4722            item.is_dirty = true;
4723        });
4724
4725        // Deactivating the window saves the file.
4726        cx.simulate_deactivation();
4727        cx.executor().run_until_parked();
4728        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4729
4730        // Autosave on focus change.
4731        item.update(cx, |item, cx| {
4732            cx.focus_self();
4733            cx.update_global(|settings: &mut SettingsStore, cx| {
4734                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4735                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4736                })
4737            });
4738            item.is_dirty = true;
4739        });
4740
4741        // Blurring the item saves the file.
4742        item.update(cx, |_, cx| cx.blur());
4743        cx.executor().run_until_parked();
4744        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4745
4746        // Deactivating the window still saves the file.
4747        cx.simulate_activation();
4748        item.update(cx, |item, cx| {
4749            cx.focus_self();
4750            item.is_dirty = true;
4751        });
4752        cx.simulate_deactivation();
4753
4754        cx.executor().run_until_parked();
4755        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4756
4757        // Autosave after delay.
4758        item.update(cx, |item, cx| {
4759            cx.update_global(|settings: &mut SettingsStore, cx| {
4760                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4761                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4762                })
4763            });
4764            item.is_dirty = true;
4765            cx.emit(ItemEvent::Edit);
4766        });
4767
4768        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4769        cx.executor().advance_clock(Duration::from_millis(250));
4770        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4771
4772        // After delay expires, the file is saved.
4773        cx.executor().advance_clock(Duration::from_millis(250));
4774        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4775
4776        // Autosave on focus change, ensuring closing the tab counts as such.
4777        item.update(cx, |item, cx| {
4778            cx.update_global(|settings: &mut SettingsStore, cx| {
4779                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4780                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4781                })
4782            });
4783            item.is_dirty = true;
4784        });
4785
4786        pane.update(cx, |pane, cx| {
4787            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4788        })
4789        .await
4790        .unwrap();
4791        assert!(!cx.has_pending_prompt());
4792        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4793
4794        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4795        workspace.update(cx, |workspace, cx| {
4796            workspace.add_item(Box::new(item.clone()), cx);
4797        });
4798        item.update(cx, |item, cx| {
4799            item.project_items[0].update(cx, |item, _| {
4800                item.entry_id = None;
4801            });
4802            item.is_dirty = true;
4803            cx.blur();
4804        });
4805        cx.run_until_parked();
4806        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4807
4808        // Ensure autosave is prevented for deleted files also when closing the buffer.
4809        let _close_items = pane.update(cx, |pane, cx| {
4810            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4811        });
4812        cx.run_until_parked();
4813        assert!(cx.has_pending_prompt());
4814        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4815    }
4816
4817    #[gpui::test]
4818    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4819        init_test(cx);
4820
4821        let fs = FakeFs::new(cx.executor());
4822
4823        let project = Project::test(fs, [], cx).await;
4824        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4825
4826        let item = cx.build_view(|cx| {
4827            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4828        });
4829        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4830        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4831        let toolbar_notify_count = Rc::new(RefCell::new(0));
4832
4833        workspace.update(cx, |workspace, cx| {
4834            workspace.add_item(Box::new(item.clone()), cx);
4835            let toolbar_notification_count = toolbar_notify_count.clone();
4836            cx.observe(&toolbar, move |_, _, _| {
4837                *toolbar_notification_count.borrow_mut() += 1
4838            })
4839            .detach();
4840        });
4841
4842        pane.update(cx, |pane, _| {
4843            assert!(!pane.can_navigate_backward());
4844            assert!(!pane.can_navigate_forward());
4845        });
4846
4847        item.update(cx, |item, cx| {
4848            item.set_state("one".to_string(), cx);
4849        });
4850
4851        // Toolbar must be notified to re-render the navigation buttons
4852        assert_eq!(*toolbar_notify_count.borrow(), 1);
4853
4854        pane.update(cx, |pane, _| {
4855            assert!(pane.can_navigate_backward());
4856            assert!(!pane.can_navigate_forward());
4857        });
4858
4859        workspace
4860            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4861            .await
4862            .unwrap();
4863
4864        assert_eq!(*toolbar_notify_count.borrow(), 2);
4865        pane.update(cx, |pane, _| {
4866            assert!(!pane.can_navigate_backward());
4867            assert!(pane.can_navigate_forward());
4868        });
4869    }
4870
4871    // #[gpui::test]
4872    // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4873    //     init_test(cx);
4874    //     let fs = FakeFs::new(cx.executor());
4875
4876    //     let project = Project::test(fs, [], cx).await;
4877    //     let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4878
4879    //     let panel = workspace.update(cx, |workspace, cx| {
4880    //         let panel = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx));
4881    //         workspace.add_panel(panel.clone(), cx);
4882
4883    //         workspace
4884    //             .right_dock()
4885    //             .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4886
4887    //         panel
4888    //     });
4889
4890    //     let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4891    //     pane.update(cx, |pane, cx| {
4892    //         let item = cx.build_view(|cx| TestItem::new(cx));
4893    //         pane.add_item(Box::new(item), true, true, None, cx);
4894    //     });
4895
4896    //     // Transfer focus from center to panel
4897    //     workspace.update(cx, |workspace, cx| {
4898    //         workspace.toggle_panel_focus::<TestPanel>(cx);
4899    //     });
4900
4901    //     workspace.update(cx, |workspace, cx| {
4902    //         assert!(workspace.right_dock().read(cx).is_open());
4903    //         assert!(!panel.is_zoomed(cx));
4904    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4905    //     });
4906
4907    //     // Transfer focus from panel to center
4908    //     workspace.update(cx, |workspace, cx| {
4909    //         workspace.toggle_panel_focus::<TestPanel>(cx);
4910    //     });
4911
4912    //     workspace.update(cx, |workspace, cx| {
4913    //         assert!(workspace.right_dock().read(cx).is_open());
4914    //         assert!(!panel.is_zoomed(cx));
4915    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4916    //     });
4917
4918    //     // Close the dock
4919    //     workspace.update(cx, |workspace, cx| {
4920    //         workspace.toggle_dock(DockPosition::Right, cx);
4921    //     });
4922
4923    //     workspace.update(cx, |workspace, cx| {
4924    //         assert!(!workspace.right_dock().read(cx).is_open());
4925    //         assert!(!panel.is_zoomed(cx));
4926    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4927    //     });
4928
4929    //     // Open the dock
4930    //     workspace.update(cx, |workspace, cx| {
4931    //         workspace.toggle_dock(DockPosition::Right, cx);
4932    //     });
4933
4934    //     workspace.update(cx, |workspace, cx| {
4935    //         assert!(workspace.right_dock().read(cx).is_open());
4936    //         assert!(!panel.is_zoomed(cx));
4937    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4938    //     });
4939
4940    //     // Focus and zoom panel
4941    //     panel.update(cx, |panel, cx| {
4942    //         cx.focus_self();
4943    //         panel.set_zoomed(true, cx)
4944    //     });
4945
4946    //     workspace.update(cx, |workspace, cx| {
4947    //         assert!(workspace.right_dock().read(cx).is_open());
4948    //         assert!(panel.is_zoomed(cx));
4949    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4950    //     });
4951
4952    //     // Transfer focus to the center closes the dock
4953    //     workspace.update(cx, |workspace, cx| {
4954    //         workspace.toggle_panel_focus::<TestPanel>(cx);
4955    //     });
4956
4957    //     workspace.update(cx, |workspace, cx| {
4958    //         assert!(!workspace.right_dock().read(cx).is_open());
4959    //         assert!(panel.is_zoomed(cx));
4960    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4961    //     });
4962
4963    //     // Transferring focus back to the panel keeps it zoomed
4964    //     workspace.update(cx, |workspace, cx| {
4965    //         workspace.toggle_panel_focus::<TestPanel>(cx);
4966    //     });
4967
4968    //     workspace.update(cx, |workspace, cx| {
4969    //         assert!(workspace.right_dock().read(cx).is_open());
4970    //         assert!(panel.is_zoomed(cx));
4971    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4972    //     });
4973
4974    //     // Close the dock while it is zoomed
4975    //     workspace.update(cx, |workspace, cx| {
4976    //         workspace.toggle_dock(DockPosition::Right, cx)
4977    //     });
4978
4979    //     workspace.update(cx, |workspace, cx| {
4980    //         assert!(!workspace.right_dock().read(cx).is_open());
4981    //         assert!(panel.is_zoomed(cx));
4982    //         assert!(workspace.zoomed.is_none());
4983    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4984    //     });
4985
4986    //     // Opening the dock, when it's zoomed, retains focus
4987    //     workspace.update(cx, |workspace, cx| {
4988    //         workspace.toggle_dock(DockPosition::Right, cx)
4989    //     });
4990
4991    //     workspace.update(cx, |workspace, cx| {
4992    //         assert!(workspace.right_dock().read(cx).is_open());
4993    //         assert!(panel.is_zoomed(cx));
4994    //         assert!(workspace.zoomed.is_some());
4995    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4996    //     });
4997
4998    //     // Unzoom and close the panel, zoom the active pane.
4999    //     panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5000    //     workspace.update(cx, |workspace, cx| {
5001    //         workspace.toggle_dock(DockPosition::Right, cx)
5002    //     });
5003    //     pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5004
5005    //     // Opening a dock unzooms the pane.
5006    //     workspace.update(cx, |workspace, cx| {
5007    //         workspace.toggle_dock(DockPosition::Right, cx)
5008    //     });
5009    //     workspace.update(cx, |workspace, cx| {
5010    //         let pane = pane.read(cx);
5011    //         assert!(!pane.is_zoomed());
5012    //         assert!(!pane.focus_handle(cx).is_focused(cx));
5013    //         assert!(workspace.right_dock().read(cx).is_open());
5014    //         assert!(workspace.zoomed.is_none());
5015    //     });
5016    // }
5017
5018    // #[gpui::test]
5019    // async fn test_panels(cx: &mut gpui::TestAppContext) {
5020    //     init_test(cx);
5021    //     let fs = FakeFs::new(cx.executor());
5022
5023    //     let project = Project::test(fs, [], cx).await;
5024    //     let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5025
5026    //     let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5027    //         // Add panel_1 on the left, panel_2 on the right.
5028    //         let panel_1 = cx.build_view(|cx| TestPanel::new(DockPosition::Left, cx));
5029    //         workspace.add_panel(panel_1.clone(), cx);
5030    //         workspace
5031    //             .left_dock()
5032    //             .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5033    //         let panel_2 = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx));
5034    //         workspace.add_panel(panel_2.clone(), cx);
5035    //         workspace
5036    //             .right_dock()
5037    //             .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5038
5039    //         let left_dock = workspace.left_dock();
5040    //         assert_eq!(
5041    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5042    //             panel_1.panel_id()
5043    //         );
5044    //         assert_eq!(
5045    //             left_dock.read(cx).active_panel_size(cx).unwrap(),
5046    //             panel_1.size(cx)
5047    //         );
5048
5049    //         left_dock.update(cx, |left_dock, cx| {
5050    //             left_dock.resize_active_panel(Some(1337.), cx)
5051    //         });
5052    //         assert_eq!(
5053    //             workspace
5054    //                 .right_dock()
5055    //                 .read(cx)
5056    //                 .visible_panel()
5057    //                 .unwrap()
5058    //                 .panel_id(),
5059    //             panel_2.panel_id(),
5060    //         );
5061
5062    //         (panel_1, panel_2)
5063    //     });
5064
5065    //     // Move panel_1 to the right
5066    //     panel_1.update(cx, |panel_1, cx| {
5067    //         panel_1.set_position(DockPosition::Right, cx)
5068    //     });
5069
5070    //     workspace.update(cx, |workspace, cx| {
5071    //         // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5072    //         // Since it was the only panel on the left, the left dock should now be closed.
5073    //         assert!(!workspace.left_dock().read(cx).is_open());
5074    //         assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5075    //         let right_dock = workspace.right_dock();
5076    //         assert_eq!(
5077    //             right_dock.read(cx).visible_panel().unwrap().panel_id(),
5078    //             panel_1.panel_id()
5079    //         );
5080    //         assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5081
5082    //         // Now we move panel_2Β to the left
5083    //         panel_2.set_position(DockPosition::Left, cx);
5084    //     });
5085
5086    //     workspace.update(cx, |workspace, cx| {
5087    //         // Since panel_2 was not visible on the right, we don't open the left dock.
5088    //         assert!(!workspace.left_dock().read(cx).is_open());
5089    //         // And the right dock is unaffected in it's displaying of panel_1
5090    //         assert!(workspace.right_dock().read(cx).is_open());
5091    //         assert_eq!(
5092    //             workspace
5093    //                 .right_dock()
5094    //                 .read(cx)
5095    //                 .visible_panel()
5096    //                 .unwrap()
5097    //                 .panel_id(),
5098    //             panel_1.panel_id(),
5099    //         );
5100    //     });
5101
5102    //     // Move panel_1 back to the left
5103    //     panel_1.update(cx, |panel_1, cx| {
5104    //         panel_1.set_position(DockPosition::Left, cx)
5105    //     });
5106
5107    //     workspace.update(cx, |workspace, cx| {
5108    //         // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5109    //         let left_dock = workspace.left_dock();
5110    //         assert!(left_dock.read(cx).is_open());
5111    //         assert_eq!(
5112    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5113    //             panel_1.panel_id()
5114    //         );
5115    //         assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5116    //         // And right the dock should be closed as it no longer has any panels.
5117    //         assert!(!workspace.right_dock().read(cx).is_open());
5118
5119    //         // Now we move panel_1 to the bottom
5120    //         panel_1.set_position(DockPosition::Bottom, cx);
5121    //     });
5122
5123    //     workspace.update(cx, |workspace, cx| {
5124    //         // Since panel_1 was visible on the left, we close the left dock.
5125    //         assert!(!workspace.left_dock().read(cx).is_open());
5126    //         // The bottom dock is sized based on the panel's default size,
5127    //         // since the panel orientation changed from vertical to horizontal.
5128    //         let bottom_dock = workspace.bottom_dock();
5129    //         assert_eq!(
5130    //             bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5131    //             panel_1.size(cx),
5132    //         );
5133    //         // Close bottom dock and move panel_1 back to the left.
5134    //         bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5135    //         panel_1.set_position(DockPosition::Left, cx);
5136    //     });
5137
5138    //     // Emit activated event on panel 1
5139    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5140
5141    //     // Now the left dock is open and panel_1 is active and focused.
5142    //     workspace.update(cx, |workspace, cx| {
5143    //         let left_dock = workspace.left_dock();
5144    //         assert!(left_dock.read(cx).is_open());
5145    //         assert_eq!(
5146    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5147    //             panel_1.panel_id(),
5148    //         );
5149    //         assert!(panel_1.focus_handle(cx).is_focused(cx));
5150    //     });
5151
5152    //     // Emit closed event on panel 2, which is not active
5153    //     panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5154
5155    //     // Wo don't close the left dock, because panel_2 wasn't the active panel
5156    //     workspace.update(cx, |workspace, cx| {
5157    //         let left_dock = workspace.left_dock();
5158    //         assert!(left_dock.read(cx).is_open());
5159    //         assert_eq!(
5160    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5161    //             panel_1.panel_id(),
5162    //         );
5163    //     });
5164
5165    //     // Emitting a ZoomIn event shows the panel as zoomed.
5166    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5167    //     workspace.update(cx, |workspace, _| {
5168    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5169    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5170    //     });
5171
5172    //     // Move panel to another dock while it is zoomed
5173    //     panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5174    //     workspace.update(cx, |workspace, _| {
5175    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5176
5177    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5178    //     });
5179
5180    //     // If focus is transferred to another view that's not a panel or another pane, we still show
5181    //     // the panel as zoomed.
5182    //     let other_focus_handle = cx.update(|cx| cx.focus_handle());
5183    //     cx.update(|cx| cx.focus(&other_focus_handle));
5184    //     workspace.update(cx, |workspace, _| {
5185    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5186    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5187    //     });
5188
5189    //     // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5190    //     workspace.update(cx, |_, cx| cx.focus_self());
5191    //     workspace.update(cx, |workspace, _| {
5192    //         assert_eq!(workspace.zoomed, None);
5193    //         assert_eq!(workspace.zoomed_position, None);
5194    //     });
5195
5196    //     // If focus is transferred again to another view that's not a panel or a pane, we won't
5197    //     // show the panel as zoomed because it wasn't zoomed before.
5198    //     cx.update(|cx| cx.focus(&other_focus_handle));
5199    //     workspace.update(cx, |workspace, _| {
5200    //         assert_eq!(workspace.zoomed, None);
5201    //         assert_eq!(workspace.zoomed_position, None);
5202    //     });
5203
5204    //     // When focus is transferred back to the panel, it is zoomed again.
5205    //     panel_1.update(cx, |_, cx| cx.focus_self());
5206    //     workspace.update(cx, |workspace, _| {
5207    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5208    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5209    //     });
5210
5211    //     // Emitting a ZoomOut event unzooms the panel.
5212    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5213    //     workspace.update(cx, |workspace, _| {
5214    //         assert_eq!(workspace.zoomed, None);
5215    //         assert_eq!(workspace.zoomed_position, None);
5216    //     });
5217
5218    //     // Emit closed event on panel 1, which is active
5219    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5220
5221    //     // Now the left dock is closed, because panel_1 was the active panel
5222    //     workspace.update(cx, |workspace, cx| {
5223    //         let right_dock = workspace.right_dock();
5224    //         assert!(!right_dock.read(cx).is_open());
5225    //     });
5226    // }
5227
5228    pub fn init_test(cx: &mut TestAppContext) {
5229        cx.update(|cx| {
5230            let settings_store = SettingsStore::test(cx);
5231            cx.set_global(settings_store);
5232            theme::init(theme::LoadThemes::JustBase, cx);
5233            language::init(cx);
5234            crate::init_settings(cx);
5235            Project::init_settings(cx);
5236        });
5237    }
5238}