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