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