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