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