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