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