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