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