workspace.rs

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