workspace.rs

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