alma 0.1.1

A Bevy-native modal text editor with Vim-style navigation.
Documentation
//! Buffer ECS components.

use crate::{
    buffer::UndoStack,
    text_stream::{TextByteStream, TextByteStreamShape, TextRevision},
};
use bevy::prelude::{Component, Entity};
use std::fmt::{Debug, Formatter};

/// Typed ECS identity for an authoritative editor buffer entity.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct BufferEntity(pub Entity);

impl BufferEntity {
    /// Returns the raw Bevy entity.
    pub const fn get(self) -> Entity {
        self.0
    }
}

/// Typed ECS identity for an editor view entity.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ViewEntity(pub Entity);

impl ViewEntity {
    /// Returns the raw Bevy entity.
    pub const fn get(self) -> Entity {
        self.0
    }
}

/// Marker for the current editor buffer entity.
#[derive(Clone, Copy, Component, Debug, Default, Eq, PartialEq)]
pub struct EditorBuffer;

/// Interactive editor view over an authoritative buffer.
#[derive(Clone, Copy, Component, Debug, Eq, PartialEq)]
pub struct EditorView {
    /// Buffer whose text and persistence state this view observes.
    pub buffer: BufferEntity,
}

/// Marker for the editor view currently receiving input and chrome sync.
#[derive(Clone, Copy, Component, Debug, Default, Eq, PartialEq)]
pub struct FocusedEditorView;

/// Authoritative text attached to an editor buffer entity.
#[derive(Clone, Component, Default, Eq, PartialEq)]
pub struct BufferText {
    /// Validated UTF-8 text stream.
    pub stream: TextByteStream,
}

impl Debug for BufferText {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter
            .debug_struct("BufferText")
            .field("shape", &BufferTextShape::from(self))
            .finish()
    }
}

/// Redacted buffer text diagnostic shape.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BufferTextShape {
    /// Redacted stream shape.
    stream: TextByteStreamShape,
}

impl BufferTextShape {
    /// Builds a redacted diagnostic shape from authoritative buffer text.
    #[must_use]
    pub fn from_buffer_text(buffer_text: &BufferText) -> Self {
        Self::from(buffer_text)
    }

    /// Returns the redacted stream shape.
    #[must_use]
    pub const fn stream(self) -> TextByteStreamShape {
        self.stream
    }
}

impl From<&BufferText> for BufferTextShape {
    fn from(buffer_text: &BufferText) -> Self {
        Self {
            stream: TextByteStreamShape::from(&buffer_text.stream),
        }
    }
}

/// Cached stream revision for systems that need cheap revision checks.
#[derive(Clone, Copy, Component, Debug, Default, Eq, PartialEq)]
pub struct BufferRevision {
    /// Monotonic buffer revision.
    pub revision: TextRevision,
}

/// Bounded undo and redo history attached to one authoritative buffer.
#[derive(Clone, Component, Debug, Default, Eq, PartialEq)]
pub struct BufferUndoHistory {
    /// Redacted undo stack. The stack stores user text but never exposes it through `Debug`.
    stack: UndoStack,
}

impl BufferUndoHistory {
    /// Returns mutable access for the buffer owner system.
    pub const fn stack_mut(&mut self) -> &mut UndoStack {
        &mut self.stack
    }

    /// Returns shared access for tests and diagnostics.
    #[must_use]
    pub const fn stack(&self) -> &UndoStack {
        &self.stack
    }
}

#[cfg(test)]
mod tests {
    use super::BufferText;
    use crate::text_stream::TextByteStream;

    #[test]
    fn buffer_text_debug_output_redacts_stream_text() {
        let buffer_text = BufferText {
            stream: TextByteStream::new("secret buffer text"),
        };
        let debug = format!("{buffer_text:?}");

        assert!(debug.contains("BufferText"));
        assert!(debug.contains("byte_len"));
        assert!(!debug.contains("secret"));
        assert!(!debug.contains("buffer text"));
    }
}