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