workspace.rs

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