workspace.rs

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