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