workspace.rs

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