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