workspace.rs

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