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