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