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