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