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;
  15mod workspace_settings;
  16
  17use anyhow::{anyhow, Context, Result};
  18use assets::Assets;
  19use call::ActiveCall;
  20use client::{
  21    proto::{self, PeerId},
  22    Client, TypedEnvelope, UserStore,
  23};
  24use collections::{hash_map, HashMap, HashSet};
  25use drag_and_drop::DragAndDrop;
  26use futures::{
  27    channel::{mpsc, oneshot},
  28    future::try_join_all,
  29    FutureExt, StreamExt,
  30};
  31use gpui::{
  32    actions,
  33    elements::*,
  34    geometry::{
  35        rect::RectF,
  36        vector::{vec2f, Vector2F},
  37    },
  38    impl_actions,
  39    platform::{
  40        CursorStyle, MouseButton, PathPromptOptions, Platform, PromptLevel, WindowBounds,
  41        WindowOptions,
  42    },
  43    AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
  44    ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
  45    WeakViewHandle, WindowContext,
  46};
  47use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
  48use itertools::Itertools;
  49use language::{LanguageRegistry, Rope};
  50use std::{
  51    any::TypeId,
  52    borrow::Cow,
  53    cmp, env,
  54    future::Future,
  55    path::{Path, PathBuf},
  56    rc::Rc,
  57    str,
  58    sync::{atomic::AtomicUsize, Arc},
  59    time::Duration,
  60};
  61
  62use crate::{
  63    notifications::simple_message_notification::MessageNotification,
  64    persistence::model::{
  65        DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
  66    },
  67};
  68use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  69use lazy_static::lazy_static;
  70use notifications::{NotificationHandle, NotifyResultExt};
  71pub use pane::*;
  72pub use pane_group::*;
  73use persistence::{model::SerializedItem, DB};
  74pub use persistence::{
  75    model::{ItemId, WorkspaceLocation},
  76    WorkspaceDb, DB as WORKSPACE_DB,
  77};
  78use postage::prelude::Stream;
  79use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  80use serde::Deserialize;
  81use shared_screen::SharedScreen;
  82use status_bar::StatusBar;
  83pub use status_bar::StatusItemView;
  84use theme::Theme;
  85pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
  86use util::{async_iife, paths, ResultExt};
  87pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
  88
  89lazy_static! {
  90    static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
  91        .ok()
  92        .as_deref()
  93        .and_then(parse_pixel_position_env_var);
  94    static ref ZED_WINDOW_POSITION: Option<Vector2F> = env::var("ZED_WINDOW_POSITION")
  95        .ok()
  96        .as_deref()
  97        .and_then(parse_pixel_position_env_var);
  98}
  99
 100pub trait Modal: View {
 101    fn dismiss_on_event(event: &Self::Event) -> bool;
 102}
 103
 104#[derive(Clone, PartialEq)]
 105pub struct RemoveWorktreeFromProject(pub WorktreeId);
 106
 107actions!(
 108    workspace,
 109    [
 110        Open,
 111        NewFile,
 112        NewWindow,
 113        CloseWindow,
 114        AddFolderToProject,
 115        Unfollow,
 116        Save,
 117        SaveAs,
 118        SaveAll,
 119        ActivatePreviousPane,
 120        ActivateNextPane,
 121        FollowNextCollaborator,
 122        NewTerminal,
 123        NewCenterTerminal,
 124        ToggleTerminalFocus,
 125        NewSearch,
 126        Feedback,
 127        Restart,
 128        Welcome,
 129        ToggleZoom,
 130        ToggleLeftDock,
 131        ToggleRightDock,
 132        ToggleBottomDock,
 133    ]
 134);
 135
 136actions!(zed, [OpenSettings]);
 137
 138#[derive(Clone, PartialEq)]
 139pub struct OpenPaths {
 140    pub paths: Vec<PathBuf>,
 141}
 142
 143#[derive(Clone, Deserialize, PartialEq)]
 144pub struct ActivatePane(pub usize);
 145
 146pub struct Toast {
 147    id: usize,
 148    msg: Cow<'static, str>,
 149    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 150}
 151
 152impl Toast {
 153    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 154        Toast {
 155            id,
 156            msg: msg.into(),
 157            on_click: None,
 158        }
 159    }
 160
 161    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 162    where
 163        M: Into<Cow<'static, str>>,
 164        F: Fn(&mut WindowContext) + 'static,
 165    {
 166        self.on_click = Some((message.into(), Arc::new(on_click)));
 167        self
 168    }
 169}
 170
 171impl PartialEq for Toast {
 172    fn eq(&self, other: &Self) -> bool {
 173        self.id == other.id
 174            && self.msg == other.msg
 175            && self.on_click.is_some() == other.on_click.is_some()
 176    }
 177}
 178
 179impl Clone for Toast {
 180    fn clone(&self) -> Self {
 181        Toast {
 182            id: self.id,
 183            msg: self.msg.to_owned(),
 184            on_click: self.on_click.clone(),
 185        }
 186    }
 187}
 188
 189pub type WorkspaceId = i64;
 190
 191impl_actions!(workspace, [ActivatePane]);
 192
 193pub fn init_settings(cx: &mut AppContext) {
 194    settings::register::<WorkspaceSettings>(cx);
 195}
 196
 197pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 198    init_settings(cx);
 199    pane::init(cx);
 200    notifications::init(cx);
 201
 202    cx.add_global_action({
 203        let app_state = Arc::downgrade(&app_state);
 204        move |_: &Open, cx: &mut AppContext| {
 205            let mut paths = cx.prompt_for_paths(PathPromptOptions {
 206                files: true,
 207                directories: true,
 208                multiple: true,
 209            });
 210
 211            if let Some(app_state) = app_state.upgrade() {
 212                cx.spawn(move |mut cx| async move {
 213                    if let Some(paths) = paths.recv().await.flatten() {
 214                        cx.update(|cx| {
 215                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 216                        });
 217                    }
 218                })
 219                .detach();
 220            }
 221        }
 222    });
 223    cx.add_async_action(Workspace::open);
 224
 225    cx.add_async_action(Workspace::follow_next_collaborator);
 226    cx.add_async_action(Workspace::close);
 227    cx.add_global_action(Workspace::close_global);
 228    cx.add_global_action(restart);
 229    cx.add_async_action(Workspace::save_all);
 230    cx.add_action(Workspace::add_folder_to_project);
 231    cx.add_action(
 232        |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
 233            let pane = workspace.active_pane().clone();
 234            workspace.unfollow(&pane, cx);
 235        },
 236    );
 237    cx.add_action(
 238        |workspace: &mut Workspace, _: &Save, cx: &mut ViewContext<Workspace>| {
 239            workspace.save_active_item(false, cx).detach_and_log_err(cx);
 240        },
 241    );
 242    cx.add_action(
 243        |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
 244            workspace.save_active_item(true, cx).detach_and_log_err(cx);
 245        },
 246    );
 247    cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
 248        workspace.activate_previous_pane(cx)
 249    });
 250    cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
 251        workspace.activate_next_pane(cx)
 252    });
 253    cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
 254        workspace.toggle_dock(DockPosition::Left, cx);
 255    });
 256    cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
 257        workspace.toggle_dock(DockPosition::Right, cx);
 258    });
 259    cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
 260        workspace.toggle_dock(DockPosition::Bottom, cx);
 261    });
 262    cx.add_action(Workspace::activate_pane_at_index);
 263    cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
 264        workspace.reopen_closed_item(cx).detach();
 265    });
 266    cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
 267        workspace
 268            .go_back(workspace.active_pane().downgrade(), cx)
 269            .detach();
 270    });
 271    cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
 272        workspace
 273            .go_forward(workspace.active_pane().downgrade(), cx)
 274            .detach();
 275    });
 276
 277    cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
 278        cx.spawn(|workspace, mut cx| async move {
 279            let err = install_cli::install_cli(&cx)
 280                .await
 281                .context("Failed to create CLI symlink");
 282
 283            workspace.update(&mut cx, |workspace, cx| {
 284                if matches!(err, Err(_)) {
 285                    err.notify_err(workspace, cx);
 286                } else {
 287                    workspace.show_notification(1, cx, |cx| {
 288                        cx.add_view(|_| {
 289                            MessageNotification::new("Successfully installed the `zed` binary")
 290                        })
 291                    });
 292                }
 293            })
 294        })
 295        .detach();
 296    });
 297
 298    cx.add_action(
 299        move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
 300            create_and_open_local_file(&paths::SETTINGS, cx, || {
 301                settings::initial_user_settings_content(&Assets)
 302                    .as_ref()
 303                    .into()
 304            })
 305            .detach_and_log_err(cx);
 306        },
 307    );
 308
 309    let client = &app_state.client;
 310    client.add_view_request_handler(Workspace::handle_follow);
 311    client.add_view_message_handler(Workspace::handle_unfollow);
 312    client.add_view_message_handler(Workspace::handle_update_followers);
 313}
 314
 315type ProjectItemBuilders = HashMap<
 316    TypeId,
 317    fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
 318>;
 319pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 320    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
 321        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 322            let item = model.downcast::<I::Item>().unwrap();
 323            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
 324        });
 325    });
 326}
 327
 328type FollowableItemBuilder = fn(
 329    ViewHandle<Pane>,
 330    ModelHandle<Project>,
 331    ViewId,
 332    &mut Option<proto::view::Variant>,
 333    &mut AppContext,
 334) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 335type FollowableItemBuilders = HashMap<
 336    TypeId,
 337    (
 338        FollowableItemBuilder,
 339        fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
 340    ),
 341>;
 342pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 343    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
 344        builders.insert(
 345            TypeId::of::<I>(),
 346            (
 347                |pane, project, id, state, cx| {
 348                    I::from_state_proto(pane, project, id, state, cx).map(|task| {
 349                        cx.foreground()
 350                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 351                    })
 352                },
 353                |this| Box::new(this.clone().downcast::<I>().unwrap()),
 354            ),
 355        );
 356    });
 357}
 358
 359type ItemDeserializers = HashMap<
 360    Arc<str>,
 361    fn(
 362        ModelHandle<Project>,
 363        WeakViewHandle<Workspace>,
 364        WorkspaceId,
 365        ItemId,
 366        &mut ViewContext<Pane>,
 367    ) -> Task<Result<Box<dyn ItemHandle>>>,
 368>;
 369pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 370    cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
 371        if let Some(serialized_item_kind) = I::serialized_item_kind() {
 372            deserializers.insert(
 373                Arc::from(serialized_item_kind),
 374                |project, workspace, workspace_id, item_id, cx| {
 375                    let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 376                    cx.foreground()
 377                        .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 378                },
 379            );
 380        }
 381    });
 382}
 383
 384pub struct AppState {
 385    pub languages: Arc<LanguageRegistry>,
 386    pub client: Arc<client::Client>,
 387    pub user_store: ModelHandle<client::UserStore>,
 388    pub fs: Arc<dyn fs::Fs>,
 389    pub build_window_options:
 390        fn(Option<WindowBounds>, Option<uuid::Uuid>, &dyn Platform) -> WindowOptions<'static>,
 391    pub initialize_workspace:
 392        fn(WeakViewHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<Result<()>>,
 393    pub background_actions: BackgroundActions,
 394}
 395
 396impl AppState {
 397    #[cfg(any(test, feature = "test-support"))]
 398    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 399        use settings::SettingsStore;
 400
 401        if !cx.has_global::<SettingsStore>() {
 402            cx.set_global(SettingsStore::test(cx));
 403        }
 404
 405        let fs = fs::FakeFs::new(cx.background().clone());
 406        let languages = Arc::new(LanguageRegistry::test());
 407        let http_client = util::http::FakeHttpClient::with_404_response();
 408        let client = Client::new(http_client.clone(), cx);
 409        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 410
 411        theme::init((), cx);
 412        client::init(&client, cx);
 413        crate::init_settings(cx);
 414
 415        Arc::new(Self {
 416            client,
 417            fs,
 418            languages,
 419            user_store,
 420            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
 421            build_window_options: |_, _, _| Default::default(),
 422            background_actions: || &[],
 423        })
 424    }
 425}
 426
 427struct DelayedDebouncedEditAction {
 428    task: Option<Task<()>>,
 429    cancel_channel: Option<oneshot::Sender<()>>,
 430}
 431
 432impl DelayedDebouncedEditAction {
 433    fn new() -> DelayedDebouncedEditAction {
 434        DelayedDebouncedEditAction {
 435            task: None,
 436            cancel_channel: None,
 437        }
 438    }
 439
 440    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 441    where
 442        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 443    {
 444        if let Some(channel) = self.cancel_channel.take() {
 445            _ = channel.send(());
 446        }
 447
 448        let (sender, mut receiver) = oneshot::channel::<()>();
 449        self.cancel_channel = Some(sender);
 450
 451        let previous_task = self.task.take();
 452        self.task = Some(cx.spawn(|workspace, mut cx| async move {
 453            let mut timer = cx.background().timer(delay).fuse();
 454            if let Some(previous_task) = previous_task {
 455                previous_task.await;
 456            }
 457
 458            futures::select_biased! {
 459                _ = receiver => return,
 460                    _ = timer => {}
 461            }
 462
 463            if let Some(result) = workspace
 464                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 465                .log_err()
 466            {
 467                result.await.log_err();
 468            }
 469        }));
 470    }
 471}
 472
 473pub enum Event {
 474    PaneAdded(ViewHandle<Pane>),
 475    ContactRequestedJoin(u64),
 476}
 477
 478pub struct Workspace {
 479    weak_self: WeakViewHandle<Self>,
 480    remote_entity_subscription: Option<client::Subscription>,
 481    modal: Option<AnyViewHandle>,
 482    zoomed: Option<AnyWeakViewHandle>,
 483    zoomed_position: Option<DockPosition>,
 484    center: PaneGroup,
 485    left_dock: ViewHandle<Dock>,
 486    bottom_dock: ViewHandle<Dock>,
 487    right_dock: ViewHandle<Dock>,
 488    panes: Vec<ViewHandle<Pane>>,
 489    panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
 490    active_pane: ViewHandle<Pane>,
 491    last_active_center_pane: Option<WeakViewHandle<Pane>>,
 492    status_bar: ViewHandle<StatusBar>,
 493    titlebar_item: Option<AnyViewHandle>,
 494    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 495    project: ModelHandle<Project>,
 496    leader_state: LeaderState,
 497    follower_states_by_leader: FollowerStatesByLeader,
 498    last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
 499    window_edited: bool,
 500    active_call: Option<(ModelHandle<ActiveCall>, Vec<gpui::Subscription>)>,
 501    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 502    database_id: WorkspaceId,
 503    app_state: Arc<AppState>,
 504    subscriptions: Vec<Subscription>,
 505    _apply_leader_updates: Task<Result<()>>,
 506    _observe_current_user: Task<Result<()>>,
 507    pane_history_timestamp: Arc<AtomicUsize>,
 508}
 509
 510#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 511pub struct ViewId {
 512    pub creator: PeerId,
 513    pub id: u64,
 514}
 515
 516#[derive(Default)]
 517struct LeaderState {
 518    followers: HashSet<PeerId>,
 519}
 520
 521type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
 522
 523#[derive(Default)]
 524struct FollowerState {
 525    active_view_id: Option<ViewId>,
 526    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 527}
 528
 529impl Workspace {
 530    pub fn new(
 531        workspace_id: WorkspaceId,
 532        project: ModelHandle<Project>,
 533        app_state: Arc<AppState>,
 534        cx: &mut ViewContext<Self>,
 535    ) -> Self {
 536        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 537        cx.subscribe(&project, move |this, _, event, cx| {
 538            match event {
 539                project::Event::RemoteIdChanged(remote_id) => {
 540                    this.update_window_title(cx);
 541                    this.project_remote_id_changed(*remote_id, cx);
 542                }
 543
 544                project::Event::CollaboratorLeft(peer_id) => {
 545                    this.collaborator_left(*peer_id, cx);
 546                }
 547
 548                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 549                    this.update_window_title(cx);
 550                    this.serialize_workspace(cx);
 551                }
 552
 553                project::Event::DisconnectedFromHost => {
 554                    this.update_window_edited(cx);
 555                    cx.blur();
 556                }
 557
 558                project::Event::Closed => {
 559                    cx.remove_window();
 560                }
 561
 562                project::Event::DeletedEntry(entry_id) => {
 563                    for pane in this.panes.iter() {
 564                        pane.update(cx, |pane, cx| {
 565                            pane.handle_deleted_project_item(*entry_id, cx)
 566                        });
 567                    }
 568                }
 569
 570                _ => {}
 571            }
 572            cx.notify()
 573        })
 574        .detach();
 575
 576        let weak_handle = cx.weak_handle();
 577        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 578
 579        let center_pane = cx.add_view(|cx| {
 580            Pane::new(
 581                weak_handle.clone(),
 582                project.clone(),
 583                app_state.background_actions,
 584                pane_history_timestamp.clone(),
 585                cx,
 586            )
 587        });
 588        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 589        cx.focus(&center_pane);
 590        cx.emit(Event::PaneAdded(center_pane.clone()));
 591
 592        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 593        let mut connection_status = app_state.client.status();
 594        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 595            current_user.recv().await;
 596            connection_status.recv().await;
 597            let mut stream =
 598                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 599
 600            while stream.recv().await.is_some() {
 601                this.update(&mut cx, |_, cx| cx.notify())?;
 602            }
 603            anyhow::Ok(())
 604        });
 605
 606        // All leader updates are enqueued and then processed in a single task, so
 607        // that each asynchronous operation can be run in order.
 608        let (leader_updates_tx, mut leader_updates_rx) =
 609            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 610        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 611            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 612                Self::process_leader_update(&this, leader_id, update, &mut cx)
 613                    .await
 614                    .log_err();
 615            }
 616
 617            Ok(())
 618        });
 619
 620        cx.emit_global(WorkspaceCreated(weak_handle.clone()));
 621
 622        let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
 623        let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
 624        let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
 625        let left_dock_buttons =
 626            cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
 627        let bottom_dock_buttons =
 628            cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
 629        let right_dock_buttons =
 630            cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
 631        let status_bar = cx.add_view(|cx| {
 632            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 633            status_bar.add_left_item(left_dock_buttons, cx);
 634            status_bar.add_right_item(right_dock_buttons, cx);
 635            status_bar.add_right_item(bottom_dock_buttons, cx);
 636            status_bar
 637        });
 638
 639        cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
 640            drag_and_drop.register_container(weak_handle.clone());
 641        });
 642
 643        let mut active_call = None;
 644        if cx.has_global::<ModelHandle<ActiveCall>>() {
 645            let call = cx.global::<ModelHandle<ActiveCall>>().clone();
 646            let mut subscriptions = Vec::new();
 647            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 648            active_call = Some((call, subscriptions));
 649        }
 650
 651        let subscriptions = vec![
 652            cx.observe_fullscreen(|_, _, cx| cx.notify()),
 653            cx.observe_window_activation(Self::on_window_activation_changed),
 654            cx.observe_window_bounds(move |_, mut bounds, display, cx| {
 655                // Transform fixed bounds to be stored in terms of the containing display
 656                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 657                    if let Some(screen) = cx.platform().screen_by_id(display) {
 658                        let screen_bounds = screen.bounds();
 659                        window_bounds
 660                            .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
 661                        window_bounds
 662                            .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
 663                        bounds = WindowBounds::Fixed(window_bounds);
 664                    }
 665                }
 666
 667                cx.background()
 668                    .spawn(DB.set_window_bounds(workspace_id, bounds, display))
 669                    .detach_and_log_err(cx);
 670            }),
 671            cx.observe(&left_dock, |this, _, cx| {
 672                this.serialize_workspace(cx);
 673                cx.notify();
 674            }),
 675            cx.observe(&bottom_dock, |this, _, cx| {
 676                this.serialize_workspace(cx);
 677                cx.notify();
 678            }),
 679            cx.observe(&right_dock, |this, _, cx| {
 680                this.serialize_workspace(cx);
 681                cx.notify();
 682            }),
 683        ];
 684
 685        let mut this = Workspace {
 686            weak_self: weak_handle.clone(),
 687            modal: None,
 688            zoomed: None,
 689            zoomed_position: None,
 690            center: PaneGroup::new(center_pane.clone()),
 691            panes: vec![center_pane.clone()],
 692            panes_by_item: Default::default(),
 693            active_pane: center_pane.clone(),
 694            last_active_center_pane: Some(center_pane.downgrade()),
 695            status_bar,
 696            titlebar_item: None,
 697            notifications: Default::default(),
 698            remote_entity_subscription: None,
 699            left_dock,
 700            bottom_dock,
 701            right_dock,
 702            project: project.clone(),
 703            leader_state: Default::default(),
 704            follower_states_by_leader: Default::default(),
 705            last_leaders_by_pane: Default::default(),
 706            window_edited: false,
 707            active_call,
 708            database_id: workspace_id,
 709            app_state,
 710            _observe_current_user,
 711            _apply_leader_updates,
 712            leader_updates_tx,
 713            subscriptions,
 714            pane_history_timestamp,
 715        };
 716        this.project_remote_id_changed(project.read(cx).remote_id(), cx);
 717        cx.defer(|this, cx| this.update_window_title(cx));
 718        this
 719    }
 720
 721    fn new_local(
 722        abs_paths: Vec<PathBuf>,
 723        app_state: Arc<AppState>,
 724        requesting_window_id: Option<usize>,
 725        cx: &mut AppContext,
 726    ) -> Task<(
 727        WeakViewHandle<Workspace>,
 728        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 729    )> {
 730        let project_handle = Project::local(
 731            app_state.client.clone(),
 732            app_state.user_store.clone(),
 733            app_state.languages.clone(),
 734            app_state.fs.clone(),
 735            cx,
 736        );
 737
 738        cx.spawn(|mut cx| async move {
 739            let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 740
 741            let paths_to_open = Arc::new(abs_paths);
 742
 743            // Get project paths for all of the abs_paths
 744            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 745            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 746                Vec::with_capacity(paths_to_open.len());
 747            for path in paths_to_open.iter().cloned() {
 748                if let Some((worktree, project_entry)) = cx
 749                    .update(|cx| {
 750                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 751                    })
 752                    .await
 753                    .log_err()
 754                {
 755                    worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
 756                    project_paths.push((path, Some(project_entry)));
 757                } else {
 758                    project_paths.push((path, None));
 759                }
 760            }
 761
 762            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 763                serialized_workspace.id
 764            } else {
 765                DB.next_id().await.unwrap_or(0)
 766            };
 767
 768            let window_bounds_override =
 769                ZED_WINDOW_POSITION
 770                    .zip(*ZED_WINDOW_SIZE)
 771                    .map(|(position, size)| {
 772                        WindowBounds::Fixed(RectF::new(
 773                            cx.platform().screens()[0].bounds().origin() + position,
 774                            size,
 775                        ))
 776                    });
 777
 778            let build_workspace = |cx: &mut ViewContext<Workspace>| {
 779                Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 780            };
 781
 782            let workspace = requesting_window_id
 783                .and_then(|window_id| {
 784                    cx.update(|cx| cx.replace_root_view(window_id, |cx| build_workspace(cx)))
 785                })
 786                .unwrap_or_else(|| {
 787                    let (bounds, display) = if let Some(bounds) = window_bounds_override {
 788                        (Some(bounds), None)
 789                    } else {
 790                        serialized_workspace
 791                            .as_ref()
 792                            .and_then(|serialized_workspace| {
 793                                let display = serialized_workspace.display?;
 794                                let mut bounds = serialized_workspace.bounds?;
 795
 796                                // Stored bounds are relative to the containing display.
 797                                // So convert back to global coordinates if that screen still exists
 798                                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 799                                    if let Some(screen) = cx.platform().screen_by_id(display) {
 800                                        let screen_bounds = screen.bounds();
 801                                        window_bounds.set_origin_x(
 802                                            window_bounds.origin_x() + screen_bounds.origin_x(),
 803                                        );
 804                                        window_bounds.set_origin_y(
 805                                            window_bounds.origin_y() + screen_bounds.origin_y(),
 806                                        );
 807                                        bounds = WindowBounds::Fixed(window_bounds);
 808                                    } else {
 809                                        // Screen no longer exists. Return none here.
 810                                        return None;
 811                                    }
 812                                }
 813
 814                                Some((bounds, display))
 815                            })
 816                            .unzip()
 817                    };
 818
 819                    // Use the serialized workspace to construct the new window
 820                    cx.add_window(
 821                        (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
 822                        |cx| build_workspace(cx),
 823                    )
 824                    .1
 825                });
 826
 827            (app_state.initialize_workspace)(
 828                workspace.downgrade(),
 829                serialized_workspace.is_some(),
 830                app_state.clone(),
 831                cx.clone(),
 832            )
 833            .await
 834            .log_err();
 835
 836            cx.update_window(workspace.window_id(), |cx| cx.activate_window());
 837
 838            let workspace = workspace.downgrade();
 839            notify_if_database_failed(&workspace, &mut cx);
 840            let opened_items = open_items(
 841                serialized_workspace,
 842                &workspace,
 843                project_paths,
 844                app_state,
 845                cx,
 846            )
 847            .await;
 848
 849            (workspace, opened_items)
 850        })
 851    }
 852
 853    pub fn weak_handle(&self) -> WeakViewHandle<Self> {
 854        self.weak_self.clone()
 855    }
 856
 857    pub fn left_dock(&self) -> &ViewHandle<Dock> {
 858        &self.left_dock
 859    }
 860
 861    pub fn bottom_dock(&self) -> &ViewHandle<Dock> {
 862        &self.bottom_dock
 863    }
 864
 865    pub fn right_dock(&self) -> &ViewHandle<Dock> {
 866        &self.right_dock
 867    }
 868
 869    pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
 870        let dock = match panel.position(cx) {
 871            DockPosition::Left => &self.left_dock,
 872            DockPosition::Bottom => &self.bottom_dock,
 873            DockPosition::Right => &self.right_dock,
 874        };
 875
 876        self.subscriptions.push(cx.subscribe(&panel, {
 877            let mut dock = dock.clone();
 878            let mut prev_position = panel.position(cx);
 879            move |this, panel, event, cx| {
 880                if T::should_change_position_on_event(event) {
 881                    let new_position = panel.read(cx).position(cx);
 882                    let mut was_visible = false;
 883                    dock.update(cx, |dock, cx| {
 884                        prev_position = new_position;
 885
 886                        was_visible = dock.is_open()
 887                            && dock
 888                                .visible_panel()
 889                                .map_or(false, |active_panel| active_panel.id() == panel.id());
 890                        dock.remove_panel(&panel, cx);
 891                    });
 892
 893                    if panel.is_zoomed(cx) {
 894                        this.zoomed_position = Some(new_position);
 895                    }
 896
 897                    dock = match panel.read(cx).position(cx) {
 898                        DockPosition::Left => &this.left_dock,
 899                        DockPosition::Bottom => &this.bottom_dock,
 900                        DockPosition::Right => &this.right_dock,
 901                    }
 902                    .clone();
 903                    dock.update(cx, |dock, cx| {
 904                        dock.add_panel(panel.clone(), cx);
 905                        if was_visible {
 906                            dock.set_open(true, cx);
 907                            dock.activate_panel(dock.panels_len() - 1, cx);
 908                        }
 909                    });
 910                } else if T::should_zoom_in_on_event(event) {
 911                    this.zoom_out(cx);
 912                    dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
 913                    if panel.has_focus(cx) {
 914                        this.zoomed = Some(panel.downgrade().into_any());
 915                        this.zoomed_position = Some(panel.read(cx).position(cx));
 916                    }
 917                } else if T::should_zoom_out_on_event(event) {
 918                    this.zoom_out(cx);
 919                } else if T::is_focus_event(event) {
 920                    if panel.is_zoomed(cx) {
 921                        this.zoomed = Some(panel.downgrade().into_any());
 922                        this.zoomed_position = Some(panel.read(cx).position(cx));
 923                    } else {
 924                        this.zoomed = None;
 925                        this.zoomed_position = None;
 926                    }
 927                    cx.notify();
 928                }
 929            }
 930        }));
 931
 932        dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
 933    }
 934
 935    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
 936        &self.status_bar
 937    }
 938
 939    pub fn app_state(&self) -> &Arc<AppState> {
 940        &self.app_state
 941    }
 942
 943    pub fn user_store(&self) -> &ModelHandle<UserStore> {
 944        &self.app_state.user_store
 945    }
 946
 947    pub fn project(&self) -> &ModelHandle<Project> {
 948        &self.project
 949    }
 950
 951    pub fn recent_navigation_history(
 952        &self,
 953        limit: Option<usize>,
 954        cx: &AppContext,
 955    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
 956        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
 957        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
 958        for pane in &self.panes {
 959            let pane = pane.read(cx);
 960            pane.nav_history()
 961                .for_each_entry(cx, |entry, (project_path, fs_path)| {
 962                    if let Some(fs_path) = &fs_path {
 963                        abs_paths_opened
 964                            .entry(fs_path.clone())
 965                            .or_default()
 966                            .insert(project_path.clone());
 967                    }
 968                    let timestamp = entry.timestamp;
 969                    match history.entry(project_path) {
 970                        hash_map::Entry::Occupied(mut entry) => {
 971                            let (old_fs_path, old_timestamp) = entry.get();
 972                            if &timestamp > old_timestamp {
 973                                assert_eq!(&fs_path, old_fs_path, "Inconsistent nav history");
 974                                entry.insert((fs_path, timestamp));
 975                            }
 976                        }
 977                        hash_map::Entry::Vacant(entry) => {
 978                            entry.insert((fs_path, timestamp));
 979                        }
 980                    }
 981                });
 982        }
 983
 984        history
 985            .into_iter()
 986            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
 987            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
 988            .rev()
 989            .filter(|(history_path, abs_path)| {
 990                let latest_project_path_opened = abs_path
 991                    .as_ref()
 992                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
 993                    .and_then(|project_paths| {
 994                        project_paths
 995                            .iter()
 996                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
 997                    });
 998
 999                match latest_project_path_opened {
1000                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
1001                    None => true,
1002                }
1003            })
1004            .take(limit.unwrap_or(usize::MAX))
1005            .collect()
1006    }
1007
1008    fn navigate_history(
1009        &mut self,
1010        pane: WeakViewHandle<Pane>,
1011        mode: NavigationMode,
1012        cx: &mut ViewContext<Workspace>,
1013    ) -> Task<Result<()>> {
1014        let to_load = if let Some(pane) = pane.upgrade(cx) {
1015            cx.focus(&pane);
1016
1017            pane.update(cx, |pane, cx| {
1018                loop {
1019                    // Retrieve the weak item handle from the history.
1020                    let entry = pane.nav_history_mut().pop(mode, cx)?;
1021
1022                    // If the item is still present in this pane, then activate it.
1023                    if let Some(index) = entry
1024                        .item
1025                        .upgrade(cx)
1026                        .and_then(|v| pane.index_for_item(v.as_ref()))
1027                    {
1028                        let prev_active_item_index = pane.active_item_index();
1029                        pane.nav_history_mut().set_mode(mode);
1030                        pane.activate_item(index, true, true, cx);
1031                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1032
1033                        let mut navigated = prev_active_item_index != pane.active_item_index();
1034                        if let Some(data) = entry.data {
1035                            navigated |= pane.active_item()?.navigate(data, cx);
1036                        }
1037
1038                        if navigated {
1039                            break None;
1040                        }
1041                    }
1042                    // If the item is no longer present in this pane, then retrieve its
1043                    // project path in order to reopen it.
1044                    else {
1045                        break pane
1046                            .nav_history()
1047                            .path_for_item(entry.item.id())
1048                            .map(|(project_path, _)| (project_path, entry));
1049                    }
1050                }
1051            })
1052        } else {
1053            None
1054        };
1055
1056        if let Some((project_path, entry)) = to_load {
1057            // If the item was no longer present, then load it again from its previous path.
1058            let task = self.load_path(project_path, cx);
1059            cx.spawn(|workspace, mut cx| async move {
1060                let task = task.await;
1061                let mut navigated = false;
1062                if let Some((project_entry_id, build_item)) = task.log_err() {
1063                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
1064                        pane.nav_history_mut().set_mode(mode);
1065                        pane.active_item().map(|p| p.id())
1066                    })?;
1067
1068                    pane.update(&mut cx, |pane, cx| {
1069                        let item = pane.open_item(project_entry_id, true, cx, build_item);
1070                        navigated |= Some(item.id()) != prev_active_item_id;
1071                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1072                        if let Some(data) = entry.data {
1073                            navigated |= item.navigate(data, cx);
1074                        }
1075                    })?;
1076                }
1077
1078                if !navigated {
1079                    workspace
1080                        .update(&mut cx, |workspace, cx| {
1081                            Self::navigate_history(workspace, pane, mode, cx)
1082                        })?
1083                        .await?;
1084                }
1085
1086                Ok(())
1087            })
1088        } else {
1089            Task::ready(Ok(()))
1090        }
1091    }
1092
1093    pub fn go_back(
1094        &mut self,
1095        pane: WeakViewHandle<Pane>,
1096        cx: &mut ViewContext<Workspace>,
1097    ) -> Task<Result<()>> {
1098        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1099    }
1100
1101    pub fn go_forward(
1102        &mut self,
1103        pane: WeakViewHandle<Pane>,
1104        cx: &mut ViewContext<Workspace>,
1105    ) -> Task<Result<()>> {
1106        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1107    }
1108
1109    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1110        self.navigate_history(
1111            self.active_pane().downgrade(),
1112            NavigationMode::ReopeningClosedItem,
1113            cx,
1114        )
1115    }
1116
1117    pub fn client(&self) -> &Client {
1118        &self.app_state.client
1119    }
1120
1121    pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
1122        self.titlebar_item = Some(item);
1123        cx.notify();
1124    }
1125
1126    pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
1127        self.titlebar_item.clone()
1128    }
1129
1130    /// Call the given callback with a workspace whose project is local.
1131    ///
1132    /// If the given workspace has a local project, then it will be passed
1133    /// to the callback. Otherwise, a new empty window will be created.
1134    pub fn with_local_workspace<T, F>(
1135        &mut self,
1136        cx: &mut ViewContext<Self>,
1137        callback: F,
1138    ) -> Task<Result<T>>
1139    where
1140        T: 'static,
1141        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1142    {
1143        if self.project.read(cx).is_local() {
1144            Task::Ready(Some(Ok(callback(self, cx))))
1145        } else {
1146            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1147            cx.spawn(|_vh, mut cx| async move {
1148                let (workspace, _) = task.await;
1149                workspace.update(&mut cx, callback)
1150            })
1151        }
1152    }
1153
1154    pub fn worktrees<'a>(
1155        &self,
1156        cx: &'a AppContext,
1157    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1158        self.project.read(cx).worktrees(cx)
1159    }
1160
1161    pub fn visible_worktrees<'a>(
1162        &self,
1163        cx: &'a AppContext,
1164    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1165        self.project.read(cx).visible_worktrees(cx)
1166    }
1167
1168    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1169        let futures = self
1170            .worktrees(cx)
1171            .filter_map(|worktree| worktree.read(cx).as_local())
1172            .map(|worktree| worktree.scan_complete())
1173            .collect::<Vec<_>>();
1174        async move {
1175            for future in futures {
1176                future.await;
1177            }
1178        }
1179    }
1180
1181    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1182        cx.spawn(|mut cx| async move {
1183            let id = cx
1184                .window_ids()
1185                .into_iter()
1186                .find(|&id| cx.window_is_active(id));
1187            if let Some(id) = id {
1188                //This can only get called when the window's project connection has been lost
1189                //so we don't need to prompt the user for anything and instead just close the window
1190                cx.remove_window(id);
1191            }
1192        })
1193        .detach();
1194    }
1195
1196    pub fn close(
1197        &mut self,
1198        _: &CloseWindow,
1199        cx: &mut ViewContext<Self>,
1200    ) -> Option<Task<Result<()>>> {
1201        let window_id = cx.window_id();
1202        let prepare = self.prepare_to_close(false, cx);
1203        Some(cx.spawn(|_, mut cx| async move {
1204            if prepare.await? {
1205                cx.remove_window(window_id);
1206            }
1207            Ok(())
1208        }))
1209    }
1210
1211    pub fn prepare_to_close(
1212        &mut self,
1213        quitting: bool,
1214        cx: &mut ViewContext<Self>,
1215    ) -> Task<Result<bool>> {
1216        let active_call = self.active_call().cloned();
1217        let window_id = cx.window_id();
1218
1219        cx.spawn(|this, mut cx| async move {
1220            let workspace_count = cx
1221                .window_ids()
1222                .into_iter()
1223                .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
1224                .count();
1225
1226            if let Some(active_call) = active_call {
1227                if !quitting
1228                    && workspace_count == 1
1229                    && active_call.read_with(&cx, |call, _| call.room().is_some())
1230                {
1231                    let answer = cx.prompt(
1232                        window_id,
1233                        PromptLevel::Warning,
1234                        "Do you want to leave the current call?",
1235                        &["Close window and hang up", "Cancel"],
1236                    );
1237
1238                    if let Some(mut answer) = answer {
1239                        if answer.next().await == Some(1) {
1240                            return anyhow::Ok(false);
1241                        } else {
1242                            active_call
1243                                .update(&mut cx, |call, cx| call.hang_up(cx))
1244                                .await
1245                                .log_err();
1246                        }
1247                    }
1248                }
1249            }
1250
1251            Ok(this
1252                .update(&mut cx, |this, cx| this.save_all_internal(true, cx))?
1253                .await?)
1254        })
1255    }
1256
1257    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1258        let save_all = self.save_all_internal(false, cx);
1259        Some(cx.foreground().spawn(async move {
1260            save_all.await?;
1261            Ok(())
1262        }))
1263    }
1264
1265    fn save_all_internal(
1266        &mut self,
1267        should_prompt_to_save: bool,
1268        cx: &mut ViewContext<Self>,
1269    ) -> Task<Result<bool>> {
1270        if self.project.read(cx).is_read_only() {
1271            return Task::ready(Ok(true));
1272        }
1273
1274        let dirty_items = self
1275            .panes
1276            .iter()
1277            .flat_map(|pane| {
1278                pane.read(cx).items().filter_map(|item| {
1279                    if item.is_dirty(cx) {
1280                        Some((pane.downgrade(), item.boxed_clone()))
1281                    } else {
1282                        None
1283                    }
1284                })
1285            })
1286            .collect::<Vec<_>>();
1287
1288        let project = self.project.clone();
1289        cx.spawn(|_, mut cx| async move {
1290            for (pane, item) in dirty_items {
1291                let (singleton, project_entry_ids) =
1292                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1293                if singleton || !project_entry_ids.is_empty() {
1294                    if let Some(ix) =
1295                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
1296                    {
1297                        if !Pane::save_item(
1298                            project.clone(),
1299                            &pane,
1300                            ix,
1301                            &*item,
1302                            should_prompt_to_save,
1303                            &mut cx,
1304                        )
1305                        .await?
1306                        {
1307                            return Ok(false);
1308                        }
1309                    }
1310                }
1311            }
1312            Ok(true)
1313        })
1314    }
1315
1316    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1317        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1318            files: true,
1319            directories: true,
1320            multiple: true,
1321        });
1322
1323        Some(cx.spawn(|this, mut cx| async move {
1324            if let Some(paths) = paths.recv().await.flatten() {
1325                if let Some(task) = this
1326                    .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1327                    .log_err()
1328                {
1329                    task.await?
1330                }
1331            }
1332            Ok(())
1333        }))
1334    }
1335
1336    pub fn open_workspace_for_paths(
1337        &mut self,
1338        paths: Vec<PathBuf>,
1339        cx: &mut ViewContext<Self>,
1340    ) -> Task<Result<()>> {
1341        let window_id = cx.window_id();
1342        let is_remote = self.project.read(cx).is_remote();
1343        let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
1344        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1345        let close_task = if is_remote || has_worktree || has_dirty_items {
1346            None
1347        } else {
1348            Some(self.prepare_to_close(false, cx))
1349        };
1350        let app_state = self.app_state.clone();
1351
1352        cx.spawn(|_, mut cx| async move {
1353            let window_id_to_replace = if let Some(close_task) = close_task {
1354                if !close_task.await? {
1355                    return Ok(());
1356                }
1357                Some(window_id)
1358            } else {
1359                None
1360            };
1361            cx.update(|cx| open_paths(&paths, &app_state, window_id_to_replace, cx))
1362                .await?;
1363            Ok(())
1364        })
1365    }
1366
1367    #[allow(clippy::type_complexity)]
1368    pub fn open_paths(
1369        &mut self,
1370        mut abs_paths: Vec<PathBuf>,
1371        visible: bool,
1372        cx: &mut ViewContext<Self>,
1373    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1374        log::info!("open paths {:?}", abs_paths);
1375
1376        let fs = self.app_state.fs.clone();
1377
1378        // Sort the paths to ensure we add worktrees for parents before their children.
1379        abs_paths.sort_unstable();
1380        cx.spawn(|this, mut cx| async move {
1381            let mut project_paths = Vec::new();
1382            for path in &abs_paths {
1383                if let Some(project_path) = this
1384                    .update(&mut cx, |this, cx| {
1385                        Workspace::project_path_for_path(this.project.clone(), path, visible, cx)
1386                    })
1387                    .log_err()
1388                {
1389                    project_paths.push(project_path.await.log_err());
1390                } else {
1391                    project_paths.push(None);
1392                }
1393            }
1394
1395            let tasks = abs_paths
1396                .iter()
1397                .cloned()
1398                .zip(project_paths.into_iter())
1399                .map(|(abs_path, project_path)| {
1400                    let this = this.clone();
1401                    cx.spawn(|mut cx| {
1402                        let fs = fs.clone();
1403                        async move {
1404                            let (_worktree, project_path) = project_path?;
1405                            if fs.is_file(&abs_path).await {
1406                                Some(
1407                                    this.update(&mut cx, |this, cx| {
1408                                        this.open_path(project_path, None, true, cx)
1409                                    })
1410                                    .log_err()?
1411                                    .await,
1412                                )
1413                            } else {
1414                                None
1415                            }
1416                        }
1417                    })
1418                })
1419                .collect::<Vec<_>>();
1420
1421            futures::future::join_all(tasks).await
1422        })
1423    }
1424
1425    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1426        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1427            files: false,
1428            directories: true,
1429            multiple: true,
1430        });
1431        cx.spawn(|this, mut cx| async move {
1432            if let Some(paths) = paths.recv().await.flatten() {
1433                let results = this
1434                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
1435                    .await;
1436                for result in results.into_iter().flatten() {
1437                    result.log_err();
1438                }
1439            }
1440            anyhow::Ok(())
1441        })
1442        .detach_and_log_err(cx);
1443    }
1444
1445    fn project_path_for_path(
1446        project: ModelHandle<Project>,
1447        abs_path: &Path,
1448        visible: bool,
1449        cx: &mut AppContext,
1450    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1451        let entry = project.update(cx, |project, cx| {
1452            project.find_or_create_local_worktree(abs_path, visible, cx)
1453        });
1454        cx.spawn(|cx| async move {
1455            let (worktree, path) = entry.await?;
1456            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1457            Ok((
1458                worktree,
1459                ProjectPath {
1460                    worktree_id,
1461                    path: path.into(),
1462                },
1463            ))
1464        })
1465    }
1466
1467    /// Returns the modal that was toggled closed if it was open.
1468    pub fn toggle_modal<V, F>(
1469        &mut self,
1470        cx: &mut ViewContext<Self>,
1471        add_view: F,
1472    ) -> Option<ViewHandle<V>>
1473    where
1474        V: 'static + Modal,
1475        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1476    {
1477        cx.notify();
1478        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1479        // it. Otherwise, create a new modal and set it as active.
1480        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1481        if let Some(already_open_modal) = already_open_modal {
1482            cx.focus_self();
1483            Some(already_open_modal)
1484        } else {
1485            let modal = add_view(self, cx);
1486            cx.subscribe(&modal, |this, _, event, cx| {
1487                if V::dismiss_on_event(event) {
1488                    this.dismiss_modal(cx);
1489                }
1490            })
1491            .detach();
1492            cx.focus(&modal);
1493            self.modal = Some(modal.into_any());
1494            None
1495        }
1496    }
1497
1498    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1499        self.modal
1500            .as_ref()
1501            .and_then(|modal| modal.clone().downcast::<V>())
1502    }
1503
1504    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1505        if self.modal.take().is_some() {
1506            cx.focus(&self.active_pane);
1507            cx.notify();
1508        }
1509    }
1510
1511    pub fn items<'a>(
1512        &'a self,
1513        cx: &'a AppContext,
1514    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1515        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1516    }
1517
1518    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1519        self.items_of_type(cx).max_by_key(|item| item.id())
1520    }
1521
1522    pub fn items_of_type<'a, T: Item>(
1523        &'a self,
1524        cx: &'a AppContext,
1525    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1526        self.panes
1527            .iter()
1528            .flat_map(|pane| pane.read(cx).items_of_type())
1529    }
1530
1531    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1532        self.active_pane().read(cx).active_item()
1533    }
1534
1535    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1536        self.active_item(cx).and_then(|item| item.project_path(cx))
1537    }
1538
1539    pub fn save_active_item(
1540        &mut self,
1541        force_name_change: bool,
1542        cx: &mut ViewContext<Self>,
1543    ) -> Task<Result<()>> {
1544        let project = self.project.clone();
1545        if let Some(item) = self.active_item(cx) {
1546            if !force_name_change && item.can_save(cx) {
1547                if item.has_conflict(cx) {
1548                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1549
1550                    let mut answer = cx.prompt(
1551                        PromptLevel::Warning,
1552                        CONFLICT_MESSAGE,
1553                        &["Overwrite", "Cancel"],
1554                    );
1555                    cx.spawn(|this, mut cx| async move {
1556                        let answer = answer.recv().await;
1557                        if answer == Some(0) {
1558                            this.update(&mut cx, |this, cx| item.save(this.project.clone(), cx))?
1559                                .await?;
1560                        }
1561                        Ok(())
1562                    })
1563                } else {
1564                    item.save(self.project.clone(), cx)
1565                }
1566            } else if item.is_singleton(cx) {
1567                let worktree = self.worktrees(cx).next();
1568                let start_abs_path = worktree
1569                    .and_then(|w| w.read(cx).as_local())
1570                    .map_or(Path::new(""), |w| w.abs_path())
1571                    .to_path_buf();
1572                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1573                cx.spawn(|this, mut cx| async move {
1574                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1575                        this.update(&mut cx, |_, cx| item.save_as(project, abs_path, cx))?
1576                            .await?;
1577                    }
1578                    Ok(())
1579                })
1580            } else {
1581                Task::ready(Ok(()))
1582            }
1583        } else {
1584            Task::ready(Ok(()))
1585        }
1586    }
1587
1588    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1589        let dock = match dock_side {
1590            DockPosition::Left => &self.left_dock,
1591            DockPosition::Bottom => &self.bottom_dock,
1592            DockPosition::Right => &self.right_dock,
1593        };
1594        let mut focus_center = false;
1595        let mut zoom_out = false;
1596        dock.update(cx, |dock, cx| {
1597            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1598            let was_visible = dock.is_open() && !other_is_zoomed;
1599            dock.set_open(!was_visible, cx);
1600
1601            if let Some(active_panel) = dock.active_panel() {
1602                if was_visible {
1603                    if active_panel.has_focus(cx) {
1604                        focus_center = true;
1605                    }
1606                } else {
1607                    if active_panel.is_zoomed(cx) {
1608                        cx.focus(active_panel.as_any());
1609                    }
1610                    zoom_out = true;
1611                }
1612            }
1613        });
1614
1615        if zoom_out {
1616            self.zoom_out_everything_except(dock_side, cx);
1617        }
1618        if focus_center {
1619            cx.focus_self();
1620        }
1621
1622        cx.notify();
1623        self.serialize_workspace(cx);
1624    }
1625
1626    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
1627        self.show_or_hide_panel::<T>(cx, |_, _| true)?
1628            .as_any()
1629            .clone()
1630            .downcast()
1631    }
1632
1633    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1634        self.show_or_hide_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
1635    }
1636
1637    fn show_or_hide_panel<T: Panel>(
1638        &mut self,
1639        cx: &mut ViewContext<Self>,
1640        show: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1641    ) -> Option<Rc<dyn PanelHandle>> {
1642        for (dock, position) in [
1643            self.left_dock.clone(),
1644            self.bottom_dock.clone(),
1645            self.right_dock.clone(),
1646        ]
1647        .into_iter()
1648        .zip(
1649            [
1650                DockPosition::Left,
1651                DockPosition::Bottom,
1652                DockPosition::Right,
1653            ]
1654            .into_iter(),
1655        ) {
1656            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1657                let mut focus_center = false;
1658                let mut zoom_out = false;
1659                let panel = dock.update(cx, |dock, cx| {
1660                    dock.activate_panel(panel_index, cx);
1661
1662                    let panel = dock.active_panel().cloned();
1663                    if let Some(panel) = panel.as_ref() {
1664                        let should_show = show(&**panel, cx);
1665                        if should_show {
1666                            dock.set_open(true, cx);
1667                            cx.focus(panel.as_any());
1668                            zoom_out = true;
1669                        } else {
1670                            if panel.is_zoomed(cx) {
1671                                dock.set_open(false, cx);
1672                            }
1673                            focus_center = true;
1674                        }
1675                    }
1676                    panel
1677                });
1678
1679                if zoom_out {
1680                    self.zoom_out_everything_except(position, cx);
1681                }
1682                if focus_center {
1683                    cx.focus_self();
1684                }
1685
1686                self.serialize_workspace(cx);
1687                cx.notify();
1688                return panel;
1689            }
1690        }
1691        None
1692    }
1693
1694    fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
1695        for pane in &self.panes {
1696            pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1697        }
1698
1699        self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1700        self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1701        self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1702        self.zoomed = None;
1703        self.zoomed_position = None;
1704
1705        cx.notify();
1706    }
1707
1708    fn zoom_out_everything_except(
1709        &mut self,
1710        except_position: DockPosition,
1711        cx: &mut ViewContext<Self>,
1712    ) {
1713        for pane in &self.panes {
1714            pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1715        }
1716
1717        if except_position != DockPosition::Left {
1718            self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1719        }
1720
1721        if except_position != DockPosition::Bottom {
1722            self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1723        }
1724
1725        if except_position != DockPosition::Right {
1726            self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1727        }
1728
1729        if self.zoomed_position != Some(except_position) {
1730            self.zoomed = None;
1731            self.zoomed_position = None;
1732        }
1733
1734        cx.notify();
1735    }
1736
1737    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1738        let pane = cx.add_view(|cx| {
1739            Pane::new(
1740                self.weak_handle(),
1741                self.project.clone(),
1742                self.app_state.background_actions,
1743                self.pane_history_timestamp.clone(),
1744                cx,
1745            )
1746        });
1747        cx.subscribe(&pane, Self::handle_pane_event).detach();
1748        self.panes.push(pane.clone());
1749        cx.focus(&pane);
1750        cx.emit(Event::PaneAdded(pane.clone()));
1751        pane
1752    }
1753
1754    pub fn add_item_to_center(
1755        &mut self,
1756        item: Box<dyn ItemHandle>,
1757        cx: &mut ViewContext<Self>,
1758    ) -> bool {
1759        if let Some(center_pane) = self.last_active_center_pane.clone() {
1760            if let Some(center_pane) = center_pane.upgrade(cx) {
1761                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1762                true
1763            } else {
1764                false
1765            }
1766        } else {
1767            false
1768        }
1769    }
1770
1771    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1772        self.active_pane
1773            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1774    }
1775
1776    pub fn open_abs_path(
1777        &mut self,
1778        abs_path: PathBuf,
1779        visible: bool,
1780        cx: &mut ViewContext<Self>,
1781    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1782        cx.spawn(|workspace, mut cx| async move {
1783            let open_paths_task_result = workspace
1784                .update(&mut cx, |workspace, cx| {
1785                    workspace.open_paths(vec![abs_path.clone()], visible, cx)
1786                })
1787                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1788                .await;
1789            anyhow::ensure!(
1790                open_paths_task_result.len() == 1,
1791                "open abs path {abs_path:?} task returned incorrect number of results"
1792            );
1793            match open_paths_task_result
1794                .into_iter()
1795                .next()
1796                .expect("ensured single task result")
1797            {
1798                Some(open_result) => {
1799                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1800                }
1801                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1802            }
1803        })
1804    }
1805
1806    pub fn open_path(
1807        &mut self,
1808        path: impl Into<ProjectPath>,
1809        pane: Option<WeakViewHandle<Pane>>,
1810        focus_item: bool,
1811        cx: &mut ViewContext<Self>,
1812    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1813        let pane = pane.unwrap_or_else(|| {
1814            self.last_active_center_pane.clone().unwrap_or_else(|| {
1815                self.panes
1816                    .first()
1817                    .expect("There must be an active pane")
1818                    .downgrade()
1819            })
1820        });
1821
1822        let task = self.load_path(path.into(), cx);
1823        cx.spawn(|_, mut cx| async move {
1824            let (project_entry_id, build_item) = task.await?;
1825            pane.update(&mut cx, |pane, cx| {
1826                pane.open_item(project_entry_id, focus_item, cx, build_item)
1827            })
1828        })
1829    }
1830
1831    pub(crate) fn load_path(
1832        &mut self,
1833        path: ProjectPath,
1834        cx: &mut ViewContext<Self>,
1835    ) -> Task<
1836        Result<(
1837            ProjectEntryId,
1838            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1839        )>,
1840    > {
1841        let project = self.project().clone();
1842        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1843        cx.spawn(|_, mut cx| async move {
1844            let (project_entry_id, project_item) = project_item.await?;
1845            let build_item = cx.update(|cx| {
1846                cx.default_global::<ProjectItemBuilders>()
1847                    .get(&project_item.model_type())
1848                    .ok_or_else(|| anyhow!("no item builder for project item"))
1849                    .cloned()
1850            })?;
1851            let build_item =
1852                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1853            Ok((project_entry_id, build_item))
1854        })
1855    }
1856
1857    pub fn open_project_item<T>(
1858        &mut self,
1859        project_item: ModelHandle<T::Item>,
1860        cx: &mut ViewContext<Self>,
1861    ) -> ViewHandle<T>
1862    where
1863        T: ProjectItem,
1864    {
1865        use project::Item as _;
1866
1867        let entry_id = project_item.read(cx).entry_id(cx);
1868        if let Some(item) = entry_id
1869            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1870            .and_then(|item| item.downcast())
1871        {
1872            self.activate_item(&item, cx);
1873            return item;
1874        }
1875
1876        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1877        self.add_item(Box::new(item.clone()), cx);
1878        item
1879    }
1880
1881    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1882        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
1883            self.active_pane.update(cx, |pane, cx| {
1884                pane.add_item(Box::new(shared_screen), false, true, None, cx)
1885            });
1886        }
1887    }
1888
1889    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1890        let result = self.panes.iter().find_map(|pane| {
1891            pane.read(cx)
1892                .index_for_item(item)
1893                .map(|ix| (pane.clone(), ix))
1894        });
1895        if let Some((pane, ix)) = result {
1896            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1897            true
1898        } else {
1899            false
1900        }
1901    }
1902
1903    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1904        let panes = self.center.panes();
1905        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1906            cx.focus(&pane);
1907        } else {
1908            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1909        }
1910    }
1911
1912    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1913        let panes = self.center.panes();
1914        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1915            let next_ix = (ix + 1) % panes.len();
1916            let next_pane = panes[next_ix].clone();
1917            cx.focus(&next_pane);
1918        }
1919    }
1920
1921    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1922        let panes = self.center.panes();
1923        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1924            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
1925            let prev_pane = panes[prev_ix].clone();
1926            cx.focus(&prev_pane);
1927        }
1928    }
1929
1930    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1931        if self.active_pane != pane {
1932            self.active_pane = pane.clone();
1933            self.status_bar.update(cx, |status_bar, cx| {
1934                status_bar.set_active_pane(&self.active_pane, cx);
1935            });
1936            self.active_item_path_changed(cx);
1937            self.last_active_center_pane = Some(pane.downgrade());
1938        }
1939
1940        if pane.read(cx).is_zoomed() {
1941            self.zoomed = Some(pane.downgrade().into_any());
1942        } else {
1943            self.zoomed = None;
1944        }
1945        self.zoomed_position = None;
1946
1947        self.update_followers(
1948            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1949                id: self.active_item(cx).and_then(|item| {
1950                    item.to_followable_item_handle(cx)?
1951                        .remote_id(&self.app_state.client, cx)
1952                        .map(|id| id.to_proto())
1953                }),
1954                leader_id: self.leader_for_pane(&pane),
1955            }),
1956            cx,
1957        );
1958
1959        cx.notify();
1960    }
1961
1962    fn handle_pane_event(
1963        &mut self,
1964        pane: ViewHandle<Pane>,
1965        event: &pane::Event,
1966        cx: &mut ViewContext<Self>,
1967    ) {
1968        match event {
1969            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
1970            pane::Event::Split(direction) => {
1971                self.split_pane(pane, *direction, cx);
1972            }
1973            pane::Event::Remove => self.remove_pane(pane, cx),
1974            pane::Event::ActivateItem { local } => {
1975                if *local {
1976                    self.unfollow(&pane, cx);
1977                }
1978                if &pane == self.active_pane() {
1979                    self.active_item_path_changed(cx);
1980                }
1981            }
1982            pane::Event::ChangeItemTitle => {
1983                if pane == self.active_pane {
1984                    self.active_item_path_changed(cx);
1985                }
1986                self.update_window_edited(cx);
1987            }
1988            pane::Event::RemoveItem { item_id } => {
1989                self.update_window_edited(cx);
1990                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
1991                    if entry.get().id() == pane.id() {
1992                        entry.remove();
1993                    }
1994                }
1995            }
1996            pane::Event::Focus => {
1997                self.handle_pane_focused(pane.clone(), cx);
1998            }
1999            pane::Event::ZoomIn => {
2000                if pane == self.active_pane {
2001                    self.zoom_out(cx);
2002                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2003                    if pane.read(cx).has_focus() {
2004                        self.zoomed = Some(pane.downgrade().into_any());
2005                        self.zoomed_position = None;
2006                    }
2007                    cx.notify();
2008                }
2009            }
2010            pane::Event::ZoomOut => self.zoom_out(cx),
2011        }
2012
2013        self.serialize_workspace(cx);
2014    }
2015
2016    pub fn split_pane(
2017        &mut self,
2018        pane: ViewHandle<Pane>,
2019        direction: SplitDirection,
2020        cx: &mut ViewContext<Self>,
2021    ) -> Option<ViewHandle<Pane>> {
2022        let item = pane.read(cx).active_item()?;
2023        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2024            let new_pane = self.add_pane(cx);
2025            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2026            self.center.split(&pane, &new_pane, direction).unwrap();
2027            Some(new_pane)
2028        } else {
2029            None
2030        };
2031        cx.notify();
2032        maybe_pane_handle
2033    }
2034
2035    pub fn split_pane_with_item(
2036        &mut self,
2037        pane_to_split: WeakViewHandle<Pane>,
2038        split_direction: SplitDirection,
2039        from: WeakViewHandle<Pane>,
2040        item_id_to_move: usize,
2041        cx: &mut ViewContext<Self>,
2042    ) {
2043        let Some(pane_to_split) = pane_to_split.upgrade(cx) else { return; };
2044        let Some(from) = from.upgrade(cx) else { return; };
2045
2046        let new_pane = self.add_pane(cx);
2047        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2048        self.center
2049            .split(&pane_to_split, &new_pane, split_direction)
2050            .unwrap();
2051        cx.notify();
2052    }
2053
2054    pub fn split_pane_with_project_entry(
2055        &mut self,
2056        pane_to_split: WeakViewHandle<Pane>,
2057        split_direction: SplitDirection,
2058        project_entry: ProjectEntryId,
2059        cx: &mut ViewContext<Self>,
2060    ) -> Option<Task<Result<()>>> {
2061        let pane_to_split = pane_to_split.upgrade(cx)?;
2062        let new_pane = self.add_pane(cx);
2063        self.center
2064            .split(&pane_to_split, &new_pane, split_direction)
2065            .unwrap();
2066
2067        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2068        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2069        Some(cx.foreground().spawn(async move {
2070            task.await?;
2071            Ok(())
2072        }))
2073    }
2074
2075    pub fn move_item(
2076        &mut self,
2077        source: ViewHandle<Pane>,
2078        destination: ViewHandle<Pane>,
2079        item_id_to_move: usize,
2080        destination_index: usize,
2081        cx: &mut ViewContext<Self>,
2082    ) {
2083        let item_to_move = source
2084            .read(cx)
2085            .items()
2086            .enumerate()
2087            .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
2088
2089        if item_to_move.is_none() {
2090            log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
2091            return;
2092        }
2093        let (item_ix, item_handle) = item_to_move.unwrap();
2094        let item_handle = item_handle.clone();
2095
2096        if source != destination {
2097            // Close item from previous pane
2098            source.update(cx, |source, cx| {
2099                source.remove_item(item_ix, false, cx);
2100            });
2101        }
2102
2103        // This automatically removes duplicate items in the pane
2104        destination.update(cx, |destination, cx| {
2105            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2106            cx.focus_self();
2107        });
2108    }
2109
2110    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2111        if self.center.remove(&pane).unwrap() {
2112            self.force_remove_pane(&pane, cx);
2113            self.unfollow(&pane, cx);
2114            self.last_leaders_by_pane.remove(&pane.downgrade());
2115            for removed_item in pane.read(cx).items() {
2116                self.panes_by_item.remove(&removed_item.id());
2117            }
2118
2119            cx.notify();
2120        } else {
2121            self.active_item_path_changed(cx);
2122        }
2123    }
2124
2125    pub fn panes(&self) -> &[ViewHandle<Pane>] {
2126        &self.panes
2127    }
2128
2129    pub fn active_pane(&self) -> &ViewHandle<Pane> {
2130        &self.active_pane
2131    }
2132
2133    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
2134        if let Some(remote_id) = remote_id {
2135            self.remote_entity_subscription = Some(
2136                self.app_state
2137                    .client
2138                    .add_view_for_remote_entity(remote_id, cx),
2139            );
2140        } else {
2141            self.remote_entity_subscription.take();
2142        }
2143    }
2144
2145    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2146        self.leader_state.followers.remove(&peer_id);
2147        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
2148            for state in states_by_pane.into_values() {
2149                for item in state.items_by_leader_view_id.into_values() {
2150                    item.set_leader_replica_id(None, cx);
2151                }
2152            }
2153        }
2154        cx.notify();
2155    }
2156
2157    pub fn toggle_follow(
2158        &mut self,
2159        leader_id: PeerId,
2160        cx: &mut ViewContext<Self>,
2161    ) -> Option<Task<Result<()>>> {
2162        let pane = self.active_pane().clone();
2163
2164        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
2165            if leader_id == prev_leader_id {
2166                return None;
2167            }
2168        }
2169
2170        self.last_leaders_by_pane
2171            .insert(pane.downgrade(), leader_id);
2172        self.follower_states_by_leader
2173            .entry(leader_id)
2174            .or_default()
2175            .insert(pane.clone(), Default::default());
2176        cx.notify();
2177
2178        let project_id = self.project.read(cx).remote_id()?;
2179        let request = self.app_state.client.request(proto::Follow {
2180            project_id,
2181            leader_id: Some(leader_id),
2182        });
2183
2184        Some(cx.spawn(|this, mut cx| async move {
2185            let response = request.await?;
2186            this.update(&mut cx, |this, _| {
2187                let state = this
2188                    .follower_states_by_leader
2189                    .get_mut(&leader_id)
2190                    .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
2191                    .ok_or_else(|| anyhow!("following interrupted"))?;
2192                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2193                    Some(ViewId::from_proto(active_view_id)?)
2194                } else {
2195                    None
2196                };
2197                Ok::<_, anyhow::Error>(())
2198            })??;
2199            Self::add_views_from_leader(
2200                this.clone(),
2201                leader_id,
2202                vec![pane],
2203                response.views,
2204                &mut cx,
2205            )
2206            .await?;
2207            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2208            Ok(())
2209        }))
2210    }
2211
2212    pub fn follow_next_collaborator(
2213        &mut self,
2214        _: &FollowNextCollaborator,
2215        cx: &mut ViewContext<Self>,
2216    ) -> Option<Task<Result<()>>> {
2217        let collaborators = self.project.read(cx).collaborators();
2218        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2219            let mut collaborators = collaborators.keys().copied();
2220            for peer_id in collaborators.by_ref() {
2221                if peer_id == leader_id {
2222                    break;
2223                }
2224            }
2225            collaborators.next()
2226        } else if let Some(last_leader_id) =
2227            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2228        {
2229            if collaborators.contains_key(last_leader_id) {
2230                Some(*last_leader_id)
2231            } else {
2232                None
2233            }
2234        } else {
2235            None
2236        };
2237
2238        next_leader_id
2239            .or_else(|| collaborators.keys().copied().next())
2240            .and_then(|leader_id| self.toggle_follow(leader_id, cx))
2241    }
2242
2243    pub fn unfollow(
2244        &mut self,
2245        pane: &ViewHandle<Pane>,
2246        cx: &mut ViewContext<Self>,
2247    ) -> Option<PeerId> {
2248        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2249            let leader_id = *leader_id;
2250            if let Some(state) = states_by_pane.remove(pane) {
2251                for (_, item) in state.items_by_leader_view_id {
2252                    item.set_leader_replica_id(None, cx);
2253                }
2254
2255                if states_by_pane.is_empty() {
2256                    self.follower_states_by_leader.remove(&leader_id);
2257                    if let Some(project_id) = self.project.read(cx).remote_id() {
2258                        self.app_state
2259                            .client
2260                            .send(proto::Unfollow {
2261                                project_id,
2262                                leader_id: Some(leader_id),
2263                            })
2264                            .log_err();
2265                    }
2266                }
2267
2268                cx.notify();
2269                return Some(leader_id);
2270            }
2271        }
2272        None
2273    }
2274
2275    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2276        self.follower_states_by_leader.contains_key(&peer_id)
2277    }
2278
2279    pub fn is_followed_by(&self, peer_id: PeerId) -> bool {
2280        self.leader_state.followers.contains(&peer_id)
2281    }
2282
2283    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2284        // TODO: There should be a better system in place for this
2285        // (https://github.com/zed-industries/zed/issues/1290)
2286        let is_fullscreen = cx.window_is_fullscreen();
2287        let container_theme = if is_fullscreen {
2288            let mut container_theme = theme.workspace.titlebar.container;
2289            container_theme.padding.left = container_theme.padding.right;
2290            container_theme
2291        } else {
2292            theme.workspace.titlebar.container
2293        };
2294
2295        enum TitleBar {}
2296        MouseEventHandler::<TitleBar, _>::new(0, cx, |_, cx| {
2297            Stack::new()
2298                .with_children(
2299                    self.titlebar_item
2300                        .as_ref()
2301                        .map(|item| ChildView::new(item, cx)),
2302                )
2303                .contained()
2304                .with_style(container_theme)
2305        })
2306        .on_click(MouseButton::Left, |event, _, cx| {
2307            if event.click_count == 2 {
2308                cx.zoom_window();
2309            }
2310        })
2311        .constrained()
2312        .with_height(theme.workspace.titlebar.height)
2313        .into_any_named("titlebar")
2314    }
2315
2316    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2317        let active_entry = self.active_project_path(cx);
2318        self.project
2319            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2320        self.update_window_title(cx);
2321    }
2322
2323    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2324        let project = self.project().read(cx);
2325        let mut title = String::new();
2326
2327        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2328            let filename = path
2329                .path
2330                .file_name()
2331                .map(|s| s.to_string_lossy())
2332                .or_else(|| {
2333                    Some(Cow::Borrowed(
2334                        project
2335                            .worktree_for_id(path.worktree_id, cx)?
2336                            .read(cx)
2337                            .root_name(),
2338                    ))
2339                });
2340
2341            if let Some(filename) = filename {
2342                title.push_str(filename.as_ref());
2343                title.push_str(" β€” ");
2344            }
2345        }
2346
2347        for (i, name) in project.worktree_root_names(cx).enumerate() {
2348            if i > 0 {
2349                title.push_str(", ");
2350            }
2351            title.push_str(name);
2352        }
2353
2354        if title.is_empty() {
2355            title = "empty project".to_string();
2356        }
2357
2358        if project.is_remote() {
2359            title.push_str(" ↙");
2360        } else if project.is_shared() {
2361            title.push_str(" β†—");
2362        }
2363
2364        cx.set_window_title(&title);
2365    }
2366
2367    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2368        let is_edited = !self.project.read(cx).is_read_only()
2369            && self
2370                .items(cx)
2371                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2372        if is_edited != self.window_edited {
2373            self.window_edited = is_edited;
2374            cx.set_window_edited(self.window_edited)
2375        }
2376    }
2377
2378    fn render_disconnected_overlay(
2379        &self,
2380        cx: &mut ViewContext<Workspace>,
2381    ) -> Option<AnyElement<Workspace>> {
2382        if self.project.read(cx).is_read_only() {
2383            enum DisconnectedOverlay {}
2384            Some(
2385                MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
2386                    let theme = &theme::current(cx);
2387                    Label::new(
2388                        "Your connection to the remote project has been lost.",
2389                        theme.workspace.disconnected_overlay.text.clone(),
2390                    )
2391                    .aligned()
2392                    .contained()
2393                    .with_style(theme.workspace.disconnected_overlay.container)
2394                })
2395                .with_cursor_style(CursorStyle::Arrow)
2396                .capture_all()
2397                .into_any_named("disconnected overlay"),
2398            )
2399        } else {
2400            None
2401        }
2402    }
2403
2404    fn render_notifications(
2405        &self,
2406        theme: &theme::Workspace,
2407        cx: &AppContext,
2408    ) -> Option<AnyElement<Workspace>> {
2409        if self.notifications.is_empty() {
2410            None
2411        } else {
2412            Some(
2413                Flex::column()
2414                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2415                        ChildView::new(notification.as_any(), cx)
2416                            .contained()
2417                            .with_style(theme.notification)
2418                    }))
2419                    .constrained()
2420                    .with_width(theme.notifications.width)
2421                    .contained()
2422                    .with_style(theme.notifications.container)
2423                    .aligned()
2424                    .bottom()
2425                    .right()
2426                    .into_any(),
2427            )
2428        }
2429    }
2430
2431    // RPC handlers
2432
2433    async fn handle_follow(
2434        this: WeakViewHandle<Self>,
2435        envelope: TypedEnvelope<proto::Follow>,
2436        _: Arc<Client>,
2437        mut cx: AsyncAppContext,
2438    ) -> Result<proto::FollowResponse> {
2439        this.update(&mut cx, |this, cx| {
2440            let client = &this.app_state.client;
2441            this.leader_state
2442                .followers
2443                .insert(envelope.original_sender_id()?);
2444
2445            let active_view_id = this.active_item(cx).and_then(|i| {
2446                Some(
2447                    i.to_followable_item_handle(cx)?
2448                        .remote_id(client, cx)?
2449                        .to_proto(),
2450                )
2451            });
2452
2453            cx.notify();
2454
2455            Ok(proto::FollowResponse {
2456                active_view_id,
2457                views: this
2458                    .panes()
2459                    .iter()
2460                    .flat_map(|pane| {
2461                        let leader_id = this.leader_for_pane(pane);
2462                        pane.read(cx).items().filter_map({
2463                            let cx = &cx;
2464                            move |item| {
2465                                let item = item.to_followable_item_handle(cx)?;
2466                                let id = item.remote_id(client, cx)?.to_proto();
2467                                let variant = item.to_state_proto(cx)?;
2468                                Some(proto::View {
2469                                    id: Some(id),
2470                                    leader_id,
2471                                    variant: Some(variant),
2472                                })
2473                            }
2474                        })
2475                    })
2476                    .collect(),
2477            })
2478        })?
2479    }
2480
2481    async fn handle_unfollow(
2482        this: WeakViewHandle<Self>,
2483        envelope: TypedEnvelope<proto::Unfollow>,
2484        _: Arc<Client>,
2485        mut cx: AsyncAppContext,
2486    ) -> Result<()> {
2487        this.update(&mut cx, |this, cx| {
2488            this.leader_state
2489                .followers
2490                .remove(&envelope.original_sender_id()?);
2491            cx.notify();
2492            Ok(())
2493        })?
2494    }
2495
2496    async fn handle_update_followers(
2497        this: WeakViewHandle<Self>,
2498        envelope: TypedEnvelope<proto::UpdateFollowers>,
2499        _: Arc<Client>,
2500        cx: AsyncAppContext,
2501    ) -> Result<()> {
2502        let leader_id = envelope.original_sender_id()?;
2503        this.read_with(&cx, |this, _| {
2504            this.leader_updates_tx
2505                .unbounded_send((leader_id, envelope.payload))
2506        })??;
2507        Ok(())
2508    }
2509
2510    async fn process_leader_update(
2511        this: &WeakViewHandle<Self>,
2512        leader_id: PeerId,
2513        update: proto::UpdateFollowers,
2514        cx: &mut AsyncAppContext,
2515    ) -> Result<()> {
2516        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2517            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2518                this.update(cx, |this, _| {
2519                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2520                        for state in state.values_mut() {
2521                            state.active_view_id =
2522                                if let Some(active_view_id) = update_active_view.id.clone() {
2523                                    Some(ViewId::from_proto(active_view_id)?)
2524                                } else {
2525                                    None
2526                                };
2527                        }
2528                    }
2529                    anyhow::Ok(())
2530                })??;
2531            }
2532            proto::update_followers::Variant::UpdateView(update_view) => {
2533                let variant = update_view
2534                    .variant
2535                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2536                let id = update_view
2537                    .id
2538                    .ok_or_else(|| anyhow!("missing update view id"))?;
2539                let mut tasks = Vec::new();
2540                this.update(cx, |this, cx| {
2541                    let project = this.project.clone();
2542                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2543                        for state in state.values_mut() {
2544                            let view_id = ViewId::from_proto(id.clone())?;
2545                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2546                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2547                            }
2548                        }
2549                    }
2550                    anyhow::Ok(())
2551                })??;
2552                try_join_all(tasks).await.log_err();
2553            }
2554            proto::update_followers::Variant::CreateView(view) => {
2555                let panes = this.read_with(cx, |this, _| {
2556                    this.follower_states_by_leader
2557                        .get(&leader_id)
2558                        .into_iter()
2559                        .flat_map(|states_by_pane| states_by_pane.keys())
2560                        .cloned()
2561                        .collect()
2562                })?;
2563                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2564            }
2565        }
2566        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2567        Ok(())
2568    }
2569
2570    async fn add_views_from_leader(
2571        this: WeakViewHandle<Self>,
2572        leader_id: PeerId,
2573        panes: Vec<ViewHandle<Pane>>,
2574        views: Vec<proto::View>,
2575        cx: &mut AsyncAppContext,
2576    ) -> Result<()> {
2577        let project = this.read_with(cx, |this, _| this.project.clone())?;
2578        let replica_id = project
2579            .read_with(cx, |project, _| {
2580                project
2581                    .collaborators()
2582                    .get(&leader_id)
2583                    .map(|c| c.replica_id)
2584            })
2585            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2586
2587        let item_builders = cx.update(|cx| {
2588            cx.default_global::<FollowableItemBuilders>()
2589                .values()
2590                .map(|b| b.0)
2591                .collect::<Vec<_>>()
2592        });
2593
2594        let mut item_tasks_by_pane = HashMap::default();
2595        for pane in panes {
2596            let mut item_tasks = Vec::new();
2597            let mut leader_view_ids = Vec::new();
2598            for view in &views {
2599                let Some(id) = &view.id else { continue };
2600                let id = ViewId::from_proto(id.clone())?;
2601                let mut variant = view.variant.clone();
2602                if variant.is_none() {
2603                    Err(anyhow!("missing variant"))?;
2604                }
2605                for build_item in &item_builders {
2606                    let task = cx.update(|cx| {
2607                        build_item(pane.clone(), project.clone(), id, &mut variant, cx)
2608                    });
2609                    if let Some(task) = task {
2610                        item_tasks.push(task);
2611                        leader_view_ids.push(id);
2612                        break;
2613                    } else {
2614                        assert!(variant.is_some());
2615                    }
2616                }
2617            }
2618
2619            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2620        }
2621
2622        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2623            let items = futures::future::try_join_all(item_tasks).await?;
2624            this.update(cx, |this, cx| {
2625                let state = this
2626                    .follower_states_by_leader
2627                    .get_mut(&leader_id)?
2628                    .get_mut(&pane)?;
2629
2630                for (id, item) in leader_view_ids.into_iter().zip(items) {
2631                    item.set_leader_replica_id(Some(replica_id), cx);
2632                    state.items_by_leader_view_id.insert(id, item);
2633                }
2634
2635                Some(())
2636            })?;
2637        }
2638        Ok(())
2639    }
2640
2641    fn update_followers(
2642        &self,
2643        update: proto::update_followers::Variant,
2644        cx: &AppContext,
2645    ) -> Option<()> {
2646        let project_id = self.project.read(cx).remote_id()?;
2647        if !self.leader_state.followers.is_empty() {
2648            self.app_state
2649                .client
2650                .send(proto::UpdateFollowers {
2651                    project_id,
2652                    follower_ids: self.leader_state.followers.iter().copied().collect(),
2653                    variant: Some(update),
2654                })
2655                .log_err();
2656        }
2657        None
2658    }
2659
2660    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2661        self.follower_states_by_leader
2662            .iter()
2663            .find_map(|(leader_id, state)| {
2664                if state.contains_key(pane) {
2665                    Some(*leader_id)
2666                } else {
2667                    None
2668                }
2669            })
2670    }
2671
2672    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2673        cx.notify();
2674
2675        let call = self.active_call()?;
2676        let room = call.read(cx).room()?.read(cx);
2677        let participant = room.remote_participant_for_peer_id(leader_id)?;
2678        let mut items_to_activate = Vec::new();
2679        match participant.location {
2680            call::ParticipantLocation::SharedProject { project_id } => {
2681                if Some(project_id) == self.project.read(cx).remote_id() {
2682                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2683                        if let Some(item) = state
2684                            .active_view_id
2685                            .and_then(|id| state.items_by_leader_view_id.get(&id))
2686                        {
2687                            items_to_activate.push((pane.clone(), item.boxed_clone()));
2688                        } else {
2689                            if let Some(shared_screen) =
2690                                self.shared_screen_for_peer(leader_id, pane, cx)
2691                            {
2692                                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2693                            }
2694                        }
2695                    }
2696                }
2697            }
2698            call::ParticipantLocation::UnsharedProject => {}
2699            call::ParticipantLocation::External => {
2700                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
2701                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2702                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2703                    }
2704                }
2705            }
2706        }
2707
2708        for (pane, item) in items_to_activate {
2709            let pane_was_focused = pane.read(cx).has_focus();
2710            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2711                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2712            } else {
2713                pane.update(cx, |pane, cx| {
2714                    pane.add_item(item.boxed_clone(), false, false, None, cx)
2715                });
2716            }
2717
2718            if pane_was_focused {
2719                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2720            }
2721        }
2722
2723        None
2724    }
2725
2726    fn shared_screen_for_peer(
2727        &self,
2728        peer_id: PeerId,
2729        pane: &ViewHandle<Pane>,
2730        cx: &mut ViewContext<Self>,
2731    ) -> Option<ViewHandle<SharedScreen>> {
2732        let call = self.active_call()?;
2733        let room = call.read(cx).room()?.read(cx);
2734        let participant = room.remote_participant_for_peer_id(peer_id)?;
2735        let track = participant.tracks.values().next()?.clone();
2736        let user = participant.user.clone();
2737
2738        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2739            if item.read(cx).peer_id == peer_id {
2740                return Some(item);
2741            }
2742        }
2743
2744        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2745    }
2746
2747    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2748        if active {
2749            cx.background()
2750                .spawn(persistence::DB.update_timestamp(self.database_id()))
2751                .detach();
2752        } else {
2753            for pane in &self.panes {
2754                pane.update(cx, |pane, cx| {
2755                    if let Some(item) = pane.active_item() {
2756                        item.workspace_deactivated(cx);
2757                    }
2758                    if matches!(
2759                        settings::get::<WorkspaceSettings>(cx).autosave,
2760                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2761                    ) {
2762                        for item in pane.items() {
2763                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2764                                .detach_and_log_err(cx);
2765                        }
2766                    }
2767                });
2768            }
2769        }
2770    }
2771
2772    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
2773        self.active_call.as_ref().map(|(call, _)| call)
2774    }
2775
2776    fn on_active_call_event(
2777        &mut self,
2778        _: ModelHandle<ActiveCall>,
2779        event: &call::room::Event,
2780        cx: &mut ViewContext<Self>,
2781    ) {
2782        match event {
2783            call::room::Event::ParticipantLocationChanged { participant_id }
2784            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2785                self.leader_updated(*participant_id, cx);
2786            }
2787            _ => {}
2788        }
2789    }
2790
2791    pub fn database_id(&self) -> WorkspaceId {
2792        self.database_id
2793    }
2794
2795    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2796        let project = self.project().read(cx);
2797
2798        if project.is_local() {
2799            Some(
2800                project
2801                    .visible_worktrees(cx)
2802                    .map(|worktree| worktree.read(cx).abs_path())
2803                    .collect::<Vec<_>>()
2804                    .into(),
2805            )
2806        } else {
2807            None
2808        }
2809    }
2810
2811    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2812        match member {
2813            Member::Axis(PaneAxis { members, .. }) => {
2814                for child in members.iter() {
2815                    self.remove_panes(child.clone(), cx)
2816                }
2817            }
2818            Member::Pane(pane) => {
2819                self.force_remove_pane(&pane, cx);
2820            }
2821        }
2822    }
2823
2824    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
2825        self.panes.retain(|p| p != pane);
2826        cx.focus(self.panes.last().unwrap());
2827        if self.last_active_center_pane == Some(pane.downgrade()) {
2828            self.last_active_center_pane = None;
2829        }
2830        cx.notify();
2831    }
2832
2833    fn serialize_workspace(&self, cx: &AppContext) {
2834        fn serialize_pane_handle(
2835            pane_handle: &ViewHandle<Pane>,
2836            cx: &AppContext,
2837        ) -> SerializedPane {
2838            let (items, active) = {
2839                let pane = pane_handle.read(cx);
2840                let active_item_id = pane.active_item().map(|item| item.id());
2841                (
2842                    pane.items()
2843                        .filter_map(|item_handle| {
2844                            Some(SerializedItem {
2845                                kind: Arc::from(item_handle.serialized_item_kind()?),
2846                                item_id: item_handle.id(),
2847                                active: Some(item_handle.id()) == active_item_id,
2848                            })
2849                        })
2850                        .collect::<Vec<_>>(),
2851                    pane.has_focus(),
2852                )
2853            };
2854
2855            SerializedPane::new(items, active)
2856        }
2857
2858        fn build_serialized_pane_group(
2859            pane_group: &Member,
2860            cx: &AppContext,
2861        ) -> SerializedPaneGroup {
2862            match pane_group {
2863                Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group {
2864                    axis: *axis,
2865                    children: members
2866                        .iter()
2867                        .map(|member| build_serialized_pane_group(member, cx))
2868                        .collect::<Vec<_>>(),
2869                },
2870                Member::Pane(pane_handle) => {
2871                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
2872                }
2873            }
2874        }
2875
2876        fn build_serialized_docks(this: &Workspace, cx: &AppContext) -> DockStructure {
2877            let left_dock = this.left_dock.read(cx);
2878            let left_visible = left_dock.is_open();
2879            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
2880                Some(
2881                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2882                        .to_string(),
2883                )
2884            });
2885
2886            let right_dock = this.right_dock.read(cx);
2887            let right_visible = right_dock.is_open();
2888            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
2889                Some(
2890                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2891                        .to_string(),
2892                )
2893            });
2894
2895            let bottom_dock = this.bottom_dock.read(cx);
2896            let bottom_visible = bottom_dock.is_open();
2897            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
2898                Some(
2899                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2900                        .to_string(),
2901                )
2902            });
2903
2904            DockStructure {
2905                left: DockData {
2906                    visible: left_visible,
2907                    active_panel: left_active_panel,
2908                },
2909                right: DockData {
2910                    visible: right_visible,
2911                    active_panel: right_active_panel,
2912                },
2913                bottom: DockData {
2914                    visible: bottom_visible,
2915                    active_panel: bottom_active_panel,
2916                },
2917            }
2918        }
2919
2920        if let Some(location) = self.location(cx) {
2921            // Load bearing special case:
2922            //  - with_local_workspace() relies on this to not have other stuff open
2923            //    when you open your log
2924            if !location.paths().is_empty() {
2925                let center_group = build_serialized_pane_group(&self.center.root, cx);
2926                let docks = build_serialized_docks(self, cx);
2927
2928                let serialized_workspace = SerializedWorkspace {
2929                    id: self.database_id,
2930                    location,
2931                    center_group,
2932                    bounds: Default::default(),
2933                    display: Default::default(),
2934                    docks,
2935                };
2936
2937                cx.background()
2938                    .spawn(persistence::DB.save_workspace(serialized_workspace))
2939                    .detach();
2940            }
2941        }
2942    }
2943
2944    pub(crate) fn load_workspace(
2945        workspace: WeakViewHandle<Workspace>,
2946        serialized_workspace: SerializedWorkspace,
2947        paths_to_open: Vec<Option<ProjectPath>>,
2948        cx: &mut AppContext,
2949    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
2950        cx.spawn(|mut cx| async move {
2951            let result = async_iife! {{
2952                let (project, old_center_pane) =
2953                workspace.read_with(&cx, |workspace, _| {
2954                    (
2955                        workspace.project().clone(),
2956                        workspace.last_active_center_pane.clone(),
2957                    )
2958                })?;
2959
2960                let mut center_items = None;
2961                let mut center_group = None;
2962                // Traverse the splits tree and add to things
2963                if let Some((group, active_pane, items)) = serialized_workspace
2964                        .center_group
2965                        .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
2966                        .await {
2967                    center_items = Some(items);
2968                    center_group = Some((group, active_pane))
2969                }
2970
2971                let resulting_list = cx.read(|cx| {
2972                    let mut opened_items = center_items
2973                        .unwrap_or_default()
2974                        .into_iter()
2975                        .filter_map(|item| {
2976                            let item = item?;
2977                            let project_path = item.project_path(cx)?;
2978                            Some((project_path, item))
2979                        })
2980                        .collect::<HashMap<_, _>>();
2981
2982                    paths_to_open
2983                        .into_iter()
2984                        .map(|path_to_open| {
2985                            path_to_open.map(|path_to_open| {
2986                                Ok(opened_items.remove(&path_to_open))
2987                            })
2988                            .transpose()
2989                            .map(|item| item.flatten())
2990                            .transpose()
2991                        })
2992                        .collect::<Vec<_>>()
2993                });
2994
2995                // Remove old panes from workspace panes list
2996                workspace.update(&mut cx, |workspace, cx| {
2997                    if let Some((center_group, active_pane)) = center_group {
2998                        workspace.remove_panes(workspace.center.root.clone(), cx);
2999
3000                        // Swap workspace center group
3001                        workspace.center = PaneGroup::with_root(center_group);
3002
3003                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
3004                        cx.focus_self();
3005
3006                        if let Some(active_pane) = active_pane {
3007                            cx.focus(&active_pane);
3008                        } else {
3009                            cx.focus(workspace.panes.last().unwrap());
3010                        }
3011                    } else {
3012                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3013                        if let Some(old_center_handle) = old_center_handle {
3014                            cx.focus(&old_center_handle)
3015                        } else {
3016                            cx.focus_self()
3017                        }
3018                    }
3019
3020                    let docks = serialized_workspace.docks;
3021                    workspace.left_dock.update(cx, |dock, cx| {
3022                        dock.set_open(docks.left.visible, cx);
3023                        if let Some(active_panel) = docks.left.active_panel {
3024                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3025                                dock.activate_panel(ix, cx);
3026                            }
3027                        }
3028                    });
3029                    workspace.right_dock.update(cx, |dock, cx| {
3030                        dock.set_open(docks.right.visible, cx);
3031                        if let Some(active_panel) = docks.right.active_panel {
3032                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3033                                dock.activate_panel(ix, cx);
3034                            }
3035                        }
3036                    });
3037                    workspace.bottom_dock.update(cx, |dock, cx| {
3038                        dock.set_open(docks.bottom.visible, cx);
3039                        if let Some(active_panel) = docks.bottom.active_panel {
3040                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3041                                dock.activate_panel(ix, cx);
3042                            }
3043                        }
3044                    });
3045
3046                    cx.notify();
3047                })?;
3048
3049                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3050                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3051
3052                Ok::<_, anyhow::Error>(resulting_list)
3053            }};
3054
3055            result.await.unwrap_or_default()
3056        })
3057    }
3058
3059    #[cfg(any(test, feature = "test-support"))]
3060    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3061        let app_state = Arc::new(AppState {
3062            languages: project.read(cx).languages().clone(),
3063            client: project.read(cx).client(),
3064            user_store: project.read(cx).user_store(),
3065            fs: project.read(cx).fs().clone(),
3066            build_window_options: |_, _, _| Default::default(),
3067            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3068            background_actions: || &[],
3069        });
3070        Self::new(0, project, app_state, cx)
3071    }
3072
3073    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3074        let dock = match position {
3075            DockPosition::Left => &self.left_dock,
3076            DockPosition::Right => &self.right_dock,
3077            DockPosition::Bottom => &self.bottom_dock,
3078        };
3079        let active_panel = dock.read(cx).visible_panel()?;
3080        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3081            dock.read(cx).render_placeholder(cx)
3082        } else {
3083            ChildView::new(dock, cx).into_any()
3084        };
3085
3086        Some(
3087            element
3088                .constrained()
3089                .dynamically(move |constraint, _, cx| match position {
3090                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3091                        Vector2F::new(20., constraint.min.y()),
3092                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3093                    ),
3094                    DockPosition::Bottom => SizeConstraint::new(
3095                        Vector2F::new(constraint.min.x(), 20.),
3096                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3097                    ),
3098                })
3099                .into_any(),
3100        )
3101    }
3102}
3103
3104async fn open_items(
3105    serialized_workspace: Option<SerializedWorkspace>,
3106    workspace: &WeakViewHandle<Workspace>,
3107    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3108    app_state: Arc<AppState>,
3109    mut cx: AsyncAppContext,
3110) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
3111    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3112
3113    if let Some(serialized_workspace) = serialized_workspace {
3114        let workspace = workspace.clone();
3115        let restored_items = cx
3116            .update(|cx| {
3117                Workspace::load_workspace(
3118                    workspace,
3119                    serialized_workspace,
3120                    project_paths_to_open
3121                        .iter()
3122                        .map(|(_, project_path)| project_path)
3123                        .cloned()
3124                        .collect(),
3125                    cx,
3126                )
3127            })
3128            .await;
3129
3130        let restored_project_paths = cx.read(|cx| {
3131            restored_items
3132                .iter()
3133                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
3134                .collect::<HashSet<_>>()
3135        });
3136
3137        opened_items = restored_items;
3138        project_paths_to_open
3139            .iter_mut()
3140            .for_each(|(_, project_path)| {
3141                if let Some(project_path_to_open) = project_path {
3142                    if restored_project_paths.contains(project_path_to_open) {
3143                        *project_path = None;
3144                    }
3145                }
3146            });
3147    } else {
3148        for _ in 0..project_paths_to_open.len() {
3149            opened_items.push(None);
3150        }
3151    }
3152    assert!(opened_items.len() == project_paths_to_open.len());
3153
3154    let tasks =
3155        project_paths_to_open
3156            .into_iter()
3157            .enumerate()
3158            .map(|(i, (abs_path, project_path))| {
3159                let workspace = workspace.clone();
3160                cx.spawn(|mut cx| {
3161                    let fs = app_state.fs.clone();
3162                    async move {
3163                        let file_project_path = project_path?;
3164                        if fs.is_file(&abs_path).await {
3165                            Some((
3166                                i,
3167                                workspace
3168                                    .update(&mut cx, |workspace, cx| {
3169                                        workspace.open_path(file_project_path, None, true, cx)
3170                                    })
3171                                    .log_err()?
3172                                    .await,
3173                            ))
3174                        } else {
3175                            None
3176                        }
3177                    }
3178                })
3179            });
3180
3181    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3182        .await
3183        .into_iter()
3184    {
3185        if let Some((i, path_open_result)) = maybe_opened_path {
3186            opened_items[i] = Some(path_open_result);
3187        }
3188    }
3189
3190    opened_items
3191}
3192
3193fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3194    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3195
3196    workspace
3197        .update(cx, |workspace, cx| {
3198            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3199                workspace.show_notification_once(0, cx, |cx| {
3200                    cx.add_view(|_| {
3201                        MessageNotification::new("Failed to load any database file.")
3202                            .with_click_message("Click to let us know about this error")
3203                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3204                    })
3205                });
3206            } else {
3207                let backup_path = (*db::BACKUP_DB_PATH).read();
3208                if let Some(backup_path) = backup_path.clone() {
3209                    workspace.show_notification_once(0, cx, move |cx| {
3210                        cx.add_view(move |_| {
3211                            MessageNotification::new(format!(
3212                                "Database file was corrupted. Old database backed up to {}",
3213                                backup_path.display()
3214                            ))
3215                            .with_click_message("Click to show old database in finder")
3216                            .on_click(move |cx| {
3217                                cx.platform().open_url(&backup_path.to_string_lossy())
3218                            })
3219                        })
3220                    });
3221                }
3222            }
3223        })
3224        .log_err();
3225}
3226
3227impl Entity for Workspace {
3228    type Event = Event;
3229}
3230
3231impl View for Workspace {
3232    fn ui_name() -> &'static str {
3233        "Workspace"
3234    }
3235
3236    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3237        let theme = theme::current(cx).clone();
3238        Stack::new()
3239            .with_child(
3240                Flex::column()
3241                    .with_child(self.render_titlebar(&theme, cx))
3242                    .with_child(
3243                        Stack::new()
3244                            .with_child({
3245                                let project = self.project.clone();
3246                                Flex::row()
3247                                    .with_children(self.render_dock(DockPosition::Left, cx))
3248                                    .with_child(
3249                                        Flex::column()
3250                                            .with_child(
3251                                                FlexItem::new(
3252                                                    self.center.render(
3253                                                        &project,
3254                                                        &theme,
3255                                                        &self.follower_states_by_leader,
3256                                                        self.active_call(),
3257                                                        self.active_pane(),
3258                                                        self.zoomed
3259                                                            .as_ref()
3260                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3261                                                            .as_ref(),
3262                                                        &self.app_state,
3263                                                        cx,
3264                                                    ),
3265                                                )
3266                                                .flex(1., true),
3267                                            )
3268                                            .with_children(
3269                                                self.render_dock(DockPosition::Bottom, cx),
3270                                            )
3271                                            .flex(1., true),
3272                                    )
3273                                    .with_children(self.render_dock(DockPosition::Right, cx))
3274                            })
3275                            .with_child(Overlay::new(
3276                                Stack::new()
3277                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3278                                        enum ZoomBackground {}
3279                                        let zoomed = zoomed.upgrade(cx)?;
3280
3281                                        let mut foreground_style;
3282                                        match self.zoomed_position {
3283                                            Some(DockPosition::Left) => {
3284                                                foreground_style =
3285                                                    theme.workspace.zoomed_panel_foreground;
3286                                                foreground_style.margin.left = 0.;
3287                                                foreground_style.margin.top = 0.;
3288                                                foreground_style.margin.bottom = 0.;
3289                                            }
3290                                            Some(DockPosition::Right) => {
3291                                                foreground_style =
3292                                                    theme.workspace.zoomed_panel_foreground;
3293                                                foreground_style.margin.right = 0.;
3294                                                foreground_style.margin.top = 0.;
3295                                                foreground_style.margin.bottom = 0.;
3296                                            }
3297                                            Some(DockPosition::Bottom) => {
3298                                                foreground_style =
3299                                                    theme.workspace.zoomed_panel_foreground;
3300                                                foreground_style.margin.left = 0.;
3301                                                foreground_style.margin.right = 0.;
3302                                                foreground_style.margin.bottom = 0.;
3303                                            }
3304                                            None => {
3305                                                foreground_style =
3306                                                    theme.workspace.zoomed_pane_foreground;
3307                                            }
3308                                        }
3309
3310                                        Some(
3311                                            ChildView::new(&zoomed, cx)
3312                                                .contained()
3313                                                .with_style(foreground_style)
3314                                                .aligned()
3315                                                .contained()
3316                                                .with_style(theme.workspace.zoomed_background)
3317                                                .mouse::<ZoomBackground>(0)
3318                                                .capture_all()
3319                                                .on_down(
3320                                                    MouseButton::Left,
3321                                                    |_, this: &mut Self, cx| {
3322                                                        this.zoom_out(cx);
3323                                                    },
3324                                                ),
3325                                        )
3326                                    }))
3327                                    .with_children(self.modal.as_ref().map(|modal| {
3328                                        ChildView::new(modal, cx)
3329                                            .contained()
3330                                            .with_style(theme.workspace.modal)
3331                                            .aligned()
3332                                            .top()
3333                                    }))
3334                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3335                            ))
3336                            .flex(1.0, true),
3337                    )
3338                    .with_child(ChildView::new(&self.status_bar, cx))
3339                    .contained()
3340                    .with_background_color(theme.workspace.background),
3341            )
3342            .with_children(DragAndDrop::render(cx))
3343            .with_children(self.render_disconnected_overlay(cx))
3344            .into_any_named("workspace")
3345    }
3346
3347    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3348        if cx.is_self_focused() {
3349            cx.focus(&self.active_pane);
3350        }
3351    }
3352}
3353
3354impl ViewId {
3355    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3356        Ok(Self {
3357            creator: message
3358                .creator
3359                .ok_or_else(|| anyhow!("creator is missing"))?,
3360            id: message.id,
3361        })
3362    }
3363
3364    pub(crate) fn to_proto(&self) -> proto::ViewId {
3365        proto::ViewId {
3366            creator: Some(self.creator),
3367            id: self.id,
3368        }
3369    }
3370}
3371
3372pub trait WorkspaceHandle {
3373    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3374}
3375
3376impl WorkspaceHandle for ViewHandle<Workspace> {
3377    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3378        self.read(cx)
3379            .worktrees(cx)
3380            .flat_map(|worktree| {
3381                let worktree_id = worktree.read(cx).id();
3382                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3383                    worktree_id,
3384                    path: f.path.clone(),
3385                })
3386            })
3387            .collect::<Vec<_>>()
3388    }
3389}
3390
3391impl std::fmt::Debug for OpenPaths {
3392    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3393        f.debug_struct("OpenPaths")
3394            .field("paths", &self.paths)
3395            .finish()
3396    }
3397}
3398
3399pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
3400
3401pub fn activate_workspace_for_project(
3402    cx: &mut AsyncAppContext,
3403    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3404) -> Option<WeakViewHandle<Workspace>> {
3405    for window_id in cx.window_ids() {
3406        let handle = cx
3407            .update_window(window_id, |cx| {
3408                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3409                    let project = workspace_handle.read(cx).project.clone();
3410                    if project.update(cx, &predicate) {
3411                        cx.activate_window();
3412                        return Some(workspace_handle.clone());
3413                    }
3414                }
3415                None
3416            })
3417            .flatten();
3418
3419        if let Some(handle) = handle {
3420            return Some(handle.downgrade());
3421        }
3422    }
3423    None
3424}
3425
3426pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3427    DB.last_workspace().await.log_err().flatten()
3428}
3429
3430#[allow(clippy::type_complexity)]
3431pub fn open_paths(
3432    abs_paths: &[PathBuf],
3433    app_state: &Arc<AppState>,
3434    requesting_window_id: Option<usize>,
3435    cx: &mut AppContext,
3436) -> Task<
3437    Result<(
3438        WeakViewHandle<Workspace>,
3439        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3440    )>,
3441> {
3442    let app_state = app_state.clone();
3443    let abs_paths = abs_paths.to_vec();
3444    cx.spawn(|mut cx| async move {
3445        // Open paths in existing workspace if possible
3446        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3447            project.contains_paths(&abs_paths, cx)
3448        });
3449
3450        if let Some(existing) = existing {
3451            Ok((
3452                existing.clone(),
3453                existing
3454                    .update(&mut cx, |workspace, cx| {
3455                        workspace.open_paths(abs_paths, true, cx)
3456                    })?
3457                    .await,
3458            ))
3459        } else {
3460            Ok(cx
3461                .update(|cx| {
3462                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx)
3463                })
3464                .await)
3465        }
3466    })
3467}
3468
3469pub fn open_new(
3470    app_state: &Arc<AppState>,
3471    cx: &mut AppContext,
3472    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3473) -> Task<()> {
3474    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3475    cx.spawn(|mut cx| async move {
3476        let (workspace, opened_paths) = task.await;
3477
3478        workspace
3479            .update(&mut cx, |workspace, cx| {
3480                if opened_paths.is_empty() {
3481                    init(workspace, cx)
3482                }
3483            })
3484            .log_err();
3485    })
3486}
3487
3488pub fn create_and_open_local_file(
3489    path: &'static Path,
3490    cx: &mut ViewContext<Workspace>,
3491    default_content: impl 'static + Send + FnOnce() -> Rope,
3492) -> Task<Result<Box<dyn ItemHandle>>> {
3493    cx.spawn(|workspace, mut cx| async move {
3494        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
3495        if !fs.is_file(path).await {
3496            fs.create_file(path, Default::default()).await?;
3497            fs.save(path, &default_content(), Default::default())
3498                .await?;
3499        }
3500
3501        let mut items = workspace
3502            .update(&mut cx, |workspace, cx| {
3503                workspace.with_local_workspace(cx, |workspace, cx| {
3504                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
3505                })
3506            })?
3507            .await?
3508            .await;
3509
3510        let item = items.pop().flatten();
3511        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
3512    })
3513}
3514
3515pub fn join_remote_project(
3516    project_id: u64,
3517    follow_user_id: u64,
3518    app_state: Arc<AppState>,
3519    cx: &mut AppContext,
3520) -> Task<Result<()>> {
3521    cx.spawn(|mut cx| async move {
3522        let existing_workspace = cx
3523            .window_ids()
3524            .into_iter()
3525            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
3526            .find(|workspace| {
3527                cx.read_window(workspace.window_id(), |cx| {
3528                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
3529                })
3530                .unwrap_or(false)
3531            });
3532
3533        let workspace = if let Some(existing_workspace) = existing_workspace {
3534            existing_workspace.downgrade()
3535        } else {
3536            let active_call = cx.read(ActiveCall::global);
3537            let room = active_call
3538                .read_with(&cx, |call, _| call.room().cloned())
3539                .ok_or_else(|| anyhow!("not in a call"))?;
3540            let project = room
3541                .update(&mut cx, |room, cx| {
3542                    room.join_project(
3543                        project_id,
3544                        app_state.languages.clone(),
3545                        app_state.fs.clone(),
3546                        cx,
3547                    )
3548                })
3549                .await?;
3550
3551            let (_, workspace) = cx.add_window(
3552                (app_state.build_window_options)(None, None, cx.platform().as_ref()),
3553                |cx| Workspace::new(0, project, app_state.clone(), cx),
3554            );
3555            (app_state.initialize_workspace)(
3556                workspace.downgrade(),
3557                false,
3558                app_state.clone(),
3559                cx.clone(),
3560            )
3561            .await
3562            .log_err();
3563
3564            workspace.downgrade()
3565        };
3566
3567        cx.activate_window(workspace.window_id());
3568        cx.platform().activate(true);
3569
3570        workspace.update(&mut cx, |workspace, cx| {
3571            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
3572                let follow_peer_id = room
3573                    .read(cx)
3574                    .remote_participants()
3575                    .iter()
3576                    .find(|(_, participant)| participant.user.id == follow_user_id)
3577                    .map(|(_, p)| p.peer_id)
3578                    .or_else(|| {
3579                        // If we couldn't follow the given user, follow the host instead.
3580                        let collaborator = workspace
3581                            .project()
3582                            .read(cx)
3583                            .collaborators()
3584                            .values()
3585                            .find(|collaborator| collaborator.replica_id == 0)?;
3586                        Some(collaborator.peer_id)
3587                    });
3588
3589                if let Some(follow_peer_id) = follow_peer_id {
3590                    if !workspace.is_being_followed(follow_peer_id) {
3591                        workspace
3592                            .toggle_follow(follow_peer_id, cx)
3593                            .map(|follow| follow.detach_and_log_err(cx));
3594                    }
3595                }
3596            }
3597        })?;
3598
3599        anyhow::Ok(())
3600    })
3601}
3602
3603pub fn restart(_: &Restart, cx: &mut AppContext) {
3604    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
3605    cx.spawn(|mut cx| async move {
3606        let mut workspaces = cx
3607            .window_ids()
3608            .into_iter()
3609            .filter_map(|window_id| {
3610                Some(
3611                    cx.root_view(window_id)?
3612                        .clone()
3613                        .downcast::<Workspace>()?
3614                        .downgrade(),
3615                )
3616            })
3617            .collect::<Vec<_>>();
3618
3619        // If multiple windows have unsaved changes, and need a save prompt,
3620        // prompt in the active window before switching to a different window.
3621        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
3622
3623        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
3624            let answer = cx.prompt(
3625                workspace.window_id(),
3626                PromptLevel::Info,
3627                "Are you sure you want to restart?",
3628                &["Restart", "Cancel"],
3629            );
3630
3631            if let Some(mut answer) = answer {
3632                let answer = answer.next().await;
3633                if answer != Some(0) {
3634                    return Ok(());
3635                }
3636            }
3637        }
3638
3639        // If the user cancels any save prompt, then keep the app open.
3640        for workspace in workspaces {
3641            if !workspace
3642                .update(&mut cx, |workspace, cx| {
3643                    workspace.prepare_to_close(true, cx)
3644                })?
3645                .await?
3646            {
3647                return Ok(());
3648            }
3649        }
3650        cx.platform().restart();
3651        anyhow::Ok(())
3652    })
3653    .detach_and_log_err(cx);
3654}
3655
3656fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
3657    let mut parts = value.split(',');
3658    let width: usize = parts.next()?.parse().ok()?;
3659    let height: usize = parts.next()?.parse().ok()?;
3660    Some(vec2f(width as f32, height as f32))
3661}
3662
3663#[cfg(test)]
3664mod tests {
3665    use super::*;
3666    use crate::{
3667        dock::test::{TestPanel, TestPanelEvent},
3668        item::test::{TestItem, TestItemEvent, TestProjectItem},
3669    };
3670    use fs::FakeFs;
3671    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
3672    use project::{Project, ProjectEntryId};
3673    use serde_json::json;
3674    use settings::SettingsStore;
3675    use std::{cell::RefCell, rc::Rc};
3676
3677    #[gpui::test]
3678    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3679        init_test(cx);
3680
3681        let fs = FakeFs::new(cx.background());
3682        let project = Project::test(fs, [], cx).await;
3683        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3684
3685        // Adding an item with no ambiguity renders the tab without detail.
3686        let item1 = cx.add_view(window_id, |_| {
3687            let mut item = TestItem::new();
3688            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3689            item
3690        });
3691        workspace.update(cx, |workspace, cx| {
3692            workspace.add_item(Box::new(item1.clone()), cx);
3693        });
3694        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3695
3696        // Adding an item that creates ambiguity increases the level of detail on
3697        // both tabs.
3698        let item2 = cx.add_view(window_id, |_| {
3699            let mut item = TestItem::new();
3700            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3701            item
3702        });
3703        workspace.update(cx, |workspace, cx| {
3704            workspace.add_item(Box::new(item2.clone()), cx);
3705        });
3706        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3707        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3708
3709        // Adding an item that creates ambiguity increases the level of detail only
3710        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3711        // we stop at the highest detail available.
3712        let item3 = cx.add_view(window_id, |_| {
3713            let mut item = TestItem::new();
3714            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3715            item
3716        });
3717        workspace.update(cx, |workspace, cx| {
3718            workspace.add_item(Box::new(item3.clone()), cx);
3719        });
3720        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3721        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3722        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3723    }
3724
3725    #[gpui::test]
3726    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3727        init_test(cx);
3728
3729        let fs = FakeFs::new(cx.background());
3730        fs.insert_tree(
3731            "/root1",
3732            json!({
3733                "one.txt": "",
3734                "two.txt": "",
3735            }),
3736        )
3737        .await;
3738        fs.insert_tree(
3739            "/root2",
3740            json!({
3741                "three.txt": "",
3742            }),
3743        )
3744        .await;
3745
3746        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3747        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3748        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3749        let worktree_id = project.read_with(cx, |project, cx| {
3750            project.worktrees(cx).next().unwrap().read(cx).id()
3751        });
3752
3753        let item1 = cx.add_view(window_id, |cx| {
3754            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
3755        });
3756        let item2 = cx.add_view(window_id, |cx| {
3757            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
3758        });
3759
3760        // Add an item to an empty pane
3761        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3762        project.read_with(cx, |project, cx| {
3763            assert_eq!(
3764                project.active_entry(),
3765                project
3766                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3767                    .map(|e| e.id)
3768            );
3769        });
3770        assert_eq!(
3771            cx.current_window_title(window_id).as_deref(),
3772            Some("one.txt β€” root1")
3773        );
3774
3775        // Add a second item to a non-empty pane
3776        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3777        assert_eq!(
3778            cx.current_window_title(window_id).as_deref(),
3779            Some("two.txt β€” root1")
3780        );
3781        project.read_with(cx, |project, cx| {
3782            assert_eq!(
3783                project.active_entry(),
3784                project
3785                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3786                    .map(|e| e.id)
3787            );
3788        });
3789
3790        // Close the active item
3791        pane.update(cx, |pane, cx| {
3792            pane.close_active_item(&Default::default(), cx).unwrap()
3793        })
3794        .await
3795        .unwrap();
3796        assert_eq!(
3797            cx.current_window_title(window_id).as_deref(),
3798            Some("one.txt β€” root1")
3799        );
3800        project.read_with(cx, |project, cx| {
3801            assert_eq!(
3802                project.active_entry(),
3803                project
3804                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3805                    .map(|e| e.id)
3806            );
3807        });
3808
3809        // Add a project folder
3810        project
3811            .update(cx, |project, cx| {
3812                project.find_or_create_local_worktree("/root2", true, cx)
3813            })
3814            .await
3815            .unwrap();
3816        assert_eq!(
3817            cx.current_window_title(window_id).as_deref(),
3818            Some("one.txt β€” root1, root2")
3819        );
3820
3821        // Remove a project folder
3822        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
3823        assert_eq!(
3824            cx.current_window_title(window_id).as_deref(),
3825            Some("one.txt β€” root2")
3826        );
3827    }
3828
3829    #[gpui::test]
3830    async fn test_close_window(cx: &mut TestAppContext) {
3831        init_test(cx);
3832
3833        let fs = FakeFs::new(cx.background());
3834        fs.insert_tree("/root", json!({ "one": "" })).await;
3835
3836        let project = Project::test(fs, ["root".as_ref()], cx).await;
3837        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3838
3839        // When there are no dirty items, there's nothing to do.
3840        let item1 = cx.add_view(window_id, |_| TestItem::new());
3841        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3842        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3843        assert!(task.await.unwrap());
3844
3845        // When there are dirty untitled items, prompt to save each one. If the user
3846        // cancels any prompt, then abort.
3847        let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
3848        let item3 = cx.add_view(window_id, |cx| {
3849            TestItem::new()
3850                .with_dirty(true)
3851                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3852        });
3853        workspace.update(cx, |w, cx| {
3854            w.add_item(Box::new(item2.clone()), cx);
3855            w.add_item(Box::new(item3.clone()), cx);
3856        });
3857        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3858        cx.foreground().run_until_parked();
3859        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3860        cx.foreground().run_until_parked();
3861        assert!(!cx.has_pending_prompt(window_id));
3862        assert!(!task.await.unwrap());
3863    }
3864
3865    #[gpui::test]
3866    async fn test_close_pane_items(cx: &mut TestAppContext) {
3867        init_test(cx);
3868
3869        let fs = FakeFs::new(cx.background());
3870
3871        let project = Project::test(fs, None, cx).await;
3872        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3873
3874        let item1 = cx.add_view(window_id, |cx| {
3875            TestItem::new()
3876                .with_dirty(true)
3877                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3878        });
3879        let item2 = cx.add_view(window_id, |cx| {
3880            TestItem::new()
3881                .with_dirty(true)
3882                .with_conflict(true)
3883                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
3884        });
3885        let item3 = cx.add_view(window_id, |cx| {
3886            TestItem::new()
3887                .with_dirty(true)
3888                .with_conflict(true)
3889                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
3890        });
3891        let item4 = cx.add_view(window_id, |cx| {
3892            TestItem::new()
3893                .with_dirty(true)
3894                .with_project_items(&[TestProjectItem::new_untitled(cx)])
3895        });
3896        let pane = workspace.update(cx, |workspace, cx| {
3897            workspace.add_item(Box::new(item1.clone()), cx);
3898            workspace.add_item(Box::new(item2.clone()), cx);
3899            workspace.add_item(Box::new(item3.clone()), cx);
3900            workspace.add_item(Box::new(item4.clone()), cx);
3901            workspace.active_pane().clone()
3902        });
3903
3904        let close_items = pane.update(cx, |pane, cx| {
3905            pane.activate_item(1, true, true, cx);
3906            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3907            let item1_id = item1.id();
3908            let item3_id = item3.id();
3909            let item4_id = item4.id();
3910            pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id))
3911        });
3912        cx.foreground().run_until_parked();
3913
3914        // There's a prompt to save item 1.
3915        pane.read_with(cx, |pane, _| {
3916            assert_eq!(pane.items_len(), 4);
3917            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3918        });
3919        assert!(cx.has_pending_prompt(window_id));
3920
3921        // Confirm saving item 1.
3922        cx.simulate_prompt_answer(window_id, 0);
3923        cx.foreground().run_until_parked();
3924
3925        // Item 1 is saved. There's a prompt to save item 3.
3926        pane.read_with(cx, |pane, cx| {
3927            assert_eq!(item1.read(cx).save_count, 1);
3928            assert_eq!(item1.read(cx).save_as_count, 0);
3929            assert_eq!(item1.read(cx).reload_count, 0);
3930            assert_eq!(pane.items_len(), 3);
3931            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3932        });
3933        assert!(cx.has_pending_prompt(window_id));
3934
3935        // Cancel saving item 3.
3936        cx.simulate_prompt_answer(window_id, 1);
3937        cx.foreground().run_until_parked();
3938
3939        // Item 3 is reloaded. There's a prompt to save item 4.
3940        pane.read_with(cx, |pane, cx| {
3941            assert_eq!(item3.read(cx).save_count, 0);
3942            assert_eq!(item3.read(cx).save_as_count, 0);
3943            assert_eq!(item3.read(cx).reload_count, 1);
3944            assert_eq!(pane.items_len(), 2);
3945            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3946        });
3947        assert!(cx.has_pending_prompt(window_id));
3948
3949        // Confirm saving item 4.
3950        cx.simulate_prompt_answer(window_id, 0);
3951        cx.foreground().run_until_parked();
3952
3953        // There's a prompt for a path for item 4.
3954        cx.simulate_new_path_selection(|_| Some(Default::default()));
3955        close_items.await.unwrap();
3956
3957        // The requested items are closed.
3958        pane.read_with(cx, |pane, cx| {
3959            assert_eq!(item4.read(cx).save_count, 0);
3960            assert_eq!(item4.read(cx).save_as_count, 1);
3961            assert_eq!(item4.read(cx).reload_count, 0);
3962            assert_eq!(pane.items_len(), 1);
3963            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3964        });
3965    }
3966
3967    #[gpui::test]
3968    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3969        init_test(cx);
3970
3971        let fs = FakeFs::new(cx.background());
3972
3973        let project = Project::test(fs, [], cx).await;
3974        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3975
3976        // Create several workspace items with single project entries, and two
3977        // workspace items with multiple project entries.
3978        let single_entry_items = (0..=4)
3979            .map(|project_entry_id| {
3980                cx.add_view(window_id, |cx| {
3981                    TestItem::new()
3982                        .with_dirty(true)
3983                        .with_project_items(&[TestProjectItem::new(
3984                            project_entry_id,
3985                            &format!("{project_entry_id}.txt"),
3986                            cx,
3987                        )])
3988                })
3989            })
3990            .collect::<Vec<_>>();
3991        let item_2_3 = cx.add_view(window_id, |cx| {
3992            TestItem::new()
3993                .with_dirty(true)
3994                .with_singleton(false)
3995                .with_project_items(&[
3996                    single_entry_items[2].read(cx).project_items[0].clone(),
3997                    single_entry_items[3].read(cx).project_items[0].clone(),
3998                ])
3999        });
4000        let item_3_4 = cx.add_view(window_id, |cx| {
4001            TestItem::new()
4002                .with_dirty(true)
4003                .with_singleton(false)
4004                .with_project_items(&[
4005                    single_entry_items[3].read(cx).project_items[0].clone(),
4006                    single_entry_items[4].read(cx).project_items[0].clone(),
4007                ])
4008        });
4009
4010        // Create two panes that contain the following project entries:
4011        //   left pane:
4012        //     multi-entry items:   (2, 3)
4013        //     single-entry items:  0, 1, 2, 3, 4
4014        //   right pane:
4015        //     single-entry items:  1
4016        //     multi-entry items:   (3, 4)
4017        let left_pane = workspace.update(cx, |workspace, cx| {
4018            let left_pane = workspace.active_pane().clone();
4019            workspace.add_item(Box::new(item_2_3.clone()), cx);
4020            for item in single_entry_items {
4021                workspace.add_item(Box::new(item), cx);
4022            }
4023            left_pane.update(cx, |pane, cx| {
4024                pane.activate_item(2, true, true, cx);
4025            });
4026
4027            workspace
4028                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
4029                .unwrap();
4030
4031            left_pane
4032        });
4033
4034        //Need to cause an effect flush in order to respect new focus
4035        workspace.update(cx, |workspace, cx| {
4036            workspace.add_item(Box::new(item_3_4.clone()), cx);
4037            cx.focus(&left_pane);
4038        });
4039
4040        // When closing all of the items in the left pane, we should be prompted twice:
4041        // once for project entry 0, and once for project entry 2. After those two
4042        // prompts, the task should complete.
4043
4044        let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true));
4045        cx.foreground().run_until_parked();
4046        left_pane.read_with(cx, |pane, cx| {
4047            assert_eq!(
4048                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4049                &[ProjectEntryId::from_proto(0)]
4050            );
4051        });
4052        cx.simulate_prompt_answer(window_id, 0);
4053
4054        cx.foreground().run_until_parked();
4055        left_pane.read_with(cx, |pane, cx| {
4056            assert_eq!(
4057                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4058                &[ProjectEntryId::from_proto(2)]
4059            );
4060        });
4061        cx.simulate_prompt_answer(window_id, 0);
4062
4063        cx.foreground().run_until_parked();
4064        close.await.unwrap();
4065        left_pane.read_with(cx, |pane, _| {
4066            assert_eq!(pane.items_len(), 0);
4067        });
4068    }
4069
4070    #[gpui::test]
4071    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4072        init_test(cx);
4073
4074        let fs = FakeFs::new(cx.background());
4075
4076        let project = Project::test(fs, [], cx).await;
4077        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4078        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4079
4080        let item = cx.add_view(window_id, |cx| {
4081            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4082        });
4083        let item_id = item.id();
4084        workspace.update(cx, |workspace, cx| {
4085            workspace.add_item(Box::new(item.clone()), cx);
4086        });
4087
4088        // Autosave on window change.
4089        item.update(cx, |item, cx| {
4090            cx.update_global(|settings: &mut SettingsStore, cx| {
4091                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4092                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4093                })
4094            });
4095            item.is_dirty = true;
4096        });
4097
4098        // Deactivating the window saves the file.
4099        cx.simulate_window_activation(None);
4100        deterministic.run_until_parked();
4101        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4102
4103        // Autosave on focus change.
4104        item.update(cx, |item, cx| {
4105            cx.focus_self();
4106            cx.update_global(|settings: &mut SettingsStore, cx| {
4107                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4108                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4109                })
4110            });
4111            item.is_dirty = true;
4112        });
4113
4114        // Blurring the item saves the file.
4115        item.update(cx, |_, cx| cx.blur());
4116        deterministic.run_until_parked();
4117        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4118
4119        // Deactivating the window still saves the file.
4120        cx.simulate_window_activation(Some(window_id));
4121        item.update(cx, |item, cx| {
4122            cx.focus_self();
4123            item.is_dirty = true;
4124        });
4125        cx.simulate_window_activation(None);
4126
4127        deterministic.run_until_parked();
4128        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4129
4130        // Autosave after delay.
4131        item.update(cx, |item, cx| {
4132            cx.update_global(|settings: &mut SettingsStore, cx| {
4133                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4134                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4135                })
4136            });
4137            item.is_dirty = true;
4138            cx.emit(TestItemEvent::Edit);
4139        });
4140
4141        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4142        deterministic.advance_clock(Duration::from_millis(250));
4143        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4144
4145        // After delay expires, the file is saved.
4146        deterministic.advance_clock(Duration::from_millis(250));
4147        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4148
4149        // Autosave on focus change, ensuring closing the tab counts as such.
4150        item.update(cx, |item, cx| {
4151            cx.update_global(|settings: &mut SettingsStore, cx| {
4152                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4153                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4154                })
4155            });
4156            item.is_dirty = true;
4157        });
4158
4159        pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
4160            .await
4161            .unwrap();
4162        assert!(!cx.has_pending_prompt(window_id));
4163        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4164
4165        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4166        workspace.update(cx, |workspace, cx| {
4167            workspace.add_item(Box::new(item.clone()), cx);
4168        });
4169        item.update(cx, |item, cx| {
4170            item.project_items[0].update(cx, |item, _| {
4171                item.entry_id = None;
4172            });
4173            item.is_dirty = true;
4174            cx.blur();
4175        });
4176        deterministic.run_until_parked();
4177        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4178
4179        // Ensure autosave is prevented for deleted files also when closing the buffer.
4180        let _close_items =
4181            pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
4182        deterministic.run_until_parked();
4183        assert!(cx.has_pending_prompt(window_id));
4184        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4185    }
4186
4187    #[gpui::test]
4188    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4189        init_test(cx);
4190
4191        let fs = FakeFs::new(cx.background());
4192
4193        let project = Project::test(fs, [], cx).await;
4194        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4195
4196        let item = cx.add_view(window_id, |cx| {
4197            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4198        });
4199        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4200        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4201        let toolbar_notify_count = Rc::new(RefCell::new(0));
4202
4203        workspace.update(cx, |workspace, cx| {
4204            workspace.add_item(Box::new(item.clone()), cx);
4205            let toolbar_notification_count = toolbar_notify_count.clone();
4206            cx.observe(&toolbar, move |_, _, _| {
4207                *toolbar_notification_count.borrow_mut() += 1
4208            })
4209            .detach();
4210        });
4211
4212        pane.read_with(cx, |pane, _| {
4213            assert!(!pane.can_navigate_backward());
4214            assert!(!pane.can_navigate_forward());
4215        });
4216
4217        item.update(cx, |item, cx| {
4218            item.set_state("one".to_string(), cx);
4219        });
4220
4221        // Toolbar must be notified to re-render the navigation buttons
4222        assert_eq!(*toolbar_notify_count.borrow(), 1);
4223
4224        pane.read_with(cx, |pane, _| {
4225            assert!(pane.can_navigate_backward());
4226            assert!(!pane.can_navigate_forward());
4227        });
4228
4229        workspace
4230            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4231            .await
4232            .unwrap();
4233
4234        assert_eq!(*toolbar_notify_count.borrow(), 3);
4235        pane.read_with(cx, |pane, _| {
4236            assert!(!pane.can_navigate_backward());
4237            assert!(pane.can_navigate_forward());
4238        });
4239    }
4240
4241    #[gpui::test]
4242    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4243        init_test(cx);
4244        let fs = FakeFs::new(cx.background());
4245
4246        let project = Project::test(fs, [], cx).await;
4247        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4248
4249        let panel = workspace.update(cx, |workspace, cx| {
4250            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4251            workspace.add_panel(panel.clone(), cx);
4252
4253            workspace
4254                .right_dock()
4255                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4256
4257            panel
4258        });
4259
4260        // Transfer focus from center to panel
4261        workspace.update(cx, |workspace, cx| {
4262            workspace.toggle_panel_focus::<TestPanel>(cx);
4263        });
4264
4265        workspace.read_with(cx, |workspace, cx| {
4266            assert!(workspace.right_dock().read(cx).is_open());
4267            assert!(!panel.is_zoomed(cx));
4268            assert!(panel.has_focus(cx));
4269        });
4270
4271        // Transfer focus from panel to center
4272        workspace.update(cx, |workspace, cx| {
4273            workspace.toggle_panel_focus::<TestPanel>(cx);
4274        });
4275
4276        workspace.read_with(cx, |workspace, cx| {
4277            assert!(workspace.right_dock().read(cx).is_open());
4278            assert!(!panel.is_zoomed(cx));
4279            assert!(!panel.has_focus(cx));
4280        });
4281
4282        // Close the dock
4283        workspace.update(cx, |workspace, cx| {
4284            workspace.toggle_dock(DockPosition::Right, cx);
4285        });
4286
4287        workspace.read_with(cx, |workspace, cx| {
4288            assert!(!workspace.right_dock().read(cx).is_open());
4289            assert!(!panel.is_zoomed(cx));
4290            assert!(!panel.has_focus(cx));
4291        });
4292
4293        // Open the dock
4294        workspace.update(cx, |workspace, cx| {
4295            workspace.toggle_dock(DockPosition::Right, cx);
4296        });
4297
4298        workspace.read_with(cx, |workspace, cx| {
4299            assert!(workspace.right_dock().read(cx).is_open());
4300            assert!(!panel.is_zoomed(cx));
4301            assert!(!panel.has_focus(cx));
4302        });
4303
4304        // Focus and zoom panel
4305        panel.update(cx, |panel, cx| {
4306            cx.focus_self();
4307            panel.set_zoomed(true, cx)
4308        });
4309
4310        workspace.read_with(cx, |workspace, cx| {
4311            assert!(workspace.right_dock().read(cx).is_open());
4312            assert!(panel.is_zoomed(cx));
4313            assert!(panel.has_focus(cx));
4314        });
4315
4316        // Transfer focus to the center closes the dock
4317        workspace.update(cx, |workspace, cx| {
4318            workspace.toggle_panel_focus::<TestPanel>(cx);
4319        });
4320
4321        workspace.read_with(cx, |workspace, cx| {
4322            assert!(!workspace.right_dock().read(cx).is_open());
4323            assert!(panel.is_zoomed(cx));
4324            assert!(!panel.has_focus(cx));
4325        });
4326
4327        // Transfering focus back to the panel keeps it zoomed
4328        workspace.update(cx, |workspace, cx| {
4329            workspace.toggle_panel_focus::<TestPanel>(cx);
4330        });
4331
4332        workspace.read_with(cx, |workspace, cx| {
4333            assert!(workspace.right_dock().read(cx).is_open());
4334            assert!(panel.is_zoomed(cx));
4335            assert!(panel.has_focus(cx));
4336        });
4337
4338        // Close the dock while it is zoomed
4339        workspace.update(cx, |workspace, cx| {
4340            workspace.toggle_dock(DockPosition::Right, cx)
4341        });
4342
4343        workspace.read_with(cx, |workspace, cx| {
4344            assert!(!workspace.right_dock().read(cx).is_open());
4345            assert!(panel.is_zoomed(cx));
4346            assert!(workspace.zoomed.is_none());
4347            assert!(!panel.has_focus(cx));
4348        });
4349
4350        // Opening the dock, when it's zoomed, retains focus
4351        workspace.update(cx, |workspace, cx| {
4352            workspace.toggle_dock(DockPosition::Right, cx)
4353        });
4354
4355        workspace.read_with(cx, |workspace, cx| {
4356            assert!(workspace.right_dock().read(cx).is_open());
4357            assert!(panel.is_zoomed(cx));
4358            assert!(workspace.zoomed.is_some());
4359            assert!(panel.has_focus(cx));
4360        });
4361    }
4362
4363    #[gpui::test]
4364    async fn test_panels(cx: &mut gpui::TestAppContext) {
4365        init_test(cx);
4366        let fs = FakeFs::new(cx.background());
4367
4368        let project = Project::test(fs, [], cx).await;
4369        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4370
4371        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4372            // Add panel_1 on the left, panel_2 on the right.
4373            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4374            workspace.add_panel(panel_1.clone(), cx);
4375            workspace
4376                .left_dock()
4377                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4378            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4379            workspace.add_panel(panel_2.clone(), cx);
4380            workspace
4381                .right_dock()
4382                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4383
4384            let left_dock = workspace.left_dock();
4385            assert_eq!(
4386                left_dock.read(cx).visible_panel().unwrap().id(),
4387                panel_1.id()
4388            );
4389            assert_eq!(
4390                left_dock.read(cx).active_panel_size(cx).unwrap(),
4391                panel_1.size(cx)
4392            );
4393
4394            left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx));
4395            assert_eq!(
4396                workspace
4397                    .right_dock()
4398                    .read(cx)
4399                    .visible_panel()
4400                    .unwrap()
4401                    .id(),
4402                panel_2.id()
4403            );
4404
4405            (panel_1, panel_2)
4406        });
4407
4408        // Move panel_1 to the right
4409        panel_1.update(cx, |panel_1, cx| {
4410            panel_1.set_position(DockPosition::Right, cx)
4411        });
4412
4413        workspace.update(cx, |workspace, cx| {
4414            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4415            // Since it was the only panel on the left, the left dock should now be closed.
4416            assert!(!workspace.left_dock().read(cx).is_open());
4417            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
4418            let right_dock = workspace.right_dock();
4419            assert_eq!(
4420                right_dock.read(cx).visible_panel().unwrap().id(),
4421                panel_1.id()
4422            );
4423            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4424
4425            // Now we move panel_2Β to the left
4426            panel_2.set_position(DockPosition::Left, cx);
4427        });
4428
4429        workspace.update(cx, |workspace, cx| {
4430            // Since panel_2 was not visible on the right, we don't open the left dock.
4431            assert!(!workspace.left_dock().read(cx).is_open());
4432            // And the right dock is unaffected in it's displaying of panel_1
4433            assert!(workspace.right_dock().read(cx).is_open());
4434            assert_eq!(
4435                workspace
4436                    .right_dock()
4437                    .read(cx)
4438                    .visible_panel()
4439                    .unwrap()
4440                    .id(),
4441                panel_1.id()
4442            );
4443        });
4444
4445        // Move panel_1 back to the left
4446        panel_1.update(cx, |panel_1, cx| {
4447            panel_1.set_position(DockPosition::Left, cx)
4448        });
4449
4450        workspace.update(cx, |workspace, cx| {
4451            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4452            let left_dock = workspace.left_dock();
4453            assert!(left_dock.read(cx).is_open());
4454            assert_eq!(
4455                left_dock.read(cx).visible_panel().unwrap().id(),
4456                panel_1.id()
4457            );
4458            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4459            // And right the dock should be closed as it no longer has any panels.
4460            assert!(!workspace.right_dock().read(cx).is_open());
4461
4462            // Now we move panel_1 to the bottom
4463            panel_1.set_position(DockPosition::Bottom, cx);
4464        });
4465
4466        workspace.update(cx, |workspace, cx| {
4467            // Since panel_1 was visible on the left, we close the left dock.
4468            assert!(!workspace.left_dock().read(cx).is_open());
4469            // The bottom dock is sized based on the panel's default size,
4470            // since the panel orientation changed from vertical to horizontal.
4471            let bottom_dock = workspace.bottom_dock();
4472            assert_eq!(
4473                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
4474                panel_1.size(cx),
4475            );
4476            // Close bottom dock and move panel_1 back to the left.
4477            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
4478            panel_1.set_position(DockPosition::Left, cx);
4479        });
4480
4481        // Emit activated event on panel 1
4482        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
4483
4484        // Now the left dock is open and panel_1 is active and focused.
4485        workspace.read_with(cx, |workspace, cx| {
4486            let left_dock = workspace.left_dock();
4487            assert!(left_dock.read(cx).is_open());
4488            assert_eq!(
4489                left_dock.read(cx).visible_panel().unwrap().id(),
4490                panel_1.id()
4491            );
4492            assert!(panel_1.is_focused(cx));
4493        });
4494
4495        // Emit closed event on panel 2, which is not active
4496        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4497
4498        // Wo don't close the left dock, because panel_2 wasn't the active panel
4499        workspace.read_with(cx, |workspace, cx| {
4500            let left_dock = workspace.left_dock();
4501            assert!(left_dock.read(cx).is_open());
4502            assert_eq!(
4503                left_dock.read(cx).visible_panel().unwrap().id(),
4504                panel_1.id()
4505            );
4506        });
4507
4508        // Emitting a ZoomIn event shows the panel as zoomed.
4509        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
4510        workspace.read_with(cx, |workspace, _| {
4511            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4512            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
4513        });
4514
4515        // Move panel to another dock while it is zoomed
4516        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
4517        workspace.read_with(cx, |workspace, _| {
4518            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4519            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4520        });
4521
4522        // If focus is transferred to another view that's not a panel or another pane, we still show
4523        // the panel as zoomed.
4524        let focus_receiver = cx.add_view(window_id, |_| EmptyView);
4525        focus_receiver.update(cx, |_, cx| cx.focus_self());
4526        workspace.read_with(cx, |workspace, _| {
4527            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4528            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4529        });
4530
4531        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
4532        workspace.update(cx, |_, cx| cx.focus_self());
4533        workspace.read_with(cx, |workspace, _| {
4534            assert_eq!(workspace.zoomed, None);
4535            assert_eq!(workspace.zoomed_position, None);
4536        });
4537
4538        // If focus is transferred again to another view that's not a panel or a pane, we won't
4539        // show the panel as zoomed because it wasn't zoomed before.
4540        focus_receiver.update(cx, |_, cx| cx.focus_self());
4541        workspace.read_with(cx, |workspace, _| {
4542            assert_eq!(workspace.zoomed, None);
4543            assert_eq!(workspace.zoomed_position, None);
4544        });
4545
4546        // When focus is transferred back to the panel, it is zoomed again.
4547        panel_1.update(cx, |_, cx| cx.focus_self());
4548        workspace.read_with(cx, |workspace, _| {
4549            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4550            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4551        });
4552
4553        // Emitting a ZoomOut event unzooms the panel.
4554        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
4555        workspace.read_with(cx, |workspace, _| {
4556            assert_eq!(workspace.zoomed, None);
4557            assert_eq!(workspace.zoomed_position, None);
4558        });
4559
4560        // Emit closed event on panel 1, which is active
4561        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4562
4563        // Now the left dock is closed, because panel_1 was the active panel
4564        workspace.read_with(cx, |workspace, cx| {
4565            let right_dock = workspace.right_dock();
4566            assert!(!right_dock.read(cx).is_open());
4567        });
4568    }
4569
4570    pub fn init_test(cx: &mut TestAppContext) {
4571        cx.foreground().forbid_parking();
4572        cx.update(|cx| {
4573            cx.set_global(SettingsStore::test(cx));
4574            theme::init((), cx);
4575            language::init(cx);
4576            crate::init_settings(cx);
4577        });
4578    }
4579}