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