workspace.rs

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