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