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