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