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