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