workspace.rs

   1pub mod dock;
   2pub mod item;
   3mod modal_layer;
   4pub mod notifications;
   5pub mod pane;
   6pub mod pane_group;
   7mod persistence;
   8pub mod searchable;
   9pub mod shared_screen;
  10mod status_bar;
  11pub mod tasks;
  12mod toolbar;
  13mod workspace_settings;
  14
  15use anyhow::{anyhow, Context as _, Result};
  16use call::{call_settings::CallSettings, ActiveCall};
  17use client::{
  18    proto::{self, ErrorCode, PeerId},
  19    ChannelId, Client, ErrorExt, ProjectId, Status, TypedEnvelope, UserStore,
  20};
  21use collections::{hash_map, HashMap, HashSet};
  22use derive_more::{Deref, DerefMut};
  23use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  24use futures::{
  25    channel::{mpsc, oneshot},
  26    future::try_join_all,
  27    Future, FutureExt, StreamExt,
  28};
  29use gpui::{
  30    actions, canvas, impl_actions, point, relative, size, Action, AnyElement, AnyView, AnyWeakView,
  31    AppContext, AsyncAppContext, AsyncWindowContext, Bounds, DevicePixels, DragMoveEvent,
  32    ElementId, Entity as _, EntityId, EventEmitter, FocusHandle, FocusableView, Global,
  33    GlobalElementId, KeyContext, Keystroke, LayoutId, ManagedView, Model, ModelContext,
  34    PathPromptOptions, Point, PromptLevel, Render, Size, Subscription, Task, View, WeakView,
  35    WindowBounds, WindowHandle, WindowOptions,
  36};
  37use item::{
  38    FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
  39    ProjectItem,
  40};
  41use itertools::Itertools;
  42use language::{LanguageRegistry, Rope};
  43use lazy_static::lazy_static;
  44pub use modal_layer::*;
  45use node_runtime::NodeRuntime;
  46use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
  47pub use pane::*;
  48pub use pane_group::*;
  49use persistence::{model::SerializedWorkspace, SerializedWindowBounds, DB};
  50pub use persistence::{
  51    model::{ItemId, LocalPaths, SerializedDevServerProject, SerializedWorkspaceLocation},
  52    WorkspaceDb, DB as WORKSPACE_DB,
  53};
  54use postage::stream::Stream;
  55use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  56use serde::Deserialize;
  57use settings::Settings;
  58use shared_screen::SharedScreen;
  59use sqlez::{
  60    bindable::{Bind, Column, StaticColumnCount},
  61    statement::Statement,
  62};
  63use status_bar::StatusBar;
  64pub use status_bar::StatusItemView;
  65use std::{
  66    any::TypeId,
  67    borrow::Cow,
  68    cell::RefCell,
  69    cmp,
  70    collections::hash_map::DefaultHasher,
  71    env,
  72    hash::{Hash, Hasher},
  73    path::{Path, PathBuf},
  74    rc::Rc,
  75    sync::{atomic::AtomicUsize, Arc, Weak},
  76    time::Duration,
  77};
  78use task::SpawnInTerminal;
  79use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
  80pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
  81pub use ui;
  82use ui::{
  83    div, h_flex, Context as _, Div, Element, FluentBuilder, InteractiveElement as _, IntoElement,
  84    Label, ParentElement as _, Pixels, SharedString, Styled as _, ViewContext, VisualContext as _,
  85    WindowContext,
  86};
  87use util::{maybe, ResultExt};
  88use uuid::Uuid;
  89pub use workspace_settings::{
  90    AutosaveSetting, RestoreOnStartupBehaviour, TabBarSettings, WorkspaceSettings,
  91};
  92
  93use crate::notifications::NotificationId;
  94use crate::persistence::{
  95    model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
  96    SerializedAxis,
  97};
  98
  99lazy_static! {
 100    static ref ZED_WINDOW_SIZE: Option<Size<DevicePixels>> = env::var("ZED_WINDOW_SIZE")
 101        .ok()
 102        .as_deref()
 103        .and_then(parse_pixel_size_env_var);
 104    static ref ZED_WINDOW_POSITION: Option<Point<DevicePixels>> = env::var("ZED_WINDOW_POSITION")
 105        .ok()
 106        .as_deref()
 107        .and_then(parse_pixel_position_env_var);
 108}
 109
 110#[derive(Clone, PartialEq)]
 111pub struct RemoveWorktreeFromProject(pub WorktreeId);
 112
 113actions!(
 114    workspace,
 115    [
 116        Open,
 117        OpenInTerminal,
 118        NewFile,
 119        NewWindow,
 120        CloseWindow,
 121        AddFolderToProject,
 122        Unfollow,
 123        SaveAs,
 124        SaveWithoutFormat,
 125        ReloadActiveItem,
 126        ActivatePreviousPane,
 127        ActivateNextPane,
 128        FollowNextCollaborator,
 129        NewTerminal,
 130        NewCenterTerminal,
 131        NewSearch,
 132        Feedback,
 133        Restart,
 134        Welcome,
 135        ToggleZoom,
 136        ToggleLeftDock,
 137        ToggleRightDock,
 138        ToggleBottomDock,
 139        ToggleCenteredLayout,
 140        CloseAllDocks,
 141    ]
 142);
 143
 144#[derive(Clone, PartialEq)]
 145pub struct OpenPaths {
 146    pub paths: Vec<PathBuf>,
 147}
 148
 149#[derive(Clone, Deserialize, PartialEq)]
 150pub struct ActivatePane(pub usize);
 151
 152#[derive(Clone, Deserialize, PartialEq)]
 153pub struct ActivatePaneInDirection(pub SplitDirection);
 154
 155#[derive(Clone, Deserialize, PartialEq)]
 156pub struct SwapPaneInDirection(pub SplitDirection);
 157
 158#[derive(Clone, Deserialize, PartialEq)]
 159pub struct NewFileInDirection(pub SplitDirection);
 160
 161#[derive(Clone, PartialEq, Debug, Deserialize)]
 162#[serde(rename_all = "camelCase")]
 163pub struct SaveAll {
 164    pub save_intent: Option<SaveIntent>,
 165}
 166
 167#[derive(Clone, PartialEq, Debug, Deserialize)]
 168#[serde(rename_all = "camelCase")]
 169pub struct Save {
 170    pub save_intent: Option<SaveIntent>,
 171}
 172
 173#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 174#[serde(rename_all = "camelCase")]
 175pub struct CloseAllItemsAndPanes {
 176    pub save_intent: Option<SaveIntent>,
 177}
 178
 179#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 180#[serde(rename_all = "camelCase")]
 181pub struct CloseInactiveTabsAndPanes {
 182    pub save_intent: Option<SaveIntent>,
 183}
 184
 185#[derive(Clone, Deserialize, PartialEq)]
 186pub struct SendKeystrokes(pub String);
 187
 188impl_actions!(
 189    workspace,
 190    [
 191        ActivatePane,
 192        ActivatePaneInDirection,
 193        CloseAllItemsAndPanes,
 194        CloseInactiveTabsAndPanes,
 195        NewFileInDirection,
 196        OpenTerminal,
 197        Save,
 198        SaveAll,
 199        SwapPaneInDirection,
 200        SendKeystrokes,
 201    ]
 202);
 203
 204pub struct Toast {
 205    id: NotificationId,
 206    msg: Cow<'static, str>,
 207    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 208}
 209
 210impl Toast {
 211    pub fn new<I: Into<Cow<'static, str>>>(id: NotificationId, msg: I) -> Self {
 212        Toast {
 213            id,
 214            msg: msg.into(),
 215            on_click: None,
 216        }
 217    }
 218
 219    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 220    where
 221        M: Into<Cow<'static, str>>,
 222        F: Fn(&mut WindowContext) + 'static,
 223    {
 224        self.on_click = Some((message.into(), Arc::new(on_click)));
 225        self
 226    }
 227}
 228
 229impl PartialEq for Toast {
 230    fn eq(&self, other: &Self) -> bool {
 231        self.id == other.id
 232            && self.msg == other.msg
 233            && self.on_click.is_some() == other.on_click.is_some()
 234    }
 235}
 236
 237impl Clone for Toast {
 238    fn clone(&self) -> Self {
 239        Toast {
 240            id: self.id.clone(),
 241            msg: self.msg.clone(),
 242            on_click: self.on_click.clone(),
 243        }
 244    }
 245}
 246
 247#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
 248pub struct OpenTerminal {
 249    pub working_directory: PathBuf,
 250}
 251
 252#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
 253pub struct WorkspaceId(i64);
 254
 255impl StaticColumnCount for WorkspaceId {}
 256impl Bind for WorkspaceId {
 257    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
 258        self.0.bind(statement, start_index)
 259    }
 260}
 261impl Column for WorkspaceId {
 262    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
 263        i64::column(statement, start_index)
 264            .map(|(i, next_index)| (Self(i), next_index))
 265            .with_context(|| format!("Failed to read WorkspaceId at index {start_index}"))
 266    }
 267}
 268pub fn init_settings(cx: &mut AppContext) {
 269    WorkspaceSettings::register(cx);
 270    ItemSettings::register(cx);
 271    PreviewTabsSettings::register(cx);
 272    TabBarSettings::register(cx);
 273}
 274
 275pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 276    init_settings(cx);
 277    notifications::init(cx);
 278
 279    cx.on_action(Workspace::close_global);
 280    cx.on_action(restart);
 281
 282    cx.on_action({
 283        let app_state = Arc::downgrade(&app_state);
 284        move |_: &Open, cx: &mut AppContext| {
 285            let paths = cx.prompt_for_paths(PathPromptOptions {
 286                files: true,
 287                directories: true,
 288                multiple: true,
 289            });
 290
 291            if let Some(app_state) = app_state.upgrade() {
 292                cx.spawn(move |cx| async move {
 293                    if let Some(paths) = paths.await.log_err().flatten() {
 294                        cx.update(|cx| {
 295                            open_paths(&paths, app_state, OpenOptions::default(), cx)
 296                                .detach_and_log_err(cx)
 297                        })
 298                        .ok();
 299                    }
 300                })
 301                .detach();
 302            }
 303        }
 304    });
 305}
 306
 307#[derive(Clone, Default, Deref, DerefMut)]
 308struct ProjectItemOpeners(Vec<ProjectItemOpener>);
 309
 310type ProjectItemOpener = fn(
 311    &Model<Project>,
 312    &ProjectPath,
 313    &mut WindowContext,
 314)
 315    -> Option<Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>>>;
 316
 317type WorkspaceItemBuilder = Box<dyn FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
 318
 319impl Global for ProjectItemOpeners {}
 320
 321/// Registers a [ProjectItem] for the app. When opening a file, all the registered
 322/// items will get a chance to open the file, starting from the project item that
 323/// was added last.
 324pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 325    let builders = cx.default_global::<ProjectItemOpeners>();
 326    builders.push(|project, project_path, cx| {
 327        let project_item = <I::Item as project::Item>::try_open(&project, project_path, cx)?;
 328        let project = project.clone();
 329        Some(cx.spawn(|cx| async move {
 330            let project_item = project_item.await?;
 331            let project_entry_id: Option<ProjectEntryId> =
 332                project_item.read_with(&cx, |item, cx| project::Item::entry_id(item, cx))?;
 333            let build_workspace_item = Box::new(|cx: &mut ViewContext<Pane>| {
 334                Box::new(cx.new_view(|cx| I::for_project_item(project, project_item, cx)))
 335                    as Box<dyn ItemHandle>
 336            }) as Box<_>;
 337            Ok((project_entry_id, build_workspace_item))
 338        }))
 339    });
 340}
 341
 342type FollowableItemBuilder = fn(
 343    View<Pane>,
 344    View<Workspace>,
 345    ViewId,
 346    &mut Option<proto::view::Variant>,
 347    &mut WindowContext,
 348) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 349
 350#[derive(Default, Deref, DerefMut)]
 351struct FollowableItemBuilders(
 352    HashMap<
 353        TypeId,
 354        (
 355            FollowableItemBuilder,
 356            fn(&AnyView) -> Box<dyn FollowableItemHandle>,
 357        ),
 358    >,
 359);
 360
 361impl Global for FollowableItemBuilders {}
 362
 363pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 364    let builders = cx.default_global::<FollowableItemBuilders>();
 365    builders.insert(
 366        TypeId::of::<I>(),
 367        (
 368            |pane, workspace, id, state, cx| {
 369                I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
 370                    cx.foreground_executor()
 371                        .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 372                })
 373            },
 374            |this| Box::new(this.clone().downcast::<I>().unwrap()),
 375        ),
 376    );
 377}
 378
 379#[derive(Default, Deref, DerefMut)]
 380struct ItemDeserializers(
 381    HashMap<
 382        Arc<str>,
 383        fn(
 384            Model<Project>,
 385            WeakView<Workspace>,
 386            WorkspaceId,
 387            ItemId,
 388            &mut ViewContext<Pane>,
 389        ) -> Task<Result<Box<dyn ItemHandle>>>,
 390    >,
 391);
 392
 393impl Global for ItemDeserializers {}
 394
 395pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 396    if let Some(serialized_item_kind) = I::serialized_item_kind() {
 397        let deserializers = cx.default_global::<ItemDeserializers>();
 398        deserializers.insert(
 399            Arc::from(serialized_item_kind),
 400            |project, workspace, workspace_id, item_id, cx| {
 401                let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 402                cx.foreground_executor()
 403                    .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 404            },
 405        );
 406    }
 407}
 408
 409pub struct AppState {
 410    pub languages: Arc<LanguageRegistry>,
 411    pub client: Arc<Client>,
 412    pub user_store: Model<UserStore>,
 413    pub workspace_store: Model<WorkspaceStore>,
 414    pub fs: Arc<dyn fs::Fs>,
 415    pub build_window_options: fn(Option<Uuid>, &mut AppContext) -> WindowOptions,
 416    pub node_runtime: Arc<dyn NodeRuntime>,
 417}
 418
 419struct GlobalAppState(Weak<AppState>);
 420
 421impl Global for GlobalAppState {}
 422
 423pub struct WorkspaceStore {
 424    workspaces: HashSet<WindowHandle<Workspace>>,
 425    client: Arc<Client>,
 426    _subscriptions: Vec<client::Subscription>,
 427}
 428
 429#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 430struct Follower {
 431    project_id: Option<u64>,
 432    peer_id: PeerId,
 433}
 434
 435impl AppState {
 436    pub fn global(cx: &AppContext) -> Weak<Self> {
 437        cx.global::<GlobalAppState>().0.clone()
 438    }
 439    pub fn try_global(cx: &AppContext) -> Option<Weak<Self>> {
 440        cx.try_global::<GlobalAppState>()
 441            .map(|state| state.0.clone())
 442    }
 443    pub fn set_global(state: Weak<AppState>, cx: &mut AppContext) {
 444        cx.set_global(GlobalAppState(state));
 445    }
 446
 447    #[cfg(any(test, feature = "test-support"))]
 448    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 449        use node_runtime::FakeNodeRuntime;
 450        use settings::SettingsStore;
 451        use ui::Context as _;
 452
 453        if !cx.has_global::<SettingsStore>() {
 454            let settings_store = SettingsStore::test(cx);
 455            cx.set_global(settings_store);
 456        }
 457
 458        let fs = fs::FakeFs::new(cx.background_executor().clone());
 459        let languages = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
 460        let clock = Arc::new(clock::FakeSystemClock::default());
 461        let http_client = util::http::FakeHttpClient::with_404_response();
 462        let client = Client::new(clock, http_client.clone(), cx);
 463        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
 464        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
 465
 466        theme::init(theme::LoadThemes::JustBase, cx);
 467        client::init(&client, cx);
 468        crate::init_settings(cx);
 469
 470        Arc::new(Self {
 471            client,
 472            fs,
 473            languages,
 474            user_store,
 475            workspace_store,
 476            node_runtime: FakeNodeRuntime::new(),
 477            build_window_options: |_, _| Default::default(),
 478        })
 479    }
 480}
 481
 482struct DelayedDebouncedEditAction {
 483    task: Option<Task<()>>,
 484    cancel_channel: Option<oneshot::Sender<()>>,
 485}
 486
 487impl DelayedDebouncedEditAction {
 488    fn new() -> DelayedDebouncedEditAction {
 489        DelayedDebouncedEditAction {
 490            task: None,
 491            cancel_channel: None,
 492        }
 493    }
 494
 495    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 496    where
 497        F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 498    {
 499        if let Some(channel) = self.cancel_channel.take() {
 500            _ = channel.send(());
 501        }
 502
 503        let (sender, mut receiver) = oneshot::channel::<()>();
 504        self.cancel_channel = Some(sender);
 505
 506        let previous_task = self.task.take();
 507        self.task = Some(cx.spawn(move |workspace, mut cx| async move {
 508            let mut timer = cx.background_executor().timer(delay).fuse();
 509            if let Some(previous_task) = previous_task {
 510                previous_task.await;
 511            }
 512
 513            futures::select_biased! {
 514                _ = receiver => return,
 515                    _ = timer => {}
 516            }
 517
 518            if let Some(result) = workspace
 519                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 520                .log_err()
 521            {
 522                result.await.log_err();
 523            }
 524        }));
 525    }
 526}
 527
 528pub enum Event {
 529    PaneAdded(View<Pane>),
 530    ActiveItemChanged,
 531    ContactRequestedJoin(u64),
 532    WorkspaceCreated(WeakView<Workspace>),
 533    SpawnTask(SpawnInTerminal),
 534    OpenBundledFile {
 535        text: Cow<'static, str>,
 536        title: &'static str,
 537        language: &'static str,
 538    },
 539    ZoomChanged,
 540}
 541
 542pub enum OpenVisible {
 543    All,
 544    None,
 545    OnlyFiles,
 546    OnlyDirectories,
 547}
 548
 549type PromptForNewPath = Box<
 550    dyn Fn(&mut Workspace, &mut ViewContext<Workspace>) -> oneshot::Receiver<Option<ProjectPath>>,
 551>;
 552
 553/// Collects everything project-related for a certain window opened.
 554/// In some way, is a counterpart of a window, as the [`WindowHandle`] could be downcast into `Workspace`.
 555///
 556/// A `Workspace` usually consists of 1 or more projects, a central pane group, 3 docks and a status bar.
 557/// The `Workspace` owns everybody's state and serves as a default, "global context",
 558/// that can be used to register a global action to be triggered from any place in the window.
 559pub struct Workspace {
 560    weak_self: WeakView<Self>,
 561    workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
 562    zoomed: Option<AnyWeakView>,
 563    zoomed_position: Option<DockPosition>,
 564    center: PaneGroup,
 565    left_dock: View<Dock>,
 566    bottom_dock: View<Dock>,
 567    right_dock: View<Dock>,
 568    panes: Vec<View<Pane>>,
 569    panes_by_item: HashMap<EntityId, WeakView<Pane>>,
 570    active_pane: View<Pane>,
 571    last_active_center_pane: Option<WeakView<Pane>>,
 572    last_active_view_id: Option<proto::ViewId>,
 573    status_bar: View<StatusBar>,
 574    modal_layer: View<ModalLayer>,
 575    titlebar_item: Option<AnyView>,
 576    notifications: Vec<(NotificationId, Box<dyn NotificationHandle>)>,
 577    project: Model<Project>,
 578    follower_states: HashMap<View<Pane>, FollowerState>,
 579    last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
 580    window_edited: bool,
 581    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
 582    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 583    database_id: WorkspaceId,
 584    app_state: Arc<AppState>,
 585    dispatching_keystrokes: Rc<RefCell<Vec<Keystroke>>>,
 586    _subscriptions: Vec<Subscription>,
 587    _apply_leader_updates: Task<Result<()>>,
 588    _observe_current_user: Task<Result<()>>,
 589    _schedule_serialize: Option<Task<()>>,
 590    pane_history_timestamp: Arc<AtomicUsize>,
 591    bounds: Bounds<Pixels>,
 592    centered_layout: bool,
 593    bounds_save_task_queued: Option<Task<()>>,
 594    on_prompt_for_new_path: Option<PromptForNewPath>,
 595}
 596
 597impl EventEmitter<Event> for Workspace {}
 598
 599#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 600pub struct ViewId {
 601    pub creator: PeerId,
 602    pub id: u64,
 603}
 604
 605#[derive(Default)]
 606struct FollowerState {
 607    leader_id: PeerId,
 608    active_view_id: Option<ViewId>,
 609    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 610}
 611
 612impl Workspace {
 613    const DEFAULT_PADDING: f32 = 0.2;
 614    const MAX_PADDING: f32 = 0.4;
 615
 616    pub fn new(
 617        workspace_id: WorkspaceId,
 618        project: Model<Project>,
 619        app_state: Arc<AppState>,
 620        cx: &mut ViewContext<Self>,
 621    ) -> Self {
 622        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 623        cx.subscribe(&project, move |this, _, event, cx| {
 624            match event {
 625                project::Event::RemoteIdChanged(_) => {
 626                    this.update_window_title(cx);
 627                }
 628
 629                project::Event::CollaboratorLeft(peer_id) => {
 630                    this.collaborator_left(*peer_id, cx);
 631                }
 632
 633                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 634                    this.update_window_title(cx);
 635                    this.serialize_workspace(cx);
 636                }
 637
 638                project::Event::DisconnectedFromHost => {
 639                    this.update_window_edited(cx);
 640                    let panes_to_unfollow: Vec<View<Pane>> =
 641                        this.follower_states.keys().map(|k| k.clone()).collect();
 642                    for pane in panes_to_unfollow {
 643                        this.unfollow(&pane, cx);
 644                    }
 645                    cx.disable_focus();
 646                }
 647
 648                project::Event::Closed => {
 649                    cx.remove_window();
 650                }
 651
 652                project::Event::DeletedEntry(entry_id) => {
 653                    for pane in this.panes.iter() {
 654                        pane.update(cx, |pane, cx| {
 655                            pane.handle_deleted_project_item(*entry_id, cx)
 656                        });
 657                    }
 658                }
 659
 660                project::Event::Notification(message) => {
 661                    struct ProjectNotification;
 662
 663                    this.show_notification(
 664                        NotificationId::unique::<ProjectNotification>(),
 665                        cx,
 666                        |cx| cx.new_view(|_| MessageNotification::new(message.clone())),
 667                    )
 668                }
 669
 670                project::Event::LanguageServerPrompt(request) => {
 671                    struct LanguageServerPrompt;
 672
 673                    let mut hasher = DefaultHasher::new();
 674                    request.lsp_name.as_str().hash(&mut hasher);
 675                    let id = hasher.finish();
 676
 677                    this.show_notification(
 678                        NotificationId::identified::<LanguageServerPrompt>(id as usize),
 679                        cx,
 680                        |cx| {
 681                            cx.new_view(|_| {
 682                                notifications::LanguageServerPrompt::new(request.clone())
 683                            })
 684                        },
 685                    );
 686                }
 687
 688                _ => {}
 689            }
 690            cx.notify()
 691        })
 692        .detach();
 693
 694        cx.on_focus_lost(|this, cx| {
 695            let focus_handle = this.focus_handle(cx);
 696            cx.focus(&focus_handle);
 697        })
 698        .detach();
 699
 700        let weak_handle = cx.view().downgrade();
 701        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 702
 703        let center_pane = cx.new_view(|cx| {
 704            Pane::new(
 705                weak_handle.clone(),
 706                project.clone(),
 707                pane_history_timestamp.clone(),
 708                None,
 709                NewFile.boxed_clone(),
 710                cx,
 711            )
 712        });
 713        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 714
 715        cx.focus_view(&center_pane);
 716        cx.emit(Event::PaneAdded(center_pane.clone()));
 717
 718        let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
 719        app_state.workspace_store.update(cx, |store, _| {
 720            store.workspaces.insert(window_handle);
 721        });
 722
 723        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 724        let mut connection_status = app_state.client.status();
 725        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 726            current_user.next().await;
 727            connection_status.next().await;
 728            let mut stream =
 729                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 730
 731            while stream.recv().await.is_some() {
 732                this.update(&mut cx, |_, cx| cx.notify())?;
 733            }
 734            anyhow::Ok(())
 735        });
 736
 737        // All leader updates are enqueued and then processed in a single task, so
 738        // that each asynchronous operation can be run in order.
 739        let (leader_updates_tx, mut leader_updates_rx) =
 740            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 741        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 742            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 743                Self::process_leader_update(&this, leader_id, update, &mut cx)
 744                    .await
 745                    .log_err();
 746            }
 747
 748            Ok(())
 749        });
 750
 751        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
 752
 753        let left_dock = Dock::new(DockPosition::Left, cx);
 754        let bottom_dock = Dock::new(DockPosition::Bottom, cx);
 755        let right_dock = Dock::new(DockPosition::Right, cx);
 756        let left_dock_buttons = cx.new_view(|cx| PanelButtons::new(left_dock.clone(), cx));
 757        let bottom_dock_buttons = cx.new_view(|cx| PanelButtons::new(bottom_dock.clone(), cx));
 758        let right_dock_buttons = cx.new_view(|cx| PanelButtons::new(right_dock.clone(), cx));
 759        let status_bar = cx.new_view(|cx| {
 760            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 761            status_bar.add_left_item(left_dock_buttons, cx);
 762            status_bar.add_right_item(right_dock_buttons, cx);
 763            status_bar.add_right_item(bottom_dock_buttons, cx);
 764            status_bar
 765        });
 766
 767        let modal_layer = cx.new_view(|_| ModalLayer::new());
 768
 769        let mut active_call = None;
 770        if let Some(call) = ActiveCall::try_global(cx) {
 771            let call = call.clone();
 772            let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)];
 773            active_call = Some((call, subscriptions));
 774        }
 775
 776        let subscriptions = vec![
 777            cx.observe_window_activation(Self::on_window_activation_changed),
 778            cx.observe_window_bounds(move |this, cx| {
 779                if this.bounds_save_task_queued.is_some() {
 780                    return;
 781                }
 782                this.bounds_save_task_queued = Some(cx.spawn(|this, mut cx| async move {
 783                    cx.background_executor()
 784                        .timer(Duration::from_millis(100))
 785                        .await;
 786                    this.update(&mut cx, |this, cx| {
 787                        if let Some(display) = cx.display() {
 788                            if let Some(display_uuid) = display.uuid().log_err() {
 789                                let window_bounds = cx.window_bounds();
 790                                cx.background_executor()
 791                                    .spawn(DB.set_window_open_status(
 792                                        workspace_id,
 793                                        SerializedWindowBounds(window_bounds),
 794                                        display_uuid,
 795                                    ))
 796                                    .detach_and_log_err(cx);
 797                            }
 798                        }
 799                        this.bounds_save_task_queued.take();
 800                    })
 801                    .ok();
 802                }));
 803                cx.notify();
 804            }),
 805            cx.observe_window_appearance(|_, cx| {
 806                let window_appearance = cx.appearance();
 807
 808                *SystemAppearance::global_mut(cx) = SystemAppearance(window_appearance.into());
 809
 810                ThemeSettings::reload_current_theme(cx);
 811            }),
 812            cx.observe(&left_dock, |this, _, cx| {
 813                this.serialize_workspace(cx);
 814                cx.notify();
 815            }),
 816            cx.observe(&bottom_dock, |this, _, cx| {
 817                this.serialize_workspace(cx);
 818                cx.notify();
 819            }),
 820            cx.observe(&right_dock, |this, _, cx| {
 821                this.serialize_workspace(cx);
 822                cx.notify();
 823            }),
 824            cx.on_release(|this, window, cx| {
 825                this.app_state.workspace_store.update(cx, |store, _| {
 826                    let window = window.downcast::<Self>().unwrap();
 827                    store.workspaces.remove(&window);
 828                })
 829            }),
 830        ];
 831
 832        cx.defer(|this, cx| {
 833            this.update_window_title(cx);
 834        });
 835        Workspace {
 836            weak_self: weak_handle.clone(),
 837            zoomed: None,
 838            zoomed_position: None,
 839            center: PaneGroup::new(center_pane.clone()),
 840            panes: vec![center_pane.clone()],
 841            panes_by_item: Default::default(),
 842            active_pane: center_pane.clone(),
 843            last_active_center_pane: Some(center_pane.downgrade()),
 844            last_active_view_id: None,
 845            status_bar,
 846            modal_layer,
 847            titlebar_item: None,
 848            notifications: Default::default(),
 849            left_dock,
 850            bottom_dock,
 851            right_dock,
 852            project: project.clone(),
 853            follower_states: Default::default(),
 854            last_leaders_by_pane: Default::default(),
 855            dispatching_keystrokes: Default::default(),
 856            window_edited: false,
 857            active_call,
 858            database_id: workspace_id,
 859            app_state,
 860            _observe_current_user,
 861            _apply_leader_updates,
 862            _schedule_serialize: None,
 863            leader_updates_tx,
 864            _subscriptions: subscriptions,
 865            pane_history_timestamp,
 866            workspace_actions: Default::default(),
 867            // This data will be incorrect, but it will be overwritten by the time it needs to be used.
 868            bounds: Default::default(),
 869            centered_layout: false,
 870            bounds_save_task_queued: None,
 871            on_prompt_for_new_path: None,
 872        }
 873    }
 874
 875    fn new_local(
 876        abs_paths: Vec<PathBuf>,
 877        app_state: Arc<AppState>,
 878        requesting_window: Option<WindowHandle<Workspace>>,
 879        cx: &mut AppContext,
 880    ) -> Task<
 881        anyhow::Result<(
 882            WindowHandle<Workspace>,
 883            Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 884        )>,
 885    > {
 886        let project_handle = Project::local(
 887            app_state.client.clone(),
 888            app_state.node_runtime.clone(),
 889            app_state.user_store.clone(),
 890            app_state.languages.clone(),
 891            app_state.fs.clone(),
 892            cx,
 893        );
 894
 895        cx.spawn(|mut cx| async move {
 896            let serialized_workspace: Option<SerializedWorkspace> =
 897                persistence::DB.workspace_for_roots(abs_paths.as_slice());
 898
 899            let paths_to_open = Arc::new(abs_paths);
 900
 901            // Get project paths for all of the abs_paths
 902            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 903            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 904                Vec::with_capacity(paths_to_open.len());
 905            for path in paths_to_open.iter().cloned() {
 906                if let Some((worktree, project_entry)) = cx
 907                    .update(|cx| {
 908                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 909                    })?
 910                    .await
 911                    .log_err()
 912                {
 913                    worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
 914                    project_paths.push((path, Some(project_entry)));
 915                } else {
 916                    project_paths.push((path, None));
 917                }
 918            }
 919
 920            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 921                serialized_workspace.id
 922            } else {
 923                DB.next_id().await.unwrap_or_else(|_| Default::default())
 924            };
 925
 926            let window = if let Some(window) = requesting_window {
 927                cx.update_window(window.into(), |_, cx| {
 928                    cx.replace_root_view(|cx| {
 929                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 930                    });
 931                })?;
 932                window
 933            } else {
 934                let window_bounds_override = window_bounds_env_override();
 935
 936                let (window_bounds, display) = if let Some(bounds) = window_bounds_override {
 937                    (Some(WindowBounds::Windowed(bounds)), None)
 938                } else {
 939                    let restorable_bounds = serialized_workspace
 940                        .as_ref()
 941                        .and_then(|workspace| Some((workspace.display?, workspace.window_bounds?)))
 942                        .or_else(|| {
 943                            let (display, window_bounds) = DB.last_window().log_err()?;
 944                            Some((display?, window_bounds?))
 945                        });
 946
 947                    if let Some((serialized_display, serialized_status)) = restorable_bounds {
 948                        (Some(serialized_status.0), Some(serialized_display))
 949                    } else {
 950                        (None, None)
 951                    }
 952                };
 953
 954                // Use the serialized workspace to construct the new window
 955                let mut options = cx.update(|cx| (app_state.build_window_options)(display, cx))?;
 956                options.window_bounds = window_bounds;
 957                let centered_layout = serialized_workspace
 958                    .as_ref()
 959                    .map(|w| w.centered_layout)
 960                    .unwrap_or(false);
 961                cx.open_window(options, {
 962                    let app_state = app_state.clone();
 963                    let project_handle = project_handle.clone();
 964                    move |cx| {
 965                        cx.new_view(|cx| {
 966                            let mut workspace =
 967                                Workspace::new(workspace_id, project_handle, app_state, cx);
 968                            workspace.centered_layout = centered_layout;
 969                            workspace
 970                        })
 971                    }
 972                })?
 973            };
 974
 975            notify_if_database_failed(window, &mut cx);
 976            let opened_items = window
 977                .update(&mut cx, |_workspace, cx| {
 978                    open_items(serialized_workspace, project_paths, app_state, cx)
 979                })?
 980                .await
 981                .unwrap_or_default();
 982
 983            window
 984                .update(&mut cx, |_, cx| cx.activate_window())
 985                .log_err();
 986            Ok((window, opened_items))
 987        })
 988    }
 989
 990    pub fn weak_handle(&self) -> WeakView<Self> {
 991        self.weak_self.clone()
 992    }
 993
 994    pub fn left_dock(&self) -> &View<Dock> {
 995        &self.left_dock
 996    }
 997
 998    pub fn bottom_dock(&self) -> &View<Dock> {
 999        &self.bottom_dock
1000    }
1001
1002    pub fn right_dock(&self) -> &View<Dock> {
1003        &self.right_dock
1004    }
1005
1006    pub fn is_edited(&self) -> bool {
1007        self.window_edited
1008    }
1009
1010    pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut WindowContext) {
1011        let dock = match panel.position(cx) {
1012            DockPosition::Left => &self.left_dock,
1013            DockPosition::Bottom => &self.bottom_dock,
1014            DockPosition::Right => &self.right_dock,
1015        };
1016
1017        dock.update(cx, |dock, cx| {
1018            dock.add_panel(panel, self.weak_self.clone(), cx)
1019        });
1020    }
1021
1022    pub fn status_bar(&self) -> &View<StatusBar> {
1023        &self.status_bar
1024    }
1025
1026    pub fn app_state(&self) -> &Arc<AppState> {
1027        &self.app_state
1028    }
1029
1030    pub fn user_store(&self) -> &Model<UserStore> {
1031        &self.app_state.user_store
1032    }
1033
1034    pub fn project(&self) -> &Model<Project> {
1035        &self.project
1036    }
1037
1038    pub fn recent_navigation_history(
1039        &self,
1040        limit: Option<usize>,
1041        cx: &AppContext,
1042    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
1043        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
1044        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
1045        for pane in &self.panes {
1046            let pane = pane.read(cx);
1047            pane.nav_history()
1048                .for_each_entry(cx, |entry, (project_path, fs_path)| {
1049                    if let Some(fs_path) = &fs_path {
1050                        abs_paths_opened
1051                            .entry(fs_path.clone())
1052                            .or_default()
1053                            .insert(project_path.clone());
1054                    }
1055                    let timestamp = entry.timestamp;
1056                    match history.entry(project_path) {
1057                        hash_map::Entry::Occupied(mut entry) => {
1058                            let (_, old_timestamp) = entry.get();
1059                            if &timestamp > old_timestamp {
1060                                entry.insert((fs_path, timestamp));
1061                            }
1062                        }
1063                        hash_map::Entry::Vacant(entry) => {
1064                            entry.insert((fs_path, timestamp));
1065                        }
1066                    }
1067                });
1068        }
1069
1070        history
1071            .into_iter()
1072            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
1073            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
1074            .rev()
1075            .filter(|(history_path, abs_path)| {
1076                let latest_project_path_opened = abs_path
1077                    .as_ref()
1078                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
1079                    .and_then(|project_paths| {
1080                        project_paths
1081                            .iter()
1082                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
1083                    });
1084
1085                match latest_project_path_opened {
1086                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
1087                    None => true,
1088                }
1089            })
1090            .take(limit.unwrap_or(usize::MAX))
1091            .collect()
1092    }
1093
1094    fn navigate_history(
1095        &mut self,
1096        pane: WeakView<Pane>,
1097        mode: NavigationMode,
1098        cx: &mut ViewContext<Workspace>,
1099    ) -> Task<Result<()>> {
1100        let to_load = if let Some(pane) = pane.upgrade() {
1101            pane.update(cx, |pane, cx| {
1102                pane.focus(cx);
1103                loop {
1104                    // Retrieve the weak item handle from the history.
1105                    let entry = pane.nav_history_mut().pop(mode, cx)?;
1106
1107                    // If the item is still present in this pane, then activate it.
1108                    if let Some(index) = entry
1109                        .item
1110                        .upgrade()
1111                        .and_then(|v| pane.index_for_item(v.as_ref()))
1112                    {
1113                        let prev_active_item_index = pane.active_item_index();
1114                        pane.nav_history_mut().set_mode(mode);
1115                        pane.activate_item(index, true, true, cx);
1116                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1117
1118                        let mut navigated = prev_active_item_index != pane.active_item_index();
1119                        if let Some(data) = entry.data {
1120                            navigated |= pane.active_item()?.navigate(data, cx);
1121                        }
1122
1123                        if navigated {
1124                            break None;
1125                        }
1126                    }
1127                    // If the item is no longer present in this pane, then retrieve its
1128                    // project path in order to reopen it.
1129                    else {
1130                        break pane
1131                            .nav_history()
1132                            .path_for_item(entry.item.id())
1133                            .map(|(project_path, _)| (project_path, entry));
1134                    }
1135                }
1136            })
1137        } else {
1138            None
1139        };
1140
1141        if let Some((project_path, entry)) = to_load {
1142            // If the item was no longer present, then load it again from its previous path.
1143            let task = self.load_path(project_path, cx);
1144            cx.spawn(|workspace, mut cx| async move {
1145                let task = task.await;
1146                let mut navigated = false;
1147                if let Some((project_entry_id, build_item)) = task.log_err() {
1148                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
1149                        pane.nav_history_mut().set_mode(mode);
1150                        pane.active_item().map(|p| p.item_id())
1151                    })?;
1152
1153                    pane.update(&mut cx, |pane, cx| {
1154                        let item = pane.open_item(
1155                            project_entry_id,
1156                            true,
1157                            entry.is_preview,
1158                            cx,
1159                            build_item,
1160                        );
1161                        navigated |= Some(item.item_id()) != prev_active_item_id;
1162                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1163                        if let Some(data) = entry.data {
1164                            navigated |= item.navigate(data, cx);
1165                        }
1166                    })?;
1167                }
1168
1169                if !navigated {
1170                    workspace
1171                        .update(&mut cx, |workspace, cx| {
1172                            Self::navigate_history(workspace, pane, mode, cx)
1173                        })?
1174                        .await?;
1175                }
1176
1177                Ok(())
1178            })
1179        } else {
1180            Task::ready(Ok(()))
1181        }
1182    }
1183
1184    pub fn go_back(
1185        &mut self,
1186        pane: WeakView<Pane>,
1187        cx: &mut ViewContext<Workspace>,
1188    ) -> Task<Result<()>> {
1189        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1190    }
1191
1192    pub fn go_forward(
1193        &mut self,
1194        pane: WeakView<Pane>,
1195        cx: &mut ViewContext<Workspace>,
1196    ) -> Task<Result<()>> {
1197        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1198    }
1199
1200    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1201        self.navigate_history(
1202            self.active_pane().downgrade(),
1203            NavigationMode::ReopeningClosedItem,
1204            cx,
1205        )
1206    }
1207
1208    pub fn client(&self) -> &Arc<Client> {
1209        &self.app_state.client
1210    }
1211
1212    pub fn set_titlebar_item(&mut self, item: AnyView, cx: &mut ViewContext<Self>) {
1213        self.titlebar_item = Some(item);
1214        cx.notify();
1215    }
1216
1217    pub fn set_prompt_for_new_path(&mut self, prompt: PromptForNewPath) {
1218        self.on_prompt_for_new_path = Some(prompt)
1219    }
1220
1221    pub fn prompt_for_new_path(
1222        &mut self,
1223        cx: &mut ViewContext<Self>,
1224    ) -> oneshot::Receiver<Option<ProjectPath>> {
1225        if let Some(prompt) = self.on_prompt_for_new_path.take() {
1226            let rx = prompt(self, cx);
1227            self.on_prompt_for_new_path = Some(prompt);
1228            rx
1229        } else {
1230            let start_abs_path = self
1231                .project
1232                .update(cx, |project, cx| {
1233                    let worktree = project.visible_worktrees(cx).next()?;
1234                    Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
1235                })
1236                .unwrap_or_else(|| Path::new("").into());
1237
1238            let (tx, rx) = oneshot::channel();
1239            let abs_path = cx.prompt_for_new_path(&start_abs_path);
1240            cx.spawn(|this, mut cx| async move {
1241                let abs_path = abs_path.await?;
1242                let project_path = abs_path.and_then(|abs_path| {
1243                    this.update(&mut cx, |this, cx| {
1244                        this.project.update(cx, |project, cx| {
1245                            project.find_or_create_local_worktree(abs_path, true, cx)
1246                        })
1247                    })
1248                    .ok()
1249                });
1250
1251                if let Some(project_path) = project_path {
1252                    let (worktree, path) = project_path.await?;
1253                    let worktree_id = worktree.read_with(&cx, |worktree, _| worktree.id())?;
1254                    tx.send(Some(ProjectPath {
1255                        worktree_id,
1256                        path: path.into(),
1257                    }))
1258                    .ok();
1259                } else {
1260                    tx.send(None).ok();
1261                }
1262                anyhow::Ok(())
1263            })
1264            .detach_and_log_err(cx);
1265
1266            rx
1267        }
1268    }
1269
1270    pub fn titlebar_item(&self) -> Option<AnyView> {
1271        self.titlebar_item.clone()
1272    }
1273
1274    /// Call the given callback with a workspace whose project is local.
1275    ///
1276    /// If the given workspace has a local project, then it will be passed
1277    /// to the callback. Otherwise, a new empty window will be created.
1278    pub fn with_local_workspace<T, F>(
1279        &mut self,
1280        cx: &mut ViewContext<Self>,
1281        callback: F,
1282    ) -> Task<Result<T>>
1283    where
1284        T: 'static,
1285        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1286    {
1287        if self.project.read(cx).is_local() {
1288            Task::Ready(Some(Ok(callback(self, cx))))
1289        } else {
1290            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1291            cx.spawn(|_vh, mut cx| async move {
1292                let (workspace, _) = task.await?;
1293                workspace.update(&mut cx, callback)
1294            })
1295        }
1296    }
1297
1298    pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Model<Worktree>> {
1299        self.project.read(cx).worktrees()
1300    }
1301
1302    pub fn visible_worktrees<'a>(
1303        &self,
1304        cx: &'a AppContext,
1305    ) -> impl 'a + Iterator<Item = Model<Worktree>> {
1306        self.project.read(cx).visible_worktrees(cx)
1307    }
1308
1309    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1310        let futures = self
1311            .worktrees(cx)
1312            .filter_map(|worktree| worktree.read(cx).as_local())
1313            .map(|worktree| worktree.scan_complete())
1314            .collect::<Vec<_>>();
1315        async move {
1316            for future in futures {
1317                future.await;
1318            }
1319        }
1320    }
1321
1322    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1323        cx.defer(|cx| {
1324            cx.windows().iter().find(|window| {
1325                window
1326                    .update(cx, |_, window| {
1327                        if window.is_window_active() {
1328                            //This can only get called when the window's project connection has been lost
1329                            //so we don't need to prompt the user for anything and instead just close the window
1330                            window.remove_window();
1331                            true
1332                        } else {
1333                            false
1334                        }
1335                    })
1336                    .unwrap_or(false)
1337            });
1338        });
1339    }
1340
1341    pub fn close_window(&mut self, _: &CloseWindow, cx: &mut ViewContext<Self>) {
1342        let window = cx.window_handle();
1343        let prepare = self.prepare_to_close(false, cx);
1344        cx.spawn(|_, mut cx| async move {
1345            if prepare.await? {
1346                window.update(&mut cx, |_, cx| {
1347                    cx.remove_window();
1348                })?;
1349            }
1350            anyhow::Ok(())
1351        })
1352        .detach_and_log_err(cx)
1353    }
1354
1355    pub fn prepare_to_close(
1356        &mut self,
1357        quitting: bool,
1358        cx: &mut ViewContext<Self>,
1359    ) -> Task<Result<bool>> {
1360        let active_call = self.active_call().cloned();
1361        let window = cx.window_handle();
1362
1363        cx.spawn(|this, mut cx| async move {
1364            let workspace_count = (*cx).update(|cx| {
1365                cx.windows()
1366                    .iter()
1367                    .filter(|window| window.downcast::<Workspace>().is_some())
1368                    .count()
1369            })?;
1370
1371            if let Some(active_call) = active_call {
1372                if !quitting
1373                    && workspace_count == 1
1374                    && active_call.read_with(&cx, |call, _| call.room().is_some())?
1375                {
1376                    let answer = window.update(&mut cx, |_, cx| {
1377                        cx.prompt(
1378                            PromptLevel::Warning,
1379                            "Do you want to leave the current call?",
1380                            None,
1381                            &["Close window and hang up", "Cancel"],
1382                        )
1383                    })?;
1384
1385                    if answer.await.log_err() == Some(1) {
1386                        return anyhow::Ok(false);
1387                    } else {
1388                        active_call
1389                            .update(&mut cx, |call, cx| call.hang_up(cx))?
1390                            .await
1391                            .log_err();
1392                    }
1393                }
1394            }
1395
1396            this.update(&mut cx, |this, cx| {
1397                this.save_all_internal(SaveIntent::Close, cx)
1398            })?
1399            .await
1400        })
1401    }
1402
1403    fn save_all(&mut self, action: &SaveAll, cx: &mut ViewContext<Self>) {
1404        self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx)
1405            .detach_and_log_err(cx);
1406    }
1407
1408    fn send_keystrokes(&mut self, action: &SendKeystrokes, cx: &mut ViewContext<Self>) {
1409        let mut keystrokes: Vec<Keystroke> = action
1410            .0
1411            .split(' ')
1412            .flat_map(|k| Keystroke::parse(k).log_err())
1413            .collect();
1414        keystrokes.reverse();
1415
1416        self.dispatching_keystrokes
1417            .borrow_mut()
1418            .append(&mut keystrokes);
1419
1420        let keystrokes = self.dispatching_keystrokes.clone();
1421        cx.window_context()
1422            .spawn(|mut cx| async move {
1423                // limit to 100 keystrokes to avoid infinite recursion.
1424                for _ in 0..100 {
1425                    let Some(keystroke) = keystrokes.borrow_mut().pop() else {
1426                        return Ok(());
1427                    };
1428                    cx.update(|cx| {
1429                        let focused = cx.focused();
1430                        cx.dispatch_keystroke(keystroke.clone());
1431                        if cx.focused() != focused {
1432                            // dispatch_keystroke may cause the focus to change.
1433                            // draw's side effect is to schedule the FocusChanged events in the current flush effect cycle
1434                            // And we need that to happen before the next keystroke to keep vim mode happy...
1435                            // (Note that the tests always do this implicitly, so you must manually test with something like:
1436                            //   "bindings": { "g z": ["workspace::SendKeystrokes", ": j <enter> u"]}
1437                            // )
1438                            cx.draw();
1439                        }
1440                    })?;
1441                }
1442                keystrokes.borrow_mut().clear();
1443                Err(anyhow!("over 100 keystrokes passed to send_keystrokes"))
1444            })
1445            .detach_and_log_err(cx);
1446    }
1447
1448    fn save_all_internal(
1449        &mut self,
1450        mut save_intent: SaveIntent,
1451        cx: &mut ViewContext<Self>,
1452    ) -> Task<Result<bool>> {
1453        if self.project.read(cx).is_disconnected() {
1454            return Task::ready(Ok(true));
1455        }
1456        let dirty_items = self
1457            .panes
1458            .iter()
1459            .flat_map(|pane| {
1460                pane.read(cx).items().filter_map(|item| {
1461                    if item.is_dirty(cx) {
1462                        Some((pane.downgrade(), item.boxed_clone()))
1463                    } else {
1464                        None
1465                    }
1466                })
1467            })
1468            .collect::<Vec<_>>();
1469
1470        let project = self.project.clone();
1471        cx.spawn(|workspace, mut cx| async move {
1472            // Override save mode and display "Save all files" prompt
1473            if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
1474                let answer = workspace.update(&mut cx, |_, cx| {
1475                    let (prompt, detail) = Pane::file_names_for_prompt(
1476                        &mut dirty_items.iter().map(|(_, handle)| handle),
1477                        dirty_items.len(),
1478                        cx,
1479                    );
1480                    cx.prompt(
1481                        PromptLevel::Warning,
1482                        &prompt,
1483                        Some(&detail),
1484                        &["Save all", "Discard all", "Cancel"],
1485                    )
1486                })?;
1487                match answer.await.log_err() {
1488                    Some(0) => save_intent = SaveIntent::SaveAll,
1489                    Some(1) => save_intent = SaveIntent::Skip,
1490                    _ => {}
1491                }
1492            }
1493            for (pane, item) in dirty_items {
1494                let (singleton, project_entry_ids) =
1495                    cx.update(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
1496                if singleton || !project_entry_ids.is_empty() {
1497                    if let Some(ix) =
1498                        pane.update(&mut cx, |pane, _| pane.index_for_item(item.as_ref()))?
1499                    {
1500                        if !Pane::save_item(
1501                            project.clone(),
1502                            &pane,
1503                            ix,
1504                            &*item,
1505                            save_intent,
1506                            &mut cx,
1507                        )
1508                        .await?
1509                        {
1510                            return Ok(false);
1511                        }
1512                    }
1513                }
1514            }
1515            Ok(true)
1516        })
1517    }
1518
1519    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
1520        self.client()
1521            .telemetry()
1522            .report_app_event("open project".to_string());
1523        let paths = cx.prompt_for_paths(PathPromptOptions {
1524            files: true,
1525            directories: true,
1526            multiple: true,
1527        });
1528
1529        cx.spawn(|this, mut cx| async move {
1530            let Some(paths) = paths.await.log_err().flatten() else {
1531                return;
1532            };
1533
1534            if let Some(task) = this
1535                .update(&mut cx, |this, cx| {
1536                    this.open_workspace_for_paths(false, paths, cx)
1537                })
1538                .log_err()
1539            {
1540                task.await.log_err();
1541            }
1542        })
1543        .detach()
1544    }
1545
1546    pub fn open_workspace_for_paths(
1547        &mut self,
1548        replace_current_window: bool,
1549        paths: Vec<PathBuf>,
1550        cx: &mut ViewContext<Self>,
1551    ) -> Task<Result<()>> {
1552        let window = cx.window_handle().downcast::<Self>();
1553        let is_remote = self.project.read(cx).is_remote();
1554        let has_worktree = self.project.read(cx).worktrees().next().is_some();
1555        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1556
1557        let window_to_replace = if replace_current_window {
1558            window
1559        } else if is_remote || has_worktree || has_dirty_items {
1560            None
1561        } else {
1562            window
1563        };
1564        let app_state = self.app_state.clone();
1565
1566        cx.spawn(|_, mut cx| async move {
1567            cx.update(|cx| {
1568                open_paths(
1569                    &paths,
1570                    app_state,
1571                    OpenOptions {
1572                        replace_window: window_to_replace,
1573                        ..Default::default()
1574                    },
1575                    cx,
1576                )
1577            })?
1578            .await?;
1579            Ok(())
1580        })
1581    }
1582
1583    #[allow(clippy::type_complexity)]
1584    pub fn open_paths(
1585        &mut self,
1586        mut abs_paths: Vec<PathBuf>,
1587        visible: OpenVisible,
1588        pane: Option<WeakView<Pane>>,
1589        cx: &mut ViewContext<Self>,
1590    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1591        log::info!("open paths {abs_paths:?}");
1592
1593        let fs = self.app_state.fs.clone();
1594
1595        // Sort the paths to ensure we add worktrees for parents before their children.
1596        abs_paths.sort_unstable();
1597        cx.spawn(move |this, mut cx| async move {
1598            let mut tasks = Vec::with_capacity(abs_paths.len());
1599
1600            for abs_path in &abs_paths {
1601                let visible = match visible {
1602                    OpenVisible::All => Some(true),
1603                    OpenVisible::None => Some(false),
1604                    OpenVisible::OnlyFiles => match fs.metadata(abs_path).await.log_err() {
1605                        Some(Some(metadata)) => Some(!metadata.is_dir),
1606                        Some(None) => Some(true),
1607                        None => None,
1608                    },
1609                    OpenVisible::OnlyDirectories => match fs.metadata(abs_path).await.log_err() {
1610                        Some(Some(metadata)) => Some(metadata.is_dir),
1611                        Some(None) => Some(false),
1612                        None => None,
1613                    },
1614                };
1615                let project_path = match visible {
1616                    Some(visible) => match this
1617                        .update(&mut cx, |this, cx| {
1618                            Workspace::project_path_for_path(
1619                                this.project.clone(),
1620                                abs_path,
1621                                visible,
1622                                cx,
1623                            )
1624                        })
1625                        .log_err()
1626                    {
1627                        Some(project_path) => project_path.await.log_err(),
1628                        None => None,
1629                    },
1630                    None => None,
1631                };
1632
1633                let this = this.clone();
1634                let abs_path = abs_path.clone();
1635                let fs = fs.clone();
1636                let pane = pane.clone();
1637                let task = cx.spawn(move |mut cx| async move {
1638                    let (worktree, project_path) = project_path?;
1639                    if fs.is_dir(&abs_path).await {
1640                        this.update(&mut cx, |workspace, cx| {
1641                            let worktree = worktree.read(cx);
1642                            let worktree_abs_path = worktree.abs_path();
1643                            let entry_id = if abs_path == worktree_abs_path.as_ref() {
1644                                worktree.root_entry()
1645                            } else {
1646                                abs_path
1647                                    .strip_prefix(worktree_abs_path.as_ref())
1648                                    .ok()
1649                                    .and_then(|relative_path| {
1650                                        worktree.entry_for_path(relative_path)
1651                                    })
1652                            }
1653                            .map(|entry| entry.id);
1654                            if let Some(entry_id) = entry_id {
1655                                workspace.project.update(cx, |_, cx| {
1656                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
1657                                })
1658                            }
1659                        })
1660                        .log_err()?;
1661                        None
1662                    } else {
1663                        Some(
1664                            this.update(&mut cx, |this, cx| {
1665                                this.open_path(project_path, pane, true, cx)
1666                            })
1667                            .log_err()?
1668                            .await,
1669                        )
1670                    }
1671                });
1672                tasks.push(task);
1673            }
1674
1675            futures::future::join_all(tasks).await
1676        })
1677    }
1678
1679    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1680        if self.project.read(cx).is_remote() {
1681            self.show_error(
1682                &anyhow!("Folders cannot yet be added to remote projects"),
1683                cx,
1684            );
1685            return;
1686        }
1687        let paths = cx.prompt_for_paths(PathPromptOptions {
1688            files: false,
1689            directories: true,
1690            multiple: true,
1691        });
1692        cx.spawn(|this, mut cx| async move {
1693            if let Some(paths) = paths.await.log_err().flatten() {
1694                let results = this
1695                    .update(&mut cx, |this, cx| {
1696                        this.open_paths(paths, OpenVisible::All, None, cx)
1697                    })?
1698                    .await;
1699                for result in results.into_iter().flatten() {
1700                    result.log_err();
1701                }
1702            }
1703            anyhow::Ok(())
1704        })
1705        .detach_and_log_err(cx);
1706    }
1707
1708    fn project_path_for_path(
1709        project: Model<Project>,
1710        abs_path: &Path,
1711        visible: bool,
1712        cx: &mut AppContext,
1713    ) -> Task<Result<(Model<Worktree>, ProjectPath)>> {
1714        let entry = project.update(cx, |project, cx| {
1715            project.find_or_create_local_worktree(abs_path, visible, cx)
1716        });
1717        cx.spawn(|mut cx| async move {
1718            let (worktree, path) = entry.await?;
1719            let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
1720            Ok((
1721                worktree,
1722                ProjectPath {
1723                    worktree_id,
1724                    path: path.into(),
1725                },
1726            ))
1727        })
1728    }
1729
1730    pub fn items<'a>(
1731        &'a self,
1732        cx: &'a AppContext,
1733    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1734        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1735    }
1736
1737    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
1738        self.items_of_type(cx).max_by_key(|item| item.item_id())
1739    }
1740
1741    pub fn items_of_type<'a, T: Item>(
1742        &'a self,
1743        cx: &'a AppContext,
1744    ) -> impl 'a + Iterator<Item = View<T>> {
1745        self.panes
1746            .iter()
1747            .flat_map(|pane| pane.read(cx).items_of_type())
1748    }
1749
1750    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1751        self.active_pane().read(cx).active_item()
1752    }
1753
1754    pub fn active_item_as<I: 'static>(&self, cx: &AppContext) -> Option<View<I>> {
1755        let item = self.active_item(cx)?;
1756        item.to_any().downcast::<I>().ok()
1757    }
1758
1759    fn active_project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
1760        self.active_item(cx).and_then(|item| item.project_path(cx))
1761    }
1762
1763    pub fn save_active_item(
1764        &mut self,
1765        save_intent: SaveIntent,
1766        cx: &mut WindowContext,
1767    ) -> Task<Result<()>> {
1768        let project = self.project.clone();
1769        let pane = self.active_pane();
1770        let item_ix = pane.read(cx).active_item_index();
1771        let item = pane.read(cx).active_item();
1772        let pane = pane.downgrade();
1773
1774        cx.spawn(|mut cx| async move {
1775            if let Some(item) = item {
1776                Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
1777                    .await
1778                    .map(|_| ())
1779            } else {
1780                Ok(())
1781            }
1782        })
1783    }
1784
1785    pub fn close_inactive_items_and_panes(
1786        &mut self,
1787        action: &CloseInactiveTabsAndPanes,
1788        cx: &mut ViewContext<Self>,
1789    ) {
1790        if let Some(task) =
1791            self.close_all_internal(true, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1792        {
1793            task.detach_and_log_err(cx)
1794        }
1795    }
1796
1797    pub fn close_all_items_and_panes(
1798        &mut self,
1799        action: &CloseAllItemsAndPanes,
1800        cx: &mut ViewContext<Self>,
1801    ) {
1802        if let Some(task) =
1803            self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1804        {
1805            task.detach_and_log_err(cx)
1806        }
1807    }
1808
1809    fn close_all_internal(
1810        &mut self,
1811        retain_active_pane: bool,
1812        save_intent: SaveIntent,
1813        cx: &mut ViewContext<Self>,
1814    ) -> Option<Task<Result<()>>> {
1815        let current_pane = self.active_pane();
1816
1817        let mut tasks = Vec::new();
1818
1819        if retain_active_pane {
1820            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1821                pane.close_inactive_items(&CloseInactiveItems { save_intent: None }, cx)
1822            }) {
1823                tasks.push(current_pane_close);
1824            };
1825        }
1826
1827        for pane in self.panes() {
1828            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
1829                continue;
1830            }
1831
1832            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1833                pane.close_all_items(
1834                    &CloseAllItems {
1835                        save_intent: Some(save_intent),
1836                    },
1837                    cx,
1838                )
1839            }) {
1840                tasks.push(close_pane_items)
1841            }
1842        }
1843
1844        if tasks.is_empty() {
1845            None
1846        } else {
1847            Some(cx.spawn(|_, _| async move {
1848                for task in tasks {
1849                    task.await?
1850                }
1851                Ok(())
1852            }))
1853        }
1854    }
1855
1856    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1857        let dock = match dock_side {
1858            DockPosition::Left => &self.left_dock,
1859            DockPosition::Bottom => &self.bottom_dock,
1860            DockPosition::Right => &self.right_dock,
1861        };
1862        let mut focus_center = false;
1863        let mut reveal_dock = false;
1864        dock.update(cx, |dock, cx| {
1865            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1866            let was_visible = dock.is_open() && !other_is_zoomed;
1867            dock.set_open(!was_visible, cx);
1868
1869            if let Some(active_panel) = dock.active_panel() {
1870                if was_visible {
1871                    if active_panel.focus_handle(cx).contains_focused(cx) {
1872                        focus_center = true;
1873                    }
1874                } else {
1875                    let focus_handle = &active_panel.focus_handle(cx);
1876                    cx.focus(focus_handle);
1877                    reveal_dock = true;
1878                }
1879            }
1880        });
1881
1882        if reveal_dock {
1883            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1884        }
1885
1886        if focus_center {
1887            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1888        }
1889
1890        cx.notify();
1891        self.serialize_workspace(cx);
1892    }
1893
1894    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1895        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1896
1897        for dock in docks {
1898            dock.update(cx, |dock, cx| {
1899                dock.set_open(false, cx);
1900            });
1901        }
1902
1903        cx.focus_self();
1904        cx.notify();
1905        self.serialize_workspace(cx);
1906    }
1907
1908    /// Transfer focus to the panel of the given type.
1909    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
1910        let panel = self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?;
1911        panel.to_any().downcast().ok()
1912    }
1913
1914    /// Focus the panel of the given type if it isn't already focused. If it is
1915    /// already focused, then transfer focus back to the workspace center.
1916    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1917        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| {
1918            !panel.focus_handle(cx).contains_focused(cx)
1919        });
1920    }
1921
1922    /// Focus or unfocus the given panel type, depending on the given callback.
1923    fn focus_or_unfocus_panel<T: Panel>(
1924        &mut self,
1925        cx: &mut ViewContext<Self>,
1926        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1927    ) -> Option<Arc<dyn PanelHandle>> {
1928        let mut result_panel = None;
1929        let mut serialize = false;
1930        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1931            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1932                let mut focus_center = false;
1933                let panel = dock.update(cx, |dock, cx| {
1934                    dock.activate_panel(panel_index, cx);
1935
1936                    let panel = dock.active_panel().cloned();
1937                    if let Some(panel) = panel.as_ref() {
1938                        if should_focus(&**panel, cx) {
1939                            dock.set_open(true, cx);
1940                            panel.focus_handle(cx).focus(cx);
1941                        } else {
1942                            focus_center = true;
1943                        }
1944                    }
1945                    panel
1946                });
1947
1948                if focus_center {
1949                    self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1950                }
1951
1952                result_panel = panel;
1953                serialize = true;
1954                break;
1955            }
1956        }
1957
1958        if serialize {
1959            self.serialize_workspace(cx);
1960        }
1961
1962        cx.notify();
1963        result_panel
1964    }
1965
1966    /// Open the panel of the given type
1967    pub fn open_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1968        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1969            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1970                dock.update(cx, |dock, cx| {
1971                    dock.activate_panel(panel_index, cx);
1972                    dock.set_open(true, cx);
1973                });
1974            }
1975        }
1976    }
1977
1978    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
1979        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1980            let dock = dock.read(cx);
1981            if let Some(panel) = dock.panel::<T>() {
1982                return Some(panel);
1983            }
1984        }
1985        None
1986    }
1987
1988    fn dismiss_zoomed_items_to_reveal(
1989        &mut self,
1990        dock_to_reveal: Option<DockPosition>,
1991        cx: &mut ViewContext<Self>,
1992    ) {
1993        // If a center pane is zoomed, unzoom it.
1994        for pane in &self.panes {
1995            if pane != &self.active_pane || dock_to_reveal.is_some() {
1996                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1997            }
1998        }
1999
2000        // If another dock is zoomed, hide it.
2001        let mut focus_center = false;
2002        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
2003            dock.update(cx, |dock, cx| {
2004                if Some(dock.position()) != dock_to_reveal {
2005                    if let Some(panel) = dock.active_panel() {
2006                        if panel.is_zoomed(cx) {
2007                            focus_center |= panel.focus_handle(cx).contains_focused(cx);
2008                            dock.set_open(false, cx);
2009                        }
2010                    }
2011                }
2012            });
2013        }
2014
2015        if focus_center {
2016            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
2017        }
2018
2019        if self.zoomed_position != dock_to_reveal {
2020            self.zoomed = None;
2021            self.zoomed_position = None;
2022            cx.emit(Event::ZoomChanged);
2023        }
2024
2025        cx.notify();
2026    }
2027
2028    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
2029        let pane = cx.new_view(|cx| {
2030            Pane::new(
2031                self.weak_handle(),
2032                self.project.clone(),
2033                self.pane_history_timestamp.clone(),
2034                None,
2035                NewFile.boxed_clone(),
2036                cx,
2037            )
2038        });
2039        cx.subscribe(&pane, Self::handle_pane_event).detach();
2040        self.panes.push(pane.clone());
2041        cx.focus_view(&pane);
2042        cx.emit(Event::PaneAdded(pane.clone()));
2043        pane
2044    }
2045
2046    pub fn add_item_to_center(
2047        &mut self,
2048        item: Box<dyn ItemHandle>,
2049        cx: &mut ViewContext<Self>,
2050    ) -> bool {
2051        if let Some(center_pane) = self.last_active_center_pane.clone() {
2052            if let Some(center_pane) = center_pane.upgrade() {
2053                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
2054                true
2055            } else {
2056                false
2057            }
2058        } else {
2059            false
2060        }
2061    }
2062
2063    pub fn add_item_to_active_pane(
2064        &mut self,
2065        item: Box<dyn ItemHandle>,
2066        destination_index: Option<usize>,
2067        cx: &mut WindowContext,
2068    ) {
2069        self.add_item(self.active_pane.clone(), item, destination_index, cx)
2070    }
2071
2072    pub fn add_item(
2073        &mut self,
2074        pane: View<Pane>,
2075        item: Box<dyn ItemHandle>,
2076        destination_index: Option<usize>,
2077        cx: &mut WindowContext,
2078    ) {
2079        if let Some(text) = item.telemetry_event_text(cx) {
2080            self.client()
2081                .telemetry()
2082                .report_app_event(format!("{}: open", text));
2083        }
2084
2085        pane.update(cx, |pane, cx| {
2086            pane.add_item(item, true, true, destination_index, cx)
2087        });
2088    }
2089
2090    pub fn split_item(
2091        &mut self,
2092        split_direction: SplitDirection,
2093        item: Box<dyn ItemHandle>,
2094        cx: &mut ViewContext<Self>,
2095    ) {
2096        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
2097        self.add_item(new_pane, item, None, cx);
2098    }
2099
2100    pub fn open_abs_path(
2101        &mut self,
2102        abs_path: PathBuf,
2103        visible: bool,
2104        cx: &mut ViewContext<Self>,
2105    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2106        cx.spawn(|workspace, mut cx| async move {
2107            let open_paths_task_result = workspace
2108                .update(&mut cx, |workspace, cx| {
2109                    workspace.open_paths(
2110                        vec![abs_path.clone()],
2111                        if visible {
2112                            OpenVisible::All
2113                        } else {
2114                            OpenVisible::None
2115                        },
2116                        None,
2117                        cx,
2118                    )
2119                })
2120                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
2121                .await;
2122            anyhow::ensure!(
2123                open_paths_task_result.len() == 1,
2124                "open abs path {abs_path:?} task returned incorrect number of results"
2125            );
2126            match open_paths_task_result
2127                .into_iter()
2128                .next()
2129                .expect("ensured single task result")
2130            {
2131                Some(open_result) => {
2132                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
2133                }
2134                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
2135            }
2136        })
2137    }
2138
2139    pub fn split_abs_path(
2140        &mut self,
2141        abs_path: PathBuf,
2142        visible: bool,
2143        cx: &mut ViewContext<Self>,
2144    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2145        let project_path_task =
2146            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
2147        cx.spawn(|this, mut cx| async move {
2148            let (_, path) = project_path_task.await?;
2149            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
2150                .await
2151        })
2152    }
2153
2154    pub fn open_path(
2155        &mut self,
2156        path: impl Into<ProjectPath>,
2157        pane: Option<WeakView<Pane>>,
2158        focus_item: bool,
2159        cx: &mut WindowContext,
2160    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2161        self.open_path_preview(path, pane, focus_item, false, cx)
2162    }
2163
2164    pub fn open_path_preview(
2165        &mut self,
2166        path: impl Into<ProjectPath>,
2167        pane: Option<WeakView<Pane>>,
2168        focus_item: bool,
2169        allow_preview: bool,
2170        cx: &mut WindowContext,
2171    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2172        let pane = pane.unwrap_or_else(|| {
2173            self.last_active_center_pane.clone().unwrap_or_else(|| {
2174                self.panes
2175                    .first()
2176                    .expect("There must be an active pane")
2177                    .downgrade()
2178            })
2179        });
2180
2181        let task = self.load_path(path.into(), cx);
2182        cx.spawn(move |mut cx| async move {
2183            let (project_entry_id, build_item) = task.await?;
2184            pane.update(&mut cx, |pane, cx| {
2185                pane.open_item(project_entry_id, focus_item, allow_preview, cx, build_item)
2186            })
2187        })
2188    }
2189
2190    pub fn split_path(
2191        &mut self,
2192        path: impl Into<ProjectPath>,
2193        cx: &mut ViewContext<Self>,
2194    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2195        self.split_path_preview(path, false, cx)
2196    }
2197
2198    pub fn split_path_preview(
2199        &mut self,
2200        path: impl Into<ProjectPath>,
2201        allow_preview: bool,
2202        cx: &mut ViewContext<Self>,
2203    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2204        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
2205            self.panes
2206                .first()
2207                .expect("There must be an active pane")
2208                .downgrade()
2209        });
2210
2211        if let Member::Pane(center_pane) = &self.center.root {
2212            if center_pane.read(cx).items_len() == 0 {
2213                return self.open_path(path, Some(pane), true, cx);
2214            }
2215        }
2216
2217        let task = self.load_path(path.into(), cx);
2218        cx.spawn(|this, mut cx| async move {
2219            let (project_entry_id, build_item) = task.await?;
2220            this.update(&mut cx, move |this, cx| -> Option<_> {
2221                let pane = pane.upgrade()?;
2222                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
2223                new_pane.update(cx, |new_pane, cx| {
2224                    Some(new_pane.open_item(project_entry_id, true, allow_preview, cx, build_item))
2225                })
2226            })
2227            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
2228        })
2229    }
2230
2231    fn load_path(
2232        &mut self,
2233        path: ProjectPath,
2234        cx: &mut WindowContext,
2235    ) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
2236        let project = self.project().clone();
2237        let project_item_builders = cx.default_global::<ProjectItemOpeners>().clone();
2238        let Some(open_project_item) = project_item_builders
2239            .iter()
2240            .rev()
2241            .find_map(|open_project_item| open_project_item(&project, &path, cx))
2242        else {
2243            return Task::ready(Err(anyhow!("cannot open file {:?}", path.path)));
2244        };
2245        open_project_item
2246    }
2247
2248    pub fn open_project_item<T>(
2249        &mut self,
2250        pane: View<Pane>,
2251        project_item: Model<T::Item>,
2252        cx: &mut ViewContext<Self>,
2253    ) -> View<T>
2254    where
2255        T: ProjectItem,
2256    {
2257        use project::Item as _;
2258
2259        let entry_id = project_item.read(cx).entry_id(cx);
2260        if let Some(item) = entry_id
2261            .and_then(|entry_id| pane.read(cx).item_for_entry(entry_id, cx))
2262            .and_then(|item| item.downcast())
2263        {
2264            self.activate_item(&item, cx);
2265            return item;
2266        }
2267
2268        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2269
2270        let item_id = item.item_id();
2271        let mut destination_index = None;
2272        pane.update(cx, |pane, cx| {
2273            if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
2274                if let Some(preview_item_id) = pane.preview_item_id() {
2275                    if preview_item_id != item_id {
2276                        destination_index = pane.close_current_preview_item(cx);
2277                    }
2278                }
2279            }
2280            pane.set_preview_item_id(Some(item.item_id()), cx)
2281        });
2282
2283        self.add_item(pane, Box::new(item.clone()), destination_index, cx);
2284        item
2285    }
2286
2287    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2288        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2289            self.active_pane.update(cx, |pane, cx| {
2290                pane.add_item(Box::new(shared_screen), false, true, None, cx)
2291            });
2292        }
2293    }
2294
2295    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut WindowContext) -> bool {
2296        let result = self.panes.iter().find_map(|pane| {
2297            pane.read(cx)
2298                .index_for_item(item)
2299                .map(|ix| (pane.clone(), ix))
2300        });
2301        if let Some((pane, ix)) = result {
2302            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2303            true
2304        } else {
2305            false
2306        }
2307    }
2308
2309    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2310        let panes = self.center.panes();
2311        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2312            cx.focus_view(&pane);
2313        } else {
2314            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2315        }
2316    }
2317
2318    pub fn activate_next_pane(&mut self, cx: &mut WindowContext) {
2319        let panes = self.center.panes();
2320        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2321            let next_ix = (ix + 1) % panes.len();
2322            let next_pane = panes[next_ix].clone();
2323            cx.focus_view(&next_pane);
2324        }
2325    }
2326
2327    pub fn activate_previous_pane(&mut self, cx: &mut WindowContext) {
2328        let panes = self.center.panes();
2329        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2330            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2331            let prev_pane = panes[prev_ix].clone();
2332            cx.focus_view(&prev_pane);
2333        }
2334    }
2335
2336    pub fn activate_pane_in_direction(
2337        &mut self,
2338        direction: SplitDirection,
2339        cx: &mut WindowContext,
2340    ) {
2341        use ActivateInDirectionTarget as Target;
2342        enum Origin {
2343            LeftDock,
2344            RightDock,
2345            BottomDock,
2346            Center,
2347        }
2348
2349        let origin: Origin = [
2350            (&self.left_dock, Origin::LeftDock),
2351            (&self.right_dock, Origin::RightDock),
2352            (&self.bottom_dock, Origin::BottomDock),
2353        ]
2354        .into_iter()
2355        .find_map(|(dock, origin)| {
2356            if dock.focus_handle(cx).contains_focused(cx) && dock.read(cx).is_open() {
2357                Some(origin)
2358            } else {
2359                None
2360            }
2361        })
2362        .unwrap_or(Origin::Center);
2363
2364        let get_last_active_pane = || {
2365            self.last_active_center_pane.as_ref().and_then(|p| {
2366                let p = p.upgrade()?;
2367                (p.read(cx).items_len() != 0).then_some(p)
2368            })
2369        };
2370
2371        let try_dock =
2372            |dock: &View<Dock>| dock.read(cx).is_open().then(|| Target::Dock(dock.clone()));
2373
2374        let target = match (origin, direction) {
2375            // We're in the center, so we first try to go to a different pane,
2376            // otherwise try to go to a dock.
2377            (Origin::Center, direction) => {
2378                if let Some(pane) = self.find_pane_in_direction(direction, cx) {
2379                    Some(Target::Pane(pane))
2380                } else {
2381                    match direction {
2382                        SplitDirection::Up => None,
2383                        SplitDirection::Down => try_dock(&self.bottom_dock),
2384                        SplitDirection::Left => try_dock(&self.left_dock),
2385                        SplitDirection::Right => try_dock(&self.right_dock),
2386                    }
2387                }
2388            }
2389
2390            (Origin::LeftDock, SplitDirection::Right) => {
2391                if let Some(last_active_pane) = get_last_active_pane() {
2392                    Some(Target::Pane(last_active_pane))
2393                } else {
2394                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock))
2395                }
2396            }
2397
2398            (Origin::LeftDock, SplitDirection::Down)
2399            | (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock),
2400
2401            (Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane),
2402            (Origin::BottomDock, SplitDirection::Left) => try_dock(&self.left_dock),
2403            (Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock),
2404
2405            (Origin::RightDock, SplitDirection::Left) => {
2406                if let Some(last_active_pane) = get_last_active_pane() {
2407                    Some(Target::Pane(last_active_pane))
2408                } else {
2409                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock))
2410                }
2411            }
2412
2413            _ => None,
2414        };
2415
2416        match target {
2417            Some(ActivateInDirectionTarget::Pane(pane)) => cx.focus_view(&pane),
2418            Some(ActivateInDirectionTarget::Dock(dock)) => {
2419                if let Some(panel) = dock.read(cx).active_panel() {
2420                    panel.focus_handle(cx).focus(cx);
2421                } else {
2422                    log::error!("Could not find a focus target when in switching focus in {direction} direction for a {:?} dock", dock.read(cx).position());
2423                }
2424            }
2425            None => {}
2426        }
2427    }
2428
2429    pub fn find_pane_in_direction(
2430        &mut self,
2431        direction: SplitDirection,
2432        cx: &WindowContext,
2433    ) -> Option<View<Pane>> {
2434        let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
2435            return None;
2436        };
2437        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2438        let center = match cursor {
2439            Some(cursor) if bounding_box.contains(&cursor) => cursor,
2440            _ => bounding_box.center(),
2441        };
2442
2443        let distance_to_next = pane_group::HANDLE_HITBOX_SIZE;
2444
2445        let target = match direction {
2446            SplitDirection::Left => {
2447                Point::new(bounding_box.left() - distance_to_next.into(), center.y)
2448            }
2449            SplitDirection::Right => {
2450                Point::new(bounding_box.right() + distance_to_next.into(), center.y)
2451            }
2452            SplitDirection::Up => {
2453                Point::new(center.x, bounding_box.top() - distance_to_next.into())
2454            }
2455            SplitDirection::Down => {
2456                Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
2457            }
2458        };
2459        self.center.pane_at_pixel_position(target).cloned()
2460    }
2461
2462    pub fn swap_pane_in_direction(
2463        &mut self,
2464        direction: SplitDirection,
2465        cx: &mut ViewContext<Self>,
2466    ) {
2467        if let Some(to) = self
2468            .find_pane_in_direction(direction, cx)
2469            .map(|pane| pane.clone())
2470        {
2471            self.center.swap(&self.active_pane.clone(), &to);
2472            cx.notify();
2473        }
2474    }
2475
2476    fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2477        // This is explicitly hoisted out of the following check for pane identity as
2478        // terminal panel panes are not registered as a center panes.
2479        self.status_bar.update(cx, |status_bar, cx| {
2480            status_bar.set_active_pane(&pane, cx);
2481        });
2482        if self.active_pane != pane {
2483            self.active_pane = pane.clone();
2484            self.active_item_path_changed(cx);
2485            self.last_active_center_pane = Some(pane.downgrade());
2486        }
2487
2488        self.dismiss_zoomed_items_to_reveal(None, cx);
2489        if pane.read(cx).is_zoomed() {
2490            self.zoomed = Some(pane.downgrade().into());
2491        } else {
2492            self.zoomed = None;
2493        }
2494        self.zoomed_position = None;
2495        cx.emit(Event::ZoomChanged);
2496        self.update_active_view_for_followers(cx);
2497
2498        cx.notify();
2499    }
2500
2501    fn handle_pane_event(
2502        &mut self,
2503        pane: View<Pane>,
2504        event: &pane::Event,
2505        cx: &mut ViewContext<Self>,
2506    ) {
2507        match event {
2508            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2509            pane::Event::Split(direction) => {
2510                self.split_and_clone(pane, *direction, cx);
2511            }
2512            pane::Event::Remove => self.remove_pane(pane, cx),
2513            pane::Event::ActivateItem { local } => {
2514                if *local {
2515                    self.unfollow(&pane, cx);
2516                }
2517                if &pane == self.active_pane() {
2518                    self.active_item_path_changed(cx);
2519                    self.update_active_view_for_followers(cx);
2520                }
2521            }
2522            pane::Event::ChangeItemTitle => {
2523                if pane == self.active_pane {
2524                    self.active_item_path_changed(cx);
2525                }
2526                self.update_window_edited(cx);
2527            }
2528            pane::Event::RemoveItem { item_id } => {
2529                cx.emit(Event::ActiveItemChanged);
2530                self.update_window_edited(cx);
2531                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2532                    if entry.get().entity_id() == pane.entity_id() {
2533                        entry.remove();
2534                    }
2535                }
2536            }
2537            pane::Event::Focus => {
2538                self.handle_pane_focused(pane.clone(), cx);
2539            }
2540            pane::Event::ZoomIn => {
2541                if pane == self.active_pane {
2542                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2543                    if pane.read(cx).has_focus(cx) {
2544                        self.zoomed = Some(pane.downgrade().into());
2545                        self.zoomed_position = None;
2546                        cx.emit(Event::ZoomChanged);
2547                    }
2548                    cx.notify();
2549                }
2550            }
2551            pane::Event::ZoomOut => {
2552                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2553                if self.zoomed_position.is_none() {
2554                    self.zoomed = None;
2555                    cx.emit(Event::ZoomChanged);
2556                }
2557                cx.notify();
2558            }
2559        }
2560
2561        self.serialize_workspace(cx);
2562    }
2563
2564    pub fn split_pane(
2565        &mut self,
2566        pane_to_split: View<Pane>,
2567        split_direction: SplitDirection,
2568        cx: &mut ViewContext<Self>,
2569    ) -> View<Pane> {
2570        let new_pane = self.add_pane(cx);
2571        self.center
2572            .split(&pane_to_split, &new_pane, split_direction)
2573            .unwrap();
2574        cx.notify();
2575        new_pane
2576    }
2577
2578    pub fn split_and_clone(
2579        &mut self,
2580        pane: View<Pane>,
2581        direction: SplitDirection,
2582        cx: &mut ViewContext<Self>,
2583    ) -> Option<View<Pane>> {
2584        let item = pane.read(cx).active_item()?;
2585        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2586            let new_pane = self.add_pane(cx);
2587            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2588            self.center.split(&pane, &new_pane, direction).unwrap();
2589            Some(new_pane)
2590        } else {
2591            None
2592        };
2593        cx.notify();
2594        maybe_pane_handle
2595    }
2596
2597    pub fn split_pane_with_item(
2598        &mut self,
2599        pane_to_split: WeakView<Pane>,
2600        split_direction: SplitDirection,
2601        from: WeakView<Pane>,
2602        item_id_to_move: EntityId,
2603        cx: &mut ViewContext<Self>,
2604    ) {
2605        let Some(pane_to_split) = pane_to_split.upgrade() else {
2606            return;
2607        };
2608        let Some(from) = from.upgrade() else {
2609            return;
2610        };
2611
2612        let new_pane = self.add_pane(cx);
2613        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2614        self.center
2615            .split(&pane_to_split, &new_pane, split_direction)
2616            .unwrap();
2617        cx.notify();
2618    }
2619
2620    pub fn split_pane_with_project_entry(
2621        &mut self,
2622        pane_to_split: WeakView<Pane>,
2623        split_direction: SplitDirection,
2624        project_entry: ProjectEntryId,
2625        cx: &mut ViewContext<Self>,
2626    ) -> Option<Task<Result<()>>> {
2627        let pane_to_split = pane_to_split.upgrade()?;
2628        let new_pane = self.add_pane(cx);
2629        self.center
2630            .split(&pane_to_split, &new_pane, split_direction)
2631            .unwrap();
2632
2633        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2634        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2635        Some(cx.foreground_executor().spawn(async move {
2636            task.await?;
2637            Ok(())
2638        }))
2639    }
2640
2641    pub fn move_item(
2642        &mut self,
2643        source: View<Pane>,
2644        destination: View<Pane>,
2645        item_id_to_move: EntityId,
2646        destination_index: usize,
2647        cx: &mut ViewContext<Self>,
2648    ) {
2649        let Some((item_ix, item_handle)) = source
2650            .read(cx)
2651            .items()
2652            .enumerate()
2653            .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
2654        else {
2655            // Tab was closed during drag
2656            return;
2657        };
2658
2659        let item_handle = item_handle.clone();
2660
2661        if source != destination {
2662            // Close item from previous pane
2663            source.update(cx, |source, cx| {
2664                source.remove_item(item_ix, false, true, cx);
2665            });
2666        }
2667
2668        // This automatically removes duplicate items in the pane
2669        destination.update(cx, |destination, cx| {
2670            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2671            destination.focus(cx)
2672        });
2673    }
2674
2675    fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2676        if self.center.remove(&pane).unwrap() {
2677            self.force_remove_pane(&pane, cx);
2678            self.unfollow(&pane, cx);
2679            self.last_leaders_by_pane.remove(&pane.downgrade());
2680            for removed_item in pane.read(cx).items() {
2681                self.panes_by_item.remove(&removed_item.item_id());
2682            }
2683
2684            cx.notify();
2685        } else {
2686            self.active_item_path_changed(cx);
2687        }
2688    }
2689
2690    pub fn panes(&self) -> &[View<Pane>] {
2691        &self.panes
2692    }
2693
2694    pub fn active_pane(&self) -> &View<Pane> {
2695        &self.active_pane
2696    }
2697
2698    pub fn adjacent_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
2699        self.find_pane_in_direction(SplitDirection::Right, cx)
2700            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
2701            .unwrap_or_else(|| self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx))
2702            .clone()
2703    }
2704
2705    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
2706        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
2707        weak_pane.upgrade()
2708    }
2709
2710    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2711        self.follower_states.retain(|_, state| {
2712            if state.leader_id == peer_id {
2713                for item in state.items_by_leader_view_id.values() {
2714                    item.set_leader_peer_id(None, cx);
2715                }
2716                false
2717            } else {
2718                true
2719            }
2720        });
2721        cx.notify();
2722    }
2723
2724    pub fn start_following(
2725        &mut self,
2726        leader_id: PeerId,
2727        cx: &mut ViewContext<Self>,
2728    ) -> Option<Task<Result<()>>> {
2729        let pane = self.active_pane().clone();
2730
2731        self.last_leaders_by_pane
2732            .insert(pane.downgrade(), leader_id);
2733        self.unfollow(&pane, cx);
2734        self.follower_states.insert(
2735            pane.clone(),
2736            FollowerState {
2737                leader_id,
2738                active_view_id: None,
2739                items_by_leader_view_id: Default::default(),
2740            },
2741        );
2742        cx.notify();
2743
2744        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2745        let project_id = self.project.read(cx).remote_id();
2746        let request = self.app_state.client.request(proto::Follow {
2747            room_id,
2748            project_id,
2749            leader_id: Some(leader_id),
2750        });
2751
2752        Some(cx.spawn(|this, mut cx| async move {
2753            let response = request.await?;
2754            this.update(&mut cx, |this, _| {
2755                let state = this
2756                    .follower_states
2757                    .get_mut(&pane)
2758                    .ok_or_else(|| anyhow!("following interrupted"))?;
2759                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2760                    Some(ViewId::from_proto(active_view_id)?)
2761                } else {
2762                    None
2763                };
2764                Ok::<_, anyhow::Error>(())
2765            })??;
2766            if let Some(view) = response.active_view {
2767                Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, &mut cx)
2768                    .await?;
2769            }
2770            Self::add_views_from_leader(
2771                this.clone(),
2772                leader_id,
2773                vec![pane],
2774                response.views,
2775                &mut cx,
2776            )
2777            .await?;
2778            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2779            Ok(())
2780        }))
2781    }
2782
2783    pub fn follow_next_collaborator(
2784        &mut self,
2785        _: &FollowNextCollaborator,
2786        cx: &mut ViewContext<Self>,
2787    ) {
2788        let collaborators = self.project.read(cx).collaborators();
2789        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2790            let mut collaborators = collaborators.keys().copied();
2791            for peer_id in collaborators.by_ref() {
2792                if peer_id == leader_id {
2793                    break;
2794                }
2795            }
2796            collaborators.next()
2797        } else if let Some(last_leader_id) =
2798            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2799        {
2800            if collaborators.contains_key(last_leader_id) {
2801                Some(*last_leader_id)
2802            } else {
2803                None
2804            }
2805        } else {
2806            None
2807        };
2808
2809        let pane = self.active_pane.clone();
2810        let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
2811        else {
2812            return;
2813        };
2814        if Some(leader_id) == self.unfollow(&pane, cx) {
2815            return;
2816        }
2817        if let Some(task) = self.start_following(leader_id, cx) {
2818            task.detach_and_log_err(cx)
2819        }
2820    }
2821
2822    pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) {
2823        let Some(room) = ActiveCall::global(cx).read(cx).room() else {
2824            return;
2825        };
2826        let room = room.read(cx);
2827        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2828            return;
2829        };
2830
2831        let project = self.project.read(cx);
2832
2833        let other_project_id = match remote_participant.location {
2834            call::ParticipantLocation::External => None,
2835            call::ParticipantLocation::UnsharedProject => None,
2836            call::ParticipantLocation::SharedProject { project_id } => {
2837                if Some(project_id) == project.remote_id() {
2838                    None
2839                } else {
2840                    Some(project_id)
2841                }
2842            }
2843        };
2844
2845        // if they are active in another project, follow there.
2846        if let Some(project_id) = other_project_id {
2847            let app_state = self.app_state.clone();
2848            crate::join_in_room_project(project_id, remote_participant.user.id, app_state, cx)
2849                .detach_and_log_err(cx);
2850        }
2851
2852        // if you're already following, find the right pane and focus it.
2853        for (pane, state) in &self.follower_states {
2854            if leader_id == state.leader_id {
2855                cx.focus_view(pane);
2856                return;
2857            }
2858        }
2859
2860        // Otherwise, follow.
2861        if let Some(task) = self.start_following(leader_id, cx) {
2862            task.detach_and_log_err(cx)
2863        }
2864    }
2865
2866    pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
2867        let state = self.follower_states.remove(pane)?;
2868        let leader_id = state.leader_id;
2869        for (_, item) in state.items_by_leader_view_id {
2870            item.set_leader_peer_id(None, cx);
2871        }
2872
2873        if self
2874            .follower_states
2875            .values()
2876            .all(|state| state.leader_id != leader_id)
2877        {
2878            let project_id = self.project.read(cx).remote_id();
2879            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2880            self.app_state
2881                .client
2882                .send(proto::Unfollow {
2883                    room_id,
2884                    project_id,
2885                    leader_id: Some(leader_id),
2886                })
2887                .log_err();
2888        }
2889
2890        cx.notify();
2891        Some(leader_id)
2892    }
2893
2894    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2895        self.follower_states
2896            .values()
2897            .any(|state| state.leader_id == peer_id)
2898    }
2899
2900    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2901        cx.emit(Event::ActiveItemChanged);
2902        let active_entry = self.active_project_path(cx);
2903        self.project
2904            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2905
2906        self.update_window_title(cx);
2907    }
2908
2909    fn update_window_title(&mut self, cx: &mut WindowContext) {
2910        let project = self.project().read(cx);
2911        let mut title = String::new();
2912
2913        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2914            let filename = path
2915                .path
2916                .file_name()
2917                .map(|s| s.to_string_lossy())
2918                .or_else(|| {
2919                    Some(Cow::Borrowed(
2920                        project
2921                            .worktree_for_id(path.worktree_id, cx)?
2922                            .read(cx)
2923                            .root_name(),
2924                    ))
2925                });
2926
2927            if let Some(filename) = filename {
2928                title.push_str(filename.as_ref());
2929                title.push_str("");
2930            }
2931        }
2932
2933        for (i, name) in project.worktree_root_names(cx).enumerate() {
2934            if i > 0 {
2935                title.push_str(", ");
2936            }
2937            title.push_str(name);
2938        }
2939
2940        if title.is_empty() {
2941            title = "empty project".to_string();
2942        }
2943
2944        if project.is_remote() {
2945            title.push_str("");
2946        } else if project.is_shared() {
2947            title.push_str("");
2948        }
2949
2950        cx.set_window_title(&title);
2951    }
2952
2953    fn update_window_edited(&mut self, cx: &mut WindowContext) {
2954        let is_edited = !self.project.read(cx).is_disconnected()
2955            && self
2956                .items(cx)
2957                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2958        if is_edited != self.window_edited {
2959            self.window_edited = is_edited;
2960            cx.set_window_edited(self.window_edited)
2961        }
2962    }
2963
2964    fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
2965        if self.notifications.is_empty() {
2966            None
2967        } else {
2968            Some(
2969                div()
2970                    .absolute()
2971                    .right_3()
2972                    .bottom_3()
2973                    .w_112()
2974                    .h_full()
2975                    .flex()
2976                    .flex_col()
2977                    .justify_end()
2978                    .gap_2()
2979                    .children(
2980                        self.notifications
2981                            .iter()
2982                            .map(|(_, notification)| notification.to_any()),
2983                    ),
2984            )
2985        }
2986    }
2987
2988    // RPC handlers
2989
2990    fn active_view_for_follower(
2991        &self,
2992        follower_project_id: Option<u64>,
2993        cx: &mut ViewContext<Self>,
2994    ) -> Option<proto::View> {
2995        let item = self.active_item(cx)?;
2996        let leader_id = self
2997            .pane_for(&*item)
2998            .and_then(|pane| self.leader_for_pane(&pane));
2999
3000        let item_handle = item.to_followable_item_handle(cx)?;
3001        let id = item_handle.remote_id(&self.app_state.client, cx)?;
3002        let variant = item_handle.to_state_proto(cx)?;
3003
3004        if item_handle.is_project_item(cx)
3005            && (follower_project_id.is_none()
3006                || follower_project_id != self.project.read(cx).remote_id())
3007        {
3008            return None;
3009        }
3010
3011        Some(proto::View {
3012            id: Some(id.to_proto()),
3013            leader_id,
3014            variant: Some(variant),
3015        })
3016    }
3017
3018    fn handle_follow(
3019        &mut self,
3020        follower_project_id: Option<u64>,
3021        cx: &mut ViewContext<Self>,
3022    ) -> proto::FollowResponse {
3023        let client = &self.app_state.client;
3024        let project_id = self.project.read(cx).remote_id();
3025
3026        let active_view = self.active_view_for_follower(follower_project_id, cx);
3027        let active_view_id = active_view.as_ref().and_then(|view| view.id.clone());
3028
3029        cx.notify();
3030
3031        proto::FollowResponse {
3032            active_view,
3033            // TODO: once v0.124.0 is retired we can stop sending these
3034            active_view_id,
3035            views: self
3036                .panes()
3037                .iter()
3038                .flat_map(|pane| {
3039                    let leader_id = self.leader_for_pane(pane);
3040                    pane.read(cx).items().filter_map({
3041                        let cx = &cx;
3042                        move |item| {
3043                            let item = item.to_followable_item_handle(cx)?;
3044
3045                            // If the item belongs to a particular project, then it should
3046                            // only be included if this project is shared, and the follower
3047                            // is in the project.
3048                            //
3049                            // Some items, like channel notes, do not belong to a particular
3050                            // project, so they should be included regardless of whether the
3051                            // current project is shared, or what project the follower is in.
3052                            if item.is_project_item(cx)
3053                                && (project_id.is_none() || project_id != follower_project_id)
3054                            {
3055                                return None;
3056                            }
3057
3058                            let id = item.remote_id(client, cx)?.to_proto();
3059                            let variant = item.to_state_proto(cx)?;
3060                            Some(proto::View {
3061                                id: Some(id),
3062                                leader_id,
3063                                variant: Some(variant),
3064                            })
3065                        }
3066                    })
3067                })
3068                .collect(),
3069        }
3070    }
3071
3072    fn handle_update_followers(
3073        &mut self,
3074        leader_id: PeerId,
3075        message: proto::UpdateFollowers,
3076        _cx: &mut ViewContext<Self>,
3077    ) {
3078        self.leader_updates_tx
3079            .unbounded_send((leader_id, message))
3080            .ok();
3081    }
3082
3083    async fn process_leader_update(
3084        this: &WeakView<Self>,
3085        leader_id: PeerId,
3086        update: proto::UpdateFollowers,
3087        cx: &mut AsyncWindowContext,
3088    ) -> Result<()> {
3089        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
3090            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
3091                let panes_missing_view = this.update(cx, |this, _| {
3092                    let mut panes = vec![];
3093                    for (pane, state) in &mut this.follower_states {
3094                        if state.leader_id != leader_id {
3095                            continue;
3096                        }
3097
3098                        state.active_view_id =
3099                            if let Some(active_view_id) = update_active_view.id.clone() {
3100                                Some(ViewId::from_proto(active_view_id)?)
3101                            } else {
3102                                None
3103                            };
3104
3105                        if state.active_view_id.is_some_and(|view_id| {
3106                            !state.items_by_leader_view_id.contains_key(&view_id)
3107                        }) {
3108                            panes.push(pane.clone())
3109                        }
3110                    }
3111                    anyhow::Ok(panes)
3112                })??;
3113
3114                if let Some(view) = update_active_view.view {
3115                    for pane in panes_missing_view {
3116                        Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, cx)
3117                            .await?
3118                    }
3119                }
3120            }
3121            proto::update_followers::Variant::UpdateView(update_view) => {
3122                let variant = update_view
3123                    .variant
3124                    .ok_or_else(|| anyhow!("missing update view variant"))?;
3125                let id = update_view
3126                    .id
3127                    .ok_or_else(|| anyhow!("missing update view id"))?;
3128                let mut tasks = Vec::new();
3129                this.update(cx, |this, cx| {
3130                    let project = this.project.clone();
3131                    for (_, state) in &mut this.follower_states {
3132                        if state.leader_id == leader_id {
3133                            let view_id = ViewId::from_proto(id.clone())?;
3134                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
3135                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
3136                            }
3137                        }
3138                    }
3139                    anyhow::Ok(())
3140                })??;
3141                try_join_all(tasks).await.log_err();
3142            }
3143            proto::update_followers::Variant::CreateView(view) => {
3144                let panes = this.update(cx, |this, _| {
3145                    this.follower_states
3146                        .iter()
3147                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
3148                        .cloned()
3149                        .collect()
3150                })?;
3151                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
3152            }
3153        }
3154        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
3155        Ok(())
3156    }
3157
3158    async fn add_view_from_leader(
3159        this: WeakView<Self>,
3160        leader_id: PeerId,
3161        pane: View<Pane>,
3162        view: &proto::View,
3163        cx: &mut AsyncWindowContext,
3164    ) -> Result<()> {
3165        let this = this.upgrade().context("workspace dropped")?;
3166
3167        let item_builders = cx.update(|cx| {
3168            cx.default_global::<FollowableItemBuilders>()
3169                .values()
3170                .map(|b| b.0)
3171                .collect::<Vec<_>>()
3172        })?;
3173
3174        let Some(id) = view.id.clone() else {
3175            return Err(anyhow!("no id for view"));
3176        };
3177        let id = ViewId::from_proto(id)?;
3178
3179        let mut variant = view.variant.clone();
3180        if variant.is_none() {
3181            Err(anyhow!("missing view variant"))?;
3182        }
3183
3184        let task = item_builders.iter().find_map(|build_item| {
3185            cx.update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx))
3186                .log_err()
3187                .flatten()
3188        });
3189        let Some(task) = task else {
3190            return Err(anyhow!(
3191                "failed to construct view from leader (maybe from a different version of zed?)"
3192            ));
3193        };
3194
3195        let item = task.await?;
3196
3197        this.update(cx, |this, cx| {
3198            let state = this.follower_states.get_mut(&pane)?;
3199            item.set_leader_peer_id(Some(leader_id), cx);
3200            state.items_by_leader_view_id.insert(id, item);
3201
3202            Some(())
3203        })?;
3204
3205        Ok(())
3206    }
3207
3208    async fn add_views_from_leader(
3209        this: WeakView<Self>,
3210        leader_id: PeerId,
3211        panes: Vec<View<Pane>>,
3212        views: Vec<proto::View>,
3213        cx: &mut AsyncWindowContext,
3214    ) -> Result<()> {
3215        let this = this.upgrade().context("workspace dropped")?;
3216
3217        let item_builders = cx.update(|cx| {
3218            cx.default_global::<FollowableItemBuilders>()
3219                .values()
3220                .map(|b| b.0)
3221                .collect::<Vec<_>>()
3222        })?;
3223
3224        let mut item_tasks_by_pane = HashMap::default();
3225        for pane in panes {
3226            let mut item_tasks = Vec::new();
3227            let mut leader_view_ids = Vec::new();
3228            for view in &views {
3229                let Some(id) = &view.id else {
3230                    continue;
3231                };
3232                let id = ViewId::from_proto(id.clone())?;
3233                let mut variant = view.variant.clone();
3234                if variant.is_none() {
3235                    Err(anyhow!("missing view variant"))?;
3236                }
3237                for build_item in &item_builders {
3238                    let task = cx.update(|cx| {
3239                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
3240                    })?;
3241                    if let Some(task) = task {
3242                        item_tasks.push(task);
3243                        leader_view_ids.push(id);
3244                        break;
3245                    } else if variant.is_none() {
3246                        Err(anyhow!(
3247                            "failed to construct view from leader (maybe from a different version of zed?)"
3248                        ))?;
3249                    }
3250                }
3251            }
3252
3253            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
3254        }
3255
3256        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
3257            let items = futures::future::try_join_all(item_tasks).await?;
3258            this.update(cx, |this, cx| {
3259                let state = this.follower_states.get_mut(&pane)?;
3260                for (id, item) in leader_view_ids.into_iter().zip(items) {
3261                    item.set_leader_peer_id(Some(leader_id), cx);
3262                    state.items_by_leader_view_id.insert(id, item);
3263                }
3264
3265                Some(())
3266            })?;
3267        }
3268        Ok(())
3269    }
3270
3271    pub fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
3272        let mut is_project_item = true;
3273        let mut update = proto::UpdateActiveView::default();
3274        if cx.is_window_active() {
3275            if let Some(item) = self.active_item(cx) {
3276                if item.focus_handle(cx).contains_focused(cx) {
3277                    let leader_id = self
3278                        .pane_for(&*item)
3279                        .and_then(|pane| self.leader_for_pane(&pane));
3280
3281                    if let Some(item) = item.to_followable_item_handle(cx) {
3282                        let id = item
3283                            .remote_id(&self.app_state.client, cx)
3284                            .map(|id| id.to_proto());
3285
3286                        if let Some(id) = id.clone() {
3287                            if let Some(variant) = item.to_state_proto(cx) {
3288                                let view = Some(proto::View {
3289                                    id: Some(id.clone()),
3290                                    leader_id,
3291                                    variant: Some(variant),
3292                                });
3293
3294                                is_project_item = item.is_project_item(cx);
3295                                update = proto::UpdateActiveView {
3296                                    view,
3297                                    // TODO: once v0.124.0 is retired we can stop sending these
3298                                    id: Some(id),
3299                                    leader_id,
3300                                };
3301                            }
3302                        };
3303                    }
3304                }
3305            }
3306        }
3307
3308        if &update.id != &self.last_active_view_id {
3309            self.last_active_view_id.clone_from(&update.id);
3310            self.update_followers(
3311                is_project_item,
3312                proto::update_followers::Variant::UpdateActiveView(update),
3313                cx,
3314            );
3315        }
3316    }
3317
3318    fn update_followers(
3319        &self,
3320        project_only: bool,
3321        update: proto::update_followers::Variant,
3322        cx: &mut WindowContext,
3323    ) -> Option<()> {
3324        // If this update only applies to for followers in the current project,
3325        // then skip it unless this project is shared. If it applies to all
3326        // followers, regardless of project, then set `project_id` to none,
3327        // indicating that it goes to all followers.
3328        let project_id = if project_only {
3329            Some(self.project.read(cx).remote_id()?)
3330        } else {
3331            None
3332        };
3333        self.app_state().workspace_store.update(cx, |store, cx| {
3334            store.update_followers(project_id, update, cx)
3335        })
3336    }
3337
3338    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
3339        self.follower_states.get(pane).map(|state| state.leader_id)
3340    }
3341
3342    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3343        cx.notify();
3344
3345        let call = self.active_call()?;
3346        let room = call.read(cx).room()?.read(cx);
3347        let participant = room.remote_participant_for_peer_id(leader_id)?;
3348        let mut items_to_activate = Vec::new();
3349
3350        let leader_in_this_app;
3351        let leader_in_this_project;
3352        match participant.location {
3353            call::ParticipantLocation::SharedProject { project_id } => {
3354                leader_in_this_app = true;
3355                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3356            }
3357            call::ParticipantLocation::UnsharedProject => {
3358                leader_in_this_app = true;
3359                leader_in_this_project = false;
3360            }
3361            call::ParticipantLocation::External => {
3362                leader_in_this_app = false;
3363                leader_in_this_project = false;
3364            }
3365        };
3366
3367        for (pane, state) in &self.follower_states {
3368            if state.leader_id != leader_id {
3369                continue;
3370            }
3371            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
3372                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
3373                    if leader_in_this_project || !item.is_project_item(cx) {
3374                        items_to_activate.push((pane.clone(), item.boxed_clone()));
3375                    }
3376                }
3377                continue;
3378            }
3379
3380            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3381                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3382            }
3383        }
3384
3385        for (pane, item) in items_to_activate {
3386            let pane_was_focused = pane.read(cx).has_focus(cx);
3387            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3388                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3389            } else {
3390                pane.update(cx, |pane, cx| {
3391                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3392                });
3393            }
3394
3395            if pane_was_focused {
3396                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3397            }
3398        }
3399
3400        None
3401    }
3402
3403    fn shared_screen_for_peer(
3404        &self,
3405        peer_id: PeerId,
3406        pane: &View<Pane>,
3407        cx: &mut WindowContext,
3408    ) -> Option<View<SharedScreen>> {
3409        let call = self.active_call()?;
3410        let room = call.read(cx).room()?.read(cx);
3411        let participant = room.remote_participant_for_peer_id(peer_id)?;
3412        let track = participant.video_tracks.values().next()?.clone();
3413        let user = participant.user.clone();
3414
3415        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3416            if item.read(cx).peer_id == peer_id {
3417                return Some(item);
3418            }
3419        }
3420
3421        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3422    }
3423
3424    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
3425        if cx.is_window_active() {
3426            self.update_active_view_for_followers(cx);
3427            cx.background_executor()
3428                .spawn(persistence::DB.update_timestamp(self.database_id()))
3429                .detach();
3430        } else {
3431            for pane in &self.panes {
3432                pane.update(cx, |pane, cx| {
3433                    if let Some(item) = pane.active_item() {
3434                        item.workspace_deactivated(cx);
3435                    }
3436                    if matches!(
3437                        WorkspaceSettings::get_global(cx).autosave,
3438                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3439                    ) {
3440                        for item in pane.items() {
3441                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3442                                .detach_and_log_err(cx);
3443                        }
3444                    }
3445                });
3446            }
3447        }
3448    }
3449
3450    fn active_call(&self) -> Option<&Model<ActiveCall>> {
3451        self.active_call.as_ref().map(|(call, _)| call)
3452    }
3453
3454    fn on_active_call_event(
3455        &mut self,
3456        _: Model<ActiveCall>,
3457        event: &call::room::Event,
3458        cx: &mut ViewContext<Self>,
3459    ) {
3460        match event {
3461            call::room::Event::ParticipantLocationChanged { participant_id }
3462            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3463                self.leader_updated(*participant_id, cx);
3464            }
3465            _ => {}
3466        }
3467    }
3468
3469    pub fn database_id(&self) -> WorkspaceId {
3470        self.database_id
3471    }
3472
3473    fn local_paths(&self, cx: &AppContext) -> Option<LocalPaths> {
3474        let project = self.project().read(cx);
3475
3476        if project.is_local() {
3477            Some(LocalPaths::new(
3478                project
3479                    .visible_worktrees(cx)
3480                    .map(|worktree| worktree.read(cx).abs_path())
3481                    .collect::<Vec<_>>(),
3482            ))
3483        } else {
3484            None
3485        }
3486    }
3487
3488    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3489        match member {
3490            Member::Axis(PaneAxis { members, .. }) => {
3491                for child in members.iter() {
3492                    self.remove_panes(child.clone(), cx)
3493                }
3494            }
3495            Member::Pane(pane) => {
3496                self.force_remove_pane(&pane, cx);
3497            }
3498        }
3499    }
3500
3501    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
3502        self.panes.retain(|p| p != pane);
3503        self.panes
3504            .last()
3505            .unwrap()
3506            .update(cx, |pane, cx| pane.focus(cx));
3507        if self.last_active_center_pane == Some(pane.downgrade()) {
3508            self.last_active_center_pane = None;
3509        }
3510        cx.notify();
3511    }
3512
3513    fn serialize_workspace(&mut self, cx: &mut ViewContext<Self>) {
3514        if self._schedule_serialize.is_none() {
3515            self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3516                cx.background_executor()
3517                    .timer(Duration::from_millis(100))
3518                    .await;
3519                this.update(&mut cx, |this, cx| {
3520                    this.serialize_workspace_internal(cx).detach();
3521                    this._schedule_serialize.take();
3522                })
3523                .log_err();
3524            }));
3525        }
3526    }
3527
3528    fn serialize_workspace_internal(&self, cx: &mut WindowContext) -> Task<()> {
3529        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3530            let (items, active) = {
3531                let pane = pane_handle.read(cx);
3532                let active_item_id = pane.active_item().map(|item| item.item_id());
3533                (
3534                    pane.items()
3535                        .filter_map(|item_handle| {
3536                            Some(SerializedItem {
3537                                kind: Arc::from(item_handle.serialized_item_kind()?),
3538                                item_id: item_handle.item_id().as_u64(),
3539                                active: Some(item_handle.item_id()) == active_item_id,
3540                                preview: pane.is_active_preview_item(item_handle.item_id()),
3541                            })
3542                        })
3543                        .collect::<Vec<_>>(),
3544                    pane.has_focus(cx),
3545                )
3546            };
3547
3548            SerializedPane::new(items, active)
3549        }
3550
3551        fn build_serialized_pane_group(
3552            pane_group: &Member,
3553            cx: &WindowContext,
3554        ) -> SerializedPaneGroup {
3555            match pane_group {
3556                Member::Axis(PaneAxis {
3557                    axis,
3558                    members,
3559                    flexes,
3560                    bounding_boxes: _,
3561                }) => SerializedPaneGroup::Group {
3562                    axis: SerializedAxis(*axis),
3563                    children: members
3564                        .iter()
3565                        .map(|member| build_serialized_pane_group(member, cx))
3566                        .collect::<Vec<_>>(),
3567                    flexes: Some(flexes.lock().clone()),
3568                },
3569                Member::Pane(pane_handle) => {
3570                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
3571                }
3572            }
3573        }
3574
3575        fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
3576            let left_dock = this.left_dock.read(cx);
3577            let left_visible = left_dock.is_open();
3578            let left_active_panel = left_dock
3579                .visible_panel()
3580                .map(|panel| panel.persistent_name().to_string());
3581            let left_dock_zoom = left_dock
3582                .visible_panel()
3583                .map(|panel| panel.is_zoomed(cx))
3584                .unwrap_or(false);
3585
3586            let right_dock = this.right_dock.read(cx);
3587            let right_visible = right_dock.is_open();
3588            let right_active_panel = right_dock
3589                .visible_panel()
3590                .map(|panel| panel.persistent_name().to_string());
3591            let right_dock_zoom = right_dock
3592                .visible_panel()
3593                .map(|panel| panel.is_zoomed(cx))
3594                .unwrap_or(false);
3595
3596            let bottom_dock = this.bottom_dock.read(cx);
3597            let bottom_visible = bottom_dock.is_open();
3598            let bottom_active_panel = bottom_dock
3599                .visible_panel()
3600                .map(|panel| panel.persistent_name().to_string());
3601            let bottom_dock_zoom = bottom_dock
3602                .visible_panel()
3603                .map(|panel| panel.is_zoomed(cx))
3604                .unwrap_or(false);
3605
3606            DockStructure {
3607                left: DockData {
3608                    visible: left_visible,
3609                    active_panel: left_active_panel,
3610                    zoom: left_dock_zoom,
3611                },
3612                right: DockData {
3613                    visible: right_visible,
3614                    active_panel: right_active_panel,
3615                    zoom: right_dock_zoom,
3616                },
3617                bottom: DockData {
3618                    visible: bottom_visible,
3619                    active_panel: bottom_active_panel,
3620                    zoom: bottom_dock_zoom,
3621                },
3622            }
3623        }
3624
3625        let location = if let Some(local_paths) = self.local_paths(cx) {
3626            if !local_paths.paths().is_empty() {
3627                Some(SerializedWorkspaceLocation::Local(local_paths))
3628            } else {
3629                None
3630            }
3631        } else if let Some(dev_server_project_id) = self.project().read(cx).dev_server_project_id()
3632        {
3633            let store = dev_server_projects::Store::global(cx).read(cx);
3634            maybe!({
3635                let project = store.dev_server_project(dev_server_project_id)?;
3636                let dev_server = store.dev_server(project.dev_server_id)?;
3637
3638                let dev_server_project = SerializedDevServerProject {
3639                    id: dev_server_project_id,
3640                    dev_server_name: dev_server.name.to_string(),
3641                    path: project.path.to_string(),
3642                };
3643                Some(SerializedWorkspaceLocation::DevServer(dev_server_project))
3644            })
3645        } else {
3646            None
3647        };
3648
3649        // don't save workspace state for the empty workspace.
3650        if let Some(location) = location {
3651            let center_group = build_serialized_pane_group(&self.center.root, cx);
3652            let docks = build_serialized_docks(self, cx);
3653            let window_bounds = Some(SerializedWindowBounds(cx.window_bounds()));
3654            let serialized_workspace = SerializedWorkspace {
3655                id: self.database_id,
3656                location,
3657                center_group,
3658                window_bounds,
3659                display: Default::default(),
3660                docks,
3661                centered_layout: self.centered_layout,
3662            };
3663            return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
3664        }
3665        Task::ready(())
3666    }
3667
3668    pub(crate) fn load_workspace(
3669        serialized_workspace: SerializedWorkspace,
3670        paths_to_open: Vec<Option<ProjectPath>>,
3671        cx: &mut ViewContext<Workspace>,
3672    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3673        cx.spawn(|workspace, mut cx| async move {
3674            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3675
3676            let mut center_group = None;
3677            let mut center_items = None;
3678
3679            // Traverse the splits tree and add to things
3680            if let Some((group, active_pane, items)) = serialized_workspace
3681                .center_group
3682                .deserialize(
3683                    &project,
3684                    serialized_workspace.id,
3685                    workspace.clone(),
3686                    &mut cx,
3687                )
3688                .await
3689            {
3690                center_items = Some(items);
3691                center_group = Some((group, active_pane))
3692            }
3693
3694            let mut items_by_project_path = cx.update(|cx| {
3695                center_items
3696                    .unwrap_or_default()
3697                    .into_iter()
3698                    .filter_map(|item| {
3699                        let item = item?;
3700                        let project_path = item.project_path(cx)?;
3701                        Some((project_path, item))
3702                    })
3703                    .collect::<HashMap<_, _>>()
3704            })?;
3705
3706            let opened_items = paths_to_open
3707                .into_iter()
3708                .map(|path_to_open| {
3709                    path_to_open
3710                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3711                })
3712                .collect::<Vec<_>>();
3713
3714            // Remove old panes from workspace panes list
3715            workspace.update(&mut cx, |workspace, cx| {
3716                if let Some((center_group, active_pane)) = center_group {
3717                    workspace.remove_panes(workspace.center.root.clone(), cx);
3718
3719                    // Swap workspace center group
3720                    workspace.center = PaneGroup::with_root(center_group);
3721                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3722                    if let Some(active_pane) = active_pane {
3723                        workspace.active_pane = active_pane;
3724                        cx.focus_self();
3725                    } else {
3726                        workspace.active_pane = workspace.center.first_pane().clone();
3727                    }
3728                }
3729
3730                let docks = serialized_workspace.docks;
3731
3732                let right = docks.right.clone();
3733                workspace
3734                    .right_dock
3735                    .update(cx, |dock, _| dock.serialized_dock = Some(right));
3736                let left = docks.left.clone();
3737                workspace
3738                    .left_dock
3739                    .update(cx, |dock, _| dock.serialized_dock = Some(left));
3740                let bottom = docks.bottom.clone();
3741                workspace
3742                    .bottom_dock
3743                    .update(cx, |dock, _| dock.serialized_dock = Some(bottom));
3744
3745                cx.notify();
3746            })?;
3747
3748            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3749            workspace
3750                .update(&mut cx, |workspace, cx| {
3751                    workspace.serialize_workspace_internal(cx).detach();
3752                })
3753                .ok();
3754
3755            Ok(opened_items)
3756        })
3757    }
3758
3759    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3760        self.add_workspace_actions_listeners(div, cx)
3761            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3762            .on_action(cx.listener(Self::close_all_items_and_panes))
3763            .on_action(cx.listener(Self::save_all))
3764            .on_action(cx.listener(Self::send_keystrokes))
3765            .on_action(cx.listener(Self::add_folder_to_project))
3766            .on_action(cx.listener(Self::follow_next_collaborator))
3767            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3768                let pane = workspace.active_pane().clone();
3769                workspace.unfollow(&pane, cx);
3770            }))
3771            .on_action(cx.listener(|workspace, action: &Save, cx| {
3772                workspace
3773                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3774                    .detach_and_log_err(cx);
3775            }))
3776            .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, cx| {
3777                workspace
3778                    .save_active_item(SaveIntent::SaveWithoutFormat, cx)
3779                    .detach_and_log_err(cx);
3780            }))
3781            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3782                workspace
3783                    .save_active_item(SaveIntent::SaveAs, cx)
3784                    .detach_and_log_err(cx);
3785            }))
3786            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3787                workspace.activate_previous_pane(cx)
3788            }))
3789            .on_action(
3790                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3791            )
3792            .on_action(
3793                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3794                    workspace.activate_pane_in_direction(action.0, cx)
3795                }),
3796            )
3797            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3798                workspace.swap_pane_in_direction(action.0, cx)
3799            }))
3800            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3801                this.toggle_dock(DockPosition::Left, cx);
3802            }))
3803            .on_action(
3804                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3805                    workspace.toggle_dock(DockPosition::Right, cx);
3806                }),
3807            )
3808            .on_action(
3809                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3810                    workspace.toggle_dock(DockPosition::Bottom, cx);
3811                }),
3812            )
3813            .on_action(
3814                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3815                    workspace.close_all_docks(cx);
3816                }),
3817            )
3818            .on_action(cx.listener(Workspace::open))
3819            .on_action(cx.listener(Workspace::close_window))
3820            .on_action(cx.listener(Workspace::activate_pane_at_index))
3821            .on_action(
3822                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3823                    workspace.reopen_closed_item(cx).detach();
3824                }),
3825            )
3826            .on_action(cx.listener(Workspace::toggle_centered_layout))
3827    }
3828
3829    #[cfg(any(test, feature = "test-support"))]
3830    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3831        use node_runtime::FakeNodeRuntime;
3832
3833        let client = project.read(cx).client();
3834        let user_store = project.read(cx).user_store();
3835
3836        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
3837        cx.activate_window();
3838        let app_state = Arc::new(AppState {
3839            languages: project.read(cx).languages().clone(),
3840            workspace_store,
3841            client,
3842            user_store,
3843            fs: project.read(cx).fs().clone(),
3844            build_window_options: |_, _| Default::default(),
3845            node_runtime: FakeNodeRuntime::new(),
3846        });
3847        let workspace = Self::new(Default::default(), project, app_state, cx);
3848        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3849        workspace
3850    }
3851
3852    pub fn register_action<A: Action>(
3853        &mut self,
3854        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3855    ) -> &mut Self {
3856        let callback = Arc::new(callback);
3857
3858        self.workspace_actions.push(Box::new(move |div, cx| {
3859            let callback = callback.clone();
3860            div.on_action(
3861                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3862            )
3863        }));
3864        self
3865    }
3866
3867    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3868        let mut div = div
3869            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3870            .on_action(cx.listener(Self::close_all_items_and_panes))
3871            .on_action(cx.listener(Self::add_folder_to_project))
3872            .on_action(cx.listener(Self::save_all))
3873            .on_action(cx.listener(Self::open));
3874        for action in self.workspace_actions.iter() {
3875            div = (action)(div, cx)
3876        }
3877        div
3878    }
3879
3880    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3881        self.modal_layer.read(cx).has_active_modal()
3882    }
3883
3884    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
3885        self.modal_layer.read(cx).active_modal()
3886    }
3887
3888    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
3889    where
3890        B: FnOnce(&mut ViewContext<V>) -> V,
3891    {
3892        self.modal_layer
3893            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3894    }
3895
3896    pub fn toggle_centered_layout(&mut self, _: &ToggleCenteredLayout, cx: &mut ViewContext<Self>) {
3897        self.centered_layout = !self.centered_layout;
3898        cx.background_executor()
3899            .spawn(DB.set_centered_layout(self.database_id, self.centered_layout))
3900            .detach_and_log_err(cx);
3901        cx.notify();
3902    }
3903
3904    fn adjust_padding(padding: Option<f32>) -> f32 {
3905        padding
3906            .unwrap_or(Self::DEFAULT_PADDING)
3907            .min(Self::MAX_PADDING)
3908            .max(0.0)
3909    }
3910}
3911
3912fn window_bounds_env_override() -> Option<Bounds<DevicePixels>> {
3913    ZED_WINDOW_POSITION
3914        .zip(*ZED_WINDOW_SIZE)
3915        .map(|(position, size)| Bounds {
3916            origin: position,
3917            size,
3918        })
3919}
3920
3921fn open_items(
3922    serialized_workspace: Option<SerializedWorkspace>,
3923    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3924    app_state: Arc<AppState>,
3925    cx: &mut ViewContext<Workspace>,
3926) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3927    let restored_items = serialized_workspace.map(|serialized_workspace| {
3928        Workspace::load_workspace(
3929            serialized_workspace,
3930            project_paths_to_open
3931                .iter()
3932                .map(|(_, project_path)| project_path)
3933                .cloned()
3934                .collect(),
3935            cx,
3936        )
3937    });
3938
3939    cx.spawn(|workspace, mut cx| async move {
3940        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3941
3942        if let Some(restored_items) = restored_items {
3943            let restored_items = restored_items.await?;
3944
3945            let restored_project_paths = restored_items
3946                .iter()
3947                .filter_map(|item| {
3948                    cx.update(|cx| item.as_ref()?.project_path(cx))
3949                        .ok()
3950                        .flatten()
3951                })
3952                .collect::<HashSet<_>>();
3953
3954            for restored_item in restored_items {
3955                opened_items.push(restored_item.map(Ok));
3956            }
3957
3958            project_paths_to_open
3959                .iter_mut()
3960                .for_each(|(_, project_path)| {
3961                    if let Some(project_path_to_open) = project_path {
3962                        if restored_project_paths.contains(project_path_to_open) {
3963                            *project_path = None;
3964                        }
3965                    }
3966                });
3967        } else {
3968            for _ in 0..project_paths_to_open.len() {
3969                opened_items.push(None);
3970            }
3971        }
3972        assert!(opened_items.len() == project_paths_to_open.len());
3973
3974        let tasks =
3975            project_paths_to_open
3976                .into_iter()
3977                .enumerate()
3978                .map(|(ix, (abs_path, project_path))| {
3979                    let workspace = workspace.clone();
3980                    cx.spawn(|mut cx| {
3981                        let fs = app_state.fs.clone();
3982                        async move {
3983                            let file_project_path = project_path?;
3984                            if fs.is_dir(&abs_path).await {
3985                                None
3986                            } else {
3987                                Some((
3988                                    ix,
3989                                    workspace
3990                                        .update(&mut cx, |workspace, cx| {
3991                                            workspace.open_path(file_project_path, None, true, cx)
3992                                        })
3993                                        .log_err()?
3994                                        .await,
3995                                ))
3996                            }
3997                        }
3998                    })
3999                });
4000
4001        let tasks = tasks.collect::<Vec<_>>();
4002
4003        let tasks = futures::future::join_all(tasks);
4004        for (ix, path_open_result) in tasks.await.into_iter().flatten() {
4005            opened_items[ix] = Some(path_open_result);
4006        }
4007
4008        Ok(opened_items)
4009    })
4010}
4011
4012enum ActivateInDirectionTarget {
4013    Pane(View<Pane>),
4014    Dock(View<Dock>),
4015}
4016
4017fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
4018    const REPORT_ISSUE_URL: &str = "https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
4019
4020    workspace
4021        .update(cx, |workspace, cx| {
4022            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
4023                struct DatabaseFailedNotification;
4024
4025                workspace.show_notification_once(
4026                    NotificationId::unique::<DatabaseFailedNotification>(),
4027                    cx,
4028                    |cx| {
4029                        cx.new_view(|_| {
4030                            MessageNotification::new("Failed to load the database file.")
4031                                .with_click_message("Click to let us know about this error")
4032                                .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
4033                        })
4034                    },
4035                );
4036            }
4037        })
4038        .log_err();
4039}
4040
4041impl FocusableView for Workspace {
4042    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4043        self.active_pane.focus_handle(cx)
4044    }
4045}
4046
4047#[derive(Clone, Render)]
4048struct DraggedDock(DockPosition);
4049
4050impl Render for Workspace {
4051    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4052        let mut context = KeyContext::new_with_defaults();
4053        context.add("Workspace");
4054        let centered_layout = self.centered_layout
4055            && self.center.panes().len() == 1
4056            && self.active_item(cx).is_some();
4057        let render_padding = |size| {
4058            (size > 0.0).then(|| {
4059                div()
4060                    .h_full()
4061                    .w(relative(size))
4062                    .bg(cx.theme().colors().editor_background)
4063                    .border_color(cx.theme().colors().pane_group_border)
4064            })
4065        };
4066        let paddings = if centered_layout {
4067            let settings = WorkspaceSettings::get_global(cx).centered_layout;
4068            (
4069                render_padding(Self::adjust_padding(settings.left_padding)),
4070                render_padding(Self::adjust_padding(settings.right_padding)),
4071            )
4072        } else {
4073            (None, None)
4074        };
4075        let (ui_font, ui_font_size) = {
4076            let theme_settings = ThemeSettings::get_global(cx);
4077            (
4078                theme_settings.ui_font.family.clone(),
4079                theme_settings.ui_font_size,
4080            )
4081        };
4082
4083        let theme = cx.theme().clone();
4084        let colors = theme.colors();
4085        cx.set_rem_size(ui_font_size);
4086
4087        self.actions(div(), cx)
4088            .key_context(context)
4089            .relative()
4090            .size_full()
4091            .flex()
4092            .flex_col()
4093            .font_family(ui_font)
4094            .gap_0()
4095            .justify_start()
4096            .items_start()
4097            .text_color(colors.text)
4098            .bg(colors.background)
4099            .children(self.titlebar_item.clone())
4100            .child(
4101                div()
4102                    .id("workspace")
4103                    .relative()
4104                    .flex_1()
4105                    .w_full()
4106                    .flex()
4107                    .flex_col()
4108                    .overflow_hidden()
4109                    .border_t_1()
4110                    .border_b_1()
4111                    .border_color(colors.border)
4112                    .child({
4113                        let this = cx.view().clone();
4114                        canvas(
4115                            move |bounds, cx| this.update(cx, |this, _cx| this.bounds = bounds),
4116                            |_, _, _| {},
4117                        )
4118                        .absolute()
4119                        .size_full()
4120                    })
4121                    .when(self.zoomed.is_none(), |this| {
4122                        this.on_drag_move(cx.listener(
4123                            |workspace, e: &DragMoveEvent<DraggedDock>, cx| match e.drag(cx).0 {
4124                                DockPosition::Left => {
4125                                    let size = workspace.bounds.left() + e.event.position.x;
4126                                    workspace.left_dock.update(cx, |left_dock, cx| {
4127                                        left_dock.resize_active_panel(Some(size), cx);
4128                                    });
4129                                }
4130                                DockPosition::Right => {
4131                                    let size = workspace.bounds.right() - e.event.position.x;
4132                                    workspace.right_dock.update(cx, |right_dock, cx| {
4133                                        right_dock.resize_active_panel(Some(size), cx);
4134                                    });
4135                                }
4136                                DockPosition::Bottom => {
4137                                    let size = workspace.bounds.bottom() - e.event.position.y;
4138                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
4139                                        bottom_dock.resize_active_panel(Some(size), cx);
4140                                    });
4141                                }
4142                            },
4143                        ))
4144                    })
4145                    .child(
4146                        div()
4147                            .flex()
4148                            .flex_row()
4149                            .h_full()
4150                            // Left Dock
4151                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
4152                                || {
4153                                    div()
4154                                        .flex()
4155                                        .flex_none()
4156                                        .overflow_hidden()
4157                                        .child(self.left_dock.clone())
4158                                },
4159                            ))
4160                            // Panes
4161                            .child(
4162                                div()
4163                                    .flex()
4164                                    .flex_col()
4165                                    .flex_1()
4166                                    .overflow_hidden()
4167                                    .child(
4168                                        h_flex()
4169                                            .flex_1()
4170                                            .when_some(paddings.0, |this, p| {
4171                                                this.child(p.border_r_1())
4172                                            })
4173                                            .child(self.center.render(
4174                                                &self.project,
4175                                                &self.follower_states,
4176                                                self.active_call(),
4177                                                &self.active_pane,
4178                                                self.zoomed.as_ref(),
4179                                                &self.app_state,
4180                                                cx,
4181                                            ))
4182                                            .when_some(paddings.1, |this, p| {
4183                                                this.child(p.border_l_1())
4184                                            }),
4185                                    )
4186                                    .children(
4187                                        self.zoomed_position
4188                                            .ne(&Some(DockPosition::Bottom))
4189                                            .then(|| self.bottom_dock.clone()),
4190                                    ),
4191                            )
4192                            // Right Dock
4193                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
4194                                || {
4195                                    div()
4196                                        .flex()
4197                                        .flex_none()
4198                                        .overflow_hidden()
4199                                        .child(self.right_dock.clone())
4200                                },
4201                            )),
4202                    )
4203                    .children(self.zoomed.as_ref().and_then(|view| {
4204                        let zoomed_view = view.upgrade()?;
4205                        let div = div()
4206                            .occlude()
4207                            .absolute()
4208                            .overflow_hidden()
4209                            .border_color(colors.border)
4210                            .bg(colors.background)
4211                            .child(zoomed_view)
4212                            .inset_0()
4213                            .shadow_lg();
4214
4215                        Some(match self.zoomed_position {
4216                            Some(DockPosition::Left) => div.right_2().border_r_1(),
4217                            Some(DockPosition::Right) => div.left_2().border_l_1(),
4218                            Some(DockPosition::Bottom) => div.top_2().border_t_1(),
4219                            None => div.top_2().bottom_2().left_2().right_2().border_1(),
4220                        })
4221                    }))
4222                    .child(self.modal_layer.clone())
4223                    .children(self.render_notifications(cx)),
4224            )
4225            .child(self.status_bar.clone())
4226            .children(if self.project.read(cx).is_disconnected() {
4227                Some(DisconnectedOverlay)
4228            } else {
4229                None
4230            })
4231    }
4232}
4233
4234impl WorkspaceStore {
4235    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
4236        Self {
4237            workspaces: Default::default(),
4238            _subscriptions: vec![
4239                client.add_request_handler(cx.weak_model(), Self::handle_follow),
4240                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
4241            ],
4242            client,
4243        }
4244    }
4245
4246    pub fn update_followers(
4247        &self,
4248        project_id: Option<u64>,
4249        update: proto::update_followers::Variant,
4250        cx: &AppContext,
4251    ) -> Option<()> {
4252        let active_call = ActiveCall::try_global(cx)?;
4253        let room_id = active_call.read(cx).room()?.read(cx).id();
4254        self.client
4255            .send(proto::UpdateFollowers {
4256                room_id,
4257                project_id,
4258                variant: Some(update),
4259            })
4260            .log_err()
4261    }
4262
4263    pub async fn handle_follow(
4264        this: Model<Self>,
4265        envelope: TypedEnvelope<proto::Follow>,
4266        _: Arc<Client>,
4267        mut cx: AsyncAppContext,
4268    ) -> Result<proto::FollowResponse> {
4269        this.update(&mut cx, |this, cx| {
4270            let follower = Follower {
4271                project_id: envelope.payload.project_id,
4272                peer_id: envelope.original_sender_id()?,
4273            };
4274
4275            let mut response = proto::FollowResponse::default();
4276            this.workspaces.retain(|workspace| {
4277                workspace
4278                    .update(cx, |workspace, cx| {
4279                        let handler_response = workspace.handle_follow(follower.project_id, cx);
4280                        if response.views.is_empty() {
4281                            response.views = handler_response.views;
4282                        } else {
4283                            response.views.extend_from_slice(&handler_response.views);
4284                        }
4285
4286                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
4287                            if response.active_view_id.is_none()
4288                                || workspace.project.read(cx).remote_id() == follower.project_id
4289                            {
4290                                response.active_view_id = Some(active_view_id);
4291                            }
4292                        }
4293
4294                        if let Some(active_view) = handler_response.active_view.clone() {
4295                            if response.active_view_id.is_none()
4296                                || workspace.project.read(cx).remote_id() == follower.project_id
4297                            {
4298                                response.active_view = Some(active_view)
4299                            }
4300                        }
4301                    })
4302                    .is_ok()
4303            });
4304
4305            Ok(response)
4306        })?
4307    }
4308
4309    async fn handle_update_followers(
4310        this: Model<Self>,
4311        envelope: TypedEnvelope<proto::UpdateFollowers>,
4312        _: Arc<Client>,
4313        mut cx: AsyncAppContext,
4314    ) -> Result<()> {
4315        let leader_id = envelope.original_sender_id()?;
4316        let update = envelope.payload;
4317
4318        this.update(&mut cx, |this, cx| {
4319            this.workspaces.retain(|workspace| {
4320                workspace
4321                    .update(cx, |workspace, cx| {
4322                        let project_id = workspace.project.read(cx).remote_id();
4323                        if update.project_id != project_id && update.project_id.is_some() {
4324                            return;
4325                        }
4326                        workspace.handle_update_followers(leader_id, update.clone(), cx);
4327                    })
4328                    .is_ok()
4329            });
4330            Ok(())
4331        })?
4332    }
4333}
4334
4335impl ViewId {
4336    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4337        Ok(Self {
4338            creator: message
4339                .creator
4340                .ok_or_else(|| anyhow!("creator is missing"))?,
4341            id: message.id,
4342        })
4343    }
4344
4345    pub(crate) fn to_proto(&self) -> proto::ViewId {
4346        proto::ViewId {
4347            creator: Some(self.creator),
4348            id: self.id,
4349        }
4350    }
4351}
4352
4353pub trait WorkspaceHandle {
4354    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4355}
4356
4357impl WorkspaceHandle for View<Workspace> {
4358    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4359        self.read(cx)
4360            .worktrees(cx)
4361            .flat_map(|worktree| {
4362                let worktree_id = worktree.read(cx).id();
4363                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4364                    worktree_id,
4365                    path: f.path.clone(),
4366                })
4367            })
4368            .collect::<Vec<_>>()
4369    }
4370}
4371
4372impl std::fmt::Debug for OpenPaths {
4373    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4374        f.debug_struct("OpenPaths")
4375            .field("paths", &self.paths)
4376            .finish()
4377    }
4378}
4379
4380pub fn activate_workspace_for_project(
4381    cx: &mut AppContext,
4382    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4383) -> Option<WindowHandle<Workspace>> {
4384    for window in cx.windows() {
4385        let Some(workspace) = window.downcast::<Workspace>() else {
4386            continue;
4387        };
4388
4389        let predicate = workspace
4390            .update(cx, |workspace, cx| {
4391                let project = workspace.project.read(cx);
4392                if predicate(project, cx) {
4393                    cx.activate_window();
4394                    true
4395                } else {
4396                    false
4397                }
4398            })
4399            .log_err()
4400            .unwrap_or(false);
4401
4402        if predicate {
4403            return Some(workspace);
4404        }
4405    }
4406
4407    None
4408}
4409
4410pub async fn last_opened_workspace_paths() -> Option<LocalPaths> {
4411    DB.last_workspace().await.log_err().flatten()
4412}
4413
4414actions!(collab, [OpenChannelNotes]);
4415actions!(zed, [OpenLog]);
4416
4417async fn join_channel_internal(
4418    channel_id: ChannelId,
4419    app_state: &Arc<AppState>,
4420    requesting_window: Option<WindowHandle<Workspace>>,
4421    active_call: &Model<ActiveCall>,
4422    cx: &mut AsyncAppContext,
4423) -> Result<bool> {
4424    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4425        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4426            return (false, None);
4427        };
4428
4429        let already_in_channel = room.channel_id() == Some(channel_id);
4430        let should_prompt = room.is_sharing_project()
4431            && room.remote_participants().len() > 0
4432            && !already_in_channel;
4433        let open_room = if already_in_channel {
4434            active_call.room().cloned()
4435        } else {
4436            None
4437        };
4438        (should_prompt, open_room)
4439    })?;
4440
4441    if let Some(room) = open_room {
4442        let task = room.update(cx, |room, cx| {
4443            if let Some((project, host)) = room.most_active_project(cx) {
4444                return Some(join_in_room_project(project, host, app_state.clone(), cx));
4445            }
4446
4447            None
4448        })?;
4449        if let Some(task) = task {
4450            task.await?;
4451        }
4452        return anyhow::Ok(true);
4453    }
4454
4455    if should_prompt {
4456        if let Some(workspace) = requesting_window {
4457            let answer = workspace
4458                .update(cx, |_, cx| {
4459                    cx.prompt(
4460                        PromptLevel::Warning,
4461                        "Do you want to switch channels?",
4462                        Some("Leaving this call will unshare your current project."),
4463                        &["Yes, Join Channel", "Cancel"],
4464                    )
4465                })?
4466                .await;
4467
4468            if answer == Ok(1) {
4469                return Ok(false);
4470            }
4471        } else {
4472            return Ok(false); // unreachable!() hopefully
4473        }
4474    }
4475
4476    let client = cx.update(|cx| active_call.read(cx).client())?;
4477
4478    let mut client_status = client.status();
4479
4480    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4481    'outer: loop {
4482        let Some(status) = client_status.recv().await else {
4483            return Err(anyhow!("error connecting"));
4484        };
4485
4486        match status {
4487            Status::Connecting
4488            | Status::Authenticating
4489            | Status::Reconnecting
4490            | Status::Reauthenticating => continue,
4491            Status::Connected { .. } => break 'outer,
4492            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
4493            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
4494            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4495                return Err(ErrorCode::Disconnected.into());
4496            }
4497        }
4498    }
4499
4500    let room = active_call
4501        .update(cx, |active_call, cx| {
4502            active_call.join_channel(channel_id, cx)
4503        })?
4504        .await?;
4505
4506    let Some(room) = room else {
4507        return anyhow::Ok(true);
4508    };
4509
4510    room.update(cx, |room, _| room.room_update_completed())?
4511        .await;
4512
4513    let task = room.update(cx, |room, cx| {
4514        if let Some((project, host)) = room.most_active_project(cx) {
4515            return Some(join_in_room_project(project, host, app_state.clone(), cx));
4516        }
4517        // if you are the first to join a channel, share your project
4518        if room.remote_participants().len() == 0 && !room.local_participant_is_guest() {
4519            if let Some(workspace) = requesting_window {
4520                let project = workspace.update(cx, |workspace, cx| {
4521                    if !CallSettings::get_global(cx).share_on_join {
4522                        return None;
4523                    }
4524                    let project = workspace.project.read(cx);
4525                    if (project.is_local() || project.dev_server_project_id().is_some())
4526                        && project.visible_worktrees(cx).any(|tree| {
4527                            tree.read(cx)
4528                                .root_entry()
4529                                .map_or(false, |entry| entry.is_dir())
4530                        })
4531                    {
4532                        Some(workspace.project.clone())
4533                    } else {
4534                        None
4535                    }
4536                });
4537                if let Ok(Some(project)) = project {
4538                    return Some(cx.spawn(|room, mut cx| async move {
4539                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
4540                            .await?;
4541                        Ok(())
4542                    }));
4543                }
4544            }
4545        }
4546
4547        None
4548    })?;
4549    if let Some(task) = task {
4550        task.await?;
4551        return anyhow::Ok(true);
4552    }
4553    anyhow::Ok(false)
4554}
4555
4556pub fn join_channel(
4557    channel_id: ChannelId,
4558    app_state: Arc<AppState>,
4559    requesting_window: Option<WindowHandle<Workspace>>,
4560    cx: &mut AppContext,
4561) -> Task<Result<()>> {
4562    let active_call = ActiveCall::global(cx);
4563    cx.spawn(|mut cx| async move {
4564        let result = join_channel_internal(
4565            channel_id,
4566            &app_state,
4567            requesting_window,
4568            &active_call,
4569            &mut cx,
4570        )
4571            .await;
4572
4573        // join channel succeeded, and opened a window
4574        if matches!(result, Ok(true)) {
4575            return anyhow::Ok(());
4576        }
4577
4578        // find an existing workspace to focus and show call controls
4579        let mut active_window =
4580            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4581        if active_window.is_none() {
4582            // no open workspaces, make one to show the error in (blergh)
4583            let (window_handle, _) = cx
4584                .update(|cx| {
4585                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4586                })?
4587                .await?;
4588
4589            if result.is_ok() {
4590                cx.update(|cx| {
4591                    cx.dispatch_action(&OpenChannelNotes);
4592                }).log_err();
4593            }
4594
4595            active_window = Some(window_handle);
4596        }
4597
4598        if let Err(err) = result {
4599            log::error!("failed to join channel: {}", err);
4600            if let Some(active_window) = active_window {
4601                active_window
4602                    .update(&mut cx, |_, cx| {
4603                        let detail: SharedString = match err.error_code() {
4604                            ErrorCode::SignedOut => {
4605                                "Please sign in to continue.".into()
4606                            }
4607                            ErrorCode::UpgradeRequired => {
4608                                "Your are running an unsupported version of Zed. Please update to continue.".into()
4609                            }
4610                            ErrorCode::NoSuchChannel => {
4611                                "No matching channel was found. Please check the link and try again.".into()
4612                            }
4613                            ErrorCode::Forbidden => {
4614                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
4615                            }
4616                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
4617                            _ => format!("{}\n\nPlease try again.", err).into(),
4618                        };
4619                        cx.prompt(
4620                            PromptLevel::Critical,
4621                            "Failed to join channel",
4622                            Some(&detail),
4623                            &["Ok"],
4624                        )
4625                    })?
4626                    .await
4627                    .ok();
4628            }
4629        }
4630
4631        // return ok, we showed the error to the user.
4632        return anyhow::Ok(());
4633    })
4634}
4635
4636pub async fn get_any_active_workspace(
4637    app_state: Arc<AppState>,
4638    mut cx: AsyncAppContext,
4639) -> anyhow::Result<WindowHandle<Workspace>> {
4640    // find an existing workspace to focus and show call controls
4641    let active_window = activate_any_workspace_window(&mut cx);
4642    if active_window.is_none() {
4643        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4644            .await?;
4645    }
4646    activate_any_workspace_window(&mut cx).context("could not open zed")
4647}
4648
4649fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4650    cx.update(|cx| {
4651        if let Some(workspace_window) = cx
4652            .active_window()
4653            .and_then(|window| window.downcast::<Workspace>())
4654        {
4655            return Some(workspace_window);
4656        }
4657
4658        for window in cx.windows() {
4659            if let Some(workspace_window) = window.downcast::<Workspace>() {
4660                workspace_window
4661                    .update(cx, |_, cx| cx.activate_window())
4662                    .ok();
4663                return Some(workspace_window);
4664            }
4665        }
4666        None
4667    })
4668    .ok()
4669    .flatten()
4670}
4671
4672fn local_workspace_windows(cx: &AppContext) -> Vec<WindowHandle<Workspace>> {
4673    cx.windows()
4674        .into_iter()
4675        .filter_map(|window| window.downcast::<Workspace>())
4676        .filter(|workspace| {
4677            workspace
4678                .read(cx)
4679                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
4680        })
4681        .collect()
4682}
4683
4684#[derive(Default)]
4685pub struct OpenOptions {
4686    pub open_new_workspace: Option<bool>,
4687    pub replace_window: Option<WindowHandle<Workspace>>,
4688}
4689
4690#[allow(clippy::type_complexity)]
4691pub fn open_paths(
4692    abs_paths: &[PathBuf],
4693    app_state: Arc<AppState>,
4694    open_options: OpenOptions,
4695    cx: &mut AppContext,
4696) -> Task<
4697    anyhow::Result<(
4698        WindowHandle<Workspace>,
4699        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4700    )>,
4701> {
4702    let abs_paths = abs_paths.to_vec();
4703    let mut existing = None;
4704    let mut best_match = None;
4705    let mut open_visible = OpenVisible::All;
4706
4707    if open_options.open_new_workspace != Some(true) {
4708        for window in local_workspace_windows(cx) {
4709            if let Ok(workspace) = window.read(cx) {
4710                let m = workspace
4711                    .project
4712                    .read(cx)
4713                    .visibility_for_paths(&abs_paths, cx);
4714                if m > best_match {
4715                    existing = Some(window);
4716                    best_match = m;
4717                } else if best_match.is_none() && open_options.open_new_workspace == Some(false) {
4718                    existing = Some(window)
4719                }
4720            }
4721        }
4722    }
4723
4724    cx.spawn(move |mut cx| async move {
4725        if open_options.open_new_workspace.is_none() && existing.is_none() {
4726            let all_files = abs_paths.iter().map(|path| app_state.fs.metadata(path));
4727            if futures::future::join_all(all_files)
4728                .await
4729                .into_iter()
4730                .filter_map(|result| result.ok().flatten())
4731                .all(|file| !file.is_dir)
4732            {
4733                cx.update(|cx| {
4734                    for window in local_workspace_windows(cx) {
4735                        if let Ok(workspace) = window.read(cx) {
4736                            let project = workspace.project().read(cx);
4737                            if project.is_remote() {
4738                                continue;
4739                            }
4740                            existing = Some(window);
4741                            open_visible = OpenVisible::None;
4742                            break;
4743                        }
4744                    }
4745                })?;
4746            }
4747        }
4748
4749        if let Some(existing) = existing {
4750            Ok((
4751                existing,
4752                existing
4753                    .update(&mut cx, |workspace, cx| {
4754                        cx.activate_window();
4755                        workspace.open_paths(abs_paths, open_visible, None, cx)
4756                    })?
4757                    .await,
4758            ))
4759        } else {
4760            cx.update(move |cx| {
4761                Workspace::new_local(
4762                    abs_paths,
4763                    app_state.clone(),
4764                    open_options.replace_window,
4765                    cx,
4766                )
4767            })?
4768            .await
4769        }
4770    })
4771}
4772
4773pub fn open_new(
4774    app_state: Arc<AppState>,
4775    cx: &mut AppContext,
4776    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4777) -> Task<()> {
4778    let task = Workspace::new_local(Vec::new(), app_state, None, cx);
4779    cx.spawn(|mut cx| async move {
4780        if let Some((workspace, opened_paths)) = task.await.log_err() {
4781            workspace
4782                .update(&mut cx, |workspace, cx| {
4783                    if opened_paths.is_empty() {
4784                        init(workspace, cx)
4785                    }
4786                })
4787                .log_err();
4788        }
4789    })
4790}
4791
4792pub fn create_and_open_local_file(
4793    path: &'static Path,
4794    cx: &mut ViewContext<Workspace>,
4795    default_content: impl 'static + Send + FnOnce() -> Rope,
4796) -> Task<Result<Box<dyn ItemHandle>>> {
4797    cx.spawn(|workspace, mut cx| async move {
4798        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4799        if !fs.is_file(path).await {
4800            fs.create_file(path, Default::default()).await?;
4801            fs.save(path, &default_content(), Default::default())
4802                .await?;
4803        }
4804
4805        let mut items = workspace
4806            .update(&mut cx, |workspace, cx| {
4807                workspace.with_local_workspace(cx, |workspace, cx| {
4808                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4809                })
4810            })?
4811            .await?
4812            .await;
4813
4814        let item = items.pop().flatten();
4815        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4816    })
4817}
4818
4819pub fn join_hosted_project(
4820    hosted_project_id: ProjectId,
4821    app_state: Arc<AppState>,
4822    cx: &mut AppContext,
4823) -> Task<Result<()>> {
4824    cx.spawn(|mut cx| async move {
4825        let existing_window = cx.update(|cx| {
4826            cx.windows().into_iter().find_map(|window| {
4827                let workspace = window.downcast::<Workspace>()?;
4828                workspace
4829                    .read(cx)
4830                    .is_ok_and(|workspace| {
4831                        workspace.project().read(cx).hosted_project_id() == Some(hosted_project_id)
4832                    })
4833                    .then(|| workspace)
4834            })
4835        })?;
4836
4837        let workspace = if let Some(existing_window) = existing_window {
4838            existing_window
4839        } else {
4840            let project = Project::hosted(
4841                hosted_project_id,
4842                app_state.user_store.clone(),
4843                app_state.client.clone(),
4844                app_state.languages.clone(),
4845                app_state.fs.clone(),
4846                cx.clone(),
4847            )
4848            .await?;
4849
4850            let window_bounds_override = window_bounds_env_override();
4851            cx.update(|cx| {
4852                let mut options = (app_state.build_window_options)(None, cx);
4853                options.window_bounds =
4854                    window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
4855                cx.open_window(options, |cx| {
4856                    cx.new_view(|cx| {
4857                        Workspace::new(Default::default(), project, app_state.clone(), cx)
4858                    })
4859                })
4860            })?
4861        };
4862
4863        workspace.update(&mut cx, |_, cx| {
4864            cx.activate(true);
4865            cx.activate_window();
4866        })?;
4867
4868        Ok(())
4869    })
4870}
4871
4872pub fn join_dev_server_project(
4873    project_id: ProjectId,
4874    app_state: Arc<AppState>,
4875    window_to_replace: Option<WindowHandle<Workspace>>,
4876    cx: &mut AppContext,
4877) -> Task<Result<WindowHandle<Workspace>>> {
4878    let windows = cx.windows();
4879    cx.spawn(|mut cx| async move {
4880        let existing_workspace = windows.into_iter().find_map(|window| {
4881            window.downcast::<Workspace>().and_then(|window| {
4882                window
4883                    .update(&mut cx, |workspace, cx| {
4884                        if workspace.project().read(cx).remote_id() == Some(project_id.0) {
4885                            Some(window)
4886                        } else {
4887                            None
4888                        }
4889                    })
4890                    .unwrap_or(None)
4891            })
4892        });
4893
4894        let workspace = if let Some(existing_workspace) = existing_workspace {
4895            existing_workspace
4896        } else {
4897            let project = Project::remote(
4898                project_id.0,
4899                app_state.client.clone(),
4900                app_state.user_store.clone(),
4901                app_state.languages.clone(),
4902                app_state.fs.clone(),
4903                cx.clone(),
4904            )
4905            .await?;
4906
4907            if let Some(window_to_replace) = window_to_replace {
4908                cx.update_window(window_to_replace.into(), |_, cx| {
4909                    cx.replace_root_view(|cx| {
4910                        Workspace::new(Default::default(), project, app_state.clone(), cx)
4911                    });
4912                })?;
4913                window_to_replace
4914            } else {
4915                let window_bounds_override = window_bounds_env_override();
4916                cx.update(|cx| {
4917                    let mut options = (app_state.build_window_options)(None, cx);
4918                    options.window_bounds =
4919                        window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
4920                    cx.open_window(options, |cx| {
4921                        cx.new_view(|cx| {
4922                            Workspace::new(Default::default(), project, app_state.clone(), cx)
4923                        })
4924                    })
4925                })?
4926            }
4927        };
4928
4929        workspace.update(&mut cx, |_, cx| {
4930            cx.activate(true);
4931            cx.activate_window();
4932        })?;
4933
4934        anyhow::Ok(workspace)
4935    })
4936}
4937
4938pub fn join_in_room_project(
4939    project_id: u64,
4940    follow_user_id: u64,
4941    app_state: Arc<AppState>,
4942    cx: &mut AppContext,
4943) -> Task<Result<()>> {
4944    let windows = cx.windows();
4945    cx.spawn(|mut cx| async move {
4946        let existing_workspace = windows.into_iter().find_map(|window| {
4947            window.downcast::<Workspace>().and_then(|window| {
4948                window
4949                    .update(&mut cx, |workspace, cx| {
4950                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4951                            Some(window)
4952                        } else {
4953                            None
4954                        }
4955                    })
4956                    .unwrap_or(None)
4957            })
4958        });
4959
4960        let workspace = if let Some(existing_workspace) = existing_workspace {
4961            existing_workspace
4962        } else {
4963            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4964            let room = active_call
4965                .read_with(&cx, |call, _| call.room().cloned())?
4966                .ok_or_else(|| anyhow!("not in a call"))?;
4967            let project = room
4968                .update(&mut cx, |room, cx| {
4969                    room.join_project(
4970                        project_id,
4971                        app_state.languages.clone(),
4972                        app_state.fs.clone(),
4973                        cx,
4974                    )
4975                })?
4976                .await?;
4977
4978            let window_bounds_override = window_bounds_env_override();
4979            cx.update(|cx| {
4980                let mut options = (app_state.build_window_options)(None, cx);
4981                options.window_bounds =
4982                    window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
4983                cx.open_window(options, |cx| {
4984                    cx.new_view(|cx| {
4985                        Workspace::new(Default::default(), project, app_state.clone(), cx)
4986                    })
4987                })
4988            })?
4989        };
4990
4991        workspace.update(&mut cx, |workspace, cx| {
4992            cx.activate(true);
4993            cx.activate_window();
4994
4995            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4996                let follow_peer_id = room
4997                    .read(cx)
4998                    .remote_participants()
4999                    .iter()
5000                    .find(|(_, participant)| participant.user.id == follow_user_id)
5001                    .map(|(_, p)| p.peer_id)
5002                    .or_else(|| {
5003                        // If we couldn't follow the given user, follow the host instead.
5004                        let collaborator = workspace
5005                            .project()
5006                            .read(cx)
5007                            .collaborators()
5008                            .values()
5009                            .find(|collaborator| collaborator.replica_id == 0)?;
5010                        Some(collaborator.peer_id)
5011                    });
5012
5013                if let Some(follow_peer_id) = follow_peer_id {
5014                    workspace.follow(follow_peer_id, cx);
5015                }
5016            }
5017        })?;
5018
5019        anyhow::Ok(())
5020    })
5021}
5022
5023pub fn restart(_: &Restart, cx: &mut AppContext) {
5024    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
5025    let mut workspace_windows = cx
5026        .windows()
5027        .into_iter()
5028        .filter_map(|window| window.downcast::<Workspace>())
5029        .collect::<Vec<_>>();
5030
5031    // If multiple windows have unsaved changes, and need a save prompt,
5032    // prompt in the active window before switching to a different window.
5033    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
5034
5035    let mut prompt = None;
5036    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
5037        prompt = window
5038            .update(cx, |_, cx| {
5039                cx.prompt(
5040                    PromptLevel::Info,
5041                    "Are you sure you want to restart?",
5042                    None,
5043                    &["Restart", "Cancel"],
5044                )
5045            })
5046            .ok();
5047    }
5048
5049    cx.spawn(|mut cx| async move {
5050        if let Some(prompt) = prompt {
5051            let answer = prompt.await?;
5052            if answer != 0 {
5053                return Ok(());
5054            }
5055        }
5056
5057        // If the user cancels any save prompt, then keep the app open.
5058        for window in workspace_windows {
5059            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
5060                workspace.prepare_to_close(true, cx)
5061            }) {
5062                if !should_close.await? {
5063                    return Ok(());
5064                }
5065            }
5066        }
5067
5068        cx.update(|cx| cx.restart())
5069    })
5070    .detach_and_log_err(cx);
5071}
5072
5073fn parse_pixel_position_env_var(value: &str) -> Option<Point<DevicePixels>> {
5074    let mut parts = value.split(',');
5075    let x: usize = parts.next()?.parse().ok()?;
5076    let y: usize = parts.next()?.parse().ok()?;
5077    Some(point((x as i32).into(), (y as i32).into()))
5078}
5079
5080fn parse_pixel_size_env_var(value: &str) -> Option<Size<DevicePixels>> {
5081    let mut parts = value.split(',');
5082    let width: usize = parts.next()?.parse().ok()?;
5083    let height: usize = parts.next()?.parse().ok()?;
5084    Some(size((width as i32).into(), (height as i32).into()))
5085}
5086
5087struct DisconnectedOverlay;
5088
5089impl Element for DisconnectedOverlay {
5090    type RequestLayoutState = AnyElement;
5091    type PrepaintState = ();
5092
5093    fn id(&self) -> Option<ElementId> {
5094        None
5095    }
5096
5097    fn request_layout(
5098        &mut self,
5099        _id: Option<&GlobalElementId>,
5100        cx: &mut WindowContext,
5101    ) -> (LayoutId, Self::RequestLayoutState) {
5102        let mut background = cx.theme().colors().elevated_surface_background;
5103        background.fade_out(0.2);
5104        let mut overlay = div()
5105            .bg(background)
5106            .absolute()
5107            .left_0()
5108            .top(ui::TitleBar::height(cx))
5109            .size_full()
5110            .flex()
5111            .items_center()
5112            .justify_center()
5113            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
5114            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
5115            .child(Label::new(
5116                "Your connection to the remote project has been lost.",
5117            ))
5118            .into_any();
5119        (overlay.request_layout(cx), overlay)
5120    }
5121
5122    fn prepaint(
5123        &mut self,
5124        _id: Option<&GlobalElementId>,
5125        bounds: Bounds<Pixels>,
5126        overlay: &mut Self::RequestLayoutState,
5127        cx: &mut WindowContext,
5128    ) {
5129        cx.insert_hitbox(bounds, true);
5130        overlay.prepaint(cx);
5131    }
5132
5133    fn paint(
5134        &mut self,
5135        _id: Option<&GlobalElementId>,
5136        _: Bounds<Pixels>,
5137        overlay: &mut Self::RequestLayoutState,
5138        _: &mut Self::PrepaintState,
5139        cx: &mut WindowContext,
5140    ) {
5141        overlay.paint(cx)
5142    }
5143}
5144
5145impl IntoElement for DisconnectedOverlay {
5146    type Element = Self;
5147
5148    fn into_element(self) -> Self::Element {
5149        self
5150    }
5151}
5152
5153#[cfg(test)]
5154mod tests {
5155    use std::{cell::RefCell, rc::Rc};
5156
5157    use super::*;
5158    use crate::{
5159        dock::{test::TestPanel, PanelEvent},
5160        item::{
5161            test::{TestItem, TestProjectItem},
5162            ItemEvent,
5163        },
5164    };
5165    use fs::FakeFs;
5166    use gpui::{
5167        px, BorrowAppContext, DismissEvent, Empty, EventEmitter, FocusHandle, FocusableView,
5168        Render, TestAppContext, VisualTestContext,
5169    };
5170    use project::{Project, ProjectEntryId};
5171    use serde_json::json;
5172    use settings::SettingsStore;
5173
5174    #[gpui::test]
5175    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
5176        init_test(cx);
5177
5178        let fs = FakeFs::new(cx.executor());
5179        let project = Project::test(fs, [], cx).await;
5180        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5181
5182        // Adding an item with no ambiguity renders the tab without detail.
5183        let item1 = cx.new_view(|cx| {
5184            let mut item = TestItem::new(cx);
5185            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
5186            item
5187        });
5188        workspace.update(cx, |workspace, cx| {
5189            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, cx);
5190        });
5191        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
5192
5193        // Adding an item that creates ambiguity increases the level of detail on
5194        // both tabs.
5195        let item2 = cx.new_view(|cx| {
5196            let mut item = TestItem::new(cx);
5197            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5198            item
5199        });
5200        workspace.update(cx, |workspace, cx| {
5201            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5202        });
5203        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5204        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5205
5206        // Adding an item that creates ambiguity increases the level of detail only
5207        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
5208        // we stop at the highest detail available.
5209        let item3 = cx.new_view(|cx| {
5210            let mut item = TestItem::new(cx);
5211            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5212            item
5213        });
5214        workspace.update(cx, |workspace, cx| {
5215            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5216        });
5217        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5218        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
5219        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
5220    }
5221
5222    #[gpui::test]
5223    async fn test_tracking_active_path(cx: &mut TestAppContext) {
5224        init_test(cx);
5225
5226        let fs = FakeFs::new(cx.executor());
5227        fs.insert_tree(
5228            "/root1",
5229            json!({
5230                "one.txt": "",
5231                "two.txt": "",
5232            }),
5233        )
5234        .await;
5235        fs.insert_tree(
5236            "/root2",
5237            json!({
5238                "three.txt": "",
5239            }),
5240        )
5241        .await;
5242
5243        let project = Project::test(fs, ["root1".as_ref()], cx).await;
5244        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5245        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5246        let worktree_id = project.update(cx, |project, cx| {
5247            project.worktrees().next().unwrap().read(cx).id()
5248        });
5249
5250        let item1 = cx.new_view(|cx| {
5251            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
5252        });
5253        let item2 = cx.new_view(|cx| {
5254            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
5255        });
5256
5257        // Add an item to an empty pane
5258        workspace.update(cx, |workspace, cx| {
5259            workspace.add_item_to_active_pane(Box::new(item1), None, cx)
5260        });
5261        project.update(cx, |project, cx| {
5262            assert_eq!(
5263                project.active_entry(),
5264                project
5265                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5266                    .map(|e| e.id)
5267            );
5268        });
5269        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5270
5271        // Add a second item to a non-empty pane
5272        workspace.update(cx, |workspace, cx| {
5273            workspace.add_item_to_active_pane(Box::new(item2), None, cx)
5274        });
5275        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
5276        project.update(cx, |project, cx| {
5277            assert_eq!(
5278                project.active_entry(),
5279                project
5280                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
5281                    .map(|e| e.id)
5282            );
5283        });
5284
5285        // Close the active item
5286        pane.update(cx, |pane, cx| {
5287            pane.close_active_item(&Default::default(), cx).unwrap()
5288        })
5289        .await
5290        .unwrap();
5291        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5292        project.update(cx, |project, cx| {
5293            assert_eq!(
5294                project.active_entry(),
5295                project
5296                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5297                    .map(|e| e.id)
5298            );
5299        });
5300
5301        // Add a project folder
5302        project
5303            .update(cx, |project, cx| {
5304                project.find_or_create_local_worktree("/root2", true, cx)
5305            })
5306            .await
5307            .unwrap();
5308        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
5309
5310        // Remove a project folder
5311        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
5312        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
5313    }
5314
5315    #[gpui::test]
5316    async fn test_close_window(cx: &mut TestAppContext) {
5317        init_test(cx);
5318
5319        let fs = FakeFs::new(cx.executor());
5320        fs.insert_tree("/root", json!({ "one": "" })).await;
5321
5322        let project = Project::test(fs, ["root".as_ref()], cx).await;
5323        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5324
5325        // When there are no dirty items, there's nothing to do.
5326        let item1 = cx.new_view(|cx| TestItem::new(cx));
5327        workspace.update(cx, |w, cx| {
5328            w.add_item_to_active_pane(Box::new(item1.clone()), None, cx)
5329        });
5330        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5331        assert!(task.await.unwrap());
5332
5333        // When there are dirty untitled items, prompt to save each one. If the user
5334        // cancels any prompt, then abort.
5335        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
5336        let item3 = cx.new_view(|cx| {
5337            TestItem::new(cx)
5338                .with_dirty(true)
5339                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5340        });
5341        workspace.update(cx, |w, cx| {
5342            w.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5343            w.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5344        });
5345        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5346        cx.executor().run_until_parked();
5347        cx.simulate_prompt_answer(2); // cancel save all
5348        cx.executor().run_until_parked();
5349        cx.simulate_prompt_answer(2); // cancel save all
5350        cx.executor().run_until_parked();
5351        assert!(!cx.has_pending_prompt());
5352        assert!(!task.await.unwrap());
5353    }
5354
5355    #[gpui::test]
5356    async fn test_close_pane_items(cx: &mut TestAppContext) {
5357        init_test(cx);
5358
5359        let fs = FakeFs::new(cx.executor());
5360
5361        let project = Project::test(fs, None, cx).await;
5362        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5363
5364        let item1 = cx.new_view(|cx| {
5365            TestItem::new(cx)
5366                .with_dirty(true)
5367                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5368        });
5369        let item2 = cx.new_view(|cx| {
5370            TestItem::new(cx)
5371                .with_dirty(true)
5372                .with_conflict(true)
5373                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
5374        });
5375        let item3 = cx.new_view(|cx| {
5376            TestItem::new(cx)
5377                .with_dirty(true)
5378                .with_conflict(true)
5379                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
5380        });
5381        let item4 = cx.new_view(|cx| {
5382            TestItem::new(cx)
5383                .with_dirty(true)
5384                .with_project_items(&[TestProjectItem::new_untitled(cx)])
5385        });
5386        let pane = workspace.update(cx, |workspace, cx| {
5387            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, cx);
5388            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5389            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5390            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, cx);
5391            workspace.active_pane().clone()
5392        });
5393
5394        let close_items = pane.update(cx, |pane, cx| {
5395            pane.activate_item(1, true, true, cx);
5396            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5397            let item1_id = item1.item_id();
5398            let item3_id = item3.item_id();
5399            let item4_id = item4.item_id();
5400            pane.close_items(cx, SaveIntent::Close, move |id| {
5401                [item1_id, item3_id, item4_id].contains(&id)
5402            })
5403        });
5404        cx.executor().run_until_parked();
5405
5406        assert!(cx.has_pending_prompt());
5407        // Ignore "Save all" prompt
5408        cx.simulate_prompt_answer(2);
5409        cx.executor().run_until_parked();
5410        // There's a prompt to save item 1.
5411        pane.update(cx, |pane, _| {
5412            assert_eq!(pane.items_len(), 4);
5413            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
5414        });
5415        // Confirm saving item 1.
5416        cx.simulate_prompt_answer(0);
5417        cx.executor().run_until_parked();
5418
5419        // Item 1 is saved. There's a prompt to save item 3.
5420        pane.update(cx, |pane, cx| {
5421            assert_eq!(item1.read(cx).save_count, 1);
5422            assert_eq!(item1.read(cx).save_as_count, 0);
5423            assert_eq!(item1.read(cx).reload_count, 0);
5424            assert_eq!(pane.items_len(), 3);
5425            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
5426        });
5427        assert!(cx.has_pending_prompt());
5428
5429        // Cancel saving item 3.
5430        cx.simulate_prompt_answer(1);
5431        cx.executor().run_until_parked();
5432
5433        // Item 3 is reloaded. There's a prompt to save item 4.
5434        pane.update(cx, |pane, cx| {
5435            assert_eq!(item3.read(cx).save_count, 0);
5436            assert_eq!(item3.read(cx).save_as_count, 0);
5437            assert_eq!(item3.read(cx).reload_count, 1);
5438            assert_eq!(pane.items_len(), 2);
5439            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
5440        });
5441        assert!(cx.has_pending_prompt());
5442
5443        // Confirm saving item 4.
5444        cx.simulate_prompt_answer(0);
5445        cx.executor().run_until_parked();
5446
5447        // There's a prompt for a path for item 4.
5448        cx.simulate_new_path_selection(|_| Some(Default::default()));
5449        close_items.await.unwrap();
5450
5451        // The requested items are closed.
5452        pane.update(cx, |pane, cx| {
5453            assert_eq!(item4.read(cx).save_count, 0);
5454            assert_eq!(item4.read(cx).save_as_count, 1);
5455            assert_eq!(item4.read(cx).reload_count, 0);
5456            assert_eq!(pane.items_len(), 1);
5457            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5458        });
5459    }
5460
5461    #[gpui::test]
5462    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
5463        init_test(cx);
5464
5465        let fs = FakeFs::new(cx.executor());
5466        let project = Project::test(fs, [], cx).await;
5467        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5468
5469        // Create several workspace items with single project entries, and two
5470        // workspace items with multiple project entries.
5471        let single_entry_items = (0..=4)
5472            .map(|project_entry_id| {
5473                cx.new_view(|cx| {
5474                    TestItem::new(cx)
5475                        .with_dirty(true)
5476                        .with_project_items(&[TestProjectItem::new(
5477                            project_entry_id,
5478                            &format!("{project_entry_id}.txt"),
5479                            cx,
5480                        )])
5481                })
5482            })
5483            .collect::<Vec<_>>();
5484        let item_2_3 = cx.new_view(|cx| {
5485            TestItem::new(cx)
5486                .with_dirty(true)
5487                .with_singleton(false)
5488                .with_project_items(&[
5489                    single_entry_items[2].read(cx).project_items[0].clone(),
5490                    single_entry_items[3].read(cx).project_items[0].clone(),
5491                ])
5492        });
5493        let item_3_4 = cx.new_view(|cx| {
5494            TestItem::new(cx)
5495                .with_dirty(true)
5496                .with_singleton(false)
5497                .with_project_items(&[
5498                    single_entry_items[3].read(cx).project_items[0].clone(),
5499                    single_entry_items[4].read(cx).project_items[0].clone(),
5500                ])
5501        });
5502
5503        // Create two panes that contain the following project entries:
5504        //   left pane:
5505        //     multi-entry items:   (2, 3)
5506        //     single-entry items:  0, 1, 2, 3, 4
5507        //   right pane:
5508        //     single-entry items:  1
5509        //     multi-entry items:   (3, 4)
5510        let left_pane = workspace.update(cx, |workspace, cx| {
5511            let left_pane = workspace.active_pane().clone();
5512            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, cx);
5513            for item in single_entry_items {
5514                workspace.add_item_to_active_pane(Box::new(item), None, cx);
5515            }
5516            left_pane.update(cx, |pane, cx| {
5517                pane.activate_item(2, true, true, cx);
5518            });
5519
5520            let right_pane = workspace
5521                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
5522                .unwrap();
5523
5524            right_pane.update(cx, |pane, cx| {
5525                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
5526            });
5527
5528            left_pane
5529        });
5530
5531        cx.focus_view(&left_pane);
5532
5533        // When closing all of the items in the left pane, we should be prompted twice:
5534        // once for project entry 0, and once for project entry 2. Project entries 1,
5535        // 3, and 4 are all still open in the other paten. After those two
5536        // prompts, the task should complete.
5537
5538        let close = left_pane.update(cx, |pane, cx| {
5539            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
5540        });
5541        cx.executor().run_until_parked();
5542
5543        // Discard "Save all" prompt
5544        cx.simulate_prompt_answer(2);
5545
5546        cx.executor().run_until_parked();
5547        left_pane.update(cx, |pane, cx| {
5548            assert_eq!(
5549                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5550                &[ProjectEntryId::from_proto(0)]
5551            );
5552        });
5553        cx.simulate_prompt_answer(0);
5554
5555        cx.executor().run_until_parked();
5556        left_pane.update(cx, |pane, cx| {
5557            assert_eq!(
5558                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5559                &[ProjectEntryId::from_proto(2)]
5560            );
5561        });
5562        cx.simulate_prompt_answer(0);
5563
5564        cx.executor().run_until_parked();
5565        close.await.unwrap();
5566        left_pane.update(cx, |pane, _| {
5567            assert_eq!(pane.items_len(), 0);
5568        });
5569    }
5570
5571    #[gpui::test]
5572    async fn test_autosave(cx: &mut gpui::TestAppContext) {
5573        init_test(cx);
5574
5575        let fs = FakeFs::new(cx.executor());
5576        let project = Project::test(fs, [], cx).await;
5577        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5578        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5579
5580        let item = cx.new_view(|cx| {
5581            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5582        });
5583        let item_id = item.entity_id();
5584        workspace.update(cx, |workspace, cx| {
5585            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
5586        });
5587
5588        // Autosave on window change.
5589        item.update(cx, |item, cx| {
5590            cx.update_global(|settings: &mut SettingsStore, cx| {
5591                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5592                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
5593                })
5594            });
5595            item.is_dirty = true;
5596        });
5597
5598        // Deactivating the window saves the file.
5599        cx.deactivate_window();
5600        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5601
5602        // Re-activating the window doesn't save the file.
5603        cx.update(|cx| cx.activate_window());
5604        cx.executor().run_until_parked();
5605        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5606
5607        // Autosave on focus change.
5608        item.update(cx, |item, cx| {
5609            cx.focus_self();
5610            cx.update_global(|settings: &mut SettingsStore, cx| {
5611                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5612                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5613                })
5614            });
5615            item.is_dirty = true;
5616        });
5617
5618        // Blurring the item saves the file.
5619        item.update(cx, |_, cx| cx.blur());
5620        cx.executor().run_until_parked();
5621        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
5622
5623        // Deactivating the window still saves the file.
5624        item.update(cx, |item, cx| {
5625            cx.focus_self();
5626            item.is_dirty = true;
5627        });
5628        cx.deactivate_window();
5629        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5630
5631        // Autosave after delay.
5632        item.update(cx, |item, cx| {
5633            cx.update_global(|settings: &mut SettingsStore, cx| {
5634                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5635                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5636                })
5637            });
5638            item.is_dirty = true;
5639            cx.emit(ItemEvent::Edit);
5640        });
5641
5642        // Delay hasn't fully expired, so the file is still dirty and unsaved.
5643        cx.executor().advance_clock(Duration::from_millis(250));
5644        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5645
5646        // After delay expires, the file is saved.
5647        cx.executor().advance_clock(Duration::from_millis(250));
5648        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
5649
5650        // Autosave on focus change, ensuring closing the tab counts as such.
5651        item.update(cx, |item, cx| {
5652            cx.update_global(|settings: &mut SettingsStore, cx| {
5653                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5654                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5655                })
5656            });
5657            item.is_dirty = true;
5658        });
5659
5660        pane.update(cx, |pane, cx| {
5661            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5662        })
5663        .await
5664        .unwrap();
5665        assert!(!cx.has_pending_prompt());
5666        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5667
5668        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5669        workspace.update(cx, |workspace, cx| {
5670            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
5671        });
5672        item.update(cx, |item, cx| {
5673            item.project_items[0].update(cx, |item, _| {
5674                item.entry_id = None;
5675            });
5676            item.is_dirty = true;
5677            cx.blur();
5678        });
5679        cx.run_until_parked();
5680        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5681
5682        // Ensure autosave is prevented for deleted files also when closing the buffer.
5683        let _close_items = pane.update(cx, |pane, cx| {
5684            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5685        });
5686        cx.run_until_parked();
5687        assert!(cx.has_pending_prompt());
5688        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5689    }
5690
5691    #[gpui::test]
5692    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5693        init_test(cx);
5694
5695        let fs = FakeFs::new(cx.executor());
5696
5697        let project = Project::test(fs, [], cx).await;
5698        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5699
5700        let item = cx.new_view(|cx| {
5701            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5702        });
5703        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5704        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
5705        let toolbar_notify_count = Rc::new(RefCell::new(0));
5706
5707        workspace.update(cx, |workspace, cx| {
5708            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
5709            let toolbar_notification_count = toolbar_notify_count.clone();
5710            cx.observe(&toolbar, move |_, _, _| {
5711                *toolbar_notification_count.borrow_mut() += 1
5712            })
5713            .detach();
5714        });
5715
5716        pane.update(cx, |pane, _| {
5717            assert!(!pane.can_navigate_backward());
5718            assert!(!pane.can_navigate_forward());
5719        });
5720
5721        item.update(cx, |item, cx| {
5722            item.set_state("one".to_string(), cx);
5723        });
5724
5725        // Toolbar must be notified to re-render the navigation buttons
5726        assert_eq!(*toolbar_notify_count.borrow(), 1);
5727
5728        pane.update(cx, |pane, _| {
5729            assert!(pane.can_navigate_backward());
5730            assert!(!pane.can_navigate_forward());
5731        });
5732
5733        workspace
5734            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5735            .await
5736            .unwrap();
5737
5738        assert_eq!(*toolbar_notify_count.borrow(), 2);
5739        pane.update(cx, |pane, _| {
5740            assert!(!pane.can_navigate_backward());
5741            assert!(pane.can_navigate_forward());
5742        });
5743    }
5744
5745    #[gpui::test]
5746    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5747        init_test(cx);
5748        let fs = FakeFs::new(cx.executor());
5749
5750        let project = Project::test(fs, [], cx).await;
5751        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5752
5753        let panel = workspace.update(cx, |workspace, cx| {
5754            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5755            workspace.add_panel(panel.clone(), cx);
5756
5757            workspace
5758                .right_dock()
5759                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5760
5761            panel
5762        });
5763
5764        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5765        pane.update(cx, |pane, cx| {
5766            let item = cx.new_view(|cx| TestItem::new(cx));
5767            pane.add_item(Box::new(item), true, true, None, cx);
5768        });
5769
5770        // Transfer focus from center to panel
5771        workspace.update(cx, |workspace, cx| {
5772            workspace.toggle_panel_focus::<TestPanel>(cx);
5773        });
5774
5775        workspace.update(cx, |workspace, cx| {
5776            assert!(workspace.right_dock().read(cx).is_open());
5777            assert!(!panel.is_zoomed(cx));
5778            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5779        });
5780
5781        // Transfer focus from panel to center
5782        workspace.update(cx, |workspace, cx| {
5783            workspace.toggle_panel_focus::<TestPanel>(cx);
5784        });
5785
5786        workspace.update(cx, |workspace, cx| {
5787            assert!(workspace.right_dock().read(cx).is_open());
5788            assert!(!panel.is_zoomed(cx));
5789            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5790        });
5791
5792        // Close the dock
5793        workspace.update(cx, |workspace, cx| {
5794            workspace.toggle_dock(DockPosition::Right, cx);
5795        });
5796
5797        workspace.update(cx, |workspace, cx| {
5798            assert!(!workspace.right_dock().read(cx).is_open());
5799            assert!(!panel.is_zoomed(cx));
5800            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5801        });
5802
5803        // Open the dock
5804        workspace.update(cx, |workspace, cx| {
5805            workspace.toggle_dock(DockPosition::Right, cx);
5806        });
5807
5808        workspace.update(cx, |workspace, cx| {
5809            assert!(workspace.right_dock().read(cx).is_open());
5810            assert!(!panel.is_zoomed(cx));
5811            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5812        });
5813
5814        // Focus and zoom panel
5815        panel.update(cx, |panel, cx| {
5816            cx.focus_self();
5817            panel.set_zoomed(true, cx)
5818        });
5819
5820        workspace.update(cx, |workspace, cx| {
5821            assert!(workspace.right_dock().read(cx).is_open());
5822            assert!(panel.is_zoomed(cx));
5823            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5824        });
5825
5826        // Transfer focus to the center closes the dock
5827        workspace.update(cx, |workspace, cx| {
5828            workspace.toggle_panel_focus::<TestPanel>(cx);
5829        });
5830
5831        workspace.update(cx, |workspace, cx| {
5832            assert!(!workspace.right_dock().read(cx).is_open());
5833            assert!(panel.is_zoomed(cx));
5834            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5835        });
5836
5837        // Transferring focus back to the panel keeps it zoomed
5838        workspace.update(cx, |workspace, cx| {
5839            workspace.toggle_panel_focus::<TestPanel>(cx);
5840        });
5841
5842        workspace.update(cx, |workspace, cx| {
5843            assert!(workspace.right_dock().read(cx).is_open());
5844            assert!(panel.is_zoomed(cx));
5845            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5846        });
5847
5848        // Close the dock while it is zoomed
5849        workspace.update(cx, |workspace, cx| {
5850            workspace.toggle_dock(DockPosition::Right, cx)
5851        });
5852
5853        workspace.update(cx, |workspace, cx| {
5854            assert!(!workspace.right_dock().read(cx).is_open());
5855            assert!(panel.is_zoomed(cx));
5856            assert!(workspace.zoomed.is_none());
5857            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5858        });
5859
5860        // Opening the dock, when it's zoomed, retains focus
5861        workspace.update(cx, |workspace, cx| {
5862            workspace.toggle_dock(DockPosition::Right, cx)
5863        });
5864
5865        workspace.update(cx, |workspace, cx| {
5866            assert!(workspace.right_dock().read(cx).is_open());
5867            assert!(panel.is_zoomed(cx));
5868            assert!(workspace.zoomed.is_some());
5869            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5870        });
5871
5872        // Unzoom and close the panel, zoom the active pane.
5873        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5874        workspace.update(cx, |workspace, cx| {
5875            workspace.toggle_dock(DockPosition::Right, cx)
5876        });
5877        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5878
5879        // Opening a dock unzooms the pane.
5880        workspace.update(cx, |workspace, cx| {
5881            workspace.toggle_dock(DockPosition::Right, cx)
5882        });
5883        workspace.update(cx, |workspace, cx| {
5884            let pane = pane.read(cx);
5885            assert!(!pane.is_zoomed());
5886            assert!(!pane.focus_handle(cx).is_focused(cx));
5887            assert!(workspace.right_dock().read(cx).is_open());
5888            assert!(workspace.zoomed.is_none());
5889        });
5890    }
5891
5892    struct TestModal(FocusHandle);
5893
5894    impl TestModal {
5895        fn new(cx: &mut ViewContext<Self>) -> Self {
5896            Self(cx.focus_handle())
5897        }
5898    }
5899
5900    impl EventEmitter<DismissEvent> for TestModal {}
5901
5902    impl FocusableView for TestModal {
5903        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5904            self.0.clone()
5905        }
5906    }
5907
5908    impl ModalView for TestModal {}
5909
5910    impl Render for TestModal {
5911        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5912            div().track_focus(&self.0)
5913        }
5914    }
5915
5916    #[gpui::test]
5917    async fn test_panels(cx: &mut gpui::TestAppContext) {
5918        init_test(cx);
5919        let fs = FakeFs::new(cx.executor());
5920
5921        let project = Project::test(fs, [], cx).await;
5922        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5923
5924        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5925            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5926            workspace.add_panel(panel_1.clone(), cx);
5927            workspace
5928                .left_dock()
5929                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5930            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5931            workspace.add_panel(panel_2.clone(), cx);
5932            workspace
5933                .right_dock()
5934                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5935
5936            let left_dock = workspace.left_dock();
5937            assert_eq!(
5938                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5939                panel_1.panel_id()
5940            );
5941            assert_eq!(
5942                left_dock.read(cx).active_panel_size(cx).unwrap(),
5943                panel_1.size(cx)
5944            );
5945
5946            left_dock.update(cx, |left_dock, cx| {
5947                left_dock.resize_active_panel(Some(px(1337.)), cx)
5948            });
5949            assert_eq!(
5950                workspace
5951                    .right_dock()
5952                    .read(cx)
5953                    .visible_panel()
5954                    .unwrap()
5955                    .panel_id(),
5956                panel_2.panel_id(),
5957            );
5958
5959            (panel_1, panel_2)
5960        });
5961
5962        // Move panel_1 to the right
5963        panel_1.update(cx, |panel_1, cx| {
5964            panel_1.set_position(DockPosition::Right, cx)
5965        });
5966
5967        workspace.update(cx, |workspace, cx| {
5968            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5969            // Since it was the only panel on the left, the left dock should now be closed.
5970            assert!(!workspace.left_dock().read(cx).is_open());
5971            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5972            let right_dock = workspace.right_dock();
5973            assert_eq!(
5974                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5975                panel_1.panel_id()
5976            );
5977            assert_eq!(
5978                right_dock.read(cx).active_panel_size(cx).unwrap(),
5979                px(1337.)
5980            );
5981
5982            // Now we move panel_2 to the left
5983            panel_2.set_position(DockPosition::Left, cx);
5984        });
5985
5986        workspace.update(cx, |workspace, cx| {
5987            // Since panel_2 was not visible on the right, we don't open the left dock.
5988            assert!(!workspace.left_dock().read(cx).is_open());
5989            // And the right dock is unaffected in its displaying of panel_1
5990            assert!(workspace.right_dock().read(cx).is_open());
5991            assert_eq!(
5992                workspace
5993                    .right_dock()
5994                    .read(cx)
5995                    .visible_panel()
5996                    .unwrap()
5997                    .panel_id(),
5998                panel_1.panel_id(),
5999            );
6000        });
6001
6002        // Move panel_1 back to the left
6003        panel_1.update(cx, |panel_1, cx| {
6004            panel_1.set_position(DockPosition::Left, cx)
6005        });
6006
6007        workspace.update(cx, |workspace, cx| {
6008            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
6009            let left_dock = workspace.left_dock();
6010            assert!(left_dock.read(cx).is_open());
6011            assert_eq!(
6012                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6013                panel_1.panel_id()
6014            );
6015            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
6016            // And the right dock should be closed as it no longer has any panels.
6017            assert!(!workspace.right_dock().read(cx).is_open());
6018
6019            // Now we move panel_1 to the bottom
6020            panel_1.set_position(DockPosition::Bottom, cx);
6021        });
6022
6023        workspace.update(cx, |workspace, cx| {
6024            // Since panel_1 was visible on the left, we close the left dock.
6025            assert!(!workspace.left_dock().read(cx).is_open());
6026            // The bottom dock is sized based on the panel's default size,
6027            // since the panel orientation changed from vertical to horizontal.
6028            let bottom_dock = workspace.bottom_dock();
6029            assert_eq!(
6030                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
6031                panel_1.size(cx),
6032            );
6033            // Close bottom dock and move panel_1 back to the left.
6034            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
6035            panel_1.set_position(DockPosition::Left, cx);
6036        });
6037
6038        // Emit activated event on panel 1
6039        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
6040
6041        // Now the left dock is open and panel_1 is active and focused.
6042        workspace.update(cx, |workspace, cx| {
6043            let left_dock = workspace.left_dock();
6044            assert!(left_dock.read(cx).is_open());
6045            assert_eq!(
6046                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6047                panel_1.panel_id(),
6048            );
6049            assert!(panel_1.focus_handle(cx).is_focused(cx));
6050        });
6051
6052        // Emit closed event on panel 2, which is not active
6053        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
6054
6055        // Wo don't close the left dock, because panel_2 wasn't the active panel
6056        workspace.update(cx, |workspace, cx| {
6057            let left_dock = workspace.left_dock();
6058            assert!(left_dock.read(cx).is_open());
6059            assert_eq!(
6060                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6061                panel_1.panel_id(),
6062            );
6063        });
6064
6065        // Emitting a ZoomIn event shows the panel as zoomed.
6066        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
6067        workspace.update(cx, |workspace, _| {
6068            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6069            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
6070        });
6071
6072        // Move panel to another dock while it is zoomed
6073        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
6074        workspace.update(cx, |workspace, _| {
6075            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6076
6077            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6078        });
6079
6080        // This is a helper for getting a:
6081        // - valid focus on an element,
6082        // - that isn't a part of the panes and panels system of the Workspace,
6083        // - and doesn't trigger the 'on_focus_lost' API.
6084        let focus_other_view = {
6085            let workspace = workspace.clone();
6086            move |cx: &mut VisualTestContext| {
6087                workspace.update(cx, |workspace, cx| {
6088                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
6089                        workspace.toggle_modal(cx, TestModal::new);
6090                        workspace.toggle_modal(cx, TestModal::new);
6091                    } else {
6092                        workspace.toggle_modal(cx, TestModal::new);
6093                    }
6094                })
6095            }
6096        };
6097
6098        // If focus is transferred to another view that's not a panel or another pane, we still show
6099        // the panel as zoomed.
6100        focus_other_view(cx);
6101        workspace.update(cx, |workspace, _| {
6102            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6103            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6104        });
6105
6106        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
6107        workspace.update(cx, |_, cx| cx.focus_self());
6108        workspace.update(cx, |workspace, _| {
6109            assert_eq!(workspace.zoomed, None);
6110            assert_eq!(workspace.zoomed_position, None);
6111        });
6112
6113        // If focus is transferred again to another view that's not a panel or a pane, we won't
6114        // show the panel as zoomed because it wasn't zoomed before.
6115        focus_other_view(cx);
6116        workspace.update(cx, |workspace, _| {
6117            assert_eq!(workspace.zoomed, None);
6118            assert_eq!(workspace.zoomed_position, None);
6119        });
6120
6121        // When the panel is activated, it is zoomed again.
6122        cx.dispatch_action(ToggleRightDock);
6123        workspace.update(cx, |workspace, _| {
6124            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6125            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6126        });
6127
6128        // Emitting a ZoomOut event unzooms the panel.
6129        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
6130        workspace.update(cx, |workspace, _| {
6131            assert_eq!(workspace.zoomed, None);
6132            assert_eq!(workspace.zoomed_position, None);
6133        });
6134
6135        // Emit closed event on panel 1, which is active
6136        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
6137
6138        // Now the left dock is closed, because panel_1 was the active panel
6139        workspace.update(cx, |workspace, cx| {
6140            let right_dock = workspace.right_dock();
6141            assert!(!right_dock.read(cx).is_open());
6142        });
6143    }
6144
6145    mod register_project_item_tests {
6146        use ui::Context as _;
6147
6148        use super::*;
6149
6150        const TEST_PNG_KIND: &str = "TestPngItemView";
6151        // View
6152        struct TestPngItemView {
6153            focus_handle: FocusHandle,
6154        }
6155        // Model
6156        struct TestPngItem {}
6157
6158        impl project::Item for TestPngItem {
6159            fn try_open(
6160                _project: &Model<Project>,
6161                path: &ProjectPath,
6162                cx: &mut AppContext,
6163            ) -> Option<Task<gpui::Result<Model<Self>>>> {
6164                if path.path.extension().unwrap() == "png" {
6165                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestPngItem {}) }))
6166                } else {
6167                    None
6168                }
6169            }
6170
6171            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
6172                None
6173            }
6174
6175            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
6176                None
6177            }
6178        }
6179
6180        impl Item for TestPngItemView {
6181            type Event = ();
6182
6183            fn serialized_item_kind() -> Option<&'static str> {
6184                Some(TEST_PNG_KIND)
6185            }
6186        }
6187        impl EventEmitter<()> for TestPngItemView {}
6188        impl FocusableView for TestPngItemView {
6189            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6190                self.focus_handle.clone()
6191            }
6192        }
6193
6194        impl Render for TestPngItemView {
6195            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6196                Empty
6197            }
6198        }
6199
6200        impl ProjectItem for TestPngItemView {
6201            type Item = TestPngItem;
6202
6203            fn for_project_item(
6204                _project: Model<Project>,
6205                _item: Model<Self::Item>,
6206                cx: &mut ViewContext<Self>,
6207            ) -> Self
6208            where
6209                Self: Sized,
6210            {
6211                Self {
6212                    focus_handle: cx.focus_handle(),
6213                }
6214            }
6215        }
6216
6217        const TEST_IPYNB_KIND: &str = "TestIpynbItemView";
6218        // View
6219        struct TestIpynbItemView {
6220            focus_handle: FocusHandle,
6221        }
6222        // Model
6223        struct TestIpynbItem {}
6224
6225        impl project::Item for TestIpynbItem {
6226            fn try_open(
6227                _project: &Model<Project>,
6228                path: &ProjectPath,
6229                cx: &mut AppContext,
6230            ) -> Option<Task<gpui::Result<Model<Self>>>> {
6231                if path.path.extension().unwrap() == "ipynb" {
6232                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestIpynbItem {}) }))
6233                } else {
6234                    None
6235                }
6236            }
6237
6238            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
6239                None
6240            }
6241
6242            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
6243                None
6244            }
6245        }
6246
6247        impl Item for TestIpynbItemView {
6248            type Event = ();
6249
6250            fn serialized_item_kind() -> Option<&'static str> {
6251                Some(TEST_IPYNB_KIND)
6252            }
6253        }
6254        impl EventEmitter<()> for TestIpynbItemView {}
6255        impl FocusableView for TestIpynbItemView {
6256            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6257                self.focus_handle.clone()
6258            }
6259        }
6260
6261        impl Render for TestIpynbItemView {
6262            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6263                Empty
6264            }
6265        }
6266
6267        impl ProjectItem for TestIpynbItemView {
6268            type Item = TestIpynbItem;
6269
6270            fn for_project_item(
6271                _project: Model<Project>,
6272                _item: Model<Self::Item>,
6273                cx: &mut ViewContext<Self>,
6274            ) -> Self
6275            where
6276                Self: Sized,
6277            {
6278                Self {
6279                    focus_handle: cx.focus_handle(),
6280                }
6281            }
6282        }
6283
6284        struct TestAlternatePngItemView {
6285            focus_handle: FocusHandle,
6286        }
6287
6288        const TEST_ALTERNATE_PNG_KIND: &str = "TestAlternatePngItemView";
6289        impl Item for TestAlternatePngItemView {
6290            type Event = ();
6291
6292            fn serialized_item_kind() -> Option<&'static str> {
6293                Some(TEST_ALTERNATE_PNG_KIND)
6294            }
6295        }
6296        impl EventEmitter<()> for TestAlternatePngItemView {}
6297        impl FocusableView for TestAlternatePngItemView {
6298            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6299                self.focus_handle.clone()
6300            }
6301        }
6302
6303        impl Render for TestAlternatePngItemView {
6304            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6305                Empty
6306            }
6307        }
6308
6309        impl ProjectItem for TestAlternatePngItemView {
6310            type Item = TestPngItem;
6311
6312            fn for_project_item(
6313                _project: Model<Project>,
6314                _item: Model<Self::Item>,
6315                cx: &mut ViewContext<Self>,
6316            ) -> Self
6317            where
6318                Self: Sized,
6319            {
6320                Self {
6321                    focus_handle: cx.focus_handle(),
6322                }
6323            }
6324        }
6325
6326        #[gpui::test]
6327        async fn test_register_project_item(cx: &mut TestAppContext) {
6328            init_test(cx);
6329
6330            cx.update(|cx| {
6331                register_project_item::<TestPngItemView>(cx);
6332                register_project_item::<TestIpynbItemView>(cx);
6333            });
6334
6335            let fs = FakeFs::new(cx.executor());
6336            fs.insert_tree(
6337                "/root1",
6338                json!({
6339                    "one.png": "BINARYDATAHERE",
6340                    "two.ipynb": "{ totally a notebook }",
6341                    "three.txt": "editing text, sure why not?"
6342                }),
6343            )
6344            .await;
6345
6346            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6347            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6348
6349            let worktree_id = project.update(cx, |project, cx| {
6350                project.worktrees().next().unwrap().read(cx).id()
6351            });
6352
6353            let handle = workspace
6354                .update(cx, |workspace, cx| {
6355                    let project_path = (worktree_id, "one.png");
6356                    workspace.open_path(project_path, None, true, cx)
6357                })
6358                .await
6359                .unwrap();
6360
6361            // Now we can check if the handle we got back errored or not
6362            assert_eq!(handle.serialized_item_kind().unwrap(), TEST_PNG_KIND);
6363
6364            let handle = workspace
6365                .update(cx, |workspace, cx| {
6366                    let project_path = (worktree_id, "two.ipynb");
6367                    workspace.open_path(project_path, None, true, cx)
6368                })
6369                .await
6370                .unwrap();
6371
6372            assert_eq!(handle.serialized_item_kind().unwrap(), TEST_IPYNB_KIND);
6373
6374            let handle = workspace
6375                .update(cx, |workspace, cx| {
6376                    let project_path = (worktree_id, "three.txt");
6377                    workspace.open_path(project_path, None, true, cx)
6378                })
6379                .await;
6380            assert!(handle.is_err());
6381        }
6382
6383        #[gpui::test]
6384        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
6385            init_test(cx);
6386
6387            cx.update(|cx| {
6388                register_project_item::<TestPngItemView>(cx);
6389                register_project_item::<TestAlternatePngItemView>(cx);
6390            });
6391
6392            let fs = FakeFs::new(cx.executor());
6393            fs.insert_tree(
6394                "/root1",
6395                json!({
6396                    "one.png": "BINARYDATAHERE",
6397                    "two.ipynb": "{ totally a notebook }",
6398                    "three.txt": "editing text, sure why not?"
6399                }),
6400            )
6401            .await;
6402
6403            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6404            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6405
6406            let worktree_id = project.update(cx, |project, cx| {
6407                project.worktrees().next().unwrap().read(cx).id()
6408            });
6409
6410            let handle = workspace
6411                .update(cx, |workspace, cx| {
6412                    let project_path = (worktree_id, "one.png");
6413                    workspace.open_path(project_path, None, true, cx)
6414                })
6415                .await
6416                .unwrap();
6417
6418            // This _must_ be the second item registered
6419            assert_eq!(
6420                handle.serialized_item_kind().unwrap(),
6421                TEST_ALTERNATE_PNG_KIND
6422            );
6423
6424            let handle = workspace
6425                .update(cx, |workspace, cx| {
6426                    let project_path = (worktree_id, "three.txt");
6427                    workspace.open_path(project_path, None, true, cx)
6428                })
6429                .await;
6430            assert!(handle.is_err());
6431        }
6432    }
6433
6434    pub fn init_test(cx: &mut TestAppContext) {
6435        cx.update(|cx| {
6436            let settings_store = SettingsStore::test(cx);
6437            cx.set_global(settings_store);
6438            theme::init(theme::LoadThemes::JustBase, cx);
6439            language::init(cx);
6440            crate::init_settings(cx);
6441            Project::init_settings(cx);
6442        });
6443    }
6444}