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