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