workspace.rs

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