alma 0.1.1

A Bevy-native modal text editor with Vim-style navigation.
Documentation
use super::display::EditShape;
use crate::{
    buffer::BufferEditShape,
    ecs::events::edit::{
        PluginBufferEditProposalReceipt, PluginBufferEditProposalReceiptOutcome,
        PluginBufferEditProposalReceiptOwner, PluginBufferEditProposalReceiptRejectionReason,
        PluginBufferProposalCapability,
    },
    text_stream::TextRevision,
};
use std::fmt::{Display, Formatter};

/// Redacted proposal receipt trace payload.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PluginRuntimeTraceProposalReceipt {
    /// Host proposal id.
    proposal: u64,
    /// Capability used to submit the proposal.
    capability: PluginBufferProposalCapability,
    /// Target shape.
    target: PluginRuntimeTraceTarget,
    /// Observed base revision.
    base_revision: TextRevision,
    /// Redacted proposed edit shape.
    proposed: BufferEditShape,
    /// Owner-boundary outcome.
    outcome: PluginRuntimeTraceProposalOutcome,
}

impl PluginRuntimeTraceProposalReceipt {
    /// Returns the host proposal id.
    #[must_use]
    pub const fn proposal(&self) -> u64 {
        self.proposal
    }

    /// Returns the proposal capability.
    #[must_use]
    pub const fn capability(&self) -> PluginBufferProposalCapability {
        self.capability
    }

    /// Returns the target shape.
    #[must_use]
    pub const fn target(&self) -> PluginRuntimeTraceTarget {
        self.target
    }

    /// Returns the observed base revision.
    #[must_use]
    pub const fn base_revision(&self) -> TextRevision {
        self.base_revision
    }

    /// Returns the redacted proposed edit shape.
    #[must_use]
    pub const fn proposed(&self) -> &BufferEditShape {
        &self.proposed
    }

    /// Returns the owner-boundary outcome.
    #[must_use]
    pub const fn outcome(&self) -> &PluginRuntimeTraceProposalOutcome {
        &self.outcome
    }
}

impl From<&PluginBufferEditProposalReceipt> for PluginRuntimeTraceProposalReceipt {
    fn from(receipt: &PluginBufferEditProposalReceipt) -> Self {
        Self {
            proposal: receipt.proposal().get(),
            capability: receipt.capability(),
            target: PluginRuntimeTraceTarget::Buffer,
            base_revision: receipt.base_revision(),
            proposed: receipt.proposed().clone(),
            outcome: PluginRuntimeTraceProposalOutcome::from(receipt.outcome()),
        }
    }
}

impl Display for PluginRuntimeTraceProposalReceipt {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            formatter,
            "proposal={} capability={} target={} base_revision={} proposed={} outcome={}",
            self.proposal,
            self.capability.as_str(),
            self.target,
            self.base_revision,
            EditShape(&self.proposed),
            self.outcome
        )
    }
}

/// Redacted owner target shape.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PluginRuntimeTraceTarget {
    /// Buffer owner target.
    Buffer,
}

impl PluginRuntimeTraceTarget {
    /// Stable target class text.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Buffer => "buffer",
        }
    }
}

impl Display for PluginRuntimeTraceTarget {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(self.as_str())
    }
}

/// Redacted proposal receipt outcome.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PluginRuntimeTraceProposalOutcome {
    /// Buffer owner applied the proposal.
    Applied {
        /// Revision before mutation.
        previous_revision: TextRevision,
        /// Revision after mutation.
        current_revision: TextRevision,
    },
    /// Proposal was rejected at an owner boundary.
    Rejected {
        /// Boundary that made the decision.
        owner: PluginRuntimeTraceProposalOwner,
        /// Closed rejection shape.
        reason: PluginRuntimeTraceProposalRejection,
    },
}

impl From<&PluginBufferEditProposalReceiptOutcome> for PluginRuntimeTraceProposalOutcome {
    fn from(outcome: &PluginBufferEditProposalReceiptOutcome) -> Self {
        match outcome {
            PluginBufferEditProposalReceiptOutcome::Applied {
                previous_revision,
                current_revision,
            } => Self::Applied {
                previous_revision: previous_revision.revision,
                current_revision: current_revision.revision,
            },
            PluginBufferEditProposalReceiptOutcome::Rejected { owner, reason } => Self::Rejected {
                owner: PluginRuntimeTraceProposalOwner::from(*owner),
                reason: PluginRuntimeTraceProposalRejection::from(reason),
            },
        }
    }
}

impl Display for PluginRuntimeTraceProposalOutcome {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Applied {
                previous_revision,
                current_revision,
            } => write!(
                formatter,
                "applied previous_revision={previous_revision} current_revision={current_revision}"
            ),
            Self::Rejected { owner, reason } => {
                write!(formatter, "rejected owner={owner} reason={reason}")
            }
        }
    }
}

/// Boundary that decided a proposal.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PluginRuntimeTraceProposalOwner {
    /// Proposal lane rejected before buffer mutation.
    ProposalLane,
    /// Buffer owner rejected during mutation validation.
    BufferOwner,
}

impl PluginRuntimeTraceProposalOwner {
    /// Stable owner class text.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::ProposalLane => "proposal-lane",
            Self::BufferOwner => "buffer-owner",
        }
    }
}

impl From<PluginBufferEditProposalReceiptOwner> for PluginRuntimeTraceProposalOwner {
    fn from(owner: PluginBufferEditProposalReceiptOwner) -> Self {
        match owner {
            PluginBufferEditProposalReceiptOwner::ProposalLane => Self::ProposalLane,
            PluginBufferEditProposalReceiptOwner::BufferOwner => Self::BufferOwner,
        }
    }
}

impl Display for PluginRuntimeTraceProposalOwner {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(self.as_str())
    }
}

/// Closed proposal rejection shape.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PluginRuntimeTraceProposalRejection {
    /// User or policy rejected before owner mutation.
    RejectedBeforeOwner,
    /// Target buffer was missing.
    MissingTarget,
    /// Proposed edit failed text invariants.
    InvalidEdit,
    /// Target buffer changed since observation.
    StaleRevision {
        /// Observed revision.
        expected: TextRevision,
        /// Owner-boundary revision.
        actual: TextRevision,
    },
}

impl PluginRuntimeTraceProposalRejection {
    /// Stable rejection class text.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::RejectedBeforeOwner => "rejected-before-owner",
            Self::MissingTarget => "missing-target",
            Self::InvalidEdit => "invalid-edit",
            Self::StaleRevision { .. } => "stale-revision",
        }
    }
}

impl From<&PluginBufferEditProposalReceiptRejectionReason> for PluginRuntimeTraceProposalRejection {
    fn from(reason: &PluginBufferEditProposalReceiptRejectionReason) -> Self {
        match reason {
            PluginBufferEditProposalReceiptRejectionReason::RejectedBeforeOwner => {
                Self::RejectedBeforeOwner
            }
            PluginBufferEditProposalReceiptRejectionReason::MissingTarget => Self::MissingTarget,
            PluginBufferEditProposalReceiptRejectionReason::InvalidEdit { .. } => Self::InvalidEdit,
            PluginBufferEditProposalReceiptRejectionReason::StaleRevision { expected, actual } => {
                Self::StaleRevision {
                    expected: *expected,
                    actual: *actual,
                }
            }
        }
    }
}

impl Display for PluginRuntimeTraceProposalRejection {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::StaleRevision { expected, actual } => {
                write!(
                    formatter,
                    "stale-revision expected={expected} actual={actual}"
                )
            }
            _ => formatter.write_str(self.as_str()),
        }
    }
}