alma 0.1.1

A Bevy-native modal text editor with Vim-style navigation.
Documentation
//! Cursor movement messages.

use crate::ecs::components::{buffer::ViewEntity, cursor::ByteIndex};
use bevy::prelude::Message;

pub use super::error::CursorMoveRejectionReason;

/// Normal/visual cursor-cell coordinate.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CursorCell {
    /// Byte index of the visible cursor cell.
    byte_index: ByteIndex,
}

impl CursorCell {
    /// Creates a normal/visual cursor-cell coordinate.
    #[must_use]
    pub const fn new(byte_index: ByteIndex) -> Self {
        Self { byte_index }
    }

    /// Returns the underlying byte index.
    #[must_use]
    pub const fn byte_index(self) -> ByteIndex {
        self.byte_index
    }
}

/// Insert caret-boundary coordinate.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct InsertCaret {
    /// Byte index of the insert caret boundary.
    byte_index: ByteIndex,
}

impl InsertCaret {
    /// Creates an insert caret-boundary coordinate.
    #[must_use]
    pub const fn new(byte_index: ByteIndex) -> Self {
        Self { byte_index }
    }

    /// Returns the underlying byte index.
    #[must_use]
    pub const fn byte_index(self) -> ByteIndex {
        self.byte_index
    }
}

/// Cursor movement coordinate kind.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CursorMoveDestination {
    /// Normal/visual cursor cell.
    CursorCell(CursorCell),
    /// Insert caret boundary.
    InsertCaret(InsertCaret),
}

impl CursorMoveDestination {
    /// Creates a normal/visual cursor-cell destination.
    #[must_use]
    pub const fn cursor_cell(byte_index: ByteIndex) -> Self {
        Self::CursorCell(CursorCell::new(byte_index))
    }

    /// Creates an insert caret-boundary destination.
    #[must_use]
    pub const fn insert_caret(byte_index: ByteIndex) -> Self {
        Self::InsertCaret(InsertCaret::new(byte_index))
    }

    /// Returns the destination byte index independent of coordinate kind.
    #[must_use]
    pub const fn byte_index(self) -> ByteIndex {
        match self {
            Self::CursorCell(cell) => cell.byte_index(),
            Self::InsertCaret(caret) => caret.byte_index(),
        }
    }
}

/// Cursor move request. The destination names the coordinate invariant.
#[derive(Clone, Copy, Debug, Eq, Message, PartialEq)]
pub struct CursorMoveRequested {
    /// View to move.
    pub target: ViewEntity,
    /// Requested coordinate kind.
    pub destination: CursorMoveDestination,
    /// Desired column for repeated vertical movement.
    pub desired_column: Option<usize>,
}

impl CursorMoveRequested {
    /// Builds a request to move a normal/visual cursor cell.
    #[must_use]
    pub const fn cursor_cell(
        target: ViewEntity,
        byte_index: ByteIndex,
        desired_column: Option<usize>,
    ) -> Self {
        Self {
            target,
            destination: CursorMoveDestination::cursor_cell(byte_index),
            desired_column,
        }
    }

    /// Builds a request to move an insert caret boundary.
    #[must_use]
    pub const fn insert_caret(
        target: ViewEntity,
        byte_index: ByteIndex,
        desired_column: Option<usize>,
    ) -> Self {
        Self {
            target,
            destination: CursorMoveDestination::insert_caret(byte_index),
            desired_column,
        }
    }
}

/// Notification that the editor cursor moved.
#[derive(Clone, Copy, Debug, Eq, Message, PartialEq)]
pub struct CursorMoved {
    /// View that moved.
    pub target: ViewEntity,
    /// Previous cursor byte index.
    pub previous_byte_index: ByteIndex,
    /// Current cursor byte index.
    pub current_byte_index: ByteIndex,
    /// Preferred character column after movement.
    pub desired_column: Option<usize>,
}

/// Notification that a cursor movement request was rejected at the owner boundary.
#[derive(Clone, Copy, Debug, Eq, Message, PartialEq)]
pub struct CursorMoveRejected {
    /// View that could not apply the move.
    pub target: ViewEntity,
    /// Rejected move shape.
    pub rejected: CursorMoveRequestShape,
    /// Boundary rejection reason.
    pub reason: CursorMoveRejectionReason,
}

/// Redacted cursor move shape.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CursorMoveRequestShape {
    /// Requested coordinate kind.
    pub destination: CursorMoveDestination,
    /// Requested UTF-8 byte index.
    pub byte_index: ByteIndex,
    /// Desired column for repeated vertical movement.
    pub desired_column: Option<usize>,
}

impl From<&CursorMoveRequested> for CursorMoveRequestShape {
    fn from(request: &CursorMoveRequested) -> Self {
        Self {
            destination: request.destination,
            byte_index: request.destination.byte_index(),
            desired_column: request.desired_column,
        }
    }
}