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