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                    for item in pane.items() {
3506                        if matches!(
3507                            item.workspace_settings(cx).autosave,
3508                            AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3509                        ) {
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        mut cx: AsyncAppContext,
4354    ) -> Result<proto::FollowResponse> {
4355        this.update(&mut cx, |this, cx| {
4356            let follower = Follower {
4357                project_id: envelope.payload.project_id,
4358                peer_id: envelope.original_sender_id()?,
4359            };
4360
4361            let mut response = proto::FollowResponse::default();
4362            this.workspaces.retain(|workspace| {
4363                workspace
4364                    .update(cx, |workspace, cx| {
4365                        let handler_response = workspace.handle_follow(follower.project_id, cx);
4366                        if response.views.is_empty() {
4367                            response.views = handler_response.views;
4368                        } else {
4369                            response.views.extend_from_slice(&handler_response.views);
4370                        }
4371
4372                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
4373                            if response.active_view_id.is_none()
4374                                || workspace.project.read(cx).remote_id() == follower.project_id
4375                            {
4376                                response.active_view_id = Some(active_view_id);
4377                            }
4378                        }
4379
4380                        if let Some(active_view) = handler_response.active_view.clone() {
4381                            if response.active_view_id.is_none()
4382                                || workspace.project.read(cx).remote_id() == follower.project_id
4383                            {
4384                                response.active_view = Some(active_view)
4385                            }
4386                        }
4387                    })
4388                    .is_ok()
4389            });
4390
4391            Ok(response)
4392        })?
4393    }
4394
4395    async fn handle_update_followers(
4396        this: Model<Self>,
4397        envelope: TypedEnvelope<proto::UpdateFollowers>,
4398        mut cx: AsyncAppContext,
4399    ) -> Result<()> {
4400        let leader_id = envelope.original_sender_id()?;
4401        let update = envelope.payload;
4402
4403        this.update(&mut cx, |this, cx| {
4404            this.workspaces.retain(|workspace| {
4405                workspace
4406                    .update(cx, |workspace, cx| {
4407                        let project_id = workspace.project.read(cx).remote_id();
4408                        if update.project_id != project_id && update.project_id.is_some() {
4409                            return;
4410                        }
4411                        workspace.handle_update_followers(leader_id, update.clone(), cx);
4412                    })
4413                    .is_ok()
4414            });
4415            Ok(())
4416        })?
4417    }
4418}
4419
4420impl ViewId {
4421    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4422        Ok(Self {
4423            creator: message
4424                .creator
4425                .ok_or_else(|| anyhow!("creator is missing"))?,
4426            id: message.id,
4427        })
4428    }
4429
4430    pub(crate) fn to_proto(&self) -> proto::ViewId {
4431        proto::ViewId {
4432            creator: Some(self.creator),
4433            id: self.id,
4434        }
4435    }
4436}
4437
4438pub trait WorkspaceHandle {
4439    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4440}
4441
4442impl WorkspaceHandle for View<Workspace> {
4443    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4444        self.read(cx)
4445            .worktrees(cx)
4446            .flat_map(|worktree| {
4447                let worktree_id = worktree.read(cx).id();
4448                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4449                    worktree_id,
4450                    path: f.path.clone(),
4451                })
4452            })
4453            .collect::<Vec<_>>()
4454    }
4455}
4456
4457impl std::fmt::Debug for OpenPaths {
4458    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4459        f.debug_struct("OpenPaths")
4460            .field("paths", &self.paths)
4461            .finish()
4462    }
4463}
4464
4465pub fn activate_workspace_for_project(
4466    cx: &mut AppContext,
4467    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4468) -> Option<WindowHandle<Workspace>> {
4469    for window in cx.windows() {
4470        let Some(workspace) = window.downcast::<Workspace>() else {
4471            continue;
4472        };
4473
4474        let predicate = workspace
4475            .update(cx, |workspace, cx| {
4476                let project = workspace.project.read(cx);
4477                if predicate(project, cx) {
4478                    cx.activate_window();
4479                    true
4480                } else {
4481                    false
4482                }
4483            })
4484            .log_err()
4485            .unwrap_or(false);
4486
4487        if predicate {
4488            return Some(workspace);
4489        }
4490    }
4491
4492    None
4493}
4494
4495pub async fn last_opened_workspace_paths() -> Option<LocalPaths> {
4496    DB.last_workspace().await.log_err().flatten()
4497}
4498
4499actions!(collab, [OpenChannelNotes]);
4500actions!(zed, [OpenLog]);
4501
4502async fn join_channel_internal(
4503    channel_id: ChannelId,
4504    app_state: &Arc<AppState>,
4505    requesting_window: Option<WindowHandle<Workspace>>,
4506    active_call: &Model<ActiveCall>,
4507    cx: &mut AsyncAppContext,
4508) -> Result<bool> {
4509    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4510        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4511            return (false, None);
4512        };
4513
4514        let already_in_channel = room.channel_id() == Some(channel_id);
4515        let should_prompt = room.is_sharing_project()
4516            && room.remote_participants().len() > 0
4517            && !already_in_channel;
4518        let open_room = if already_in_channel {
4519            active_call.room().cloned()
4520        } else {
4521            None
4522        };
4523        (should_prompt, open_room)
4524    })?;
4525
4526    if let Some(room) = open_room {
4527        let task = room.update(cx, |room, cx| {
4528            if let Some((project, host)) = room.most_active_project(cx) {
4529                return Some(join_in_room_project(project, host, app_state.clone(), cx));
4530            }
4531
4532            None
4533        })?;
4534        if let Some(task) = task {
4535            task.await?;
4536        }
4537        return anyhow::Ok(true);
4538    }
4539
4540    if should_prompt {
4541        if let Some(workspace) = requesting_window {
4542            let answer = workspace
4543                .update(cx, |_, cx| {
4544                    cx.prompt(
4545                        PromptLevel::Warning,
4546                        "Do you want to switch channels?",
4547                        Some("Leaving this call will unshare your current project."),
4548                        &["Yes, Join Channel", "Cancel"],
4549                    )
4550                })?
4551                .await;
4552
4553            if answer == Ok(1) {
4554                return Ok(false);
4555            }
4556        } else {
4557            return Ok(false); // unreachable!() hopefully
4558        }
4559    }
4560
4561    let client = cx.update(|cx| active_call.read(cx).client())?;
4562
4563    let mut client_status = client.status();
4564
4565    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4566    'outer: loop {
4567        let Some(status) = client_status.recv().await else {
4568            return Err(anyhow!("error connecting"));
4569        };
4570
4571        match status {
4572            Status::Connecting
4573            | Status::Authenticating
4574            | Status::Reconnecting
4575            | Status::Reauthenticating => continue,
4576            Status::Connected { .. } => break 'outer,
4577            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
4578            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
4579            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4580                return Err(ErrorCode::Disconnected.into());
4581            }
4582        }
4583    }
4584
4585    let room = active_call
4586        .update(cx, |active_call, cx| {
4587            active_call.join_channel(channel_id, cx)
4588        })?
4589        .await?;
4590
4591    let Some(room) = room else {
4592        return anyhow::Ok(true);
4593    };
4594
4595    room.update(cx, |room, _| room.room_update_completed())?
4596        .await;
4597
4598    let task = room.update(cx, |room, cx| {
4599        if let Some((project, host)) = room.most_active_project(cx) {
4600            return Some(join_in_room_project(project, host, app_state.clone(), cx));
4601        }
4602
4603        // If you are the first to join a channel, see if you should share your project.
4604        if room.remote_participants().is_empty() && !room.local_participant_is_guest() {
4605            if let Some(workspace) = requesting_window {
4606                let project = workspace.update(cx, |workspace, cx| {
4607                    let project = workspace.project.read(cx);
4608                    let is_dev_server = project.dev_server_project_id().is_some();
4609
4610                    if !is_dev_server && !CallSettings::get_global(cx).share_on_join {
4611                        return None;
4612                    }
4613
4614                    if (project.is_local() || is_dev_server)
4615                        && project.visible_worktrees(cx).any(|tree| {
4616                            tree.read(cx)
4617                                .root_entry()
4618                                .map_or(false, |entry| entry.is_dir())
4619                        })
4620                    {
4621                        Some(workspace.project.clone())
4622                    } else {
4623                        None
4624                    }
4625                });
4626                if let Ok(Some(project)) = project {
4627                    return Some(cx.spawn(|room, mut cx| async move {
4628                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
4629                            .await?;
4630                        Ok(())
4631                    }));
4632                }
4633            }
4634        }
4635
4636        None
4637    })?;
4638    if let Some(task) = task {
4639        task.await?;
4640        return anyhow::Ok(true);
4641    }
4642    anyhow::Ok(false)
4643}
4644
4645pub fn join_channel(
4646    channel_id: ChannelId,
4647    app_state: Arc<AppState>,
4648    requesting_window: Option<WindowHandle<Workspace>>,
4649    cx: &mut AppContext,
4650) -> Task<Result<()>> {
4651    let active_call = ActiveCall::global(cx);
4652    cx.spawn(|mut cx| async move {
4653        let result = join_channel_internal(
4654            channel_id,
4655            &app_state,
4656            requesting_window,
4657            &active_call,
4658            &mut cx,
4659        )
4660            .await;
4661
4662        // join channel succeeded, and opened a window
4663        if matches!(result, Ok(true)) {
4664            return anyhow::Ok(());
4665        }
4666
4667        // find an existing workspace to focus and show call controls
4668        let mut active_window =
4669            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4670        if active_window.is_none() {
4671            // no open workspaces, make one to show the error in (blergh)
4672            let (window_handle, _) = cx
4673                .update(|cx| {
4674                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4675                })?
4676                .await?;
4677
4678            if result.is_ok() {
4679                cx.update(|cx| {
4680                    cx.dispatch_action(&OpenChannelNotes);
4681                }).log_err();
4682            }
4683
4684            active_window = Some(window_handle);
4685        }
4686
4687        if let Err(err) = result {
4688            log::error!("failed to join channel: {}", err);
4689            if let Some(active_window) = active_window {
4690                active_window
4691                    .update(&mut cx, |_, cx| {
4692                        let detail: SharedString = match err.error_code() {
4693                            ErrorCode::SignedOut => {
4694                                "Please sign in to continue.".into()
4695                            }
4696                            ErrorCode::UpgradeRequired => {
4697                                "Your are running an unsupported version of Zed. Please update to continue.".into()
4698                            }
4699                            ErrorCode::NoSuchChannel => {
4700                                "No matching channel was found. Please check the link and try again.".into()
4701                            }
4702                            ErrorCode::Forbidden => {
4703                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
4704                            }
4705                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
4706                            _ => format!("{}\n\nPlease try again.", err).into(),
4707                        };
4708                        cx.prompt(
4709                            PromptLevel::Critical,
4710                            "Failed to join channel",
4711                            Some(&detail),
4712                            &["Ok"],
4713                        )
4714                    })?
4715                    .await
4716                    .ok();
4717            }
4718        }
4719
4720        // return ok, we showed the error to the user.
4721        return anyhow::Ok(());
4722    })
4723}
4724
4725pub async fn get_any_active_workspace(
4726    app_state: Arc<AppState>,
4727    mut cx: AsyncAppContext,
4728) -> anyhow::Result<WindowHandle<Workspace>> {
4729    // find an existing workspace to focus and show call controls
4730    let active_window = activate_any_workspace_window(&mut cx);
4731    if active_window.is_none() {
4732        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4733            .await?;
4734    }
4735    activate_any_workspace_window(&mut cx).context("could not open zed")
4736}
4737
4738fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4739    cx.update(|cx| {
4740        if let Some(workspace_window) = cx
4741            .active_window()
4742            .and_then(|window| window.downcast::<Workspace>())
4743        {
4744            return Some(workspace_window);
4745        }
4746
4747        for window in cx.windows() {
4748            if let Some(workspace_window) = window.downcast::<Workspace>() {
4749                workspace_window
4750                    .update(cx, |_, cx| cx.activate_window())
4751                    .ok();
4752                return Some(workspace_window);
4753            }
4754        }
4755        None
4756    })
4757    .ok()
4758    .flatten()
4759}
4760
4761fn local_workspace_windows(cx: &AppContext) -> Vec<WindowHandle<Workspace>> {
4762    cx.windows()
4763        .into_iter()
4764        .filter_map(|window| window.downcast::<Workspace>())
4765        .filter(|workspace| {
4766            workspace
4767                .read(cx)
4768                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
4769        })
4770        .collect()
4771}
4772
4773#[derive(Default)]
4774pub struct OpenOptions {
4775    pub open_new_workspace: Option<bool>,
4776    pub replace_window: Option<WindowHandle<Workspace>>,
4777}
4778
4779#[allow(clippy::type_complexity)]
4780pub fn open_paths(
4781    abs_paths: &[PathBuf],
4782    app_state: Arc<AppState>,
4783    open_options: OpenOptions,
4784    cx: &mut AppContext,
4785) -> Task<
4786    anyhow::Result<(
4787        WindowHandle<Workspace>,
4788        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4789    )>,
4790> {
4791    let abs_paths = abs_paths.to_vec();
4792    let mut existing = None;
4793    let mut best_match = None;
4794    let mut open_visible = OpenVisible::All;
4795
4796    if open_options.open_new_workspace != Some(true) {
4797        for window in local_workspace_windows(cx) {
4798            if let Ok(workspace) = window.read(cx) {
4799                let m = workspace
4800                    .project
4801                    .read(cx)
4802                    .visibility_for_paths(&abs_paths, cx);
4803                if m > best_match {
4804                    existing = Some(window);
4805                    best_match = m;
4806                } else if best_match.is_none() && open_options.open_new_workspace == Some(false) {
4807                    existing = Some(window)
4808                }
4809            }
4810        }
4811    }
4812
4813    cx.spawn(move |mut cx| async move {
4814        if open_options.open_new_workspace.is_none() && existing.is_none() {
4815            let all_files = abs_paths.iter().map(|path| app_state.fs.metadata(path));
4816            if futures::future::join_all(all_files)
4817                .await
4818                .into_iter()
4819                .filter_map(|result| result.ok().flatten())
4820                .all(|file| !file.is_dir)
4821            {
4822                cx.update(|cx| {
4823                    for window in local_workspace_windows(cx) {
4824                        if let Ok(workspace) = window.read(cx) {
4825                            let project = workspace.project().read(cx);
4826                            if project.is_remote() {
4827                                continue;
4828                            }
4829                            existing = Some(window);
4830                            open_visible = OpenVisible::None;
4831                            break;
4832                        }
4833                    }
4834                })?;
4835            }
4836        }
4837
4838        if let Some(existing) = existing {
4839            Ok((
4840                existing,
4841                existing
4842                    .update(&mut cx, |workspace, cx| {
4843                        cx.activate_window();
4844                        workspace.open_paths(abs_paths, open_visible, None, cx)
4845                    })?
4846                    .await,
4847            ))
4848        } else {
4849            cx.update(move |cx| {
4850                Workspace::new_local(
4851                    abs_paths,
4852                    app_state.clone(),
4853                    open_options.replace_window,
4854                    cx,
4855                )
4856            })?
4857            .await
4858        }
4859    })
4860}
4861
4862pub fn open_new(
4863    app_state: Arc<AppState>,
4864    cx: &mut AppContext,
4865    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4866) -> Task<anyhow::Result<()>> {
4867    let task = Workspace::new_local(Vec::new(), app_state, None, cx);
4868    cx.spawn(|mut cx| async move {
4869        let (workspace, opened_paths) = task.await?;
4870        workspace.update(&mut cx, |workspace, cx| {
4871            if opened_paths.is_empty() {
4872                init(workspace, cx)
4873            }
4874        })?;
4875        Ok(())
4876    })
4877}
4878
4879pub fn create_and_open_local_file(
4880    path: &'static Path,
4881    cx: &mut ViewContext<Workspace>,
4882    default_content: impl 'static + Send + FnOnce() -> Rope,
4883) -> Task<Result<Box<dyn ItemHandle>>> {
4884    cx.spawn(|workspace, mut cx| async move {
4885        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4886        if !fs.is_file(path).await {
4887            fs.create_file(path, Default::default()).await?;
4888            fs.save(path, &default_content(), Default::default())
4889                .await?;
4890        }
4891
4892        let mut items = workspace
4893            .update(&mut cx, |workspace, cx| {
4894                workspace.with_local_workspace(cx, |workspace, cx| {
4895                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4896                })
4897            })?
4898            .await?
4899            .await;
4900
4901        let item = items.pop().flatten();
4902        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4903    })
4904}
4905
4906pub fn join_hosted_project(
4907    hosted_project_id: ProjectId,
4908    app_state: Arc<AppState>,
4909    cx: &mut AppContext,
4910) -> Task<Result<()>> {
4911    cx.spawn(|mut cx| async move {
4912        let existing_window = cx.update(|cx| {
4913            cx.windows().into_iter().find_map(|window| {
4914                let workspace = window.downcast::<Workspace>()?;
4915                workspace
4916                    .read(cx)
4917                    .is_ok_and(|workspace| {
4918                        workspace.project().read(cx).hosted_project_id() == Some(hosted_project_id)
4919                    })
4920                    .then(|| workspace)
4921            })
4922        })?;
4923
4924        let workspace = if let Some(existing_window) = existing_window {
4925            existing_window
4926        } else {
4927            let project = Project::hosted(
4928                hosted_project_id,
4929                app_state.user_store.clone(),
4930                app_state.client.clone(),
4931                app_state.languages.clone(),
4932                app_state.fs.clone(),
4933                cx.clone(),
4934            )
4935            .await?;
4936
4937            let window_bounds_override = window_bounds_env_override();
4938            cx.update(|cx| {
4939                let mut options = (app_state.build_window_options)(None, cx);
4940                options.window_bounds =
4941                    window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
4942                cx.open_window(options, |cx| {
4943                    cx.new_view(|cx| {
4944                        Workspace::new(Default::default(), project, app_state.clone(), cx)
4945                    })
4946                })
4947            })??
4948        };
4949
4950        workspace.update(&mut cx, |_, cx| {
4951            cx.activate(true);
4952            cx.activate_window();
4953        })?;
4954
4955        Ok(())
4956    })
4957}
4958
4959pub fn join_dev_server_project(
4960    dev_server_project_id: DevServerProjectId,
4961    project_id: ProjectId,
4962    app_state: Arc<AppState>,
4963    window_to_replace: Option<WindowHandle<Workspace>>,
4964    cx: &mut AppContext,
4965) -> Task<Result<WindowHandle<Workspace>>> {
4966    let windows = cx.windows();
4967    cx.spawn(|mut cx| async move {
4968        let existing_workspace = windows.into_iter().find_map(|window| {
4969            window.downcast::<Workspace>().and_then(|window| {
4970                window
4971                    .update(&mut cx, |workspace, cx| {
4972                        if workspace.project().read(cx).remote_id() == Some(project_id.0) {
4973                            Some(window)
4974                        } else {
4975                            None
4976                        }
4977                    })
4978                    .unwrap_or(None)
4979            })
4980        });
4981
4982        let workspace = if let Some(existing_workspace) = existing_workspace {
4983            existing_workspace
4984        } else {
4985            let project = Project::remote(
4986                project_id.0,
4987                app_state.client.clone(),
4988                app_state.user_store.clone(),
4989                app_state.languages.clone(),
4990                app_state.fs.clone(),
4991                cx.clone(),
4992            )
4993            .await?;
4994
4995            let serialized_workspace: Option<SerializedWorkspace> =
4996                persistence::DB.workspace_for_dev_server_project(dev_server_project_id);
4997
4998            let workspace_id = if let Some(serialized_workspace) = serialized_workspace {
4999                serialized_workspace.id
5000            } else {
5001                persistence::DB.next_id().await?
5002            };
5003
5004            if let Some(window_to_replace) = window_to_replace {
5005                cx.update_window(window_to_replace.into(), |_, cx| {
5006                    cx.replace_root_view(|cx| {
5007                        Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
5008                    });
5009                })?;
5010                window_to_replace
5011            } else {
5012                let window_bounds_override = window_bounds_env_override();
5013                cx.update(|cx| {
5014                    let mut options = (app_state.build_window_options)(None, cx);
5015                    options.window_bounds =
5016                        window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
5017                    cx.open_window(options, |cx| {
5018                        cx.new_view(|cx| {
5019                            Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
5020                        })
5021                    })
5022                })??
5023            }
5024        };
5025
5026        workspace.update(&mut cx, |_, cx| {
5027            cx.activate(true);
5028            cx.activate_window();
5029        })?;
5030
5031        anyhow::Ok(workspace)
5032    })
5033}
5034
5035pub fn join_in_room_project(
5036    project_id: u64,
5037    follow_user_id: u64,
5038    app_state: Arc<AppState>,
5039    cx: &mut AppContext,
5040) -> Task<Result<()>> {
5041    let windows = cx.windows();
5042    cx.spawn(|mut cx| async move {
5043        let existing_workspace = windows.into_iter().find_map(|window| {
5044            window.downcast::<Workspace>().and_then(|window| {
5045                window
5046                    .update(&mut cx, |workspace, cx| {
5047                        if workspace.project().read(cx).remote_id() == Some(project_id) {
5048                            Some(window)
5049                        } else {
5050                            None
5051                        }
5052                    })
5053                    .unwrap_or(None)
5054            })
5055        });
5056
5057        let workspace = if let Some(existing_workspace) = existing_workspace {
5058            existing_workspace
5059        } else {
5060            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
5061            let room = active_call
5062                .read_with(&cx, |call, _| call.room().cloned())?
5063                .ok_or_else(|| anyhow!("not in a call"))?;
5064            let project = room
5065                .update(&mut cx, |room, cx| {
5066                    room.join_project(
5067                        project_id,
5068                        app_state.languages.clone(),
5069                        app_state.fs.clone(),
5070                        cx,
5071                    )
5072                })?
5073                .await?;
5074
5075            let window_bounds_override = window_bounds_env_override();
5076            cx.update(|cx| {
5077                let mut options = (app_state.build_window_options)(None, cx);
5078                options.window_bounds =
5079                    window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
5080                cx.open_window(options, |cx| {
5081                    cx.new_view(|cx| {
5082                        Workspace::new(Default::default(), project, app_state.clone(), cx)
5083                    })
5084                })
5085            })??
5086        };
5087
5088        workspace.update(&mut cx, |workspace, cx| {
5089            cx.activate(true);
5090            cx.activate_window();
5091
5092            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
5093                let follow_peer_id = room
5094                    .read(cx)
5095                    .remote_participants()
5096                    .iter()
5097                    .find(|(_, participant)| participant.user.id == follow_user_id)
5098                    .map(|(_, p)| p.peer_id)
5099                    .or_else(|| {
5100                        // If we couldn't follow the given user, follow the host instead.
5101                        let collaborator = workspace
5102                            .project()
5103                            .read(cx)
5104                            .collaborators()
5105                            .values()
5106                            .find(|collaborator| collaborator.replica_id == 0)?;
5107                        Some(collaborator.peer_id)
5108                    });
5109
5110                if let Some(follow_peer_id) = follow_peer_id {
5111                    workspace.follow(follow_peer_id, cx);
5112                }
5113            }
5114        })?;
5115
5116        anyhow::Ok(())
5117    })
5118}
5119
5120pub fn reload(reload: &Reload, cx: &mut AppContext) {
5121    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
5122    let mut workspace_windows = cx
5123        .windows()
5124        .into_iter()
5125        .filter_map(|window| window.downcast::<Workspace>())
5126        .collect::<Vec<_>>();
5127
5128    // If multiple windows have unsaved changes, and need a save prompt,
5129    // prompt in the active window before switching to a different window.
5130    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
5131
5132    let mut prompt = None;
5133    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
5134        prompt = window
5135            .update(cx, |_, cx| {
5136                cx.prompt(
5137                    PromptLevel::Info,
5138                    "Are you sure you want to restart?",
5139                    None,
5140                    &["Restart", "Cancel"],
5141                )
5142            })
5143            .ok();
5144    }
5145
5146    let binary_path = reload.binary_path.clone();
5147    cx.spawn(|mut cx| async move {
5148        if let Some(prompt) = prompt {
5149            let answer = prompt.await?;
5150            if answer != 0 {
5151                return Ok(());
5152            }
5153        }
5154
5155        // If the user cancels any save prompt, then keep the app open.
5156        for window in workspace_windows {
5157            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
5158                workspace.prepare_to_close(true, cx)
5159            }) {
5160                if !should_close.await? {
5161                    return Ok(());
5162                }
5163            }
5164        }
5165
5166        cx.update(|cx| cx.restart(binary_path))
5167    })
5168    .detach_and_log_err(cx);
5169}
5170
5171fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
5172    let mut parts = value.split(',');
5173    let x: usize = parts.next()?.parse().ok()?;
5174    let y: usize = parts.next()?.parse().ok()?;
5175    Some(point(px(x as f32), px(y as f32)))
5176}
5177
5178fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
5179    let mut parts = value.split(',');
5180    let width: usize = parts.next()?.parse().ok()?;
5181    let height: usize = parts.next()?.parse().ok()?;
5182    Some(size(px(width as f32), px(height as f32)))
5183}
5184
5185#[cfg(test)]
5186mod tests {
5187    use std::{cell::RefCell, rc::Rc};
5188
5189    use super::*;
5190    use crate::{
5191        dock::{test::TestPanel, PanelEvent},
5192        item::{
5193            test::{TestItem, TestProjectItem},
5194            ItemEvent,
5195        },
5196    };
5197    use fs::FakeFs;
5198    use gpui::{
5199        px, DismissEvent, Empty, EventEmitter, FocusHandle, FocusableView, Render, TestAppContext,
5200        UpdateGlobal, VisualTestContext,
5201    };
5202    use project::{Project, ProjectEntryId};
5203    use serde_json::json;
5204    use settings::SettingsStore;
5205
5206    #[gpui::test]
5207    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
5208        init_test(cx);
5209
5210        let fs = FakeFs::new(cx.executor());
5211        let project = Project::test(fs, [], cx).await;
5212        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5213
5214        // Adding an item with no ambiguity renders the tab without detail.
5215        let item1 = cx.new_view(|cx| {
5216            let mut item = TestItem::new(cx);
5217            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
5218            item
5219        });
5220        workspace.update(cx, |workspace, cx| {
5221            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, cx);
5222        });
5223        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
5224
5225        // Adding an item that creates ambiguity increases the level of detail on
5226        // both tabs.
5227        let item2 = cx.new_view(|cx| {
5228            let mut item = TestItem::new(cx);
5229            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5230            item
5231        });
5232        workspace.update(cx, |workspace, cx| {
5233            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5234        });
5235        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5236        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5237
5238        // Adding an item that creates ambiguity increases the level of detail only
5239        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
5240        // we stop at the highest detail available.
5241        let item3 = cx.new_view(|cx| {
5242            let mut item = TestItem::new(cx);
5243            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5244            item
5245        });
5246        workspace.update(cx, |workspace, cx| {
5247            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5248        });
5249        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5250        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
5251        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
5252    }
5253
5254    #[gpui::test]
5255    async fn test_tracking_active_path(cx: &mut TestAppContext) {
5256        init_test(cx);
5257
5258        let fs = FakeFs::new(cx.executor());
5259        fs.insert_tree(
5260            "/root1",
5261            json!({
5262                "one.txt": "",
5263                "two.txt": "",
5264            }),
5265        )
5266        .await;
5267        fs.insert_tree(
5268            "/root2",
5269            json!({
5270                "three.txt": "",
5271            }),
5272        )
5273        .await;
5274
5275        let project = Project::test(fs, ["root1".as_ref()], cx).await;
5276        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5277        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5278        let worktree_id = project.update(cx, |project, cx| {
5279            project.worktrees().next().unwrap().read(cx).id()
5280        });
5281
5282        let item1 = cx.new_view(|cx| {
5283            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
5284        });
5285        let item2 = cx.new_view(|cx| {
5286            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
5287        });
5288
5289        // Add an item to an empty pane
5290        workspace.update(cx, |workspace, cx| {
5291            workspace.add_item_to_active_pane(Box::new(item1), None, cx)
5292        });
5293        project.update(cx, |project, cx| {
5294            assert_eq!(
5295                project.active_entry(),
5296                project
5297                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5298                    .map(|e| e.id)
5299            );
5300        });
5301        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5302
5303        // Add a second item to a non-empty pane
5304        workspace.update(cx, |workspace, cx| {
5305            workspace.add_item_to_active_pane(Box::new(item2), None, cx)
5306        });
5307        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
5308        project.update(cx, |project, cx| {
5309            assert_eq!(
5310                project.active_entry(),
5311                project
5312                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
5313                    .map(|e| e.id)
5314            );
5315        });
5316
5317        // Close the active item
5318        pane.update(cx, |pane, cx| {
5319            pane.close_active_item(&Default::default(), cx).unwrap()
5320        })
5321        .await
5322        .unwrap();
5323        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5324        project.update(cx, |project, cx| {
5325            assert_eq!(
5326                project.active_entry(),
5327                project
5328                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5329                    .map(|e| e.id)
5330            );
5331        });
5332
5333        // Add a project folder
5334        project
5335            .update(cx, |project, cx| {
5336                project.find_or_create_local_worktree("root2", true, cx)
5337            })
5338            .await
5339            .unwrap();
5340        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
5341
5342        // Remove a project folder
5343        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
5344        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
5345    }
5346
5347    #[gpui::test]
5348    async fn test_close_window(cx: &mut TestAppContext) {
5349        init_test(cx);
5350
5351        let fs = FakeFs::new(cx.executor());
5352        fs.insert_tree("/root", json!({ "one": "" })).await;
5353
5354        let project = Project::test(fs, ["root".as_ref()], cx).await;
5355        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5356
5357        // When there are no dirty items, there's nothing to do.
5358        let item1 = cx.new_view(|cx| TestItem::new(cx));
5359        workspace.update(cx, |w, cx| {
5360            w.add_item_to_active_pane(Box::new(item1.clone()), None, cx)
5361        });
5362        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5363        assert!(task.await.unwrap());
5364
5365        // When there are dirty untitled items, prompt to save each one. If the user
5366        // cancels any prompt, then abort.
5367        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
5368        let item3 = cx.new_view(|cx| {
5369            TestItem::new(cx)
5370                .with_dirty(true)
5371                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5372        });
5373        workspace.update(cx, |w, cx| {
5374            w.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5375            w.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5376        });
5377        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5378        cx.executor().run_until_parked();
5379        cx.simulate_prompt_answer(2); // cancel save all
5380        cx.executor().run_until_parked();
5381        cx.simulate_prompt_answer(2); // cancel save all
5382        cx.executor().run_until_parked();
5383        assert!(!cx.has_pending_prompt());
5384        assert!(!task.await.unwrap());
5385    }
5386
5387    #[gpui::test]
5388    async fn test_close_pane_items(cx: &mut TestAppContext) {
5389        init_test(cx);
5390
5391        let fs = FakeFs::new(cx.executor());
5392
5393        let project = Project::test(fs, None, cx).await;
5394        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5395
5396        let item1 = cx.new_view(|cx| {
5397            TestItem::new(cx)
5398                .with_dirty(true)
5399                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5400        });
5401        let item2 = cx.new_view(|cx| {
5402            TestItem::new(cx)
5403                .with_dirty(true)
5404                .with_conflict(true)
5405                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
5406        });
5407        let item3 = cx.new_view(|cx| {
5408            TestItem::new(cx)
5409                .with_dirty(true)
5410                .with_conflict(true)
5411                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
5412        });
5413        let item4 = cx.new_view(|cx| {
5414            TestItem::new(cx)
5415                .with_dirty(true)
5416                .with_project_items(&[TestProjectItem::new_untitled(cx)])
5417        });
5418        let pane = workspace.update(cx, |workspace, cx| {
5419            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, cx);
5420            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5421            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5422            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, cx);
5423            workspace.active_pane().clone()
5424        });
5425
5426        let close_items = pane.update(cx, |pane, cx| {
5427            pane.activate_item(1, true, true, cx);
5428            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5429            let item1_id = item1.item_id();
5430            let item3_id = item3.item_id();
5431            let item4_id = item4.item_id();
5432            pane.close_items(cx, SaveIntent::Close, move |id| {
5433                [item1_id, item3_id, item4_id].contains(&id)
5434            })
5435        });
5436        cx.executor().run_until_parked();
5437
5438        assert!(cx.has_pending_prompt());
5439        // Ignore "Save all" prompt
5440        cx.simulate_prompt_answer(2);
5441        cx.executor().run_until_parked();
5442        // There's a prompt to save item 1.
5443        pane.update(cx, |pane, _| {
5444            assert_eq!(pane.items_len(), 4);
5445            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
5446        });
5447        // Confirm saving item 1.
5448        cx.simulate_prompt_answer(0);
5449        cx.executor().run_until_parked();
5450
5451        // Item 1 is saved. There's a prompt to save item 3.
5452        pane.update(cx, |pane, cx| {
5453            assert_eq!(item1.read(cx).save_count, 1);
5454            assert_eq!(item1.read(cx).save_as_count, 0);
5455            assert_eq!(item1.read(cx).reload_count, 0);
5456            assert_eq!(pane.items_len(), 3);
5457            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
5458        });
5459        assert!(cx.has_pending_prompt());
5460
5461        // Cancel saving item 3.
5462        cx.simulate_prompt_answer(1);
5463        cx.executor().run_until_parked();
5464
5465        // Item 3 is reloaded. There's a prompt to save item 4.
5466        pane.update(cx, |pane, cx| {
5467            assert_eq!(item3.read(cx).save_count, 0);
5468            assert_eq!(item3.read(cx).save_as_count, 0);
5469            assert_eq!(item3.read(cx).reload_count, 1);
5470            assert_eq!(pane.items_len(), 2);
5471            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
5472        });
5473        assert!(cx.has_pending_prompt());
5474
5475        // Confirm saving item 4.
5476        cx.simulate_prompt_answer(0);
5477        cx.executor().run_until_parked();
5478
5479        // There's a prompt for a path for item 4.
5480        cx.simulate_new_path_selection(|_| Some(Default::default()));
5481        close_items.await.unwrap();
5482
5483        // The requested items are closed.
5484        pane.update(cx, |pane, cx| {
5485            assert_eq!(item4.read(cx).save_count, 0);
5486            assert_eq!(item4.read(cx).save_as_count, 1);
5487            assert_eq!(item4.read(cx).reload_count, 0);
5488            assert_eq!(pane.items_len(), 1);
5489            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5490        });
5491    }
5492
5493    #[gpui::test]
5494    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
5495        init_test(cx);
5496
5497        let fs = FakeFs::new(cx.executor());
5498        let project = Project::test(fs, [], cx).await;
5499        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5500
5501        // Create several workspace items with single project entries, and two
5502        // workspace items with multiple project entries.
5503        let single_entry_items = (0..=4)
5504            .map(|project_entry_id| {
5505                cx.new_view(|cx| {
5506                    TestItem::new(cx)
5507                        .with_dirty(true)
5508                        .with_project_items(&[TestProjectItem::new(
5509                            project_entry_id,
5510                            &format!("{project_entry_id}.txt"),
5511                            cx,
5512                        )])
5513                })
5514            })
5515            .collect::<Vec<_>>();
5516        let item_2_3 = cx.new_view(|cx| {
5517            TestItem::new(cx)
5518                .with_dirty(true)
5519                .with_singleton(false)
5520                .with_project_items(&[
5521                    single_entry_items[2].read(cx).project_items[0].clone(),
5522                    single_entry_items[3].read(cx).project_items[0].clone(),
5523                ])
5524        });
5525        let item_3_4 = cx.new_view(|cx| {
5526            TestItem::new(cx)
5527                .with_dirty(true)
5528                .with_singleton(false)
5529                .with_project_items(&[
5530                    single_entry_items[3].read(cx).project_items[0].clone(),
5531                    single_entry_items[4].read(cx).project_items[0].clone(),
5532                ])
5533        });
5534
5535        // Create two panes that contain the following project entries:
5536        //   left pane:
5537        //     multi-entry items:   (2, 3)
5538        //     single-entry items:  0, 1, 2, 3, 4
5539        //   right pane:
5540        //     single-entry items:  1
5541        //     multi-entry items:   (3, 4)
5542        let left_pane = workspace.update(cx, |workspace, cx| {
5543            let left_pane = workspace.active_pane().clone();
5544            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, cx);
5545            for item in single_entry_items {
5546                workspace.add_item_to_active_pane(Box::new(item), None, cx);
5547            }
5548            left_pane.update(cx, |pane, cx| {
5549                pane.activate_item(2, true, true, cx);
5550            });
5551
5552            let right_pane = workspace
5553                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
5554                .unwrap();
5555
5556            right_pane.update(cx, |pane, cx| {
5557                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
5558            });
5559
5560            left_pane
5561        });
5562
5563        cx.focus_view(&left_pane);
5564
5565        // When closing all of the items in the left pane, we should be prompted twice:
5566        // once for project entry 0, and once for project entry 2. Project entries 1,
5567        // 3, and 4 are all still open in the other paten. After those two
5568        // prompts, the task should complete.
5569
5570        let close = left_pane.update(cx, |pane, cx| {
5571            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
5572        });
5573        cx.executor().run_until_parked();
5574
5575        // Discard "Save all" prompt
5576        cx.simulate_prompt_answer(2);
5577
5578        cx.executor().run_until_parked();
5579        left_pane.update(cx, |pane, cx| {
5580            assert_eq!(
5581                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5582                &[ProjectEntryId::from_proto(0)]
5583            );
5584        });
5585        cx.simulate_prompt_answer(0);
5586
5587        cx.executor().run_until_parked();
5588        left_pane.update(cx, |pane, cx| {
5589            assert_eq!(
5590                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5591                &[ProjectEntryId::from_proto(2)]
5592            );
5593        });
5594        cx.simulate_prompt_answer(0);
5595
5596        cx.executor().run_until_parked();
5597        close.await.unwrap();
5598        left_pane.update(cx, |pane, _| {
5599            assert_eq!(pane.items_len(), 0);
5600        });
5601    }
5602
5603    #[gpui::test]
5604    async fn test_autosave(cx: &mut gpui::TestAppContext) {
5605        init_test(cx);
5606
5607        let fs = FakeFs::new(cx.executor());
5608        let project = Project::test(fs, [], cx).await;
5609        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5610        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5611
5612        let item = cx.new_view(|cx| {
5613            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5614        });
5615        let item_id = item.entity_id();
5616        workspace.update(cx, |workspace, cx| {
5617            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
5618        });
5619
5620        // Autosave on window change.
5621        item.update(cx, |item, cx| {
5622            SettingsStore::update_global(cx, |settings, cx| {
5623                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5624                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
5625                })
5626            });
5627            item.is_dirty = true;
5628        });
5629
5630        // Deactivating the window saves the file.
5631        cx.deactivate_window();
5632        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5633
5634        // Re-activating the window doesn't save the file.
5635        cx.update(|cx| cx.activate_window());
5636        cx.executor().run_until_parked();
5637        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5638
5639        // Autosave on focus change.
5640        item.update(cx, |item, cx| {
5641            cx.focus_self();
5642            SettingsStore::update_global(cx, |settings, cx| {
5643                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5644                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5645                })
5646            });
5647            item.is_dirty = true;
5648        });
5649
5650        // Blurring the item saves the file.
5651        item.update(cx, |_, cx| cx.blur());
5652        cx.executor().run_until_parked();
5653        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
5654
5655        // Deactivating the window still saves the file.
5656        item.update(cx, |item, cx| {
5657            cx.focus_self();
5658            item.is_dirty = true;
5659        });
5660        cx.deactivate_window();
5661        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5662
5663        // Autosave after delay.
5664        item.update(cx, |item, cx| {
5665            SettingsStore::update_global(cx, |settings, cx| {
5666                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5667                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5668                })
5669            });
5670            item.is_dirty = true;
5671            cx.emit(ItemEvent::Edit);
5672        });
5673
5674        // Delay hasn't fully expired, so the file is still dirty and unsaved.
5675        cx.executor().advance_clock(Duration::from_millis(250));
5676        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5677
5678        // After delay expires, the file is saved.
5679        cx.executor().advance_clock(Duration::from_millis(250));
5680        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
5681
5682        // Autosave on focus change, ensuring closing the tab counts as such.
5683        item.update(cx, |item, cx| {
5684            SettingsStore::update_global(cx, |settings, cx| {
5685                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5686                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5687                })
5688            });
5689            item.is_dirty = true;
5690        });
5691
5692        pane.update(cx, |pane, cx| {
5693            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5694        })
5695        .await
5696        .unwrap();
5697        assert!(!cx.has_pending_prompt());
5698        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5699
5700        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5701        workspace.update(cx, |workspace, cx| {
5702            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
5703        });
5704        item.update(cx, |item, cx| {
5705            item.project_items[0].update(cx, |item, _| {
5706                item.entry_id = None;
5707            });
5708            item.is_dirty = true;
5709            cx.blur();
5710        });
5711        cx.run_until_parked();
5712        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5713
5714        // Ensure autosave is prevented for deleted files also when closing the buffer.
5715        let _close_items = pane.update(cx, |pane, cx| {
5716            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5717        });
5718        cx.run_until_parked();
5719        assert!(cx.has_pending_prompt());
5720        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5721    }
5722
5723    #[gpui::test]
5724    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5725        init_test(cx);
5726
5727        let fs = FakeFs::new(cx.executor());
5728
5729        let project = Project::test(fs, [], cx).await;
5730        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5731
5732        let item = cx.new_view(|cx| {
5733            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5734        });
5735        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5736        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
5737        let toolbar_notify_count = Rc::new(RefCell::new(0));
5738
5739        workspace.update(cx, |workspace, cx| {
5740            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
5741            let toolbar_notification_count = toolbar_notify_count.clone();
5742            cx.observe(&toolbar, move |_, _, _| {
5743                *toolbar_notification_count.borrow_mut() += 1
5744            })
5745            .detach();
5746        });
5747
5748        pane.update(cx, |pane, _| {
5749            assert!(!pane.can_navigate_backward());
5750            assert!(!pane.can_navigate_forward());
5751        });
5752
5753        item.update(cx, |item, cx| {
5754            item.set_state("one".to_string(), cx);
5755        });
5756
5757        // Toolbar must be notified to re-render the navigation buttons
5758        assert_eq!(*toolbar_notify_count.borrow(), 1);
5759
5760        pane.update(cx, |pane, _| {
5761            assert!(pane.can_navigate_backward());
5762            assert!(!pane.can_navigate_forward());
5763        });
5764
5765        workspace
5766            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5767            .await
5768            .unwrap();
5769
5770        assert_eq!(*toolbar_notify_count.borrow(), 2);
5771        pane.update(cx, |pane, _| {
5772            assert!(!pane.can_navigate_backward());
5773            assert!(pane.can_navigate_forward());
5774        });
5775    }
5776
5777    #[gpui::test]
5778    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5779        init_test(cx);
5780        let fs = FakeFs::new(cx.executor());
5781
5782        let project = Project::test(fs, [], cx).await;
5783        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5784
5785        let panel = workspace.update(cx, |workspace, cx| {
5786            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5787            workspace.add_panel(panel.clone(), cx);
5788
5789            workspace
5790                .right_dock()
5791                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5792
5793            panel
5794        });
5795
5796        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5797        pane.update(cx, |pane, cx| {
5798            let item = cx.new_view(|cx| TestItem::new(cx));
5799            pane.add_item(Box::new(item), true, true, None, cx);
5800        });
5801
5802        // Transfer focus from center to panel
5803        workspace.update(cx, |workspace, cx| {
5804            workspace.toggle_panel_focus::<TestPanel>(cx);
5805        });
5806
5807        workspace.update(cx, |workspace, cx| {
5808            assert!(workspace.right_dock().read(cx).is_open());
5809            assert!(!panel.is_zoomed(cx));
5810            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5811        });
5812
5813        // Transfer focus from panel to center
5814        workspace.update(cx, |workspace, cx| {
5815            workspace.toggle_panel_focus::<TestPanel>(cx);
5816        });
5817
5818        workspace.update(cx, |workspace, cx| {
5819            assert!(workspace.right_dock().read(cx).is_open());
5820            assert!(!panel.is_zoomed(cx));
5821            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5822        });
5823
5824        // Close the dock
5825        workspace.update(cx, |workspace, cx| {
5826            workspace.toggle_dock(DockPosition::Right, cx);
5827        });
5828
5829        workspace.update(cx, |workspace, cx| {
5830            assert!(!workspace.right_dock().read(cx).is_open());
5831            assert!(!panel.is_zoomed(cx));
5832            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5833        });
5834
5835        // Open the dock
5836        workspace.update(cx, |workspace, cx| {
5837            workspace.toggle_dock(DockPosition::Right, cx);
5838        });
5839
5840        workspace.update(cx, |workspace, cx| {
5841            assert!(workspace.right_dock().read(cx).is_open());
5842            assert!(!panel.is_zoomed(cx));
5843            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5844        });
5845
5846        // Focus and zoom panel
5847        panel.update(cx, |panel, cx| {
5848            cx.focus_self();
5849            panel.set_zoomed(true, cx)
5850        });
5851
5852        workspace.update(cx, |workspace, cx| {
5853            assert!(workspace.right_dock().read(cx).is_open());
5854            assert!(panel.is_zoomed(cx));
5855            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5856        });
5857
5858        // Transfer focus to the center closes the dock
5859        workspace.update(cx, |workspace, cx| {
5860            workspace.toggle_panel_focus::<TestPanel>(cx);
5861        });
5862
5863        workspace.update(cx, |workspace, cx| {
5864            assert!(!workspace.right_dock().read(cx).is_open());
5865            assert!(panel.is_zoomed(cx));
5866            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5867        });
5868
5869        // Transferring focus back to the panel keeps it zoomed
5870        workspace.update(cx, |workspace, cx| {
5871            workspace.toggle_panel_focus::<TestPanel>(cx);
5872        });
5873
5874        workspace.update(cx, |workspace, cx| {
5875            assert!(workspace.right_dock().read(cx).is_open());
5876            assert!(panel.is_zoomed(cx));
5877            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5878        });
5879
5880        // Close the dock while it is zoomed
5881        workspace.update(cx, |workspace, cx| {
5882            workspace.toggle_dock(DockPosition::Right, cx)
5883        });
5884
5885        workspace.update(cx, |workspace, cx| {
5886            assert!(!workspace.right_dock().read(cx).is_open());
5887            assert!(panel.is_zoomed(cx));
5888            assert!(workspace.zoomed.is_none());
5889            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5890        });
5891
5892        // Opening the dock, when it's zoomed, retains focus
5893        workspace.update(cx, |workspace, cx| {
5894            workspace.toggle_dock(DockPosition::Right, cx)
5895        });
5896
5897        workspace.update(cx, |workspace, cx| {
5898            assert!(workspace.right_dock().read(cx).is_open());
5899            assert!(panel.is_zoomed(cx));
5900            assert!(workspace.zoomed.is_some());
5901            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5902        });
5903
5904        // Unzoom and close the panel, zoom the active pane.
5905        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5906        workspace.update(cx, |workspace, cx| {
5907            workspace.toggle_dock(DockPosition::Right, cx)
5908        });
5909        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5910
5911        // Opening a dock unzooms the pane.
5912        workspace.update(cx, |workspace, cx| {
5913            workspace.toggle_dock(DockPosition::Right, cx)
5914        });
5915        workspace.update(cx, |workspace, cx| {
5916            let pane = pane.read(cx);
5917            assert!(!pane.is_zoomed());
5918            assert!(!pane.focus_handle(cx).is_focused(cx));
5919            assert!(workspace.right_dock().read(cx).is_open());
5920            assert!(workspace.zoomed.is_none());
5921        });
5922    }
5923
5924    struct TestModal(FocusHandle);
5925
5926    impl TestModal {
5927        fn new(cx: &mut ViewContext<Self>) -> Self {
5928            Self(cx.focus_handle())
5929        }
5930    }
5931
5932    impl EventEmitter<DismissEvent> for TestModal {}
5933
5934    impl FocusableView for TestModal {
5935        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5936            self.0.clone()
5937        }
5938    }
5939
5940    impl ModalView for TestModal {}
5941
5942    impl Render for TestModal {
5943        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5944            div().track_focus(&self.0)
5945        }
5946    }
5947
5948    #[gpui::test]
5949    async fn test_panels(cx: &mut gpui::TestAppContext) {
5950        init_test(cx);
5951        let fs = FakeFs::new(cx.executor());
5952
5953        let project = Project::test(fs, [], cx).await;
5954        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5955
5956        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5957            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5958            workspace.add_panel(panel_1.clone(), cx);
5959            workspace
5960                .left_dock()
5961                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5962            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5963            workspace.add_panel(panel_2.clone(), cx);
5964            workspace
5965                .right_dock()
5966                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5967
5968            let left_dock = workspace.left_dock();
5969            assert_eq!(
5970                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5971                panel_1.panel_id()
5972            );
5973            assert_eq!(
5974                left_dock.read(cx).active_panel_size(cx).unwrap(),
5975                panel_1.size(cx)
5976            );
5977
5978            left_dock.update(cx, |left_dock, cx| {
5979                left_dock.resize_active_panel(Some(px(1337.)), cx)
5980            });
5981            assert_eq!(
5982                workspace
5983                    .right_dock()
5984                    .read(cx)
5985                    .visible_panel()
5986                    .unwrap()
5987                    .panel_id(),
5988                panel_2.panel_id(),
5989            );
5990
5991            (panel_1, panel_2)
5992        });
5993
5994        // Move panel_1 to the right
5995        panel_1.update(cx, |panel_1, cx| {
5996            panel_1.set_position(DockPosition::Right, cx)
5997        });
5998
5999        workspace.update(cx, |workspace, cx| {
6000            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
6001            // Since it was the only panel on the left, the left dock should now be closed.
6002            assert!(!workspace.left_dock().read(cx).is_open());
6003            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
6004            let right_dock = workspace.right_dock();
6005            assert_eq!(
6006                right_dock.read(cx).visible_panel().unwrap().panel_id(),
6007                panel_1.panel_id()
6008            );
6009            assert_eq!(
6010                right_dock.read(cx).active_panel_size(cx).unwrap(),
6011                px(1337.)
6012            );
6013
6014            // Now we move panel_2 to the left
6015            panel_2.set_position(DockPosition::Left, cx);
6016        });
6017
6018        workspace.update(cx, |workspace, cx| {
6019            // Since panel_2 was not visible on the right, we don't open the left dock.
6020            assert!(!workspace.left_dock().read(cx).is_open());
6021            // And the right dock is unaffected in its displaying of panel_1
6022            assert!(workspace.right_dock().read(cx).is_open());
6023            assert_eq!(
6024                workspace
6025                    .right_dock()
6026                    .read(cx)
6027                    .visible_panel()
6028                    .unwrap()
6029                    .panel_id(),
6030                panel_1.panel_id(),
6031            );
6032        });
6033
6034        // Move panel_1 back to the left
6035        panel_1.update(cx, |panel_1, cx| {
6036            panel_1.set_position(DockPosition::Left, cx)
6037        });
6038
6039        workspace.update(cx, |workspace, cx| {
6040            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
6041            let left_dock = workspace.left_dock();
6042            assert!(left_dock.read(cx).is_open());
6043            assert_eq!(
6044                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6045                panel_1.panel_id()
6046            );
6047            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
6048            // And the right dock should be closed as it no longer has any panels.
6049            assert!(!workspace.right_dock().read(cx).is_open());
6050
6051            // Now we move panel_1 to the bottom
6052            panel_1.set_position(DockPosition::Bottom, cx);
6053        });
6054
6055        workspace.update(cx, |workspace, cx| {
6056            // Since panel_1 was visible on the left, we close the left dock.
6057            assert!(!workspace.left_dock().read(cx).is_open());
6058            // The bottom dock is sized based on the panel's default size,
6059            // since the panel orientation changed from vertical to horizontal.
6060            let bottom_dock = workspace.bottom_dock();
6061            assert_eq!(
6062                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
6063                panel_1.size(cx),
6064            );
6065            // Close bottom dock and move panel_1 back to the left.
6066            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
6067            panel_1.set_position(DockPosition::Left, cx);
6068        });
6069
6070        // Emit activated event on panel 1
6071        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
6072
6073        // Now the left dock is open and panel_1 is active and focused.
6074        workspace.update(cx, |workspace, cx| {
6075            let left_dock = workspace.left_dock();
6076            assert!(left_dock.read(cx).is_open());
6077            assert_eq!(
6078                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6079                panel_1.panel_id(),
6080            );
6081            assert!(panel_1.focus_handle(cx).is_focused(cx));
6082        });
6083
6084        // Emit closed event on panel 2, which is not active
6085        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
6086
6087        // Wo don't close the left dock, because panel_2 wasn't the active panel
6088        workspace.update(cx, |workspace, cx| {
6089            let left_dock = workspace.left_dock();
6090            assert!(left_dock.read(cx).is_open());
6091            assert_eq!(
6092                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6093                panel_1.panel_id(),
6094            );
6095        });
6096
6097        // Emitting a ZoomIn event shows the panel as zoomed.
6098        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
6099        workspace.update(cx, |workspace, _| {
6100            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6101            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
6102        });
6103
6104        // Move panel to another dock while it is zoomed
6105        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
6106        workspace.update(cx, |workspace, _| {
6107            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6108
6109            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6110        });
6111
6112        // This is a helper for getting a:
6113        // - valid focus on an element,
6114        // - that isn't a part of the panes and panels system of the Workspace,
6115        // - and doesn't trigger the 'on_focus_lost' API.
6116        let focus_other_view = {
6117            let workspace = workspace.clone();
6118            move |cx: &mut VisualTestContext| {
6119                workspace.update(cx, |workspace, cx| {
6120                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
6121                        workspace.toggle_modal(cx, TestModal::new);
6122                        workspace.toggle_modal(cx, TestModal::new);
6123                    } else {
6124                        workspace.toggle_modal(cx, TestModal::new);
6125                    }
6126                })
6127            }
6128        };
6129
6130        // If focus is transferred to another view that's not a panel or another pane, we still show
6131        // the panel as zoomed.
6132        focus_other_view(cx);
6133        workspace.update(cx, |workspace, _| {
6134            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6135            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6136        });
6137
6138        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
6139        workspace.update(cx, |_, cx| cx.focus_self());
6140        workspace.update(cx, |workspace, _| {
6141            assert_eq!(workspace.zoomed, None);
6142            assert_eq!(workspace.zoomed_position, None);
6143        });
6144
6145        // If focus is transferred again to another view that's not a panel or a pane, we won't
6146        // show the panel as zoomed because it wasn't zoomed before.
6147        focus_other_view(cx);
6148        workspace.update(cx, |workspace, _| {
6149            assert_eq!(workspace.zoomed, None);
6150            assert_eq!(workspace.zoomed_position, None);
6151        });
6152
6153        // When the panel is activated, it is zoomed again.
6154        cx.dispatch_action(ToggleRightDock);
6155        workspace.update(cx, |workspace, _| {
6156            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6157            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6158        });
6159
6160        // Emitting a ZoomOut event unzooms the panel.
6161        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
6162        workspace.update(cx, |workspace, _| {
6163            assert_eq!(workspace.zoomed, None);
6164            assert_eq!(workspace.zoomed_position, None);
6165        });
6166
6167        // Emit closed event on panel 1, which is active
6168        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
6169
6170        // Now the left dock is closed, because panel_1 was the active panel
6171        workspace.update(cx, |workspace, cx| {
6172            let right_dock = workspace.right_dock();
6173            assert!(!right_dock.read(cx).is_open());
6174        });
6175    }
6176
6177    mod register_project_item_tests {
6178        use ui::Context as _;
6179
6180        use super::*;
6181
6182        const TEST_PNG_KIND: &str = "TestPngItemView";
6183        // View
6184        struct TestPngItemView {
6185            focus_handle: FocusHandle,
6186        }
6187        // Model
6188        struct TestPngItem {}
6189
6190        impl project::Item for TestPngItem {
6191            fn try_open(
6192                _project: &Model<Project>,
6193                path: &ProjectPath,
6194                cx: &mut AppContext,
6195            ) -> Option<Task<gpui::Result<Model<Self>>>> {
6196                if path.path.extension().unwrap() == "png" {
6197                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestPngItem {}) }))
6198                } else {
6199                    None
6200                }
6201            }
6202
6203            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
6204                None
6205            }
6206
6207            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
6208                None
6209            }
6210        }
6211
6212        impl Item for TestPngItemView {
6213            type Event = ();
6214
6215            fn serialized_item_kind() -> Option<&'static str> {
6216                Some(TEST_PNG_KIND)
6217            }
6218        }
6219        impl EventEmitter<()> for TestPngItemView {}
6220        impl FocusableView for TestPngItemView {
6221            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6222                self.focus_handle.clone()
6223            }
6224        }
6225
6226        impl Render for TestPngItemView {
6227            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6228                Empty
6229            }
6230        }
6231
6232        impl ProjectItem for TestPngItemView {
6233            type Item = TestPngItem;
6234
6235            fn for_project_item(
6236                _project: Model<Project>,
6237                _item: Model<Self::Item>,
6238                cx: &mut ViewContext<Self>,
6239            ) -> Self
6240            where
6241                Self: Sized,
6242            {
6243                Self {
6244                    focus_handle: cx.focus_handle(),
6245                }
6246            }
6247        }
6248
6249        const TEST_IPYNB_KIND: &str = "TestIpynbItemView";
6250        // View
6251        struct TestIpynbItemView {
6252            focus_handle: FocusHandle,
6253        }
6254        // Model
6255        struct TestIpynbItem {}
6256
6257        impl project::Item for TestIpynbItem {
6258            fn try_open(
6259                _project: &Model<Project>,
6260                path: &ProjectPath,
6261                cx: &mut AppContext,
6262            ) -> Option<Task<gpui::Result<Model<Self>>>> {
6263                if path.path.extension().unwrap() == "ipynb" {
6264                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestIpynbItem {}) }))
6265                } else {
6266                    None
6267                }
6268            }
6269
6270            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
6271                None
6272            }
6273
6274            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
6275                None
6276            }
6277        }
6278
6279        impl Item for TestIpynbItemView {
6280            type Event = ();
6281
6282            fn serialized_item_kind() -> Option<&'static str> {
6283                Some(TEST_IPYNB_KIND)
6284            }
6285        }
6286        impl EventEmitter<()> for TestIpynbItemView {}
6287        impl FocusableView for TestIpynbItemView {
6288            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6289                self.focus_handle.clone()
6290            }
6291        }
6292
6293        impl Render for TestIpynbItemView {
6294            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6295                Empty
6296            }
6297        }
6298
6299        impl ProjectItem for TestIpynbItemView {
6300            type Item = TestIpynbItem;
6301
6302            fn for_project_item(
6303                _project: Model<Project>,
6304                _item: Model<Self::Item>,
6305                cx: &mut ViewContext<Self>,
6306            ) -> Self
6307            where
6308                Self: Sized,
6309            {
6310                Self {
6311                    focus_handle: cx.focus_handle(),
6312                }
6313            }
6314        }
6315
6316        struct TestAlternatePngItemView {
6317            focus_handle: FocusHandle,
6318        }
6319
6320        const TEST_ALTERNATE_PNG_KIND: &str = "TestAlternatePngItemView";
6321        impl Item for TestAlternatePngItemView {
6322            type Event = ();
6323
6324            fn serialized_item_kind() -> Option<&'static str> {
6325                Some(TEST_ALTERNATE_PNG_KIND)
6326            }
6327        }
6328        impl EventEmitter<()> for TestAlternatePngItemView {}
6329        impl FocusableView for TestAlternatePngItemView {
6330            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6331                self.focus_handle.clone()
6332            }
6333        }
6334
6335        impl Render for TestAlternatePngItemView {
6336            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6337                Empty
6338            }
6339        }
6340
6341        impl ProjectItem for TestAlternatePngItemView {
6342            type Item = TestPngItem;
6343
6344            fn for_project_item(
6345                _project: Model<Project>,
6346                _item: Model<Self::Item>,
6347                cx: &mut ViewContext<Self>,
6348            ) -> Self
6349            where
6350                Self: Sized,
6351            {
6352                Self {
6353                    focus_handle: cx.focus_handle(),
6354                }
6355            }
6356        }
6357
6358        #[gpui::test]
6359        async fn test_register_project_item(cx: &mut TestAppContext) {
6360            init_test(cx);
6361
6362            cx.update(|cx| {
6363                register_project_item::<TestPngItemView>(cx);
6364                register_project_item::<TestIpynbItemView>(cx);
6365            });
6366
6367            let fs = FakeFs::new(cx.executor());
6368            fs.insert_tree(
6369                "/root1",
6370                json!({
6371                    "one.png": "BINARYDATAHERE",
6372                    "two.ipynb": "{ totally a notebook }",
6373                    "three.txt": "editing text, sure why not?"
6374                }),
6375            )
6376            .await;
6377
6378            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6379            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6380
6381            let worktree_id = project.update(cx, |project, cx| {
6382                project.worktrees().next().unwrap().read(cx).id()
6383            });
6384
6385            let handle = workspace
6386                .update(cx, |workspace, cx| {
6387                    let project_path = (worktree_id, "one.png");
6388                    workspace.open_path(project_path, None, true, cx)
6389                })
6390                .await
6391                .unwrap();
6392
6393            // Now we can check if the handle we got back errored or not
6394            assert_eq!(handle.serialized_item_kind().unwrap(), TEST_PNG_KIND);
6395
6396            let handle = workspace
6397                .update(cx, |workspace, cx| {
6398                    let project_path = (worktree_id, "two.ipynb");
6399                    workspace.open_path(project_path, None, true, cx)
6400                })
6401                .await
6402                .unwrap();
6403
6404            assert_eq!(handle.serialized_item_kind().unwrap(), TEST_IPYNB_KIND);
6405
6406            let handle = workspace
6407                .update(cx, |workspace, cx| {
6408                    let project_path = (worktree_id, "three.txt");
6409                    workspace.open_path(project_path, None, true, cx)
6410                })
6411                .await;
6412            assert!(handle.is_err());
6413        }
6414
6415        #[gpui::test]
6416        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
6417            init_test(cx);
6418
6419            cx.update(|cx| {
6420                register_project_item::<TestPngItemView>(cx);
6421                register_project_item::<TestAlternatePngItemView>(cx);
6422            });
6423
6424            let fs = FakeFs::new(cx.executor());
6425            fs.insert_tree(
6426                "/root1",
6427                json!({
6428                    "one.png": "BINARYDATAHERE",
6429                    "two.ipynb": "{ totally a notebook }",
6430                    "three.txt": "editing text, sure why not?"
6431                }),
6432            )
6433            .await;
6434
6435            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6436            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6437
6438            let worktree_id = project.update(cx, |project, cx| {
6439                project.worktrees().next().unwrap().read(cx).id()
6440            });
6441
6442            let handle = workspace
6443                .update(cx, |workspace, cx| {
6444                    let project_path = (worktree_id, "one.png");
6445                    workspace.open_path(project_path, None, true, cx)
6446                })
6447                .await
6448                .unwrap();
6449
6450            // This _must_ be the second item registered
6451            assert_eq!(
6452                handle.serialized_item_kind().unwrap(),
6453                TEST_ALTERNATE_PNG_KIND
6454            );
6455
6456            let handle = workspace
6457                .update(cx, |workspace, cx| {
6458                    let project_path = (worktree_id, "three.txt");
6459                    workspace.open_path(project_path, None, true, cx)
6460                })
6461                .await;
6462            assert!(handle.is_err());
6463        }
6464    }
6465
6466    pub fn init_test(cx: &mut TestAppContext) {
6467        cx.update(|cx| {
6468            let settings_store = SettingsStore::test(cx);
6469            cx.set_global(settings_store);
6470            theme::init(theme::LoadThemes::JustBase, cx);
6471            language::init(cx);
6472            crate::init_settings(cx);
6473            Project::init_settings(cx);
6474        });
6475    }
6476}