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,
  31    transparent_black, Action, AnyElement, AnyView, AnyWeakView, AppContext, AsyncAppContext,
  32    AsyncWindowContext, Bounds, CursorStyle, Decorations, DragMoveEvent, Entity as _, EntityId,
  33    EventEmitter, FocusHandle, FocusableView, Global, Hsla, KeyContext, Keystroke, ManagedView,
  34    Model, ModelContext, MouseButton, PathPromptOptions, Point, PromptLevel, Render, ResizeEdge,
  35    Size, Stateful, Subscription, Task, Tiling, View, WeakView, WindowBounds, WindowHandle,
  36    WindowOptions,
  37};
  38use item::{
  39    FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
  40    ProjectItem,
  41};
  42use itertools::Itertools;
  43use language::{LanguageRegistry, Rope};
  44use lazy_static::lazy_static;
  45pub use modal_layer::*;
  46use node_runtime::NodeRuntime;
  47use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
  48pub use pane::*;
  49pub use pane_group::*;
  50use persistence::{model::SerializedWorkspace, SerializedWindowBounds, DB};
  51pub use persistence::{
  52    model::{ItemId, LocalPaths, SerializedDevServerProject, SerializedWorkspaceLocation},
  53    WorkspaceDb, DB as WORKSPACE_DB,
  54};
  55use postage::stream::Stream;
  56use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  57use serde::Deserialize;
  58use settings::Settings;
  59use shared_screen::SharedScreen;
  60use sqlez::{
  61    bindable::{Bind, Column, StaticColumnCount},
  62    statement::Statement,
  63};
  64use status_bar::StatusBar;
  65pub use status_bar::StatusItemView;
  66use std::{
  67    any::TypeId,
  68    borrow::Cow,
  69    cell::RefCell,
  70    cmp,
  71    collections::hash_map::DefaultHasher,
  72    env,
  73    hash::{Hash, Hasher},
  74    path::{Path, PathBuf},
  75    rc::Rc,
  76    sync::{atomic::AtomicUsize, Arc, Weak},
  77    time::Duration,
  78};
  79use task::SpawnInTerminal;
  80use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
  81pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
  82pub use ui;
  83use ui::{
  84    div, h_flex, px, Context as _, Div, FluentBuilder, InteractiveElement as _, IntoElement,
  85    ParentElement as _, Pixels, SharedString, Styled as _, ViewContext, VisualContext as _,
  86    WindowContext,
  87};
  88use util::{maybe, ResultExt};
  89use uuid::Uuid;
  90pub use workspace_settings::{
  91    AutosaveSetting, RestoreOnStartupBehaviour, TabBarSettings, WorkspaceSettings,
  92};
  93
  94use crate::persistence::{
  95    model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
  96    SerializedAxis,
  97};
  98use crate::{notifications::NotificationId, persistence::model::LocalPathsOrder};
  99
 100lazy_static! {
 101    static ref ZED_WINDOW_SIZE: Option<Size<Pixels>> = env::var("ZED_WINDOW_SIZE")
 102        .ok()
 103        .as_deref()
 104        .and_then(parse_pixel_size_env_var);
 105    static ref ZED_WINDOW_POSITION: Option<Point<Pixels>> = env::var("ZED_WINDOW_POSITION")
 106        .ok()
 107        .as_deref()
 108        .and_then(parse_pixel_position_env_var);
 109}
 110
 111#[derive(Clone, PartialEq)]
 112pub struct RemoveWorktreeFromProject(pub WorktreeId);
 113
 114actions!(
 115    workspace,
 116    [
 117        ActivateNextPane,
 118        ActivatePreviousPane,
 119        AddFolderToProject,
 120        ClearAllNotifications,
 121        CloseAllDocks,
 122        CloseWindow,
 123        Feedback,
 124        FollowNextCollaborator,
 125        NewCenterTerminal,
 126        NewFile,
 127        NewSearch,
 128        NewTerminal,
 129        NewWindow,
 130        Open,
 131        OpenInTerminal,
 132        ReloadActiveItem,
 133        SaveAs,
 134        SaveWithoutFormat,
 135        ToggleBottomDock,
 136        ToggleCenteredLayout,
 137        ToggleLeftDock,
 138        ToggleRightDock,
 139        ToggleZoom,
 140        Unfollow,
 141        Welcome,
 142    ]
 143);
 144
 145#[derive(Clone, PartialEq)]
 146pub struct OpenPaths {
 147    pub paths: Vec<PathBuf>,
 148}
 149
 150#[derive(Clone, Deserialize, PartialEq)]
 151pub struct ActivatePane(pub usize);
 152
 153#[derive(Clone, Deserialize, PartialEq)]
 154pub struct ActivatePaneInDirection(pub SplitDirection);
 155
 156#[derive(Clone, Deserialize, PartialEq)]
 157pub struct SwapPaneInDirection(pub SplitDirection);
 158
 159#[derive(Clone, Deserialize, PartialEq)]
 160pub struct NewFileInDirection(pub SplitDirection);
 161
 162#[derive(Clone, PartialEq, Debug, Deserialize)]
 163#[serde(rename_all = "camelCase")]
 164pub struct SaveAll {
 165    pub save_intent: Option<SaveIntent>,
 166}
 167
 168#[derive(Clone, PartialEq, Debug, Deserialize)]
 169#[serde(rename_all = "camelCase")]
 170pub struct Save {
 171    pub save_intent: Option<SaveIntent>,
 172}
 173
 174#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 175#[serde(rename_all = "camelCase")]
 176pub struct CloseAllItemsAndPanes {
 177    pub save_intent: Option<SaveIntent>,
 178}
 179
 180#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 181#[serde(rename_all = "camelCase")]
 182pub struct CloseInactiveTabsAndPanes {
 183    pub save_intent: Option<SaveIntent>,
 184}
 185
 186#[derive(Clone, Deserialize, PartialEq)]
 187pub struct SendKeystrokes(pub String);
 188
 189#[derive(Clone, Deserialize, PartialEq, Default)]
 190pub struct Reload {
 191    pub binary_path: Option<PathBuf>,
 192}
 193
 194action_as!(project_symbols, ToggleProjectSymbols as Toggle);
 195
 196#[derive(Default, PartialEq, Eq, Clone, serde::Deserialize)]
 197pub struct ToggleFileFinder {
 198    #[serde(default)]
 199    pub separate_history: bool,
 200}
 201
 202impl_action_as!(file_finder, ToggleFileFinder as Toggle);
 203
 204impl_actions!(
 205    workspace,
 206    [
 207        ActivatePane,
 208        ActivatePaneInDirection,
 209        CloseAllItemsAndPanes,
 210        CloseInactiveTabsAndPanes,
 211        NewFileInDirection,
 212        OpenTerminal,
 213        Reload,
 214        Save,
 215        SaveAll,
 216        SwapPaneInDirection,
 217        SendKeystrokes,
 218    ]
 219);
 220
 221#[derive(Clone)]
 222pub struct Toast {
 223    id: NotificationId,
 224    msg: Cow<'static, str>,
 225    autohide: bool,
 226    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 227}
 228
 229impl Toast {
 230    pub fn new<I: Into<Cow<'static, str>>>(id: NotificationId, msg: I) -> Self {
 231        Toast {
 232            id,
 233            msg: msg.into(),
 234            on_click: None,
 235            autohide: false,
 236        }
 237    }
 238
 239    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 240    where
 241        M: Into<Cow<'static, str>>,
 242        F: Fn(&mut WindowContext) + 'static,
 243    {
 244        self.on_click = Some((message.into(), Arc::new(on_click)));
 245        self
 246    }
 247
 248    pub fn autohide(mut self) -> Self {
 249        self.autohide = true;
 250        self
 251    }
 252}
 253
 254impl PartialEq for Toast {
 255    fn eq(&self, other: &Self) -> bool {
 256        self.id == other.id
 257            && self.msg == other.msg
 258            && self.on_click.is_some() == other.on_click.is_some()
 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                for (dock, serialized_dock) in [
3815                    (&mut workspace.right_dock, docks.right),
3816                    (&mut workspace.left_dock, docks.left),
3817                    (&mut workspace.bottom_dock, docks.bottom),
3818                ]
3819                .iter_mut()
3820                {
3821                    dock.update(cx, |dock, cx| {
3822                        dock.serialized_dock = Some(serialized_dock.clone());
3823                        dock.restore_state(cx);
3824                    });
3825                }
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        client_side_decorations(
4169            self.actions(div(), cx)
4170                .key_context(context)
4171                .relative()
4172                .size_full()
4173                .flex()
4174                .flex_col()
4175                .font(ui_font)
4176                .gap_0()
4177                .justify_start()
4178                .items_start()
4179                .text_color(colors.text)
4180                .overflow_hidden()
4181                .children(self.titlebar_item.clone())
4182                .child(
4183                    div()
4184                        .id("workspace")
4185                        .bg(colors.background)
4186                        .relative()
4187                        .flex_1()
4188                        .w_full()
4189                        .flex()
4190                        .flex_col()
4191                        .overflow_hidden()
4192                        .border_t_1()
4193                        .border_b_1()
4194                        .border_color(colors.border)
4195                        .child({
4196                            let this = cx.view().clone();
4197                            canvas(
4198                                move |bounds, cx| this.update(cx, |this, _cx| this.bounds = bounds),
4199                                |_, _, _| {},
4200                            )
4201                            .absolute()
4202                            .size_full()
4203                        })
4204                        .when(self.zoomed.is_none(), |this| {
4205                            this.on_drag_move(cx.listener(
4206                                |workspace, e: &DragMoveEvent<DraggedDock>, cx| match e.drag(cx).0 {
4207                                    DockPosition::Left => {
4208                                        let size = e.event.position.x - workspace.bounds.left();
4209                                        workspace.left_dock.update(cx, |left_dock, cx| {
4210                                            left_dock.resize_active_panel(Some(size), cx);
4211                                        });
4212                                    }
4213                                    DockPosition::Right => {
4214                                        let size = workspace.bounds.right() - e.event.position.x;
4215                                        workspace.right_dock.update(cx, |right_dock, cx| {
4216                                            right_dock.resize_active_panel(Some(size), cx);
4217                                        });
4218                                    }
4219                                    DockPosition::Bottom => {
4220                                        let size = workspace.bounds.bottom() - e.event.position.y;
4221                                        workspace.bottom_dock.update(cx, |bottom_dock, cx| {
4222                                            bottom_dock.resize_active_panel(Some(size), cx);
4223                                        });
4224                                    }
4225                                },
4226                            ))
4227                        })
4228                        .child(
4229                            div()
4230                                .flex()
4231                                .flex_row()
4232                                .h_full()
4233                                // Left Dock
4234                                .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
4235                                    || {
4236                                        div()
4237                                            .flex()
4238                                            .flex_none()
4239                                            .overflow_hidden()
4240                                            .child(self.left_dock.clone())
4241                                    },
4242                                ))
4243                                // Panes
4244                                .child(
4245                                    div()
4246                                        .flex()
4247                                        .flex_col()
4248                                        .flex_1()
4249                                        .overflow_hidden()
4250                                        .child(
4251                                            h_flex()
4252                                                .flex_1()
4253                                                .when_some(paddings.0, |this, p| {
4254                                                    this.child(p.border_r_1())
4255                                                })
4256                                                .child(self.center.render(
4257                                                    &self.project,
4258                                                    &self.follower_states,
4259                                                    self.active_call(),
4260                                                    &self.active_pane,
4261                                                    self.zoomed.as_ref(),
4262                                                    &self.app_state,
4263                                                    cx,
4264                                                ))
4265                                                .when_some(paddings.1, |this, p| {
4266                                                    this.child(p.border_l_1())
4267                                                }),
4268                                        )
4269                                        .children(
4270                                            self.zoomed_position
4271                                                .ne(&Some(DockPosition::Bottom))
4272                                                .then(|| self.bottom_dock.clone()),
4273                                        ),
4274                                )
4275                                // Right Dock
4276                                .children(
4277                                    self.zoomed_position.ne(&Some(DockPosition::Right)).then(
4278                                        || {
4279                                            div()
4280                                                .flex()
4281                                                .flex_none()
4282                                                .overflow_hidden()
4283                                                .child(self.right_dock.clone())
4284                                        },
4285                                    ),
4286                                ),
4287                        )
4288                        .children(self.zoomed.as_ref().and_then(|view| {
4289                            let zoomed_view = view.upgrade()?;
4290                            let div = div()
4291                                .occlude()
4292                                .absolute()
4293                                .overflow_hidden()
4294                                .border_color(colors.border)
4295                                .bg(colors.background)
4296                                .child(zoomed_view)
4297                                .inset_0()
4298                                .shadow_lg();
4299
4300                            Some(match self.zoomed_position {
4301                                Some(DockPosition::Left) => div.right_2().border_r_1(),
4302                                Some(DockPosition::Right) => div.left_2().border_l_1(),
4303                                Some(DockPosition::Bottom) => div.top_2().border_t_1(),
4304                                None => div.top_2().bottom_2().left_2().right_2().border_1(),
4305                            })
4306                        }))
4307                        .child(self.modal_layer.clone())
4308                        .children(self.render_notifications(cx)),
4309                )
4310                .child(self.status_bar.clone())
4311                .children(if self.project.read(cx).is_disconnected() {
4312                    if let Some(render) = self.render_disconnected_overlay.take() {
4313                        let result = render(self, cx);
4314                        self.render_disconnected_overlay = Some(render);
4315                        Some(result)
4316                    } else {
4317                        None
4318                    }
4319                } else {
4320                    None
4321                }),
4322            cx,
4323        )
4324    }
4325}
4326
4327impl WorkspaceStore {
4328    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
4329        Self {
4330            workspaces: Default::default(),
4331            _subscriptions: vec![
4332                client.add_request_handler(cx.weak_model(), Self::handle_follow),
4333                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
4334            ],
4335            client,
4336        }
4337    }
4338
4339    pub fn update_followers(
4340        &self,
4341        project_id: Option<u64>,
4342        update: proto::update_followers::Variant,
4343        cx: &AppContext,
4344    ) -> Option<()> {
4345        let active_call = ActiveCall::try_global(cx)?;
4346        let room_id = active_call.read(cx).room()?.read(cx).id();
4347        self.client
4348            .send(proto::UpdateFollowers {
4349                room_id,
4350                project_id,
4351                variant: Some(update),
4352            })
4353            .log_err()
4354    }
4355
4356    pub async fn handle_follow(
4357        this: Model<Self>,
4358        envelope: TypedEnvelope<proto::Follow>,
4359        mut cx: AsyncAppContext,
4360    ) -> Result<proto::FollowResponse> {
4361        this.update(&mut cx, |this, cx| {
4362            let follower = Follower {
4363                project_id: envelope.payload.project_id,
4364                peer_id: envelope.original_sender_id()?,
4365            };
4366
4367            let mut response = proto::FollowResponse::default();
4368            this.workspaces.retain(|workspace| {
4369                workspace
4370                    .update(cx, |workspace, cx| {
4371                        let handler_response = workspace.handle_follow(follower.project_id, cx);
4372                        if response.views.is_empty() {
4373                            response.views = handler_response.views;
4374                        } else {
4375                            response.views.extend_from_slice(&handler_response.views);
4376                        }
4377
4378                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
4379                            if response.active_view_id.is_none()
4380                                || workspace.project.read(cx).remote_id() == follower.project_id
4381                            {
4382                                response.active_view_id = Some(active_view_id);
4383                            }
4384                        }
4385
4386                        if let Some(active_view) = handler_response.active_view.clone() {
4387                            if response.active_view_id.is_none()
4388                                || workspace.project.read(cx).remote_id() == follower.project_id
4389                            {
4390                                response.active_view = Some(active_view)
4391                            }
4392                        }
4393                    })
4394                    .is_ok()
4395            });
4396
4397            Ok(response)
4398        })?
4399    }
4400
4401    async fn handle_update_followers(
4402        this: Model<Self>,
4403        envelope: TypedEnvelope<proto::UpdateFollowers>,
4404        mut cx: AsyncAppContext,
4405    ) -> Result<()> {
4406        let leader_id = envelope.original_sender_id()?;
4407        let update = envelope.payload;
4408
4409        this.update(&mut cx, |this, cx| {
4410            this.workspaces.retain(|workspace| {
4411                workspace
4412                    .update(cx, |workspace, cx| {
4413                        let project_id = workspace.project.read(cx).remote_id();
4414                        if update.project_id != project_id && update.project_id.is_some() {
4415                            return;
4416                        }
4417                        workspace.handle_update_followers(leader_id, update.clone(), cx);
4418                    })
4419                    .is_ok()
4420            });
4421            Ok(())
4422        })?
4423    }
4424}
4425
4426impl ViewId {
4427    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4428        Ok(Self {
4429            creator: message
4430                .creator
4431                .ok_or_else(|| anyhow!("creator is missing"))?,
4432            id: message.id,
4433        })
4434    }
4435
4436    pub(crate) fn to_proto(&self) -> proto::ViewId {
4437        proto::ViewId {
4438            creator: Some(self.creator),
4439            id: self.id,
4440        }
4441    }
4442}
4443
4444pub trait WorkspaceHandle {
4445    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4446}
4447
4448impl WorkspaceHandle for View<Workspace> {
4449    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4450        self.read(cx)
4451            .worktrees(cx)
4452            .flat_map(|worktree| {
4453                let worktree_id = worktree.read(cx).id();
4454                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4455                    worktree_id,
4456                    path: f.path.clone(),
4457                })
4458            })
4459            .collect::<Vec<_>>()
4460    }
4461}
4462
4463impl std::fmt::Debug for OpenPaths {
4464    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4465        f.debug_struct("OpenPaths")
4466            .field("paths", &self.paths)
4467            .finish()
4468    }
4469}
4470
4471pub fn activate_workspace_for_project(
4472    cx: &mut AppContext,
4473    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4474) -> Option<WindowHandle<Workspace>> {
4475    for window in cx.windows() {
4476        let Some(workspace) = window.downcast::<Workspace>() else {
4477            continue;
4478        };
4479
4480        let predicate = workspace
4481            .update(cx, |workspace, cx| {
4482                let project = workspace.project.read(cx);
4483                if predicate(project, cx) {
4484                    cx.activate_window();
4485                    true
4486                } else {
4487                    false
4488                }
4489            })
4490            .log_err()
4491            .unwrap_or(false);
4492
4493        if predicate {
4494            return Some(workspace);
4495        }
4496    }
4497
4498    None
4499}
4500
4501pub async fn last_opened_workspace_paths() -> Option<LocalPaths> {
4502    DB.last_workspace().await.log_err().flatten()
4503}
4504
4505actions!(collab, [OpenChannelNotes]);
4506actions!(zed, [OpenLog]);
4507
4508async fn join_channel_internal(
4509    channel_id: ChannelId,
4510    app_state: &Arc<AppState>,
4511    requesting_window: Option<WindowHandle<Workspace>>,
4512    active_call: &Model<ActiveCall>,
4513    cx: &mut AsyncAppContext,
4514) -> Result<bool> {
4515    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4516        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4517            return (false, None);
4518        };
4519
4520        let already_in_channel = room.channel_id() == Some(channel_id);
4521        let should_prompt = room.is_sharing_project()
4522            && room.remote_participants().len() > 0
4523            && !already_in_channel;
4524        let open_room = if already_in_channel {
4525            active_call.room().cloned()
4526        } else {
4527            None
4528        };
4529        (should_prompt, open_room)
4530    })?;
4531
4532    if let Some(room) = open_room {
4533        let task = room.update(cx, |room, cx| {
4534            if let Some((project, host)) = room.most_active_project(cx) {
4535                return Some(join_in_room_project(project, host, app_state.clone(), cx));
4536            }
4537
4538            None
4539        })?;
4540        if let Some(task) = task {
4541            task.await?;
4542        }
4543        return anyhow::Ok(true);
4544    }
4545
4546    if should_prompt {
4547        if let Some(workspace) = requesting_window {
4548            let answer = workspace
4549                .update(cx, |_, cx| {
4550                    cx.prompt(
4551                        PromptLevel::Warning,
4552                        "Do you want to switch channels?",
4553                        Some("Leaving this call will unshare your current project."),
4554                        &["Yes, Join Channel", "Cancel"],
4555                    )
4556                })?
4557                .await;
4558
4559            if answer == Ok(1) {
4560                return Ok(false);
4561            }
4562        } else {
4563            return Ok(false); // unreachable!() hopefully
4564        }
4565    }
4566
4567    let client = cx.update(|cx| active_call.read(cx).client())?;
4568
4569    let mut client_status = client.status();
4570
4571    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4572    'outer: loop {
4573        let Some(status) = client_status.recv().await else {
4574            return Err(anyhow!("error connecting"));
4575        };
4576
4577        match status {
4578            Status::Connecting
4579            | Status::Authenticating
4580            | Status::Reconnecting
4581            | Status::Reauthenticating => continue,
4582            Status::Connected { .. } => break 'outer,
4583            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
4584            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
4585            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4586                return Err(ErrorCode::Disconnected.into());
4587            }
4588        }
4589    }
4590
4591    let room = active_call
4592        .update(cx, |active_call, cx| {
4593            active_call.join_channel(channel_id, cx)
4594        })?
4595        .await?;
4596
4597    let Some(room) = room else {
4598        return anyhow::Ok(true);
4599    };
4600
4601    room.update(cx, |room, _| room.room_update_completed())?
4602        .await;
4603
4604    let task = room.update(cx, |room, cx| {
4605        if let Some((project, host)) = room.most_active_project(cx) {
4606            return Some(join_in_room_project(project, host, app_state.clone(), cx));
4607        }
4608
4609        // If you are the first to join a channel, see if you should share your project.
4610        if room.remote_participants().is_empty() && !room.local_participant_is_guest() {
4611            if let Some(workspace) = requesting_window {
4612                let project = workspace.update(cx, |workspace, cx| {
4613                    let project = workspace.project.read(cx);
4614                    let is_dev_server = project.dev_server_project_id().is_some();
4615
4616                    if !is_dev_server && !CallSettings::get_global(cx).share_on_join {
4617                        return None;
4618                    }
4619
4620                    if (project.is_local() || is_dev_server)
4621                        && project.visible_worktrees(cx).any(|tree| {
4622                            tree.read(cx)
4623                                .root_entry()
4624                                .map_or(false, |entry| entry.is_dir())
4625                        })
4626                    {
4627                        Some(workspace.project.clone())
4628                    } else {
4629                        None
4630                    }
4631                });
4632                if let Ok(Some(project)) = project {
4633                    return Some(cx.spawn(|room, mut cx| async move {
4634                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
4635                            .await?;
4636                        Ok(())
4637                    }));
4638                }
4639            }
4640        }
4641
4642        None
4643    })?;
4644    if let Some(task) = task {
4645        task.await?;
4646        return anyhow::Ok(true);
4647    }
4648    anyhow::Ok(false)
4649}
4650
4651pub fn join_channel(
4652    channel_id: ChannelId,
4653    app_state: Arc<AppState>,
4654    requesting_window: Option<WindowHandle<Workspace>>,
4655    cx: &mut AppContext,
4656) -> Task<Result<()>> {
4657    let active_call = ActiveCall::global(cx);
4658    cx.spawn(|mut cx| async move {
4659        let result = join_channel_internal(
4660            channel_id,
4661            &app_state,
4662            requesting_window,
4663            &active_call,
4664            &mut cx,
4665        )
4666            .await;
4667
4668        // join channel succeeded, and opened a window
4669        if matches!(result, Ok(true)) {
4670            return anyhow::Ok(());
4671        }
4672
4673        // find an existing workspace to focus and show call controls
4674        let mut active_window =
4675            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4676        if active_window.is_none() {
4677            // no open workspaces, make one to show the error in (blergh)
4678            let (window_handle, _) = cx
4679                .update(|cx| {
4680                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4681                })?
4682                .await?;
4683
4684            if result.is_ok() {
4685                cx.update(|cx| {
4686                    cx.dispatch_action(&OpenChannelNotes);
4687                }).log_err();
4688            }
4689
4690            active_window = Some(window_handle);
4691        }
4692
4693        if let Err(err) = result {
4694            log::error!("failed to join channel: {}", err);
4695            if let Some(active_window) = active_window {
4696                active_window
4697                    .update(&mut cx, |_, cx| {
4698                        let detail: SharedString = match err.error_code() {
4699                            ErrorCode::SignedOut => {
4700                                "Please sign in to continue.".into()
4701                            }
4702                            ErrorCode::UpgradeRequired => {
4703                                "Your are running an unsupported version of Zed. Please update to continue.".into()
4704                            }
4705                            ErrorCode::NoSuchChannel => {
4706                                "No matching channel was found. Please check the link and try again.".into()
4707                            }
4708                            ErrorCode::Forbidden => {
4709                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
4710                            }
4711                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
4712                            _ => format!("{}\n\nPlease try again.", err).into(),
4713                        };
4714                        cx.prompt(
4715                            PromptLevel::Critical,
4716                            "Failed to join channel",
4717                            Some(&detail),
4718                            &["Ok"],
4719                        )
4720                    })?
4721                    .await
4722                    .ok();
4723            }
4724        }
4725
4726        // return ok, we showed the error to the user.
4727        return anyhow::Ok(());
4728    })
4729}
4730
4731pub async fn get_any_active_workspace(
4732    app_state: Arc<AppState>,
4733    mut cx: AsyncAppContext,
4734) -> anyhow::Result<WindowHandle<Workspace>> {
4735    // find an existing workspace to focus and show call controls
4736    let active_window = activate_any_workspace_window(&mut cx);
4737    if active_window.is_none() {
4738        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4739            .await?;
4740    }
4741    activate_any_workspace_window(&mut cx).context("could not open zed")
4742}
4743
4744fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4745    cx.update(|cx| {
4746        if let Some(workspace_window) = cx
4747            .active_window()
4748            .and_then(|window| window.downcast::<Workspace>())
4749        {
4750            return Some(workspace_window);
4751        }
4752
4753        for window in cx.windows() {
4754            if let Some(workspace_window) = window.downcast::<Workspace>() {
4755                workspace_window
4756                    .update(cx, |_, cx| cx.activate_window())
4757                    .ok();
4758                return Some(workspace_window);
4759            }
4760        }
4761        None
4762    })
4763    .ok()
4764    .flatten()
4765}
4766
4767fn local_workspace_windows(cx: &AppContext) -> Vec<WindowHandle<Workspace>> {
4768    cx.windows()
4769        .into_iter()
4770        .filter_map(|window| window.downcast::<Workspace>())
4771        .filter(|workspace| {
4772            workspace
4773                .read(cx)
4774                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
4775        })
4776        .collect()
4777}
4778
4779#[derive(Default)]
4780pub struct OpenOptions {
4781    pub open_new_workspace: Option<bool>,
4782    pub replace_window: Option<WindowHandle<Workspace>>,
4783}
4784
4785#[allow(clippy::type_complexity)]
4786pub fn open_paths(
4787    abs_paths: &[PathBuf],
4788    app_state: Arc<AppState>,
4789    open_options: OpenOptions,
4790    cx: &mut AppContext,
4791) -> Task<
4792    anyhow::Result<(
4793        WindowHandle<Workspace>,
4794        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4795    )>,
4796> {
4797    let abs_paths = abs_paths.to_vec();
4798    let mut existing = None;
4799    let mut best_match = None;
4800    let mut open_visible = OpenVisible::All;
4801
4802    if open_options.open_new_workspace != Some(true) {
4803        for window in local_workspace_windows(cx) {
4804            if let Ok(workspace) = window.read(cx) {
4805                let m = workspace
4806                    .project
4807                    .read(cx)
4808                    .visibility_for_paths(&abs_paths, cx);
4809                if m > best_match {
4810                    existing = Some(window);
4811                    best_match = m;
4812                } else if best_match.is_none() && open_options.open_new_workspace == Some(false) {
4813                    existing = Some(window)
4814                }
4815            }
4816        }
4817    }
4818
4819    cx.spawn(move |mut cx| async move {
4820        if open_options.open_new_workspace.is_none() && existing.is_none() {
4821            let all_files = abs_paths.iter().map(|path| app_state.fs.metadata(path));
4822            if futures::future::join_all(all_files)
4823                .await
4824                .into_iter()
4825                .filter_map(|result| result.ok().flatten())
4826                .all(|file| !file.is_dir)
4827            {
4828                cx.update(|cx| {
4829                    for window in local_workspace_windows(cx) {
4830                        if let Ok(workspace) = window.read(cx) {
4831                            let project = workspace.project().read(cx);
4832                            if project.is_remote() {
4833                                continue;
4834                            }
4835                            existing = Some(window);
4836                            open_visible = OpenVisible::None;
4837                            break;
4838                        }
4839                    }
4840                })?;
4841            }
4842        }
4843
4844        if let Some(existing) = existing {
4845            Ok((
4846                existing,
4847                existing
4848                    .update(&mut cx, |workspace, cx| {
4849                        cx.activate_window();
4850                        workspace.open_paths(abs_paths, open_visible, None, cx)
4851                    })?
4852                    .await,
4853            ))
4854        } else {
4855            cx.update(move |cx| {
4856                Workspace::new_local(
4857                    abs_paths,
4858                    app_state.clone(),
4859                    open_options.replace_window,
4860                    cx,
4861                )
4862            })?
4863            .await
4864        }
4865    })
4866}
4867
4868pub fn open_new(
4869    app_state: Arc<AppState>,
4870    cx: &mut AppContext,
4871    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4872) -> Task<anyhow::Result<()>> {
4873    let task = Workspace::new_local(Vec::new(), app_state, None, cx);
4874    cx.spawn(|mut cx| async move {
4875        let (workspace, opened_paths) = task.await?;
4876        workspace.update(&mut cx, |workspace, cx| {
4877            if opened_paths.is_empty() {
4878                init(workspace, cx)
4879            }
4880        })?;
4881        Ok(())
4882    })
4883}
4884
4885pub fn create_and_open_local_file(
4886    path: &'static Path,
4887    cx: &mut ViewContext<Workspace>,
4888    default_content: impl 'static + Send + FnOnce() -> Rope,
4889) -> Task<Result<Box<dyn ItemHandle>>> {
4890    cx.spawn(|workspace, mut cx| async move {
4891        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4892        if !fs.is_file(path).await {
4893            fs.create_file(path, Default::default()).await?;
4894            fs.save(path, &default_content(), Default::default())
4895                .await?;
4896        }
4897
4898        let mut items = workspace
4899            .update(&mut cx, |workspace, cx| {
4900                workspace.with_local_workspace(cx, |workspace, cx| {
4901                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4902                })
4903            })?
4904            .await?
4905            .await;
4906
4907        let item = items.pop().flatten();
4908        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4909    })
4910}
4911
4912pub fn join_hosted_project(
4913    hosted_project_id: ProjectId,
4914    app_state: Arc<AppState>,
4915    cx: &mut AppContext,
4916) -> Task<Result<()>> {
4917    cx.spawn(|mut cx| async move {
4918        let existing_window = cx.update(|cx| {
4919            cx.windows().into_iter().find_map(|window| {
4920                let workspace = window.downcast::<Workspace>()?;
4921                workspace
4922                    .read(cx)
4923                    .is_ok_and(|workspace| {
4924                        workspace.project().read(cx).hosted_project_id() == Some(hosted_project_id)
4925                    })
4926                    .then(|| workspace)
4927            })
4928        })?;
4929
4930        let workspace = if let Some(existing_window) = existing_window {
4931            existing_window
4932        } else {
4933            let project = Project::hosted(
4934                hosted_project_id,
4935                app_state.user_store.clone(),
4936                app_state.client.clone(),
4937                app_state.languages.clone(),
4938                app_state.fs.clone(),
4939                cx.clone(),
4940            )
4941            .await?;
4942
4943            let window_bounds_override = window_bounds_env_override();
4944            cx.update(|cx| {
4945                let mut options = (app_state.build_window_options)(None, cx);
4946                options.window_bounds =
4947                    window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
4948                cx.open_window(options, |cx| {
4949                    cx.new_view(|cx| {
4950                        Workspace::new(Default::default(), project, app_state.clone(), cx)
4951                    })
4952                })
4953            })??
4954        };
4955
4956        workspace.update(&mut cx, |_, cx| {
4957            cx.activate(true);
4958            cx.activate_window();
4959        })?;
4960
4961        Ok(())
4962    })
4963}
4964
4965pub fn join_dev_server_project(
4966    dev_server_project_id: DevServerProjectId,
4967    project_id: ProjectId,
4968    app_state: Arc<AppState>,
4969    window_to_replace: Option<WindowHandle<Workspace>>,
4970    cx: &mut AppContext,
4971) -> Task<Result<WindowHandle<Workspace>>> {
4972    let windows = cx.windows();
4973    cx.spawn(|mut cx| async move {
4974        let existing_workspace = windows.into_iter().find_map(|window| {
4975            window.downcast::<Workspace>().and_then(|window| {
4976                window
4977                    .update(&mut cx, |workspace, cx| {
4978                        if workspace.project().read(cx).remote_id() == Some(project_id.0) {
4979                            Some(window)
4980                        } else {
4981                            None
4982                        }
4983                    })
4984                    .unwrap_or(None)
4985            })
4986        });
4987
4988        let workspace = if let Some(existing_workspace) = existing_workspace {
4989            existing_workspace
4990        } else {
4991            let project = Project::remote(
4992                project_id.0,
4993                app_state.client.clone(),
4994                app_state.user_store.clone(),
4995                app_state.languages.clone(),
4996                app_state.fs.clone(),
4997                cx.clone(),
4998            )
4999            .await?;
5000
5001            let serialized_workspace: Option<SerializedWorkspace> =
5002                persistence::DB.workspace_for_dev_server_project(dev_server_project_id);
5003
5004            let workspace_id = if let Some(serialized_workspace) = serialized_workspace {
5005                serialized_workspace.id
5006            } else {
5007                persistence::DB.next_id().await?
5008            };
5009
5010            if let Some(window_to_replace) = window_to_replace {
5011                cx.update_window(window_to_replace.into(), |_, cx| {
5012                    cx.replace_root_view(|cx| {
5013                        Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
5014                    });
5015                })?;
5016                window_to_replace
5017            } else {
5018                let window_bounds_override = window_bounds_env_override();
5019                cx.update(|cx| {
5020                    let mut options = (app_state.build_window_options)(None, cx);
5021                    options.window_bounds =
5022                        window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
5023                    cx.open_window(options, |cx| {
5024                        cx.new_view(|cx| {
5025                            Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
5026                        })
5027                    })
5028                })??
5029            }
5030        };
5031
5032        workspace.update(&mut cx, |_, cx| {
5033            cx.activate(true);
5034            cx.activate_window();
5035        })?;
5036
5037        anyhow::Ok(workspace)
5038    })
5039}
5040
5041pub fn join_in_room_project(
5042    project_id: u64,
5043    follow_user_id: u64,
5044    app_state: Arc<AppState>,
5045    cx: &mut AppContext,
5046) -> Task<Result<()>> {
5047    let windows = cx.windows();
5048    cx.spawn(|mut cx| async move {
5049        let existing_workspace = windows.into_iter().find_map(|window| {
5050            window.downcast::<Workspace>().and_then(|window| {
5051                window
5052                    .update(&mut cx, |workspace, cx| {
5053                        if workspace.project().read(cx).remote_id() == Some(project_id) {
5054                            Some(window)
5055                        } else {
5056                            None
5057                        }
5058                    })
5059                    .unwrap_or(None)
5060            })
5061        });
5062
5063        let workspace = if let Some(existing_workspace) = existing_workspace {
5064            existing_workspace
5065        } else {
5066            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
5067            let room = active_call
5068                .read_with(&cx, |call, _| call.room().cloned())?
5069                .ok_or_else(|| anyhow!("not in a call"))?;
5070            let project = room
5071                .update(&mut cx, |room, cx| {
5072                    room.join_project(
5073                        project_id,
5074                        app_state.languages.clone(),
5075                        app_state.fs.clone(),
5076                        cx,
5077                    )
5078                })?
5079                .await?;
5080
5081            let window_bounds_override = window_bounds_env_override();
5082            cx.update(|cx| {
5083                let mut options = (app_state.build_window_options)(None, cx);
5084                options.window_bounds =
5085                    window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
5086                cx.open_window(options, |cx| {
5087                    cx.new_view(|cx| {
5088                        Workspace::new(Default::default(), project, app_state.clone(), cx)
5089                    })
5090                })
5091            })??
5092        };
5093
5094        workspace.update(&mut cx, |workspace, cx| {
5095            cx.activate(true);
5096            cx.activate_window();
5097
5098            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
5099                let follow_peer_id = room
5100                    .read(cx)
5101                    .remote_participants()
5102                    .iter()
5103                    .find(|(_, participant)| participant.user.id == follow_user_id)
5104                    .map(|(_, p)| p.peer_id)
5105                    .or_else(|| {
5106                        // If we couldn't follow the given user, follow the host instead.
5107                        let collaborator = workspace
5108                            .project()
5109                            .read(cx)
5110                            .collaborators()
5111                            .values()
5112                            .find(|collaborator| collaborator.replica_id == 0)?;
5113                        Some(collaborator.peer_id)
5114                    });
5115
5116                if let Some(follow_peer_id) = follow_peer_id {
5117                    workspace.follow(follow_peer_id, cx);
5118                }
5119            }
5120        })?;
5121
5122        anyhow::Ok(())
5123    })
5124}
5125
5126pub fn reload(reload: &Reload, cx: &mut AppContext) {
5127    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
5128    let mut workspace_windows = cx
5129        .windows()
5130        .into_iter()
5131        .filter_map(|window| window.downcast::<Workspace>())
5132        .collect::<Vec<_>>();
5133
5134    // If multiple windows have unsaved changes, and need a save prompt,
5135    // prompt in the active window before switching to a different window.
5136    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
5137
5138    let mut prompt = None;
5139    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
5140        prompt = window
5141            .update(cx, |_, cx| {
5142                cx.prompt(
5143                    PromptLevel::Info,
5144                    "Are you sure you want to restart?",
5145                    None,
5146                    &["Restart", "Cancel"],
5147                )
5148            })
5149            .ok();
5150    }
5151
5152    let binary_path = reload.binary_path.clone();
5153    cx.spawn(|mut cx| async move {
5154        if let Some(prompt) = prompt {
5155            let answer = prompt.await?;
5156            if answer != 0 {
5157                return Ok(());
5158            }
5159        }
5160
5161        // If the user cancels any save prompt, then keep the app open.
5162        for window in workspace_windows {
5163            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
5164                workspace.prepare_to_close(true, cx)
5165            }) {
5166                if !should_close.await? {
5167                    return Ok(());
5168                }
5169            }
5170        }
5171
5172        cx.update(|cx| cx.restart(binary_path))
5173    })
5174    .detach_and_log_err(cx);
5175}
5176
5177fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
5178    let mut parts = value.split(',');
5179    let x: usize = parts.next()?.parse().ok()?;
5180    let y: usize = parts.next()?.parse().ok()?;
5181    Some(point(px(x as f32), px(y as f32)))
5182}
5183
5184fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
5185    let mut parts = value.split(',');
5186    let width: usize = parts.next()?.parse().ok()?;
5187    let height: usize = parts.next()?.parse().ok()?;
5188    Some(size(px(width as f32), px(height as f32)))
5189}
5190
5191#[cfg(test)]
5192mod tests {
5193    use std::{cell::RefCell, rc::Rc};
5194
5195    use super::*;
5196    use crate::{
5197        dock::{test::TestPanel, PanelEvent},
5198        item::{
5199            test::{TestItem, TestProjectItem},
5200            ItemEvent,
5201        },
5202    };
5203    use fs::FakeFs;
5204    use gpui::{
5205        px, DismissEvent, Empty, EventEmitter, FocusHandle, FocusableView, Render, TestAppContext,
5206        UpdateGlobal, VisualTestContext,
5207    };
5208    use project::{Project, ProjectEntryId};
5209    use serde_json::json;
5210    use settings::SettingsStore;
5211
5212    #[gpui::test]
5213    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
5214        init_test(cx);
5215
5216        let fs = FakeFs::new(cx.executor());
5217        let project = Project::test(fs, [], cx).await;
5218        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5219
5220        // Adding an item with no ambiguity renders the tab without detail.
5221        let item1 = cx.new_view(|cx| {
5222            let mut item = TestItem::new(cx);
5223            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
5224            item
5225        });
5226        workspace.update(cx, |workspace, cx| {
5227            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, cx);
5228        });
5229        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
5230
5231        // Adding an item that creates ambiguity increases the level of detail on
5232        // both tabs.
5233        let item2 = cx.new_view(|cx| {
5234            let mut item = TestItem::new(cx);
5235            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5236            item
5237        });
5238        workspace.update(cx, |workspace, cx| {
5239            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5240        });
5241        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5242        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5243
5244        // Adding an item that creates ambiguity increases the level of detail only
5245        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
5246        // we stop at the highest detail available.
5247        let item3 = cx.new_view(|cx| {
5248            let mut item = TestItem::new(cx);
5249            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5250            item
5251        });
5252        workspace.update(cx, |workspace, cx| {
5253            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5254        });
5255        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5256        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
5257        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
5258    }
5259
5260    #[gpui::test]
5261    async fn test_tracking_active_path(cx: &mut TestAppContext) {
5262        init_test(cx);
5263
5264        let fs = FakeFs::new(cx.executor());
5265        fs.insert_tree(
5266            "/root1",
5267            json!({
5268                "one.txt": "",
5269                "two.txt": "",
5270            }),
5271        )
5272        .await;
5273        fs.insert_tree(
5274            "/root2",
5275            json!({
5276                "three.txt": "",
5277            }),
5278        )
5279        .await;
5280
5281        let project = Project::test(fs, ["root1".as_ref()], cx).await;
5282        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5283        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5284        let worktree_id = project.update(cx, |project, cx| {
5285            project.worktrees().next().unwrap().read(cx).id()
5286        });
5287
5288        let item1 = cx.new_view(|cx| {
5289            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
5290        });
5291        let item2 = cx.new_view(|cx| {
5292            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
5293        });
5294
5295        // Add an item to an empty pane
5296        workspace.update(cx, |workspace, cx| {
5297            workspace.add_item_to_active_pane(Box::new(item1), None, cx)
5298        });
5299        project.update(cx, |project, cx| {
5300            assert_eq!(
5301                project.active_entry(),
5302                project
5303                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5304                    .map(|e| e.id)
5305            );
5306        });
5307        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5308
5309        // Add a second item to a non-empty pane
5310        workspace.update(cx, |workspace, cx| {
5311            workspace.add_item_to_active_pane(Box::new(item2), None, cx)
5312        });
5313        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
5314        project.update(cx, |project, cx| {
5315            assert_eq!(
5316                project.active_entry(),
5317                project
5318                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
5319                    .map(|e| e.id)
5320            );
5321        });
5322
5323        // Close the active item
5324        pane.update(cx, |pane, cx| {
5325            pane.close_active_item(&Default::default(), cx).unwrap()
5326        })
5327        .await
5328        .unwrap();
5329        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5330        project.update(cx, |project, cx| {
5331            assert_eq!(
5332                project.active_entry(),
5333                project
5334                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5335                    .map(|e| e.id)
5336            );
5337        });
5338
5339        // Add a project folder
5340        project
5341            .update(cx, |project, cx| {
5342                project.find_or_create_local_worktree("root2", true, cx)
5343            })
5344            .await
5345            .unwrap();
5346        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
5347
5348        // Remove a project folder
5349        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
5350        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
5351    }
5352
5353    #[gpui::test]
5354    async fn test_close_window(cx: &mut TestAppContext) {
5355        init_test(cx);
5356
5357        let fs = FakeFs::new(cx.executor());
5358        fs.insert_tree("/root", json!({ "one": "" })).await;
5359
5360        let project = Project::test(fs, ["root".as_ref()], cx).await;
5361        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5362
5363        // When there are no dirty items, there's nothing to do.
5364        let item1 = cx.new_view(|cx| TestItem::new(cx));
5365        workspace.update(cx, |w, cx| {
5366            w.add_item_to_active_pane(Box::new(item1.clone()), None, cx)
5367        });
5368        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5369        assert!(task.await.unwrap());
5370
5371        // When there are dirty untitled items, prompt to save each one. If the user
5372        // cancels any prompt, then abort.
5373        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
5374        let item3 = cx.new_view(|cx| {
5375            TestItem::new(cx)
5376                .with_dirty(true)
5377                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5378        });
5379        workspace.update(cx, |w, cx| {
5380            w.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5381            w.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5382        });
5383        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5384        cx.executor().run_until_parked();
5385        cx.simulate_prompt_answer(2); // cancel save all
5386        cx.executor().run_until_parked();
5387        cx.simulate_prompt_answer(2); // cancel save all
5388        cx.executor().run_until_parked();
5389        assert!(!cx.has_pending_prompt());
5390        assert!(!task.await.unwrap());
5391    }
5392
5393    #[gpui::test]
5394    async fn test_close_pane_items(cx: &mut TestAppContext) {
5395        init_test(cx);
5396
5397        let fs = FakeFs::new(cx.executor());
5398
5399        let project = Project::test(fs, None, cx).await;
5400        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5401
5402        let item1 = cx.new_view(|cx| {
5403            TestItem::new(cx)
5404                .with_dirty(true)
5405                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5406        });
5407        let item2 = cx.new_view(|cx| {
5408            TestItem::new(cx)
5409                .with_dirty(true)
5410                .with_conflict(true)
5411                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
5412        });
5413        let item3 = cx.new_view(|cx| {
5414            TestItem::new(cx)
5415                .with_dirty(true)
5416                .with_conflict(true)
5417                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
5418        });
5419        let item4 = cx.new_view(|cx| {
5420            TestItem::new(cx)
5421                .with_dirty(true)
5422                .with_project_items(&[TestProjectItem::new_untitled(cx)])
5423        });
5424        let pane = workspace.update(cx, |workspace, cx| {
5425            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, cx);
5426            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5427            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5428            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, cx);
5429            workspace.active_pane().clone()
5430        });
5431
5432        let close_items = pane.update(cx, |pane, cx| {
5433            pane.activate_item(1, true, true, cx);
5434            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5435            let item1_id = item1.item_id();
5436            let item3_id = item3.item_id();
5437            let item4_id = item4.item_id();
5438            pane.close_items(cx, SaveIntent::Close, move |id| {
5439                [item1_id, item3_id, item4_id].contains(&id)
5440            })
5441        });
5442        cx.executor().run_until_parked();
5443
5444        assert!(cx.has_pending_prompt());
5445        // Ignore "Save all" prompt
5446        cx.simulate_prompt_answer(2);
5447        cx.executor().run_until_parked();
5448        // There's a prompt to save item 1.
5449        pane.update(cx, |pane, _| {
5450            assert_eq!(pane.items_len(), 4);
5451            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
5452        });
5453        // Confirm saving item 1.
5454        cx.simulate_prompt_answer(0);
5455        cx.executor().run_until_parked();
5456
5457        // Item 1 is saved. There's a prompt to save item 3.
5458        pane.update(cx, |pane, cx| {
5459            assert_eq!(item1.read(cx).save_count, 1);
5460            assert_eq!(item1.read(cx).save_as_count, 0);
5461            assert_eq!(item1.read(cx).reload_count, 0);
5462            assert_eq!(pane.items_len(), 3);
5463            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
5464        });
5465        assert!(cx.has_pending_prompt());
5466
5467        // Cancel saving item 3.
5468        cx.simulate_prompt_answer(1);
5469        cx.executor().run_until_parked();
5470
5471        // Item 3 is reloaded. There's a prompt to save item 4.
5472        pane.update(cx, |pane, cx| {
5473            assert_eq!(item3.read(cx).save_count, 0);
5474            assert_eq!(item3.read(cx).save_as_count, 0);
5475            assert_eq!(item3.read(cx).reload_count, 1);
5476            assert_eq!(pane.items_len(), 2);
5477            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
5478        });
5479        assert!(cx.has_pending_prompt());
5480
5481        // Confirm saving item 4.
5482        cx.simulate_prompt_answer(0);
5483        cx.executor().run_until_parked();
5484
5485        // There's a prompt for a path for item 4.
5486        cx.simulate_new_path_selection(|_| Some(Default::default()));
5487        close_items.await.unwrap();
5488
5489        // The requested items are closed.
5490        pane.update(cx, |pane, cx| {
5491            assert_eq!(item4.read(cx).save_count, 0);
5492            assert_eq!(item4.read(cx).save_as_count, 1);
5493            assert_eq!(item4.read(cx).reload_count, 0);
5494            assert_eq!(pane.items_len(), 1);
5495            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5496        });
5497    }
5498
5499    #[gpui::test]
5500    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
5501        init_test(cx);
5502
5503        let fs = FakeFs::new(cx.executor());
5504        let project = Project::test(fs, [], cx).await;
5505        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5506
5507        // Create several workspace items with single project entries, and two
5508        // workspace items with multiple project entries.
5509        let single_entry_items = (0..=4)
5510            .map(|project_entry_id| {
5511                cx.new_view(|cx| {
5512                    TestItem::new(cx)
5513                        .with_dirty(true)
5514                        .with_project_items(&[TestProjectItem::new(
5515                            project_entry_id,
5516                            &format!("{project_entry_id}.txt"),
5517                            cx,
5518                        )])
5519                })
5520            })
5521            .collect::<Vec<_>>();
5522        let item_2_3 = cx.new_view(|cx| {
5523            TestItem::new(cx)
5524                .with_dirty(true)
5525                .with_singleton(false)
5526                .with_project_items(&[
5527                    single_entry_items[2].read(cx).project_items[0].clone(),
5528                    single_entry_items[3].read(cx).project_items[0].clone(),
5529                ])
5530        });
5531        let item_3_4 = cx.new_view(|cx| {
5532            TestItem::new(cx)
5533                .with_dirty(true)
5534                .with_singleton(false)
5535                .with_project_items(&[
5536                    single_entry_items[3].read(cx).project_items[0].clone(),
5537                    single_entry_items[4].read(cx).project_items[0].clone(),
5538                ])
5539        });
5540
5541        // Create two panes that contain the following project entries:
5542        //   left pane:
5543        //     multi-entry items:   (2, 3)
5544        //     single-entry items:  0, 1, 2, 3, 4
5545        //   right pane:
5546        //     single-entry items:  1
5547        //     multi-entry items:   (3, 4)
5548        let left_pane = workspace.update(cx, |workspace, cx| {
5549            let left_pane = workspace.active_pane().clone();
5550            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, cx);
5551            for item in single_entry_items {
5552                workspace.add_item_to_active_pane(Box::new(item), None, cx);
5553            }
5554            left_pane.update(cx, |pane, cx| {
5555                pane.activate_item(2, true, true, cx);
5556            });
5557
5558            let right_pane = workspace
5559                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
5560                .unwrap();
5561
5562            right_pane.update(cx, |pane, cx| {
5563                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
5564            });
5565
5566            left_pane
5567        });
5568
5569        cx.focus_view(&left_pane);
5570
5571        // When closing all of the items in the left pane, we should be prompted twice:
5572        // once for project entry 0, and once for project entry 2. Project entries 1,
5573        // 3, and 4 are all still open in the other paten. After those two
5574        // prompts, the task should complete.
5575
5576        let close = left_pane.update(cx, |pane, cx| {
5577            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
5578        });
5579        cx.executor().run_until_parked();
5580
5581        // Discard "Save all" prompt
5582        cx.simulate_prompt_answer(2);
5583
5584        cx.executor().run_until_parked();
5585        left_pane.update(cx, |pane, cx| {
5586            assert_eq!(
5587                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5588                &[ProjectEntryId::from_proto(0)]
5589            );
5590        });
5591        cx.simulate_prompt_answer(0);
5592
5593        cx.executor().run_until_parked();
5594        left_pane.update(cx, |pane, cx| {
5595            assert_eq!(
5596                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5597                &[ProjectEntryId::from_proto(2)]
5598            );
5599        });
5600        cx.simulate_prompt_answer(0);
5601
5602        cx.executor().run_until_parked();
5603        close.await.unwrap();
5604        left_pane.update(cx, |pane, _| {
5605            assert_eq!(pane.items_len(), 0);
5606        });
5607    }
5608
5609    #[gpui::test]
5610    async fn test_autosave(cx: &mut gpui::TestAppContext) {
5611        init_test(cx);
5612
5613        let fs = FakeFs::new(cx.executor());
5614        let project = Project::test(fs, [], cx).await;
5615        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5616        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5617
5618        let item = cx.new_view(|cx| {
5619            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5620        });
5621        let item_id = item.entity_id();
5622        workspace.update(cx, |workspace, cx| {
5623            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
5624        });
5625
5626        // Autosave on window change.
5627        item.update(cx, |item, cx| {
5628            SettingsStore::update_global(cx, |settings, cx| {
5629                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5630                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
5631                })
5632            });
5633            item.is_dirty = true;
5634        });
5635
5636        // Deactivating the window saves the file.
5637        cx.deactivate_window();
5638        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5639
5640        // Re-activating the window doesn't save the file.
5641        cx.update(|cx| cx.activate_window());
5642        cx.executor().run_until_parked();
5643        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5644
5645        // Autosave on focus change.
5646        item.update(cx, |item, cx| {
5647            cx.focus_self();
5648            SettingsStore::update_global(cx, |settings, cx| {
5649                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5650                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5651                })
5652            });
5653            item.is_dirty = true;
5654        });
5655
5656        // Blurring the item saves the file.
5657        item.update(cx, |_, cx| cx.blur());
5658        cx.executor().run_until_parked();
5659        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
5660
5661        // Deactivating the window still saves the file.
5662        item.update(cx, |item, cx| {
5663            cx.focus_self();
5664            item.is_dirty = true;
5665        });
5666        cx.deactivate_window();
5667        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5668
5669        // Autosave after delay.
5670        item.update(cx, |item, cx| {
5671            SettingsStore::update_global(cx, |settings, cx| {
5672                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5673                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5674                })
5675            });
5676            item.is_dirty = true;
5677            cx.emit(ItemEvent::Edit);
5678        });
5679
5680        // Delay hasn't fully expired, so the file is still dirty and unsaved.
5681        cx.executor().advance_clock(Duration::from_millis(250));
5682        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5683
5684        // After delay expires, the file is saved.
5685        cx.executor().advance_clock(Duration::from_millis(250));
5686        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
5687
5688        // Autosave on focus change, ensuring closing the tab counts as such.
5689        item.update(cx, |item, cx| {
5690            SettingsStore::update_global(cx, |settings, cx| {
5691                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5692                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5693                })
5694            });
5695            item.is_dirty = true;
5696        });
5697
5698        pane.update(cx, |pane, cx| {
5699            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5700        })
5701        .await
5702        .unwrap();
5703        assert!(!cx.has_pending_prompt());
5704        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5705
5706        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5707        workspace.update(cx, |workspace, cx| {
5708            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
5709        });
5710        item.update(cx, |item, cx| {
5711            item.project_items[0].update(cx, |item, _| {
5712                item.entry_id = None;
5713            });
5714            item.is_dirty = true;
5715            cx.blur();
5716        });
5717        cx.run_until_parked();
5718        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5719
5720        // Ensure autosave is prevented for deleted files also when closing the buffer.
5721        let _close_items = pane.update(cx, |pane, cx| {
5722            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5723        });
5724        cx.run_until_parked();
5725        assert!(cx.has_pending_prompt());
5726        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5727    }
5728
5729    #[gpui::test]
5730    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5731        init_test(cx);
5732
5733        let fs = FakeFs::new(cx.executor());
5734
5735        let project = Project::test(fs, [], cx).await;
5736        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5737
5738        let item = cx.new_view(|cx| {
5739            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5740        });
5741        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5742        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
5743        let toolbar_notify_count = Rc::new(RefCell::new(0));
5744
5745        workspace.update(cx, |workspace, cx| {
5746            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
5747            let toolbar_notification_count = toolbar_notify_count.clone();
5748            cx.observe(&toolbar, move |_, _, _| {
5749                *toolbar_notification_count.borrow_mut() += 1
5750            })
5751            .detach();
5752        });
5753
5754        pane.update(cx, |pane, _| {
5755            assert!(!pane.can_navigate_backward());
5756            assert!(!pane.can_navigate_forward());
5757        });
5758
5759        item.update(cx, |item, cx| {
5760            item.set_state("one".to_string(), cx);
5761        });
5762
5763        // Toolbar must be notified to re-render the navigation buttons
5764        assert_eq!(*toolbar_notify_count.borrow(), 1);
5765
5766        pane.update(cx, |pane, _| {
5767            assert!(pane.can_navigate_backward());
5768            assert!(!pane.can_navigate_forward());
5769        });
5770
5771        workspace
5772            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5773            .await
5774            .unwrap();
5775
5776        assert_eq!(*toolbar_notify_count.borrow(), 2);
5777        pane.update(cx, |pane, _| {
5778            assert!(!pane.can_navigate_backward());
5779            assert!(pane.can_navigate_forward());
5780        });
5781    }
5782
5783    #[gpui::test]
5784    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5785        init_test(cx);
5786        let fs = FakeFs::new(cx.executor());
5787
5788        let project = Project::test(fs, [], cx).await;
5789        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5790
5791        let panel = workspace.update(cx, |workspace, cx| {
5792            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5793            workspace.add_panel(panel.clone(), cx);
5794
5795            workspace
5796                .right_dock()
5797                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5798
5799            panel
5800        });
5801
5802        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5803        pane.update(cx, |pane, cx| {
5804            let item = cx.new_view(|cx| TestItem::new(cx));
5805            pane.add_item(Box::new(item), true, true, None, cx);
5806        });
5807
5808        // Transfer focus from center to panel
5809        workspace.update(cx, |workspace, cx| {
5810            workspace.toggle_panel_focus::<TestPanel>(cx);
5811        });
5812
5813        workspace.update(cx, |workspace, cx| {
5814            assert!(workspace.right_dock().read(cx).is_open());
5815            assert!(!panel.is_zoomed(cx));
5816            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5817        });
5818
5819        // Transfer focus from panel to center
5820        workspace.update(cx, |workspace, cx| {
5821            workspace.toggle_panel_focus::<TestPanel>(cx);
5822        });
5823
5824        workspace.update(cx, |workspace, cx| {
5825            assert!(workspace.right_dock().read(cx).is_open());
5826            assert!(!panel.is_zoomed(cx));
5827            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5828        });
5829
5830        // Close the dock
5831        workspace.update(cx, |workspace, cx| {
5832            workspace.toggle_dock(DockPosition::Right, cx);
5833        });
5834
5835        workspace.update(cx, |workspace, cx| {
5836            assert!(!workspace.right_dock().read(cx).is_open());
5837            assert!(!panel.is_zoomed(cx));
5838            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5839        });
5840
5841        // Open the dock
5842        workspace.update(cx, |workspace, cx| {
5843            workspace.toggle_dock(DockPosition::Right, cx);
5844        });
5845
5846        workspace.update(cx, |workspace, cx| {
5847            assert!(workspace.right_dock().read(cx).is_open());
5848            assert!(!panel.is_zoomed(cx));
5849            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5850        });
5851
5852        // Focus and zoom panel
5853        panel.update(cx, |panel, cx| {
5854            cx.focus_self();
5855            panel.set_zoomed(true, cx)
5856        });
5857
5858        workspace.update(cx, |workspace, cx| {
5859            assert!(workspace.right_dock().read(cx).is_open());
5860            assert!(panel.is_zoomed(cx));
5861            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5862        });
5863
5864        // Transfer focus to the center closes the dock
5865        workspace.update(cx, |workspace, cx| {
5866            workspace.toggle_panel_focus::<TestPanel>(cx);
5867        });
5868
5869        workspace.update(cx, |workspace, cx| {
5870            assert!(!workspace.right_dock().read(cx).is_open());
5871            assert!(panel.is_zoomed(cx));
5872            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5873        });
5874
5875        // Transferring focus back to the panel keeps it zoomed
5876        workspace.update(cx, |workspace, cx| {
5877            workspace.toggle_panel_focus::<TestPanel>(cx);
5878        });
5879
5880        workspace.update(cx, |workspace, cx| {
5881            assert!(workspace.right_dock().read(cx).is_open());
5882            assert!(panel.is_zoomed(cx));
5883            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5884        });
5885
5886        // Close the dock while it is zoomed
5887        workspace.update(cx, |workspace, cx| {
5888            workspace.toggle_dock(DockPosition::Right, cx)
5889        });
5890
5891        workspace.update(cx, |workspace, cx| {
5892            assert!(!workspace.right_dock().read(cx).is_open());
5893            assert!(panel.is_zoomed(cx));
5894            assert!(workspace.zoomed.is_none());
5895            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5896        });
5897
5898        // Opening the dock, when it's zoomed, retains focus
5899        workspace.update(cx, |workspace, cx| {
5900            workspace.toggle_dock(DockPosition::Right, cx)
5901        });
5902
5903        workspace.update(cx, |workspace, cx| {
5904            assert!(workspace.right_dock().read(cx).is_open());
5905            assert!(panel.is_zoomed(cx));
5906            assert!(workspace.zoomed.is_some());
5907            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5908        });
5909
5910        // Unzoom and close the panel, zoom the active pane.
5911        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5912        workspace.update(cx, |workspace, cx| {
5913            workspace.toggle_dock(DockPosition::Right, cx)
5914        });
5915        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5916
5917        // Opening a dock unzooms the pane.
5918        workspace.update(cx, |workspace, cx| {
5919            workspace.toggle_dock(DockPosition::Right, cx)
5920        });
5921        workspace.update(cx, |workspace, cx| {
5922            let pane = pane.read(cx);
5923            assert!(!pane.is_zoomed());
5924            assert!(!pane.focus_handle(cx).is_focused(cx));
5925            assert!(workspace.right_dock().read(cx).is_open());
5926            assert!(workspace.zoomed.is_none());
5927        });
5928    }
5929
5930    struct TestModal(FocusHandle);
5931
5932    impl TestModal {
5933        fn new(cx: &mut ViewContext<Self>) -> Self {
5934            Self(cx.focus_handle())
5935        }
5936    }
5937
5938    impl EventEmitter<DismissEvent> for TestModal {}
5939
5940    impl FocusableView for TestModal {
5941        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5942            self.0.clone()
5943        }
5944    }
5945
5946    impl ModalView for TestModal {}
5947
5948    impl Render for TestModal {
5949        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5950            div().track_focus(&self.0)
5951        }
5952    }
5953
5954    #[gpui::test]
5955    async fn test_panels(cx: &mut gpui::TestAppContext) {
5956        init_test(cx);
5957        let fs = FakeFs::new(cx.executor());
5958
5959        let project = Project::test(fs, [], cx).await;
5960        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5961
5962        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5963            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5964            workspace.add_panel(panel_1.clone(), cx);
5965            workspace
5966                .left_dock()
5967                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5968            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5969            workspace.add_panel(panel_2.clone(), cx);
5970            workspace
5971                .right_dock()
5972                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5973
5974            let left_dock = workspace.left_dock();
5975            assert_eq!(
5976                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5977                panel_1.panel_id()
5978            );
5979            assert_eq!(
5980                left_dock.read(cx).active_panel_size(cx).unwrap(),
5981                panel_1.size(cx)
5982            );
5983
5984            left_dock.update(cx, |left_dock, cx| {
5985                left_dock.resize_active_panel(Some(px(1337.)), cx)
5986            });
5987            assert_eq!(
5988                workspace
5989                    .right_dock()
5990                    .read(cx)
5991                    .visible_panel()
5992                    .unwrap()
5993                    .panel_id(),
5994                panel_2.panel_id(),
5995            );
5996
5997            (panel_1, panel_2)
5998        });
5999
6000        // Move panel_1 to the right
6001        panel_1.update(cx, |panel_1, cx| {
6002            panel_1.set_position(DockPosition::Right, cx)
6003        });
6004
6005        workspace.update(cx, |workspace, cx| {
6006            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
6007            // Since it was the only panel on the left, the left dock should now be closed.
6008            assert!(!workspace.left_dock().read(cx).is_open());
6009            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
6010            let right_dock = workspace.right_dock();
6011            assert_eq!(
6012                right_dock.read(cx).visible_panel().unwrap().panel_id(),
6013                panel_1.panel_id()
6014            );
6015            assert_eq!(
6016                right_dock.read(cx).active_panel_size(cx).unwrap(),
6017                px(1337.)
6018            );
6019
6020            // Now we move panel_2 to the left
6021            panel_2.set_position(DockPosition::Left, cx);
6022        });
6023
6024        workspace.update(cx, |workspace, cx| {
6025            // Since panel_2 was not visible on the right, we don't open the left dock.
6026            assert!(!workspace.left_dock().read(cx).is_open());
6027            // And the right dock is unaffected in its displaying of panel_1
6028            assert!(workspace.right_dock().read(cx).is_open());
6029            assert_eq!(
6030                workspace
6031                    .right_dock()
6032                    .read(cx)
6033                    .visible_panel()
6034                    .unwrap()
6035                    .panel_id(),
6036                panel_1.panel_id(),
6037            );
6038        });
6039
6040        // Move panel_1 back to the left
6041        panel_1.update(cx, |panel_1, cx| {
6042            panel_1.set_position(DockPosition::Left, cx)
6043        });
6044
6045        workspace.update(cx, |workspace, cx| {
6046            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
6047            let left_dock = workspace.left_dock();
6048            assert!(left_dock.read(cx).is_open());
6049            assert_eq!(
6050                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6051                panel_1.panel_id()
6052            );
6053            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
6054            // And the right dock should be closed as it no longer has any panels.
6055            assert!(!workspace.right_dock().read(cx).is_open());
6056
6057            // Now we move panel_1 to the bottom
6058            panel_1.set_position(DockPosition::Bottom, cx);
6059        });
6060
6061        workspace.update(cx, |workspace, cx| {
6062            // Since panel_1 was visible on the left, we close the left dock.
6063            assert!(!workspace.left_dock().read(cx).is_open());
6064            // The bottom dock is sized based on the panel's default size,
6065            // since the panel orientation changed from vertical to horizontal.
6066            let bottom_dock = workspace.bottom_dock();
6067            assert_eq!(
6068                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
6069                panel_1.size(cx),
6070            );
6071            // Close bottom dock and move panel_1 back to the left.
6072            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
6073            panel_1.set_position(DockPosition::Left, cx);
6074        });
6075
6076        // Emit activated event on panel 1
6077        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
6078
6079        // Now the left dock is open and panel_1 is active and focused.
6080        workspace.update(cx, |workspace, cx| {
6081            let left_dock = workspace.left_dock();
6082            assert!(left_dock.read(cx).is_open());
6083            assert_eq!(
6084                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6085                panel_1.panel_id(),
6086            );
6087            assert!(panel_1.focus_handle(cx).is_focused(cx));
6088        });
6089
6090        // Emit closed event on panel 2, which is not active
6091        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
6092
6093        // Wo don't close the left dock, because panel_2 wasn't the active panel
6094        workspace.update(cx, |workspace, cx| {
6095            let left_dock = workspace.left_dock();
6096            assert!(left_dock.read(cx).is_open());
6097            assert_eq!(
6098                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6099                panel_1.panel_id(),
6100            );
6101        });
6102
6103        // Emitting a ZoomIn event shows the panel as zoomed.
6104        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
6105        workspace.update(cx, |workspace, _| {
6106            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6107            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
6108        });
6109
6110        // Move panel to another dock while it is zoomed
6111        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
6112        workspace.update(cx, |workspace, _| {
6113            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6114
6115            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6116        });
6117
6118        // This is a helper for getting a:
6119        // - valid focus on an element,
6120        // - that isn't a part of the panes and panels system of the Workspace,
6121        // - and doesn't trigger the 'on_focus_lost' API.
6122        let focus_other_view = {
6123            let workspace = workspace.clone();
6124            move |cx: &mut VisualTestContext| {
6125                workspace.update(cx, |workspace, cx| {
6126                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
6127                        workspace.toggle_modal(cx, TestModal::new);
6128                        workspace.toggle_modal(cx, TestModal::new);
6129                    } else {
6130                        workspace.toggle_modal(cx, TestModal::new);
6131                    }
6132                })
6133            }
6134        };
6135
6136        // If focus is transferred to another view that's not a panel or another pane, we still show
6137        // the panel as zoomed.
6138        focus_other_view(cx);
6139        workspace.update(cx, |workspace, _| {
6140            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6141            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6142        });
6143
6144        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
6145        workspace.update(cx, |_, cx| cx.focus_self());
6146        workspace.update(cx, |workspace, _| {
6147            assert_eq!(workspace.zoomed, None);
6148            assert_eq!(workspace.zoomed_position, None);
6149        });
6150
6151        // If focus is transferred again to another view that's not a panel or a pane, we won't
6152        // show the panel as zoomed because it wasn't zoomed before.
6153        focus_other_view(cx);
6154        workspace.update(cx, |workspace, _| {
6155            assert_eq!(workspace.zoomed, None);
6156            assert_eq!(workspace.zoomed_position, None);
6157        });
6158
6159        // When the panel is activated, it is zoomed again.
6160        cx.dispatch_action(ToggleRightDock);
6161        workspace.update(cx, |workspace, _| {
6162            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6163            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6164        });
6165
6166        // Emitting a ZoomOut event unzooms the panel.
6167        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
6168        workspace.update(cx, |workspace, _| {
6169            assert_eq!(workspace.zoomed, None);
6170            assert_eq!(workspace.zoomed_position, None);
6171        });
6172
6173        // Emit closed event on panel 1, which is active
6174        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
6175
6176        // Now the left dock is closed, because panel_1 was the active panel
6177        workspace.update(cx, |workspace, cx| {
6178            let right_dock = workspace.right_dock();
6179            assert!(!right_dock.read(cx).is_open());
6180        });
6181    }
6182
6183    mod register_project_item_tests {
6184        use ui::Context as _;
6185
6186        use super::*;
6187
6188        const TEST_PNG_KIND: &str = "TestPngItemView";
6189        // View
6190        struct TestPngItemView {
6191            focus_handle: FocusHandle,
6192        }
6193        // Model
6194        struct TestPngItem {}
6195
6196        impl project::Item for TestPngItem {
6197            fn try_open(
6198                _project: &Model<Project>,
6199                path: &ProjectPath,
6200                cx: &mut AppContext,
6201            ) -> Option<Task<gpui::Result<Model<Self>>>> {
6202                if path.path.extension().unwrap() == "png" {
6203                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestPngItem {}) }))
6204                } else {
6205                    None
6206                }
6207            }
6208
6209            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
6210                None
6211            }
6212
6213            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
6214                None
6215            }
6216        }
6217
6218        impl Item for TestPngItemView {
6219            type Event = ();
6220
6221            fn serialized_item_kind() -> Option<&'static str> {
6222                Some(TEST_PNG_KIND)
6223            }
6224        }
6225        impl EventEmitter<()> for TestPngItemView {}
6226        impl FocusableView for TestPngItemView {
6227            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6228                self.focus_handle.clone()
6229            }
6230        }
6231
6232        impl Render for TestPngItemView {
6233            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6234                Empty
6235            }
6236        }
6237
6238        impl ProjectItem for TestPngItemView {
6239            type Item = TestPngItem;
6240
6241            fn for_project_item(
6242                _project: Model<Project>,
6243                _item: Model<Self::Item>,
6244                cx: &mut ViewContext<Self>,
6245            ) -> Self
6246            where
6247                Self: Sized,
6248            {
6249                Self {
6250                    focus_handle: cx.focus_handle(),
6251                }
6252            }
6253        }
6254
6255        const TEST_IPYNB_KIND: &str = "TestIpynbItemView";
6256        // View
6257        struct TestIpynbItemView {
6258            focus_handle: FocusHandle,
6259        }
6260        // Model
6261        struct TestIpynbItem {}
6262
6263        impl project::Item for TestIpynbItem {
6264            fn try_open(
6265                _project: &Model<Project>,
6266                path: &ProjectPath,
6267                cx: &mut AppContext,
6268            ) -> Option<Task<gpui::Result<Model<Self>>>> {
6269                if path.path.extension().unwrap() == "ipynb" {
6270                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestIpynbItem {}) }))
6271                } else {
6272                    None
6273                }
6274            }
6275
6276            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
6277                None
6278            }
6279
6280            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
6281                None
6282            }
6283        }
6284
6285        impl Item for TestIpynbItemView {
6286            type Event = ();
6287
6288            fn serialized_item_kind() -> Option<&'static str> {
6289                Some(TEST_IPYNB_KIND)
6290            }
6291        }
6292        impl EventEmitter<()> for TestIpynbItemView {}
6293        impl FocusableView for TestIpynbItemView {
6294            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6295                self.focus_handle.clone()
6296            }
6297        }
6298
6299        impl Render for TestIpynbItemView {
6300            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6301                Empty
6302            }
6303        }
6304
6305        impl ProjectItem for TestIpynbItemView {
6306            type Item = TestIpynbItem;
6307
6308            fn for_project_item(
6309                _project: Model<Project>,
6310                _item: Model<Self::Item>,
6311                cx: &mut ViewContext<Self>,
6312            ) -> Self
6313            where
6314                Self: Sized,
6315            {
6316                Self {
6317                    focus_handle: cx.focus_handle(),
6318                }
6319            }
6320        }
6321
6322        struct TestAlternatePngItemView {
6323            focus_handle: FocusHandle,
6324        }
6325
6326        const TEST_ALTERNATE_PNG_KIND: &str = "TestAlternatePngItemView";
6327        impl Item for TestAlternatePngItemView {
6328            type Event = ();
6329
6330            fn serialized_item_kind() -> Option<&'static str> {
6331                Some(TEST_ALTERNATE_PNG_KIND)
6332            }
6333        }
6334        impl EventEmitter<()> for TestAlternatePngItemView {}
6335        impl FocusableView for TestAlternatePngItemView {
6336            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6337                self.focus_handle.clone()
6338            }
6339        }
6340
6341        impl Render for TestAlternatePngItemView {
6342            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6343                Empty
6344            }
6345        }
6346
6347        impl ProjectItem for TestAlternatePngItemView {
6348            type Item = TestPngItem;
6349
6350            fn for_project_item(
6351                _project: Model<Project>,
6352                _item: Model<Self::Item>,
6353                cx: &mut ViewContext<Self>,
6354            ) -> Self
6355            where
6356                Self: Sized,
6357            {
6358                Self {
6359                    focus_handle: cx.focus_handle(),
6360                }
6361            }
6362        }
6363
6364        #[gpui::test]
6365        async fn test_register_project_item(cx: &mut TestAppContext) {
6366            init_test(cx);
6367
6368            cx.update(|cx| {
6369                register_project_item::<TestPngItemView>(cx);
6370                register_project_item::<TestIpynbItemView>(cx);
6371            });
6372
6373            let fs = FakeFs::new(cx.executor());
6374            fs.insert_tree(
6375                "/root1",
6376                json!({
6377                    "one.png": "BINARYDATAHERE",
6378                    "two.ipynb": "{ totally a notebook }",
6379                    "three.txt": "editing text, sure why not?"
6380                }),
6381            )
6382            .await;
6383
6384            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6385            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6386
6387            let worktree_id = project.update(cx, |project, cx| {
6388                project.worktrees().next().unwrap().read(cx).id()
6389            });
6390
6391            let handle = workspace
6392                .update(cx, |workspace, cx| {
6393                    let project_path = (worktree_id, "one.png");
6394                    workspace.open_path(project_path, None, true, cx)
6395                })
6396                .await
6397                .unwrap();
6398
6399            // Now we can check if the handle we got back errored or not
6400            assert_eq!(handle.serialized_item_kind().unwrap(), TEST_PNG_KIND);
6401
6402            let handle = workspace
6403                .update(cx, |workspace, cx| {
6404                    let project_path = (worktree_id, "two.ipynb");
6405                    workspace.open_path(project_path, None, true, cx)
6406                })
6407                .await
6408                .unwrap();
6409
6410            assert_eq!(handle.serialized_item_kind().unwrap(), TEST_IPYNB_KIND);
6411
6412            let handle = workspace
6413                .update(cx, |workspace, cx| {
6414                    let project_path = (worktree_id, "three.txt");
6415                    workspace.open_path(project_path, None, true, cx)
6416                })
6417                .await;
6418            assert!(handle.is_err());
6419        }
6420
6421        #[gpui::test]
6422        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
6423            init_test(cx);
6424
6425            cx.update(|cx| {
6426                register_project_item::<TestPngItemView>(cx);
6427                register_project_item::<TestAlternatePngItemView>(cx);
6428            });
6429
6430            let fs = FakeFs::new(cx.executor());
6431            fs.insert_tree(
6432                "/root1",
6433                json!({
6434                    "one.png": "BINARYDATAHERE",
6435                    "two.ipynb": "{ totally a notebook }",
6436                    "three.txt": "editing text, sure why not?"
6437                }),
6438            )
6439            .await;
6440
6441            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6442            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6443
6444            let worktree_id = project.update(cx, |project, cx| {
6445                project.worktrees().next().unwrap().read(cx).id()
6446            });
6447
6448            let handle = workspace
6449                .update(cx, |workspace, cx| {
6450                    let project_path = (worktree_id, "one.png");
6451                    workspace.open_path(project_path, None, true, cx)
6452                })
6453                .await
6454                .unwrap();
6455
6456            // This _must_ be the second item registered
6457            assert_eq!(
6458                handle.serialized_item_kind().unwrap(),
6459                TEST_ALTERNATE_PNG_KIND
6460            );
6461
6462            let handle = workspace
6463                .update(cx, |workspace, cx| {
6464                    let project_path = (worktree_id, "three.txt");
6465                    workspace.open_path(project_path, None, true, cx)
6466                })
6467                .await;
6468            assert!(handle.is_err());
6469        }
6470    }
6471
6472    pub fn init_test(cx: &mut TestAppContext) {
6473        cx.update(|cx| {
6474            let settings_store = SettingsStore::test(cx);
6475            cx.set_global(settings_store);
6476            theme::init(theme::LoadThemes::JustBase, cx);
6477            language::init(cx);
6478            crate::init_settings(cx);
6479            Project::init_settings(cx);
6480        });
6481    }
6482}
6483
6484pub fn client_side_decorations(element: impl IntoElement, cx: &mut WindowContext) -> Stateful<Div> {
6485    const BORDER_SIZE: Pixels = px(1.0);
6486    let decorations = cx.window_decorations();
6487
6488    if matches!(decorations, Decorations::Client { .. }) {
6489        cx.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
6490    }
6491
6492    struct GlobalResizeEdge(ResizeEdge);
6493    impl Global for GlobalResizeEdge {}
6494
6495    div()
6496        .id("window-backdrop")
6497        .bg(transparent_black())
6498        .map(|div| match decorations {
6499            Decorations::Server => div,
6500            Decorations::Client { tiling, .. } => div
6501                .when(!(tiling.top || tiling.right), |div| {
6502                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6503                })
6504                .when(!(tiling.top || tiling.left), |div| {
6505                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6506                })
6507                .when(!(tiling.bottom || tiling.right), |div| {
6508                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6509                })
6510                .when(!(tiling.bottom || tiling.left), |div| {
6511                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6512                })
6513                .when(!tiling.top, |div| {
6514                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
6515                })
6516                .when(!tiling.bottom, |div| {
6517                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
6518                })
6519                .when(!tiling.left, |div| {
6520                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
6521                })
6522                .when(!tiling.right, |div| {
6523                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
6524                })
6525                .on_mouse_move(move |e, cx| {
6526                    let size = cx.window_bounds().get_bounds().size;
6527                    let pos = e.position;
6528
6529                    let new_edge =
6530                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
6531
6532                    let edge = cx.try_global::<GlobalResizeEdge>();
6533                    if new_edge != edge.map(|edge| edge.0) {
6534                        cx.window_handle()
6535                            .update(cx, |workspace, cx| cx.notify(workspace.entity_id()))
6536                            .ok();
6537                    }
6538                })
6539                .on_mouse_down(MouseButton::Left, move |e, cx| {
6540                    let size = cx.window_bounds().get_bounds().size;
6541                    let pos = e.position;
6542
6543                    let edge = match resize_edge(
6544                        pos,
6545                        theme::CLIENT_SIDE_DECORATION_SHADOW,
6546                        size,
6547                        tiling,
6548                    ) {
6549                        Some(value) => value,
6550                        None => return,
6551                    };
6552
6553                    cx.start_window_resize(edge);
6554                }),
6555        })
6556        .size_full()
6557        .child(
6558            div()
6559                .cursor(CursorStyle::Arrow)
6560                .map(|div| match decorations {
6561                    Decorations::Server => div,
6562                    Decorations::Client { tiling } => div
6563                        .border_color(cx.theme().colors().border)
6564                        .when(!(tiling.top || tiling.right), |div| {
6565                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6566                        })
6567                        .when(!(tiling.top || tiling.left), |div| {
6568                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6569                        })
6570                        .when(!(tiling.bottom || tiling.right), |div| {
6571                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6572                        })
6573                        .when(!(tiling.bottom || tiling.left), |div| {
6574                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6575                        })
6576                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
6577                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
6578                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
6579                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
6580                        .when(!tiling.is_tiled(), |div| {
6581                            div.shadow(smallvec::smallvec![gpui::BoxShadow {
6582                                color: Hsla {
6583                                    h: 0.,
6584                                    s: 0.,
6585                                    l: 0.,
6586                                    a: 0.4,
6587                                },
6588                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
6589                                spread_radius: px(0.),
6590                                offset: point(px(0.0), px(0.0)),
6591                            }])
6592                        }),
6593                })
6594                .on_mouse_move(|_e, cx| {
6595                    cx.stop_propagation();
6596                })
6597                .size_full()
6598                .child(element),
6599        )
6600        .map(|div| match decorations {
6601            Decorations::Server => div,
6602            Decorations::Client { tiling, .. } => div.child(
6603                canvas(
6604                    |_bounds, cx| {
6605                        cx.insert_hitbox(
6606                            Bounds::new(
6607                                point(px(0.0), px(0.0)),
6608                                cx.window_bounds().get_bounds().size,
6609                            ),
6610                            false,
6611                        )
6612                    },
6613                    move |_bounds, hitbox, cx| {
6614                        let mouse = cx.mouse_position();
6615                        let size = cx.window_bounds().get_bounds().size;
6616                        let Some(edge) =
6617                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
6618                        else {
6619                            return;
6620                        };
6621                        cx.set_global(GlobalResizeEdge(edge));
6622                        cx.set_cursor_style(
6623                            match edge {
6624                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
6625                                ResizeEdge::Left | ResizeEdge::Right => {
6626                                    CursorStyle::ResizeLeftRight
6627                                }
6628                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
6629                                    CursorStyle::ResizeUpLeftDownRight
6630                                }
6631                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
6632                                    CursorStyle::ResizeUpRightDownLeft
6633                                }
6634                            },
6635                            &hitbox,
6636                        );
6637                    },
6638                )
6639                .size_full()
6640                .absolute(),
6641            ),
6642        })
6643}
6644
6645fn resize_edge(
6646    pos: Point<Pixels>,
6647    shadow_size: Pixels,
6648    window_size: Size<Pixels>,
6649    tiling: Tiling,
6650) -> Option<ResizeEdge> {
6651    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
6652    if bounds.contains(&pos) {
6653        return None;
6654    }
6655
6656    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
6657    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
6658    if !tiling.top && top_left_bounds.contains(&pos) {
6659        return Some(ResizeEdge::TopLeft);
6660    }
6661
6662    let top_right_bounds = Bounds::new(
6663        Point::new(window_size.width - corner_size.width, px(0.)),
6664        corner_size,
6665    );
6666    if !tiling.top && top_right_bounds.contains(&pos) {
6667        return Some(ResizeEdge::TopRight);
6668    }
6669
6670    let bottom_left_bounds = Bounds::new(
6671        Point::new(px(0.), window_size.height - corner_size.height),
6672        corner_size,
6673    );
6674    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
6675        return Some(ResizeEdge::BottomLeft);
6676    }
6677
6678    let bottom_right_bounds = Bounds::new(
6679        Point::new(
6680            window_size.width - corner_size.width,
6681            window_size.height - corner_size.height,
6682        ),
6683        corner_size,
6684    );
6685    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
6686        return Some(ResizeEdge::BottomRight);
6687    }
6688
6689    if !tiling.top && pos.y < shadow_size {
6690        Some(ResizeEdge::Top)
6691    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
6692        Some(ResizeEdge::Bottom)
6693    } else if !tiling.left && pos.x < shadow_size {
6694        Some(ResizeEdge::Left)
6695    } else if !tiling.right && pos.x > window_size.width - shadow_size {
6696        Some(ResizeEdge::Right)
6697    } else {
6698        None
6699    }
6700}