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