workspace.rs

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