workspace.rs

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