workspace.rs

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