workspace2.rs

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