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