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