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        mut 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        let dirty_items = self
1343            .panes
1344            .iter()
1345            .flat_map(|pane| {
1346                pane.read(cx).items().filter_map(|item| {
1347                    if item.is_dirty(cx) {
1348                        Some((pane.downgrade(), item.boxed_clone()))
1349                    } else {
1350                        None
1351                    }
1352                })
1353            })
1354            .collect::<Vec<_>>();
1355
1356        let project = self.project.clone();
1357        cx.spawn(|workspace, mut cx| async move {
1358            // Override save mode and display "Save all files" prompt
1359            if save_behaviour == SaveBehavior::PromptOnWrite && dirty_items.len() > 1 {
1360                let mut answer = workspace.update(&mut cx, |_, cx| {
1361                    let prompt = Pane::file_names_for_prompt(
1362                        &mut dirty_items.iter().map(|(_, handle)| handle),
1363                        dirty_items.len(),
1364                        cx,
1365                    );
1366                    cx.prompt(
1367                        PromptLevel::Warning,
1368                        &prompt,
1369                        &["Save all", "Discard all", "Cancel"],
1370                    )
1371                })?;
1372                match answer.next().await {
1373                    Some(0) => save_behaviour = SaveBehavior::PromptOnConflict,
1374                    Some(1) => save_behaviour = SaveBehavior::DontSave,
1375                    _ => {}
1376                }
1377            }
1378            for (pane, item) in dirty_items {
1379                let (singleton, project_entry_ids) =
1380                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1381                if singleton || !project_entry_ids.is_empty() {
1382                    if let Some(ix) =
1383                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
1384                    {
1385                        if !Pane::save_item(
1386                            project.clone(),
1387                            &pane,
1388                            ix,
1389                            &*item,
1390                            save_behaviour,
1391                            &mut cx,
1392                        )
1393                        .await?
1394                        {
1395                            return Ok(false);
1396                        }
1397                    }
1398                }
1399            }
1400            Ok(true)
1401        })
1402    }
1403
1404    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1405        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1406            files: true,
1407            directories: true,
1408            multiple: true,
1409        });
1410
1411        Some(cx.spawn(|this, mut cx| async move {
1412            if let Some(paths) = paths.recv().await.flatten() {
1413                if let Some(task) = this
1414                    .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1415                    .log_err()
1416                {
1417                    task.await?
1418                }
1419            }
1420            Ok(())
1421        }))
1422    }
1423
1424    pub fn open_workspace_for_paths(
1425        &mut self,
1426        paths: Vec<PathBuf>,
1427        cx: &mut ViewContext<Self>,
1428    ) -> Task<Result<()>> {
1429        let window = cx.window().downcast::<Self>();
1430        let is_remote = self.project.read(cx).is_remote();
1431        let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
1432        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1433        let close_task = if is_remote || has_worktree || has_dirty_items {
1434            None
1435        } else {
1436            Some(self.prepare_to_close(false, cx))
1437        };
1438        let app_state = self.app_state.clone();
1439
1440        cx.spawn(|_, mut cx| async move {
1441            let window_to_replace = if let Some(close_task) = close_task {
1442                if !close_task.await? {
1443                    return Ok(());
1444                }
1445                window
1446            } else {
1447                None
1448            };
1449            cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))
1450                .await?;
1451            Ok(())
1452        })
1453    }
1454
1455    #[allow(clippy::type_complexity)]
1456    pub fn open_paths(
1457        &mut self,
1458        mut abs_paths: Vec<PathBuf>,
1459        visible: bool,
1460        cx: &mut ViewContext<Self>,
1461    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1462        log::info!("open paths {:?}", abs_paths);
1463
1464        let fs = self.app_state.fs.clone();
1465
1466        // Sort the paths to ensure we add worktrees for parents before their children.
1467        abs_paths.sort_unstable();
1468        cx.spawn(|this, mut cx| async move {
1469            let mut tasks = Vec::with_capacity(abs_paths.len());
1470            for abs_path in &abs_paths {
1471                let project_path = match this
1472                    .update(&mut cx, |this, cx| {
1473                        Workspace::project_path_for_path(
1474                            this.project.clone(),
1475                            abs_path,
1476                            visible,
1477                            cx,
1478                        )
1479                    })
1480                    .log_err()
1481                {
1482                    Some(project_path) => project_path.await.log_err(),
1483                    None => None,
1484                };
1485
1486                let this = this.clone();
1487                let task = cx.spawn(|mut cx| {
1488                    let fs = fs.clone();
1489                    let abs_path = abs_path.clone();
1490                    async move {
1491                        let (worktree, project_path) = project_path?;
1492                        if fs.is_file(&abs_path).await {
1493                            Some(
1494                                this.update(&mut cx, |this, cx| {
1495                                    this.open_path(project_path, None, true, cx)
1496                                })
1497                                .log_err()?
1498                                .await,
1499                            )
1500                        } else {
1501                            this.update(&mut cx, |workspace, cx| {
1502                                let worktree = worktree.read(cx);
1503                                let worktree_abs_path = worktree.abs_path();
1504                                let entry_id = if abs_path == worktree_abs_path.as_ref() {
1505                                    worktree.root_entry()
1506                                } else {
1507                                    abs_path
1508                                        .strip_prefix(worktree_abs_path.as_ref())
1509                                        .ok()
1510                                        .and_then(|relative_path| {
1511                                            worktree.entry_for_path(relative_path)
1512                                        })
1513                                }
1514                                .map(|entry| entry.id);
1515                                if let Some(entry_id) = entry_id {
1516                                    workspace.project().update(cx, |_, cx| {
1517                                        cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
1518                                    })
1519                                }
1520                            })
1521                            .log_err()?;
1522                            None
1523                        }
1524                    }
1525                });
1526                tasks.push(task);
1527            }
1528
1529            futures::future::join_all(tasks).await
1530        })
1531    }
1532
1533    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1534        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1535            files: false,
1536            directories: true,
1537            multiple: true,
1538        });
1539        cx.spawn(|this, mut cx| async move {
1540            if let Some(paths) = paths.recv().await.flatten() {
1541                let results = this
1542                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
1543                    .await;
1544                for result in results.into_iter().flatten() {
1545                    result.log_err();
1546                }
1547            }
1548            anyhow::Ok(())
1549        })
1550        .detach_and_log_err(cx);
1551    }
1552
1553    fn project_path_for_path(
1554        project: ModelHandle<Project>,
1555        abs_path: &Path,
1556        visible: bool,
1557        cx: &mut AppContext,
1558    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1559        let entry = project.update(cx, |project, cx| {
1560            project.find_or_create_local_worktree(abs_path, visible, cx)
1561        });
1562        cx.spawn(|cx| async move {
1563            let (worktree, path) = entry.await?;
1564            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1565            Ok((
1566                worktree,
1567                ProjectPath {
1568                    worktree_id,
1569                    path: path.into(),
1570                },
1571            ))
1572        })
1573    }
1574
1575    /// Returns the modal that was toggled closed if it was open.
1576    pub fn toggle_modal<V, F>(
1577        &mut self,
1578        cx: &mut ViewContext<Self>,
1579        add_view: F,
1580    ) -> Option<ViewHandle<V>>
1581    where
1582        V: 'static + Modal,
1583        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1584    {
1585        cx.notify();
1586        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1587        // it. Otherwise, create a new modal and set it as active.
1588        if let Some(already_open_modal) = self
1589            .dismiss_modal(cx)
1590            .and_then(|modal| modal.downcast::<V>())
1591        {
1592            cx.focus_self();
1593            Some(already_open_modal)
1594        } else {
1595            let modal = add_view(self, cx);
1596            cx.subscribe(&modal, |this, _, event, cx| {
1597                if V::dismiss_on_event(event) {
1598                    this.dismiss_modal(cx);
1599                }
1600            })
1601            .detach();
1602            let previously_focused_view_id = cx.focused_view_id();
1603            cx.focus(&modal);
1604            self.modal = Some(ActiveModal {
1605                view: Box::new(modal),
1606                previously_focused_view_id,
1607            });
1608            None
1609        }
1610    }
1611
1612    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1613        self.modal
1614            .as_ref()
1615            .and_then(|modal| modal.view.as_any().clone().downcast::<V>())
1616    }
1617
1618    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) -> Option<AnyViewHandle> {
1619        if let Some(modal) = self.modal.take() {
1620            if let Some(previously_focused_view_id) = modal.previously_focused_view_id {
1621                if modal.view.has_focus(cx) {
1622                    cx.window_context().focus(Some(previously_focused_view_id));
1623                }
1624            }
1625            cx.notify();
1626            Some(modal.view.as_any().clone())
1627        } else {
1628            None
1629        }
1630    }
1631
1632    pub fn items<'a>(
1633        &'a self,
1634        cx: &'a AppContext,
1635    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1636        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1637    }
1638
1639    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1640        self.items_of_type(cx).max_by_key(|item| item.id())
1641    }
1642
1643    pub fn items_of_type<'a, T: Item>(
1644        &'a self,
1645        cx: &'a AppContext,
1646    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1647        self.panes
1648            .iter()
1649            .flat_map(|pane| pane.read(cx).items_of_type())
1650    }
1651
1652    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1653        self.active_pane().read(cx).active_item()
1654    }
1655
1656    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1657        self.active_item(cx).and_then(|item| item.project_path(cx))
1658    }
1659
1660    pub fn save_active_item(
1661        &mut self,
1662        force_name_change: bool,
1663        cx: &mut ViewContext<Self>,
1664    ) -> Task<Result<()>> {
1665        let project = self.project.clone();
1666        if let Some(item) = self.active_item(cx) {
1667            if !force_name_change && item.can_save(cx) {
1668                if item.has_conflict(cx) {
1669                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1670
1671                    let mut answer = cx.prompt(
1672                        PromptLevel::Warning,
1673                        CONFLICT_MESSAGE,
1674                        &["Overwrite", "Cancel"],
1675                    );
1676                    cx.spawn(|this, mut cx| async move {
1677                        let answer = answer.recv().await;
1678                        if answer == Some(0) {
1679                            this.update(&mut cx, |this, cx| item.save(this.project.clone(), cx))?
1680                                .await?;
1681                        }
1682                        Ok(())
1683                    })
1684                } else {
1685                    item.save(self.project.clone(), cx)
1686                }
1687            } else if item.is_singleton(cx) {
1688                let worktree = self.worktrees(cx).next();
1689                let start_abs_path = worktree
1690                    .and_then(|w| w.read(cx).as_local())
1691                    .map_or(Path::new(""), |w| w.abs_path())
1692                    .to_path_buf();
1693                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1694                cx.spawn(|this, mut cx| async move {
1695                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1696                        this.update(&mut cx, |_, cx| item.save_as(project, abs_path, cx))?
1697                            .await?;
1698                    }
1699                    Ok(())
1700                })
1701            } else {
1702                Task::ready(Ok(()))
1703            }
1704        } else {
1705            Task::ready(Ok(()))
1706        }
1707    }
1708
1709    pub fn close_inactive_items_and_panes(
1710        &mut self,
1711        _: &CloseInactiveTabsAndPanes,
1712        cx: &mut ViewContext<Self>,
1713    ) -> Option<Task<Result<()>>> {
1714        let current_pane = self.active_pane();
1715
1716        let mut tasks = Vec::new();
1717
1718        if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1719            pane.close_inactive_items(&CloseInactiveItems, cx)
1720        }) {
1721            tasks.push(current_pane_close);
1722        };
1723
1724        for pane in self.panes() {
1725            if pane.id() == current_pane.id() {
1726                continue;
1727            }
1728
1729            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1730                pane.close_all_items(&CloseAllItems, cx)
1731            }) {
1732                tasks.push(close_pane_items)
1733            }
1734        }
1735
1736        if tasks.is_empty() {
1737            None
1738        } else {
1739            Some(cx.spawn(|_, _| async move {
1740                for task in tasks {
1741                    task.await?
1742                }
1743                Ok(())
1744            }))
1745        }
1746    }
1747
1748    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1749        let dock = match dock_side {
1750            DockPosition::Left => &self.left_dock,
1751            DockPosition::Bottom => &self.bottom_dock,
1752            DockPosition::Right => &self.right_dock,
1753        };
1754        let mut focus_center = false;
1755        let mut reveal_dock = false;
1756        dock.update(cx, |dock, cx| {
1757            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1758            let was_visible = dock.is_open() && !other_is_zoomed;
1759            dock.set_open(!was_visible, cx);
1760
1761            if let Some(active_panel) = dock.active_panel() {
1762                if was_visible {
1763                    if active_panel.has_focus(cx) {
1764                        focus_center = true;
1765                    }
1766                } else {
1767                    cx.focus(active_panel.as_any());
1768                    reveal_dock = true;
1769                }
1770            }
1771        });
1772
1773        if reveal_dock {
1774            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1775        }
1776
1777        if focus_center {
1778            cx.focus_self();
1779        }
1780
1781        cx.notify();
1782        self.serialize_workspace(cx);
1783    }
1784
1785    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1786        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1787
1788        for dock in docks {
1789            dock.update(cx, |dock, cx| {
1790                dock.set_open(false, cx);
1791            });
1792        }
1793
1794        cx.focus_self();
1795        cx.notify();
1796        self.serialize_workspace(cx);
1797    }
1798
1799    /// Transfer focus to the panel of the given type.
1800    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
1801        self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
1802            .as_any()
1803            .clone()
1804            .downcast()
1805    }
1806
1807    /// Focus the panel of the given type if it isn't already focused. If it is
1808    /// already focused, then transfer focus back to the workspace center.
1809    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1810        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
1811    }
1812
1813    /// Focus or unfocus the given panel type, depending on the given callback.
1814    fn focus_or_unfocus_panel<T: Panel>(
1815        &mut self,
1816        cx: &mut ViewContext<Self>,
1817        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1818    ) -> Option<Rc<dyn PanelHandle>> {
1819        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1820            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1821                let mut focus_center = false;
1822                let mut reveal_dock = false;
1823                let panel = dock.update(cx, |dock, cx| {
1824                    dock.activate_panel(panel_index, cx);
1825
1826                    let panel = dock.active_panel().cloned();
1827                    if let Some(panel) = panel.as_ref() {
1828                        if should_focus(&**panel, cx) {
1829                            dock.set_open(true, cx);
1830                            cx.focus(panel.as_any());
1831                            reveal_dock = true;
1832                        } else {
1833                            // if panel.is_zoomed(cx) {
1834                            //     dock.set_open(false, cx);
1835                            // }
1836                            focus_center = true;
1837                        }
1838                    }
1839                    panel
1840                });
1841
1842                if focus_center {
1843                    cx.focus_self();
1844                }
1845
1846                self.serialize_workspace(cx);
1847                cx.notify();
1848                return panel;
1849            }
1850        }
1851        None
1852    }
1853
1854    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<ViewHandle<T>> {
1855        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1856            let dock = dock.read(cx);
1857            if let Some(panel) = dock.panel::<T>() {
1858                return Some(panel);
1859            }
1860        }
1861        None
1862    }
1863
1864    fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
1865        for pane in &self.panes {
1866            pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1867        }
1868
1869        self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1870        self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1871        self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1872        self.zoomed = None;
1873        self.zoomed_position = None;
1874
1875        cx.notify();
1876    }
1877
1878    #[cfg(any(test, feature = "test-support"))]
1879    pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
1880        self.zoomed.and_then(|view| view.upgrade(cx))
1881    }
1882
1883    fn dismiss_zoomed_items_to_reveal(
1884        &mut self,
1885        dock_to_reveal: Option<DockPosition>,
1886        cx: &mut ViewContext<Self>,
1887    ) {
1888        // If a center pane is zoomed, unzoom it.
1889        for pane in &self.panes {
1890            if pane != &self.active_pane || dock_to_reveal.is_some() {
1891                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1892            }
1893        }
1894
1895        // If another dock is zoomed, hide it.
1896        let mut focus_center = false;
1897        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1898            dock.update(cx, |dock, cx| {
1899                if Some(dock.position()) != dock_to_reveal {
1900                    if let Some(panel) = dock.active_panel() {
1901                        if panel.is_zoomed(cx) {
1902                            focus_center |= panel.has_focus(cx);
1903                            dock.set_open(false, cx);
1904                        }
1905                    }
1906                }
1907            });
1908        }
1909
1910        if focus_center {
1911            cx.focus_self();
1912        }
1913
1914        if self.zoomed_position != dock_to_reveal {
1915            self.zoomed = None;
1916            self.zoomed_position = None;
1917        }
1918
1919        cx.notify();
1920    }
1921
1922    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1923        let pane = cx.add_view(|cx| {
1924            Pane::new(
1925                self.weak_handle(),
1926                self.project.clone(),
1927                self.app_state.background_actions,
1928                self.pane_history_timestamp.clone(),
1929                cx,
1930            )
1931        });
1932        cx.subscribe(&pane, Self::handle_pane_event).detach();
1933        self.panes.push(pane.clone());
1934        cx.focus(&pane);
1935        cx.emit(Event::PaneAdded(pane.clone()));
1936        pane
1937    }
1938
1939    pub fn add_item_to_center(
1940        &mut self,
1941        item: Box<dyn ItemHandle>,
1942        cx: &mut ViewContext<Self>,
1943    ) -> bool {
1944        if let Some(center_pane) = self.last_active_center_pane.clone() {
1945            if let Some(center_pane) = center_pane.upgrade(cx) {
1946                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1947                true
1948            } else {
1949                false
1950            }
1951        } else {
1952            false
1953        }
1954    }
1955
1956    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1957        self.active_pane
1958            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1959    }
1960
1961    pub fn split_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1962        let new_pane = self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1963        new_pane.update(cx, move |new_pane, cx| {
1964            new_pane.add_item(item, true, true, None, cx)
1965        })
1966    }
1967
1968    pub fn open_abs_path(
1969        &mut self,
1970        abs_path: PathBuf,
1971        visible: bool,
1972        cx: &mut ViewContext<Self>,
1973    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1974        cx.spawn(|workspace, mut cx| async move {
1975            let open_paths_task_result = workspace
1976                .update(&mut cx, |workspace, cx| {
1977                    workspace.open_paths(vec![abs_path.clone()], visible, cx)
1978                })
1979                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1980                .await;
1981            anyhow::ensure!(
1982                open_paths_task_result.len() == 1,
1983                "open abs path {abs_path:?} task returned incorrect number of results"
1984            );
1985            match open_paths_task_result
1986                .into_iter()
1987                .next()
1988                .expect("ensured single task result")
1989            {
1990                Some(open_result) => {
1991                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1992                }
1993                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1994            }
1995        })
1996    }
1997
1998    pub fn split_abs_path(
1999        &mut self,
2000        abs_path: PathBuf,
2001        visible: bool,
2002        cx: &mut ViewContext<Self>,
2003    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2004        let project_path_task =
2005            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
2006        cx.spawn(|this, mut cx| async move {
2007            let (_, path) = project_path_task.await?;
2008            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
2009                .await
2010        })
2011    }
2012
2013    pub fn open_path(
2014        &mut self,
2015        path: impl Into<ProjectPath>,
2016        pane: Option<WeakViewHandle<Pane>>,
2017        focus_item: bool,
2018        cx: &mut ViewContext<Self>,
2019    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2020        let pane = pane.unwrap_or_else(|| {
2021            self.last_active_center_pane.clone().unwrap_or_else(|| {
2022                self.panes
2023                    .first()
2024                    .expect("There must be an active pane")
2025                    .downgrade()
2026            })
2027        });
2028
2029        let task = self.load_path(path.into(), cx);
2030        cx.spawn(|_, mut cx| async move {
2031            let (project_entry_id, build_item) = task.await?;
2032            pane.update(&mut cx, |pane, cx| {
2033                pane.open_item(project_entry_id, focus_item, cx, build_item)
2034            })
2035        })
2036    }
2037
2038    pub fn split_path(
2039        &mut self,
2040        path: impl Into<ProjectPath>,
2041        cx: &mut ViewContext<Self>,
2042    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2043        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
2044            self.panes
2045                .first()
2046                .expect("There must be an active pane")
2047                .downgrade()
2048        });
2049
2050        if let Member::Pane(center_pane) = &self.center.root {
2051            if center_pane.read(cx).items_len() == 0 {
2052                return self.open_path(path, Some(pane), true, cx);
2053            }
2054        }
2055
2056        let task = self.load_path(path.into(), cx);
2057        cx.spawn(|this, mut cx| async move {
2058            let (project_entry_id, build_item) = task.await?;
2059            this.update(&mut cx, move |this, cx| -> Option<_> {
2060                let pane = pane.upgrade(cx)?;
2061                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
2062                new_pane.update(cx, |new_pane, cx| {
2063                    Some(new_pane.open_item(project_entry_id, true, cx, build_item))
2064                })
2065            })
2066            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
2067        })
2068    }
2069
2070    pub(crate) fn load_path(
2071        &mut self,
2072        path: ProjectPath,
2073        cx: &mut ViewContext<Self>,
2074    ) -> Task<
2075        Result<(
2076            ProjectEntryId,
2077            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
2078        )>,
2079    > {
2080        let project = self.project().clone();
2081        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
2082        cx.spawn(|_, mut cx| async move {
2083            let (project_entry_id, project_item) = project_item.await?;
2084            let build_item = cx.update(|cx| {
2085                cx.default_global::<ProjectItemBuilders>()
2086                    .get(&project_item.model_type())
2087                    .ok_or_else(|| anyhow!("no item builder for project item"))
2088                    .cloned()
2089            })?;
2090            let build_item =
2091                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
2092            Ok((project_entry_id, build_item))
2093        })
2094    }
2095
2096    pub fn open_project_item<T>(
2097        &mut self,
2098        project_item: ModelHandle<T::Item>,
2099        cx: &mut ViewContext<Self>,
2100    ) -> ViewHandle<T>
2101    where
2102        T: ProjectItem,
2103    {
2104        use project::Item as _;
2105
2106        let entry_id = project_item.read(cx).entry_id(cx);
2107        if let Some(item) = entry_id
2108            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2109            .and_then(|item| item.downcast())
2110        {
2111            self.activate_item(&item, cx);
2112            return item;
2113        }
2114
2115        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2116        self.add_item(Box::new(item.clone()), cx);
2117        item
2118    }
2119
2120    pub fn split_project_item<T>(
2121        &mut self,
2122        project_item: ModelHandle<T::Item>,
2123        cx: &mut ViewContext<Self>,
2124    ) -> ViewHandle<T>
2125    where
2126        T: ProjectItem,
2127    {
2128        use project::Item as _;
2129
2130        let entry_id = project_item.read(cx).entry_id(cx);
2131        if let Some(item) = entry_id
2132            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2133            .and_then(|item| item.downcast())
2134        {
2135            self.activate_item(&item, cx);
2136            return item;
2137        }
2138
2139        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2140        self.split_item(Box::new(item.clone()), cx);
2141        item
2142    }
2143
2144    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2145        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2146            self.active_pane.update(cx, |pane, cx| {
2147                pane.add_item(Box::new(shared_screen), false, true, None, cx)
2148            });
2149        }
2150    }
2151
2152    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
2153        let result = self.panes.iter().find_map(|pane| {
2154            pane.read(cx)
2155                .index_for_item(item)
2156                .map(|ix| (pane.clone(), ix))
2157        });
2158        if let Some((pane, ix)) = result {
2159            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2160            true
2161        } else {
2162            false
2163        }
2164    }
2165
2166    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2167        let panes = self.center.panes();
2168        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2169            cx.focus(&pane);
2170        } else {
2171            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2172        }
2173    }
2174
2175    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
2176        let panes = self.center.panes();
2177        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2178            let next_ix = (ix + 1) % panes.len();
2179            let next_pane = panes[next_ix].clone();
2180            cx.focus(&next_pane);
2181        }
2182    }
2183
2184    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
2185        let panes = self.center.panes();
2186        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2187            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2188            let prev_pane = panes[prev_ix].clone();
2189            cx.focus(&prev_pane);
2190        }
2191    }
2192
2193    pub fn activate_pane_in_direction(
2194        &mut self,
2195        direction: SplitDirection,
2196        cx: &mut ViewContext<Self>,
2197    ) {
2198        let bounding_box = match self.center.bounding_box_for_pane(&self.active_pane) {
2199            Some(coordinates) => coordinates,
2200            None => {
2201                return;
2202            }
2203        };
2204        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2205        let center = match cursor {
2206            Some(cursor) if bounding_box.contains_point(cursor) => cursor,
2207            _ => bounding_box.center(),
2208        };
2209
2210        let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.;
2211
2212        let target = match direction {
2213            SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()),
2214            SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()),
2215            SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next),
2216            SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next),
2217        };
2218
2219        if let Some(pane) = self.center.pane_at_pixel_position(target) {
2220            cx.focus(pane);
2221        }
2222    }
2223
2224    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2225        if self.active_pane != pane {
2226            self.active_pane = pane.clone();
2227            self.status_bar.update(cx, |status_bar, cx| {
2228                status_bar.set_active_pane(&self.active_pane, cx);
2229            });
2230            self.active_item_path_changed(cx);
2231            self.last_active_center_pane = Some(pane.downgrade());
2232        }
2233
2234        self.dismiss_zoomed_items_to_reveal(None, cx);
2235        if pane.read(cx).is_zoomed() {
2236            self.zoomed = Some(pane.downgrade().into_any());
2237        } else {
2238            self.zoomed = None;
2239        }
2240        self.zoomed_position = None;
2241        self.update_active_view_for_followers(cx);
2242
2243        cx.notify();
2244    }
2245
2246    fn handle_pane_event(
2247        &mut self,
2248        pane: ViewHandle<Pane>,
2249        event: &pane::Event,
2250        cx: &mut ViewContext<Self>,
2251    ) {
2252        match event {
2253            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2254            pane::Event::Split(direction) => {
2255                self.split_and_clone(pane, *direction, cx);
2256            }
2257            pane::Event::Remove => self.remove_pane(pane, cx),
2258            pane::Event::ActivateItem { local } => {
2259                if *local {
2260                    self.unfollow(&pane, cx);
2261                }
2262                if &pane == self.active_pane() {
2263                    self.active_item_path_changed(cx);
2264                }
2265            }
2266            pane::Event::ChangeItemTitle => {
2267                if pane == self.active_pane {
2268                    self.active_item_path_changed(cx);
2269                }
2270                self.update_window_edited(cx);
2271            }
2272            pane::Event::RemoveItem { item_id } => {
2273                self.update_window_edited(cx);
2274                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2275                    if entry.get().id() == pane.id() {
2276                        entry.remove();
2277                    }
2278                }
2279            }
2280            pane::Event::Focus => {
2281                self.handle_pane_focused(pane.clone(), cx);
2282            }
2283            pane::Event::ZoomIn => {
2284                if pane == self.active_pane {
2285                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2286                    if pane.read(cx).has_focus() {
2287                        self.zoomed = Some(pane.downgrade().into_any());
2288                        self.zoomed_position = None;
2289                    }
2290                    cx.notify();
2291                }
2292            }
2293            pane::Event::ZoomOut => {
2294                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2295                if self.zoomed_position.is_none() {
2296                    self.zoomed = None;
2297                }
2298                cx.notify();
2299            }
2300        }
2301
2302        self.serialize_workspace(cx);
2303    }
2304
2305    pub fn split_pane(
2306        &mut self,
2307        pane_to_split: ViewHandle<Pane>,
2308        split_direction: SplitDirection,
2309        cx: &mut ViewContext<Self>,
2310    ) -> ViewHandle<Pane> {
2311        let new_pane = self.add_pane(cx);
2312        self.center
2313            .split(&pane_to_split, &new_pane, split_direction)
2314            .unwrap();
2315        cx.notify();
2316        new_pane
2317    }
2318
2319    pub fn split_and_clone(
2320        &mut self,
2321        pane: ViewHandle<Pane>,
2322        direction: SplitDirection,
2323        cx: &mut ViewContext<Self>,
2324    ) -> Option<ViewHandle<Pane>> {
2325        let item = pane.read(cx).active_item()?;
2326        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2327            let new_pane = self.add_pane(cx);
2328            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2329            self.center.split(&pane, &new_pane, direction).unwrap();
2330            Some(new_pane)
2331        } else {
2332            None
2333        };
2334        cx.notify();
2335        maybe_pane_handle
2336    }
2337
2338    pub fn split_pane_with_item(
2339        &mut self,
2340        pane_to_split: WeakViewHandle<Pane>,
2341        split_direction: SplitDirection,
2342        from: WeakViewHandle<Pane>,
2343        item_id_to_move: usize,
2344        cx: &mut ViewContext<Self>,
2345    ) {
2346        let Some(pane_to_split) = pane_to_split.upgrade(cx) else {
2347            return;
2348        };
2349        let Some(from) = from.upgrade(cx) else {
2350            return;
2351        };
2352
2353        let new_pane = self.add_pane(cx);
2354        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2355        self.center
2356            .split(&pane_to_split, &new_pane, split_direction)
2357            .unwrap();
2358        cx.notify();
2359    }
2360
2361    pub fn split_pane_with_project_entry(
2362        &mut self,
2363        pane_to_split: WeakViewHandle<Pane>,
2364        split_direction: SplitDirection,
2365        project_entry: ProjectEntryId,
2366        cx: &mut ViewContext<Self>,
2367    ) -> Option<Task<Result<()>>> {
2368        let pane_to_split = pane_to_split.upgrade(cx)?;
2369        let new_pane = self.add_pane(cx);
2370        self.center
2371            .split(&pane_to_split, &new_pane, split_direction)
2372            .unwrap();
2373
2374        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2375        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2376        Some(cx.foreground().spawn(async move {
2377            task.await?;
2378            Ok(())
2379        }))
2380    }
2381
2382    pub fn move_item(
2383        &mut self,
2384        source: ViewHandle<Pane>,
2385        destination: ViewHandle<Pane>,
2386        item_id_to_move: usize,
2387        destination_index: usize,
2388        cx: &mut ViewContext<Self>,
2389    ) {
2390        let item_to_move = source
2391            .read(cx)
2392            .items()
2393            .enumerate()
2394            .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
2395
2396        if item_to_move.is_none() {
2397            log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
2398            return;
2399        }
2400        let (item_ix, item_handle) = item_to_move.unwrap();
2401        let item_handle = item_handle.clone();
2402
2403        if source != destination {
2404            // Close item from previous pane
2405            source.update(cx, |source, cx| {
2406                source.remove_item(item_ix, false, cx);
2407            });
2408        }
2409
2410        // This automatically removes duplicate items in the pane
2411        destination.update(cx, |destination, cx| {
2412            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2413            cx.focus_self();
2414        });
2415    }
2416
2417    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2418        if self.center.remove(&pane).unwrap() {
2419            self.force_remove_pane(&pane, cx);
2420            self.unfollow(&pane, cx);
2421            self.last_leaders_by_pane.remove(&pane.downgrade());
2422            for removed_item in pane.read(cx).items() {
2423                self.panes_by_item.remove(&removed_item.id());
2424            }
2425
2426            cx.notify();
2427        } else {
2428            self.active_item_path_changed(cx);
2429        }
2430    }
2431
2432    pub fn panes(&self) -> &[ViewHandle<Pane>] {
2433        &self.panes
2434    }
2435
2436    pub fn active_pane(&self) -> &ViewHandle<Pane> {
2437        &self.active_pane
2438    }
2439
2440    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
2441        if let Some(remote_id) = remote_id {
2442            self.remote_entity_subscription = Some(
2443                self.app_state
2444                    .client
2445                    .add_view_for_remote_entity(remote_id, cx),
2446            );
2447        } else {
2448            self.remote_entity_subscription.take();
2449        }
2450    }
2451
2452    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2453        self.leader_state.followers.remove(&peer_id);
2454        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
2455            for state in states_by_pane.into_values() {
2456                for item in state.items_by_leader_view_id.into_values() {
2457                    item.set_leader_replica_id(None, cx);
2458                }
2459            }
2460        }
2461        cx.notify();
2462    }
2463
2464    pub fn toggle_follow(
2465        &mut self,
2466        leader_id: PeerId,
2467        cx: &mut ViewContext<Self>,
2468    ) -> Option<Task<Result<()>>> {
2469        let pane = self.active_pane().clone();
2470
2471        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
2472            if leader_id == prev_leader_id {
2473                return None;
2474            }
2475        }
2476
2477        self.last_leaders_by_pane
2478            .insert(pane.downgrade(), leader_id);
2479        self.follower_states_by_leader
2480            .entry(leader_id)
2481            .or_default()
2482            .insert(pane.clone(), Default::default());
2483        cx.notify();
2484
2485        let project_id = self.project.read(cx).remote_id()?;
2486        let request = self.app_state.client.request(proto::Follow {
2487            project_id,
2488            leader_id: Some(leader_id),
2489        });
2490
2491        Some(cx.spawn(|this, mut cx| async move {
2492            let response = request.await?;
2493            this.update(&mut cx, |this, _| {
2494                let state = this
2495                    .follower_states_by_leader
2496                    .get_mut(&leader_id)
2497                    .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
2498                    .ok_or_else(|| anyhow!("following interrupted"))?;
2499                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2500                    Some(ViewId::from_proto(active_view_id)?)
2501                } else {
2502                    None
2503                };
2504                Ok::<_, anyhow::Error>(())
2505            })??;
2506            Self::add_views_from_leader(
2507                this.clone(),
2508                leader_id,
2509                vec![pane],
2510                response.views,
2511                &mut cx,
2512            )
2513            .await?;
2514            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2515            Ok(())
2516        }))
2517    }
2518
2519    pub fn follow_next_collaborator(
2520        &mut self,
2521        _: &FollowNextCollaborator,
2522        cx: &mut ViewContext<Self>,
2523    ) -> Option<Task<Result<()>>> {
2524        let collaborators = self.project.read(cx).collaborators();
2525        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2526            let mut collaborators = collaborators.keys().copied();
2527            for peer_id in collaborators.by_ref() {
2528                if peer_id == leader_id {
2529                    break;
2530                }
2531            }
2532            collaborators.next()
2533        } else if let Some(last_leader_id) =
2534            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2535        {
2536            if collaborators.contains_key(last_leader_id) {
2537                Some(*last_leader_id)
2538            } else {
2539                None
2540            }
2541        } else {
2542            None
2543        };
2544
2545        next_leader_id
2546            .or_else(|| collaborators.keys().copied().next())
2547            .and_then(|leader_id| self.toggle_follow(leader_id, cx))
2548    }
2549
2550    pub fn unfollow(
2551        &mut self,
2552        pane: &ViewHandle<Pane>,
2553        cx: &mut ViewContext<Self>,
2554    ) -> Option<PeerId> {
2555        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2556            let leader_id = *leader_id;
2557            if let Some(state) = states_by_pane.remove(pane) {
2558                for (_, item) in state.items_by_leader_view_id {
2559                    item.set_leader_replica_id(None, cx);
2560                }
2561
2562                if states_by_pane.is_empty() {
2563                    self.follower_states_by_leader.remove(&leader_id);
2564                    if let Some(project_id) = self.project.read(cx).remote_id() {
2565                        self.app_state
2566                            .client
2567                            .send(proto::Unfollow {
2568                                project_id,
2569                                leader_id: Some(leader_id),
2570                            })
2571                            .log_err();
2572                    }
2573                }
2574
2575                cx.notify();
2576                return Some(leader_id);
2577            }
2578        }
2579        None
2580    }
2581
2582    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2583        self.follower_states_by_leader.contains_key(&peer_id)
2584    }
2585
2586    pub fn is_followed_by(&self, peer_id: PeerId) -> bool {
2587        self.leader_state.followers.contains(&peer_id)
2588    }
2589
2590    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2591        // TODO: There should be a better system in place for this
2592        // (https://github.com/zed-industries/zed/issues/1290)
2593        let is_fullscreen = cx.window_is_fullscreen();
2594        let container_theme = if is_fullscreen {
2595            let mut container_theme = theme.titlebar.container;
2596            container_theme.padding.left = container_theme.padding.right;
2597            container_theme
2598        } else {
2599            theme.titlebar.container
2600        };
2601
2602        enum TitleBar {}
2603        MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
2604            Stack::new()
2605                .with_children(
2606                    self.titlebar_item
2607                        .as_ref()
2608                        .map(|item| ChildView::new(item, cx)),
2609                )
2610                .contained()
2611                .with_style(container_theme)
2612        })
2613        .on_click(MouseButton::Left, |event, _, cx| {
2614            if event.click_count == 2 {
2615                cx.zoom_window();
2616            }
2617        })
2618        .constrained()
2619        .with_height(theme.titlebar.height)
2620        .into_any_named("titlebar")
2621    }
2622
2623    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2624        let active_entry = self.active_project_path(cx);
2625        self.project
2626            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2627        self.update_window_title(cx);
2628    }
2629
2630    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2631        let project = self.project().read(cx);
2632        let mut title = String::new();
2633
2634        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2635            let filename = path
2636                .path
2637                .file_name()
2638                .map(|s| s.to_string_lossy())
2639                .or_else(|| {
2640                    Some(Cow::Borrowed(
2641                        project
2642                            .worktree_for_id(path.worktree_id, cx)?
2643                            .read(cx)
2644                            .root_name(),
2645                    ))
2646                });
2647
2648            if let Some(filename) = filename {
2649                title.push_str(filename.as_ref());
2650                title.push_str(" β€” ");
2651            }
2652        }
2653
2654        for (i, name) in project.worktree_root_names(cx).enumerate() {
2655            if i > 0 {
2656                title.push_str(", ");
2657            }
2658            title.push_str(name);
2659        }
2660
2661        if title.is_empty() {
2662            title = "empty project".to_string();
2663        }
2664
2665        if project.is_remote() {
2666            title.push_str(" ↙");
2667        } else if project.is_shared() {
2668            title.push_str(" β†—");
2669        }
2670
2671        cx.set_window_title(&title);
2672    }
2673
2674    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2675        let is_edited = !self.project.read(cx).is_read_only()
2676            && self
2677                .items(cx)
2678                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2679        if is_edited != self.window_edited {
2680            self.window_edited = is_edited;
2681            cx.set_window_edited(self.window_edited)
2682        }
2683    }
2684
2685    fn render_disconnected_overlay(
2686        &self,
2687        cx: &mut ViewContext<Workspace>,
2688    ) -> Option<AnyElement<Workspace>> {
2689        if self.project.read(cx).is_read_only() {
2690            enum DisconnectedOverlay {}
2691            Some(
2692                MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
2693                    let theme = &theme::current(cx);
2694                    Label::new(
2695                        "Your connection to the remote project has been lost.",
2696                        theme.workspace.disconnected_overlay.text.clone(),
2697                    )
2698                    .aligned()
2699                    .contained()
2700                    .with_style(theme.workspace.disconnected_overlay.container)
2701                })
2702                .with_cursor_style(CursorStyle::Arrow)
2703                .capture_all()
2704                .into_any_named("disconnected overlay"),
2705            )
2706        } else {
2707            None
2708        }
2709    }
2710
2711    fn render_notifications(
2712        &self,
2713        theme: &theme::Workspace,
2714        cx: &AppContext,
2715    ) -> Option<AnyElement<Workspace>> {
2716        if self.notifications.is_empty() {
2717            None
2718        } else {
2719            Some(
2720                Flex::column()
2721                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2722                        ChildView::new(notification.as_any(), cx)
2723                            .contained()
2724                            .with_style(theme.notification)
2725                    }))
2726                    .constrained()
2727                    .with_width(theme.notifications.width)
2728                    .contained()
2729                    .with_style(theme.notifications.container)
2730                    .aligned()
2731                    .bottom()
2732                    .right()
2733                    .into_any(),
2734            )
2735        }
2736    }
2737
2738    // RPC handlers
2739
2740    async fn handle_follow(
2741        this: WeakViewHandle<Self>,
2742        envelope: TypedEnvelope<proto::Follow>,
2743        _: Arc<Client>,
2744        mut cx: AsyncAppContext,
2745    ) -> Result<proto::FollowResponse> {
2746        this.update(&mut cx, |this, cx| {
2747            let client = &this.app_state.client;
2748            this.leader_state
2749                .followers
2750                .insert(envelope.original_sender_id()?);
2751
2752            let active_view_id = this.active_item(cx).and_then(|i| {
2753                Some(
2754                    i.to_followable_item_handle(cx)?
2755                        .remote_id(client, cx)?
2756                        .to_proto(),
2757                )
2758            });
2759
2760            cx.notify();
2761
2762            Ok(proto::FollowResponse {
2763                active_view_id,
2764                views: this
2765                    .panes()
2766                    .iter()
2767                    .flat_map(|pane| {
2768                        let leader_id = this.leader_for_pane(pane);
2769                        pane.read(cx).items().filter_map({
2770                            let cx = &cx;
2771                            move |item| {
2772                                let item = item.to_followable_item_handle(cx)?;
2773                                let id = item.remote_id(client, cx)?.to_proto();
2774                                let variant = item.to_state_proto(cx)?;
2775                                Some(proto::View {
2776                                    id: Some(id),
2777                                    leader_id,
2778                                    variant: Some(variant),
2779                                })
2780                            }
2781                        })
2782                    })
2783                    .collect(),
2784            })
2785        })?
2786    }
2787
2788    async fn handle_unfollow(
2789        this: WeakViewHandle<Self>,
2790        envelope: TypedEnvelope<proto::Unfollow>,
2791        _: Arc<Client>,
2792        mut cx: AsyncAppContext,
2793    ) -> Result<()> {
2794        this.update(&mut cx, |this, cx| {
2795            this.leader_state
2796                .followers
2797                .remove(&envelope.original_sender_id()?);
2798            cx.notify();
2799            Ok(())
2800        })?
2801    }
2802
2803    async fn handle_update_followers(
2804        this: WeakViewHandle<Self>,
2805        envelope: TypedEnvelope<proto::UpdateFollowers>,
2806        _: Arc<Client>,
2807        cx: AsyncAppContext,
2808    ) -> Result<()> {
2809        let leader_id = envelope.original_sender_id()?;
2810        this.read_with(&cx, |this, _| {
2811            this.leader_updates_tx
2812                .unbounded_send((leader_id, envelope.payload))
2813        })??;
2814        Ok(())
2815    }
2816
2817    async fn process_leader_update(
2818        this: &WeakViewHandle<Self>,
2819        leader_id: PeerId,
2820        update: proto::UpdateFollowers,
2821        cx: &mut AsyncAppContext,
2822    ) -> Result<()> {
2823        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2824            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2825                this.update(cx, |this, _| {
2826                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2827                        for state in state.values_mut() {
2828                            state.active_view_id =
2829                                if let Some(active_view_id) = update_active_view.id.clone() {
2830                                    Some(ViewId::from_proto(active_view_id)?)
2831                                } else {
2832                                    None
2833                                };
2834                        }
2835                    }
2836                    anyhow::Ok(())
2837                })??;
2838            }
2839            proto::update_followers::Variant::UpdateView(update_view) => {
2840                let variant = update_view
2841                    .variant
2842                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2843                let id = update_view
2844                    .id
2845                    .ok_or_else(|| anyhow!("missing update view id"))?;
2846                let mut tasks = Vec::new();
2847                this.update(cx, |this, cx| {
2848                    let project = this.project.clone();
2849                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2850                        for state in state.values_mut() {
2851                            let view_id = ViewId::from_proto(id.clone())?;
2852                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2853                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2854                            }
2855                        }
2856                    }
2857                    anyhow::Ok(())
2858                })??;
2859                try_join_all(tasks).await.log_err();
2860            }
2861            proto::update_followers::Variant::CreateView(view) => {
2862                let panes = this.read_with(cx, |this, _| {
2863                    this.follower_states_by_leader
2864                        .get(&leader_id)
2865                        .into_iter()
2866                        .flat_map(|states_by_pane| states_by_pane.keys())
2867                        .cloned()
2868                        .collect()
2869                })?;
2870                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2871            }
2872        }
2873        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2874        Ok(())
2875    }
2876
2877    async fn add_views_from_leader(
2878        this: WeakViewHandle<Self>,
2879        leader_id: PeerId,
2880        panes: Vec<ViewHandle<Pane>>,
2881        views: Vec<proto::View>,
2882        cx: &mut AsyncAppContext,
2883    ) -> Result<()> {
2884        let this = this
2885            .upgrade(cx)
2886            .ok_or_else(|| anyhow!("workspace dropped"))?;
2887        let project = this
2888            .read_with(cx, |this, _| this.project.clone())
2889            .ok_or_else(|| anyhow!("window dropped"))?;
2890
2891        let replica_id = project
2892            .read_with(cx, |project, _| {
2893                project
2894                    .collaborators()
2895                    .get(&leader_id)
2896                    .map(|c| c.replica_id)
2897            })
2898            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2899
2900        let item_builders = cx.update(|cx| {
2901            cx.default_global::<FollowableItemBuilders>()
2902                .values()
2903                .map(|b| b.0)
2904                .collect::<Vec<_>>()
2905        });
2906
2907        let mut item_tasks_by_pane = HashMap::default();
2908        for pane in panes {
2909            let mut item_tasks = Vec::new();
2910            let mut leader_view_ids = Vec::new();
2911            for view in &views {
2912                let Some(id) = &view.id else { continue };
2913                let id = ViewId::from_proto(id.clone())?;
2914                let mut variant = view.variant.clone();
2915                if variant.is_none() {
2916                    Err(anyhow!("missing view variant"))?;
2917                }
2918                for build_item in &item_builders {
2919                    let task = cx
2920                        .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx));
2921                    if let Some(task) = task {
2922                        item_tasks.push(task);
2923                        leader_view_ids.push(id);
2924                        break;
2925                    } else {
2926                        assert!(variant.is_some());
2927                    }
2928                }
2929            }
2930
2931            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2932        }
2933
2934        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2935            let items = futures::future::try_join_all(item_tasks).await?;
2936            this.update(cx, |this, cx| {
2937                let state = this
2938                    .follower_states_by_leader
2939                    .get_mut(&leader_id)?
2940                    .get_mut(&pane)?;
2941
2942                for (id, item) in leader_view_ids.into_iter().zip(items) {
2943                    item.set_leader_replica_id(Some(replica_id), cx);
2944                    state.items_by_leader_view_id.insert(id, item);
2945                }
2946
2947                Some(())
2948            });
2949        }
2950        Ok(())
2951    }
2952
2953    fn update_active_view_for_followers(&self, cx: &AppContext) {
2954        if self.active_pane.read(cx).has_focus() {
2955            self.update_followers(
2956                proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
2957                    id: self.active_item(cx).and_then(|item| {
2958                        item.to_followable_item_handle(cx)?
2959                            .remote_id(&self.app_state.client, cx)
2960                            .map(|id| id.to_proto())
2961                    }),
2962                    leader_id: self.leader_for_pane(&self.active_pane),
2963                }),
2964                cx,
2965            );
2966        } else {
2967            self.update_followers(
2968                proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
2969                    id: None,
2970                    leader_id: None,
2971                }),
2972                cx,
2973            );
2974        }
2975    }
2976
2977    fn update_followers(
2978        &self,
2979        update: proto::update_followers::Variant,
2980        cx: &AppContext,
2981    ) -> Option<()> {
2982        let project_id = self.project.read(cx).remote_id()?;
2983        if !self.leader_state.followers.is_empty() {
2984            self.app_state
2985                .client
2986                .send(proto::UpdateFollowers {
2987                    project_id,
2988                    follower_ids: self.leader_state.followers.iter().copied().collect(),
2989                    variant: Some(update),
2990                })
2991                .log_err();
2992        }
2993        None
2994    }
2995
2996    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2997        self.follower_states_by_leader
2998            .iter()
2999            .find_map(|(leader_id, state)| {
3000                if state.contains_key(pane) {
3001                    Some(*leader_id)
3002                } else {
3003                    None
3004                }
3005            })
3006    }
3007
3008    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3009        cx.notify();
3010
3011        let call = self.active_call()?;
3012        let room = call.read(cx).room()?.read(cx);
3013        let participant = room.remote_participant_for_peer_id(leader_id)?;
3014        let mut items_to_activate = Vec::new();
3015        match participant.location {
3016            call::ParticipantLocation::SharedProject { project_id } => {
3017                if Some(project_id) == self.project.read(cx).remote_id() {
3018                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
3019                        if let Some(item) = state
3020                            .active_view_id
3021                            .and_then(|id| state.items_by_leader_view_id.get(&id))
3022                        {
3023                            items_to_activate.push((pane.clone(), item.boxed_clone()));
3024                        } else if let Some(shared_screen) =
3025                            self.shared_screen_for_peer(leader_id, pane, cx)
3026                        {
3027                            items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3028                        }
3029                    }
3030                }
3031            }
3032            call::ParticipantLocation::UnsharedProject => {}
3033            call::ParticipantLocation::External => {
3034                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
3035                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3036                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3037                    }
3038                }
3039            }
3040        }
3041
3042        for (pane, item) in items_to_activate {
3043            let pane_was_focused = pane.read(cx).has_focus();
3044            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3045                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3046            } else {
3047                pane.update(cx, |pane, cx| {
3048                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3049                });
3050            }
3051
3052            if pane_was_focused {
3053                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3054            }
3055        }
3056
3057        None
3058    }
3059
3060    fn shared_screen_for_peer(
3061        &self,
3062        peer_id: PeerId,
3063        pane: &ViewHandle<Pane>,
3064        cx: &mut ViewContext<Self>,
3065    ) -> Option<ViewHandle<SharedScreen>> {
3066        let call = self.active_call()?;
3067        let room = call.read(cx).room()?.read(cx);
3068        let participant = room.remote_participant_for_peer_id(peer_id)?;
3069        let track = participant.video_tracks.values().next()?.clone();
3070        let user = participant.user.clone();
3071
3072        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3073            if item.read(cx).peer_id == peer_id {
3074                return Some(item);
3075            }
3076        }
3077
3078        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3079    }
3080
3081    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
3082        if active {
3083            cx.background()
3084                .spawn(persistence::DB.update_timestamp(self.database_id()))
3085                .detach();
3086        } else {
3087            for pane in &self.panes {
3088                pane.update(cx, |pane, cx| {
3089                    if let Some(item) = pane.active_item() {
3090                        item.workspace_deactivated(cx);
3091                    }
3092                    if matches!(
3093                        settings::get::<WorkspaceSettings>(cx).autosave,
3094                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3095                    ) {
3096                        for item in pane.items() {
3097                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3098                                .detach_and_log_err(cx);
3099                        }
3100                    }
3101                });
3102            }
3103        }
3104    }
3105
3106    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
3107        self.active_call.as_ref().map(|(call, _)| call)
3108    }
3109
3110    fn on_active_call_event(
3111        &mut self,
3112        _: ModelHandle<ActiveCall>,
3113        event: &call::room::Event,
3114        cx: &mut ViewContext<Self>,
3115    ) {
3116        match event {
3117            call::room::Event::ParticipantLocationChanged { participant_id }
3118            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3119                self.leader_updated(*participant_id, cx);
3120            }
3121            _ => {}
3122        }
3123    }
3124
3125    pub fn database_id(&self) -> WorkspaceId {
3126        self.database_id
3127    }
3128
3129    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3130        let project = self.project().read(cx);
3131
3132        if project.is_local() {
3133            Some(
3134                project
3135                    .visible_worktrees(cx)
3136                    .map(|worktree| worktree.read(cx).abs_path())
3137                    .collect::<Vec<_>>()
3138                    .into(),
3139            )
3140        } else {
3141            None
3142        }
3143    }
3144
3145    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3146        match member {
3147            Member::Axis(PaneAxis { members, .. }) => {
3148                for child in members.iter() {
3149                    self.remove_panes(child.clone(), cx)
3150                }
3151            }
3152            Member::Pane(pane) => {
3153                self.force_remove_pane(&pane, cx);
3154            }
3155        }
3156    }
3157
3158    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
3159        self.panes.retain(|p| p != pane);
3160        cx.focus(self.panes.last().unwrap());
3161        if self.last_active_center_pane == Some(pane.downgrade()) {
3162            self.last_active_center_pane = None;
3163        }
3164        cx.notify();
3165    }
3166
3167    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3168        self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
3169            cx.background().timer(Duration::from_millis(100)).await;
3170            this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
3171                .ok();
3172        }));
3173    }
3174
3175    fn serialize_workspace(&self, cx: &ViewContext<Self>) {
3176        fn serialize_pane_handle(
3177            pane_handle: &ViewHandle<Pane>,
3178            cx: &AppContext,
3179        ) -> SerializedPane {
3180            let (items, active) = {
3181                let pane = pane_handle.read(cx);
3182                let active_item_id = pane.active_item().map(|item| item.id());
3183                (
3184                    pane.items()
3185                        .filter_map(|item_handle| {
3186                            Some(SerializedItem {
3187                                kind: Arc::from(item_handle.serialized_item_kind()?),
3188                                item_id: item_handle.id(),
3189                                active: Some(item_handle.id()) == active_item_id,
3190                            })
3191                        })
3192                        .collect::<Vec<_>>(),
3193                    pane.has_focus(),
3194                )
3195            };
3196
3197            SerializedPane::new(items, active)
3198        }
3199
3200        fn build_serialized_pane_group(
3201            pane_group: &Member,
3202            cx: &AppContext,
3203        ) -> SerializedPaneGroup {
3204            match pane_group {
3205                Member::Axis(PaneAxis {
3206                    axis,
3207                    members,
3208                    flexes,
3209                    bounding_boxes: _,
3210                }) => SerializedPaneGroup::Group {
3211                    axis: *axis,
3212                    children: members
3213                        .iter()
3214                        .map(|member| build_serialized_pane_group(member, cx))
3215                        .collect::<Vec<_>>(),
3216                    flexes: Some(flexes.borrow().clone()),
3217                },
3218                Member::Pane(pane_handle) => {
3219                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3220                }
3221            }
3222        }
3223
3224        fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
3225            let left_dock = this.left_dock.read(cx);
3226            let left_visible = left_dock.is_open();
3227            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
3228                Some(
3229                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3230                        .to_string(),
3231                )
3232            });
3233            let left_dock_zoom = left_dock
3234                .visible_panel()
3235                .map(|panel| panel.is_zoomed(cx))
3236                .unwrap_or(false);
3237
3238            let right_dock = this.right_dock.read(cx);
3239            let right_visible = right_dock.is_open();
3240            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
3241                Some(
3242                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3243                        .to_string(),
3244                )
3245            });
3246            let right_dock_zoom = right_dock
3247                .visible_panel()
3248                .map(|panel| panel.is_zoomed(cx))
3249                .unwrap_or(false);
3250
3251            let bottom_dock = this.bottom_dock.read(cx);
3252            let bottom_visible = bottom_dock.is_open();
3253            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
3254                Some(
3255                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3256                        .to_string(),
3257                )
3258            });
3259            let bottom_dock_zoom = bottom_dock
3260                .visible_panel()
3261                .map(|panel| panel.is_zoomed(cx))
3262                .unwrap_or(false);
3263
3264            DockStructure {
3265                left: DockData {
3266                    visible: left_visible,
3267                    active_panel: left_active_panel,
3268                    zoom: left_dock_zoom,
3269                },
3270                right: DockData {
3271                    visible: right_visible,
3272                    active_panel: right_active_panel,
3273                    zoom: right_dock_zoom,
3274                },
3275                bottom: DockData {
3276                    visible: bottom_visible,
3277                    active_panel: bottom_active_panel,
3278                    zoom: bottom_dock_zoom,
3279                },
3280            }
3281        }
3282
3283        if let Some(location) = self.location(cx) {
3284            // Load bearing special case:
3285            //  - with_local_workspace() relies on this to not have other stuff open
3286            //    when you open your log
3287            if !location.paths().is_empty() {
3288                let center_group = build_serialized_pane_group(&self.center.root, cx);
3289                let docks = build_serialized_docks(self, cx);
3290
3291                let serialized_workspace = SerializedWorkspace {
3292                    id: self.database_id,
3293                    location,
3294                    center_group,
3295                    bounds: Default::default(),
3296                    display: Default::default(),
3297                    docks,
3298                };
3299
3300                cx.background()
3301                    .spawn(persistence::DB.save_workspace(serialized_workspace))
3302                    .detach();
3303            }
3304        }
3305    }
3306
3307    pub(crate) fn load_workspace(
3308        workspace: WeakViewHandle<Workspace>,
3309        serialized_workspace: SerializedWorkspace,
3310        paths_to_open: Vec<Option<ProjectPath>>,
3311        cx: &mut AppContext,
3312    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
3313        cx.spawn(|mut cx| async move {
3314            let result = async_iife! {{
3315                let (project, old_center_pane) =
3316                workspace.read_with(&cx, |workspace, _| {
3317                    (
3318                        workspace.project().clone(),
3319                        workspace.last_active_center_pane.clone(),
3320                    )
3321                })?;
3322
3323                let mut center_items = None;
3324                let mut center_group = None;
3325                // Traverse the splits tree and add to things
3326                if let Some((group, active_pane, items)) = serialized_workspace
3327                        .center_group
3328                        .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
3329                        .await {
3330                    center_items = Some(items);
3331                    center_group = Some((group, active_pane))
3332                }
3333
3334                let resulting_list = cx.read(|cx| {
3335                    let mut opened_items = center_items
3336                        .unwrap_or_default()
3337                        .into_iter()
3338                        .filter_map(|item| {
3339                            let item = item?;
3340                            let project_path = item.project_path(cx)?;
3341                            Some((project_path, item))
3342                        })
3343                        .collect::<HashMap<_, _>>();
3344
3345                    paths_to_open
3346                        .into_iter()
3347                        .map(|path_to_open| {
3348                            path_to_open.map(|path_to_open| {
3349                                Ok(opened_items.remove(&path_to_open))
3350                            })
3351                            .transpose()
3352                            .map(|item| item.flatten())
3353                            .transpose()
3354                        })
3355                        .collect::<Vec<_>>()
3356                });
3357
3358                // Remove old panes from workspace panes list
3359                workspace.update(&mut cx, |workspace, cx| {
3360                    if let Some((center_group, active_pane)) = center_group {
3361                        workspace.remove_panes(workspace.center.root.clone(), cx);
3362
3363                        // Swap workspace center group
3364                        workspace.center = PaneGroup::with_root(center_group);
3365
3366                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
3367                        cx.focus_self();
3368
3369                        if let Some(active_pane) = active_pane {
3370                            cx.focus(&active_pane);
3371                        } else {
3372                            cx.focus(workspace.panes.last().unwrap());
3373                        }
3374                    } else {
3375                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3376                        if let Some(old_center_handle) = old_center_handle {
3377                            cx.focus(&old_center_handle)
3378                        } else {
3379                            cx.focus_self()
3380                        }
3381                    }
3382
3383                    let docks = serialized_workspace.docks;
3384                    workspace.left_dock.update(cx, |dock, cx| {
3385                        dock.set_open(docks.left.visible, cx);
3386                        if let Some(active_panel) = docks.left.active_panel {
3387                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3388                                dock.activate_panel(ix, cx);
3389                            }
3390                        }
3391                                dock.active_panel()
3392                                    .map(|panel| {
3393                                        panel.set_zoomed(docks.left.zoom, cx)
3394                                    });
3395                                if docks.left.visible && docks.left.zoom {
3396                                    cx.focus_self()
3397                                }
3398                    });
3399                    // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3400                    workspace.right_dock.update(cx, |dock, cx| {
3401                        dock.set_open(docks.right.visible, cx);
3402                        if let Some(active_panel) = docks.right.active_panel {
3403                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3404                                dock.activate_panel(ix, cx);
3405
3406                            }
3407                        }
3408                                dock.active_panel()
3409                                    .map(|panel| {
3410                                        panel.set_zoomed(docks.right.zoom, cx)
3411                                    });
3412
3413                                if docks.right.visible && docks.right.zoom {
3414                                    cx.focus_self()
3415                                }
3416                    });
3417                    workspace.bottom_dock.update(cx, |dock, cx| {
3418                        dock.set_open(docks.bottom.visible, cx);
3419                        if let Some(active_panel) = docks.bottom.active_panel {
3420                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3421                                dock.activate_panel(ix, cx);
3422                            }
3423                        }
3424
3425                        dock.active_panel()
3426                            .map(|panel| {
3427                                panel.set_zoomed(docks.bottom.zoom, cx)
3428                            });
3429
3430                        if docks.bottom.visible && docks.bottom.zoom {
3431                            cx.focus_self()
3432                        }
3433                    });
3434
3435
3436                    cx.notify();
3437                })?;
3438
3439                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3440                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3441
3442                Ok::<_, anyhow::Error>(resulting_list)
3443            }};
3444
3445            result.await.unwrap_or_default()
3446        })
3447    }
3448
3449    #[cfg(any(test, feature = "test-support"))]
3450    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3451        let client = project.read(cx).client();
3452        let user_store = project.read(cx).user_store();
3453
3454        let channel_store =
3455            cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
3456        let app_state = Arc::new(AppState {
3457            languages: project.read(cx).languages().clone(),
3458            client,
3459            user_store,
3460            channel_store,
3461            fs: project.read(cx).fs().clone(),
3462            build_window_options: |_, _, _| Default::default(),
3463            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3464            background_actions: || &[],
3465        });
3466        Self::new(0, project, app_state, cx)
3467    }
3468
3469    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3470        let dock = match position {
3471            DockPosition::Left => &self.left_dock,
3472            DockPosition::Right => &self.right_dock,
3473            DockPosition::Bottom => &self.bottom_dock,
3474        };
3475        let active_panel = dock.read(cx).visible_panel()?;
3476        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3477            dock.read(cx).render_placeholder(cx)
3478        } else {
3479            ChildView::new(dock, cx).into_any()
3480        };
3481
3482        Some(
3483            element
3484                .constrained()
3485                .dynamically(move |constraint, _, cx| match position {
3486                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3487                        Vector2F::new(20., constraint.min.y()),
3488                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3489                    ),
3490                    DockPosition::Bottom => SizeConstraint::new(
3491                        Vector2F::new(constraint.min.x(), 20.),
3492                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3493                    ),
3494                })
3495                .into_any(),
3496        )
3497    }
3498}
3499
3500fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3501    ZED_WINDOW_POSITION
3502        .zip(*ZED_WINDOW_SIZE)
3503        .map(|(position, size)| {
3504            WindowBounds::Fixed(RectF::new(
3505                cx.platform().screens()[0].bounds().origin() + position,
3506                size,
3507            ))
3508        })
3509}
3510
3511async fn open_items(
3512    serialized_workspace: Option<SerializedWorkspace>,
3513    workspace: &WeakViewHandle<Workspace>,
3514    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3515    app_state: Arc<AppState>,
3516    mut cx: AsyncAppContext,
3517) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
3518    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3519
3520    if let Some(serialized_workspace) = serialized_workspace {
3521        let workspace = workspace.clone();
3522        let restored_items = cx
3523            .update(|cx| {
3524                Workspace::load_workspace(
3525                    workspace,
3526                    serialized_workspace,
3527                    project_paths_to_open
3528                        .iter()
3529                        .map(|(_, project_path)| project_path)
3530                        .cloned()
3531                        .collect(),
3532                    cx,
3533                )
3534            })
3535            .await;
3536
3537        let restored_project_paths = cx.read(|cx| {
3538            restored_items
3539                .iter()
3540                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
3541                .collect::<HashSet<_>>()
3542        });
3543
3544        opened_items = restored_items;
3545        project_paths_to_open
3546            .iter_mut()
3547            .for_each(|(_, project_path)| {
3548                if let Some(project_path_to_open) = project_path {
3549                    if restored_project_paths.contains(project_path_to_open) {
3550                        *project_path = None;
3551                    }
3552                }
3553            });
3554    } else {
3555        for _ in 0..project_paths_to_open.len() {
3556            opened_items.push(None);
3557        }
3558    }
3559    assert!(opened_items.len() == project_paths_to_open.len());
3560
3561    let tasks =
3562        project_paths_to_open
3563            .into_iter()
3564            .enumerate()
3565            .map(|(i, (abs_path, project_path))| {
3566                let workspace = workspace.clone();
3567                cx.spawn(|mut cx| {
3568                    let fs = app_state.fs.clone();
3569                    async move {
3570                        let file_project_path = project_path?;
3571                        if fs.is_file(&abs_path).await {
3572                            Some((
3573                                i,
3574                                workspace
3575                                    .update(&mut cx, |workspace, cx| {
3576                                        workspace.open_path(file_project_path, None, true, cx)
3577                                    })
3578                                    .log_err()?
3579                                    .await,
3580                            ))
3581                        } else {
3582                            None
3583                        }
3584                    }
3585                })
3586            });
3587
3588    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3589        .await
3590        .into_iter()
3591    {
3592        if let Some((i, path_open_result)) = maybe_opened_path {
3593            opened_items[i] = Some(path_open_result);
3594        }
3595    }
3596
3597    opened_items
3598}
3599
3600fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3601    const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3602    const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3603    const MESSAGE_ID: usize = 2;
3604
3605    if workspace
3606        .read_with(cx, |workspace, cx| {
3607            workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3608        })
3609        .unwrap_or(false)
3610    {
3611        return;
3612    }
3613
3614    if db::kvp::KEY_VALUE_STORE
3615        .read_kvp(NEW_DOCK_HINT_KEY)
3616        .ok()
3617        .flatten()
3618        .is_some()
3619    {
3620        if !workspace
3621            .read_with(cx, |workspace, cx| {
3622                workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3623            })
3624            .unwrap_or(false)
3625        {
3626            cx.update(|cx| {
3627                cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3628                    let entry = tracker
3629                        .entry(TypeId::of::<MessageNotification>())
3630                        .or_default();
3631                    if !entry.contains(&MESSAGE_ID) {
3632                        entry.push(MESSAGE_ID);
3633                    }
3634                });
3635            });
3636        }
3637
3638        return;
3639    }
3640
3641    cx.spawn(|_| async move {
3642        db::kvp::KEY_VALUE_STORE
3643            .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3644            .await
3645            .ok();
3646    })
3647    .detach();
3648
3649    workspace
3650        .update(cx, |workspace, cx| {
3651            workspace.show_notification_once(2, cx, |cx| {
3652                cx.add_view(|_| {
3653                    MessageNotification::new_element(|text, _| {
3654                        Text::new(
3655                            "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3656                            text,
3657                        )
3658                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3659                            let code_span_background_color = settings::get::<ThemeSettings>(cx)
3660                                .theme
3661                                .editor
3662                                .document_highlight_read_background;
3663
3664                            cx.scene().push_quad(gpui::Quad {
3665                                bounds,
3666                                background: Some(code_span_background_color),
3667                                border: Default::default(),
3668                                corner_radii: (2.0).into(),
3669                            })
3670                        })
3671                        .into_any()
3672                    })
3673                    .with_click_message("Read more about the new panel system")
3674                    .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3675                })
3676            })
3677        })
3678        .ok();
3679}
3680
3681fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3682    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3683
3684    workspace
3685        .update(cx, |workspace, cx| {
3686            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3687                workspace.show_notification_once(0, cx, |cx| {
3688                    cx.add_view(|_| {
3689                        MessageNotification::new("Failed to load the database file.")
3690                            .with_click_message("Click to let us know about this error")
3691                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3692                    })
3693                });
3694            }
3695        })
3696        .log_err();
3697}
3698
3699impl Entity for Workspace {
3700    type Event = Event;
3701}
3702
3703impl View for Workspace {
3704    fn ui_name() -> &'static str {
3705        "Workspace"
3706    }
3707
3708    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3709        let theme = theme::current(cx).clone();
3710        Stack::new()
3711            .with_child(
3712                Flex::column()
3713                    .with_child(self.render_titlebar(&theme, cx))
3714                    .with_child(
3715                        Stack::new()
3716                            .with_child({
3717                                let project = self.project.clone();
3718                                Flex::row()
3719                                    .with_children(self.render_dock(DockPosition::Left, cx))
3720                                    .with_child(
3721                                        Flex::column()
3722                                            .with_child(
3723                                                FlexItem::new(
3724                                                    self.center.render(
3725                                                        &project,
3726                                                        &theme,
3727                                                        &self.follower_states_by_leader,
3728                                                        self.active_call(),
3729                                                        self.active_pane(),
3730                                                        self.zoomed
3731                                                            .as_ref()
3732                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3733                                                            .as_ref(),
3734                                                        &self.app_state,
3735                                                        cx,
3736                                                    ),
3737                                                )
3738                                                .flex(1., true),
3739                                            )
3740                                            .with_children(
3741                                                self.render_dock(DockPosition::Bottom, cx),
3742                                            )
3743                                            .flex(1., true),
3744                                    )
3745                                    .with_children(self.render_dock(DockPosition::Right, cx))
3746                            })
3747                            .with_child(Overlay::new(
3748                                Stack::new()
3749                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3750                                        enum ZoomBackground {}
3751                                        let zoomed = zoomed.upgrade(cx)?;
3752
3753                                        let mut foreground_style =
3754                                            theme.workspace.zoomed_pane_foreground;
3755                                        if let Some(zoomed_dock_position) = self.zoomed_position {
3756                                            foreground_style =
3757                                                theme.workspace.zoomed_panel_foreground;
3758                                            let margin = foreground_style.margin.top;
3759                                            let border = foreground_style.border.top;
3760
3761                                            // Only include a margin and border on the opposite side.
3762                                            foreground_style.margin.top = 0.;
3763                                            foreground_style.margin.left = 0.;
3764                                            foreground_style.margin.bottom = 0.;
3765                                            foreground_style.margin.right = 0.;
3766                                            foreground_style.border.top = false;
3767                                            foreground_style.border.left = false;
3768                                            foreground_style.border.bottom = false;
3769                                            foreground_style.border.right = false;
3770                                            match zoomed_dock_position {
3771                                                DockPosition::Left => {
3772                                                    foreground_style.margin.right = margin;
3773                                                    foreground_style.border.right = border;
3774                                                }
3775                                                DockPosition::Right => {
3776                                                    foreground_style.margin.left = margin;
3777                                                    foreground_style.border.left = border;
3778                                                }
3779                                                DockPosition::Bottom => {
3780                                                    foreground_style.margin.top = margin;
3781                                                    foreground_style.border.top = border;
3782                                                }
3783                                            }
3784                                        }
3785
3786                                        Some(
3787                                            ChildView::new(&zoomed, cx)
3788                                                .contained()
3789                                                .with_style(foreground_style)
3790                                                .aligned()
3791                                                .contained()
3792                                                .with_style(theme.workspace.zoomed_background)
3793                                                .mouse::<ZoomBackground>(0)
3794                                                .capture_all()
3795                                                .on_down(
3796                                                    MouseButton::Left,
3797                                                    |_, this: &mut Self, cx| {
3798                                                        this.zoom_out(cx);
3799                                                    },
3800                                                ),
3801                                        )
3802                                    }))
3803                                    .with_children(self.modal.as_ref().map(|modal| {
3804                                        // Prevent clicks within the modal from falling
3805                                        // through to the rest of the workspace.
3806                                        enum ModalBackground {}
3807                                        MouseEventHandler::new::<ModalBackground, _>(
3808                                            0,
3809                                            cx,
3810                                            |_, cx| ChildView::new(modal.view.as_any(), cx),
3811                                        )
3812                                        .on_click(MouseButton::Left, |_, _, _| {})
3813                                        .contained()
3814                                        .with_style(theme.workspace.modal)
3815                                        .aligned()
3816                                        .top()
3817                                    }))
3818                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3819                            ))
3820                            .provide_resize_bounds::<WorkspaceBounds>()
3821                            .flex(1.0, true),
3822                    )
3823                    .with_child(ChildView::new(&self.status_bar, cx))
3824                    .contained()
3825                    .with_background_color(theme.workspace.background),
3826            )
3827            .with_children(DragAndDrop::render(cx))
3828            .with_children(self.render_disconnected_overlay(cx))
3829            .into_any_named("workspace")
3830    }
3831
3832    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3833        if cx.is_self_focused() {
3834            cx.focus(&self.active_pane);
3835        }
3836    }
3837
3838    fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3839        DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3840    }
3841}
3842
3843impl ViewId {
3844    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3845        Ok(Self {
3846            creator: message
3847                .creator
3848                .ok_or_else(|| anyhow!("creator is missing"))?,
3849            id: message.id,
3850        })
3851    }
3852
3853    pub(crate) fn to_proto(&self) -> proto::ViewId {
3854        proto::ViewId {
3855            creator: Some(self.creator),
3856            id: self.id,
3857        }
3858    }
3859}
3860
3861pub trait WorkspaceHandle {
3862    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3863}
3864
3865impl WorkspaceHandle for ViewHandle<Workspace> {
3866    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3867        self.read(cx)
3868            .worktrees(cx)
3869            .flat_map(|worktree| {
3870                let worktree_id = worktree.read(cx).id();
3871                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3872                    worktree_id,
3873                    path: f.path.clone(),
3874                })
3875            })
3876            .collect::<Vec<_>>()
3877    }
3878}
3879
3880impl std::fmt::Debug for OpenPaths {
3881    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3882        f.debug_struct("OpenPaths")
3883            .field("paths", &self.paths)
3884            .finish()
3885    }
3886}
3887
3888pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
3889
3890pub fn activate_workspace_for_project(
3891    cx: &mut AsyncAppContext,
3892    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3893) -> Option<WeakViewHandle<Workspace>> {
3894    for window in cx.windows() {
3895        let handle = window
3896            .update(cx, |cx| {
3897                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3898                    let project = workspace_handle.read(cx).project.clone();
3899                    if project.update(cx, &predicate) {
3900                        cx.activate_window();
3901                        return Some(workspace_handle.clone());
3902                    }
3903                }
3904                None
3905            })
3906            .flatten();
3907
3908        if let Some(handle) = handle {
3909            return Some(handle.downgrade());
3910        }
3911    }
3912    None
3913}
3914
3915pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3916    DB.last_workspace().await.log_err().flatten()
3917}
3918
3919#[allow(clippy::type_complexity)]
3920pub fn open_paths(
3921    abs_paths: &[PathBuf],
3922    app_state: &Arc<AppState>,
3923    requesting_window: Option<WindowHandle<Workspace>>,
3924    cx: &mut AppContext,
3925) -> Task<
3926    Result<(
3927        WeakViewHandle<Workspace>,
3928        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3929    )>,
3930> {
3931    let app_state = app_state.clone();
3932    let abs_paths = abs_paths.to_vec();
3933    cx.spawn(|mut cx| async move {
3934        // Open paths in existing workspace if possible
3935        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3936            project.contains_paths(&abs_paths, cx)
3937        });
3938
3939        if let Some(existing) = existing {
3940            Ok((
3941                existing.clone(),
3942                existing
3943                    .update(&mut cx, |workspace, cx| {
3944                        workspace.open_paths(abs_paths, true, cx)
3945                    })?
3946                    .await,
3947            ))
3948        } else {
3949            Ok(cx
3950                .update(|cx| {
3951                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
3952                })
3953                .await)
3954        }
3955    })
3956}
3957
3958pub fn open_new(
3959    app_state: &Arc<AppState>,
3960    cx: &mut AppContext,
3961    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3962) -> Task<()> {
3963    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3964    cx.spawn(|mut cx| async move {
3965        let (workspace, opened_paths) = task.await;
3966
3967        workspace
3968            .update(&mut cx, |workspace, cx| {
3969                if opened_paths.is_empty() {
3970                    init(workspace, cx)
3971                }
3972            })
3973            .log_err();
3974    })
3975}
3976
3977pub fn create_and_open_local_file(
3978    path: &'static Path,
3979    cx: &mut ViewContext<Workspace>,
3980    default_content: impl 'static + Send + FnOnce() -> Rope,
3981) -> Task<Result<Box<dyn ItemHandle>>> {
3982    cx.spawn(|workspace, mut cx| async move {
3983        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
3984        if !fs.is_file(path).await {
3985            fs.create_file(path, Default::default()).await?;
3986            fs.save(path, &default_content(), Default::default())
3987                .await?;
3988        }
3989
3990        let mut items = workspace
3991            .update(&mut cx, |workspace, cx| {
3992                workspace.with_local_workspace(cx, |workspace, cx| {
3993                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
3994                })
3995            })?
3996            .await?
3997            .await;
3998
3999        let item = items.pop().flatten();
4000        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4001    })
4002}
4003
4004pub fn join_remote_project(
4005    project_id: u64,
4006    follow_user_id: u64,
4007    app_state: Arc<AppState>,
4008    cx: &mut AppContext,
4009) -> Task<Result<()>> {
4010    cx.spawn(|mut cx| async move {
4011        let existing_workspace = cx
4012            .windows()
4013            .into_iter()
4014            .find_map(|window| {
4015                window.downcast::<Workspace>().and_then(|window| {
4016                    window.read_root_with(&cx, |workspace, cx| {
4017                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4018                            Some(cx.handle().downgrade())
4019                        } else {
4020                            None
4021                        }
4022                    })
4023                })
4024            })
4025            .flatten();
4026
4027        let workspace = if let Some(existing_workspace) = existing_workspace {
4028            existing_workspace
4029        } else {
4030            let active_call = cx.read(ActiveCall::global);
4031            let room = active_call
4032                .read_with(&cx, |call, _| call.room().cloned())
4033                .ok_or_else(|| anyhow!("not in a call"))?;
4034            let project = room
4035                .update(&mut cx, |room, cx| {
4036                    room.join_project(
4037                        project_id,
4038                        app_state.languages.clone(),
4039                        app_state.fs.clone(),
4040                        cx,
4041                    )
4042                })
4043                .await?;
4044
4045            let window_bounds_override = window_bounds_env_override(&cx);
4046            let window = cx.add_window(
4047                (app_state.build_window_options)(
4048                    window_bounds_override,
4049                    None,
4050                    cx.platform().as_ref(),
4051                ),
4052                |cx| Workspace::new(0, project, app_state.clone(), cx),
4053            );
4054            let workspace = window.root(&cx).unwrap();
4055            (app_state.initialize_workspace)(
4056                workspace.downgrade(),
4057                false,
4058                app_state.clone(),
4059                cx.clone(),
4060            )
4061            .await
4062            .log_err();
4063
4064            workspace.downgrade()
4065        };
4066
4067        workspace.window().activate(&mut cx);
4068        cx.platform().activate(true);
4069
4070        workspace.update(&mut cx, |workspace, cx| {
4071            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4072                let follow_peer_id = room
4073                    .read(cx)
4074                    .remote_participants()
4075                    .iter()
4076                    .find(|(_, participant)| participant.user.id == follow_user_id)
4077                    .map(|(_, p)| p.peer_id)
4078                    .or_else(|| {
4079                        // If we couldn't follow the given user, follow the host instead.
4080                        let collaborator = workspace
4081                            .project()
4082                            .read(cx)
4083                            .collaborators()
4084                            .values()
4085                            .find(|collaborator| collaborator.replica_id == 0)?;
4086                        Some(collaborator.peer_id)
4087                    });
4088
4089                if let Some(follow_peer_id) = follow_peer_id {
4090                    if !workspace.is_being_followed(follow_peer_id) {
4091                        workspace
4092                            .toggle_follow(follow_peer_id, cx)
4093                            .map(|follow| follow.detach_and_log_err(cx));
4094                    }
4095                }
4096            }
4097        })?;
4098
4099        anyhow::Ok(())
4100    })
4101}
4102
4103pub fn restart(_: &Restart, cx: &mut AppContext) {
4104    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4105    cx.spawn(|mut cx| async move {
4106        let mut workspace_windows = cx
4107            .windows()
4108            .into_iter()
4109            .filter_map(|window| window.downcast::<Workspace>())
4110            .collect::<Vec<_>>();
4111
4112        // If multiple windows have unsaved changes, and need a save prompt,
4113        // prompt in the active window before switching to a different window.
4114        workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4115
4116        if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4117            let answer = window.prompt(
4118                PromptLevel::Info,
4119                "Are you sure you want to restart?",
4120                &["Restart", "Cancel"],
4121                &mut cx,
4122            );
4123
4124            if let Some(mut answer) = answer {
4125                let answer = answer.next().await;
4126                if answer != Some(0) {
4127                    return Ok(());
4128                }
4129            }
4130        }
4131
4132        // If the user cancels any save prompt, then keep the app open.
4133        for window in workspace_windows {
4134            if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4135                workspace.prepare_to_close(true, cx)
4136            }) {
4137                if !should_close.await? {
4138                    return Ok(());
4139                }
4140            }
4141        }
4142        cx.platform().restart();
4143        anyhow::Ok(())
4144    })
4145    .detach_and_log_err(cx);
4146}
4147
4148fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4149    let mut parts = value.split(',');
4150    let width: usize = parts.next()?.parse().ok()?;
4151    let height: usize = parts.next()?.parse().ok()?;
4152    Some(vec2f(width as f32, height as f32))
4153}
4154
4155#[cfg(test)]
4156mod tests {
4157    use super::*;
4158    use crate::{
4159        dock::test::{TestPanel, TestPanelEvent},
4160        item::test::{TestItem, TestItemEvent, TestProjectItem},
4161    };
4162    use fs::FakeFs;
4163    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4164    use project::{Project, ProjectEntryId};
4165    use serde_json::json;
4166    use settings::SettingsStore;
4167    use std::{cell::RefCell, rc::Rc};
4168
4169    #[gpui::test]
4170    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4171        init_test(cx);
4172
4173        let fs = FakeFs::new(cx.background());
4174        let project = Project::test(fs, [], cx).await;
4175        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4176        let workspace = window.root(cx);
4177
4178        // Adding an item with no ambiguity renders the tab without detail.
4179        let item1 = window.add_view(cx, |_| {
4180            let mut item = TestItem::new();
4181            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4182            item
4183        });
4184        workspace.update(cx, |workspace, cx| {
4185            workspace.add_item(Box::new(item1.clone()), cx);
4186        });
4187        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4188
4189        // Adding an item that creates ambiguity increases the level of detail on
4190        // both tabs.
4191        let item2 = window.add_view(cx, |_| {
4192            let mut item = TestItem::new();
4193            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4194            item
4195        });
4196        workspace.update(cx, |workspace, cx| {
4197            workspace.add_item(Box::new(item2.clone()), cx);
4198        });
4199        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4200        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4201
4202        // Adding an item that creates ambiguity increases the level of detail only
4203        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4204        // we stop at the highest detail available.
4205        let item3 = window.add_view(cx, |_| {
4206            let mut item = TestItem::new();
4207            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4208            item
4209        });
4210        workspace.update(cx, |workspace, cx| {
4211            workspace.add_item(Box::new(item3.clone()), cx);
4212        });
4213        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4214        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4215        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4216    }
4217
4218    #[gpui::test]
4219    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4220        init_test(cx);
4221
4222        let fs = FakeFs::new(cx.background());
4223        fs.insert_tree(
4224            "/root1",
4225            json!({
4226                "one.txt": "",
4227                "two.txt": "",
4228            }),
4229        )
4230        .await;
4231        fs.insert_tree(
4232            "/root2",
4233            json!({
4234                "three.txt": "",
4235            }),
4236        )
4237        .await;
4238
4239        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4240        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4241        let workspace = window.root(cx);
4242        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4243        let worktree_id = project.read_with(cx, |project, cx| {
4244            project.worktrees(cx).next().unwrap().read(cx).id()
4245        });
4246
4247        let item1 = window.add_view(cx, |cx| {
4248            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4249        });
4250        let item2 = window.add_view(cx, |cx| {
4251            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4252        });
4253
4254        // Add an item to an empty pane
4255        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4256        project.read_with(cx, |project, cx| {
4257            assert_eq!(
4258                project.active_entry(),
4259                project
4260                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4261                    .map(|e| e.id)
4262            );
4263        });
4264        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4265
4266        // Add a second item to a non-empty pane
4267        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4268        assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
4269        project.read_with(cx, |project, cx| {
4270            assert_eq!(
4271                project.active_entry(),
4272                project
4273                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4274                    .map(|e| e.id)
4275            );
4276        });
4277
4278        // Close the active item
4279        pane.update(cx, |pane, cx| {
4280            pane.close_active_item(&Default::default(), cx).unwrap()
4281        })
4282        .await
4283        .unwrap();
4284        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4285        project.read_with(cx, |project, cx| {
4286            assert_eq!(
4287                project.active_entry(),
4288                project
4289                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4290                    .map(|e| e.id)
4291            );
4292        });
4293
4294        // Add a project folder
4295        project
4296            .update(cx, |project, cx| {
4297                project.find_or_create_local_worktree("/root2", true, cx)
4298            })
4299            .await
4300            .unwrap();
4301        assert_eq!(
4302            window.current_title(cx).as_deref(),
4303            Some("one.txt β€” root1, root2")
4304        );
4305
4306        // Remove a project folder
4307        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4308        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
4309    }
4310
4311    #[gpui::test]
4312    async fn test_close_window(cx: &mut TestAppContext) {
4313        init_test(cx);
4314
4315        let fs = FakeFs::new(cx.background());
4316        fs.insert_tree("/root", json!({ "one": "" })).await;
4317
4318        let project = Project::test(fs, ["root".as_ref()], cx).await;
4319        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4320        let workspace = window.root(cx);
4321
4322        // When there are no dirty items, there's nothing to do.
4323        let item1 = window.add_view(cx, |_| TestItem::new());
4324        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4325        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4326        assert!(task.await.unwrap());
4327
4328        // When there are dirty untitled items, prompt to save each one. If the user
4329        // cancels any prompt, then abort.
4330        let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
4331        let item3 = window.add_view(cx, |cx| {
4332            TestItem::new()
4333                .with_dirty(true)
4334                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4335        });
4336        workspace.update(cx, |w, cx| {
4337            w.add_item(Box::new(item2.clone()), cx);
4338            w.add_item(Box::new(item3.clone()), cx);
4339        });
4340        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4341        cx.foreground().run_until_parked();
4342        window.simulate_prompt_answer(2, cx); // cancel save all
4343        cx.foreground().run_until_parked();
4344        window.simulate_prompt_answer(2, cx); // cancel save all
4345        cx.foreground().run_until_parked();
4346        assert!(!window.has_pending_prompt(cx));
4347        assert!(!task.await.unwrap());
4348    }
4349
4350    #[gpui::test]
4351    async fn test_close_pane_items(cx: &mut TestAppContext) {
4352        init_test(cx);
4353
4354        let fs = FakeFs::new(cx.background());
4355
4356        let project = Project::test(fs, None, cx).await;
4357        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4358        let workspace = window.root(cx);
4359
4360        let item1 = window.add_view(cx, |cx| {
4361            TestItem::new()
4362                .with_dirty(true)
4363                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4364        });
4365        let item2 = window.add_view(cx, |cx| {
4366            TestItem::new()
4367                .with_dirty(true)
4368                .with_conflict(true)
4369                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4370        });
4371        let item3 = window.add_view(cx, |cx| {
4372            TestItem::new()
4373                .with_dirty(true)
4374                .with_conflict(true)
4375                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4376        });
4377        let item4 = window.add_view(cx, |cx| {
4378            TestItem::new()
4379                .with_dirty(true)
4380                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4381        });
4382        let pane = workspace.update(cx, |workspace, cx| {
4383            workspace.add_item(Box::new(item1.clone()), cx);
4384            workspace.add_item(Box::new(item2.clone()), cx);
4385            workspace.add_item(Box::new(item3.clone()), cx);
4386            workspace.add_item(Box::new(item4.clone()), cx);
4387            workspace.active_pane().clone()
4388        });
4389
4390        let close_items = pane.update(cx, |pane, cx| {
4391            pane.activate_item(1, true, true, cx);
4392            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4393            let item1_id = item1.id();
4394            let item3_id = item3.id();
4395            let item4_id = item4.id();
4396            pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| {
4397                [item1_id, item3_id, item4_id].contains(&id)
4398            })
4399        });
4400        cx.foreground().run_until_parked();
4401
4402        assert!(window.has_pending_prompt(cx));
4403        // Ignore "Save all" prompt
4404        window.simulate_prompt_answer(2, cx);
4405        cx.foreground().run_until_parked();
4406        // There's a prompt to save item 1.
4407        pane.read_with(cx, |pane, _| {
4408            assert_eq!(pane.items_len(), 4);
4409            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4410        });
4411        // Confirm saving item 1.
4412        window.simulate_prompt_answer(0, cx);
4413        cx.foreground().run_until_parked();
4414
4415        // Item 1 is saved. There's a prompt to save item 3.
4416        pane.read_with(cx, |pane, cx| {
4417            assert_eq!(item1.read(cx).save_count, 1);
4418            assert_eq!(item1.read(cx).save_as_count, 0);
4419            assert_eq!(item1.read(cx).reload_count, 0);
4420            assert_eq!(pane.items_len(), 3);
4421            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4422        });
4423        assert!(window.has_pending_prompt(cx));
4424
4425        // Cancel saving item 3.
4426        window.simulate_prompt_answer(1, cx);
4427        cx.foreground().run_until_parked();
4428
4429        // Item 3 is reloaded. There's a prompt to save item 4.
4430        pane.read_with(cx, |pane, cx| {
4431            assert_eq!(item3.read(cx).save_count, 0);
4432            assert_eq!(item3.read(cx).save_as_count, 0);
4433            assert_eq!(item3.read(cx).reload_count, 1);
4434            assert_eq!(pane.items_len(), 2);
4435            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4436        });
4437        assert!(window.has_pending_prompt(cx));
4438
4439        // Confirm saving item 4.
4440        window.simulate_prompt_answer(0, cx);
4441        cx.foreground().run_until_parked();
4442
4443        // There's a prompt for a path for item 4.
4444        cx.simulate_new_path_selection(|_| Some(Default::default()));
4445        close_items.await.unwrap();
4446
4447        // The requested items are closed.
4448        pane.read_with(cx, |pane, cx| {
4449            assert_eq!(item4.read(cx).save_count, 0);
4450            assert_eq!(item4.read(cx).save_as_count, 1);
4451            assert_eq!(item4.read(cx).reload_count, 0);
4452            assert_eq!(pane.items_len(), 1);
4453            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4454        });
4455    }
4456
4457    #[gpui::test]
4458    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4459        init_test(cx);
4460
4461        let fs = FakeFs::new(cx.background());
4462
4463        let project = Project::test(fs, [], cx).await;
4464        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4465        let workspace = window.root(cx);
4466
4467        // Create several workspace items with single project entries, and two
4468        // workspace items with multiple project entries.
4469        let single_entry_items = (0..=4)
4470            .map(|project_entry_id| {
4471                window.add_view(cx, |cx| {
4472                    TestItem::new()
4473                        .with_dirty(true)
4474                        .with_project_items(&[TestProjectItem::new(
4475                            project_entry_id,
4476                            &format!("{project_entry_id}.txt"),
4477                            cx,
4478                        )])
4479                })
4480            })
4481            .collect::<Vec<_>>();
4482        let item_2_3 = window.add_view(cx, |cx| {
4483            TestItem::new()
4484                .with_dirty(true)
4485                .with_singleton(false)
4486                .with_project_items(&[
4487                    single_entry_items[2].read(cx).project_items[0].clone(),
4488                    single_entry_items[3].read(cx).project_items[0].clone(),
4489                ])
4490        });
4491        let item_3_4 = window.add_view(cx, |cx| {
4492            TestItem::new()
4493                .with_dirty(true)
4494                .with_singleton(false)
4495                .with_project_items(&[
4496                    single_entry_items[3].read(cx).project_items[0].clone(),
4497                    single_entry_items[4].read(cx).project_items[0].clone(),
4498                ])
4499        });
4500
4501        // Create two panes that contain the following project entries:
4502        //   left pane:
4503        //     multi-entry items:   (2, 3)
4504        //     single-entry items:  0, 1, 2, 3, 4
4505        //   right pane:
4506        //     single-entry items:  1
4507        //     multi-entry items:   (3, 4)
4508        let left_pane = workspace.update(cx, |workspace, cx| {
4509            let left_pane = workspace.active_pane().clone();
4510            workspace.add_item(Box::new(item_2_3.clone()), cx);
4511            for item in single_entry_items {
4512                workspace.add_item(Box::new(item), cx);
4513            }
4514            left_pane.update(cx, |pane, cx| {
4515                pane.activate_item(2, true, true, cx);
4516            });
4517
4518            workspace
4519                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4520                .unwrap();
4521
4522            left_pane
4523        });
4524
4525        //Need to cause an effect flush in order to respect new focus
4526        workspace.update(cx, |workspace, cx| {
4527            workspace.add_item(Box::new(item_3_4.clone()), cx);
4528            cx.focus(&left_pane);
4529        });
4530
4531        // When closing all of the items in the left pane, we should be prompted twice:
4532        // once for project entry 0, and once for project entry 2. After those two
4533        // prompts, the task should complete.
4534
4535        let close = left_pane.update(cx, |pane, cx| {
4536            pane.close_items(cx, SaveBehavior::PromptOnWrite, move |_| true)
4537        });
4538        cx.foreground().run_until_parked();
4539        // Discard "Save all" prompt
4540        window.simulate_prompt_answer(2, cx);
4541
4542        cx.foreground().run_until_parked();
4543        left_pane.read_with(cx, |pane, cx| {
4544            assert_eq!(
4545                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4546                &[ProjectEntryId::from_proto(0)]
4547            );
4548        });
4549        window.simulate_prompt_answer(0, cx);
4550
4551        cx.foreground().run_until_parked();
4552        left_pane.read_with(cx, |pane, cx| {
4553            assert_eq!(
4554                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4555                &[ProjectEntryId::from_proto(2)]
4556            );
4557        });
4558        window.simulate_prompt_answer(0, cx);
4559
4560        cx.foreground().run_until_parked();
4561        close.await.unwrap();
4562        left_pane.read_with(cx, |pane, _| {
4563            assert_eq!(pane.items_len(), 0);
4564        });
4565    }
4566
4567    #[gpui::test]
4568    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4569        init_test(cx);
4570
4571        let fs = FakeFs::new(cx.background());
4572
4573        let project = Project::test(fs, [], cx).await;
4574        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4575        let workspace = window.root(cx);
4576        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4577
4578        let item = window.add_view(cx, |cx| {
4579            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4580        });
4581        let item_id = item.id();
4582        workspace.update(cx, |workspace, cx| {
4583            workspace.add_item(Box::new(item.clone()), cx);
4584        });
4585
4586        // Autosave on window change.
4587        item.update(cx, |item, cx| {
4588            cx.update_global(|settings: &mut SettingsStore, cx| {
4589                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4590                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4591                })
4592            });
4593            item.is_dirty = true;
4594        });
4595
4596        // Deactivating the window saves the file.
4597        window.simulate_deactivation(cx);
4598        deterministic.run_until_parked();
4599        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4600
4601        // Autosave on focus change.
4602        item.update(cx, |item, cx| {
4603            cx.focus_self();
4604            cx.update_global(|settings: &mut SettingsStore, cx| {
4605                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4606                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4607                })
4608            });
4609            item.is_dirty = true;
4610        });
4611
4612        // Blurring the item saves the file.
4613        item.update(cx, |_, cx| cx.blur());
4614        deterministic.run_until_parked();
4615        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4616
4617        // Deactivating the window still saves the file.
4618        window.simulate_activation(cx);
4619        item.update(cx, |item, cx| {
4620            cx.focus_self();
4621            item.is_dirty = true;
4622        });
4623        window.simulate_deactivation(cx);
4624
4625        deterministic.run_until_parked();
4626        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4627
4628        // Autosave after delay.
4629        item.update(cx, |item, cx| {
4630            cx.update_global(|settings: &mut SettingsStore, cx| {
4631                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4632                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4633                })
4634            });
4635            item.is_dirty = true;
4636            cx.emit(TestItemEvent::Edit);
4637        });
4638
4639        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4640        deterministic.advance_clock(Duration::from_millis(250));
4641        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4642
4643        // After delay expires, the file is saved.
4644        deterministic.advance_clock(Duration::from_millis(250));
4645        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4646
4647        // Autosave on focus change, ensuring closing the tab counts as such.
4648        item.update(cx, |item, cx| {
4649            cx.update_global(|settings: &mut SettingsStore, cx| {
4650                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4651                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4652                })
4653            });
4654            item.is_dirty = true;
4655        });
4656
4657        pane.update(cx, |pane, cx| {
4658            pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| id == item_id)
4659        })
4660        .await
4661        .unwrap();
4662        assert!(!window.has_pending_prompt(cx));
4663        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4664
4665        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4666        workspace.update(cx, |workspace, cx| {
4667            workspace.add_item(Box::new(item.clone()), cx);
4668        });
4669        item.update(cx, |item, cx| {
4670            item.project_items[0].update(cx, |item, _| {
4671                item.entry_id = None;
4672            });
4673            item.is_dirty = true;
4674            cx.blur();
4675        });
4676        deterministic.run_until_parked();
4677        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4678
4679        // Ensure autosave is prevented for deleted files also when closing the buffer.
4680        let _close_items = pane.update(cx, |pane, cx| {
4681            pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| id == item_id)
4682        });
4683        deterministic.run_until_parked();
4684        assert!(window.has_pending_prompt(cx));
4685        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4686    }
4687
4688    #[gpui::test]
4689    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4690        init_test(cx);
4691
4692        let fs = FakeFs::new(cx.background());
4693
4694        let project = Project::test(fs, [], cx).await;
4695        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4696        let workspace = window.root(cx);
4697
4698        let item = window.add_view(cx, |cx| {
4699            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4700        });
4701        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4702        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4703        let toolbar_notify_count = Rc::new(RefCell::new(0));
4704
4705        workspace.update(cx, |workspace, cx| {
4706            workspace.add_item(Box::new(item.clone()), cx);
4707            let toolbar_notification_count = toolbar_notify_count.clone();
4708            cx.observe(&toolbar, move |_, _, _| {
4709                *toolbar_notification_count.borrow_mut() += 1
4710            })
4711            .detach();
4712        });
4713
4714        pane.read_with(cx, |pane, _| {
4715            assert!(!pane.can_navigate_backward());
4716            assert!(!pane.can_navigate_forward());
4717        });
4718
4719        item.update(cx, |item, cx| {
4720            item.set_state("one".to_string(), cx);
4721        });
4722
4723        // Toolbar must be notified to re-render the navigation buttons
4724        assert_eq!(*toolbar_notify_count.borrow(), 1);
4725
4726        pane.read_with(cx, |pane, _| {
4727            assert!(pane.can_navigate_backward());
4728            assert!(!pane.can_navigate_forward());
4729        });
4730
4731        workspace
4732            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4733            .await
4734            .unwrap();
4735
4736        assert_eq!(*toolbar_notify_count.borrow(), 3);
4737        pane.read_with(cx, |pane, _| {
4738            assert!(!pane.can_navigate_backward());
4739            assert!(pane.can_navigate_forward());
4740        });
4741    }
4742
4743    #[gpui::test]
4744    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4745        init_test(cx);
4746        let fs = FakeFs::new(cx.background());
4747
4748        let project = Project::test(fs, [], cx).await;
4749        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4750        let workspace = window.root(cx);
4751
4752        let panel = workspace.update(cx, |workspace, cx| {
4753            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4754            workspace.add_panel(panel.clone(), cx);
4755
4756            workspace
4757                .right_dock()
4758                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4759
4760            panel
4761        });
4762
4763        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4764        pane.update(cx, |pane, cx| {
4765            let item = cx.add_view(|_| TestItem::new());
4766            pane.add_item(Box::new(item), true, true, None, cx);
4767        });
4768
4769        // Transfer focus from center to panel
4770        workspace.update(cx, |workspace, cx| {
4771            workspace.toggle_panel_focus::<TestPanel>(cx);
4772        });
4773
4774        workspace.read_with(cx, |workspace, cx| {
4775            assert!(workspace.right_dock().read(cx).is_open());
4776            assert!(!panel.is_zoomed(cx));
4777            assert!(panel.has_focus(cx));
4778        });
4779
4780        // Transfer focus from panel to center
4781        workspace.update(cx, |workspace, cx| {
4782            workspace.toggle_panel_focus::<TestPanel>(cx);
4783        });
4784
4785        workspace.read_with(cx, |workspace, cx| {
4786            assert!(workspace.right_dock().read(cx).is_open());
4787            assert!(!panel.is_zoomed(cx));
4788            assert!(!panel.has_focus(cx));
4789        });
4790
4791        // Close the dock
4792        workspace.update(cx, |workspace, cx| {
4793            workspace.toggle_dock(DockPosition::Right, cx);
4794        });
4795
4796        workspace.read_with(cx, |workspace, cx| {
4797            assert!(!workspace.right_dock().read(cx).is_open());
4798            assert!(!panel.is_zoomed(cx));
4799            assert!(!panel.has_focus(cx));
4800        });
4801
4802        // Open the dock
4803        workspace.update(cx, |workspace, cx| {
4804            workspace.toggle_dock(DockPosition::Right, cx);
4805        });
4806
4807        workspace.read_with(cx, |workspace, cx| {
4808            assert!(workspace.right_dock().read(cx).is_open());
4809            assert!(!panel.is_zoomed(cx));
4810            assert!(panel.has_focus(cx));
4811        });
4812
4813        // Focus and zoom panel
4814        panel.update(cx, |panel, cx| {
4815            cx.focus_self();
4816            panel.set_zoomed(true, cx)
4817        });
4818
4819        workspace.read_with(cx, |workspace, cx| {
4820            assert!(workspace.right_dock().read(cx).is_open());
4821            assert!(panel.is_zoomed(cx));
4822            assert!(panel.has_focus(cx));
4823        });
4824
4825        // Transfer focus to the center closes the dock
4826        workspace.update(cx, |workspace, cx| {
4827            workspace.toggle_panel_focus::<TestPanel>(cx);
4828        });
4829
4830        workspace.read_with(cx, |workspace, cx| {
4831            assert!(!workspace.right_dock().read(cx).is_open());
4832            assert!(panel.is_zoomed(cx));
4833            assert!(!panel.has_focus(cx));
4834        });
4835
4836        // Transferring focus back to the panel keeps it zoomed
4837        workspace.update(cx, |workspace, cx| {
4838            workspace.toggle_panel_focus::<TestPanel>(cx);
4839        });
4840
4841        workspace.read_with(cx, |workspace, cx| {
4842            assert!(workspace.right_dock().read(cx).is_open());
4843            assert!(panel.is_zoomed(cx));
4844            assert!(panel.has_focus(cx));
4845        });
4846
4847        // Close the dock while it is zoomed
4848        workspace.update(cx, |workspace, cx| {
4849            workspace.toggle_dock(DockPosition::Right, cx)
4850        });
4851
4852        workspace.read_with(cx, |workspace, cx| {
4853            assert!(!workspace.right_dock().read(cx).is_open());
4854            assert!(panel.is_zoomed(cx));
4855            assert!(workspace.zoomed.is_none());
4856            assert!(!panel.has_focus(cx));
4857        });
4858
4859        // Opening the dock, when it's zoomed, retains focus
4860        workspace.update(cx, |workspace, cx| {
4861            workspace.toggle_dock(DockPosition::Right, cx)
4862        });
4863
4864        workspace.read_with(cx, |workspace, cx| {
4865            assert!(workspace.right_dock().read(cx).is_open());
4866            assert!(panel.is_zoomed(cx));
4867            assert!(workspace.zoomed.is_some());
4868            assert!(panel.has_focus(cx));
4869        });
4870
4871        // Unzoom and close the panel, zoom the active pane.
4872        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
4873        workspace.update(cx, |workspace, cx| {
4874            workspace.toggle_dock(DockPosition::Right, cx)
4875        });
4876        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
4877
4878        // Opening a dock unzooms the pane.
4879        workspace.update(cx, |workspace, cx| {
4880            workspace.toggle_dock(DockPosition::Right, cx)
4881        });
4882        workspace.read_with(cx, |workspace, cx| {
4883            let pane = pane.read(cx);
4884            assert!(!pane.is_zoomed());
4885            assert!(!pane.has_focus());
4886            assert!(workspace.right_dock().read(cx).is_open());
4887            assert!(workspace.zoomed.is_none());
4888        });
4889    }
4890
4891    #[gpui::test]
4892    async fn test_panels(cx: &mut gpui::TestAppContext) {
4893        init_test(cx);
4894        let fs = FakeFs::new(cx.background());
4895
4896        let project = Project::test(fs, [], cx).await;
4897        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4898        let workspace = window.root(cx);
4899
4900        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4901            // Add panel_1 on the left, panel_2 on the right.
4902            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4903            workspace.add_panel(panel_1.clone(), cx);
4904            workspace
4905                .left_dock()
4906                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4907            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4908            workspace.add_panel(panel_2.clone(), cx);
4909            workspace
4910                .right_dock()
4911                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4912
4913            let left_dock = workspace.left_dock();
4914            assert_eq!(
4915                left_dock.read(cx).visible_panel().unwrap().id(),
4916                panel_1.id()
4917            );
4918            assert_eq!(
4919                left_dock.read(cx).active_panel_size(cx).unwrap(),
4920                panel_1.size(cx)
4921            );
4922
4923            left_dock.update(cx, |left_dock, cx| {
4924                left_dock.resize_active_panel(Some(1337.), cx)
4925            });
4926            assert_eq!(
4927                workspace
4928                    .right_dock()
4929                    .read(cx)
4930                    .visible_panel()
4931                    .unwrap()
4932                    .id(),
4933                panel_2.id()
4934            );
4935
4936            (panel_1, panel_2)
4937        });
4938
4939        // Move panel_1 to the right
4940        panel_1.update(cx, |panel_1, cx| {
4941            panel_1.set_position(DockPosition::Right, cx)
4942        });
4943
4944        workspace.update(cx, |workspace, cx| {
4945            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4946            // Since it was the only panel on the left, the left dock should now be closed.
4947            assert!(!workspace.left_dock().read(cx).is_open());
4948            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
4949            let right_dock = workspace.right_dock();
4950            assert_eq!(
4951                right_dock.read(cx).visible_panel().unwrap().id(),
4952                panel_1.id()
4953            );
4954            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4955
4956            // Now we move panel_2Β to the left
4957            panel_2.set_position(DockPosition::Left, cx);
4958        });
4959
4960        workspace.update(cx, |workspace, cx| {
4961            // Since panel_2 was not visible on the right, we don't open the left dock.
4962            assert!(!workspace.left_dock().read(cx).is_open());
4963            // And the right dock is unaffected in it's displaying of panel_1
4964            assert!(workspace.right_dock().read(cx).is_open());
4965            assert_eq!(
4966                workspace
4967                    .right_dock()
4968                    .read(cx)
4969                    .visible_panel()
4970                    .unwrap()
4971                    .id(),
4972                panel_1.id()
4973            );
4974        });
4975
4976        // Move panel_1 back to the left
4977        panel_1.update(cx, |panel_1, cx| {
4978            panel_1.set_position(DockPosition::Left, cx)
4979        });
4980
4981        workspace.update(cx, |workspace, cx| {
4982            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4983            let left_dock = workspace.left_dock();
4984            assert!(left_dock.read(cx).is_open());
4985            assert_eq!(
4986                left_dock.read(cx).visible_panel().unwrap().id(),
4987                panel_1.id()
4988            );
4989            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4990            // And right the dock should be closed as it no longer has any panels.
4991            assert!(!workspace.right_dock().read(cx).is_open());
4992
4993            // Now we move panel_1 to the bottom
4994            panel_1.set_position(DockPosition::Bottom, cx);
4995        });
4996
4997        workspace.update(cx, |workspace, cx| {
4998            // Since panel_1 was visible on the left, we close the left dock.
4999            assert!(!workspace.left_dock().read(cx).is_open());
5000            // The bottom dock is sized based on the panel's default size,
5001            // since the panel orientation changed from vertical to horizontal.
5002            let bottom_dock = workspace.bottom_dock();
5003            assert_eq!(
5004                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5005                panel_1.size(cx),
5006            );
5007            // Close bottom dock and move panel_1 back to the left.
5008            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5009            panel_1.set_position(DockPosition::Left, cx);
5010        });
5011
5012        // Emit activated event on panel 1
5013        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5014
5015        // Now the left dock is open and panel_1 is active and focused.
5016        workspace.read_with(cx, |workspace, cx| {
5017            let left_dock = workspace.left_dock();
5018            assert!(left_dock.read(cx).is_open());
5019            assert_eq!(
5020                left_dock.read(cx).visible_panel().unwrap().id(),
5021                panel_1.id()
5022            );
5023            assert!(panel_1.is_focused(cx));
5024        });
5025
5026        // Emit closed event on panel 2, which is not active
5027        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5028
5029        // Wo don't close the left dock, because panel_2 wasn't the active panel
5030        workspace.read_with(cx, |workspace, cx| {
5031            let left_dock = workspace.left_dock();
5032            assert!(left_dock.read(cx).is_open());
5033            assert_eq!(
5034                left_dock.read(cx).visible_panel().unwrap().id(),
5035                panel_1.id()
5036            );
5037        });
5038
5039        // Emitting a ZoomIn event shows the panel as zoomed.
5040        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5041        workspace.read_with(cx, |workspace, _| {
5042            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5043            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5044        });
5045
5046        // Move panel to another dock while it is zoomed
5047        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5048        workspace.read_with(cx, |workspace, _| {
5049            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5050            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5051        });
5052
5053        // If focus is transferred to another view that's not a panel or another pane, we still show
5054        // the panel as zoomed.
5055        let focus_receiver = window.add_view(cx, |_| EmptyView);
5056        focus_receiver.update(cx, |_, cx| cx.focus_self());
5057        workspace.read_with(cx, |workspace, _| {
5058            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5059            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5060        });
5061
5062        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5063        workspace.update(cx, |_, cx| cx.focus_self());
5064        workspace.read_with(cx, |workspace, _| {
5065            assert_eq!(workspace.zoomed, None);
5066            assert_eq!(workspace.zoomed_position, None);
5067        });
5068
5069        // If focus is transferred again to another view that's not a panel or a pane, we won't
5070        // show the panel as zoomed because it wasn't zoomed before.
5071        focus_receiver.update(cx, |_, cx| cx.focus_self());
5072        workspace.read_with(cx, |workspace, _| {
5073            assert_eq!(workspace.zoomed, None);
5074            assert_eq!(workspace.zoomed_position, None);
5075        });
5076
5077        // When focus is transferred back to the panel, it is zoomed again.
5078        panel_1.update(cx, |_, cx| cx.focus_self());
5079        workspace.read_with(cx, |workspace, _| {
5080            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5081            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5082        });
5083
5084        // Emitting a ZoomOut event unzooms the panel.
5085        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5086        workspace.read_with(cx, |workspace, _| {
5087            assert_eq!(workspace.zoomed, None);
5088            assert_eq!(workspace.zoomed_position, None);
5089        });
5090
5091        // Emit closed event on panel 1, which is active
5092        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5093
5094        // Now the left dock is closed, because panel_1 was the active panel
5095        workspace.read_with(cx, |workspace, cx| {
5096            let right_dock = workspace.right_dock();
5097            assert!(!right_dock.read(cx).is_open());
5098        });
5099    }
5100
5101    pub fn init_test(cx: &mut TestAppContext) {
5102        cx.foreground().forbid_parking();
5103        cx.update(|cx| {
5104            cx.set_global(SettingsStore::test(cx));
5105            theme::init((), cx);
5106            language::init(cx);
5107            crate::init_settings(cx);
5108            Project::init_settings(cx);
5109        });
5110    }
5111}