workspace.rs

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