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