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