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