workspace.rs

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