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