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 if variant.is_none() {
2788                        Err(anyhow!(
2789                            "failed to construct view from leader (maybe from a different version of zed?)"
2790                        ))?;
2791                    }
2792                }
2793            }
2794
2795            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2796        }
2797
2798        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2799            let items = futures::future::try_join_all(item_tasks).await?;
2800            this.update(cx, |this, cx| {
2801                let state = this.follower_states.get_mut(&pane)?;
2802                for (id, item) in leader_view_ids.into_iter().zip(items) {
2803                    item.set_leader_peer_id(Some(leader_id), cx);
2804                    state.items_by_leader_view_id.insert(id, item);
2805                }
2806
2807                Some(())
2808            })?;
2809        }
2810        Ok(())
2811    }
2812
2813    fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
2814        let mut is_project_item = true;
2815        let mut update = proto::UpdateActiveView::default();
2816
2817        if let Some(item) = self.active_item(cx) {
2818            if item.focus_handle(cx).contains_focused(cx) {
2819                if let Some(item) = item.to_followable_item_handle(cx) {
2820                    is_project_item = item.is_project_item(cx);
2821                    update = proto::UpdateActiveView {
2822                        id: item
2823                            .remote_id(&self.app_state.client, cx)
2824                            .map(|id| id.to_proto()),
2825                        leader_id: self.leader_for_pane(&self.active_pane),
2826                    };
2827                }
2828            }
2829        }
2830
2831        if update.id != self.last_active_view_id {
2832            self.last_active_view_id = update.id.clone();
2833            self.update_followers(
2834                is_project_item,
2835                proto::update_followers::Variant::UpdateActiveView(update),
2836                cx,
2837            );
2838        }
2839    }
2840
2841    fn update_followers(
2842        &self,
2843        project_only: bool,
2844        update: proto::update_followers::Variant,
2845        cx: &mut WindowContext,
2846    ) -> Option<()> {
2847        // If this update only applies to for followers in the current project,
2848        // then skip it unless this project is shared. If it applies to all
2849        // followers, regardless of project, then set `project_id` to none,
2850        // indicating that it goes to all followers.
2851        let project_id = if project_only {
2852            Some(self.project.read(cx).remote_id()?)
2853        } else {
2854            None
2855        };
2856        self.app_state().workspace_store.update(cx, |store, cx| {
2857            store.update_followers(project_id, update, cx)
2858        })
2859    }
2860
2861    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
2862        self.follower_states.get(pane).map(|state| state.leader_id)
2863    }
2864
2865    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2866        cx.notify();
2867
2868        let call = self.active_call()?;
2869        let room = call.read(cx).room()?.read(cx);
2870        let participant = room.remote_participant_for_peer_id(leader_id)?;
2871        let mut items_to_activate = Vec::new();
2872
2873        let leader_in_this_app;
2874        let leader_in_this_project;
2875        match participant.location {
2876            call::ParticipantLocation::SharedProject { project_id } => {
2877                leader_in_this_app = true;
2878                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
2879            }
2880            call::ParticipantLocation::UnsharedProject => {
2881                leader_in_this_app = true;
2882                leader_in_this_project = false;
2883            }
2884            call::ParticipantLocation::External => {
2885                leader_in_this_app = false;
2886                leader_in_this_project = false;
2887            }
2888        };
2889
2890        for (pane, state) in &self.follower_states {
2891            if state.leader_id != leader_id {
2892                continue;
2893            }
2894            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
2895                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
2896                    if leader_in_this_project || !item.is_project_item(cx) {
2897                        items_to_activate.push((pane.clone(), item.boxed_clone()));
2898                    }
2899                }
2900                continue;
2901            }
2902
2903            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2904                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2905            }
2906        }
2907
2908        for (pane, item) in items_to_activate {
2909            let pane_was_focused = pane.read(cx).has_focus(cx);
2910            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2911                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2912            } else {
2913                pane.update(cx, |pane, cx| {
2914                    pane.add_item(item.boxed_clone(), false, false, None, cx)
2915                });
2916            }
2917
2918            if pane_was_focused {
2919                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2920            }
2921        }
2922
2923        None
2924    }
2925
2926    fn shared_screen_for_peer(
2927        &self,
2928        peer_id: PeerId,
2929        pane: &View<Pane>,
2930        cx: &mut WindowContext,
2931    ) -> Option<View<SharedScreen>> {
2932        let call = self.active_call()?;
2933        let room = call.read(cx).room()?.read(cx);
2934        let participant = room.remote_participant_for_peer_id(peer_id)?;
2935        let track = participant.video_tracks.values().next()?.clone();
2936        let user = participant.user.clone();
2937
2938        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2939            if item.read(cx).peer_id == peer_id {
2940                return Some(item);
2941            }
2942        }
2943
2944        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2945    }
2946
2947    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
2948        if cx.is_window_active() {
2949            self.update_active_view_for_followers(cx);
2950            cx.background_executor()
2951                .spawn(persistence::DB.update_timestamp(self.database_id()))
2952                .detach();
2953        } else {
2954            for pane in &self.panes {
2955                pane.update(cx, |pane, cx| {
2956                    if let Some(item) = pane.active_item() {
2957                        item.workspace_deactivated(cx);
2958                    }
2959                    if matches!(
2960                        WorkspaceSettings::get_global(cx).autosave,
2961                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2962                    ) {
2963                        for item in pane.items() {
2964                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2965                                .detach_and_log_err(cx);
2966                        }
2967                    }
2968                });
2969            }
2970        }
2971    }
2972
2973    fn active_call(&self) -> Option<&Model<ActiveCall>> {
2974        self.active_call.as_ref().map(|(call, _)| call)
2975    }
2976
2977    fn on_active_call_event(
2978        &mut self,
2979        _: Model<ActiveCall>,
2980        event: &call::room::Event,
2981        cx: &mut ViewContext<Self>,
2982    ) {
2983        match event {
2984            call::room::Event::ParticipantLocationChanged { participant_id }
2985            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2986                self.leader_updated(*participant_id, cx);
2987            }
2988            _ => {}
2989        }
2990    }
2991
2992    pub fn database_id(&self) -> WorkspaceId {
2993        self.database_id
2994    }
2995
2996    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2997        let project = self.project().read(cx);
2998
2999        if project.is_local() {
3000            Some(
3001                project
3002                    .visible_worktrees(cx)
3003                    .map(|worktree| worktree.read(cx).abs_path())
3004                    .collect::<Vec<_>>()
3005                    .into(),
3006            )
3007        } else {
3008            None
3009        }
3010    }
3011
3012    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3013        match member {
3014            Member::Axis(PaneAxis { members, .. }) => {
3015                for child in members.iter() {
3016                    self.remove_panes(child.clone(), cx)
3017                }
3018            }
3019            Member::Pane(pane) => {
3020                self.force_remove_pane(&pane, cx);
3021            }
3022        }
3023    }
3024
3025    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
3026        self.panes.retain(|p| p != pane);
3027        self.panes
3028            .last()
3029            .unwrap()
3030            .update(cx, |pane, cx| pane.focus(cx));
3031        if self.last_active_center_pane == Some(pane.downgrade()) {
3032            self.last_active_center_pane = None;
3033        }
3034        cx.notify();
3035    }
3036
3037    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3038        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3039            cx.background_executor()
3040                .timer(Duration::from_millis(100))
3041                .await;
3042            this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
3043                .log_err();
3044        }));
3045    }
3046
3047    fn serialize_workspace(&self, cx: &mut WindowContext) {
3048        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3049            let (items, active) = {
3050                let pane = pane_handle.read(cx);
3051                let active_item_id = pane.active_item().map(|item| item.item_id());
3052                (
3053                    pane.items()
3054                        .filter_map(|item_handle| {
3055                            Some(SerializedItem {
3056                                kind: Arc::from(item_handle.serialized_item_kind()?),
3057                                item_id: item_handle.item_id().as_u64(),
3058                                active: Some(item_handle.item_id()) == active_item_id,
3059                            })
3060                        })
3061                        .collect::<Vec<_>>(),
3062                    pane.has_focus(cx),
3063                )
3064            };
3065
3066            SerializedPane::new(items, active)
3067        }
3068
3069        fn build_serialized_pane_group(
3070            pane_group: &Member,
3071            cx: &WindowContext,
3072        ) -> SerializedPaneGroup {
3073            match pane_group {
3074                Member::Axis(PaneAxis {
3075                    axis,
3076                    members,
3077                    flexes,
3078                    bounding_boxes: _,
3079                }) => SerializedPaneGroup::Group {
3080                    axis: SerializedAxis(*axis),
3081                    children: members
3082                        .iter()
3083                        .map(|member| build_serialized_pane_group(member, cx))
3084                        .collect::<Vec<_>>(),
3085                    flexes: Some(flexes.lock().clone()),
3086                },
3087                Member::Pane(pane_handle) => {
3088                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
3089                }
3090            }
3091        }
3092
3093        fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
3094            let left_dock = this.left_dock.read(cx);
3095            let left_visible = left_dock.is_open();
3096            let left_active_panel = left_dock
3097                .visible_panel()
3098                .and_then(|panel| Some(panel.persistent_name().to_string()));
3099            let left_dock_zoom = left_dock
3100                .visible_panel()
3101                .map(|panel| panel.is_zoomed(cx))
3102                .unwrap_or(false);
3103
3104            let right_dock = this.right_dock.read(cx);
3105            let right_visible = right_dock.is_open();
3106            let right_active_panel = right_dock
3107                .visible_panel()
3108                .and_then(|panel| Some(panel.persistent_name().to_string()));
3109            let right_dock_zoom = right_dock
3110                .visible_panel()
3111                .map(|panel| panel.is_zoomed(cx))
3112                .unwrap_or(false);
3113
3114            let bottom_dock = this.bottom_dock.read(cx);
3115            let bottom_visible = bottom_dock.is_open();
3116            let bottom_active_panel = bottom_dock
3117                .visible_panel()
3118                .and_then(|panel| Some(panel.persistent_name().to_string()));
3119            let bottom_dock_zoom = bottom_dock
3120                .visible_panel()
3121                .map(|panel| panel.is_zoomed(cx))
3122                .unwrap_or(false);
3123
3124            DockStructure {
3125                left: DockData {
3126                    visible: left_visible,
3127                    active_panel: left_active_panel,
3128                    zoom: left_dock_zoom,
3129                },
3130                right: DockData {
3131                    visible: right_visible,
3132                    active_panel: right_active_panel,
3133                    zoom: right_dock_zoom,
3134                },
3135                bottom: DockData {
3136                    visible: bottom_visible,
3137                    active_panel: bottom_active_panel,
3138                    zoom: bottom_dock_zoom,
3139                },
3140            }
3141        }
3142
3143        if let Some(location) = self.location(cx) {
3144            // Load bearing special case:
3145            //  - with_local_workspace() relies on this to not have other stuff open
3146            //    when you open your log
3147            if !location.paths().is_empty() {
3148                let center_group = build_serialized_pane_group(&self.center.root, cx);
3149                let docks = build_serialized_docks(self, cx);
3150
3151                let serialized_workspace = SerializedWorkspace {
3152                    id: self.database_id,
3153                    location,
3154                    center_group,
3155                    bounds: Default::default(),
3156                    display: Default::default(),
3157                    docks,
3158                };
3159
3160                cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace))
3161                    .detach();
3162            }
3163        }
3164    }
3165
3166    pub(crate) fn load_workspace(
3167        serialized_workspace: SerializedWorkspace,
3168        paths_to_open: Vec<Option<ProjectPath>>,
3169        cx: &mut ViewContext<Workspace>,
3170    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3171        cx.spawn(|workspace, mut cx| async move {
3172            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3173
3174            let mut center_group = None;
3175            let mut center_items = None;
3176
3177            // Traverse the splits tree and add to things
3178            if let Some((group, active_pane, items)) = serialized_workspace
3179                .center_group
3180                .deserialize(
3181                    &project,
3182                    serialized_workspace.id,
3183                    workspace.clone(),
3184                    &mut cx,
3185                )
3186                .await
3187            {
3188                center_items = Some(items);
3189                center_group = Some((group, active_pane))
3190            }
3191
3192            let mut items_by_project_path = cx.update(|cx| {
3193                center_items
3194                    .unwrap_or_default()
3195                    .into_iter()
3196                    .filter_map(|item| {
3197                        let item = item?;
3198                        let project_path = item.project_path(cx)?;
3199                        Some((project_path, item))
3200                    })
3201                    .collect::<HashMap<_, _>>()
3202            })?;
3203
3204            let opened_items = paths_to_open
3205                .into_iter()
3206                .map(|path_to_open| {
3207                    path_to_open
3208                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3209                })
3210                .collect::<Vec<_>>();
3211
3212            // Remove old panes from workspace panes list
3213            workspace.update(&mut cx, |workspace, cx| {
3214                if let Some((center_group, active_pane)) = center_group {
3215                    workspace.remove_panes(workspace.center.root.clone(), cx);
3216
3217                    // Swap workspace center group
3218                    workspace.center = PaneGroup::with_root(center_group);
3219                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3220                    if let Some(active_pane) = active_pane {
3221                        workspace.active_pane = active_pane;
3222                        cx.focus_self();
3223                    } else {
3224                        workspace.active_pane = workspace.center.first_pane().clone();
3225                    }
3226                }
3227
3228                let docks = serialized_workspace.docks;
3229
3230                let right = docks.right.clone();
3231                workspace
3232                    .right_dock
3233                    .update(cx, |dock, _| dock.serialized_dock = Some(right));
3234                let left = docks.left.clone();
3235                workspace
3236                    .left_dock
3237                    .update(cx, |dock, _| dock.serialized_dock = Some(left));
3238                let bottom = docks.bottom.clone();
3239                workspace
3240                    .bottom_dock
3241                    .update(cx, |dock, _| dock.serialized_dock = Some(bottom));
3242
3243                cx.notify();
3244            })?;
3245
3246            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3247            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3248
3249            Ok(opened_items)
3250        })
3251    }
3252
3253    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3254        self.add_workspace_actions_listeners(div, cx)
3255            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3256            .on_action(cx.listener(Self::close_all_items_and_panes))
3257            .on_action(cx.listener(Self::save_all))
3258            .on_action(cx.listener(Self::add_folder_to_project))
3259            .on_action(cx.listener(Self::follow_next_collaborator))
3260            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3261                let pane = workspace.active_pane().clone();
3262                workspace.unfollow(&pane, cx);
3263            }))
3264            .on_action(cx.listener(|workspace, action: &Save, cx| {
3265                workspace
3266                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3267                    .detach_and_log_err(cx);
3268            }))
3269            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3270                workspace
3271                    .save_active_item(SaveIntent::SaveAs, cx)
3272                    .detach_and_log_err(cx);
3273            }))
3274            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3275                workspace.activate_previous_pane(cx)
3276            }))
3277            .on_action(
3278                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3279            )
3280            .on_action(
3281                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3282                    workspace.activate_pane_in_direction(action.0, cx)
3283                }),
3284            )
3285            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3286                workspace.swap_pane_in_direction(action.0, cx)
3287            }))
3288            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3289                this.toggle_dock(DockPosition::Left, cx);
3290            }))
3291            .on_action(
3292                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3293                    workspace.toggle_dock(DockPosition::Right, cx);
3294                }),
3295            )
3296            .on_action(
3297                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3298                    workspace.toggle_dock(DockPosition::Bottom, cx);
3299                }),
3300            )
3301            .on_action(
3302                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3303                    workspace.close_all_docks(cx);
3304                }),
3305            )
3306            .on_action(cx.listener(Workspace::open))
3307            .on_action(cx.listener(Workspace::close_window))
3308            .on_action(cx.listener(Workspace::activate_pane_at_index))
3309            .on_action(
3310                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3311                    workspace.reopen_closed_item(cx).detach();
3312                }),
3313            )
3314    }
3315
3316    #[cfg(any(test, feature = "test-support"))]
3317    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3318        use node_runtime::FakeNodeRuntime;
3319
3320        let client = project.read(cx).client();
3321        let user_store = project.read(cx).user_store();
3322
3323        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
3324        cx.activate_window();
3325        let app_state = Arc::new(AppState {
3326            languages: project.read(cx).languages().clone(),
3327            workspace_store,
3328            client,
3329            user_store,
3330            fs: project.read(cx).fs().clone(),
3331            build_window_options: |_, _, _| Default::default(),
3332            node_runtime: FakeNodeRuntime::new(),
3333        });
3334        let workspace = Self::new(0, project, app_state, cx);
3335        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3336        workspace
3337    }
3338
3339    pub fn register_action<A: Action>(
3340        &mut self,
3341        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3342    ) -> &mut Self {
3343        let callback = Arc::new(callback);
3344
3345        self.workspace_actions.push(Box::new(move |div, cx| {
3346            let callback = callback.clone();
3347            div.on_action(
3348                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3349            )
3350        }));
3351        self
3352    }
3353
3354    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3355        let mut div = div
3356            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3357            .on_action(cx.listener(Self::close_all_items_and_panes))
3358            .on_action(cx.listener(Self::add_folder_to_project))
3359            .on_action(cx.listener(Self::save_all))
3360            .on_action(cx.listener(Self::open));
3361        for action in self.workspace_actions.iter() {
3362            div = (action)(div, cx)
3363        }
3364        div
3365    }
3366
3367    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3368        self.modal_layer.read(cx).has_active_modal()
3369    }
3370
3371    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
3372        self.modal_layer.read(cx).active_modal()
3373    }
3374
3375    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
3376    where
3377        B: FnOnce(&mut ViewContext<V>) -> V,
3378    {
3379        self.modal_layer
3380            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3381    }
3382}
3383
3384fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3385    let display_origin = cx
3386        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3387        .ok()??;
3388    ZED_WINDOW_POSITION
3389        .zip(*ZED_WINDOW_SIZE)
3390        .map(|(position, size)| {
3391            WindowBounds::Fixed(Bounds {
3392                origin: display_origin + position,
3393                size,
3394            })
3395        })
3396}
3397
3398fn open_items(
3399    serialized_workspace: Option<SerializedWorkspace>,
3400    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3401    app_state: Arc<AppState>,
3402    cx: &mut ViewContext<Workspace>,
3403) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3404    let restored_items = serialized_workspace.map(|serialized_workspace| {
3405        Workspace::load_workspace(
3406            serialized_workspace,
3407            project_paths_to_open
3408                .iter()
3409                .map(|(_, project_path)| project_path)
3410                .cloned()
3411                .collect(),
3412            cx,
3413        )
3414    });
3415
3416    cx.spawn(|workspace, mut cx| async move {
3417        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3418
3419        if let Some(restored_items) = restored_items {
3420            let restored_items = restored_items.await?;
3421
3422            let restored_project_paths = restored_items
3423                .iter()
3424                .filter_map(|item| {
3425                    cx.update(|cx| item.as_ref()?.project_path(cx))
3426                        .ok()
3427                        .flatten()
3428                })
3429                .collect::<HashSet<_>>();
3430
3431            for restored_item in restored_items {
3432                opened_items.push(restored_item.map(Ok));
3433            }
3434
3435            project_paths_to_open
3436                .iter_mut()
3437                .for_each(|(_, project_path)| {
3438                    if let Some(project_path_to_open) = project_path {
3439                        if restored_project_paths.contains(project_path_to_open) {
3440                            *project_path = None;
3441                        }
3442                    }
3443                });
3444        } else {
3445            for _ in 0..project_paths_to_open.len() {
3446                opened_items.push(None);
3447            }
3448        }
3449        assert!(opened_items.len() == project_paths_to_open.len());
3450
3451        let tasks =
3452            project_paths_to_open
3453                .into_iter()
3454                .enumerate()
3455                .map(|(i, (abs_path, project_path))| {
3456                    let workspace = workspace.clone();
3457                    cx.spawn(|mut cx| {
3458                        let fs = app_state.fs.clone();
3459                        async move {
3460                            let file_project_path = project_path?;
3461                            if fs.is_file(&abs_path).await {
3462                                Some((
3463                                    i,
3464                                    workspace
3465                                        .update(&mut cx, |workspace, cx| {
3466                                            workspace.open_path(file_project_path, None, true, cx)
3467                                        })
3468                                        .log_err()?
3469                                        .await,
3470                                ))
3471                            } else {
3472                                None
3473                            }
3474                        }
3475                    })
3476                });
3477
3478        let tasks = tasks.collect::<Vec<_>>();
3479
3480        let tasks = futures::future::join_all(tasks.into_iter());
3481        for maybe_opened_path in tasks.await.into_iter() {
3482            if let Some((i, path_open_result)) = maybe_opened_path {
3483                opened_items[i] = Some(path_open_result);
3484            }
3485        }
3486
3487        Ok(opened_items)
3488    })
3489}
3490
3491fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3492    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3493
3494    workspace
3495        .update(cx, |workspace, cx| {
3496            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3497                workspace.show_notification_once(0, cx, |cx| {
3498                    cx.new_view(|_| {
3499                        MessageNotification::new("Failed to load the database file.")
3500                            .with_click_message("Click to let us know about this error")
3501                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3502                    })
3503                });
3504            }
3505        })
3506        .log_err();
3507}
3508
3509impl FocusableView for Workspace {
3510    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3511        self.active_pane.focus_handle(cx)
3512    }
3513}
3514
3515#[derive(Clone, Render)]
3516struct DraggedDock(DockPosition);
3517
3518impl Render for Workspace {
3519    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3520        let mut context = KeyContext::default();
3521        context.add("Workspace");
3522
3523        let (ui_font, ui_font_size) = {
3524            let theme_settings = ThemeSettings::get_global(cx);
3525            (
3526                theme_settings.ui_font.family.clone(),
3527                theme_settings.ui_font_size.clone(),
3528            )
3529        };
3530
3531        let theme = cx.theme().clone();
3532        let colors = theme.colors();
3533        cx.set_rem_size(ui_font_size);
3534
3535        self.actions(div(), cx)
3536            .key_context(context)
3537            .relative()
3538            .size_full()
3539            .flex()
3540            .flex_col()
3541            .font(ui_font)
3542            .gap_0()
3543            .justify_start()
3544            .items_start()
3545            .text_color(colors.text)
3546            .bg(colors.background)
3547            .border()
3548            .border_color(colors.border)
3549            .children(self.titlebar_item.clone())
3550            .child(
3551                div()
3552                    .id("workspace")
3553                    .relative()
3554                    .flex_1()
3555                    .w_full()
3556                    .flex()
3557                    .flex_col()
3558                    .overflow_hidden()
3559                    .border_t()
3560                    .border_b()
3561                    .border_color(colors.border)
3562                    .child(
3563                        canvas({
3564                            let this = cx.view().clone();
3565                            move |bounds, cx| {
3566                                this.update(cx, |this, _cx| {
3567                                    this.bounds = *bounds;
3568                                })
3569                            }
3570                        })
3571                        .absolute()
3572                        .size_full(),
3573                    )
3574                    .on_drag_move(
3575                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3576                            match e.drag(cx).0 {
3577                                DockPosition::Left => {
3578                                    let size = workspace.bounds.left() + e.event.position.x;
3579                                    workspace.left_dock.update(cx, |left_dock, cx| {
3580                                        left_dock.resize_active_panel(Some(size), cx);
3581                                    });
3582                                }
3583                                DockPosition::Right => {
3584                                    let size = workspace.bounds.right() - e.event.position.x;
3585                                    workspace.right_dock.update(cx, |right_dock, cx| {
3586                                        right_dock.resize_active_panel(Some(size), cx);
3587                                    });
3588                                }
3589                                DockPosition::Bottom => {
3590                                    let size = workspace.bounds.bottom() - e.event.position.y;
3591                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3592                                        bottom_dock.resize_active_panel(Some(size), cx);
3593                                    });
3594                                }
3595                            }
3596                        }),
3597                    )
3598                    .child(self.modal_layer.clone())
3599                    .child(
3600                        div()
3601                            .flex()
3602                            .flex_row()
3603                            .h_full()
3604                            // Left Dock
3605                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3606                                || {
3607                                    div()
3608                                        .flex()
3609                                        .flex_none()
3610                                        .overflow_hidden()
3611                                        .child(self.left_dock.clone())
3612                                },
3613                            ))
3614                            // Panes
3615                            .child(
3616                                div()
3617                                    .flex()
3618                                    .flex_col()
3619                                    .flex_1()
3620                                    .overflow_hidden()
3621                                    .child(self.center.render(
3622                                        &self.project,
3623                                        &self.follower_states,
3624                                        self.active_call(),
3625                                        &self.active_pane,
3626                                        self.zoomed.as_ref(),
3627                                        &self.app_state,
3628                                        cx,
3629                                    ))
3630                                    .children(
3631                                        self.zoomed_position
3632                                            .ne(&Some(DockPosition::Bottom))
3633                                            .then(|| self.bottom_dock.clone()),
3634                                    ),
3635                            )
3636                            // Right Dock
3637                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3638                                || {
3639                                    div()
3640                                        .flex()
3641                                        .flex_none()
3642                                        .overflow_hidden()
3643                                        .child(self.right_dock.clone())
3644                                },
3645                            )),
3646                    )
3647                    .children(self.render_notifications(cx))
3648                    .children(self.zoomed.as_ref().and_then(|view| {
3649                        let zoomed_view = view.upgrade()?;
3650                        let div = div()
3651                            .z_index(1)
3652                            .absolute()
3653                            .overflow_hidden()
3654                            .border_color(colors.border)
3655                            .bg(colors.background)
3656                            .child(zoomed_view)
3657                            .inset_0()
3658                            .shadow_lg();
3659
3660                        Some(match self.zoomed_position {
3661                            Some(DockPosition::Left) => div.right_2().border_r(),
3662                            Some(DockPosition::Right) => div.left_2().border_l(),
3663                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3664                            None => div.top_2().bottom_2().left_2().right_2().border(),
3665                        })
3666                    })),
3667            )
3668            .child(self.status_bar.clone())
3669            .children(if self.project.read(cx).is_disconnected() {
3670                Some(DisconnectedOverlay)
3671            } else {
3672                None
3673            })
3674    }
3675}
3676
3677impl WorkspaceStore {
3678    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3679        Self {
3680            workspaces: Default::default(),
3681            followers: Default::default(),
3682            _subscriptions: vec![
3683                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3684                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3685                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3686            ],
3687            client,
3688        }
3689    }
3690
3691    pub fn update_followers(
3692        &self,
3693        project_id: Option<u64>,
3694        update: proto::update_followers::Variant,
3695        cx: &AppContext,
3696    ) -> Option<()> {
3697        let active_call = ActiveCall::try_global(cx)?;
3698        let room_id = active_call.read(cx).room()?.read(cx).id();
3699        let follower_ids: Vec<_> = self
3700            .followers
3701            .iter()
3702            .filter_map(|follower| {
3703                if follower.project_id == project_id || project_id.is_none() {
3704                    Some(follower.peer_id.into())
3705                } else {
3706                    None
3707                }
3708            })
3709            .collect();
3710        if follower_ids.is_empty() {
3711            return None;
3712        }
3713        self.client
3714            .send(proto::UpdateFollowers {
3715                room_id,
3716                project_id,
3717                follower_ids,
3718                variant: Some(update),
3719            })
3720            .log_err()
3721    }
3722
3723    pub async fn handle_follow(
3724        this: Model<Self>,
3725        envelope: TypedEnvelope<proto::Follow>,
3726        _: Arc<Client>,
3727        mut cx: AsyncAppContext,
3728    ) -> Result<proto::FollowResponse> {
3729        this.update(&mut cx, |this, cx| {
3730            let follower = Follower {
3731                project_id: envelope.payload.project_id,
3732                peer_id: envelope.original_sender_id()?,
3733            };
3734            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3735
3736            let mut response = proto::FollowResponse::default();
3737            this.workspaces.retain(|workspace| {
3738                workspace
3739                    .update(cx, |workspace, cx| {
3740                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3741                        if response.views.is_empty() {
3742                            response.views = handler_response.views;
3743                        } else {
3744                            response.views.extend_from_slice(&handler_response.views);
3745                        }
3746
3747                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3748                            if response.active_view_id.is_none()
3749                                || Some(workspace.project.downgrade()) == active_project
3750                            {
3751                                response.active_view_id = Some(active_view_id);
3752                            }
3753                        }
3754                    })
3755                    .is_ok()
3756            });
3757
3758            if let Err(ix) = this.followers.binary_search(&follower) {
3759                this.followers.insert(ix, follower);
3760            }
3761
3762            Ok(response)
3763        })?
3764    }
3765
3766    async fn handle_unfollow(
3767        model: Model<Self>,
3768        envelope: TypedEnvelope<proto::Unfollow>,
3769        _: Arc<Client>,
3770        mut cx: AsyncAppContext,
3771    ) -> Result<()> {
3772        model.update(&mut cx, |this, _| {
3773            let follower = Follower {
3774                project_id: envelope.payload.project_id,
3775                peer_id: envelope.original_sender_id()?,
3776            };
3777            if let Ok(ix) = this.followers.binary_search(&follower) {
3778                this.followers.remove(ix);
3779            }
3780            Ok(())
3781        })?
3782    }
3783
3784    async fn handle_update_followers(
3785        this: Model<Self>,
3786        envelope: TypedEnvelope<proto::UpdateFollowers>,
3787        _: Arc<Client>,
3788        mut cx: AsyncAppContext,
3789    ) -> Result<()> {
3790        let leader_id = envelope.original_sender_id()?;
3791        let update = envelope.payload;
3792
3793        this.update(&mut cx, |this, cx| {
3794            this.workspaces.retain(|workspace| {
3795                workspace
3796                    .update(cx, |workspace, cx| {
3797                        let project_id = workspace.project.read(cx).remote_id();
3798                        if update.project_id != project_id && update.project_id.is_some() {
3799                            return;
3800                        }
3801                        workspace.handle_update_followers(leader_id, update.clone(), cx);
3802                    })
3803                    .is_ok()
3804            });
3805            Ok(())
3806        })?
3807    }
3808}
3809
3810impl ViewId {
3811    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3812        Ok(Self {
3813            creator: message
3814                .creator
3815                .ok_or_else(|| anyhow!("creator is missing"))?,
3816            id: message.id,
3817        })
3818    }
3819
3820    pub(crate) fn to_proto(&self) -> proto::ViewId {
3821        proto::ViewId {
3822            creator: Some(self.creator),
3823            id: self.id,
3824        }
3825    }
3826}
3827
3828pub trait WorkspaceHandle {
3829    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3830}
3831
3832impl WorkspaceHandle for View<Workspace> {
3833    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3834        self.read(cx)
3835            .worktrees(cx)
3836            .flat_map(|worktree| {
3837                let worktree_id = worktree.read(cx).id();
3838                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3839                    worktree_id,
3840                    path: f.path.clone(),
3841                })
3842            })
3843            .collect::<Vec<_>>()
3844    }
3845}
3846
3847impl std::fmt::Debug for OpenPaths {
3848    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3849        f.debug_struct("OpenPaths")
3850            .field("paths", &self.paths)
3851            .finish()
3852    }
3853}
3854
3855pub fn activate_workspace_for_project(
3856    cx: &mut AppContext,
3857    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
3858) -> Option<WindowHandle<Workspace>> {
3859    for window in cx.windows() {
3860        let Some(workspace) = window.downcast::<Workspace>() else {
3861            continue;
3862        };
3863
3864        let predicate = workspace
3865            .update(cx, |workspace, cx| {
3866                let project = workspace.project.read(cx);
3867                if predicate(project, cx) {
3868                    cx.activate_window();
3869                    true
3870                } else {
3871                    false
3872                }
3873            })
3874            .log_err()
3875            .unwrap_or(false);
3876
3877        if predicate {
3878            return Some(workspace);
3879        }
3880    }
3881
3882    None
3883}
3884
3885pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3886    DB.last_workspace().await.log_err().flatten()
3887}
3888
3889async fn join_channel_internal(
3890    channel_id: u64,
3891    app_state: &Arc<AppState>,
3892    requesting_window: Option<WindowHandle<Workspace>>,
3893    active_call: &Model<ActiveCall>,
3894    cx: &mut AsyncAppContext,
3895) -> Result<bool> {
3896    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
3897        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
3898            return (false, None);
3899        };
3900
3901        let already_in_channel = room.channel_id() == Some(channel_id);
3902        let should_prompt = room.is_sharing_project()
3903            && room.remote_participants().len() > 0
3904            && !already_in_channel;
3905        let open_room = if already_in_channel {
3906            active_call.room().cloned()
3907        } else {
3908            None
3909        };
3910        (should_prompt, open_room)
3911    })?;
3912
3913    if let Some(room) = open_room {
3914        let task = room.update(cx, |room, cx| {
3915            if let Some((project, host)) = room.most_active_project(cx) {
3916                return Some(join_remote_project(project, host, app_state.clone(), cx));
3917            }
3918
3919            None
3920        })?;
3921        if let Some(task) = task {
3922            task.await?;
3923        }
3924        return anyhow::Ok(true);
3925    }
3926
3927    if should_prompt {
3928        if let Some(workspace) = requesting_window {
3929            let answer = workspace
3930                .update(cx, |_, cx| {
3931                    cx.prompt(
3932                        PromptLevel::Warning,
3933                        "Do you want to switch channels?",
3934                        Some("Leaving this call will unshare your current project."),
3935                        &["Yes, Join Channel", "Cancel"],
3936                    )
3937                })?
3938                .await;
3939
3940            if answer == Ok(1) {
3941                return Ok(false);
3942            }
3943        } else {
3944            return Ok(false); // unreachable!() hopefully
3945        }
3946    }
3947
3948    let client = cx.update(|cx| active_call.read(cx).client())?;
3949
3950    let mut client_status = client.status();
3951
3952    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
3953    'outer: loop {
3954        let Some(status) = client_status.recv().await else {
3955            return Err(anyhow!("error connecting"));
3956        };
3957
3958        match status {
3959            Status::Connecting
3960            | Status::Authenticating
3961            | Status::Reconnecting
3962            | Status::Reauthenticating => continue,
3963            Status::Connected { .. } => break 'outer,
3964            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
3965            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
3966            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
3967                return Err(ErrorCode::Disconnected.into())
3968            }
3969        }
3970    }
3971
3972    let room = active_call
3973        .update(cx, |active_call, cx| {
3974            active_call.join_channel(channel_id, cx)
3975        })?
3976        .await?;
3977
3978    let Some(room) = room else {
3979        return anyhow::Ok(true);
3980    };
3981
3982    room.update(cx, |room, _| room.room_update_completed())?
3983        .await;
3984
3985    let task = room.update(cx, |room, cx| {
3986        if let Some((project, host)) = room.most_active_project(cx) {
3987            return Some(join_remote_project(project, host, app_state.clone(), cx));
3988        }
3989
3990        None
3991    })?;
3992    if let Some(task) = task {
3993        task.await?;
3994        return anyhow::Ok(true);
3995    }
3996    anyhow::Ok(false)
3997}
3998
3999pub fn join_channel(
4000    channel_id: u64,
4001    app_state: Arc<AppState>,
4002    requesting_window: Option<WindowHandle<Workspace>>,
4003    cx: &mut AppContext,
4004) -> Task<Result<()>> {
4005    let active_call = ActiveCall::global(cx);
4006    cx.spawn(|mut cx| async move {
4007        let result = join_channel_internal(
4008            channel_id,
4009            &app_state,
4010            requesting_window,
4011            &active_call,
4012            &mut cx,
4013        )
4014        .await;
4015
4016        // join channel succeeded, and opened a window
4017        if matches!(result, Ok(true)) {
4018            return anyhow::Ok(());
4019        }
4020
4021        // find an existing workspace to focus and show call controls
4022        let mut active_window =
4023            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4024        if active_window.is_none() {
4025            // no open workspaces, make one to show the error in (blergh)
4026            let (window_handle, _) = cx
4027                .update(|cx| {
4028                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4029                })?
4030                .await?;
4031
4032            active_window = Some(window_handle);
4033        }
4034
4035        if let Err(err) = result {
4036            log::error!("failed to join channel: {}", err);
4037            if let Some(active_window) = active_window {
4038                active_window
4039                    .update(&mut cx, |_, cx| {
4040                        let detail: SharedString = match err.error_code() {
4041                            ErrorCode::SignedOut => {
4042                                "Please sign in to continue.".into()
4043                            },
4044                            ErrorCode::UpgradeRequired => {
4045                                "Your are running an unsupported version of Zed. Please update to continue.".into()
4046                            },
4047                            ErrorCode::NoSuchChannel => {
4048                                "No matching channel was found. Please check the link and try again.".into()
4049                            },
4050                            ErrorCode::Forbidden => {
4051                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
4052                            },
4053                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
4054                            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(),
4055                            _ => format!("{}\n\nPlease try again.", err).into(),
4056                        };
4057                        cx.prompt(
4058                            PromptLevel::Critical,
4059                            "Failed to join channel",
4060                            Some(&detail),
4061                            &["Ok"],
4062                        )
4063                    })?
4064                    .await
4065                    .ok();
4066            }
4067        }
4068
4069        // return ok, we showed the error to the user.
4070        return anyhow::Ok(());
4071    })
4072}
4073
4074pub async fn get_any_active_workspace(
4075    app_state: Arc<AppState>,
4076    mut cx: AsyncAppContext,
4077) -> anyhow::Result<WindowHandle<Workspace>> {
4078    // find an existing workspace to focus and show call controls
4079    let active_window = activate_any_workspace_window(&mut cx);
4080    if active_window.is_none() {
4081        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4082            .await?;
4083    }
4084    activate_any_workspace_window(&mut cx).context("could not open zed")
4085}
4086
4087fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4088    cx.update(|cx| {
4089        for window in cx.windows() {
4090            if let Some(workspace_window) = window.downcast::<Workspace>() {
4091                workspace_window
4092                    .update(cx, |_, cx| cx.activate_window())
4093                    .ok();
4094                return Some(workspace_window);
4095            }
4096        }
4097        None
4098    })
4099    .ok()
4100    .flatten()
4101}
4102
4103#[allow(clippy::type_complexity)]
4104pub fn open_paths(
4105    abs_paths: &[PathBuf],
4106    app_state: &Arc<AppState>,
4107    requesting_window: Option<WindowHandle<Workspace>>,
4108    cx: &mut AppContext,
4109) -> Task<
4110    anyhow::Result<(
4111        WindowHandle<Workspace>,
4112        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4113    )>,
4114> {
4115    let app_state = app_state.clone();
4116    let abs_paths = abs_paths.to_vec();
4117    // Open paths in existing workspace if possible
4118    let existing = activate_workspace_for_project(cx, {
4119        let abs_paths = abs_paths.clone();
4120        move |project, cx| project.contains_paths(&abs_paths, cx)
4121    });
4122    cx.spawn(move |mut cx| async move {
4123        if let Some(existing) = existing {
4124            Ok((
4125                existing.clone(),
4126                existing
4127                    .update(&mut cx, |workspace, cx| {
4128                        workspace.open_paths(abs_paths, OpenVisible::All, None, cx)
4129                    })?
4130                    .await,
4131            ))
4132        } else {
4133            cx.update(move |cx| {
4134                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4135            })?
4136            .await
4137        }
4138    })
4139}
4140
4141pub fn open_new(
4142    app_state: &Arc<AppState>,
4143    cx: &mut AppContext,
4144    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4145) -> Task<()> {
4146    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4147    cx.spawn(|mut cx| async move {
4148        if let Some((workspace, opened_paths)) = task.await.log_err() {
4149            workspace
4150                .update(&mut cx, |workspace, cx| {
4151                    if opened_paths.is_empty() {
4152                        init(workspace, cx)
4153                    }
4154                })
4155                .log_err();
4156        }
4157    })
4158}
4159
4160pub fn create_and_open_local_file(
4161    path: &'static Path,
4162    cx: &mut ViewContext<Workspace>,
4163    default_content: impl 'static + Send + FnOnce() -> Rope,
4164) -> Task<Result<Box<dyn ItemHandle>>> {
4165    cx.spawn(|workspace, mut cx| async move {
4166        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4167        if !fs.is_file(path).await {
4168            fs.create_file(path, Default::default()).await?;
4169            fs.save(path, &default_content(), Default::default())
4170                .await?;
4171        }
4172
4173        let mut items = workspace
4174            .update(&mut cx, |workspace, cx| {
4175                workspace.with_local_workspace(cx, |workspace, cx| {
4176                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4177                })
4178            })?
4179            .await?
4180            .await;
4181
4182        let item = items.pop().flatten();
4183        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4184    })
4185}
4186
4187pub fn join_remote_project(
4188    project_id: u64,
4189    follow_user_id: u64,
4190    app_state: Arc<AppState>,
4191    cx: &mut AppContext,
4192) -> Task<Result<()>> {
4193    let windows = cx.windows();
4194    cx.spawn(|mut cx| async move {
4195        let existing_workspace = windows.into_iter().find_map(|window| {
4196            window.downcast::<Workspace>().and_then(|window| {
4197                window
4198                    .update(&mut cx, |workspace, cx| {
4199                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4200                            Some(window)
4201                        } else {
4202                            None
4203                        }
4204                    })
4205                    .unwrap_or(None)
4206            })
4207        });
4208
4209        let workspace = if let Some(existing_workspace) = existing_workspace {
4210            existing_workspace
4211        } else {
4212            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4213            let room = active_call
4214                .read_with(&cx, |call, _| call.room().cloned())?
4215                .ok_or_else(|| anyhow!("not in a call"))?;
4216            let project = room
4217                .update(&mut cx, |room, cx| {
4218                    room.join_project(
4219                        project_id,
4220                        app_state.languages.clone(),
4221                        app_state.fs.clone(),
4222                        cx,
4223                    )
4224                })?
4225                .await?;
4226
4227            let window_bounds_override = window_bounds_env_override(&cx);
4228            cx.update(|cx| {
4229                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4230                cx.open_window(options, |cx| {
4231                    cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4232                })
4233            })?
4234        };
4235
4236        workspace.update(&mut cx, |workspace, cx| {
4237            cx.activate(true);
4238            cx.activate_window();
4239
4240            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4241                let follow_peer_id = room
4242                    .read(cx)
4243                    .remote_participants()
4244                    .iter()
4245                    .find(|(_, participant)| participant.user.id == follow_user_id)
4246                    .map(|(_, p)| p.peer_id)
4247                    .or_else(|| {
4248                        // If we couldn't follow the given user, follow the host instead.
4249                        let collaborator = workspace
4250                            .project()
4251                            .read(cx)
4252                            .collaborators()
4253                            .values()
4254                            .find(|collaborator| collaborator.replica_id == 0)?;
4255                        Some(collaborator.peer_id)
4256                    });
4257
4258                if let Some(follow_peer_id) = follow_peer_id {
4259                    workspace.follow(follow_peer_id, cx);
4260                }
4261            }
4262        })?;
4263
4264        anyhow::Ok(())
4265    })
4266}
4267
4268pub fn restart(_: &Restart, cx: &mut AppContext) {
4269    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4270    let mut workspace_windows = cx
4271        .windows()
4272        .into_iter()
4273        .filter_map(|window| window.downcast::<Workspace>())
4274        .collect::<Vec<_>>();
4275
4276    // If multiple windows have unsaved changes, and need a save prompt,
4277    // prompt in the active window before switching to a different window.
4278    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
4279
4280    let mut prompt = None;
4281    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4282        prompt = window
4283            .update(cx, |_, cx| {
4284                cx.prompt(
4285                    PromptLevel::Info,
4286                    "Are you sure you want to restart?",
4287                    None,
4288                    &["Restart", "Cancel"],
4289                )
4290            })
4291            .ok();
4292    }
4293
4294    cx.spawn(|mut cx| async move {
4295        if let Some(prompt) = prompt {
4296            let answer = prompt.await?;
4297            if answer != 0 {
4298                return Ok(());
4299            }
4300        }
4301
4302        // If the user cancels any save prompt, then keep the app open.
4303        for window in workspace_windows {
4304            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4305                workspace.prepare_to_close(true, cx)
4306            }) {
4307                if !should_close.await? {
4308                    return Ok(());
4309                }
4310            }
4311        }
4312
4313        cx.update(|cx| cx.restart())
4314    })
4315    .detach_and_log_err(cx);
4316}
4317
4318fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4319    let mut parts = value.split(',');
4320    let x: usize = parts.next()?.parse().ok()?;
4321    let y: usize = parts.next()?.parse().ok()?;
4322    Some(point((x as f64).into(), (y as f64).into()))
4323}
4324
4325fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4326    let mut parts = value.split(',');
4327    let width: usize = parts.next()?.parse().ok()?;
4328    let height: usize = parts.next()?.parse().ok()?;
4329    Some(size((width as f64).into(), (height as f64).into()))
4330}
4331
4332pub fn titlebar_height(cx: &mut WindowContext) -> Pixels {
4333    (1.75 * cx.rem_size()).max(px(32.))
4334}
4335
4336struct DisconnectedOverlay;
4337
4338impl Element for DisconnectedOverlay {
4339    type State = AnyElement;
4340
4341    fn request_layout(
4342        &mut self,
4343        _: Option<Self::State>,
4344        cx: &mut ElementContext,
4345    ) -> (LayoutId, Self::State) {
4346        let mut background = cx.theme().colors().elevated_surface_background;
4347        background.fade_out(0.2);
4348        let mut overlay = div()
4349            .bg(background)
4350            .absolute()
4351            .left_0()
4352            .top(titlebar_height(cx))
4353            .size_full()
4354            .flex()
4355            .items_center()
4356            .justify_center()
4357            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4358            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4359            .child(Label::new(
4360                "Your connection to the remote project has been lost.",
4361            ))
4362            .into_any();
4363        (overlay.request_layout(cx), overlay)
4364    }
4365
4366    fn paint(
4367        &mut self,
4368        bounds: Bounds<Pixels>,
4369        overlay: &mut Self::State,
4370        cx: &mut ElementContext,
4371    ) {
4372        cx.with_z_index(u16::MAX, |cx| {
4373            cx.add_opaque_layer(bounds);
4374            overlay.paint(cx);
4375        })
4376    }
4377}
4378
4379impl IntoElement for DisconnectedOverlay {
4380    type Element = Self;
4381
4382    fn element_id(&self) -> Option<ui::prelude::ElementId> {
4383        None
4384    }
4385
4386    fn into_element(self) -> Self::Element {
4387        self
4388    }
4389}
4390
4391#[cfg(test)]
4392mod tests {
4393    use std::{cell::RefCell, rc::Rc};
4394
4395    use super::*;
4396    use crate::{
4397        dock::{test::TestPanel, PanelEvent},
4398        item::{
4399            test::{TestItem, TestProjectItem},
4400            ItemEvent,
4401        },
4402    };
4403    use fs::FakeFs;
4404    use gpui::{px, DismissEvent, TestAppContext, VisualTestContext};
4405    use project::{Project, ProjectEntryId};
4406    use serde_json::json;
4407    use settings::SettingsStore;
4408
4409    #[gpui::test]
4410    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4411        init_test(cx);
4412
4413        let fs = FakeFs::new(cx.executor());
4414        let project = Project::test(fs, [], cx).await;
4415        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4416
4417        // Adding an item with no ambiguity renders the tab without detail.
4418        let item1 = cx.new_view(|cx| {
4419            let mut item = TestItem::new(cx);
4420            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4421            item
4422        });
4423        workspace.update(cx, |workspace, cx| {
4424            workspace.add_item(Box::new(item1.clone()), cx);
4425        });
4426        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4427
4428        // Adding an item that creates ambiguity increases the level of detail on
4429        // both tabs.
4430        let item2 = cx.new_view(|cx| {
4431            let mut item = TestItem::new(cx);
4432            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4433            item
4434        });
4435        workspace.update(cx, |workspace, cx| {
4436            workspace.add_item(Box::new(item2.clone()), cx);
4437        });
4438        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4439        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4440
4441        // Adding an item that creates ambiguity increases the level of detail only
4442        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4443        // we stop at the highest detail available.
4444        let item3 = cx.new_view(|cx| {
4445            let mut item = TestItem::new(cx);
4446            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4447            item
4448        });
4449        workspace.update(cx, |workspace, cx| {
4450            workspace.add_item(Box::new(item3.clone()), cx);
4451        });
4452        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4453        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4454        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4455    }
4456
4457    #[gpui::test]
4458    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4459        init_test(cx);
4460
4461        let fs = FakeFs::new(cx.executor());
4462        fs.insert_tree(
4463            "/root1",
4464            json!({
4465                "one.txt": "",
4466                "two.txt": "",
4467            }),
4468        )
4469        .await;
4470        fs.insert_tree(
4471            "/root2",
4472            json!({
4473                "three.txt": "",
4474            }),
4475        )
4476        .await;
4477
4478        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4479        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4480        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4481        let worktree_id = project.update(cx, |project, cx| {
4482            project.worktrees().next().unwrap().read(cx).id()
4483        });
4484
4485        let item1 = cx.new_view(|cx| {
4486            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4487        });
4488        let item2 = cx.new_view(|cx| {
4489            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4490        });
4491
4492        // Add an item to an empty pane
4493        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4494        project.update(cx, |project, cx| {
4495            assert_eq!(
4496                project.active_entry(),
4497                project
4498                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4499                    .map(|e| e.id)
4500            );
4501        });
4502        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4503
4504        // Add a second item to a non-empty pane
4505        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4506        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
4507        project.update(cx, |project, cx| {
4508            assert_eq!(
4509                project.active_entry(),
4510                project
4511                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4512                    .map(|e| e.id)
4513            );
4514        });
4515
4516        // Close the active item
4517        pane.update(cx, |pane, cx| {
4518            pane.close_active_item(&Default::default(), cx).unwrap()
4519        })
4520        .await
4521        .unwrap();
4522        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4523        project.update(cx, |project, cx| {
4524            assert_eq!(
4525                project.active_entry(),
4526                project
4527                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4528                    .map(|e| e.id)
4529            );
4530        });
4531
4532        // Add a project folder
4533        project
4534            .update(cx, |project, cx| {
4535                project.find_or_create_local_worktree("/root2", true, cx)
4536            })
4537            .await
4538            .unwrap();
4539        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
4540
4541        // Remove a project folder
4542        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4543        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
4544    }
4545
4546    #[gpui::test]
4547    async fn test_close_window(cx: &mut TestAppContext) {
4548        init_test(cx);
4549
4550        let fs = FakeFs::new(cx.executor());
4551        fs.insert_tree("/root", json!({ "one": "" })).await;
4552
4553        let project = Project::test(fs, ["root".as_ref()], cx).await;
4554        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4555
4556        // When there are no dirty items, there's nothing to do.
4557        let item1 = cx.new_view(|cx| TestItem::new(cx));
4558        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4559        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4560        assert!(task.await.unwrap());
4561
4562        // When there are dirty untitled items, prompt to save each one. If the user
4563        // cancels any prompt, then abort.
4564        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
4565        let item3 = cx.new_view(|cx| {
4566            TestItem::new(cx)
4567                .with_dirty(true)
4568                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4569        });
4570        workspace.update(cx, |w, cx| {
4571            w.add_item(Box::new(item2.clone()), cx);
4572            w.add_item(Box::new(item3.clone()), cx);
4573        });
4574        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4575        cx.executor().run_until_parked();
4576        cx.simulate_prompt_answer(2); // cancel save all
4577        cx.executor().run_until_parked();
4578        cx.simulate_prompt_answer(2); // cancel save all
4579        cx.executor().run_until_parked();
4580        assert!(!cx.has_pending_prompt());
4581        assert!(!task.await.unwrap());
4582    }
4583
4584    #[gpui::test]
4585    async fn test_close_pane_items(cx: &mut TestAppContext) {
4586        init_test(cx);
4587
4588        let fs = FakeFs::new(cx.executor());
4589
4590        let project = Project::test(fs, None, cx).await;
4591        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4592
4593        let item1 = cx.new_view(|cx| {
4594            TestItem::new(cx)
4595                .with_dirty(true)
4596                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4597        });
4598        let item2 = cx.new_view(|cx| {
4599            TestItem::new(cx)
4600                .with_dirty(true)
4601                .with_conflict(true)
4602                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4603        });
4604        let item3 = cx.new_view(|cx| {
4605            TestItem::new(cx)
4606                .with_dirty(true)
4607                .with_conflict(true)
4608                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4609        });
4610        let item4 = cx.new_view(|cx| {
4611            TestItem::new(cx)
4612                .with_dirty(true)
4613                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4614        });
4615        let pane = workspace.update(cx, |workspace, cx| {
4616            workspace.add_item(Box::new(item1.clone()), cx);
4617            workspace.add_item(Box::new(item2.clone()), cx);
4618            workspace.add_item(Box::new(item3.clone()), cx);
4619            workspace.add_item(Box::new(item4.clone()), cx);
4620            workspace.active_pane().clone()
4621        });
4622
4623        let close_items = pane.update(cx, |pane, cx| {
4624            pane.activate_item(1, true, true, cx);
4625            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4626            let item1_id = item1.item_id();
4627            let item3_id = item3.item_id();
4628            let item4_id = item4.item_id();
4629            pane.close_items(cx, SaveIntent::Close, move |id| {
4630                [item1_id, item3_id, item4_id].contains(&id)
4631            })
4632        });
4633        cx.executor().run_until_parked();
4634
4635        assert!(cx.has_pending_prompt());
4636        // Ignore "Save all" prompt
4637        cx.simulate_prompt_answer(2);
4638        cx.executor().run_until_parked();
4639        // There's a prompt to save item 1.
4640        pane.update(cx, |pane, _| {
4641            assert_eq!(pane.items_len(), 4);
4642            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4643        });
4644        // Confirm saving item 1.
4645        cx.simulate_prompt_answer(0);
4646        cx.executor().run_until_parked();
4647
4648        // Item 1 is saved. There's a prompt to save item 3.
4649        pane.update(cx, |pane, cx| {
4650            assert_eq!(item1.read(cx).save_count, 1);
4651            assert_eq!(item1.read(cx).save_as_count, 0);
4652            assert_eq!(item1.read(cx).reload_count, 0);
4653            assert_eq!(pane.items_len(), 3);
4654            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4655        });
4656        assert!(cx.has_pending_prompt());
4657
4658        // Cancel saving item 3.
4659        cx.simulate_prompt_answer(1);
4660        cx.executor().run_until_parked();
4661
4662        // Item 3 is reloaded. There's a prompt to save item 4.
4663        pane.update(cx, |pane, cx| {
4664            assert_eq!(item3.read(cx).save_count, 0);
4665            assert_eq!(item3.read(cx).save_as_count, 0);
4666            assert_eq!(item3.read(cx).reload_count, 1);
4667            assert_eq!(pane.items_len(), 2);
4668            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4669        });
4670        assert!(cx.has_pending_prompt());
4671
4672        // Confirm saving item 4.
4673        cx.simulate_prompt_answer(0);
4674        cx.executor().run_until_parked();
4675
4676        // There's a prompt for a path for item 4.
4677        cx.simulate_new_path_selection(|_| Some(Default::default()));
4678        close_items.await.unwrap();
4679
4680        // The requested items are closed.
4681        pane.update(cx, |pane, cx| {
4682            assert_eq!(item4.read(cx).save_count, 0);
4683            assert_eq!(item4.read(cx).save_as_count, 1);
4684            assert_eq!(item4.read(cx).reload_count, 0);
4685            assert_eq!(pane.items_len(), 1);
4686            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4687        });
4688    }
4689
4690    #[gpui::test]
4691    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4692        init_test(cx);
4693
4694        let fs = FakeFs::new(cx.executor());
4695        let project = Project::test(fs, [], cx).await;
4696        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4697
4698        // Create several workspace items with single project entries, and two
4699        // workspace items with multiple project entries.
4700        let single_entry_items = (0..=4)
4701            .map(|project_entry_id| {
4702                cx.new_view(|cx| {
4703                    TestItem::new(cx)
4704                        .with_dirty(true)
4705                        .with_project_items(&[TestProjectItem::new(
4706                            project_entry_id,
4707                            &format!("{project_entry_id}.txt"),
4708                            cx,
4709                        )])
4710                })
4711            })
4712            .collect::<Vec<_>>();
4713        let item_2_3 = cx.new_view(|cx| {
4714            TestItem::new(cx)
4715                .with_dirty(true)
4716                .with_singleton(false)
4717                .with_project_items(&[
4718                    single_entry_items[2].read(cx).project_items[0].clone(),
4719                    single_entry_items[3].read(cx).project_items[0].clone(),
4720                ])
4721        });
4722        let item_3_4 = cx.new_view(|cx| {
4723            TestItem::new(cx)
4724                .with_dirty(true)
4725                .with_singleton(false)
4726                .with_project_items(&[
4727                    single_entry_items[3].read(cx).project_items[0].clone(),
4728                    single_entry_items[4].read(cx).project_items[0].clone(),
4729                ])
4730        });
4731
4732        // Create two panes that contain the following project entries:
4733        //   left pane:
4734        //     multi-entry items:   (2, 3)
4735        //     single-entry items:  0, 1, 2, 3, 4
4736        //   right pane:
4737        //     single-entry items:  1
4738        //     multi-entry items:   (3, 4)
4739        let left_pane = workspace.update(cx, |workspace, cx| {
4740            let left_pane = workspace.active_pane().clone();
4741            workspace.add_item(Box::new(item_2_3.clone()), cx);
4742            for item in single_entry_items {
4743                workspace.add_item(Box::new(item), cx);
4744            }
4745            left_pane.update(cx, |pane, cx| {
4746                pane.activate_item(2, true, true, cx);
4747            });
4748
4749            let right_pane = workspace
4750                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4751                .unwrap();
4752
4753            right_pane.update(cx, |pane, cx| {
4754                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4755            });
4756
4757            left_pane
4758        });
4759
4760        cx.focus_view(&left_pane);
4761
4762        // When closing all of the items in the left pane, we should be prompted twice:
4763        // once for project entry 0, and once for project entry 2. Project entries 1,
4764        // 3, and 4 are all still open in the other paten. After those two
4765        // prompts, the task should complete.
4766
4767        let close = left_pane.update(cx, |pane, cx| {
4768            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4769        });
4770        cx.executor().run_until_parked();
4771
4772        // Discard "Save all" prompt
4773        cx.simulate_prompt_answer(2);
4774
4775        cx.executor().run_until_parked();
4776        left_pane.update(cx, |pane, cx| {
4777            assert_eq!(
4778                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4779                &[ProjectEntryId::from_proto(0)]
4780            );
4781        });
4782        cx.simulate_prompt_answer(0);
4783
4784        cx.executor().run_until_parked();
4785        left_pane.update(cx, |pane, cx| {
4786            assert_eq!(
4787                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4788                &[ProjectEntryId::from_proto(2)]
4789            );
4790        });
4791        cx.simulate_prompt_answer(0);
4792
4793        cx.executor().run_until_parked();
4794        close.await.unwrap();
4795        left_pane.update(cx, |pane, _| {
4796            assert_eq!(pane.items_len(), 0);
4797        });
4798    }
4799
4800    #[gpui::test]
4801    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4802        init_test(cx);
4803
4804        let fs = FakeFs::new(cx.executor());
4805        let project = Project::test(fs, [], cx).await;
4806        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4807        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4808
4809        let item = cx.new_view(|cx| {
4810            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4811        });
4812        let item_id = item.entity_id();
4813        workspace.update(cx, |workspace, cx| {
4814            workspace.add_item(Box::new(item.clone()), cx);
4815        });
4816
4817        // Autosave on window change.
4818        item.update(cx, |item, cx| {
4819            cx.update_global(|settings: &mut SettingsStore, cx| {
4820                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4821                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4822                })
4823            });
4824            item.is_dirty = true;
4825        });
4826
4827        // Deactivating the window saves the file.
4828        cx.deactivate_window();
4829        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4830
4831        // Autosave on focus change.
4832        item.update(cx, |item, cx| {
4833            cx.focus_self();
4834            cx.update_global(|settings: &mut SettingsStore, cx| {
4835                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4836                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4837                })
4838            });
4839            item.is_dirty = true;
4840        });
4841
4842        // Blurring the item saves the file.
4843        item.update(cx, |_, cx| cx.blur());
4844        cx.executor().run_until_parked();
4845        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4846
4847        // Deactivating the window still saves the file.
4848        cx.update(|cx| cx.activate_window());
4849        item.update(cx, |item, cx| {
4850            cx.focus_self();
4851            item.is_dirty = true;
4852        });
4853        cx.deactivate_window();
4854
4855        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4856
4857        // Autosave after delay.
4858        item.update(cx, |item, cx| {
4859            cx.update_global(|settings: &mut SettingsStore, cx| {
4860                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4861                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4862                })
4863            });
4864            item.is_dirty = true;
4865            cx.emit(ItemEvent::Edit);
4866        });
4867
4868        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4869        cx.executor().advance_clock(Duration::from_millis(250));
4870        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4871
4872        // After delay expires, the file is saved.
4873        cx.executor().advance_clock(Duration::from_millis(250));
4874        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4875
4876        // Autosave on focus change, ensuring closing the tab counts as such.
4877        item.update(cx, |item, cx| {
4878            cx.update_global(|settings: &mut SettingsStore, cx| {
4879                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4880                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4881                })
4882            });
4883            item.is_dirty = true;
4884        });
4885
4886        pane.update(cx, |pane, cx| {
4887            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4888        })
4889        .await
4890        .unwrap();
4891        assert!(!cx.has_pending_prompt());
4892        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4893
4894        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4895        workspace.update(cx, |workspace, cx| {
4896            workspace.add_item(Box::new(item.clone()), cx);
4897        });
4898        item.update(cx, |item, cx| {
4899            item.project_items[0].update(cx, |item, _| {
4900                item.entry_id = None;
4901            });
4902            item.is_dirty = true;
4903            cx.blur();
4904        });
4905        cx.run_until_parked();
4906        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4907
4908        // Ensure autosave is prevented for deleted files also when closing the buffer.
4909        let _close_items = pane.update(cx, |pane, cx| {
4910            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4911        });
4912        cx.run_until_parked();
4913        assert!(cx.has_pending_prompt());
4914        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4915    }
4916
4917    #[gpui::test]
4918    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4919        init_test(cx);
4920
4921        let fs = FakeFs::new(cx.executor());
4922
4923        let project = Project::test(fs, [], cx).await;
4924        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4925
4926        let item = cx.new_view(|cx| {
4927            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4928        });
4929        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4930        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4931        let toolbar_notify_count = Rc::new(RefCell::new(0));
4932
4933        workspace.update(cx, |workspace, cx| {
4934            workspace.add_item(Box::new(item.clone()), cx);
4935            let toolbar_notification_count = toolbar_notify_count.clone();
4936            cx.observe(&toolbar, move |_, _, _| {
4937                *toolbar_notification_count.borrow_mut() += 1
4938            })
4939            .detach();
4940        });
4941
4942        pane.update(cx, |pane, _| {
4943            assert!(!pane.can_navigate_backward());
4944            assert!(!pane.can_navigate_forward());
4945        });
4946
4947        item.update(cx, |item, cx| {
4948            item.set_state("one".to_string(), cx);
4949        });
4950
4951        // Toolbar must be notified to re-render the navigation buttons
4952        assert_eq!(*toolbar_notify_count.borrow(), 1);
4953
4954        pane.update(cx, |pane, _| {
4955            assert!(pane.can_navigate_backward());
4956            assert!(!pane.can_navigate_forward());
4957        });
4958
4959        workspace
4960            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4961            .await
4962            .unwrap();
4963
4964        assert_eq!(*toolbar_notify_count.borrow(), 2);
4965        pane.update(cx, |pane, _| {
4966            assert!(!pane.can_navigate_backward());
4967            assert!(pane.can_navigate_forward());
4968        });
4969    }
4970
4971    #[gpui::test]
4972    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4973        init_test(cx);
4974        let fs = FakeFs::new(cx.executor());
4975
4976        let project = Project::test(fs, [], cx).await;
4977        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4978
4979        let panel = workspace.update(cx, |workspace, cx| {
4980            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
4981            workspace.add_panel(panel.clone(), cx);
4982
4983            workspace
4984                .right_dock()
4985                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4986
4987            panel
4988        });
4989
4990        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4991        pane.update(cx, |pane, cx| {
4992            let item = cx.new_view(|cx| TestItem::new(cx));
4993            pane.add_item(Box::new(item), true, true, None, cx);
4994        });
4995
4996        // Transfer focus from center to panel
4997        workspace.update(cx, |workspace, cx| {
4998            workspace.toggle_panel_focus::<TestPanel>(cx);
4999        });
5000
5001        workspace.update(cx, |workspace, cx| {
5002            assert!(workspace.right_dock().read(cx).is_open());
5003            assert!(!panel.is_zoomed(cx));
5004            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5005        });
5006
5007        // Transfer focus from panel to center
5008        workspace.update(cx, |workspace, cx| {
5009            workspace.toggle_panel_focus::<TestPanel>(cx);
5010        });
5011
5012        workspace.update(cx, |workspace, cx| {
5013            assert!(workspace.right_dock().read(cx).is_open());
5014            assert!(!panel.is_zoomed(cx));
5015            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5016        });
5017
5018        // Close the dock
5019        workspace.update(cx, |workspace, cx| {
5020            workspace.toggle_dock(DockPosition::Right, cx);
5021        });
5022
5023        workspace.update(cx, |workspace, cx| {
5024            assert!(!workspace.right_dock().read(cx).is_open());
5025            assert!(!panel.is_zoomed(cx));
5026            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5027        });
5028
5029        // Open the dock
5030        workspace.update(cx, |workspace, cx| {
5031            workspace.toggle_dock(DockPosition::Right, cx);
5032        });
5033
5034        workspace.update(cx, |workspace, cx| {
5035            assert!(workspace.right_dock().read(cx).is_open());
5036            assert!(!panel.is_zoomed(cx));
5037            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5038        });
5039
5040        // Focus and zoom panel
5041        panel.update(cx, |panel, cx| {
5042            cx.focus_self();
5043            panel.set_zoomed(true, cx)
5044        });
5045
5046        workspace.update(cx, |workspace, cx| {
5047            assert!(workspace.right_dock().read(cx).is_open());
5048            assert!(panel.is_zoomed(cx));
5049            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5050        });
5051
5052        // Transfer focus to the center closes the dock
5053        workspace.update(cx, |workspace, cx| {
5054            workspace.toggle_panel_focus::<TestPanel>(cx);
5055        });
5056
5057        workspace.update(cx, |workspace, cx| {
5058            assert!(!workspace.right_dock().read(cx).is_open());
5059            assert!(panel.is_zoomed(cx));
5060            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5061        });
5062
5063        // Transferring focus back to the panel keeps it zoomed
5064        workspace.update(cx, |workspace, cx| {
5065            workspace.toggle_panel_focus::<TestPanel>(cx);
5066        });
5067
5068        workspace.update(cx, |workspace, cx| {
5069            assert!(workspace.right_dock().read(cx).is_open());
5070            assert!(panel.is_zoomed(cx));
5071            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5072        });
5073
5074        // Close the dock while it is zoomed
5075        workspace.update(cx, |workspace, cx| {
5076            workspace.toggle_dock(DockPosition::Right, cx)
5077        });
5078
5079        workspace.update(cx, |workspace, cx| {
5080            assert!(!workspace.right_dock().read(cx).is_open());
5081            assert!(panel.is_zoomed(cx));
5082            assert!(workspace.zoomed.is_none());
5083            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5084        });
5085
5086        // Opening the dock, when it's zoomed, retains focus
5087        workspace.update(cx, |workspace, cx| {
5088            workspace.toggle_dock(DockPosition::Right, cx)
5089        });
5090
5091        workspace.update(cx, |workspace, cx| {
5092            assert!(workspace.right_dock().read(cx).is_open());
5093            assert!(panel.is_zoomed(cx));
5094            assert!(workspace.zoomed.is_some());
5095            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5096        });
5097
5098        // Unzoom and close the panel, zoom the active pane.
5099        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5100        workspace.update(cx, |workspace, cx| {
5101            workspace.toggle_dock(DockPosition::Right, cx)
5102        });
5103        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5104
5105        // Opening a dock unzooms the pane.
5106        workspace.update(cx, |workspace, cx| {
5107            workspace.toggle_dock(DockPosition::Right, cx)
5108        });
5109        workspace.update(cx, |workspace, cx| {
5110            let pane = pane.read(cx);
5111            assert!(!pane.is_zoomed());
5112            assert!(!pane.focus_handle(cx).is_focused(cx));
5113            assert!(workspace.right_dock().read(cx).is_open());
5114            assert!(workspace.zoomed.is_none());
5115        });
5116    }
5117
5118    struct TestModal(FocusHandle);
5119
5120    impl TestModal {
5121        fn new(cx: &mut ViewContext<Self>) -> Self {
5122            Self(cx.focus_handle())
5123        }
5124    }
5125
5126    impl EventEmitter<DismissEvent> for TestModal {}
5127
5128    impl FocusableView for TestModal {
5129        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5130            self.0.clone()
5131        }
5132    }
5133
5134    impl ModalView for TestModal {}
5135
5136    impl Render for TestModal {
5137        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5138            div().track_focus(&self.0)
5139        }
5140    }
5141
5142    #[gpui::test]
5143    async fn test_panels(cx: &mut gpui::TestAppContext) {
5144        init_test(cx);
5145        let fs = FakeFs::new(cx.executor());
5146
5147        let project = Project::test(fs, [], cx).await;
5148        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5149
5150        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5151            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5152            workspace.add_panel(panel_1.clone(), cx);
5153            workspace
5154                .left_dock()
5155                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5156            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5157            workspace.add_panel(panel_2.clone(), cx);
5158            workspace
5159                .right_dock()
5160                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5161
5162            let left_dock = workspace.left_dock();
5163            assert_eq!(
5164                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5165                panel_1.panel_id()
5166            );
5167            assert_eq!(
5168                left_dock.read(cx).active_panel_size(cx).unwrap(),
5169                panel_1.size(cx)
5170            );
5171
5172            left_dock.update(cx, |left_dock, cx| {
5173                left_dock.resize_active_panel(Some(px(1337.)), cx)
5174            });
5175            assert_eq!(
5176                workspace
5177                    .right_dock()
5178                    .read(cx)
5179                    .visible_panel()
5180                    .unwrap()
5181                    .panel_id(),
5182                panel_2.panel_id(),
5183            );
5184
5185            (panel_1, panel_2)
5186        });
5187
5188        // Move panel_1 to the right
5189        panel_1.update(cx, |panel_1, cx| {
5190            panel_1.set_position(DockPosition::Right, cx)
5191        });
5192
5193        workspace.update(cx, |workspace, cx| {
5194            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5195            // Since it was the only panel on the left, the left dock should now be closed.
5196            assert!(!workspace.left_dock().read(cx).is_open());
5197            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5198            let right_dock = workspace.right_dock();
5199            assert_eq!(
5200                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5201                panel_1.panel_id()
5202            );
5203            assert_eq!(
5204                right_dock.read(cx).active_panel_size(cx).unwrap(),
5205                px(1337.)
5206            );
5207
5208            // Now we move panel_2 to the left
5209            panel_2.set_position(DockPosition::Left, cx);
5210        });
5211
5212        workspace.update(cx, |workspace, cx| {
5213            // Since panel_2 was not visible on the right, we don't open the left dock.
5214            assert!(!workspace.left_dock().read(cx).is_open());
5215            // And the right dock is unaffected in it's displaying of panel_1
5216            assert!(workspace.right_dock().read(cx).is_open());
5217            assert_eq!(
5218                workspace
5219                    .right_dock()
5220                    .read(cx)
5221                    .visible_panel()
5222                    .unwrap()
5223                    .panel_id(),
5224                panel_1.panel_id(),
5225            );
5226        });
5227
5228        // Move panel_1 back to the left
5229        panel_1.update(cx, |panel_1, cx| {
5230            panel_1.set_position(DockPosition::Left, cx)
5231        });
5232
5233        workspace.update(cx, |workspace, cx| {
5234            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5235            let left_dock = workspace.left_dock();
5236            assert!(left_dock.read(cx).is_open());
5237            assert_eq!(
5238                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5239                panel_1.panel_id()
5240            );
5241            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5242            // And the right dock should be closed as it no longer has any panels.
5243            assert!(!workspace.right_dock().read(cx).is_open());
5244
5245            // Now we move panel_1 to the bottom
5246            panel_1.set_position(DockPosition::Bottom, cx);
5247        });
5248
5249        workspace.update(cx, |workspace, cx| {
5250            // Since panel_1 was visible on the left, we close the left dock.
5251            assert!(!workspace.left_dock().read(cx).is_open());
5252            // The bottom dock is sized based on the panel's default size,
5253            // since the panel orientation changed from vertical to horizontal.
5254            let bottom_dock = workspace.bottom_dock();
5255            assert_eq!(
5256                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5257                panel_1.size(cx),
5258            );
5259            // Close bottom dock and move panel_1 back to the left.
5260            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5261            panel_1.set_position(DockPosition::Left, cx);
5262        });
5263
5264        // Emit activated event on panel 1
5265        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5266
5267        // Now the left dock is open and panel_1 is active and focused.
5268        workspace.update(cx, |workspace, cx| {
5269            let left_dock = workspace.left_dock();
5270            assert!(left_dock.read(cx).is_open());
5271            assert_eq!(
5272                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5273                panel_1.panel_id(),
5274            );
5275            assert!(panel_1.focus_handle(cx).is_focused(cx));
5276        });
5277
5278        // Emit closed event on panel 2, which is not active
5279        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5280
5281        // Wo don't close the left dock, because panel_2 wasn't the active panel
5282        workspace.update(cx, |workspace, cx| {
5283            let left_dock = workspace.left_dock();
5284            assert!(left_dock.read(cx).is_open());
5285            assert_eq!(
5286                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5287                panel_1.panel_id(),
5288            );
5289        });
5290
5291        // Emitting a ZoomIn event shows the panel as zoomed.
5292        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5293        workspace.update(cx, |workspace, _| {
5294            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5295            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5296        });
5297
5298        // Move panel to another dock while it is zoomed
5299        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5300        workspace.update(cx, |workspace, _| {
5301            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5302
5303            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5304        });
5305
5306        // This is a helper for getting a:
5307        // - valid focus on an element,
5308        // - that isn't a part of the panes and panels system of the Workspace,
5309        // - and doesn't trigger the 'on_focus_lost' API.
5310        let focus_other_view = {
5311            let workspace = workspace.clone();
5312            move |cx: &mut VisualTestContext| {
5313                workspace.update(cx, |workspace, cx| {
5314                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5315                        workspace.toggle_modal(cx, TestModal::new);
5316                        workspace.toggle_modal(cx, TestModal::new);
5317                    } else {
5318                        workspace.toggle_modal(cx, TestModal::new);
5319                    }
5320                })
5321            }
5322        };
5323
5324        // If focus is transferred to another view that's not a panel or another pane, we still show
5325        // the panel as zoomed.
5326        focus_other_view(cx);
5327        workspace.update(cx, |workspace, _| {
5328            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5329            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5330        });
5331
5332        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5333        workspace.update(cx, |_, cx| cx.focus_self());
5334        workspace.update(cx, |workspace, _| {
5335            assert_eq!(workspace.zoomed, None);
5336            assert_eq!(workspace.zoomed_position, None);
5337        });
5338
5339        // If focus is transferred again to another view that's not a panel or a pane, we won't
5340        // show the panel as zoomed because it wasn't zoomed before.
5341        focus_other_view(cx);
5342        workspace.update(cx, |workspace, _| {
5343            assert_eq!(workspace.zoomed, None);
5344            assert_eq!(workspace.zoomed_position, None);
5345        });
5346
5347        // When the panel is activated, it is zoomed again.
5348        cx.dispatch_action(ToggleRightDock);
5349        workspace.update(cx, |workspace, _| {
5350            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5351            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5352        });
5353
5354        // Emitting a ZoomOut event unzooms the panel.
5355        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5356        workspace.update(cx, |workspace, _| {
5357            assert_eq!(workspace.zoomed, None);
5358            assert_eq!(workspace.zoomed_position, None);
5359        });
5360
5361        // Emit closed event on panel 1, which is active
5362        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5363
5364        // Now the left dock is closed, because panel_1 was the active panel
5365        workspace.update(cx, |workspace, cx| {
5366            let right_dock = workspace.right_dock();
5367            assert!(!right_dock.read(cx).is_open());
5368        });
5369    }
5370
5371    pub fn init_test(cx: &mut TestAppContext) {
5372        cx.update(|cx| {
5373            let settings_store = SettingsStore::test(cx);
5374            cx.set_global(settings_store);
5375            theme::init(theme::LoadThemes::JustBase, cx);
5376            language::init(cx);
5377            crate::init_settings(cx);
5378            Project::init_settings(cx);
5379        });
5380    }
5381}