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