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