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