workspace.rs

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