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        if self.project.read(cx).is_remote() {
1697            self.show_error(
1698                &anyhow!("Folders cannot yet be added to remote projects"),
1699                cx,
1700            );
1701            return;
1702        }
1703        let paths = cx.prompt_for_paths(PathPromptOptions {
1704            files: false,
1705            directories: true,
1706            multiple: true,
1707        });
1708        cx.spawn(|this, mut cx| async move {
1709            if let Some(paths) = paths.await.log_err().flatten() {
1710                let results = this
1711                    .update(&mut cx, |this, cx| {
1712                        this.open_paths(paths, OpenVisible::All, None, cx)
1713                    })?
1714                    .await;
1715                for result in results.into_iter().flatten() {
1716                    result.log_err();
1717                }
1718            }
1719            anyhow::Ok(())
1720        })
1721        .detach_and_log_err(cx);
1722    }
1723
1724    fn project_path_for_path(
1725        project: Model<Project>,
1726        abs_path: &Path,
1727        visible: bool,
1728        cx: &mut AppContext,
1729    ) -> Task<Result<(Model<Worktree>, ProjectPath)>> {
1730        let entry = project.update(cx, |project, cx| {
1731            project.find_or_create_local_worktree(abs_path, visible, cx)
1732        });
1733        cx.spawn(|mut cx| async move {
1734            let (worktree, path) = entry.await?;
1735            let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
1736            Ok((
1737                worktree,
1738                ProjectPath {
1739                    worktree_id,
1740                    path: path.into(),
1741                },
1742            ))
1743        })
1744    }
1745
1746    pub fn items<'a>(
1747        &'a self,
1748        cx: &'a AppContext,
1749    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1750        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1751    }
1752
1753    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
1754        self.items_of_type(cx).max_by_key(|item| item.item_id())
1755    }
1756
1757    pub fn items_of_type<'a, T: Item>(
1758        &'a self,
1759        cx: &'a AppContext,
1760    ) -> impl 'a + Iterator<Item = View<T>> {
1761        self.panes
1762            .iter()
1763            .flat_map(|pane| pane.read(cx).items_of_type())
1764    }
1765
1766    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1767        self.active_pane().read(cx).active_item()
1768    }
1769
1770    pub fn active_item_as<I: 'static>(&self, cx: &AppContext) -> Option<View<I>> {
1771        let item = self.active_item(cx)?;
1772        item.to_any().downcast::<I>().ok()
1773    }
1774
1775    fn active_project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
1776        self.active_item(cx).and_then(|item| item.project_path(cx))
1777    }
1778
1779    pub fn save_active_item(
1780        &mut self,
1781        save_intent: SaveIntent,
1782        cx: &mut WindowContext,
1783    ) -> Task<Result<()>> {
1784        let project = self.project.clone();
1785        let pane = self.active_pane();
1786        let item_ix = pane.read(cx).active_item_index();
1787        let item = pane.read(cx).active_item();
1788        let pane = pane.downgrade();
1789
1790        cx.spawn(|mut cx| async move {
1791            if let Some(item) = item {
1792                Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
1793                    .await
1794                    .map(|_| ())
1795            } else {
1796                Ok(())
1797            }
1798        })
1799    }
1800
1801    pub fn close_inactive_items_and_panes(
1802        &mut self,
1803        action: &CloseInactiveTabsAndPanes,
1804        cx: &mut ViewContext<Self>,
1805    ) {
1806        if let Some(task) =
1807            self.close_all_internal(true, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1808        {
1809            task.detach_and_log_err(cx)
1810        }
1811    }
1812
1813    pub fn close_all_items_and_panes(
1814        &mut self,
1815        action: &CloseAllItemsAndPanes,
1816        cx: &mut ViewContext<Self>,
1817    ) {
1818        if let Some(task) =
1819            self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1820        {
1821            task.detach_and_log_err(cx)
1822        }
1823    }
1824
1825    fn close_all_internal(
1826        &mut self,
1827        retain_active_pane: bool,
1828        save_intent: SaveIntent,
1829        cx: &mut ViewContext<Self>,
1830    ) -> Option<Task<Result<()>>> {
1831        let current_pane = self.active_pane();
1832
1833        let mut tasks = Vec::new();
1834
1835        if retain_active_pane {
1836            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1837                pane.close_inactive_items(&CloseInactiveItems { save_intent: None }, cx)
1838            }) {
1839                tasks.push(current_pane_close);
1840            };
1841        }
1842
1843        for pane in self.panes() {
1844            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
1845                continue;
1846            }
1847
1848            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1849                pane.close_all_items(
1850                    &CloseAllItems {
1851                        save_intent: Some(save_intent),
1852                    },
1853                    cx,
1854                )
1855            }) {
1856                tasks.push(close_pane_items)
1857            }
1858        }
1859
1860        if tasks.is_empty() {
1861            None
1862        } else {
1863            Some(cx.spawn(|_, _| async move {
1864                for task in tasks {
1865                    task.await?
1866                }
1867                Ok(())
1868            }))
1869        }
1870    }
1871
1872    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1873        let dock = match dock_side {
1874            DockPosition::Left => &self.left_dock,
1875            DockPosition::Bottom => &self.bottom_dock,
1876            DockPosition::Right => &self.right_dock,
1877        };
1878        let mut focus_center = false;
1879        let mut reveal_dock = false;
1880        dock.update(cx, |dock, cx| {
1881            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1882            let was_visible = dock.is_open() && !other_is_zoomed;
1883            dock.set_open(!was_visible, cx);
1884
1885            if let Some(active_panel) = dock.active_panel() {
1886                if was_visible {
1887                    if active_panel.focus_handle(cx).contains_focused(cx) {
1888                        focus_center = true;
1889                    }
1890                } else {
1891                    let focus_handle = &active_panel.focus_handle(cx);
1892                    cx.focus(focus_handle);
1893                    reveal_dock = true;
1894                }
1895            }
1896        });
1897
1898        if reveal_dock {
1899            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1900        }
1901
1902        if focus_center {
1903            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1904        }
1905
1906        cx.notify();
1907        self.serialize_workspace(cx);
1908    }
1909
1910    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1911        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1912
1913        for dock in docks {
1914            dock.update(cx, |dock, cx| {
1915                dock.set_open(false, cx);
1916            });
1917        }
1918
1919        cx.focus_self();
1920        cx.notify();
1921        self.serialize_workspace(cx);
1922    }
1923
1924    /// Transfer focus to the panel of the given type.
1925    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
1926        let panel = self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?;
1927        panel.to_any().downcast().ok()
1928    }
1929
1930    /// Focus the panel of the given type if it isn't already focused. If it is
1931    /// already focused, then transfer focus back to the workspace center.
1932    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1933        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| {
1934            !panel.focus_handle(cx).contains_focused(cx)
1935        });
1936    }
1937
1938    /// Focus or unfocus the given panel type, depending on the given callback.
1939    fn focus_or_unfocus_panel<T: Panel>(
1940        &mut self,
1941        cx: &mut ViewContext<Self>,
1942        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1943    ) -> Option<Arc<dyn PanelHandle>> {
1944        let mut result_panel = None;
1945        let mut serialize = false;
1946        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1947            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1948                let mut focus_center = false;
1949                let panel = dock.update(cx, |dock, cx| {
1950                    dock.activate_panel(panel_index, cx);
1951
1952                    let panel = dock.active_panel().cloned();
1953                    if let Some(panel) = panel.as_ref() {
1954                        if should_focus(&**panel, cx) {
1955                            dock.set_open(true, cx);
1956                            panel.focus_handle(cx).focus(cx);
1957                        } else {
1958                            focus_center = true;
1959                        }
1960                    }
1961                    panel
1962                });
1963
1964                if focus_center {
1965                    self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1966                }
1967
1968                result_panel = panel;
1969                serialize = true;
1970                break;
1971            }
1972        }
1973
1974        if serialize {
1975            self.serialize_workspace(cx);
1976        }
1977
1978        cx.notify();
1979        result_panel
1980    }
1981
1982    /// Open the panel of the given type
1983    pub fn open_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1984        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1985            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1986                dock.update(cx, |dock, cx| {
1987                    dock.activate_panel(panel_index, cx);
1988                    dock.set_open(true, cx);
1989                });
1990            }
1991        }
1992    }
1993
1994    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
1995        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1996            let dock = dock.read(cx);
1997            if let Some(panel) = dock.panel::<T>() {
1998                return Some(panel);
1999            }
2000        }
2001        None
2002    }
2003
2004    fn dismiss_zoomed_items_to_reveal(
2005        &mut self,
2006        dock_to_reveal: Option<DockPosition>,
2007        cx: &mut ViewContext<Self>,
2008    ) {
2009        // If a center pane is zoomed, unzoom it.
2010        for pane in &self.panes {
2011            if pane != &self.active_pane || dock_to_reveal.is_some() {
2012                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2013            }
2014        }
2015
2016        // If another dock is zoomed, hide it.
2017        let mut focus_center = false;
2018        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
2019            dock.update(cx, |dock, cx| {
2020                if Some(dock.position()) != dock_to_reveal {
2021                    if let Some(panel) = dock.active_panel() {
2022                        if panel.is_zoomed(cx) {
2023                            focus_center |= panel.focus_handle(cx).contains_focused(cx);
2024                            dock.set_open(false, cx);
2025                        }
2026                    }
2027                }
2028            });
2029        }
2030
2031        if focus_center {
2032            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
2033        }
2034
2035        if self.zoomed_position != dock_to_reveal {
2036            self.zoomed = None;
2037            self.zoomed_position = None;
2038            cx.emit(Event::ZoomChanged);
2039        }
2040
2041        cx.notify();
2042    }
2043
2044    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
2045        let pane = cx.new_view(|cx| {
2046            Pane::new(
2047                self.weak_handle(),
2048                self.project.clone(),
2049                self.pane_history_timestamp.clone(),
2050                None,
2051                NewFile.boxed_clone(),
2052                cx,
2053            )
2054        });
2055        cx.subscribe(&pane, Self::handle_pane_event).detach();
2056        self.panes.push(pane.clone());
2057        cx.focus_view(&pane);
2058        cx.emit(Event::PaneAdded(pane.clone()));
2059        pane
2060    }
2061
2062    pub fn add_item_to_center(
2063        &mut self,
2064        item: Box<dyn ItemHandle>,
2065        cx: &mut ViewContext<Self>,
2066    ) -> bool {
2067        if let Some(center_pane) = self.last_active_center_pane.clone() {
2068            if let Some(center_pane) = center_pane.upgrade() {
2069                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
2070                true
2071            } else {
2072                false
2073            }
2074        } else {
2075            false
2076        }
2077    }
2078
2079    pub fn add_item_to_active_pane(
2080        &mut self,
2081        item: Box<dyn ItemHandle>,
2082        destination_index: Option<usize>,
2083        cx: &mut WindowContext,
2084    ) {
2085        self.add_item(self.active_pane.clone(), item, destination_index, cx)
2086    }
2087
2088    pub fn add_item(
2089        &mut self,
2090        pane: View<Pane>,
2091        item: Box<dyn ItemHandle>,
2092        destination_index: Option<usize>,
2093        cx: &mut WindowContext,
2094    ) {
2095        if let Some(text) = item.telemetry_event_text(cx) {
2096            self.client()
2097                .telemetry()
2098                .report_app_event(format!("{}: open", text));
2099        }
2100
2101        pane.update(cx, |pane, cx| {
2102            pane.add_item(item, true, true, destination_index, cx)
2103        });
2104    }
2105
2106    pub fn split_item(
2107        &mut self,
2108        split_direction: SplitDirection,
2109        item: Box<dyn ItemHandle>,
2110        cx: &mut ViewContext<Self>,
2111    ) {
2112        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
2113        self.add_item(new_pane, item, None, cx);
2114    }
2115
2116    pub fn open_abs_path(
2117        &mut self,
2118        abs_path: PathBuf,
2119        visible: bool,
2120        cx: &mut ViewContext<Self>,
2121    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2122        cx.spawn(|workspace, mut cx| async move {
2123            let open_paths_task_result = workspace
2124                .update(&mut cx, |workspace, cx| {
2125                    workspace.open_paths(
2126                        vec![abs_path.clone()],
2127                        if visible {
2128                            OpenVisible::All
2129                        } else {
2130                            OpenVisible::None
2131                        },
2132                        None,
2133                        cx,
2134                    )
2135                })
2136                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
2137                .await;
2138            anyhow::ensure!(
2139                open_paths_task_result.len() == 1,
2140                "open abs path {abs_path:?} task returned incorrect number of results"
2141            );
2142            match open_paths_task_result
2143                .into_iter()
2144                .next()
2145                .expect("ensured single task result")
2146            {
2147                Some(open_result) => {
2148                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
2149                }
2150                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
2151            }
2152        })
2153    }
2154
2155    pub fn split_abs_path(
2156        &mut self,
2157        abs_path: PathBuf,
2158        visible: bool,
2159        cx: &mut ViewContext<Self>,
2160    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2161        let project_path_task =
2162            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
2163        cx.spawn(|this, mut cx| async move {
2164            let (_, path) = project_path_task.await?;
2165            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
2166                .await
2167        })
2168    }
2169
2170    pub fn open_path(
2171        &mut self,
2172        path: impl Into<ProjectPath>,
2173        pane: Option<WeakView<Pane>>,
2174        focus_item: bool,
2175        cx: &mut WindowContext,
2176    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2177        self.open_path_preview(path, pane, focus_item, false, cx)
2178    }
2179
2180    pub fn open_path_preview(
2181        &mut self,
2182        path: impl Into<ProjectPath>,
2183        pane: Option<WeakView<Pane>>,
2184        focus_item: bool,
2185        allow_preview: bool,
2186        cx: &mut WindowContext,
2187    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2188        let pane = pane.unwrap_or_else(|| {
2189            self.last_active_center_pane.clone().unwrap_or_else(|| {
2190                self.panes
2191                    .first()
2192                    .expect("There must be an active pane")
2193                    .downgrade()
2194            })
2195        });
2196
2197        let task = self.load_path(path.into(), cx);
2198        cx.spawn(move |mut cx| async move {
2199            let (project_entry_id, build_item) = task.await?;
2200            pane.update(&mut cx, |pane, cx| {
2201                pane.open_item(project_entry_id, focus_item, allow_preview, cx, build_item)
2202            })
2203        })
2204    }
2205
2206    pub fn split_path(
2207        &mut self,
2208        path: impl Into<ProjectPath>,
2209        cx: &mut ViewContext<Self>,
2210    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2211        self.split_path_preview(path, false, cx)
2212    }
2213
2214    pub fn split_path_preview(
2215        &mut self,
2216        path: impl Into<ProjectPath>,
2217        allow_preview: bool,
2218        cx: &mut ViewContext<Self>,
2219    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2220        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
2221            self.panes
2222                .first()
2223                .expect("There must be an active pane")
2224                .downgrade()
2225        });
2226
2227        if let Member::Pane(center_pane) = &self.center.root {
2228            if center_pane.read(cx).items_len() == 0 {
2229                return self.open_path(path, Some(pane), true, cx);
2230            }
2231        }
2232
2233        let task = self.load_path(path.into(), cx);
2234        cx.spawn(|this, mut cx| async move {
2235            let (project_entry_id, build_item) = task.await?;
2236            this.update(&mut cx, move |this, cx| -> Option<_> {
2237                let pane = pane.upgrade()?;
2238                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
2239                new_pane.update(cx, |new_pane, cx| {
2240                    Some(new_pane.open_item(project_entry_id, true, allow_preview, cx, build_item))
2241                })
2242            })
2243            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
2244        })
2245    }
2246
2247    fn load_path(
2248        &mut self,
2249        path: ProjectPath,
2250        cx: &mut WindowContext,
2251    ) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
2252        let project = self.project().clone();
2253        let project_item_builders = cx.default_global::<ProjectItemOpeners>().clone();
2254        let Some(open_project_item) = project_item_builders
2255            .iter()
2256            .rev()
2257            .find_map(|open_project_item| open_project_item(&project, &path, cx))
2258        else {
2259            return Task::ready(Err(anyhow!("cannot open file {:?}", path.path)));
2260        };
2261        open_project_item
2262    }
2263
2264    pub fn open_project_item<T>(
2265        &mut self,
2266        pane: View<Pane>,
2267        project_item: Model<T::Item>,
2268        cx: &mut ViewContext<Self>,
2269    ) -> View<T>
2270    where
2271        T: ProjectItem,
2272    {
2273        use project::Item as _;
2274
2275        let entry_id = project_item.read(cx).entry_id(cx);
2276        if let Some(item) = entry_id
2277            .and_then(|entry_id| pane.read(cx).item_for_entry(entry_id, cx))
2278            .and_then(|item| item.downcast())
2279        {
2280            self.activate_item(&item, cx);
2281            return item;
2282        }
2283
2284        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2285
2286        let item_id = item.item_id();
2287        let mut destination_index = None;
2288        pane.update(cx, |pane, cx| {
2289            if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
2290                if let Some(preview_item_id) = pane.preview_item_id() {
2291                    if preview_item_id != item_id {
2292                        destination_index = pane.close_current_preview_item(cx);
2293                    }
2294                }
2295            }
2296            pane.set_preview_item_id(Some(item.item_id()), cx)
2297        });
2298
2299        self.add_item(pane, Box::new(item.clone()), destination_index, cx);
2300        item
2301    }
2302
2303    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2304        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2305            self.active_pane.update(cx, |pane, cx| {
2306                pane.add_item(Box::new(shared_screen), false, true, None, cx)
2307            });
2308        }
2309    }
2310
2311    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut WindowContext) -> bool {
2312        let result = self.panes.iter().find_map(|pane| {
2313            pane.read(cx)
2314                .index_for_item(item)
2315                .map(|ix| (pane.clone(), ix))
2316        });
2317        if let Some((pane, ix)) = result {
2318            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2319            true
2320        } else {
2321            false
2322        }
2323    }
2324
2325    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2326        let panes = self.center.panes();
2327        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2328            cx.focus_view(&pane);
2329        } else {
2330            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2331        }
2332    }
2333
2334    pub fn activate_next_pane(&mut self, cx: &mut WindowContext) {
2335        let panes = self.center.panes();
2336        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2337            let next_ix = (ix + 1) % panes.len();
2338            let next_pane = panes[next_ix].clone();
2339            cx.focus_view(&next_pane);
2340        }
2341    }
2342
2343    pub fn activate_previous_pane(&mut self, cx: &mut WindowContext) {
2344        let panes = self.center.panes();
2345        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2346            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2347            let prev_pane = panes[prev_ix].clone();
2348            cx.focus_view(&prev_pane);
2349        }
2350    }
2351
2352    pub fn activate_pane_in_direction(
2353        &mut self,
2354        direction: SplitDirection,
2355        cx: &mut WindowContext,
2356    ) {
2357        use ActivateInDirectionTarget as Target;
2358        enum Origin {
2359            LeftDock,
2360            RightDock,
2361            BottomDock,
2362            Center,
2363        }
2364
2365        let origin: Origin = [
2366            (&self.left_dock, Origin::LeftDock),
2367            (&self.right_dock, Origin::RightDock),
2368            (&self.bottom_dock, Origin::BottomDock),
2369        ]
2370        .into_iter()
2371        .find_map(|(dock, origin)| {
2372            if dock.focus_handle(cx).contains_focused(cx) && dock.read(cx).is_open() {
2373                Some(origin)
2374            } else {
2375                None
2376            }
2377        })
2378        .unwrap_or(Origin::Center);
2379
2380        let get_last_active_pane = || {
2381            self.last_active_center_pane.as_ref().and_then(|p| {
2382                let p = p.upgrade()?;
2383                (p.read(cx).items_len() != 0).then_some(p)
2384            })
2385        };
2386
2387        let try_dock =
2388            |dock: &View<Dock>| dock.read(cx).is_open().then(|| Target::Dock(dock.clone()));
2389
2390        let target = match (origin, direction) {
2391            // We're in the center, so we first try to go to a different pane,
2392            // otherwise try to go to a dock.
2393            (Origin::Center, direction) => {
2394                if let Some(pane) = self.find_pane_in_direction(direction, cx) {
2395                    Some(Target::Pane(pane))
2396                } else {
2397                    match direction {
2398                        SplitDirection::Up => None,
2399                        SplitDirection::Down => try_dock(&self.bottom_dock),
2400                        SplitDirection::Left => try_dock(&self.left_dock),
2401                        SplitDirection::Right => try_dock(&self.right_dock),
2402                    }
2403                }
2404            }
2405
2406            (Origin::LeftDock, SplitDirection::Right) => {
2407                if let Some(last_active_pane) = get_last_active_pane() {
2408                    Some(Target::Pane(last_active_pane))
2409                } else {
2410                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock))
2411                }
2412            }
2413
2414            (Origin::LeftDock, SplitDirection::Down)
2415            | (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock),
2416
2417            (Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane),
2418            (Origin::BottomDock, SplitDirection::Left) => try_dock(&self.left_dock),
2419            (Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock),
2420
2421            (Origin::RightDock, SplitDirection::Left) => {
2422                if let Some(last_active_pane) = get_last_active_pane() {
2423                    Some(Target::Pane(last_active_pane))
2424                } else {
2425                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock))
2426                }
2427            }
2428
2429            _ => None,
2430        };
2431
2432        match target {
2433            Some(ActivateInDirectionTarget::Pane(pane)) => cx.focus_view(&pane),
2434            Some(ActivateInDirectionTarget::Dock(dock)) => {
2435                if let Some(panel) = dock.read(cx).active_panel() {
2436                    panel.focus_handle(cx).focus(cx);
2437                } else {
2438                    log::error!("Could not find a focus target when in switching focus in {direction} direction for a {:?} dock", dock.read(cx).position());
2439                }
2440            }
2441            None => {}
2442        }
2443    }
2444
2445    pub fn find_pane_in_direction(
2446        &mut self,
2447        direction: SplitDirection,
2448        cx: &WindowContext,
2449    ) -> Option<View<Pane>> {
2450        let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
2451            return None;
2452        };
2453        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2454        let center = match cursor {
2455            Some(cursor) if bounding_box.contains(&cursor) => cursor,
2456            _ => bounding_box.center(),
2457        };
2458
2459        let distance_to_next = pane_group::HANDLE_HITBOX_SIZE;
2460
2461        let target = match direction {
2462            SplitDirection::Left => {
2463                Point::new(bounding_box.left() - distance_to_next.into(), center.y)
2464            }
2465            SplitDirection::Right => {
2466                Point::new(bounding_box.right() + distance_to_next.into(), center.y)
2467            }
2468            SplitDirection::Up => {
2469                Point::new(center.x, bounding_box.top() - distance_to_next.into())
2470            }
2471            SplitDirection::Down => {
2472                Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
2473            }
2474        };
2475        self.center.pane_at_pixel_position(target).cloned()
2476    }
2477
2478    pub fn swap_pane_in_direction(
2479        &mut self,
2480        direction: SplitDirection,
2481        cx: &mut ViewContext<Self>,
2482    ) {
2483        if let Some(to) = self
2484            .find_pane_in_direction(direction, cx)
2485            .map(|pane| pane.clone())
2486        {
2487            self.center.swap(&self.active_pane.clone(), &to);
2488            cx.notify();
2489        }
2490    }
2491
2492    fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2493        // This is explicitly hoisted out of the following check for pane identity as
2494        // terminal panel panes are not registered as a center panes.
2495        self.status_bar.update(cx, |status_bar, cx| {
2496            status_bar.set_active_pane(&pane, cx);
2497        });
2498        if self.active_pane != pane {
2499            self.active_pane = pane.clone();
2500            self.active_item_path_changed(cx);
2501            self.last_active_center_pane = Some(pane.downgrade());
2502        }
2503
2504        self.dismiss_zoomed_items_to_reveal(None, cx);
2505        if pane.read(cx).is_zoomed() {
2506            self.zoomed = Some(pane.downgrade().into());
2507        } else {
2508            self.zoomed = None;
2509        }
2510        self.zoomed_position = None;
2511        cx.emit(Event::ZoomChanged);
2512        self.update_active_view_for_followers(cx);
2513
2514        cx.notify();
2515    }
2516
2517    fn handle_pane_event(
2518        &mut self,
2519        pane: View<Pane>,
2520        event: &pane::Event,
2521        cx: &mut ViewContext<Self>,
2522    ) {
2523        match event {
2524            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2525            pane::Event::Split(direction) => {
2526                self.split_and_clone(pane, *direction, cx);
2527            }
2528            pane::Event::Remove => self.remove_pane(pane, cx),
2529            pane::Event::ActivateItem { local } => {
2530                if *local {
2531                    self.unfollow(&pane, cx);
2532                }
2533                if &pane == self.active_pane() {
2534                    self.active_item_path_changed(cx);
2535                    self.update_active_view_for_followers(cx);
2536                }
2537            }
2538            pane::Event::ChangeItemTitle => {
2539                if pane == self.active_pane {
2540                    self.active_item_path_changed(cx);
2541                }
2542                self.update_window_edited(cx);
2543            }
2544            pane::Event::RemoveItem { item_id } => {
2545                cx.emit(Event::ActiveItemChanged);
2546                self.update_window_edited(cx);
2547                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2548                    if entry.get().entity_id() == pane.entity_id() {
2549                        entry.remove();
2550                    }
2551                }
2552            }
2553            pane::Event::Focus => {
2554                self.handle_pane_focused(pane.clone(), cx);
2555            }
2556            pane::Event::ZoomIn => {
2557                if pane == self.active_pane {
2558                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2559                    if pane.read(cx).has_focus(cx) {
2560                        self.zoomed = Some(pane.downgrade().into());
2561                        self.zoomed_position = None;
2562                        cx.emit(Event::ZoomChanged);
2563                    }
2564                    cx.notify();
2565                }
2566            }
2567            pane::Event::ZoomOut => {
2568                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2569                if self.zoomed_position.is_none() {
2570                    self.zoomed = None;
2571                    cx.emit(Event::ZoomChanged);
2572                }
2573                cx.notify();
2574            }
2575        }
2576
2577        self.serialize_workspace(cx);
2578    }
2579
2580    pub fn split_pane(
2581        &mut self,
2582        pane_to_split: View<Pane>,
2583        split_direction: SplitDirection,
2584        cx: &mut ViewContext<Self>,
2585    ) -> View<Pane> {
2586        let new_pane = self.add_pane(cx);
2587        self.center
2588            .split(&pane_to_split, &new_pane, split_direction)
2589            .unwrap();
2590        cx.notify();
2591        new_pane
2592    }
2593
2594    pub fn split_and_clone(
2595        &mut self,
2596        pane: View<Pane>,
2597        direction: SplitDirection,
2598        cx: &mut ViewContext<Self>,
2599    ) -> Option<View<Pane>> {
2600        let item = pane.read(cx).active_item()?;
2601        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2602            let new_pane = self.add_pane(cx);
2603            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2604            self.center.split(&pane, &new_pane, direction).unwrap();
2605            Some(new_pane)
2606        } else {
2607            None
2608        };
2609        cx.notify();
2610        maybe_pane_handle
2611    }
2612
2613    pub fn split_pane_with_item(
2614        &mut self,
2615        pane_to_split: WeakView<Pane>,
2616        split_direction: SplitDirection,
2617        from: WeakView<Pane>,
2618        item_id_to_move: EntityId,
2619        cx: &mut ViewContext<Self>,
2620    ) {
2621        let Some(pane_to_split) = pane_to_split.upgrade() else {
2622            return;
2623        };
2624        let Some(from) = from.upgrade() else {
2625            return;
2626        };
2627
2628        let new_pane = self.add_pane(cx);
2629        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2630        self.center
2631            .split(&pane_to_split, &new_pane, split_direction)
2632            .unwrap();
2633        cx.notify();
2634    }
2635
2636    pub fn split_pane_with_project_entry(
2637        &mut self,
2638        pane_to_split: WeakView<Pane>,
2639        split_direction: SplitDirection,
2640        project_entry: ProjectEntryId,
2641        cx: &mut ViewContext<Self>,
2642    ) -> Option<Task<Result<()>>> {
2643        let pane_to_split = pane_to_split.upgrade()?;
2644        let new_pane = self.add_pane(cx);
2645        self.center
2646            .split(&pane_to_split, &new_pane, split_direction)
2647            .unwrap();
2648
2649        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2650        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2651        Some(cx.foreground_executor().spawn(async move {
2652            task.await?;
2653            Ok(())
2654        }))
2655    }
2656
2657    pub fn move_item(
2658        &mut self,
2659        source: View<Pane>,
2660        destination: View<Pane>,
2661        item_id_to_move: EntityId,
2662        destination_index: usize,
2663        cx: &mut ViewContext<Self>,
2664    ) {
2665        let Some((item_ix, item_handle)) = source
2666            .read(cx)
2667            .items()
2668            .enumerate()
2669            .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
2670        else {
2671            // Tab was closed during drag
2672            return;
2673        };
2674
2675        let item_handle = item_handle.clone();
2676
2677        if source != destination {
2678            // Close item from previous pane
2679            source.update(cx, |source, cx| {
2680                source.remove_item(item_ix, false, true, cx);
2681            });
2682        }
2683
2684        // This automatically removes duplicate items in the pane
2685        destination.update(cx, |destination, cx| {
2686            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2687            destination.focus(cx)
2688        });
2689    }
2690
2691    fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2692        if self.center.remove(&pane).unwrap() {
2693            self.force_remove_pane(&pane, cx);
2694            self.unfollow(&pane, cx);
2695            self.last_leaders_by_pane.remove(&pane.downgrade());
2696            for removed_item in pane.read(cx).items() {
2697                self.panes_by_item.remove(&removed_item.item_id());
2698            }
2699
2700            cx.notify();
2701        } else {
2702            self.active_item_path_changed(cx);
2703        }
2704    }
2705
2706    pub fn panes(&self) -> &[View<Pane>] {
2707        &self.panes
2708    }
2709
2710    pub fn active_pane(&self) -> &View<Pane> {
2711        &self.active_pane
2712    }
2713
2714    pub fn adjacent_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
2715        self.find_pane_in_direction(SplitDirection::Right, cx)
2716            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
2717            .unwrap_or_else(|| self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx))
2718            .clone()
2719    }
2720
2721    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
2722        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
2723        weak_pane.upgrade()
2724    }
2725
2726    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2727        self.follower_states.retain(|_, state| {
2728            if state.leader_id == peer_id {
2729                for item in state.items_by_leader_view_id.values() {
2730                    item.set_leader_peer_id(None, cx);
2731                }
2732                false
2733            } else {
2734                true
2735            }
2736        });
2737        cx.notify();
2738    }
2739
2740    pub fn start_following(
2741        &mut self,
2742        leader_id: PeerId,
2743        cx: &mut ViewContext<Self>,
2744    ) -> Option<Task<Result<()>>> {
2745        let pane = self.active_pane().clone();
2746
2747        self.last_leaders_by_pane
2748            .insert(pane.downgrade(), leader_id);
2749        self.unfollow(&pane, cx);
2750        self.follower_states.insert(
2751            pane.clone(),
2752            FollowerState {
2753                leader_id,
2754                active_view_id: None,
2755                items_by_leader_view_id: Default::default(),
2756            },
2757        );
2758        cx.notify();
2759
2760        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2761        let project_id = self.project.read(cx).remote_id();
2762        let request = self.app_state.client.request(proto::Follow {
2763            room_id,
2764            project_id,
2765            leader_id: Some(leader_id),
2766        });
2767
2768        Some(cx.spawn(|this, mut cx| async move {
2769            let response = request.await?;
2770            this.update(&mut cx, |this, _| {
2771                let state = this
2772                    .follower_states
2773                    .get_mut(&pane)
2774                    .ok_or_else(|| anyhow!("following interrupted"))?;
2775                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2776                    Some(ViewId::from_proto(active_view_id)?)
2777                } else {
2778                    None
2779                };
2780                Ok::<_, anyhow::Error>(())
2781            })??;
2782            if let Some(view) = response.active_view {
2783                Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, &mut cx)
2784                    .await?;
2785            }
2786            Self::add_views_from_leader(
2787                this.clone(),
2788                leader_id,
2789                vec![pane],
2790                response.views,
2791                &mut cx,
2792            )
2793            .await?;
2794            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2795            Ok(())
2796        }))
2797    }
2798
2799    pub fn follow_next_collaborator(
2800        &mut self,
2801        _: &FollowNextCollaborator,
2802        cx: &mut ViewContext<Self>,
2803    ) {
2804        let collaborators = self.project.read(cx).collaborators();
2805        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2806            let mut collaborators = collaborators.keys().copied();
2807            for peer_id in collaborators.by_ref() {
2808                if peer_id == leader_id {
2809                    break;
2810                }
2811            }
2812            collaborators.next()
2813        } else if let Some(last_leader_id) =
2814            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2815        {
2816            if collaborators.contains_key(last_leader_id) {
2817                Some(*last_leader_id)
2818            } else {
2819                None
2820            }
2821        } else {
2822            None
2823        };
2824
2825        let pane = self.active_pane.clone();
2826        let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
2827        else {
2828            return;
2829        };
2830        if Some(leader_id) == self.unfollow(&pane, cx) {
2831            return;
2832        }
2833        if let Some(task) = self.start_following(leader_id, cx) {
2834            task.detach_and_log_err(cx)
2835        }
2836    }
2837
2838    pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) {
2839        let Some(room) = ActiveCall::global(cx).read(cx).room() else {
2840            return;
2841        };
2842        let room = room.read(cx);
2843        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2844            return;
2845        };
2846
2847        let project = self.project.read(cx);
2848
2849        let other_project_id = match remote_participant.location {
2850            call::ParticipantLocation::External => None,
2851            call::ParticipantLocation::UnsharedProject => None,
2852            call::ParticipantLocation::SharedProject { project_id } => {
2853                if Some(project_id) == project.remote_id() {
2854                    None
2855                } else {
2856                    Some(project_id)
2857                }
2858            }
2859        };
2860
2861        // if they are active in another project, follow there.
2862        if let Some(project_id) = other_project_id {
2863            let app_state = self.app_state.clone();
2864            crate::join_in_room_project(project_id, remote_participant.user.id, app_state, cx)
2865                .detach_and_log_err(cx);
2866        }
2867
2868        // if you're already following, find the right pane and focus it.
2869        for (pane, state) in &self.follower_states {
2870            if leader_id == state.leader_id {
2871                cx.focus_view(pane);
2872                return;
2873            }
2874        }
2875
2876        // Otherwise, follow.
2877        if let Some(task) = self.start_following(leader_id, cx) {
2878            task.detach_and_log_err(cx)
2879        }
2880    }
2881
2882    pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
2883        let state = self.follower_states.remove(pane)?;
2884        let leader_id = state.leader_id;
2885        for (_, item) in state.items_by_leader_view_id {
2886            item.set_leader_peer_id(None, cx);
2887        }
2888
2889        if self
2890            .follower_states
2891            .values()
2892            .all(|state| state.leader_id != leader_id)
2893        {
2894            let project_id = self.project.read(cx).remote_id();
2895            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2896            self.app_state
2897                .client
2898                .send(proto::Unfollow {
2899                    room_id,
2900                    project_id,
2901                    leader_id: Some(leader_id),
2902                })
2903                .log_err();
2904        }
2905
2906        cx.notify();
2907        Some(leader_id)
2908    }
2909
2910    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2911        self.follower_states
2912            .values()
2913            .any(|state| state.leader_id == peer_id)
2914    }
2915
2916    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2917        cx.emit(Event::ActiveItemChanged);
2918        let active_entry = self.active_project_path(cx);
2919        self.project
2920            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2921
2922        self.update_window_title(cx);
2923    }
2924
2925    fn update_window_title(&mut self, cx: &mut WindowContext) {
2926        let project = self.project().read(cx);
2927        let mut title = String::new();
2928
2929        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2930            let filename = path
2931                .path
2932                .file_name()
2933                .map(|s| s.to_string_lossy())
2934                .or_else(|| {
2935                    Some(Cow::Borrowed(
2936                        project
2937                            .worktree_for_id(path.worktree_id, cx)?
2938                            .read(cx)
2939                            .root_name(),
2940                    ))
2941                });
2942
2943            if let Some(filename) = filename {
2944                title.push_str(filename.as_ref());
2945                title.push_str("");
2946            }
2947        }
2948
2949        for (i, name) in project.worktree_root_names(cx).enumerate() {
2950            if i > 0 {
2951                title.push_str(", ");
2952            }
2953            title.push_str(name);
2954        }
2955
2956        if title.is_empty() {
2957            title = "empty project".to_string();
2958        }
2959
2960        if project.is_remote() {
2961            title.push_str("");
2962        } else if project.is_shared() {
2963            title.push_str("");
2964        }
2965
2966        cx.set_window_title(&title);
2967    }
2968
2969    fn update_window_edited(&mut self, cx: &mut WindowContext) {
2970        let is_edited = !self.project.read(cx).is_disconnected()
2971            && self
2972                .items(cx)
2973                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2974        if is_edited != self.window_edited {
2975            self.window_edited = is_edited;
2976            cx.set_window_edited(self.window_edited)
2977        }
2978    }
2979
2980    fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
2981        if self.notifications.is_empty() {
2982            None
2983        } else {
2984            Some(
2985                div()
2986                    .absolute()
2987                    .right_3()
2988                    .bottom_3()
2989                    .w_112()
2990                    .h_full()
2991                    .flex()
2992                    .flex_col()
2993                    .justify_end()
2994                    .gap_2()
2995                    .children(
2996                        self.notifications
2997                            .iter()
2998                            .map(|(_, notification)| notification.to_any()),
2999                    ),
3000            )
3001        }
3002    }
3003
3004    // RPC handlers
3005
3006    fn active_view_for_follower(
3007        &self,
3008        follower_project_id: Option<u64>,
3009        cx: &mut ViewContext<Self>,
3010    ) -> Option<proto::View> {
3011        let item = self.active_item(cx)?;
3012        let leader_id = self
3013            .pane_for(&*item)
3014            .and_then(|pane| self.leader_for_pane(&pane));
3015
3016        let item_handle = item.to_followable_item_handle(cx)?;
3017        let id = item_handle.remote_id(&self.app_state.client, cx)?;
3018        let variant = item_handle.to_state_proto(cx)?;
3019
3020        if item_handle.is_project_item(cx)
3021            && (follower_project_id.is_none()
3022                || follower_project_id != self.project.read(cx).remote_id())
3023        {
3024            return None;
3025        }
3026
3027        Some(proto::View {
3028            id: Some(id.to_proto()),
3029            leader_id,
3030            variant: Some(variant),
3031        })
3032    }
3033
3034    fn handle_follow(
3035        &mut self,
3036        follower_project_id: Option<u64>,
3037        cx: &mut ViewContext<Self>,
3038    ) -> proto::FollowResponse {
3039        let client = &self.app_state.client;
3040        let project_id = self.project.read(cx).remote_id();
3041
3042        let active_view = self.active_view_for_follower(follower_project_id, cx);
3043        let active_view_id = active_view.as_ref().and_then(|view| view.id.clone());
3044
3045        cx.notify();
3046
3047        proto::FollowResponse {
3048            active_view,
3049            // TODO: once v0.124.0 is retired we can stop sending these
3050            active_view_id,
3051            views: self
3052                .panes()
3053                .iter()
3054                .flat_map(|pane| {
3055                    let leader_id = self.leader_for_pane(pane);
3056                    pane.read(cx).items().filter_map({
3057                        let cx = &cx;
3058                        move |item| {
3059                            let item = item.to_followable_item_handle(cx)?;
3060
3061                            // If the item belongs to a particular project, then it should
3062                            // only be included if this project is shared, and the follower
3063                            // is in the project.
3064                            //
3065                            // Some items, like channel notes, do not belong to a particular
3066                            // project, so they should be included regardless of whether the
3067                            // current project is shared, or what project the follower is in.
3068                            if item.is_project_item(cx)
3069                                && (project_id.is_none() || project_id != follower_project_id)
3070                            {
3071                                return None;
3072                            }
3073
3074                            let id = item.remote_id(client, cx)?.to_proto();
3075                            let variant = item.to_state_proto(cx)?;
3076                            Some(proto::View {
3077                                id: Some(id),
3078                                leader_id,
3079                                variant: Some(variant),
3080                            })
3081                        }
3082                    })
3083                })
3084                .collect(),
3085        }
3086    }
3087
3088    fn handle_update_followers(
3089        &mut self,
3090        leader_id: PeerId,
3091        message: proto::UpdateFollowers,
3092        _cx: &mut ViewContext<Self>,
3093    ) {
3094        self.leader_updates_tx
3095            .unbounded_send((leader_id, message))
3096            .ok();
3097    }
3098
3099    async fn process_leader_update(
3100        this: &WeakView<Self>,
3101        leader_id: PeerId,
3102        update: proto::UpdateFollowers,
3103        cx: &mut AsyncWindowContext,
3104    ) -> Result<()> {
3105        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
3106            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
3107                let panes_missing_view = this.update(cx, |this, _| {
3108                    let mut panes = vec![];
3109                    for (pane, state) in &mut this.follower_states {
3110                        if state.leader_id != leader_id {
3111                            continue;
3112                        }
3113
3114                        state.active_view_id =
3115                            if let Some(active_view_id) = update_active_view.id.clone() {
3116                                Some(ViewId::from_proto(active_view_id)?)
3117                            } else {
3118                                None
3119                            };
3120
3121                        if state.active_view_id.is_some_and(|view_id| {
3122                            !state.items_by_leader_view_id.contains_key(&view_id)
3123                        }) {
3124                            panes.push(pane.clone())
3125                        }
3126                    }
3127                    anyhow::Ok(panes)
3128                })??;
3129
3130                if let Some(view) = update_active_view.view {
3131                    for pane in panes_missing_view {
3132                        Self::add_view_from_leader(this.clone(), leader_id, pane.clone(), &view, cx)
3133                            .await?
3134                    }
3135                }
3136            }
3137            proto::update_followers::Variant::UpdateView(update_view) => {
3138                let variant = update_view
3139                    .variant
3140                    .ok_or_else(|| anyhow!("missing update view variant"))?;
3141                let id = update_view
3142                    .id
3143                    .ok_or_else(|| anyhow!("missing update view id"))?;
3144                let mut tasks = Vec::new();
3145                this.update(cx, |this, cx| {
3146                    let project = this.project.clone();
3147                    for (_, state) in &mut this.follower_states {
3148                        if state.leader_id == leader_id {
3149                            let view_id = ViewId::from_proto(id.clone())?;
3150                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
3151                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
3152                            }
3153                        }
3154                    }
3155                    anyhow::Ok(())
3156                })??;
3157                try_join_all(tasks).await.log_err();
3158            }
3159            proto::update_followers::Variant::CreateView(view) => {
3160                let panes = this.update(cx, |this, _| {
3161                    this.follower_states
3162                        .iter()
3163                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
3164                        .cloned()
3165                        .collect()
3166                })?;
3167                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
3168            }
3169        }
3170        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
3171        Ok(())
3172    }
3173
3174    async fn add_view_from_leader(
3175        this: WeakView<Self>,
3176        leader_id: PeerId,
3177        pane: View<Pane>,
3178        view: &proto::View,
3179        cx: &mut AsyncWindowContext,
3180    ) -> Result<()> {
3181        let this = this.upgrade().context("workspace dropped")?;
3182
3183        let item_builders = cx.update(|cx| {
3184            cx.default_global::<FollowableItemBuilders>()
3185                .values()
3186                .map(|b| b.0)
3187                .collect::<Vec<_>>()
3188        })?;
3189
3190        let Some(id) = view.id.clone() else {
3191            return Err(anyhow!("no id for view"));
3192        };
3193        let id = ViewId::from_proto(id)?;
3194
3195        let mut variant = view.variant.clone();
3196        if variant.is_none() {
3197            Err(anyhow!("missing view variant"))?;
3198        }
3199
3200        let task = item_builders.iter().find_map(|build_item| {
3201            cx.update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx))
3202                .log_err()
3203                .flatten()
3204        });
3205        let Some(task) = task else {
3206            return Err(anyhow!(
3207                "failed to construct view from leader (maybe from a different version of zed?)"
3208            ));
3209        };
3210
3211        let item = task.await?;
3212
3213        this.update(cx, |this, cx| {
3214            let state = this.follower_states.get_mut(&pane)?;
3215            item.set_leader_peer_id(Some(leader_id), cx);
3216            state.items_by_leader_view_id.insert(id, item);
3217
3218            Some(())
3219        })?;
3220
3221        Ok(())
3222    }
3223
3224    async fn add_views_from_leader(
3225        this: WeakView<Self>,
3226        leader_id: PeerId,
3227        panes: Vec<View<Pane>>,
3228        views: Vec<proto::View>,
3229        cx: &mut AsyncWindowContext,
3230    ) -> Result<()> {
3231        let this = this.upgrade().context("workspace dropped")?;
3232
3233        let item_builders = cx.update(|cx| {
3234            cx.default_global::<FollowableItemBuilders>()
3235                .values()
3236                .map(|b| b.0)
3237                .collect::<Vec<_>>()
3238        })?;
3239
3240        let mut item_tasks_by_pane = HashMap::default();
3241        for pane in panes {
3242            let mut item_tasks = Vec::new();
3243            let mut leader_view_ids = Vec::new();
3244            for view in &views {
3245                let Some(id) = &view.id else {
3246                    continue;
3247                };
3248                let id = ViewId::from_proto(id.clone())?;
3249                let mut variant = view.variant.clone();
3250                if variant.is_none() {
3251                    Err(anyhow!("missing view variant"))?;
3252                }
3253                for build_item in &item_builders {
3254                    let task = cx.update(|cx| {
3255                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
3256                    })?;
3257                    if let Some(task) = task {
3258                        item_tasks.push(task);
3259                        leader_view_ids.push(id);
3260                        break;
3261                    } else if variant.is_none() {
3262                        Err(anyhow!(
3263                            "failed to construct view from leader (maybe from a different version of zed?)"
3264                        ))?;
3265                    }
3266                }
3267            }
3268
3269            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
3270        }
3271
3272        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
3273            let items = futures::future::try_join_all(item_tasks).await?;
3274            this.update(cx, |this, cx| {
3275                let state = this.follower_states.get_mut(&pane)?;
3276                for (id, item) in leader_view_ids.into_iter().zip(items) {
3277                    item.set_leader_peer_id(Some(leader_id), cx);
3278                    state.items_by_leader_view_id.insert(id, item);
3279                }
3280
3281                Some(())
3282            })?;
3283        }
3284        Ok(())
3285    }
3286
3287    pub fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
3288        let mut is_project_item = true;
3289        let mut update = proto::UpdateActiveView::default();
3290        if cx.is_window_active() {
3291            if let Some(item) = self.active_item(cx) {
3292                if item.focus_handle(cx).contains_focused(cx) {
3293                    let leader_id = self
3294                        .pane_for(&*item)
3295                        .and_then(|pane| self.leader_for_pane(&pane));
3296
3297                    if let Some(item) = item.to_followable_item_handle(cx) {
3298                        let id = item
3299                            .remote_id(&self.app_state.client, cx)
3300                            .map(|id| id.to_proto());
3301
3302                        if let Some(id) = id.clone() {
3303                            if let Some(variant) = item.to_state_proto(cx) {
3304                                let view = Some(proto::View {
3305                                    id: Some(id.clone()),
3306                                    leader_id,
3307                                    variant: Some(variant),
3308                                });
3309
3310                                is_project_item = item.is_project_item(cx);
3311                                update = proto::UpdateActiveView {
3312                                    view,
3313                                    // TODO: once v0.124.0 is retired we can stop sending these
3314                                    id: Some(id),
3315                                    leader_id,
3316                                };
3317                            }
3318                        };
3319                    }
3320                }
3321            }
3322        }
3323
3324        if &update.id != &self.last_active_view_id {
3325            self.last_active_view_id = update.id.clone();
3326            self.update_followers(
3327                is_project_item,
3328                proto::update_followers::Variant::UpdateActiveView(update),
3329                cx,
3330            );
3331        }
3332    }
3333
3334    fn update_followers(
3335        &self,
3336        project_only: bool,
3337        update: proto::update_followers::Variant,
3338        cx: &mut WindowContext,
3339    ) -> Option<()> {
3340        // If this update only applies to for followers in the current project,
3341        // then skip it unless this project is shared. If it applies to all
3342        // followers, regardless of project, then set `project_id` to none,
3343        // indicating that it goes to all followers.
3344        let project_id = if project_only {
3345            Some(self.project.read(cx).remote_id()?)
3346        } else {
3347            None
3348        };
3349        self.app_state().workspace_store.update(cx, |store, cx| {
3350            store.update_followers(project_id, update, cx)
3351        })
3352    }
3353
3354    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
3355        self.follower_states.get(pane).map(|state| state.leader_id)
3356    }
3357
3358    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3359        cx.notify();
3360
3361        let call = self.active_call()?;
3362        let room = call.read(cx).room()?.read(cx);
3363        let participant = room.remote_participant_for_peer_id(leader_id)?;
3364        let mut items_to_activate = Vec::new();
3365
3366        let leader_in_this_app;
3367        let leader_in_this_project;
3368        match participant.location {
3369            call::ParticipantLocation::SharedProject { project_id } => {
3370                leader_in_this_app = true;
3371                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3372            }
3373            call::ParticipantLocation::UnsharedProject => {
3374                leader_in_this_app = true;
3375                leader_in_this_project = false;
3376            }
3377            call::ParticipantLocation::External => {
3378                leader_in_this_app = false;
3379                leader_in_this_project = false;
3380            }
3381        };
3382
3383        for (pane, state) in &self.follower_states {
3384            if state.leader_id != leader_id {
3385                continue;
3386            }
3387            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
3388                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
3389                    if leader_in_this_project || !item.is_project_item(cx) {
3390                        items_to_activate.push((pane.clone(), item.boxed_clone()));
3391                    }
3392                }
3393                continue;
3394            }
3395
3396            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3397                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3398            }
3399        }
3400
3401        for (pane, item) in items_to_activate {
3402            let pane_was_focused = pane.read(cx).has_focus(cx);
3403            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3404                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3405            } else {
3406                pane.update(cx, |pane, cx| {
3407                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3408                });
3409            }
3410
3411            if pane_was_focused {
3412                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3413            }
3414        }
3415
3416        None
3417    }
3418
3419    fn shared_screen_for_peer(
3420        &self,
3421        peer_id: PeerId,
3422        pane: &View<Pane>,
3423        cx: &mut WindowContext,
3424    ) -> Option<View<SharedScreen>> {
3425        let call = self.active_call()?;
3426        let room = call.read(cx).room()?.read(cx);
3427        let participant = room.remote_participant_for_peer_id(peer_id)?;
3428        let track = participant.video_tracks.values().next()?.clone();
3429        let user = participant.user.clone();
3430
3431        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3432            if item.read(cx).peer_id == peer_id {
3433                return Some(item);
3434            }
3435        }
3436
3437        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3438    }
3439
3440    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
3441        if cx.is_window_active() {
3442            self.update_active_view_for_followers(cx);
3443            cx.background_executor()
3444                .spawn(persistence::DB.update_timestamp(self.database_id()))
3445                .detach();
3446        } else {
3447            for pane in &self.panes {
3448                pane.update(cx, |pane, cx| {
3449                    if let Some(item) = pane.active_item() {
3450                        item.workspace_deactivated(cx);
3451                    }
3452                    if matches!(
3453                        WorkspaceSettings::get_global(cx).autosave,
3454                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3455                    ) {
3456                        for item in pane.items() {
3457                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3458                                .detach_and_log_err(cx);
3459                        }
3460                    }
3461                });
3462            }
3463        }
3464    }
3465
3466    fn active_call(&self) -> Option<&Model<ActiveCall>> {
3467        self.active_call.as_ref().map(|(call, _)| call)
3468    }
3469
3470    fn on_active_call_event(
3471        &mut self,
3472        _: Model<ActiveCall>,
3473        event: &call::room::Event,
3474        cx: &mut ViewContext<Self>,
3475    ) {
3476        match event {
3477            call::room::Event::ParticipantLocationChanged { participant_id }
3478            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3479                self.leader_updated(*participant_id, cx);
3480            }
3481            _ => {}
3482        }
3483    }
3484
3485    pub fn database_id(&self) -> WorkspaceId {
3486        self.database_id
3487    }
3488
3489    fn local_paths(&self, cx: &AppContext) -> Option<LocalPaths> {
3490        let project = self.project().read(cx);
3491
3492        if project.is_local() {
3493            Some(LocalPaths::new(
3494                project
3495                    .visible_worktrees(cx)
3496                    .map(|worktree| worktree.read(cx).abs_path())
3497                    .collect::<Vec<_>>(),
3498            ))
3499        } else {
3500            None
3501        }
3502    }
3503
3504    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3505        match member {
3506            Member::Axis(PaneAxis { members, .. }) => {
3507                for child in members.iter() {
3508                    self.remove_panes(child.clone(), cx)
3509                }
3510            }
3511            Member::Pane(pane) => {
3512                self.force_remove_pane(&pane, cx);
3513            }
3514        }
3515    }
3516
3517    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
3518        self.panes.retain(|p| p != pane);
3519        self.panes
3520            .last()
3521            .unwrap()
3522            .update(cx, |pane, cx| pane.focus(cx));
3523        if self.last_active_center_pane == Some(pane.downgrade()) {
3524            self.last_active_center_pane = None;
3525        }
3526        cx.notify();
3527    }
3528
3529    fn serialize_workspace(&mut self, cx: &mut ViewContext<Self>) {
3530        if self._schedule_serialize.is_none() {
3531            self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3532                cx.background_executor()
3533                    .timer(Duration::from_millis(100))
3534                    .await;
3535                this.update(&mut cx, |this, cx| {
3536                    this.serialize_workspace_internal(cx).detach();
3537                    this._schedule_serialize.take();
3538                })
3539                .log_err();
3540            }));
3541        }
3542    }
3543
3544    fn serialize_workspace_internal(&self, cx: &mut WindowContext) -> Task<()> {
3545        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3546            let (items, active) = {
3547                let pane = pane_handle.read(cx);
3548                let active_item_id = pane.active_item().map(|item| item.item_id());
3549                (
3550                    pane.items()
3551                        .filter_map(|item_handle| {
3552                            Some(SerializedItem {
3553                                kind: Arc::from(item_handle.serialized_item_kind()?),
3554                                item_id: item_handle.item_id().as_u64(),
3555                                active: Some(item_handle.item_id()) == active_item_id,
3556                                preview: pane.is_active_preview_item(item_handle.item_id()),
3557                            })
3558                        })
3559                        .collect::<Vec<_>>(),
3560                    pane.has_focus(cx),
3561                )
3562            };
3563
3564            SerializedPane::new(items, active)
3565        }
3566
3567        fn build_serialized_pane_group(
3568            pane_group: &Member,
3569            cx: &WindowContext,
3570        ) -> SerializedPaneGroup {
3571            match pane_group {
3572                Member::Axis(PaneAxis {
3573                    axis,
3574                    members,
3575                    flexes,
3576                    bounding_boxes: _,
3577                }) => SerializedPaneGroup::Group {
3578                    axis: SerializedAxis(*axis),
3579                    children: members
3580                        .iter()
3581                        .map(|member| build_serialized_pane_group(member, cx))
3582                        .collect::<Vec<_>>(),
3583                    flexes: Some(flexes.lock().clone()),
3584                },
3585                Member::Pane(pane_handle) => {
3586                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
3587                }
3588            }
3589        }
3590
3591        fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
3592            let left_dock = this.left_dock.read(cx);
3593            let left_visible = left_dock.is_open();
3594            let left_active_panel = left_dock
3595                .visible_panel()
3596                .map(|panel| panel.persistent_name().to_string());
3597            let left_dock_zoom = left_dock
3598                .visible_panel()
3599                .map(|panel| panel.is_zoomed(cx))
3600                .unwrap_or(false);
3601
3602            let right_dock = this.right_dock.read(cx);
3603            let right_visible = right_dock.is_open();
3604            let right_active_panel = right_dock
3605                .visible_panel()
3606                .map(|panel| panel.persistent_name().to_string());
3607            let right_dock_zoom = right_dock
3608                .visible_panel()
3609                .map(|panel| panel.is_zoomed(cx))
3610                .unwrap_or(false);
3611
3612            let bottom_dock = this.bottom_dock.read(cx);
3613            let bottom_visible = bottom_dock.is_open();
3614            let bottom_active_panel = bottom_dock
3615                .visible_panel()
3616                .map(|panel| panel.persistent_name().to_string());
3617            let bottom_dock_zoom = bottom_dock
3618                .visible_panel()
3619                .map(|panel| panel.is_zoomed(cx))
3620                .unwrap_or(false);
3621
3622            DockStructure {
3623                left: DockData {
3624                    visible: left_visible,
3625                    active_panel: left_active_panel,
3626                    zoom: left_dock_zoom,
3627                },
3628                right: DockData {
3629                    visible: right_visible,
3630                    active_panel: right_active_panel,
3631                    zoom: right_dock_zoom,
3632                },
3633                bottom: DockData {
3634                    visible: bottom_visible,
3635                    active_panel: bottom_active_panel,
3636                    zoom: bottom_dock_zoom,
3637                },
3638            }
3639        }
3640
3641        let location = if let Some(local_paths) = self.local_paths(cx) {
3642            if !local_paths.paths().is_empty() {
3643                Some(SerializedWorkspaceLocation::Local(local_paths))
3644            } else {
3645                None
3646            }
3647        } else if let Some(remote_project_id) = self.project().read(cx).remote_project_id() {
3648            let store = remote_projects::Store::global(cx).read(cx);
3649            maybe!({
3650                let project = store.remote_project(remote_project_id)?;
3651                let dev_server = store.dev_server(project.dev_server_id)?;
3652
3653                let remote_project = SerializedRemoteProject {
3654                    id: remote_project_id,
3655                    dev_server_name: dev_server.name.to_string(),
3656                    path: project.path.to_string(),
3657                };
3658                Some(SerializedWorkspaceLocation::Remote(remote_project))
3659            })
3660        } else {
3661            None
3662        };
3663
3664        // don't save workspace state for the empty workspace.
3665        if let Some(location) = location {
3666            let center_group = build_serialized_pane_group(&self.center.root, cx);
3667            let docks = build_serialized_docks(self, cx);
3668            let serialized_workspace = SerializedWorkspace {
3669                id: self.database_id,
3670                location,
3671                center_group,
3672                bounds: Default::default(),
3673                display: Default::default(),
3674                docks,
3675                fullscreen: cx.is_fullscreen(),
3676                centered_layout: self.centered_layout,
3677            };
3678            return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
3679        }
3680        Task::ready(())
3681    }
3682
3683    pub(crate) fn load_workspace(
3684        serialized_workspace: SerializedWorkspace,
3685        paths_to_open: Vec<Option<ProjectPath>>,
3686        cx: &mut ViewContext<Workspace>,
3687    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3688        cx.spawn(|workspace, mut cx| async move {
3689            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3690
3691            let mut center_group = None;
3692            let mut center_items = None;
3693
3694            // Traverse the splits tree and add to things
3695            if let Some((group, active_pane, items)) = serialized_workspace
3696                .center_group
3697                .deserialize(
3698                    &project,
3699                    serialized_workspace.id,
3700                    workspace.clone(),
3701                    &mut cx,
3702                )
3703                .await
3704            {
3705                center_items = Some(items);
3706                center_group = Some((group, active_pane))
3707            }
3708
3709            let mut items_by_project_path = cx.update(|cx| {
3710                center_items
3711                    .unwrap_or_default()
3712                    .into_iter()
3713                    .filter_map(|item| {
3714                        let item = item?;
3715                        let project_path = item.project_path(cx)?;
3716                        Some((project_path, item))
3717                    })
3718                    .collect::<HashMap<_, _>>()
3719            })?;
3720
3721            let opened_items = paths_to_open
3722                .into_iter()
3723                .map(|path_to_open| {
3724                    path_to_open
3725                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3726                })
3727                .collect::<Vec<_>>();
3728
3729            // Remove old panes from workspace panes list
3730            workspace.update(&mut cx, |workspace, cx| {
3731                if let Some((center_group, active_pane)) = center_group {
3732                    workspace.remove_panes(workspace.center.root.clone(), cx);
3733
3734                    // Swap workspace center group
3735                    workspace.center = PaneGroup::with_root(center_group);
3736                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3737                    if let Some(active_pane) = active_pane {
3738                        workspace.active_pane = active_pane;
3739                        cx.focus_self();
3740                    } else {
3741                        workspace.active_pane = workspace.center.first_pane().clone();
3742                    }
3743                }
3744
3745                let docks = serialized_workspace.docks;
3746
3747                let right = docks.right.clone();
3748                workspace
3749                    .right_dock
3750                    .update(cx, |dock, _| dock.serialized_dock = Some(right));
3751                let left = docks.left.clone();
3752                workspace
3753                    .left_dock
3754                    .update(cx, |dock, _| dock.serialized_dock = Some(left));
3755                let bottom = docks.bottom.clone();
3756                workspace
3757                    .bottom_dock
3758                    .update(cx, |dock, _| dock.serialized_dock = Some(bottom));
3759
3760                cx.notify();
3761            })?;
3762
3763            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3764            workspace
3765                .update(&mut cx, |workspace, cx| {
3766                    workspace.serialize_workspace_internal(cx).detach();
3767                })
3768                .ok();
3769
3770            Ok(opened_items)
3771        })
3772    }
3773
3774    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3775        self.add_workspace_actions_listeners(div, cx)
3776            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3777            .on_action(cx.listener(Self::close_all_items_and_panes))
3778            .on_action(cx.listener(Self::save_all))
3779            .on_action(cx.listener(Self::send_keystrokes))
3780            .on_action(cx.listener(Self::add_folder_to_project))
3781            .on_action(cx.listener(Self::follow_next_collaborator))
3782            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3783                let pane = workspace.active_pane().clone();
3784                workspace.unfollow(&pane, cx);
3785            }))
3786            .on_action(cx.listener(|workspace, action: &Save, cx| {
3787                workspace
3788                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3789                    .detach_and_log_err(cx);
3790            }))
3791            .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, cx| {
3792                workspace
3793                    .save_active_item(SaveIntent::SaveWithoutFormat, cx)
3794                    .detach_and_log_err(cx);
3795            }))
3796            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3797                workspace
3798                    .save_active_item(SaveIntent::SaveAs, cx)
3799                    .detach_and_log_err(cx);
3800            }))
3801            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3802                workspace.activate_previous_pane(cx)
3803            }))
3804            .on_action(
3805                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3806            )
3807            .on_action(
3808                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3809                    workspace.activate_pane_in_direction(action.0, cx)
3810                }),
3811            )
3812            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3813                workspace.swap_pane_in_direction(action.0, cx)
3814            }))
3815            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3816                this.toggle_dock(DockPosition::Left, cx);
3817            }))
3818            .on_action(
3819                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3820                    workspace.toggle_dock(DockPosition::Right, cx);
3821                }),
3822            )
3823            .on_action(
3824                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3825                    workspace.toggle_dock(DockPosition::Bottom, cx);
3826                }),
3827            )
3828            .on_action(
3829                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3830                    workspace.close_all_docks(cx);
3831                }),
3832            )
3833            .on_action(cx.listener(Workspace::open))
3834            .on_action(cx.listener(Workspace::close_window))
3835            .on_action(cx.listener(Workspace::activate_pane_at_index))
3836            .on_action(
3837                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3838                    workspace.reopen_closed_item(cx).detach();
3839                }),
3840            )
3841            .on_action(cx.listener(Workspace::toggle_centered_layout))
3842    }
3843
3844    #[cfg(any(test, feature = "test-support"))]
3845    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3846        use node_runtime::FakeNodeRuntime;
3847
3848        let client = project.read(cx).client();
3849        let user_store = project.read(cx).user_store();
3850
3851        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
3852        cx.activate_window();
3853        let app_state = Arc::new(AppState {
3854            languages: project.read(cx).languages().clone(),
3855            workspace_store,
3856            client,
3857            user_store,
3858            fs: project.read(cx).fs().clone(),
3859            build_window_options: |_, _| Default::default(),
3860            node_runtime: FakeNodeRuntime::new(),
3861        });
3862        let workspace = Self::new(Default::default(), project, app_state, cx);
3863        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3864        workspace
3865    }
3866
3867    pub fn register_action<A: Action>(
3868        &mut self,
3869        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3870    ) -> &mut Self {
3871        let callback = Arc::new(callback);
3872
3873        self.workspace_actions.push(Box::new(move |div, cx| {
3874            let callback = callback.clone();
3875            div.on_action(
3876                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3877            )
3878        }));
3879        self
3880    }
3881
3882    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3883        let mut div = div
3884            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3885            .on_action(cx.listener(Self::close_all_items_and_panes))
3886            .on_action(cx.listener(Self::add_folder_to_project))
3887            .on_action(cx.listener(Self::save_all))
3888            .on_action(cx.listener(Self::open));
3889        for action in self.workspace_actions.iter() {
3890            div = (action)(div, cx)
3891        }
3892        div
3893    }
3894
3895    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3896        self.modal_layer.read(cx).has_active_modal()
3897    }
3898
3899    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
3900        self.modal_layer.read(cx).active_modal()
3901    }
3902
3903    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
3904    where
3905        B: FnOnce(&mut ViewContext<V>) -> V,
3906    {
3907        self.modal_layer
3908            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3909    }
3910
3911    pub fn toggle_centered_layout(&mut self, _: &ToggleCenteredLayout, cx: &mut ViewContext<Self>) {
3912        self.centered_layout = !self.centered_layout;
3913        cx.background_executor()
3914            .spawn(DB.set_centered_layout(self.database_id, self.centered_layout))
3915            .detach_and_log_err(cx);
3916        cx.notify();
3917    }
3918
3919    fn adjust_padding(padding: Option<f32>) -> f32 {
3920        padding
3921            .unwrap_or(Self::DEFAULT_PADDING)
3922            .min(Self::MAX_PADDING)
3923            .max(0.0)
3924    }
3925}
3926
3927fn window_bounds_env_override() -> Option<Bounds<DevicePixels>> {
3928    ZED_WINDOW_POSITION
3929        .zip(*ZED_WINDOW_SIZE)
3930        .map(|(position, size)| Bounds {
3931            origin: position,
3932            size,
3933        })
3934}
3935
3936fn open_items(
3937    serialized_workspace: Option<SerializedWorkspace>,
3938    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3939    app_state: Arc<AppState>,
3940    cx: &mut ViewContext<Workspace>,
3941) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3942    let restored_items = serialized_workspace.map(|serialized_workspace| {
3943        Workspace::load_workspace(
3944            serialized_workspace,
3945            project_paths_to_open
3946                .iter()
3947                .map(|(_, project_path)| project_path)
3948                .cloned()
3949                .collect(),
3950            cx,
3951        )
3952    });
3953
3954    cx.spawn(|workspace, mut cx| async move {
3955        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3956
3957        if let Some(restored_items) = restored_items {
3958            let restored_items = restored_items.await?;
3959
3960            let restored_project_paths = restored_items
3961                .iter()
3962                .filter_map(|item| {
3963                    cx.update(|cx| item.as_ref()?.project_path(cx))
3964                        .ok()
3965                        .flatten()
3966                })
3967                .collect::<HashSet<_>>();
3968
3969            for restored_item in restored_items {
3970                opened_items.push(restored_item.map(Ok));
3971            }
3972
3973            project_paths_to_open
3974                .iter_mut()
3975                .for_each(|(_, project_path)| {
3976                    if let Some(project_path_to_open) = project_path {
3977                        if restored_project_paths.contains(project_path_to_open) {
3978                            *project_path = None;
3979                        }
3980                    }
3981                });
3982        } else {
3983            for _ in 0..project_paths_to_open.len() {
3984                opened_items.push(None);
3985            }
3986        }
3987        assert!(opened_items.len() == project_paths_to_open.len());
3988
3989        let tasks =
3990            project_paths_to_open
3991                .into_iter()
3992                .enumerate()
3993                .map(|(ix, (abs_path, project_path))| {
3994                    let workspace = workspace.clone();
3995                    cx.spawn(|mut cx| {
3996                        let fs = app_state.fs.clone();
3997                        async move {
3998                            let file_project_path = project_path?;
3999                            if fs.is_dir(&abs_path).await {
4000                                None
4001                            } else {
4002                                Some((
4003                                    ix,
4004                                    workspace
4005                                        .update(&mut cx, |workspace, cx| {
4006                                            workspace.open_path(file_project_path, None, true, cx)
4007                                        })
4008                                        .log_err()?
4009                                        .await,
4010                                ))
4011                            }
4012                        }
4013                    })
4014                });
4015
4016        let tasks = tasks.collect::<Vec<_>>();
4017
4018        let tasks = futures::future::join_all(tasks);
4019        for (ix, path_open_result) in tasks.await.into_iter().flatten() {
4020            opened_items[ix] = Some(path_open_result);
4021        }
4022
4023        Ok(opened_items)
4024    })
4025}
4026
4027enum ActivateInDirectionTarget {
4028    Pane(View<Pane>),
4029    Dock(View<Dock>),
4030}
4031
4032fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
4033    const REPORT_ISSUE_URL: &str = "https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
4034
4035    workspace
4036        .update(cx, |workspace, cx| {
4037            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
4038                struct DatabaseFailedNotification;
4039
4040                workspace.show_notification_once(
4041                    NotificationId::unique::<DatabaseFailedNotification>(),
4042                    cx,
4043                    |cx| {
4044                        cx.new_view(|_| {
4045                            MessageNotification::new("Failed to load the database file.")
4046                                .with_click_message("Click to let us know about this error")
4047                                .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
4048                        })
4049                    },
4050                );
4051            }
4052        })
4053        .log_err();
4054}
4055
4056impl FocusableView for Workspace {
4057    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4058        self.active_pane.focus_handle(cx)
4059    }
4060}
4061
4062#[derive(Clone, Render)]
4063struct DraggedDock(DockPosition);
4064
4065impl Render for Workspace {
4066    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4067        let mut context = KeyContext::new_with_defaults();
4068        context.add("Workspace");
4069        let centered_layout = self.centered_layout
4070            && self.center.panes().len() == 1
4071            && self.active_item(cx).is_some();
4072        let render_padding = |size| {
4073            (size > 0.0).then(|| {
4074                div()
4075                    .h_full()
4076                    .w(relative(size))
4077                    .bg(cx.theme().colors().editor_background)
4078                    .border_color(cx.theme().colors().pane_group_border)
4079            })
4080        };
4081        let paddings = if centered_layout {
4082            let settings = WorkspaceSettings::get_global(cx).centered_layout;
4083            (
4084                render_padding(Self::adjust_padding(settings.left_padding)),
4085                render_padding(Self::adjust_padding(settings.right_padding)),
4086            )
4087        } else {
4088            (None, None)
4089        };
4090        let (ui_font, ui_font_size) = {
4091            let theme_settings = ThemeSettings::get_global(cx);
4092            (
4093                theme_settings.ui_font.family.clone(),
4094                theme_settings.ui_font_size,
4095            )
4096        };
4097
4098        let theme = cx.theme().clone();
4099        let colors = theme.colors();
4100        cx.set_rem_size(ui_font_size);
4101
4102        self.actions(div(), cx)
4103            .key_context(context)
4104            .relative()
4105            .size_full()
4106            .flex()
4107            .flex_col()
4108            .font_family(ui_font)
4109            .gap_0()
4110            .justify_start()
4111            .items_start()
4112            .text_color(colors.text)
4113            .bg(colors.background)
4114            .children(self.titlebar_item.clone())
4115            .child(
4116                div()
4117                    .id("workspace")
4118                    .relative()
4119                    .flex_1()
4120                    .w_full()
4121                    .flex()
4122                    .flex_col()
4123                    .overflow_hidden()
4124                    .border_t()
4125                    .border_b()
4126                    .border_color(colors.border)
4127                    .child({
4128                        let this = cx.view().clone();
4129                        canvas(
4130                            move |bounds, cx| this.update(cx, |this, _cx| this.bounds = bounds),
4131                            |_, _, _| {},
4132                        )
4133                        .absolute()
4134                        .size_full()
4135                    })
4136                    .when(self.zoomed.is_none(), |this| {
4137                        this.on_drag_move(cx.listener(
4138                            |workspace, e: &DragMoveEvent<DraggedDock>, cx| match e.drag(cx).0 {
4139                                DockPosition::Left => {
4140                                    let size = workspace.bounds.left() + e.event.position.x;
4141                                    workspace.left_dock.update(cx, |left_dock, cx| {
4142                                        left_dock.resize_active_panel(Some(size), cx);
4143                                    });
4144                                }
4145                                DockPosition::Right => {
4146                                    let size = workspace.bounds.right() - e.event.position.x;
4147                                    workspace.right_dock.update(cx, |right_dock, cx| {
4148                                        right_dock.resize_active_panel(Some(size), cx);
4149                                    });
4150                                }
4151                                DockPosition::Bottom => {
4152                                    let size = workspace.bounds.bottom() - e.event.position.y;
4153                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
4154                                        bottom_dock.resize_active_panel(Some(size), cx);
4155                                    });
4156                                }
4157                            },
4158                        ))
4159                    })
4160                    .child(
4161                        div()
4162                            .flex()
4163                            .flex_row()
4164                            .h_full()
4165                            // Left Dock
4166                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
4167                                || {
4168                                    div()
4169                                        .flex()
4170                                        .flex_none()
4171                                        .overflow_hidden()
4172                                        .child(self.left_dock.clone())
4173                                },
4174                            ))
4175                            // Panes
4176                            .child(
4177                                div()
4178                                    .flex()
4179                                    .flex_col()
4180                                    .flex_1()
4181                                    .overflow_hidden()
4182                                    .child(
4183                                        h_flex()
4184                                            .flex_1()
4185                                            .when_some(paddings.0, |this, p| {
4186                                                this.child(p.border_r_1())
4187                                            })
4188                                            .child(self.center.render(
4189                                                &self.project,
4190                                                &self.follower_states,
4191                                                self.active_call(),
4192                                                &self.active_pane,
4193                                                self.zoomed.as_ref(),
4194                                                &self.app_state,
4195                                                cx,
4196                                            ))
4197                                            .when_some(paddings.1, |this, p| {
4198                                                this.child(p.border_l_1())
4199                                            }),
4200                                    )
4201                                    .children(
4202                                        self.zoomed_position
4203                                            .ne(&Some(DockPosition::Bottom))
4204                                            .then(|| self.bottom_dock.clone()),
4205                                    ),
4206                            )
4207                            // Right Dock
4208                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
4209                                || {
4210                                    div()
4211                                        .flex()
4212                                        .flex_none()
4213                                        .overflow_hidden()
4214                                        .child(self.right_dock.clone())
4215                                },
4216                            )),
4217                    )
4218                    .children(self.zoomed.as_ref().and_then(|view| {
4219                        let zoomed_view = view.upgrade()?;
4220                        let div = div()
4221                            .occlude()
4222                            .absolute()
4223                            .overflow_hidden()
4224                            .border_color(colors.border)
4225                            .bg(colors.background)
4226                            .child(zoomed_view)
4227                            .inset_0()
4228                            .shadow_lg();
4229
4230                        Some(match self.zoomed_position {
4231                            Some(DockPosition::Left) => div.right_2().border_r(),
4232                            Some(DockPosition::Right) => div.left_2().border_l(),
4233                            Some(DockPosition::Bottom) => div.top_2().border_t(),
4234                            None => div.top_2().bottom_2().left_2().right_2().border(),
4235                        })
4236                    }))
4237                    .child(self.modal_layer.clone())
4238                    .children(self.render_notifications(cx)),
4239            )
4240            .child(self.status_bar.clone())
4241            .children(if self.project.read(cx).is_disconnected() {
4242                Some(DisconnectedOverlay)
4243            } else {
4244                None
4245            })
4246    }
4247}
4248
4249impl WorkspaceStore {
4250    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
4251        Self {
4252            workspaces: Default::default(),
4253            _subscriptions: vec![
4254                client.add_request_handler(cx.weak_model(), Self::handle_follow),
4255                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
4256            ],
4257            client,
4258        }
4259    }
4260
4261    pub fn update_followers(
4262        &self,
4263        project_id: Option<u64>,
4264        update: proto::update_followers::Variant,
4265        cx: &AppContext,
4266    ) -> Option<()> {
4267        let active_call = ActiveCall::try_global(cx)?;
4268        let room_id = active_call.read(cx).room()?.read(cx).id();
4269        self.client
4270            .send(proto::UpdateFollowers {
4271                room_id,
4272                project_id,
4273                variant: Some(update),
4274            })
4275            .log_err()
4276    }
4277
4278    pub async fn handle_follow(
4279        this: Model<Self>,
4280        envelope: TypedEnvelope<proto::Follow>,
4281        _: Arc<Client>,
4282        mut cx: AsyncAppContext,
4283    ) -> Result<proto::FollowResponse> {
4284        this.update(&mut cx, |this, cx| {
4285            let follower = Follower {
4286                project_id: envelope.payload.project_id,
4287                peer_id: envelope.original_sender_id()?,
4288            };
4289
4290            let mut response = proto::FollowResponse::default();
4291            this.workspaces.retain(|workspace| {
4292                workspace
4293                    .update(cx, |workspace, cx| {
4294                        let handler_response = workspace.handle_follow(follower.project_id, cx);
4295                        if response.views.is_empty() {
4296                            response.views = handler_response.views;
4297                        } else {
4298                            response.views.extend_from_slice(&handler_response.views);
4299                        }
4300
4301                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
4302                            if response.active_view_id.is_none()
4303                                || workspace.project.read(cx).remote_id() == follower.project_id
4304                            {
4305                                response.active_view_id = Some(active_view_id);
4306                            }
4307                        }
4308
4309                        if let Some(active_view) = handler_response.active_view.clone() {
4310                            if response.active_view_id.is_none()
4311                                || workspace.project.read(cx).remote_id() == follower.project_id
4312                            {
4313                                response.active_view = Some(active_view)
4314                            }
4315                        }
4316                    })
4317                    .is_ok()
4318            });
4319
4320            Ok(response)
4321        })?
4322    }
4323
4324    async fn handle_update_followers(
4325        this: Model<Self>,
4326        envelope: TypedEnvelope<proto::UpdateFollowers>,
4327        _: Arc<Client>,
4328        mut cx: AsyncAppContext,
4329    ) -> Result<()> {
4330        let leader_id = envelope.original_sender_id()?;
4331        let update = envelope.payload;
4332
4333        this.update(&mut cx, |this, cx| {
4334            this.workspaces.retain(|workspace| {
4335                workspace
4336                    .update(cx, |workspace, cx| {
4337                        let project_id = workspace.project.read(cx).remote_id();
4338                        if update.project_id != project_id && update.project_id.is_some() {
4339                            return;
4340                        }
4341                        workspace.handle_update_followers(leader_id, update.clone(), cx);
4342                    })
4343                    .is_ok()
4344            });
4345            Ok(())
4346        })?
4347    }
4348}
4349
4350impl ViewId {
4351    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4352        Ok(Self {
4353            creator: message
4354                .creator
4355                .ok_or_else(|| anyhow!("creator is missing"))?,
4356            id: message.id,
4357        })
4358    }
4359
4360    pub(crate) fn to_proto(&self) -> proto::ViewId {
4361        proto::ViewId {
4362            creator: Some(self.creator),
4363            id: self.id,
4364        }
4365    }
4366}
4367
4368pub trait WorkspaceHandle {
4369    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4370}
4371
4372impl WorkspaceHandle for View<Workspace> {
4373    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4374        self.read(cx)
4375            .worktrees(cx)
4376            .flat_map(|worktree| {
4377                let worktree_id = worktree.read(cx).id();
4378                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4379                    worktree_id,
4380                    path: f.path.clone(),
4381                })
4382            })
4383            .collect::<Vec<_>>()
4384    }
4385}
4386
4387impl std::fmt::Debug for OpenPaths {
4388    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4389        f.debug_struct("OpenPaths")
4390            .field("paths", &self.paths)
4391            .finish()
4392    }
4393}
4394
4395pub fn activate_workspace_for_project(
4396    cx: &mut AppContext,
4397    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4398) -> Option<WindowHandle<Workspace>> {
4399    for window in cx.windows() {
4400        let Some(workspace) = window.downcast::<Workspace>() else {
4401            continue;
4402        };
4403
4404        let predicate = workspace
4405            .update(cx, |workspace, cx| {
4406                let project = workspace.project.read(cx);
4407                if predicate(project, cx) {
4408                    cx.activate_window();
4409                    true
4410                } else {
4411                    false
4412                }
4413            })
4414            .log_err()
4415            .unwrap_or(false);
4416
4417        if predicate {
4418            return Some(workspace);
4419        }
4420    }
4421
4422    None
4423}
4424
4425pub async fn last_opened_workspace_paths() -> Option<LocalPaths> {
4426    DB.last_workspace().await.log_err().flatten()
4427}
4428
4429actions!(collab, [OpenChannelNotes]);
4430actions!(zed, [OpenLog]);
4431
4432async fn join_channel_internal(
4433    channel_id: ChannelId,
4434    app_state: &Arc<AppState>,
4435    requesting_window: Option<WindowHandle<Workspace>>,
4436    active_call: &Model<ActiveCall>,
4437    cx: &mut AsyncAppContext,
4438) -> Result<bool> {
4439    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4440        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4441            return (false, None);
4442        };
4443
4444        let already_in_channel = room.channel_id() == Some(channel_id);
4445        let should_prompt = room.is_sharing_project()
4446            && room.remote_participants().len() > 0
4447            && !already_in_channel;
4448        let open_room = if already_in_channel {
4449            active_call.room().cloned()
4450        } else {
4451            None
4452        };
4453        (should_prompt, open_room)
4454    })?;
4455
4456    if let Some(room) = open_room {
4457        let task = room.update(cx, |room, cx| {
4458            if let Some((project, host)) = room.most_active_project(cx) {
4459                return Some(join_in_room_project(project, host, app_state.clone(), cx));
4460            }
4461
4462            None
4463        })?;
4464        if let Some(task) = task {
4465            task.await?;
4466        }
4467        return anyhow::Ok(true);
4468    }
4469
4470    if should_prompt {
4471        if let Some(workspace) = requesting_window {
4472            let answer = workspace
4473                .update(cx, |_, cx| {
4474                    cx.prompt(
4475                        PromptLevel::Warning,
4476                        "Do you want to switch channels?",
4477                        Some("Leaving this call will unshare your current project."),
4478                        &["Yes, Join Channel", "Cancel"],
4479                    )
4480                })?
4481                .await;
4482
4483            if answer == Ok(1) {
4484                return Ok(false);
4485            }
4486        } else {
4487            return Ok(false); // unreachable!() hopefully
4488        }
4489    }
4490
4491    let client = cx.update(|cx| active_call.read(cx).client())?;
4492
4493    let mut client_status = client.status();
4494
4495    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4496    'outer: loop {
4497        let Some(status) = client_status.recv().await else {
4498            return Err(anyhow!("error connecting"));
4499        };
4500
4501        match status {
4502            Status::Connecting
4503            | Status::Authenticating
4504            | Status::Reconnecting
4505            | Status::Reauthenticating => continue,
4506            Status::Connected { .. } => break 'outer,
4507            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
4508            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
4509            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4510                return Err(ErrorCode::Disconnected.into());
4511            }
4512        }
4513    }
4514
4515    let room = active_call
4516        .update(cx, |active_call, cx| {
4517            active_call.join_channel(channel_id, cx)
4518        })?
4519        .await?;
4520
4521    let Some(room) = room else {
4522        return anyhow::Ok(true);
4523    };
4524
4525    room.update(cx, |room, _| room.room_update_completed())?
4526        .await;
4527
4528    let task = room.update(cx, |room, cx| {
4529        if let Some((project, host)) = room.most_active_project(cx) {
4530            return Some(join_in_room_project(project, host, app_state.clone(), cx));
4531        }
4532        // if you are the first to join a channel, share your project
4533        if room.remote_participants().len() == 0 && !room.local_participant_is_guest() {
4534            if let Some(workspace) = requesting_window {
4535                let project = workspace.update(cx, |workspace, cx| {
4536                    if !CallSettings::get_global(cx).share_on_join {
4537                        return None;
4538                    }
4539                    let project = workspace.project.read(cx);
4540                    if (project.is_local() || project.remote_project_id().is_some())
4541                        && project.visible_worktrees(cx).any(|tree| {
4542                            tree.read(cx)
4543                                .root_entry()
4544                                .map_or(false, |entry| entry.is_dir())
4545                        })
4546                    {
4547                        Some(workspace.project.clone())
4548                    } else {
4549                        None
4550                    }
4551                });
4552                if let Ok(Some(project)) = project {
4553                    return Some(cx.spawn(|room, mut cx| async move {
4554                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
4555                            .await?;
4556                        Ok(())
4557                    }));
4558                }
4559            }
4560        }
4561
4562        None
4563    })?;
4564    if let Some(task) = task {
4565        task.await?;
4566        return anyhow::Ok(true);
4567    }
4568    anyhow::Ok(false)
4569}
4570
4571pub fn join_channel(
4572    channel_id: ChannelId,
4573    app_state: Arc<AppState>,
4574    requesting_window: Option<WindowHandle<Workspace>>,
4575    cx: &mut AppContext,
4576) -> Task<Result<()>> {
4577    let active_call = ActiveCall::global(cx);
4578    cx.spawn(|mut cx| async move {
4579        let result = join_channel_internal(
4580            channel_id,
4581            &app_state,
4582            requesting_window,
4583            &active_call,
4584            &mut cx,
4585        )
4586            .await;
4587
4588        // join channel succeeded, and opened a window
4589        if matches!(result, Ok(true)) {
4590            return anyhow::Ok(());
4591        }
4592
4593        // find an existing workspace to focus and show call controls
4594        let mut active_window =
4595            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4596        if active_window.is_none() {
4597            // no open workspaces, make one to show the error in (blergh)
4598            let (window_handle, _) = cx
4599                .update(|cx| {
4600                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4601                })?
4602                .await?;
4603
4604            if result.is_ok() {
4605                cx.update(|cx| {
4606                    cx.dispatch_action(&OpenChannelNotes);
4607                }).log_err();
4608            }
4609
4610            active_window = Some(window_handle);
4611        }
4612
4613        if let Err(err) = result {
4614            log::error!("failed to join channel: {}", err);
4615            if let Some(active_window) = active_window {
4616                active_window
4617                    .update(&mut cx, |_, cx| {
4618                        let detail: SharedString = match err.error_code() {
4619                            ErrorCode::SignedOut => {
4620                                "Please sign in to continue.".into()
4621                            }
4622                            ErrorCode::UpgradeRequired => {
4623                                "Your are running an unsupported version of Zed. Please update to continue.".into()
4624                            }
4625                            ErrorCode::NoSuchChannel => {
4626                                "No matching channel was found. Please check the link and try again.".into()
4627                            }
4628                            ErrorCode::Forbidden => {
4629                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
4630                            }
4631                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
4632                            _ => format!("{}\n\nPlease try again.", err).into(),
4633                        };
4634                        cx.prompt(
4635                            PromptLevel::Critical,
4636                            "Failed to join channel",
4637                            Some(&detail),
4638                            &["Ok"],
4639                        )
4640                    })?
4641                    .await
4642                    .ok();
4643            }
4644        }
4645
4646        // return ok, we showed the error to the user.
4647        return anyhow::Ok(());
4648    })
4649}
4650
4651pub async fn get_any_active_workspace(
4652    app_state: Arc<AppState>,
4653    mut cx: AsyncAppContext,
4654) -> anyhow::Result<WindowHandle<Workspace>> {
4655    // find an existing workspace to focus and show call controls
4656    let active_window = activate_any_workspace_window(&mut cx);
4657    if active_window.is_none() {
4658        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4659            .await?;
4660    }
4661    activate_any_workspace_window(&mut cx).context("could not open zed")
4662}
4663
4664fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4665    cx.update(|cx| {
4666        if let Some(workspace_window) = cx
4667            .active_window()
4668            .and_then(|window| window.downcast::<Workspace>())
4669        {
4670            return Some(workspace_window);
4671        }
4672
4673        for window in cx.windows() {
4674            if let Some(workspace_window) = window.downcast::<Workspace>() {
4675                workspace_window
4676                    .update(cx, |_, cx| cx.activate_window())
4677                    .ok();
4678                return Some(workspace_window);
4679            }
4680        }
4681        None
4682    })
4683    .ok()
4684    .flatten()
4685}
4686
4687fn local_workspace_windows(cx: &AppContext) -> Vec<WindowHandle<Workspace>> {
4688    cx.windows()
4689        .into_iter()
4690        .filter_map(|window| window.downcast::<Workspace>())
4691        .filter(|workspace| {
4692            workspace
4693                .read(cx)
4694                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
4695        })
4696        .collect()
4697}
4698
4699#[derive(Default)]
4700pub struct OpenOptions {
4701    pub open_new_workspace: Option<bool>,
4702    pub replace_window: Option<WindowHandle<Workspace>>,
4703}
4704
4705#[allow(clippy::type_complexity)]
4706pub fn open_paths(
4707    abs_paths: &[PathBuf],
4708    app_state: Arc<AppState>,
4709    open_options: OpenOptions,
4710    cx: &mut AppContext,
4711) -> Task<
4712    anyhow::Result<(
4713        WindowHandle<Workspace>,
4714        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4715    )>,
4716> {
4717    let abs_paths = abs_paths.to_vec();
4718    let mut existing = None;
4719    let mut best_match = None;
4720    let mut open_visible = OpenVisible::All;
4721
4722    if open_options.open_new_workspace != Some(true) {
4723        for window in local_workspace_windows(cx) {
4724            if let Ok(workspace) = window.read(cx) {
4725                let m = workspace
4726                    .project
4727                    .read(cx)
4728                    .visibility_for_paths(&abs_paths, cx);
4729                if m > best_match {
4730                    existing = Some(window);
4731                    best_match = m;
4732                } else if best_match.is_none() && open_options.open_new_workspace == Some(false) {
4733                    existing = Some(window)
4734                }
4735            }
4736        }
4737    }
4738
4739    cx.spawn(move |mut cx| async move {
4740        if open_options.open_new_workspace.is_none() && existing.is_none() {
4741            let all_files = abs_paths.iter().map(|path| app_state.fs.metadata(path));
4742            if futures::future::join_all(all_files)
4743                .await
4744                .into_iter()
4745                .filter_map(|result| result.ok().flatten())
4746                .all(|file| !file.is_dir)
4747            {
4748                cx.update(|cx| {
4749                    for window in local_workspace_windows(cx) {
4750                        if let Ok(workspace) = window.read(cx) {
4751                            let project = workspace.project().read(cx);
4752                            if project.is_remote() {
4753                                continue;
4754                            }
4755                            existing = Some(window);
4756                            open_visible = OpenVisible::None;
4757                            break;
4758                        }
4759                    }
4760                })?;
4761            }
4762        }
4763
4764        if let Some(existing) = existing {
4765            Ok((
4766                existing,
4767                existing
4768                    .update(&mut cx, |workspace, cx| {
4769                        cx.activate_window();
4770                        workspace.open_paths(abs_paths, open_visible, None, cx)
4771                    })?
4772                    .await,
4773            ))
4774        } else {
4775            cx.update(move |cx| {
4776                Workspace::new_local(
4777                    abs_paths,
4778                    app_state.clone(),
4779                    open_options.replace_window,
4780                    cx,
4781                )
4782            })?
4783            .await
4784        }
4785    })
4786}
4787
4788pub fn open_new(
4789    app_state: Arc<AppState>,
4790    cx: &mut AppContext,
4791    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4792) -> Task<()> {
4793    let task = Workspace::new_local(Vec::new(), app_state, None, cx);
4794    cx.spawn(|mut cx| async move {
4795        if let Some((workspace, opened_paths)) = task.await.log_err() {
4796            workspace
4797                .update(&mut cx, |workspace, cx| {
4798                    if opened_paths.is_empty() {
4799                        init(workspace, cx)
4800                    }
4801                })
4802                .log_err();
4803        }
4804    })
4805}
4806
4807pub fn create_and_open_local_file(
4808    path: &'static Path,
4809    cx: &mut ViewContext<Workspace>,
4810    default_content: impl 'static + Send + FnOnce() -> Rope,
4811) -> Task<Result<Box<dyn ItemHandle>>> {
4812    cx.spawn(|workspace, mut cx| async move {
4813        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4814        if !fs.is_file(path).await {
4815            fs.create_file(path, Default::default()).await?;
4816            fs.save(path, &default_content(), Default::default())
4817                .await?;
4818        }
4819
4820        let mut items = workspace
4821            .update(&mut cx, |workspace, cx| {
4822                workspace.with_local_workspace(cx, |workspace, cx| {
4823                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4824                })
4825            })?
4826            .await?
4827            .await;
4828
4829        let item = items.pop().flatten();
4830        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4831    })
4832}
4833
4834pub fn join_hosted_project(
4835    hosted_project_id: ProjectId,
4836    app_state: Arc<AppState>,
4837    cx: &mut AppContext,
4838) -> Task<Result<()>> {
4839    cx.spawn(|mut cx| async move {
4840        let existing_window = cx.update(|cx| {
4841            cx.windows().into_iter().find_map(|window| {
4842                let workspace = window.downcast::<Workspace>()?;
4843                workspace
4844                    .read(cx)
4845                    .is_ok_and(|workspace| {
4846                        workspace.project().read(cx).hosted_project_id() == Some(hosted_project_id)
4847                    })
4848                    .then(|| workspace)
4849            })
4850        })?;
4851
4852        let workspace = if let Some(existing_window) = existing_window {
4853            existing_window
4854        } else {
4855            let project = Project::hosted(
4856                hosted_project_id,
4857                app_state.user_store.clone(),
4858                app_state.client.clone(),
4859                app_state.languages.clone(),
4860                app_state.fs.clone(),
4861                cx.clone(),
4862            )
4863            .await?;
4864
4865            let window_bounds_override = window_bounds_env_override();
4866            cx.update(|cx| {
4867                let mut options = (app_state.build_window_options)(None, cx);
4868                options.bounds = window_bounds_override;
4869                cx.open_window(options, |cx| {
4870                    cx.new_view(|cx| {
4871                        Workspace::new(Default::default(), project, app_state.clone(), cx)
4872                    })
4873                })
4874            })?
4875        };
4876
4877        workspace.update(&mut cx, |_, cx| {
4878            cx.activate(true);
4879            cx.activate_window();
4880        })?;
4881
4882        Ok(())
4883    })
4884}
4885
4886pub fn join_remote_project(
4887    project_id: ProjectId,
4888    app_state: Arc<AppState>,
4889    window_to_replace: Option<WindowHandle<Workspace>>,
4890    cx: &mut AppContext,
4891) -> Task<Result<WindowHandle<Workspace>>> {
4892    let windows = cx.windows();
4893    cx.spawn(|mut cx| async move {
4894        let existing_workspace = windows.into_iter().find_map(|window| {
4895            window.downcast::<Workspace>().and_then(|window| {
4896                window
4897                    .update(&mut cx, |workspace, cx| {
4898                        if workspace.project().read(cx).remote_id() == Some(project_id.0) {
4899                            Some(window)
4900                        } else {
4901                            None
4902                        }
4903                    })
4904                    .unwrap_or(None)
4905            })
4906        });
4907
4908        let workspace = if let Some(existing_workspace) = existing_workspace {
4909            existing_workspace
4910        } else {
4911            let project = Project::remote(
4912                project_id.0,
4913                app_state.client.clone(),
4914                app_state.user_store.clone(),
4915                app_state.languages.clone(),
4916                app_state.fs.clone(),
4917                cx.clone(),
4918            )
4919            .await?;
4920
4921            if let Some(window_to_replace) = window_to_replace {
4922                cx.update_window(window_to_replace.into(), |_, cx| {
4923                    cx.replace_root_view(|cx| {
4924                        Workspace::new(Default::default(), project, app_state.clone(), cx)
4925                    });
4926                })?;
4927                window_to_replace
4928            } else {
4929                let window_bounds_override = window_bounds_env_override();
4930                cx.update(|cx| {
4931                    let mut options = (app_state.build_window_options)(None, cx);
4932                    options.bounds = window_bounds_override;
4933                    cx.open_window(options, |cx| {
4934                        cx.new_view(|cx| {
4935                            Workspace::new(Default::default(), project, app_state.clone(), cx)
4936                        })
4937                    })
4938                })?
4939            }
4940        };
4941
4942        workspace.update(&mut cx, |_, cx| {
4943            cx.activate(true);
4944            cx.activate_window();
4945        })?;
4946
4947        anyhow::Ok(workspace)
4948    })
4949}
4950
4951pub fn join_in_room_project(
4952    project_id: u64,
4953    follow_user_id: u64,
4954    app_state: Arc<AppState>,
4955    cx: &mut AppContext,
4956) -> Task<Result<()>> {
4957    let windows = cx.windows();
4958    cx.spawn(|mut cx| async move {
4959        let existing_workspace = windows.into_iter().find_map(|window| {
4960            window.downcast::<Workspace>().and_then(|window| {
4961                window
4962                    .update(&mut cx, |workspace, cx| {
4963                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4964                            Some(window)
4965                        } else {
4966                            None
4967                        }
4968                    })
4969                    .unwrap_or(None)
4970            })
4971        });
4972
4973        let workspace = if let Some(existing_workspace) = existing_workspace {
4974            existing_workspace
4975        } else {
4976            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4977            let room = active_call
4978                .read_with(&cx, |call, _| call.room().cloned())?
4979                .ok_or_else(|| anyhow!("not in a call"))?;
4980            let project = room
4981                .update(&mut cx, |room, cx| {
4982                    room.join_project(
4983                        project_id,
4984                        app_state.languages.clone(),
4985                        app_state.fs.clone(),
4986                        cx,
4987                    )
4988                })?
4989                .await?;
4990
4991            let window_bounds_override = window_bounds_env_override();
4992            cx.update(|cx| {
4993                let mut options = (app_state.build_window_options)(None, cx);
4994                options.bounds = window_bounds_override;
4995                cx.open_window(options, |cx| {
4996                    cx.new_view(|cx| {
4997                        Workspace::new(Default::default(), project, app_state.clone(), cx)
4998                    })
4999                })
5000            })?
5001        };
5002
5003        workspace.update(&mut cx, |workspace, cx| {
5004            cx.activate(true);
5005            cx.activate_window();
5006
5007            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
5008                let follow_peer_id = room
5009                    .read(cx)
5010                    .remote_participants()
5011                    .iter()
5012                    .find(|(_, participant)| participant.user.id == follow_user_id)
5013                    .map(|(_, p)| p.peer_id)
5014                    .or_else(|| {
5015                        // If we couldn't follow the given user, follow the host instead.
5016                        let collaborator = workspace
5017                            .project()
5018                            .read(cx)
5019                            .collaborators()
5020                            .values()
5021                            .find(|collaborator| collaborator.replica_id == 0)?;
5022                        Some(collaborator.peer_id)
5023                    });
5024
5025                if let Some(follow_peer_id) = follow_peer_id {
5026                    workspace.follow(follow_peer_id, cx);
5027                }
5028            }
5029        })?;
5030
5031        anyhow::Ok(())
5032    })
5033}
5034
5035pub fn restart(_: &Restart, cx: &mut AppContext) {
5036    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
5037    let mut workspace_windows = cx
5038        .windows()
5039        .into_iter()
5040        .filter_map(|window| window.downcast::<Workspace>())
5041        .collect::<Vec<_>>();
5042
5043    // If multiple windows have unsaved changes, and need a save prompt,
5044    // prompt in the active window before switching to a different window.
5045    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
5046
5047    let mut prompt = None;
5048    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
5049        prompt = window
5050            .update(cx, |_, cx| {
5051                cx.prompt(
5052                    PromptLevel::Info,
5053                    "Are you sure you want to restart?",
5054                    None,
5055                    &["Restart", "Cancel"],
5056                )
5057            })
5058            .ok();
5059    }
5060
5061    cx.spawn(|mut cx| async move {
5062        if let Some(prompt) = prompt {
5063            let answer = prompt.await?;
5064            if answer != 0 {
5065                return Ok(());
5066            }
5067        }
5068
5069        // If the user cancels any save prompt, then keep the app open.
5070        for window in workspace_windows {
5071            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
5072                workspace.prepare_to_close(true, cx)
5073            }) {
5074                if !should_close.await? {
5075                    return Ok(());
5076                }
5077            }
5078        }
5079
5080        cx.update(|cx| cx.restart())
5081    })
5082    .detach_and_log_err(cx);
5083}
5084
5085fn parse_pixel_position_env_var(value: &str) -> Option<Point<DevicePixels>> {
5086    let mut parts = value.split(',');
5087    let x: usize = parts.next()?.parse().ok()?;
5088    let y: usize = parts.next()?.parse().ok()?;
5089    Some(point((x as i32).into(), (y as i32).into()))
5090}
5091
5092fn parse_pixel_size_env_var(value: &str) -> Option<Size<DevicePixels>> {
5093    let mut parts = value.split(',');
5094    let width: usize = parts.next()?.parse().ok()?;
5095    let height: usize = parts.next()?.parse().ok()?;
5096    Some(size((width as i32).into(), (height as i32).into()))
5097}
5098
5099struct DisconnectedOverlay;
5100
5101impl Element for DisconnectedOverlay {
5102    type RequestLayoutState = AnyElement;
5103    type PrepaintState = ();
5104
5105    fn id(&self) -> Option<ElementId> {
5106        None
5107    }
5108
5109    fn request_layout(
5110        &mut self,
5111        _id: Option<&GlobalElementId>,
5112        cx: &mut WindowContext,
5113    ) -> (LayoutId, Self::RequestLayoutState) {
5114        let mut background = cx.theme().colors().elevated_surface_background;
5115        background.fade_out(0.2);
5116        let mut overlay = div()
5117            .bg(background)
5118            .absolute()
5119            .left_0()
5120            .top(ui::TitleBar::height(cx))
5121            .size_full()
5122            .flex()
5123            .items_center()
5124            .justify_center()
5125            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
5126            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
5127            .child(Label::new(
5128                "Your connection to the remote project has been lost.",
5129            ))
5130            .into_any();
5131        (overlay.request_layout(cx), overlay)
5132    }
5133
5134    fn prepaint(
5135        &mut self,
5136        _id: Option<&GlobalElementId>,
5137        bounds: Bounds<Pixels>,
5138        overlay: &mut Self::RequestLayoutState,
5139        cx: &mut WindowContext,
5140    ) {
5141        cx.insert_hitbox(bounds, true);
5142        overlay.prepaint(cx);
5143    }
5144
5145    fn paint(
5146        &mut self,
5147        _id: Option<&GlobalElementId>,
5148        _: Bounds<Pixels>,
5149        overlay: &mut Self::RequestLayoutState,
5150        _: &mut Self::PrepaintState,
5151        cx: &mut WindowContext,
5152    ) {
5153        overlay.paint(cx)
5154    }
5155}
5156
5157impl IntoElement for DisconnectedOverlay {
5158    type Element = Self;
5159
5160    fn into_element(self) -> Self::Element {
5161        self
5162    }
5163}
5164
5165#[cfg(test)]
5166mod tests {
5167    use std::{cell::RefCell, rc::Rc};
5168
5169    use super::*;
5170    use crate::{
5171        dock::{test::TestPanel, PanelEvent},
5172        item::{
5173            test::{TestItem, TestProjectItem},
5174            ItemEvent,
5175        },
5176    };
5177    use fs::FakeFs;
5178    use gpui::{
5179        px, BorrowAppContext, DismissEvent, Empty, EventEmitter, FocusHandle, FocusableView,
5180        Render, TestAppContext, VisualTestContext,
5181    };
5182    use project::{Project, ProjectEntryId};
5183    use serde_json::json;
5184    use settings::SettingsStore;
5185
5186    #[gpui::test]
5187    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
5188        init_test(cx);
5189
5190        let fs = FakeFs::new(cx.executor());
5191        let project = Project::test(fs, [], cx).await;
5192        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5193
5194        // Adding an item with no ambiguity renders the tab without detail.
5195        let item1 = cx.new_view(|cx| {
5196            let mut item = TestItem::new(cx);
5197            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
5198            item
5199        });
5200        workspace.update(cx, |workspace, cx| {
5201            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, cx);
5202        });
5203        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
5204
5205        // Adding an item that creates ambiguity increases the level of detail on
5206        // both tabs.
5207        let item2 = cx.new_view(|cx| {
5208            let mut item = TestItem::new(cx);
5209            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5210            item
5211        });
5212        workspace.update(cx, |workspace, cx| {
5213            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5214        });
5215        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5216        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5217
5218        // Adding an item that creates ambiguity increases the level of detail only
5219        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
5220        // we stop at the highest detail available.
5221        let item3 = cx.new_view(|cx| {
5222            let mut item = TestItem::new(cx);
5223            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5224            item
5225        });
5226        workspace.update(cx, |workspace, cx| {
5227            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5228        });
5229        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5230        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
5231        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
5232    }
5233
5234    #[gpui::test]
5235    async fn test_tracking_active_path(cx: &mut TestAppContext) {
5236        init_test(cx);
5237
5238        let fs = FakeFs::new(cx.executor());
5239        fs.insert_tree(
5240            "/root1",
5241            json!({
5242                "one.txt": "",
5243                "two.txt": "",
5244            }),
5245        )
5246        .await;
5247        fs.insert_tree(
5248            "/root2",
5249            json!({
5250                "three.txt": "",
5251            }),
5252        )
5253        .await;
5254
5255        let project = Project::test(fs, ["root1".as_ref()], cx).await;
5256        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5257        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5258        let worktree_id = project.update(cx, |project, cx| {
5259            project.worktrees().next().unwrap().read(cx).id()
5260        });
5261
5262        let item1 = cx.new_view(|cx| {
5263            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
5264        });
5265        let item2 = cx.new_view(|cx| {
5266            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
5267        });
5268
5269        // Add an item to an empty pane
5270        workspace.update(cx, |workspace, cx| {
5271            workspace.add_item_to_active_pane(Box::new(item1), None, cx)
5272        });
5273        project.update(cx, |project, cx| {
5274            assert_eq!(
5275                project.active_entry(),
5276                project
5277                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5278                    .map(|e| e.id)
5279            );
5280        });
5281        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5282
5283        // Add a second item to a non-empty pane
5284        workspace.update(cx, |workspace, cx| {
5285            workspace.add_item_to_active_pane(Box::new(item2), None, cx)
5286        });
5287        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
5288        project.update(cx, |project, cx| {
5289            assert_eq!(
5290                project.active_entry(),
5291                project
5292                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
5293                    .map(|e| e.id)
5294            );
5295        });
5296
5297        // Close the active item
5298        pane.update(cx, |pane, cx| {
5299            pane.close_active_item(&Default::default(), cx).unwrap()
5300        })
5301        .await
5302        .unwrap();
5303        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5304        project.update(cx, |project, cx| {
5305            assert_eq!(
5306                project.active_entry(),
5307                project
5308                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5309                    .map(|e| e.id)
5310            );
5311        });
5312
5313        // Add a project folder
5314        project
5315            .update(cx, |project, cx| {
5316                project.find_or_create_local_worktree("/root2", true, cx)
5317            })
5318            .await
5319            .unwrap();
5320        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
5321
5322        // Remove a project folder
5323        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
5324        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
5325    }
5326
5327    #[gpui::test]
5328    async fn test_close_window(cx: &mut TestAppContext) {
5329        init_test(cx);
5330
5331        let fs = FakeFs::new(cx.executor());
5332        fs.insert_tree("/root", json!({ "one": "" })).await;
5333
5334        let project = Project::test(fs, ["root".as_ref()], cx).await;
5335        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5336
5337        // When there are no dirty items, there's nothing to do.
5338        let item1 = cx.new_view(|cx| TestItem::new(cx));
5339        workspace.update(cx, |w, cx| {
5340            w.add_item_to_active_pane(Box::new(item1.clone()), None, cx)
5341        });
5342        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5343        assert!(task.await.unwrap());
5344
5345        // When there are dirty untitled items, prompt to save each one. If the user
5346        // cancels any prompt, then abort.
5347        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
5348        let item3 = cx.new_view(|cx| {
5349            TestItem::new(cx)
5350                .with_dirty(true)
5351                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5352        });
5353        workspace.update(cx, |w, cx| {
5354            w.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5355            w.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5356        });
5357        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5358        cx.executor().run_until_parked();
5359        cx.simulate_prompt_answer(2); // cancel save all
5360        cx.executor().run_until_parked();
5361        cx.simulate_prompt_answer(2); // cancel save all
5362        cx.executor().run_until_parked();
5363        assert!(!cx.has_pending_prompt());
5364        assert!(!task.await.unwrap());
5365    }
5366
5367    #[gpui::test]
5368    async fn test_close_pane_items(cx: &mut TestAppContext) {
5369        init_test(cx);
5370
5371        let fs = FakeFs::new(cx.executor());
5372
5373        let project = Project::test(fs, None, cx).await;
5374        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5375
5376        let item1 = cx.new_view(|cx| {
5377            TestItem::new(cx)
5378                .with_dirty(true)
5379                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5380        });
5381        let item2 = cx.new_view(|cx| {
5382            TestItem::new(cx)
5383                .with_dirty(true)
5384                .with_conflict(true)
5385                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
5386        });
5387        let item3 = cx.new_view(|cx| {
5388            TestItem::new(cx)
5389                .with_dirty(true)
5390                .with_conflict(true)
5391                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
5392        });
5393        let item4 = cx.new_view(|cx| {
5394            TestItem::new(cx)
5395                .with_dirty(true)
5396                .with_project_items(&[TestProjectItem::new_untitled(cx)])
5397        });
5398        let pane = workspace.update(cx, |workspace, cx| {
5399            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, cx);
5400            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, cx);
5401            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, cx);
5402            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, cx);
5403            workspace.active_pane().clone()
5404        });
5405
5406        let close_items = pane.update(cx, |pane, cx| {
5407            pane.activate_item(1, true, true, cx);
5408            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5409            let item1_id = item1.item_id();
5410            let item3_id = item3.item_id();
5411            let item4_id = item4.item_id();
5412            pane.close_items(cx, SaveIntent::Close, move |id| {
5413                [item1_id, item3_id, item4_id].contains(&id)
5414            })
5415        });
5416        cx.executor().run_until_parked();
5417
5418        assert!(cx.has_pending_prompt());
5419        // Ignore "Save all" prompt
5420        cx.simulate_prompt_answer(2);
5421        cx.executor().run_until_parked();
5422        // There's a prompt to save item 1.
5423        pane.update(cx, |pane, _| {
5424            assert_eq!(pane.items_len(), 4);
5425            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
5426        });
5427        // Confirm saving item 1.
5428        cx.simulate_prompt_answer(0);
5429        cx.executor().run_until_parked();
5430
5431        // Item 1 is saved. There's a prompt to save item 3.
5432        pane.update(cx, |pane, cx| {
5433            assert_eq!(item1.read(cx).save_count, 1);
5434            assert_eq!(item1.read(cx).save_as_count, 0);
5435            assert_eq!(item1.read(cx).reload_count, 0);
5436            assert_eq!(pane.items_len(), 3);
5437            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
5438        });
5439        assert!(cx.has_pending_prompt());
5440
5441        // Cancel saving item 3.
5442        cx.simulate_prompt_answer(1);
5443        cx.executor().run_until_parked();
5444
5445        // Item 3 is reloaded. There's a prompt to save item 4.
5446        pane.update(cx, |pane, cx| {
5447            assert_eq!(item3.read(cx).save_count, 0);
5448            assert_eq!(item3.read(cx).save_as_count, 0);
5449            assert_eq!(item3.read(cx).reload_count, 1);
5450            assert_eq!(pane.items_len(), 2);
5451            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
5452        });
5453        assert!(cx.has_pending_prompt());
5454
5455        // Confirm saving item 4.
5456        cx.simulate_prompt_answer(0);
5457        cx.executor().run_until_parked();
5458
5459        // There's a prompt for a path for item 4.
5460        cx.simulate_new_path_selection(|_| Some(Default::default()));
5461        close_items.await.unwrap();
5462
5463        // The requested items are closed.
5464        pane.update(cx, |pane, cx| {
5465            assert_eq!(item4.read(cx).save_count, 0);
5466            assert_eq!(item4.read(cx).save_as_count, 1);
5467            assert_eq!(item4.read(cx).reload_count, 0);
5468            assert_eq!(pane.items_len(), 1);
5469            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5470        });
5471    }
5472
5473    #[gpui::test]
5474    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
5475        init_test(cx);
5476
5477        let fs = FakeFs::new(cx.executor());
5478        let project = Project::test(fs, [], cx).await;
5479        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5480
5481        // Create several workspace items with single project entries, and two
5482        // workspace items with multiple project entries.
5483        let single_entry_items = (0..=4)
5484            .map(|project_entry_id| {
5485                cx.new_view(|cx| {
5486                    TestItem::new(cx)
5487                        .with_dirty(true)
5488                        .with_project_items(&[TestProjectItem::new(
5489                            project_entry_id,
5490                            &format!("{project_entry_id}.txt"),
5491                            cx,
5492                        )])
5493                })
5494            })
5495            .collect::<Vec<_>>();
5496        let item_2_3 = cx.new_view(|cx| {
5497            TestItem::new(cx)
5498                .with_dirty(true)
5499                .with_singleton(false)
5500                .with_project_items(&[
5501                    single_entry_items[2].read(cx).project_items[0].clone(),
5502                    single_entry_items[3].read(cx).project_items[0].clone(),
5503                ])
5504        });
5505        let item_3_4 = cx.new_view(|cx| {
5506            TestItem::new(cx)
5507                .with_dirty(true)
5508                .with_singleton(false)
5509                .with_project_items(&[
5510                    single_entry_items[3].read(cx).project_items[0].clone(),
5511                    single_entry_items[4].read(cx).project_items[0].clone(),
5512                ])
5513        });
5514
5515        // Create two panes that contain the following project entries:
5516        //   left pane:
5517        //     multi-entry items:   (2, 3)
5518        //     single-entry items:  0, 1, 2, 3, 4
5519        //   right pane:
5520        //     single-entry items:  1
5521        //     multi-entry items:   (3, 4)
5522        let left_pane = workspace.update(cx, |workspace, cx| {
5523            let left_pane = workspace.active_pane().clone();
5524            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, cx);
5525            for item in single_entry_items {
5526                workspace.add_item_to_active_pane(Box::new(item), None, cx);
5527            }
5528            left_pane.update(cx, |pane, cx| {
5529                pane.activate_item(2, true, true, cx);
5530            });
5531
5532            let right_pane = workspace
5533                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
5534                .unwrap();
5535
5536            right_pane.update(cx, |pane, cx| {
5537                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
5538            });
5539
5540            left_pane
5541        });
5542
5543        cx.focus_view(&left_pane);
5544
5545        // When closing all of the items in the left pane, we should be prompted twice:
5546        // once for project entry 0, and once for project entry 2. Project entries 1,
5547        // 3, and 4 are all still open in the other paten. After those two
5548        // prompts, the task should complete.
5549
5550        let close = left_pane.update(cx, |pane, cx| {
5551            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
5552        });
5553        cx.executor().run_until_parked();
5554
5555        // Discard "Save all" prompt
5556        cx.simulate_prompt_answer(2);
5557
5558        cx.executor().run_until_parked();
5559        left_pane.update(cx, |pane, cx| {
5560            assert_eq!(
5561                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5562                &[ProjectEntryId::from_proto(0)]
5563            );
5564        });
5565        cx.simulate_prompt_answer(0);
5566
5567        cx.executor().run_until_parked();
5568        left_pane.update(cx, |pane, cx| {
5569            assert_eq!(
5570                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
5571                &[ProjectEntryId::from_proto(2)]
5572            );
5573        });
5574        cx.simulate_prompt_answer(0);
5575
5576        cx.executor().run_until_parked();
5577        close.await.unwrap();
5578        left_pane.update(cx, |pane, _| {
5579            assert_eq!(pane.items_len(), 0);
5580        });
5581    }
5582
5583    #[gpui::test]
5584    async fn test_autosave(cx: &mut gpui::TestAppContext) {
5585        init_test(cx);
5586
5587        let fs = FakeFs::new(cx.executor());
5588        let project = Project::test(fs, [], cx).await;
5589        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5590        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5591
5592        let item = cx.new_view(|cx| {
5593            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5594        });
5595        let item_id = item.entity_id();
5596        workspace.update(cx, |workspace, cx| {
5597            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
5598        });
5599
5600        // Autosave on window change.
5601        item.update(cx, |item, cx| {
5602            cx.update_global(|settings: &mut SettingsStore, cx| {
5603                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5604                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
5605                })
5606            });
5607            item.is_dirty = true;
5608        });
5609
5610        // Deactivating the window saves the file.
5611        cx.deactivate_window();
5612        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5613
5614        // Re-activating the window doesn't save the file.
5615        cx.update(|cx| cx.activate_window());
5616        cx.executor().run_until_parked();
5617        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
5618
5619        // Autosave on focus change.
5620        item.update(cx, |item, cx| {
5621            cx.focus_self();
5622            cx.update_global(|settings: &mut SettingsStore, cx| {
5623                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5624                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5625                })
5626            });
5627            item.is_dirty = true;
5628        });
5629
5630        // Blurring the item saves the file.
5631        item.update(cx, |_, cx| cx.blur());
5632        cx.executor().run_until_parked();
5633        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
5634
5635        // Deactivating the window still saves the file.
5636        item.update(cx, |item, cx| {
5637            cx.focus_self();
5638            item.is_dirty = true;
5639        });
5640        cx.deactivate_window();
5641        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5642
5643        // Autosave after delay.
5644        item.update(cx, |item, cx| {
5645            cx.update_global(|settings: &mut SettingsStore, cx| {
5646                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5647                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5648                })
5649            });
5650            item.is_dirty = true;
5651            cx.emit(ItemEvent::Edit);
5652        });
5653
5654        // Delay hasn't fully expired, so the file is still dirty and unsaved.
5655        cx.executor().advance_clock(Duration::from_millis(250));
5656        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
5657
5658        // After delay expires, the file is saved.
5659        cx.executor().advance_clock(Duration::from_millis(250));
5660        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
5661
5662        // Autosave on focus change, ensuring closing the tab counts as such.
5663        item.update(cx, |item, cx| {
5664            cx.update_global(|settings: &mut SettingsStore, cx| {
5665                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5666                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
5667                })
5668            });
5669            item.is_dirty = true;
5670        });
5671
5672        pane.update(cx, |pane, cx| {
5673            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5674        })
5675        .await
5676        .unwrap();
5677        assert!(!cx.has_pending_prompt());
5678        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5679
5680        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5681        workspace.update(cx, |workspace, cx| {
5682            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
5683        });
5684        item.update(cx, |item, cx| {
5685            item.project_items[0].update(cx, |item, _| {
5686                item.entry_id = None;
5687            });
5688            item.is_dirty = true;
5689            cx.blur();
5690        });
5691        cx.run_until_parked();
5692        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5693
5694        // Ensure autosave is prevented for deleted files also when closing the buffer.
5695        let _close_items = pane.update(cx, |pane, cx| {
5696            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5697        });
5698        cx.run_until_parked();
5699        assert!(cx.has_pending_prompt());
5700        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
5701    }
5702
5703    #[gpui::test]
5704    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5705        init_test(cx);
5706
5707        let fs = FakeFs::new(cx.executor());
5708
5709        let project = Project::test(fs, [], cx).await;
5710        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5711
5712        let item = cx.new_view(|cx| {
5713            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5714        });
5715        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5716        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
5717        let toolbar_notify_count = Rc::new(RefCell::new(0));
5718
5719        workspace.update(cx, |workspace, cx| {
5720            workspace.add_item_to_active_pane(Box::new(item.clone()), None, cx);
5721            let toolbar_notification_count = toolbar_notify_count.clone();
5722            cx.observe(&toolbar, move |_, _, _| {
5723                *toolbar_notification_count.borrow_mut() += 1
5724            })
5725            .detach();
5726        });
5727
5728        pane.update(cx, |pane, _| {
5729            assert!(!pane.can_navigate_backward());
5730            assert!(!pane.can_navigate_forward());
5731        });
5732
5733        item.update(cx, |item, cx| {
5734            item.set_state("one".to_string(), cx);
5735        });
5736
5737        // Toolbar must be notified to re-render the navigation buttons
5738        assert_eq!(*toolbar_notify_count.borrow(), 1);
5739
5740        pane.update(cx, |pane, _| {
5741            assert!(pane.can_navigate_backward());
5742            assert!(!pane.can_navigate_forward());
5743        });
5744
5745        workspace
5746            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5747            .await
5748            .unwrap();
5749
5750        assert_eq!(*toolbar_notify_count.borrow(), 2);
5751        pane.update(cx, |pane, _| {
5752            assert!(!pane.can_navigate_backward());
5753            assert!(pane.can_navigate_forward());
5754        });
5755    }
5756
5757    #[gpui::test]
5758    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5759        init_test(cx);
5760        let fs = FakeFs::new(cx.executor());
5761
5762        let project = Project::test(fs, [], cx).await;
5763        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5764
5765        let panel = workspace.update(cx, |workspace, cx| {
5766            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5767            workspace.add_panel(panel.clone(), cx);
5768
5769            workspace
5770                .right_dock()
5771                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5772
5773            panel
5774        });
5775
5776        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5777        pane.update(cx, |pane, cx| {
5778            let item = cx.new_view(|cx| TestItem::new(cx));
5779            pane.add_item(Box::new(item), true, true, None, cx);
5780        });
5781
5782        // Transfer focus from center to panel
5783        workspace.update(cx, |workspace, cx| {
5784            workspace.toggle_panel_focus::<TestPanel>(cx);
5785        });
5786
5787        workspace.update(cx, |workspace, cx| {
5788            assert!(workspace.right_dock().read(cx).is_open());
5789            assert!(!panel.is_zoomed(cx));
5790            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5791        });
5792
5793        // Transfer focus from panel to center
5794        workspace.update(cx, |workspace, cx| {
5795            workspace.toggle_panel_focus::<TestPanel>(cx);
5796        });
5797
5798        workspace.update(cx, |workspace, cx| {
5799            assert!(workspace.right_dock().read(cx).is_open());
5800            assert!(!panel.is_zoomed(cx));
5801            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5802        });
5803
5804        // Close the dock
5805        workspace.update(cx, |workspace, cx| {
5806            workspace.toggle_dock(DockPosition::Right, cx);
5807        });
5808
5809        workspace.update(cx, |workspace, cx| {
5810            assert!(!workspace.right_dock().read(cx).is_open());
5811            assert!(!panel.is_zoomed(cx));
5812            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5813        });
5814
5815        // Open the dock
5816        workspace.update(cx, |workspace, cx| {
5817            workspace.toggle_dock(DockPosition::Right, cx);
5818        });
5819
5820        workspace.update(cx, |workspace, cx| {
5821            assert!(workspace.right_dock().read(cx).is_open());
5822            assert!(!panel.is_zoomed(cx));
5823            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5824        });
5825
5826        // Focus and zoom panel
5827        panel.update(cx, |panel, cx| {
5828            cx.focus_self();
5829            panel.set_zoomed(true, cx)
5830        });
5831
5832        workspace.update(cx, |workspace, cx| {
5833            assert!(workspace.right_dock().read(cx).is_open());
5834            assert!(panel.is_zoomed(cx));
5835            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5836        });
5837
5838        // Transfer focus to the center closes the dock
5839        workspace.update(cx, |workspace, cx| {
5840            workspace.toggle_panel_focus::<TestPanel>(cx);
5841        });
5842
5843        workspace.update(cx, |workspace, cx| {
5844            assert!(!workspace.right_dock().read(cx).is_open());
5845            assert!(panel.is_zoomed(cx));
5846            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5847        });
5848
5849        // Transferring focus back to the panel keeps it zoomed
5850        workspace.update(cx, |workspace, cx| {
5851            workspace.toggle_panel_focus::<TestPanel>(cx);
5852        });
5853
5854        workspace.update(cx, |workspace, cx| {
5855            assert!(workspace.right_dock().read(cx).is_open());
5856            assert!(panel.is_zoomed(cx));
5857            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5858        });
5859
5860        // Close the dock while it is zoomed
5861        workspace.update(cx, |workspace, cx| {
5862            workspace.toggle_dock(DockPosition::Right, cx)
5863        });
5864
5865        workspace.update(cx, |workspace, cx| {
5866            assert!(!workspace.right_dock().read(cx).is_open());
5867            assert!(panel.is_zoomed(cx));
5868            assert!(workspace.zoomed.is_none());
5869            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5870        });
5871
5872        // Opening the dock, when it's zoomed, retains focus
5873        workspace.update(cx, |workspace, cx| {
5874            workspace.toggle_dock(DockPosition::Right, cx)
5875        });
5876
5877        workspace.update(cx, |workspace, cx| {
5878            assert!(workspace.right_dock().read(cx).is_open());
5879            assert!(panel.is_zoomed(cx));
5880            assert!(workspace.zoomed.is_some());
5881            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5882        });
5883
5884        // Unzoom and close the panel, zoom the active pane.
5885        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5886        workspace.update(cx, |workspace, cx| {
5887            workspace.toggle_dock(DockPosition::Right, cx)
5888        });
5889        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5890
5891        // Opening a dock unzooms the pane.
5892        workspace.update(cx, |workspace, cx| {
5893            workspace.toggle_dock(DockPosition::Right, cx)
5894        });
5895        workspace.update(cx, |workspace, cx| {
5896            let pane = pane.read(cx);
5897            assert!(!pane.is_zoomed());
5898            assert!(!pane.focus_handle(cx).is_focused(cx));
5899            assert!(workspace.right_dock().read(cx).is_open());
5900            assert!(workspace.zoomed.is_none());
5901        });
5902    }
5903
5904    struct TestModal(FocusHandle);
5905
5906    impl TestModal {
5907        fn new(cx: &mut ViewContext<Self>) -> Self {
5908            Self(cx.focus_handle())
5909        }
5910    }
5911
5912    impl EventEmitter<DismissEvent> for TestModal {}
5913
5914    impl FocusableView for TestModal {
5915        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5916            self.0.clone()
5917        }
5918    }
5919
5920    impl ModalView for TestModal {}
5921
5922    impl Render for TestModal {
5923        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5924            div().track_focus(&self.0)
5925        }
5926    }
5927
5928    #[gpui::test]
5929    async fn test_panels(cx: &mut gpui::TestAppContext) {
5930        init_test(cx);
5931        let fs = FakeFs::new(cx.executor());
5932
5933        let project = Project::test(fs, [], cx).await;
5934        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5935
5936        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5937            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5938            workspace.add_panel(panel_1.clone(), cx);
5939            workspace
5940                .left_dock()
5941                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5942            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5943            workspace.add_panel(panel_2.clone(), cx);
5944            workspace
5945                .right_dock()
5946                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5947
5948            let left_dock = workspace.left_dock();
5949            assert_eq!(
5950                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5951                panel_1.panel_id()
5952            );
5953            assert_eq!(
5954                left_dock.read(cx).active_panel_size(cx).unwrap(),
5955                panel_1.size(cx)
5956            );
5957
5958            left_dock.update(cx, |left_dock, cx| {
5959                left_dock.resize_active_panel(Some(px(1337.)), cx)
5960            });
5961            assert_eq!(
5962                workspace
5963                    .right_dock()
5964                    .read(cx)
5965                    .visible_panel()
5966                    .unwrap()
5967                    .panel_id(),
5968                panel_2.panel_id(),
5969            );
5970
5971            (panel_1, panel_2)
5972        });
5973
5974        // Move panel_1 to the right
5975        panel_1.update(cx, |panel_1, cx| {
5976            panel_1.set_position(DockPosition::Right, cx)
5977        });
5978
5979        workspace.update(cx, |workspace, cx| {
5980            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5981            // Since it was the only panel on the left, the left dock should now be closed.
5982            assert!(!workspace.left_dock().read(cx).is_open());
5983            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5984            let right_dock = workspace.right_dock();
5985            assert_eq!(
5986                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5987                panel_1.panel_id()
5988            );
5989            assert_eq!(
5990                right_dock.read(cx).active_panel_size(cx).unwrap(),
5991                px(1337.)
5992            );
5993
5994            // Now we move panel_2 to the left
5995            panel_2.set_position(DockPosition::Left, cx);
5996        });
5997
5998        workspace.update(cx, |workspace, cx| {
5999            // Since panel_2 was not visible on the right, we don't open the left dock.
6000            assert!(!workspace.left_dock().read(cx).is_open());
6001            // And the right dock is unaffected in its displaying of panel_1
6002            assert!(workspace.right_dock().read(cx).is_open());
6003            assert_eq!(
6004                workspace
6005                    .right_dock()
6006                    .read(cx)
6007                    .visible_panel()
6008                    .unwrap()
6009                    .panel_id(),
6010                panel_1.panel_id(),
6011            );
6012        });
6013
6014        // Move panel_1 back to the left
6015        panel_1.update(cx, |panel_1, cx| {
6016            panel_1.set_position(DockPosition::Left, cx)
6017        });
6018
6019        workspace.update(cx, |workspace, cx| {
6020            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
6021            let left_dock = workspace.left_dock();
6022            assert!(left_dock.read(cx).is_open());
6023            assert_eq!(
6024                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6025                panel_1.panel_id()
6026            );
6027            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
6028            // And the right dock should be closed as it no longer has any panels.
6029            assert!(!workspace.right_dock().read(cx).is_open());
6030
6031            // Now we move panel_1 to the bottom
6032            panel_1.set_position(DockPosition::Bottom, cx);
6033        });
6034
6035        workspace.update(cx, |workspace, cx| {
6036            // Since panel_1 was visible on the left, we close the left dock.
6037            assert!(!workspace.left_dock().read(cx).is_open());
6038            // The bottom dock is sized based on the panel's default size,
6039            // since the panel orientation changed from vertical to horizontal.
6040            let bottom_dock = workspace.bottom_dock();
6041            assert_eq!(
6042                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
6043                panel_1.size(cx),
6044            );
6045            // Close bottom dock and move panel_1 back to the left.
6046            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
6047            panel_1.set_position(DockPosition::Left, cx);
6048        });
6049
6050        // Emit activated event on panel 1
6051        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
6052
6053        // Now the left dock is open and panel_1 is active and focused.
6054        workspace.update(cx, |workspace, cx| {
6055            let left_dock = workspace.left_dock();
6056            assert!(left_dock.read(cx).is_open());
6057            assert_eq!(
6058                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6059                panel_1.panel_id(),
6060            );
6061            assert!(panel_1.focus_handle(cx).is_focused(cx));
6062        });
6063
6064        // Emit closed event on panel 2, which is not active
6065        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
6066
6067        // Wo don't close the left dock, because panel_2 wasn't the active panel
6068        workspace.update(cx, |workspace, cx| {
6069            let left_dock = workspace.left_dock();
6070            assert!(left_dock.read(cx).is_open());
6071            assert_eq!(
6072                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6073                panel_1.panel_id(),
6074            );
6075        });
6076
6077        // Emitting a ZoomIn event shows the panel as zoomed.
6078        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
6079        workspace.update(cx, |workspace, _| {
6080            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6081            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
6082        });
6083
6084        // Move panel to another dock while it is zoomed
6085        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
6086        workspace.update(cx, |workspace, _| {
6087            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6088
6089            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6090        });
6091
6092        // This is a helper for getting a:
6093        // - valid focus on an element,
6094        // - that isn't a part of the panes and panels system of the Workspace,
6095        // - and doesn't trigger the 'on_focus_lost' API.
6096        let focus_other_view = {
6097            let workspace = workspace.clone();
6098            move |cx: &mut VisualTestContext| {
6099                workspace.update(cx, |workspace, cx| {
6100                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
6101                        workspace.toggle_modal(cx, TestModal::new);
6102                        workspace.toggle_modal(cx, TestModal::new);
6103                    } else {
6104                        workspace.toggle_modal(cx, TestModal::new);
6105                    }
6106                })
6107            }
6108        };
6109
6110        // If focus is transferred to another view that's not a panel or another pane, we still show
6111        // the panel as zoomed.
6112        focus_other_view(cx);
6113        workspace.update(cx, |workspace, _| {
6114            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6115            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6116        });
6117
6118        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
6119        workspace.update(cx, |_, cx| cx.focus_self());
6120        workspace.update(cx, |workspace, _| {
6121            assert_eq!(workspace.zoomed, None);
6122            assert_eq!(workspace.zoomed_position, None);
6123        });
6124
6125        // If focus is transferred again to another view that's not a panel or a pane, we won't
6126        // show the panel as zoomed because it wasn't zoomed before.
6127        focus_other_view(cx);
6128        workspace.update(cx, |workspace, _| {
6129            assert_eq!(workspace.zoomed, None);
6130            assert_eq!(workspace.zoomed_position, None);
6131        });
6132
6133        // When the panel is activated, it is zoomed again.
6134        cx.dispatch_action(ToggleRightDock);
6135        workspace.update(cx, |workspace, _| {
6136            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6137            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6138        });
6139
6140        // Emitting a ZoomOut event unzooms the panel.
6141        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
6142        workspace.update(cx, |workspace, _| {
6143            assert_eq!(workspace.zoomed, None);
6144            assert_eq!(workspace.zoomed_position, None);
6145        });
6146
6147        // Emit closed event on panel 1, which is active
6148        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
6149
6150        // Now the left dock is closed, because panel_1 was the active panel
6151        workspace.update(cx, |workspace, cx| {
6152            let right_dock = workspace.right_dock();
6153            assert!(!right_dock.read(cx).is_open());
6154        });
6155    }
6156
6157    mod register_project_item_tests {
6158        use ui::Context as _;
6159
6160        use super::*;
6161
6162        const TEST_PNG_KIND: &str = "TestPngItemView";
6163        // View
6164        struct TestPngItemView {
6165            focus_handle: FocusHandle,
6166        }
6167        // Model
6168        struct TestPngItem {}
6169
6170        impl project::Item for TestPngItem {
6171            fn try_open(
6172                _project: &Model<Project>,
6173                path: &ProjectPath,
6174                cx: &mut AppContext,
6175            ) -> Option<Task<gpui::Result<Model<Self>>>> {
6176                if path.path.extension().unwrap() == "png" {
6177                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestPngItem {}) }))
6178                } else {
6179                    None
6180                }
6181            }
6182
6183            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
6184                None
6185            }
6186
6187            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
6188                None
6189            }
6190        }
6191
6192        impl Item for TestPngItemView {
6193            type Event = ();
6194
6195            fn serialized_item_kind() -> Option<&'static str> {
6196                Some(TEST_PNG_KIND)
6197            }
6198        }
6199        impl EventEmitter<()> for TestPngItemView {}
6200        impl FocusableView for TestPngItemView {
6201            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6202                self.focus_handle.clone()
6203            }
6204        }
6205
6206        impl Render for TestPngItemView {
6207            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6208                Empty
6209            }
6210        }
6211
6212        impl ProjectItem for TestPngItemView {
6213            type Item = TestPngItem;
6214
6215            fn for_project_item(
6216                _project: Model<Project>,
6217                _item: Model<Self::Item>,
6218                cx: &mut ViewContext<Self>,
6219            ) -> Self
6220            where
6221                Self: Sized,
6222            {
6223                Self {
6224                    focus_handle: cx.focus_handle(),
6225                }
6226            }
6227        }
6228
6229        const TEST_IPYNB_KIND: &str = "TestIpynbItemView";
6230        // View
6231        struct TestIpynbItemView {
6232            focus_handle: FocusHandle,
6233        }
6234        // Model
6235        struct TestIpynbItem {}
6236
6237        impl project::Item for TestIpynbItem {
6238            fn try_open(
6239                _project: &Model<Project>,
6240                path: &ProjectPath,
6241                cx: &mut AppContext,
6242            ) -> Option<Task<gpui::Result<Model<Self>>>> {
6243                if path.path.extension().unwrap() == "ipynb" {
6244                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestIpynbItem {}) }))
6245                } else {
6246                    None
6247                }
6248            }
6249
6250            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
6251                None
6252            }
6253
6254            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
6255                None
6256            }
6257        }
6258
6259        impl Item for TestIpynbItemView {
6260            type Event = ();
6261
6262            fn serialized_item_kind() -> Option<&'static str> {
6263                Some(TEST_IPYNB_KIND)
6264            }
6265        }
6266        impl EventEmitter<()> for TestIpynbItemView {}
6267        impl FocusableView for TestIpynbItemView {
6268            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6269                self.focus_handle.clone()
6270            }
6271        }
6272
6273        impl Render for TestIpynbItemView {
6274            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6275                Empty
6276            }
6277        }
6278
6279        impl ProjectItem for TestIpynbItemView {
6280            type Item = TestIpynbItem;
6281
6282            fn for_project_item(
6283                _project: Model<Project>,
6284                _item: Model<Self::Item>,
6285                cx: &mut ViewContext<Self>,
6286            ) -> Self
6287            where
6288                Self: Sized,
6289            {
6290                Self {
6291                    focus_handle: cx.focus_handle(),
6292                }
6293            }
6294        }
6295
6296        struct TestAlternatePngItemView {
6297            focus_handle: FocusHandle,
6298        }
6299
6300        const TEST_ALTERNATE_PNG_KIND: &str = "TestAlternatePngItemView";
6301        impl Item for TestAlternatePngItemView {
6302            type Event = ();
6303
6304            fn serialized_item_kind() -> Option<&'static str> {
6305                Some(TEST_ALTERNATE_PNG_KIND)
6306            }
6307        }
6308        impl EventEmitter<()> for TestAlternatePngItemView {}
6309        impl FocusableView for TestAlternatePngItemView {
6310            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6311                self.focus_handle.clone()
6312            }
6313        }
6314
6315        impl Render for TestAlternatePngItemView {
6316            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6317                Empty
6318            }
6319        }
6320
6321        impl ProjectItem for TestAlternatePngItemView {
6322            type Item = TestPngItem;
6323
6324            fn for_project_item(
6325                _project: Model<Project>,
6326                _item: Model<Self::Item>,
6327                cx: &mut ViewContext<Self>,
6328            ) -> Self
6329            where
6330                Self: Sized,
6331            {
6332                Self {
6333                    focus_handle: cx.focus_handle(),
6334                }
6335            }
6336        }
6337
6338        #[gpui::test]
6339        async fn test_register_project_item(cx: &mut TestAppContext) {
6340            init_test(cx);
6341
6342            cx.update(|cx| {
6343                register_project_item::<TestPngItemView>(cx);
6344                register_project_item::<TestIpynbItemView>(cx);
6345            });
6346
6347            let fs = FakeFs::new(cx.executor());
6348            fs.insert_tree(
6349                "/root1",
6350                json!({
6351                    "one.png": "BINARYDATAHERE",
6352                    "two.ipynb": "{ totally a notebook }",
6353                    "three.txt": "editing text, sure why not?"
6354                }),
6355            )
6356            .await;
6357
6358            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6359            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6360
6361            let worktree_id = project.update(cx, |project, cx| {
6362                project.worktrees().next().unwrap().read(cx).id()
6363            });
6364
6365            let handle = workspace
6366                .update(cx, |workspace, cx| {
6367                    let project_path = (worktree_id, "one.png");
6368                    workspace.open_path(project_path, None, true, cx)
6369                })
6370                .await
6371                .unwrap();
6372
6373            // Now we can check if the handle we got back errored or not
6374            assert_eq!(handle.serialized_item_kind().unwrap(), TEST_PNG_KIND);
6375
6376            let handle = workspace
6377                .update(cx, |workspace, cx| {
6378                    let project_path = (worktree_id, "two.ipynb");
6379                    workspace.open_path(project_path, None, true, cx)
6380                })
6381                .await
6382                .unwrap();
6383
6384            assert_eq!(handle.serialized_item_kind().unwrap(), TEST_IPYNB_KIND);
6385
6386            let handle = workspace
6387                .update(cx, |workspace, cx| {
6388                    let project_path = (worktree_id, "three.txt");
6389                    workspace.open_path(project_path, None, true, cx)
6390                })
6391                .await;
6392            assert!(handle.is_err());
6393        }
6394
6395        #[gpui::test]
6396        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
6397            init_test(cx);
6398
6399            cx.update(|cx| {
6400                register_project_item::<TestPngItemView>(cx);
6401                register_project_item::<TestAlternatePngItemView>(cx);
6402            });
6403
6404            let fs = FakeFs::new(cx.executor());
6405            fs.insert_tree(
6406                "/root1",
6407                json!({
6408                    "one.png": "BINARYDATAHERE",
6409                    "two.ipynb": "{ totally a notebook }",
6410                    "three.txt": "editing text, sure why not?"
6411                }),
6412            )
6413            .await;
6414
6415            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6416            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6417
6418            let worktree_id = project.update(cx, |project, cx| {
6419                project.worktrees().next().unwrap().read(cx).id()
6420            });
6421
6422            let handle = workspace
6423                .update(cx, |workspace, cx| {
6424                    let project_path = (worktree_id, "one.png");
6425                    workspace.open_path(project_path, None, true, cx)
6426                })
6427                .await
6428                .unwrap();
6429
6430            // This _must_ be the second item registered
6431            assert_eq!(
6432                handle.serialized_item_kind().unwrap(),
6433                TEST_ALTERNATE_PNG_KIND
6434            );
6435
6436            let handle = workspace
6437                .update(cx, |workspace, cx| {
6438                    let project_path = (worktree_id, "three.txt");
6439                    workspace.open_path(project_path, None, true, cx)
6440                })
6441                .await;
6442            assert!(handle.is_err());
6443        }
6444    }
6445
6446    pub fn init_test(cx: &mut TestAppContext) {
6447        cx.update(|cx| {
6448            let settings_store = SettingsStore::test(cx);
6449            cx.set_global(settings_store);
6450            theme::init(theme::LoadThemes::JustBase, cx);
6451            language::init(cx);
6452            crate::init_settings(cx);
6453            Project::init_settings(cx);
6454        });
6455    }
6456}