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_read_only() {
1191            return Task::ready(Ok(true));
1192        }
1193        let dirty_items = self
1194            .panes
1195            .iter()
1196            .flat_map(|pane| {
1197                pane.read(cx).items().filter_map(|item| {
1198                    if item.is_dirty(cx) {
1199                        Some((pane.downgrade(), item.boxed_clone()))
1200                    } else {
1201                        None
1202                    }
1203                })
1204            })
1205            .collect::<Vec<_>>();
1206
1207        let project = self.project.clone();
1208        cx.spawn(|workspace, mut cx| async move {
1209            // Override save mode and display "Save all files" prompt
1210            if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
1211                let answer = workspace.update(&mut cx, |_, cx| {
1212                    let prompt = Pane::file_names_for_prompt(
1213                        &mut dirty_items.iter().map(|(_, handle)| handle),
1214                        dirty_items.len(),
1215                        cx,
1216                    );
1217                    cx.prompt(
1218                        PromptLevel::Warning,
1219                        &prompt,
1220                        &["Save all", "Discard all", "Cancel"],
1221                    )
1222                })?;
1223                match answer.await.log_err() {
1224                    Some(0) => save_intent = SaveIntent::SaveAll,
1225                    Some(1) => save_intent = SaveIntent::Skip,
1226                    _ => {}
1227                }
1228            }
1229            for (pane, item) in dirty_items {
1230                let (singleton, project_entry_ids) =
1231                    cx.update(|_, cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
1232                if singleton || !project_entry_ids.is_empty() {
1233                    if let Some(ix) =
1234                        pane.update(&mut cx, |pane, _| pane.index_for_item(item.as_ref()))?
1235                    {
1236                        if !Pane::save_item(
1237                            project.clone(),
1238                            &pane,
1239                            ix,
1240                            &*item,
1241                            save_intent,
1242                            &mut cx,
1243                        )
1244                        .await?
1245                        {
1246                            return Ok(false);
1247                        }
1248                    }
1249                }
1250            }
1251            Ok(true)
1252        })
1253    }
1254
1255    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
1256        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_read_only()
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        let app_state = Arc::new(AppState {
3270            languages: project.read(cx).languages().clone(),
3271            workspace_store,
3272            client,
3273            user_store,
3274            fs: project.read(cx).fs().clone(),
3275            build_window_options: |_, _, _| Default::default(),
3276            node_runtime: FakeNodeRuntime::new(),
3277        });
3278        let workspace = Self::new(0, project, app_state, cx);
3279        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3280        workspace
3281    }
3282
3283    //     fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3284    //         let dock = match position {
3285    //             DockPosition::Left => &self.left_dock,
3286    //             DockPosition::Right => &self.right_dock,
3287    //             DockPosition::Bottom => &self.bottom_dock,
3288    //         };
3289    //         let active_panel = dock.read(cx).visible_panel()?;
3290    //         let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3291    //             dock.read(cx).render_placeholder(cx)
3292    //         } else {
3293    //             ChildView::new(dock, cx).into_any()
3294    //         };
3295
3296    //         Some(
3297    //             element
3298    //                 .constrained()
3299    //                 .dynamically(move |constraint, _, cx| match position {
3300    //                     DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3301    //                         Vector2F::new(20., constraint.min.y()),
3302    //                         Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3303    //                     ),
3304    //                     DockPosition::Bottom => SizeConstraint::new(
3305    //                         Vector2F::new(constraint.min.x(), 20.),
3306    //                         Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3307    //                     ),
3308    //                 })
3309    //                 .into_any(),
3310    //         )
3311    //     }
3312    // }
3313    pub fn register_action<A: Action>(
3314        &mut self,
3315        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3316    ) -> &mut Self {
3317        let callback = Arc::new(callback);
3318
3319        self.workspace_actions.push(Box::new(move |div, cx| {
3320            let callback = callback.clone();
3321            div.on_action(
3322                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3323            )
3324        }));
3325        self
3326    }
3327
3328    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3329        let mut div = div
3330            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3331            .on_action(cx.listener(Self::close_all_items_and_panes))
3332            .on_action(cx.listener(Self::add_folder_to_project))
3333            .on_action(cx.listener(Self::save_all))
3334            .on_action(cx.listener(Self::open));
3335        for action in self.workspace_actions.iter() {
3336            div = (action)(div, cx)
3337        }
3338        div
3339    }
3340
3341    pub fn active_modal<V: ManagedView + 'static>(
3342        &mut self,
3343        cx: &ViewContext<Self>,
3344    ) -> Option<View<V>> {
3345        self.modal_layer.read(cx).active_modal()
3346    }
3347
3348    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3349    where
3350        B: FnOnce(&mut ViewContext<V>) -> V,
3351    {
3352        self.modal_layer
3353            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3354    }
3355}
3356
3357fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3358    let display_origin = cx
3359        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3360        .ok()??;
3361    ZED_WINDOW_POSITION
3362        .zip(*ZED_WINDOW_SIZE)
3363        .map(|(position, size)| {
3364            WindowBounds::Fixed(Bounds {
3365                origin: display_origin + position,
3366                size,
3367            })
3368        })
3369}
3370
3371fn open_items(
3372    serialized_workspace: Option<SerializedWorkspace>,
3373    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3374    app_state: Arc<AppState>,
3375    cx: &mut ViewContext<Workspace>,
3376) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3377    let restored_items = serialized_workspace.map(|serialized_workspace| {
3378        Workspace::load_workspace(
3379            serialized_workspace,
3380            project_paths_to_open
3381                .iter()
3382                .map(|(_, project_path)| project_path)
3383                .cloned()
3384                .collect(),
3385            cx,
3386        )
3387    });
3388
3389    cx.spawn(|workspace, mut cx| async move {
3390        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3391
3392        if let Some(restored_items) = restored_items {
3393            let restored_items = restored_items.await?;
3394
3395            let restored_project_paths = restored_items
3396                .iter()
3397                .filter_map(|item| {
3398                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
3399                        .ok()
3400                        .flatten()
3401                })
3402                .collect::<HashSet<_>>();
3403
3404            for restored_item in restored_items {
3405                opened_items.push(restored_item.map(Ok));
3406            }
3407
3408            project_paths_to_open
3409                .iter_mut()
3410                .for_each(|(_, project_path)| {
3411                    if let Some(project_path_to_open) = project_path {
3412                        if restored_project_paths.contains(project_path_to_open) {
3413                            *project_path = None;
3414                        }
3415                    }
3416                });
3417        } else {
3418            for _ in 0..project_paths_to_open.len() {
3419                opened_items.push(None);
3420            }
3421        }
3422        assert!(opened_items.len() == project_paths_to_open.len());
3423
3424        let tasks =
3425            project_paths_to_open
3426                .into_iter()
3427                .enumerate()
3428                .map(|(i, (abs_path, project_path))| {
3429                    let workspace = workspace.clone();
3430                    cx.spawn(|mut cx| {
3431                        let fs = app_state.fs.clone();
3432                        async move {
3433                            let file_project_path = project_path?;
3434                            if fs.is_file(&abs_path).await {
3435                                Some((
3436                                    i,
3437                                    workspace
3438                                        .update(&mut cx, |workspace, cx| {
3439                                            workspace.open_path(file_project_path, None, true, cx)
3440                                        })
3441                                        .log_err()?
3442                                        .await,
3443                                ))
3444                            } else {
3445                                None
3446                            }
3447                        }
3448                    })
3449                });
3450
3451        let tasks = tasks.collect::<Vec<_>>();
3452
3453        let tasks = futures::future::join_all(tasks.into_iter());
3454        for maybe_opened_path in tasks.await.into_iter() {
3455            if let Some((i, path_open_result)) = maybe_opened_path {
3456                opened_items[i] = Some(path_open_result);
3457            }
3458        }
3459
3460        Ok(opened_items)
3461    })
3462}
3463
3464fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3465    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3466
3467    workspace
3468        .update(cx, |workspace, cx| {
3469            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3470                workspace.show_notification_once(0, cx, |cx| {
3471                    cx.new_view(|_| {
3472                        MessageNotification::new("Failed to load the database file.")
3473                            .with_click_message("Click to let us know about this error")
3474                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3475                    })
3476                });
3477            }
3478        })
3479        .log_err();
3480}
3481
3482impl FocusableView for Workspace {
3483    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3484        self.active_pane.focus_handle(cx)
3485    }
3486}
3487
3488#[derive(Clone, Render)]
3489struct DraggedDock(DockPosition);
3490
3491impl Render for Workspace {
3492    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3493        let mut context = KeyContext::default();
3494        context.add("Workspace");
3495
3496        let (ui_font, ui_font_size) = {
3497            let theme_settings = ThemeSettings::get_global(cx);
3498            (
3499                theme_settings.ui_font.family.clone(),
3500                theme_settings.ui_font_size.clone(),
3501            )
3502        };
3503
3504        let theme = cx.theme().clone();
3505        let colors = theme.colors();
3506        cx.set_rem_size(ui_font_size);
3507
3508        self.actions(div(), cx)
3509            .key_context(context)
3510            .relative()
3511            .size_full()
3512            .flex()
3513            .flex_col()
3514            .font(ui_font)
3515            .gap_0()
3516            .justify_start()
3517            .items_start()
3518            .text_color(colors.text)
3519            .bg(colors.background)
3520            .border()
3521            .border_color(colors.border)
3522            .children(self.titlebar_item.clone())
3523            .child(
3524                div()
3525                    .id("workspace")
3526                    .relative()
3527                    .flex_1()
3528                    .w_full()
3529                    .flex()
3530                    .flex_col()
3531                    .overflow_hidden()
3532                    .border_t()
3533                    .border_b()
3534                    .border_color(colors.border)
3535                    .child(
3536                        canvas(cx.listener(|workspace, bounds, _| {
3537                            workspace.bounds = *bounds;
3538                        }))
3539                        .absolute()
3540                        .size_full(),
3541                    )
3542                    .on_drag_move(
3543                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3544                            match e.drag(cx).0 {
3545                                DockPosition::Left => {
3546                                    let size = workspace.bounds.left() + e.event.position.x;
3547                                    workspace.left_dock.update(cx, |left_dock, cx| {
3548                                        left_dock.resize_active_panel(Some(size), cx);
3549                                    });
3550                                }
3551                                DockPosition::Right => {
3552                                    let size = workspace.bounds.right() - e.event.position.x;
3553                                    workspace.right_dock.update(cx, |right_dock, cx| {
3554                                        right_dock.resize_active_panel(Some(size), cx);
3555                                    });
3556                                }
3557                                DockPosition::Bottom => {
3558                                    let size = workspace.bounds.bottom() - e.event.position.y;
3559                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3560                                        bottom_dock.resize_active_panel(Some(size), cx);
3561                                    });
3562                                }
3563                            }
3564                        }),
3565                    )
3566                    .child(self.modal_layer.clone())
3567                    .child(
3568                        div()
3569                            .flex()
3570                            .flex_row()
3571                            .h_full()
3572                            // Left Dock
3573                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3574                                || {
3575                                    div()
3576                                        .flex()
3577                                        .flex_none()
3578                                        .overflow_hidden()
3579                                        .child(self.left_dock.clone())
3580                                },
3581                            ))
3582                            // Panes
3583                            .child(
3584                                div()
3585                                    .flex()
3586                                    .flex_col()
3587                                    .flex_1()
3588                                    .overflow_hidden()
3589                                    .child(self.center.render(
3590                                        &self.project,
3591                                        &self.follower_states,
3592                                        self.active_call(),
3593                                        &self.active_pane,
3594                                        self.zoomed.as_ref(),
3595                                        &self.app_state,
3596                                        cx,
3597                                    ))
3598                                    .children(
3599                                        self.zoomed_position
3600                                            .ne(&Some(DockPosition::Bottom))
3601                                            .then(|| self.bottom_dock.clone()),
3602                                    ),
3603                            )
3604                            // Right Dock
3605                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3606                                || {
3607                                    div()
3608                                        .flex()
3609                                        .flex_none()
3610                                        .overflow_hidden()
3611                                        .child(self.right_dock.clone())
3612                                },
3613                            )),
3614                    )
3615                    .children(self.render_notifications(cx))
3616                    .children(self.zoomed.as_ref().and_then(|view| {
3617                        let zoomed_view = view.upgrade()?;
3618                        let div = div()
3619                            .z_index(1)
3620                            .absolute()
3621                            .overflow_hidden()
3622                            .border_color(colors.border)
3623                            .bg(colors.background)
3624                            .child(zoomed_view)
3625                            .inset_0()
3626                            .shadow_lg();
3627
3628                        Some(match self.zoomed_position {
3629                            Some(DockPosition::Left) => div.right_2().border_r(),
3630                            Some(DockPosition::Right) => div.left_2().border_l(),
3631                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3632                            None => div.top_2().bottom_2().left_2().right_2().border(),
3633                        })
3634                    })),
3635            )
3636            .child(self.status_bar.clone())
3637            .children(if self.project.read(cx).is_read_only() {
3638                Some(DisconnectedOverlay)
3639            } else {
3640                None
3641            })
3642    }
3643}
3644
3645impl WorkspaceStore {
3646    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3647        Self {
3648            workspaces: Default::default(),
3649            followers: Default::default(),
3650            _subscriptions: vec![
3651                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3652                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3653                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3654            ],
3655            client,
3656        }
3657    }
3658
3659    pub fn update_followers(
3660        &self,
3661        project_id: Option<u64>,
3662        update: proto::update_followers::Variant,
3663        cx: &AppContext,
3664    ) -> Option<()> {
3665        if !cx.has_global::<Model<ActiveCall>>() {
3666            return None;
3667        }
3668
3669        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3670        let follower_ids: Vec<_> = self
3671            .followers
3672            .iter()
3673            .filter_map(|follower| {
3674                if follower.project_id == project_id || project_id.is_none() {
3675                    Some(follower.peer_id.into())
3676                } else {
3677                    None
3678                }
3679            })
3680            .collect();
3681        if follower_ids.is_empty() {
3682            return None;
3683        }
3684        self.client
3685            .send(proto::UpdateFollowers {
3686                room_id,
3687                project_id,
3688                follower_ids,
3689                variant: Some(update),
3690            })
3691            .log_err()
3692    }
3693
3694    pub async fn handle_follow(
3695        this: Model<Self>,
3696        envelope: TypedEnvelope<proto::Follow>,
3697        _: Arc<Client>,
3698        mut cx: AsyncAppContext,
3699    ) -> Result<proto::FollowResponse> {
3700        this.update(&mut cx, |this, cx| {
3701            let follower = Follower {
3702                project_id: envelope.payload.project_id,
3703                peer_id: envelope.original_sender_id()?,
3704            };
3705            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3706
3707            let mut response = proto::FollowResponse::default();
3708            this.workspaces.retain(|workspace| {
3709                workspace
3710                    .update(cx, |workspace, cx| {
3711                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3712                        if response.views.is_empty() {
3713                            response.views = handler_response.views;
3714                        } else {
3715                            response.views.extend_from_slice(&handler_response.views);
3716                        }
3717
3718                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3719                            if response.active_view_id.is_none()
3720                                || Some(workspace.project.downgrade()) == active_project
3721                            {
3722                                response.active_view_id = Some(active_view_id);
3723                            }
3724                        }
3725                    })
3726                    .is_ok()
3727            });
3728
3729            if let Err(ix) = this.followers.binary_search(&follower) {
3730                this.followers.insert(ix, follower);
3731            }
3732
3733            Ok(response)
3734        })?
3735    }
3736
3737    async fn handle_unfollow(
3738        model: Model<Self>,
3739        envelope: TypedEnvelope<proto::Unfollow>,
3740        _: Arc<Client>,
3741        mut cx: AsyncAppContext,
3742    ) -> Result<()> {
3743        model.update(&mut cx, |this, _| {
3744            let follower = Follower {
3745                project_id: envelope.payload.project_id,
3746                peer_id: envelope.original_sender_id()?,
3747            };
3748            if let Ok(ix) = this.followers.binary_search(&follower) {
3749                this.followers.remove(ix);
3750            }
3751            Ok(())
3752        })?
3753    }
3754
3755    async fn handle_update_followers(
3756        this: Model<Self>,
3757        envelope: TypedEnvelope<proto::UpdateFollowers>,
3758        _: Arc<Client>,
3759        mut cx: AsyncAppContext,
3760    ) -> Result<()> {
3761        let leader_id = envelope.original_sender_id()?;
3762        let update = envelope.payload;
3763
3764        this.update(&mut cx, |this, cx| {
3765            this.workspaces.retain(|workspace| {
3766                workspace
3767                    .update(cx, |workspace, cx| {
3768                        let project_id = workspace.project.read(cx).remote_id();
3769                        if update.project_id != project_id && update.project_id.is_some() {
3770                            return;
3771                        }
3772                        workspace.handle_update_followers(leader_id, update.clone(), cx);
3773                    })
3774                    .is_ok()
3775            });
3776            Ok(())
3777        })?
3778    }
3779}
3780
3781impl ViewId {
3782    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3783        Ok(Self {
3784            creator: message
3785                .creator
3786                .ok_or_else(|| anyhow!("creator is missing"))?,
3787            id: message.id,
3788        })
3789    }
3790
3791    pub(crate) fn to_proto(&self) -> proto::ViewId {
3792        proto::ViewId {
3793            creator: Some(self.creator),
3794            id: self.id,
3795        }
3796    }
3797}
3798
3799pub trait WorkspaceHandle {
3800    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3801}
3802
3803impl WorkspaceHandle for View<Workspace> {
3804    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3805        self.read(cx)
3806            .worktrees(cx)
3807            .flat_map(|worktree| {
3808                let worktree_id = worktree.read(cx).id();
3809                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3810                    worktree_id,
3811                    path: f.path.clone(),
3812                })
3813            })
3814            .collect::<Vec<_>>()
3815    }
3816}
3817
3818impl std::fmt::Debug for OpenPaths {
3819    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3820        f.debug_struct("OpenPaths")
3821            .field("paths", &self.paths)
3822            .finish()
3823    }
3824}
3825
3826pub fn activate_workspace_for_project(
3827    cx: &mut AppContext,
3828    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
3829) -> Option<WindowHandle<Workspace>> {
3830    for window in cx.windows() {
3831        let Some(workspace) = window.downcast::<Workspace>() else {
3832            continue;
3833        };
3834
3835        let predicate = workspace
3836            .update(cx, |workspace, cx| {
3837                let project = workspace.project.read(cx);
3838                if predicate(project, cx) {
3839                    cx.activate_window();
3840                    true
3841                } else {
3842                    false
3843                }
3844            })
3845            .log_err()
3846            .unwrap_or(false);
3847
3848        if predicate {
3849            return Some(workspace);
3850        }
3851    }
3852
3853    None
3854}
3855
3856pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3857    DB.last_workspace().await.log_err().flatten()
3858}
3859
3860async fn join_channel_internal(
3861    channel_id: u64,
3862    app_state: &Arc<AppState>,
3863    requesting_window: Option<WindowHandle<Workspace>>,
3864    active_call: &Model<ActiveCall>,
3865    cx: &mut AsyncAppContext,
3866) -> Result<bool> {
3867    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
3868        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
3869            return (false, None);
3870        };
3871
3872        let already_in_channel = room.channel_id() == Some(channel_id);
3873        let should_prompt = room.is_sharing_project()
3874            && room.remote_participants().len() > 0
3875            && !already_in_channel;
3876        let open_room = if already_in_channel {
3877            active_call.room().cloned()
3878        } else {
3879            None
3880        };
3881        (should_prompt, open_room)
3882    })?;
3883
3884    if let Some(room) = open_room {
3885        let task = room.update(cx, |room, cx| {
3886            if let Some((project, host)) = room.most_active_project(cx) {
3887                return Some(join_remote_project(project, host, app_state.clone(), cx));
3888            }
3889
3890            None
3891        })?;
3892        if let Some(task) = task {
3893            task.await?;
3894        }
3895        return anyhow::Ok(true);
3896    }
3897
3898    if should_prompt {
3899        if let Some(workspace) = requesting_window {
3900            let answer  = workspace.update(cx, |_, cx| {
3901                cx.prompt(
3902                    PromptLevel::Warning,
3903                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
3904                    &["Yes, Join Channel", "Cancel"],
3905                )
3906            })?.await;
3907
3908            if answer == Ok(1) {
3909                return Ok(false);
3910            }
3911        } else {
3912            return Ok(false); // unreachable!() hopefully
3913        }
3914    }
3915
3916    let client = cx.update(|cx| active_call.read(cx).client())?;
3917
3918    let mut client_status = client.status();
3919
3920    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
3921    'outer: loop {
3922        let Some(status) = client_status.recv().await else {
3923            return Err(anyhow!("error connecting"));
3924        };
3925
3926        match status {
3927            Status::Connecting
3928            | Status::Authenticating
3929            | Status::Reconnecting
3930            | Status::Reauthenticating => continue,
3931            Status::Connected { .. } => break 'outer,
3932            Status::SignedOut => return Err(anyhow!("not signed in")),
3933            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
3934            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
3935                return Err(anyhow!("zed is offline"))
3936            }
3937        }
3938    }
3939
3940    let room = active_call
3941        .update(cx, |active_call, cx| {
3942            active_call.join_channel(channel_id, cx)
3943        })?
3944        .await?;
3945
3946    let Some(room) = room else {
3947        return anyhow::Ok(true);
3948    };
3949
3950    room.update(cx, |room, _| room.room_update_completed())?
3951        .await;
3952
3953    let task = room.update(cx, |room, cx| {
3954        if let Some((project, host)) = room.most_active_project(cx) {
3955            return Some(join_remote_project(project, host, app_state.clone(), cx));
3956        }
3957
3958        None
3959    })?;
3960    if let Some(task) = task {
3961        task.await?;
3962        return anyhow::Ok(true);
3963    }
3964    anyhow::Ok(false)
3965}
3966
3967pub fn join_channel(
3968    channel_id: u64,
3969    app_state: Arc<AppState>,
3970    requesting_window: Option<WindowHandle<Workspace>>,
3971    cx: &mut AppContext,
3972) -> Task<Result<()>> {
3973    let active_call = ActiveCall::global(cx);
3974    cx.spawn(|mut cx| async move {
3975        let result = join_channel_internal(
3976            channel_id,
3977            &app_state,
3978            requesting_window,
3979            &active_call,
3980            &mut cx,
3981        )
3982        .await;
3983
3984        // join channel succeeded, and opened a window
3985        if matches!(result, Ok(true)) {
3986            return anyhow::Ok(());
3987        }
3988
3989        if requesting_window.is_some() {
3990            return anyhow::Ok(());
3991        }
3992
3993        // find an existing workspace to focus and show call controls
3994        let mut active_window = activate_any_workspace_window(&mut cx);
3995        if active_window.is_none() {
3996            // no open workspaces, make one to show the error in (blergh)
3997            cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))?
3998                .await?;
3999        }
4000
4001        active_window = activate_any_workspace_window(&mut cx);
4002        let Some(active_window) = active_window else {
4003            return anyhow::Ok(());
4004        };
4005
4006        if let Err(err) = result {
4007            active_window
4008                .update(&mut cx, |_, cx| {
4009                    cx.prompt(
4010                        PromptLevel::Critical,
4011                        &format!("Failed to join channel: {}", err),
4012                        &["Ok"],
4013                    )
4014                })?
4015                .await
4016                .ok();
4017        }
4018
4019        // return ok, we showed the error to the user.
4020        return anyhow::Ok(());
4021    })
4022}
4023
4024pub async fn get_any_active_workspace(
4025    app_state: Arc<AppState>,
4026    mut cx: AsyncAppContext,
4027) -> anyhow::Result<WindowHandle<Workspace>> {
4028    // find an existing workspace to focus and show call controls
4029    let active_window = activate_any_workspace_window(&mut cx);
4030    if active_window.is_none() {
4031        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4032            .await?;
4033    }
4034    activate_any_workspace_window(&mut cx)
4035        .context("could not open zed")?
4036        .downcast::<Workspace>()
4037        .context("could not open zed workspace window")
4038}
4039
4040fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4041    cx.update(|cx| {
4042        for window in cx.windows() {
4043            let is_workspace = window.downcast::<Workspace>().is_some();
4044            if is_workspace {
4045                window.update(cx, |_, cx| cx.activate_window()).ok();
4046                return Some(window);
4047            }
4048        }
4049        None
4050    })
4051    .ok()
4052    .flatten()
4053}
4054
4055#[allow(clippy::type_complexity)]
4056pub fn open_paths(
4057    abs_paths: &[PathBuf],
4058    app_state: &Arc<AppState>,
4059    requesting_window: Option<WindowHandle<Workspace>>,
4060    cx: &mut AppContext,
4061) -> Task<
4062    anyhow::Result<(
4063        WindowHandle<Workspace>,
4064        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4065    )>,
4066> {
4067    let app_state = app_state.clone();
4068    let abs_paths = abs_paths.to_vec();
4069    // Open paths in existing workspace if possible
4070    let existing = activate_workspace_for_project(cx, {
4071        let abs_paths = abs_paths.clone();
4072        move |project, cx| project.contains_paths(&abs_paths, cx)
4073    });
4074    cx.spawn(move |mut cx| async move {
4075        if let Some(existing) = existing {
4076            Ok((
4077                existing.clone(),
4078                existing
4079                    .update(&mut cx, |workspace, cx| {
4080                        workspace.open_paths(abs_paths, true, cx)
4081                    })?
4082                    .await,
4083            ))
4084        } else {
4085            cx.update(move |cx| {
4086                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4087            })?
4088            .await
4089        }
4090    })
4091}
4092
4093pub fn open_new(
4094    app_state: &Arc<AppState>,
4095    cx: &mut AppContext,
4096    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4097) -> Task<()> {
4098    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4099    cx.spawn(|mut cx| async move {
4100        if let Some((workspace, opened_paths)) = task.await.log_err() {
4101            workspace
4102                .update(&mut cx, |workspace, cx| {
4103                    if opened_paths.is_empty() {
4104                        init(workspace, cx)
4105                    }
4106                })
4107                .log_err();
4108        }
4109    })
4110}
4111
4112pub fn create_and_open_local_file(
4113    path: &'static Path,
4114    cx: &mut ViewContext<Workspace>,
4115    default_content: impl 'static + Send + FnOnce() -> Rope,
4116) -> Task<Result<Box<dyn ItemHandle>>> {
4117    cx.spawn(|workspace, mut cx| async move {
4118        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4119        if !fs.is_file(path).await {
4120            fs.create_file(path, Default::default()).await?;
4121            fs.save(path, &default_content(), Default::default())
4122                .await?;
4123        }
4124
4125        let mut items = workspace
4126            .update(&mut cx, |workspace, cx| {
4127                workspace.with_local_workspace(cx, |workspace, cx| {
4128                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4129                })
4130            })?
4131            .await?
4132            .await;
4133
4134        let item = items.pop().flatten();
4135        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4136    })
4137}
4138
4139pub fn join_remote_project(
4140    project_id: u64,
4141    follow_user_id: u64,
4142    app_state: Arc<AppState>,
4143    cx: &mut AppContext,
4144) -> Task<Result<()>> {
4145    let windows = cx.windows();
4146    cx.spawn(|mut cx| async move {
4147        let existing_workspace = windows.into_iter().find_map(|window| {
4148            window.downcast::<Workspace>().and_then(|window| {
4149                window
4150                    .update(&mut cx, |workspace, cx| {
4151                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4152                            Some(window)
4153                        } else {
4154                            None
4155                        }
4156                    })
4157                    .unwrap_or(None)
4158            })
4159        });
4160
4161        let workspace = if let Some(existing_workspace) = existing_workspace {
4162            existing_workspace
4163        } else {
4164            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4165            let room = active_call
4166                .read_with(&cx, |call, _| call.room().cloned())?
4167                .ok_or_else(|| anyhow!("not in a call"))?;
4168            let project = room
4169                .update(&mut cx, |room, cx| {
4170                    room.join_project(
4171                        project_id,
4172                        app_state.languages.clone(),
4173                        app_state.fs.clone(),
4174                        cx,
4175                    )
4176                })?
4177                .await?;
4178
4179            let window_bounds_override = window_bounds_env_override(&cx);
4180            cx.update(|cx| {
4181                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4182                cx.open_window(options, |cx| {
4183                    cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4184                })
4185            })?
4186        };
4187
4188        workspace.update(&mut cx, |workspace, cx| {
4189            cx.activate(true);
4190            cx.activate_window();
4191
4192            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4193                let follow_peer_id = room
4194                    .read(cx)
4195                    .remote_participants()
4196                    .iter()
4197                    .find(|(_, participant)| participant.user.id == follow_user_id)
4198                    .map(|(_, p)| p.peer_id)
4199                    .or_else(|| {
4200                        // If we couldn't follow the given user, follow the host instead.
4201                        let collaborator = workspace
4202                            .project()
4203                            .read(cx)
4204                            .collaborators()
4205                            .values()
4206                            .find(|collaborator| collaborator.replica_id == 0)?;
4207                        Some(collaborator.peer_id)
4208                    });
4209
4210                if let Some(follow_peer_id) = follow_peer_id {
4211                    workspace.follow(follow_peer_id, cx);
4212                }
4213            }
4214        })?;
4215
4216        anyhow::Ok(())
4217    })
4218}
4219
4220pub fn restart(_: &Restart, cx: &mut AppContext) {
4221    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4222    let mut workspace_windows = cx
4223        .windows()
4224        .into_iter()
4225        .filter_map(|window| window.downcast::<Workspace>())
4226        .collect::<Vec<_>>();
4227
4228    // If multiple windows have unsaved changes, and need a save prompt,
4229    // prompt in the active window before switching to a different window.
4230    workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4231
4232    let mut prompt = None;
4233    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4234        prompt = window
4235            .update(cx, |_, cx| {
4236                cx.prompt(
4237                    PromptLevel::Info,
4238                    "Are you sure you want to restart?",
4239                    &["Restart", "Cancel"],
4240                )
4241            })
4242            .ok();
4243    }
4244
4245    cx.spawn(|mut cx| async move {
4246        if let Some(prompt) = prompt {
4247            let answer = prompt.await?;
4248            if answer != 0 {
4249                return Ok(());
4250            }
4251        }
4252
4253        // If the user cancels any save prompt, then keep the app open.
4254        for window in workspace_windows {
4255            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4256                workspace.prepare_to_close(true, cx)
4257            }) {
4258                if !should_close.await? {
4259                    return Ok(());
4260                }
4261            }
4262        }
4263
4264        cx.update(|cx| cx.restart())
4265    })
4266    .detach_and_log_err(cx);
4267}
4268
4269fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4270    let mut parts = value.split(',');
4271    let x: usize = parts.next()?.parse().ok()?;
4272    let y: usize = parts.next()?.parse().ok()?;
4273    Some(point((x as f64).into(), (y as f64).into()))
4274}
4275
4276fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4277    let mut parts = value.split(',');
4278    let width: usize = parts.next()?.parse().ok()?;
4279    let height: usize = parts.next()?.parse().ok()?;
4280    Some(size((width as f64).into(), (height as f64).into()))
4281}
4282
4283struct DisconnectedOverlay;
4284
4285impl Element for DisconnectedOverlay {
4286    type State = AnyElement;
4287
4288    fn request_layout(
4289        &mut self,
4290        _: Option<Self::State>,
4291        cx: &mut WindowContext,
4292    ) -> (LayoutId, Self::State) {
4293        let mut background = cx.theme().colors().elevated_surface_background;
4294        background.fade_out(0.2);
4295        let mut overlay = div()
4296            .bg(background)
4297            .absolute()
4298            .left_0()
4299            .top_0()
4300            .size_full()
4301            .flex()
4302            .items_center()
4303            .justify_center()
4304            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4305            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4306            .child(Label::new(
4307                "Your connection to the remote project has been lost.",
4308            ))
4309            .into_any();
4310        (overlay.request_layout(cx), overlay)
4311    }
4312
4313    fn paint(&mut self, bounds: Bounds<Pixels>, overlay: &mut Self::State, cx: &mut WindowContext) {
4314        cx.with_z_index(u8::MAX, |cx| {
4315            cx.add_opaque_layer(bounds);
4316            overlay.paint(cx);
4317        })
4318    }
4319}
4320
4321impl IntoElement for DisconnectedOverlay {
4322    type Element = Self;
4323
4324    fn element_id(&self) -> Option<ui::prelude::ElementId> {
4325        None
4326    }
4327
4328    fn into_element(self) -> Self::Element {
4329        self
4330    }
4331}
4332
4333#[cfg(test)]
4334mod tests {
4335    use std::{cell::RefCell, rc::Rc};
4336
4337    use super::*;
4338    use crate::item::{
4339        test::{TestItem, TestProjectItem},
4340        ItemEvent,
4341    };
4342    use fs::FakeFs;
4343    use gpui::TestAppContext;
4344    use project::{Project, ProjectEntryId};
4345    use serde_json::json;
4346    use settings::SettingsStore;
4347
4348    #[gpui::test]
4349    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4350        init_test(cx);
4351
4352        let fs = FakeFs::new(cx.executor());
4353        let project = Project::test(fs, [], cx).await;
4354        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4355
4356        // Adding an item with no ambiguity renders the tab without detail.
4357        let item1 = cx.new_view(|cx| {
4358            let mut item = TestItem::new(cx);
4359            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4360            item
4361        });
4362        workspace.update(cx, |workspace, cx| {
4363            workspace.add_item(Box::new(item1.clone()), cx);
4364        });
4365        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4366
4367        // Adding an item that creates ambiguity increases the level of detail on
4368        // both tabs.
4369        let item2 = cx.new_view(|cx| {
4370            let mut item = TestItem::new(cx);
4371            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4372            item
4373        });
4374        workspace.update(cx, |workspace, cx| {
4375            workspace.add_item(Box::new(item2.clone()), cx);
4376        });
4377        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4378        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4379
4380        // Adding an item that creates ambiguity increases the level of detail only
4381        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4382        // we stop at the highest detail available.
4383        let item3 = cx.new_view(|cx| {
4384            let mut item = TestItem::new(cx);
4385            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4386            item
4387        });
4388        workspace.update(cx, |workspace, cx| {
4389            workspace.add_item(Box::new(item3.clone()), cx);
4390        });
4391        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4392        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4393        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4394    }
4395
4396    #[gpui::test]
4397    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4398        init_test(cx);
4399
4400        let fs = FakeFs::new(cx.executor());
4401        fs.insert_tree(
4402            "/root1",
4403            json!({
4404                "one.txt": "",
4405                "two.txt": "",
4406            }),
4407        )
4408        .await;
4409        fs.insert_tree(
4410            "/root2",
4411            json!({
4412                "three.txt": "",
4413            }),
4414        )
4415        .await;
4416
4417        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4418        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4419        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4420        let worktree_id = project.update(cx, |project, cx| {
4421            project.worktrees().next().unwrap().read(cx).id()
4422        });
4423
4424        let item1 = cx.new_view(|cx| {
4425            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4426        });
4427        let item2 = cx.new_view(|cx| {
4428            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4429        });
4430
4431        // Add an item to an empty pane
4432        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4433        project.update(cx, |project, cx| {
4434            assert_eq!(
4435                project.active_entry(),
4436                project
4437                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4438                    .map(|e| e.id)
4439            );
4440        });
4441        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4442
4443        // Add a second item to a non-empty pane
4444        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4445        assert_eq!(cx.window_title().as_deref(), Some("two.txt β€” root1"));
4446        project.update(cx, |project, cx| {
4447            assert_eq!(
4448                project.active_entry(),
4449                project
4450                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4451                    .map(|e| e.id)
4452            );
4453        });
4454
4455        // Close the active item
4456        pane.update(cx, |pane, cx| {
4457            pane.close_active_item(&Default::default(), cx).unwrap()
4458        })
4459        .await
4460        .unwrap();
4461        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
4462        project.update(cx, |project, cx| {
4463            assert_eq!(
4464                project.active_entry(),
4465                project
4466                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4467                    .map(|e| e.id)
4468            );
4469        });
4470
4471        // Add a project folder
4472        project
4473            .update(cx, |project, cx| {
4474                project.find_or_create_local_worktree("/root2", true, cx)
4475            })
4476            .await
4477            .unwrap();
4478        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1, root2"));
4479
4480        // Remove a project folder
4481        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4482        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root2"));
4483    }
4484
4485    #[gpui::test]
4486    async fn test_close_window(cx: &mut TestAppContext) {
4487        init_test(cx);
4488
4489        let fs = FakeFs::new(cx.executor());
4490        fs.insert_tree("/root", json!({ "one": "" })).await;
4491
4492        let project = Project::test(fs, ["root".as_ref()], cx).await;
4493        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4494
4495        // When there are no dirty items, there's nothing to do.
4496        let item1 = cx.new_view(|cx| TestItem::new(cx));
4497        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4498        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4499        assert!(task.await.unwrap());
4500
4501        // When there are dirty untitled items, prompt to save each one. If the user
4502        // cancels any prompt, then abort.
4503        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
4504        let item3 = cx.new_view(|cx| {
4505            TestItem::new(cx)
4506                .with_dirty(true)
4507                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4508        });
4509        workspace.update(cx, |w, cx| {
4510            w.add_item(Box::new(item2.clone()), cx);
4511            w.add_item(Box::new(item3.clone()), cx);
4512        });
4513        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4514        cx.executor().run_until_parked();
4515        cx.simulate_prompt_answer(2); // cancel save all
4516        cx.executor().run_until_parked();
4517        cx.simulate_prompt_answer(2); // cancel save all
4518        cx.executor().run_until_parked();
4519        assert!(!cx.has_pending_prompt());
4520        assert!(!task.await.unwrap());
4521    }
4522
4523    #[gpui::test]
4524    async fn test_close_pane_items(cx: &mut TestAppContext) {
4525        init_test(cx);
4526
4527        let fs = FakeFs::new(cx.executor());
4528
4529        let project = Project::test(fs, None, cx).await;
4530        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4531
4532        let item1 = cx.new_view(|cx| {
4533            TestItem::new(cx)
4534                .with_dirty(true)
4535                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4536        });
4537        let item2 = cx.new_view(|cx| {
4538            TestItem::new(cx)
4539                .with_dirty(true)
4540                .with_conflict(true)
4541                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4542        });
4543        let item3 = cx.new_view(|cx| {
4544            TestItem::new(cx)
4545                .with_dirty(true)
4546                .with_conflict(true)
4547                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4548        });
4549        let item4 = cx.new_view(|cx| {
4550            TestItem::new(cx)
4551                .with_dirty(true)
4552                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4553        });
4554        let pane = workspace.update(cx, |workspace, cx| {
4555            workspace.add_item(Box::new(item1.clone()), cx);
4556            workspace.add_item(Box::new(item2.clone()), cx);
4557            workspace.add_item(Box::new(item3.clone()), cx);
4558            workspace.add_item(Box::new(item4.clone()), cx);
4559            workspace.active_pane().clone()
4560        });
4561
4562        let close_items = pane.update(cx, |pane, cx| {
4563            pane.activate_item(1, true, true, cx);
4564            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4565            let item1_id = item1.item_id();
4566            let item3_id = item3.item_id();
4567            let item4_id = item4.item_id();
4568            pane.close_items(cx, SaveIntent::Close, move |id| {
4569                [item1_id, item3_id, item4_id].contains(&id)
4570            })
4571        });
4572        cx.executor().run_until_parked();
4573
4574        assert!(cx.has_pending_prompt());
4575        // Ignore "Save all" prompt
4576        cx.simulate_prompt_answer(2);
4577        cx.executor().run_until_parked();
4578        // There's a prompt to save item 1.
4579        pane.update(cx, |pane, _| {
4580            assert_eq!(pane.items_len(), 4);
4581            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4582        });
4583        // Confirm saving item 1.
4584        cx.simulate_prompt_answer(0);
4585        cx.executor().run_until_parked();
4586
4587        // Item 1 is saved. There's a prompt to save item 3.
4588        pane.update(cx, |pane, cx| {
4589            assert_eq!(item1.read(cx).save_count, 1);
4590            assert_eq!(item1.read(cx).save_as_count, 0);
4591            assert_eq!(item1.read(cx).reload_count, 0);
4592            assert_eq!(pane.items_len(), 3);
4593            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4594        });
4595        assert!(cx.has_pending_prompt());
4596
4597        // Cancel saving item 3.
4598        cx.simulate_prompt_answer(1);
4599        cx.executor().run_until_parked();
4600
4601        // Item 3 is reloaded. There's a prompt to save item 4.
4602        pane.update(cx, |pane, cx| {
4603            assert_eq!(item3.read(cx).save_count, 0);
4604            assert_eq!(item3.read(cx).save_as_count, 0);
4605            assert_eq!(item3.read(cx).reload_count, 1);
4606            assert_eq!(pane.items_len(), 2);
4607            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4608        });
4609        assert!(cx.has_pending_prompt());
4610
4611        // Confirm saving item 4.
4612        cx.simulate_prompt_answer(0);
4613        cx.executor().run_until_parked();
4614
4615        // There's a prompt for a path for item 4.
4616        cx.simulate_new_path_selection(|_| Some(Default::default()));
4617        close_items.await.unwrap();
4618
4619        // The requested items are closed.
4620        pane.update(cx, |pane, cx| {
4621            assert_eq!(item4.read(cx).save_count, 0);
4622            assert_eq!(item4.read(cx).save_as_count, 1);
4623            assert_eq!(item4.read(cx).reload_count, 0);
4624            assert_eq!(pane.items_len(), 1);
4625            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4626        });
4627    }
4628
4629    #[gpui::test]
4630    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4631        init_test(cx);
4632
4633        let fs = FakeFs::new(cx.executor());
4634        let project = Project::test(fs, [], cx).await;
4635        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4636
4637        // Create several workspace items with single project entries, and two
4638        // workspace items with multiple project entries.
4639        let single_entry_items = (0..=4)
4640            .map(|project_entry_id| {
4641                cx.new_view(|cx| {
4642                    TestItem::new(cx)
4643                        .with_dirty(true)
4644                        .with_project_items(&[TestProjectItem::new(
4645                            project_entry_id,
4646                            &format!("{project_entry_id}.txt"),
4647                            cx,
4648                        )])
4649                })
4650            })
4651            .collect::<Vec<_>>();
4652        let item_2_3 = cx.new_view(|cx| {
4653            TestItem::new(cx)
4654                .with_dirty(true)
4655                .with_singleton(false)
4656                .with_project_items(&[
4657                    single_entry_items[2].read(cx).project_items[0].clone(),
4658                    single_entry_items[3].read(cx).project_items[0].clone(),
4659                ])
4660        });
4661        let item_3_4 = cx.new_view(|cx| {
4662            TestItem::new(cx)
4663                .with_dirty(true)
4664                .with_singleton(false)
4665                .with_project_items(&[
4666                    single_entry_items[3].read(cx).project_items[0].clone(),
4667                    single_entry_items[4].read(cx).project_items[0].clone(),
4668                ])
4669        });
4670
4671        // Create two panes that contain the following project entries:
4672        //   left pane:
4673        //     multi-entry items:   (2, 3)
4674        //     single-entry items:  0, 1, 2, 3, 4
4675        //   right pane:
4676        //     single-entry items:  1
4677        //     multi-entry items:   (3, 4)
4678        let left_pane = workspace.update(cx, |workspace, cx| {
4679            let left_pane = workspace.active_pane().clone();
4680            workspace.add_item(Box::new(item_2_3.clone()), cx);
4681            for item in single_entry_items {
4682                workspace.add_item(Box::new(item), cx);
4683            }
4684            left_pane.update(cx, |pane, cx| {
4685                pane.activate_item(2, true, true, cx);
4686            });
4687
4688            let right_pane = workspace
4689                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4690                .unwrap();
4691
4692            right_pane.update(cx, |pane, cx| {
4693                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4694            });
4695
4696            left_pane
4697        });
4698
4699        cx.focus_view(&left_pane);
4700
4701        // When closing all of the items in the left pane, we should be prompted twice:
4702        // once for project entry 0, and once for project entry 2. Project entries 1,
4703        // 3, and 4 are all still open in the other paten. After those two
4704        // prompts, the task should complete.
4705
4706        let close = left_pane.update(cx, |pane, cx| {
4707            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4708        });
4709        cx.executor().run_until_parked();
4710
4711        // Discard "Save all" prompt
4712        cx.simulate_prompt_answer(2);
4713
4714        cx.executor().run_until_parked();
4715        left_pane.update(cx, |pane, cx| {
4716            assert_eq!(
4717                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4718                &[ProjectEntryId::from_proto(0)]
4719            );
4720        });
4721        cx.simulate_prompt_answer(0);
4722
4723        cx.executor().run_until_parked();
4724        left_pane.update(cx, |pane, cx| {
4725            assert_eq!(
4726                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4727                &[ProjectEntryId::from_proto(2)]
4728            );
4729        });
4730        cx.simulate_prompt_answer(0);
4731
4732        cx.executor().run_until_parked();
4733        close.await.unwrap();
4734        left_pane.update(cx, |pane, _| {
4735            assert_eq!(pane.items_len(), 0);
4736        });
4737    }
4738
4739    #[gpui::test]
4740    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4741        init_test(cx);
4742
4743        let fs = FakeFs::new(cx.executor());
4744        let project = Project::test(fs, [], cx).await;
4745        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4746        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4747
4748        let item = cx.new_view(|cx| {
4749            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4750        });
4751        let item_id = item.entity_id();
4752        workspace.update(cx, |workspace, cx| {
4753            workspace.add_item(Box::new(item.clone()), cx);
4754        });
4755
4756        // Autosave on window change.
4757        item.update(cx, |item, cx| {
4758            cx.update_global(|settings: &mut SettingsStore, cx| {
4759                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4760                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4761                })
4762            });
4763            item.is_dirty = true;
4764        });
4765
4766        // Deactivating the window saves the file.
4767        cx.simulate_deactivation();
4768        cx.executor().run_until_parked();
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.simulate_activation();
4789        item.update(cx, |item, cx| {
4790            cx.focus_self();
4791            item.is_dirty = true;
4792        });
4793        cx.simulate_deactivation();
4794
4795        cx.executor().run_until_parked();
4796        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4797
4798        // Autosave after delay.
4799        item.update(cx, |item, cx| {
4800            cx.update_global(|settings: &mut SettingsStore, cx| {
4801                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4802                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4803                })
4804            });
4805            item.is_dirty = true;
4806            cx.emit(ItemEvent::Edit);
4807        });
4808
4809        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4810        cx.executor().advance_clock(Duration::from_millis(250));
4811        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4812
4813        // After delay expires, the file is saved.
4814        cx.executor().advance_clock(Duration::from_millis(250));
4815        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4816
4817        // Autosave on focus change, ensuring closing the tab counts as such.
4818        item.update(cx, |item, cx| {
4819            cx.update_global(|settings: &mut SettingsStore, cx| {
4820                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4821                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4822                })
4823            });
4824            item.is_dirty = true;
4825        });
4826
4827        pane.update(cx, |pane, cx| {
4828            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4829        })
4830        .await
4831        .unwrap();
4832        assert!(!cx.has_pending_prompt());
4833        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4834
4835        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4836        workspace.update(cx, |workspace, cx| {
4837            workspace.add_item(Box::new(item.clone()), cx);
4838        });
4839        item.update(cx, |item, cx| {
4840            item.project_items[0].update(cx, |item, _| {
4841                item.entry_id = None;
4842            });
4843            item.is_dirty = true;
4844            cx.blur();
4845        });
4846        cx.run_until_parked();
4847        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4848
4849        // Ensure autosave is prevented for deleted files also when closing the buffer.
4850        let _close_items = pane.update(cx, |pane, cx| {
4851            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4852        });
4853        cx.run_until_parked();
4854        assert!(cx.has_pending_prompt());
4855        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4856    }
4857
4858    #[gpui::test]
4859    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4860        init_test(cx);
4861
4862        let fs = FakeFs::new(cx.executor());
4863
4864        let project = Project::test(fs, [], cx).await;
4865        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4866
4867        let item = cx.new_view(|cx| {
4868            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4869        });
4870        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4871        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4872        let toolbar_notify_count = Rc::new(RefCell::new(0));
4873
4874        workspace.update(cx, |workspace, cx| {
4875            workspace.add_item(Box::new(item.clone()), cx);
4876            let toolbar_notification_count = toolbar_notify_count.clone();
4877            cx.observe(&toolbar, move |_, _, _| {
4878                *toolbar_notification_count.borrow_mut() += 1
4879            })
4880            .detach();
4881        });
4882
4883        pane.update(cx, |pane, _| {
4884            assert!(!pane.can_navigate_backward());
4885            assert!(!pane.can_navigate_forward());
4886        });
4887
4888        item.update(cx, |item, cx| {
4889            item.set_state("one".to_string(), cx);
4890        });
4891
4892        // Toolbar must be notified to re-render the navigation buttons
4893        assert_eq!(*toolbar_notify_count.borrow(), 1);
4894
4895        pane.update(cx, |pane, _| {
4896            assert!(pane.can_navigate_backward());
4897            assert!(!pane.can_navigate_forward());
4898        });
4899
4900        workspace
4901            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4902            .await
4903            .unwrap();
4904
4905        assert_eq!(*toolbar_notify_count.borrow(), 2);
4906        pane.update(cx, |pane, _| {
4907            assert!(!pane.can_navigate_backward());
4908            assert!(pane.can_navigate_forward());
4909        });
4910    }
4911
4912    // #[gpui::test]
4913    // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4914    //     init_test(cx);
4915    //     let fs = FakeFs::new(cx.executor());
4916
4917    //     let project = Project::test(fs, [], cx).await;
4918    //     let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4919
4920    //     let panel = workspace.update(cx, |workspace, cx| {
4921    //         let panel = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx));
4922    //         workspace.add_panel(panel.clone(), cx);
4923
4924    //         workspace
4925    //             .right_dock()
4926    //             .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4927
4928    //         panel
4929    //     });
4930
4931    //     let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4932    //     pane.update(cx, |pane, cx| {
4933    //         let item = cx.build_view(|cx| TestItem::new(cx));
4934    //         pane.add_item(Box::new(item), true, true, None, cx);
4935    //     });
4936
4937    //     // Transfer focus from center to panel
4938    //     workspace.update(cx, |workspace, cx| {
4939    //         workspace.toggle_panel_focus::<TestPanel>(cx);
4940    //     });
4941
4942    //     workspace.update(cx, |workspace, cx| {
4943    //         assert!(workspace.right_dock().read(cx).is_open());
4944    //         assert!(!panel.is_zoomed(cx));
4945    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4946    //     });
4947
4948    //     // Transfer focus from panel to center
4949    //     workspace.update(cx, |workspace, cx| {
4950    //         workspace.toggle_panel_focus::<TestPanel>(cx);
4951    //     });
4952
4953    //     workspace.update(cx, |workspace, cx| {
4954    //         assert!(workspace.right_dock().read(cx).is_open());
4955    //         assert!(!panel.is_zoomed(cx));
4956    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4957    //     });
4958
4959    //     // Close the dock
4960    //     workspace.update(cx, |workspace, cx| {
4961    //         workspace.toggle_dock(DockPosition::Right, cx);
4962    //     });
4963
4964    //     workspace.update(cx, |workspace, cx| {
4965    //         assert!(!workspace.right_dock().read(cx).is_open());
4966    //         assert!(!panel.is_zoomed(cx));
4967    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4968    //     });
4969
4970    //     // Open the dock
4971    //     workspace.update(cx, |workspace, cx| {
4972    //         workspace.toggle_dock(DockPosition::Right, cx);
4973    //     });
4974
4975    //     workspace.update(cx, |workspace, cx| {
4976    //         assert!(workspace.right_dock().read(cx).is_open());
4977    //         assert!(!panel.is_zoomed(cx));
4978    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4979    //     });
4980
4981    //     // Focus and zoom panel
4982    //     panel.update(cx, |panel, cx| {
4983    //         cx.focus_self();
4984    //         panel.set_zoomed(true, cx)
4985    //     });
4986
4987    //     workspace.update(cx, |workspace, cx| {
4988    //         assert!(workspace.right_dock().read(cx).is_open());
4989    //         assert!(panel.is_zoomed(cx));
4990    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4991    //     });
4992
4993    //     // Transfer focus to the center closes the dock
4994    //     workspace.update(cx, |workspace, cx| {
4995    //         workspace.toggle_panel_focus::<TestPanel>(cx);
4996    //     });
4997
4998    //     workspace.update(cx, |workspace, cx| {
4999    //         assert!(!workspace.right_dock().read(cx).is_open());
5000    //         assert!(panel.is_zoomed(cx));
5001    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5002    //     });
5003
5004    //     // Transferring focus back to the panel keeps it zoomed
5005    //     workspace.update(cx, |workspace, cx| {
5006    //         workspace.toggle_panel_focus::<TestPanel>(cx);
5007    //     });
5008
5009    //     workspace.update(cx, |workspace, cx| {
5010    //         assert!(workspace.right_dock().read(cx).is_open());
5011    //         assert!(panel.is_zoomed(cx));
5012    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5013    //     });
5014
5015    //     // Close the dock while it is zoomed
5016    //     workspace.update(cx, |workspace, cx| {
5017    //         workspace.toggle_dock(DockPosition::Right, cx)
5018    //     });
5019
5020    //     workspace.update(cx, |workspace, cx| {
5021    //         assert!(!workspace.right_dock().read(cx).is_open());
5022    //         assert!(panel.is_zoomed(cx));
5023    //         assert!(workspace.zoomed.is_none());
5024    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5025    //     });
5026
5027    //     // Opening the dock, when it's zoomed, retains focus
5028    //     workspace.update(cx, |workspace, cx| {
5029    //         workspace.toggle_dock(DockPosition::Right, cx)
5030    //     });
5031
5032    //     workspace.update(cx, |workspace, cx| {
5033    //         assert!(workspace.right_dock().read(cx).is_open());
5034    //         assert!(panel.is_zoomed(cx));
5035    //         assert!(workspace.zoomed.is_some());
5036    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5037    //     });
5038
5039    //     // Unzoom and close the panel, zoom the active pane.
5040    //     panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5041    //     workspace.update(cx, |workspace, cx| {
5042    //         workspace.toggle_dock(DockPosition::Right, cx)
5043    //     });
5044    //     pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5045
5046    //     // Opening a dock unzooms the pane.
5047    //     workspace.update(cx, |workspace, cx| {
5048    //         workspace.toggle_dock(DockPosition::Right, cx)
5049    //     });
5050    //     workspace.update(cx, |workspace, cx| {
5051    //         let pane = pane.read(cx);
5052    //         assert!(!pane.is_zoomed());
5053    //         assert!(!pane.focus_handle(cx).is_focused(cx));
5054    //         assert!(workspace.right_dock().read(cx).is_open());
5055    //         assert!(workspace.zoomed.is_none());
5056    //     });
5057    // }
5058
5059    // #[gpui::test]
5060    // async fn test_panels(cx: &mut gpui::TestAppContext) {
5061    //     init_test(cx);
5062    //     let fs = FakeFs::new(cx.executor());
5063
5064    //     let project = Project::test(fs, [], cx).await;
5065    //     let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5066
5067    //     let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5068    //         // Add panel_1 on the left, panel_2 on the right.
5069    //         let panel_1 = cx.build_view(|cx| TestPanel::new(DockPosition::Left, cx));
5070    //         workspace.add_panel(panel_1.clone(), cx);
5071    //         workspace
5072    //             .left_dock()
5073    //             .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5074    //         let panel_2 = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx));
5075    //         workspace.add_panel(panel_2.clone(), cx);
5076    //         workspace
5077    //             .right_dock()
5078    //             .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5079
5080    //         let left_dock = workspace.left_dock();
5081    //         assert_eq!(
5082    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5083    //             panel_1.panel_id()
5084    //         );
5085    //         assert_eq!(
5086    //             left_dock.read(cx).active_panel_size(cx).unwrap(),
5087    //             panel_1.size(cx)
5088    //         );
5089
5090    //         left_dock.update(cx, |left_dock, cx| {
5091    //             left_dock.resize_active_panel(Some(1337.), cx)
5092    //         });
5093    //         assert_eq!(
5094    //             workspace
5095    //                 .right_dock()
5096    //                 .read(cx)
5097    //                 .visible_panel()
5098    //                 .unwrap()
5099    //                 .panel_id(),
5100    //             panel_2.panel_id(),
5101    //         );
5102
5103    //         (panel_1, panel_2)
5104    //     });
5105
5106    //     // Move panel_1 to the right
5107    //     panel_1.update(cx, |panel_1, cx| {
5108    //         panel_1.set_position(DockPosition::Right, cx)
5109    //     });
5110
5111    //     workspace.update(cx, |workspace, cx| {
5112    //         // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5113    //         // Since it was the only panel on the left, the left dock should now be closed.
5114    //         assert!(!workspace.left_dock().read(cx).is_open());
5115    //         assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5116    //         let right_dock = workspace.right_dock();
5117    //         assert_eq!(
5118    //             right_dock.read(cx).visible_panel().unwrap().panel_id(),
5119    //             panel_1.panel_id()
5120    //         );
5121    //         assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5122
5123    //         // Now we move panel_2Β to the left
5124    //         panel_2.set_position(DockPosition::Left, cx);
5125    //     });
5126
5127    //     workspace.update(cx, |workspace, cx| {
5128    //         // Since panel_2 was not visible on the right, we don't open the left dock.
5129    //         assert!(!workspace.left_dock().read(cx).is_open());
5130    //         // And the right dock is unaffected in it's displaying of panel_1
5131    //         assert!(workspace.right_dock().read(cx).is_open());
5132    //         assert_eq!(
5133    //             workspace
5134    //                 .right_dock()
5135    //                 .read(cx)
5136    //                 .visible_panel()
5137    //                 .unwrap()
5138    //                 .panel_id(),
5139    //             panel_1.panel_id(),
5140    //         );
5141    //     });
5142
5143    //     // Move panel_1 back to the left
5144    //     panel_1.update(cx, |panel_1, cx| {
5145    //         panel_1.set_position(DockPosition::Left, cx)
5146    //     });
5147
5148    //     workspace.update(cx, |workspace, cx| {
5149    //         // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5150    //         let left_dock = workspace.left_dock();
5151    //         assert!(left_dock.read(cx).is_open());
5152    //         assert_eq!(
5153    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5154    //             panel_1.panel_id()
5155    //         );
5156    //         assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5157    //         // And right the dock should be closed as it no longer has any panels.
5158    //         assert!(!workspace.right_dock().read(cx).is_open());
5159
5160    //         // Now we move panel_1 to the bottom
5161    //         panel_1.set_position(DockPosition::Bottom, cx);
5162    //     });
5163
5164    //     workspace.update(cx, |workspace, cx| {
5165    //         // Since panel_1 was visible on the left, we close the left dock.
5166    //         assert!(!workspace.left_dock().read(cx).is_open());
5167    //         // The bottom dock is sized based on the panel's default size,
5168    //         // since the panel orientation changed from vertical to horizontal.
5169    //         let bottom_dock = workspace.bottom_dock();
5170    //         assert_eq!(
5171    //             bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5172    //             panel_1.size(cx),
5173    //         );
5174    //         // Close bottom dock and move panel_1 back to the left.
5175    //         bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5176    //         panel_1.set_position(DockPosition::Left, cx);
5177    //     });
5178
5179    //     // Emit activated event on panel 1
5180    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5181
5182    //     // Now the left dock is open and panel_1 is active and focused.
5183    //     workspace.update(cx, |workspace, cx| {
5184    //         let left_dock = workspace.left_dock();
5185    //         assert!(left_dock.read(cx).is_open());
5186    //         assert_eq!(
5187    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5188    //             panel_1.panel_id(),
5189    //         );
5190    //         assert!(panel_1.focus_handle(cx).is_focused(cx));
5191    //     });
5192
5193    //     // Emit closed event on panel 2, which is not active
5194    //     panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5195
5196    //     // Wo don't close the left dock, because panel_2 wasn't the active panel
5197    //     workspace.update(cx, |workspace, cx| {
5198    //         let left_dock = workspace.left_dock();
5199    //         assert!(left_dock.read(cx).is_open());
5200    //         assert_eq!(
5201    //             left_dock.read(cx).visible_panel().unwrap().panel_id(),
5202    //             panel_1.panel_id(),
5203    //         );
5204    //     });
5205
5206    //     // Emitting a ZoomIn event shows the panel as zoomed.
5207    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5208    //     workspace.update(cx, |workspace, _| {
5209    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5210    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5211    //     });
5212
5213    //     // Move panel to another dock while it is zoomed
5214    //     panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5215    //     workspace.update(cx, |workspace, _| {
5216    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5217
5218    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5219    //     });
5220
5221    //     // If focus is transferred to another view that's not a panel or another pane, we still show
5222    //     // the panel as zoomed.
5223    //     let other_focus_handle = cx.update(|cx| cx.focus_handle());
5224    //     cx.update(|cx| cx.focus(&other_focus_handle));
5225    //     workspace.update(cx, |workspace, _| {
5226    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5227    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5228    //     });
5229
5230    //     // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5231    //     workspace.update(cx, |_, cx| cx.focus_self());
5232    //     workspace.update(cx, |workspace, _| {
5233    //         assert_eq!(workspace.zoomed, None);
5234    //         assert_eq!(workspace.zoomed_position, None);
5235    //     });
5236
5237    //     // If focus is transferred again to another view that's not a panel or a pane, we won't
5238    //     // show the panel as zoomed because it wasn't zoomed before.
5239    //     cx.update(|cx| cx.focus(&other_focus_handle));
5240    //     workspace.update(cx, |workspace, _| {
5241    //         assert_eq!(workspace.zoomed, None);
5242    //         assert_eq!(workspace.zoomed_position, None);
5243    //     });
5244
5245    //     // When focus is transferred back to the panel, it is zoomed again.
5246    //     panel_1.update(cx, |_, cx| cx.focus_self());
5247    //     workspace.update(cx, |workspace, _| {
5248    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5249    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5250    //     });
5251
5252    //     // Emitting a ZoomOut event unzooms the panel.
5253    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5254    //     workspace.update(cx, |workspace, _| {
5255    //         assert_eq!(workspace.zoomed, None);
5256    //         assert_eq!(workspace.zoomed_position, None);
5257    //     });
5258
5259    //     // Emit closed event on panel 1, which is active
5260    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5261
5262    //     // Now the left dock is closed, because panel_1 was the active panel
5263    //     workspace.update(cx, |workspace, cx| {
5264    //         let right_dock = workspace.right_dock();
5265    //         assert!(!right_dock.read(cx).is_open());
5266    //     });
5267    // }
5268
5269    pub fn init_test(cx: &mut TestAppContext) {
5270        cx.update(|cx| {
5271            let settings_store = SettingsStore::test(cx);
5272            cx.set_global(settings_store);
5273            theme::init(theme::LoadThemes::JustBase, cx);
5274            language::init(cx);
5275            crate::init_settings(cx);
5276            Project::init_settings(cx);
5277        });
5278    }
5279}