lupa 0.1.1

Interactive object inspector for Rust — web UI + TUI + snapshot diffing
Documentation
//! Central storage for all snapshots and diffs collected during program execution.
//!
//! This module defines the core data structures (`Snapshot`, `DiffEvent`) and
//! a thread‑safe global `InspectorState` that holds them. Both the web server
//! and the TUI read from this state. The state is append‑only: once a snapshot
//! or diff is added, it never changes. This guarantees that all clients see a
//! consistent, growing history.
//!
//! The global instance is lazy‑initialised with `once_cell::sync::Lazy` and
//! can be accessed from anywhere via `INSPECTOR_STATE`.

use crate::diff::DiffChunk;
use serde::{Deserialize, Serialize};
use std::sync::Mutex;
use once_cell::sync::Lazy;

/// A captured debug representation of a value at a specific point in time.
///
/// This is the fundamental unit of inspection. Each call to `inspect!` or
/// `snapshot!` creates a `Snapshot`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Snapshot {
    /// The source expression that was captured (e.g. `"user"` or `"my_struct.field"`).
    pub label: String,
    /// The full debug output produced by `{:#?}`.
    pub debug_repr: String,
    /// Path to the source file where the snapshot was taken.
    pub file: String,
    /// Line number in that source file.
    pub line: u32,
    /// Millisecond‑precision UNIX timestamp when the snapshot was created.
    pub timestamp_ms: u64,
}

impl Snapshot {
    /// Creates a new snapshot from the raw components.
    ///
    /// This method is called by the `snapshot!` macro. It automatically
    /// fills the current timestamp.
    ///
    /// # Arguments
    /// * `label` – Human‑readable name (usually the expression itself)
    /// * `debug_repr` – The `{:#?}` output of the value
    /// * `file` – `file!()` from the macro call site
    /// * `line` – `line!()` from the macro call site
    pub fn capture(label: &str, debug_repr: String, file: &str, line: u32) -> Self {
        let timestamp_ms = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_millis() as u64)
            .unwrap_or(0);
        Self {
            label: label.to_string(),
            debug_repr,
            file: file.to_string(),
            line,
            timestamp_ms,
        }
    }
}

/// A diff between two snapshots, ready for display.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiffEvent {
    /// The older snapshot (before changes).
    pub old: Snapshot,
    /// The newer snapshot (after changes).
    pub new: Snapshot,
    /// The computed line‑by‑line difference.
    pub chunks: Vec<DiffChunk>,
}

/// Thread‑safe container for the entire inspection history.
///
/// Internally it uses two `Mutex`es – one for snapshots and one for diffs.
/// All operations are `O(1)` amortised (append only). The mutexes are held
/// only for the duration of the push or the clone of the whole vector.
#[derive(Default)]
pub struct InspectorState {
    snapshots: Mutex<Vec<Snapshot>>,
    diffs: Mutex<Vec<DiffEvent>>,
}

impl InspectorState {
    /// Creates a new, empty inspector state.
    pub fn new() -> Self {
        Self::default()
    }

    /// Appends a new snapshot to the history.
    pub fn push_snapshot(&self, snap: Snapshot) {
        self.snapshots.lock().unwrap().push(snap);
    }

    /// Appends a new diff event to the history.
    ///
    /// # Arguments
    /// * `old` – The snapshot before the change.
    /// * `new` – The snapshot after the change.
    /// * `chunks` – The diff chunks (usually obtained from `diff_snapshots`).
    pub fn push_diff(&self, old: Snapshot, new: Snapshot, chunks: Vec<DiffChunk>) {
        self.diffs.lock().unwrap().push(DiffEvent { old, new, chunks });
    }

    /// Returns a copy of all snapshots (in chronological order).
    pub fn snapshots(&self) -> Vec<Snapshot> {
        self.snapshots.lock().unwrap().clone()
    }

    /// Returns a copy of all diff events (in chronological order).
    pub fn diffs(&self) -> Vec<DiffEvent> {
        self.diffs.lock().unwrap().clone()
    }
}

/// Global, lazily initialised inspector state.
///
/// This is the single source of truth for all collected inspection data.
/// Both the web server (to serve the JSON API and WebSocket updates) and the
/// TUI (to render the current state) read from this instance.
pub static INSPECTOR_STATE: Lazy<InspectorState> = Lazy::new(InspectorState::new);