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