workspace.rs

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