workspace.rs

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