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