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