workspace.rs

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