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