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