workspace.rs

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