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