tastty-driver 0.1.0

Terminal automation driver built on tastty
//! Result types for [`Session::find`](crate::Session::find).

use std::time::Duration;

use tastty::AbsolutePosition;

/// One match returned by [`Session::find`](crate::Session::find).
///
/// The position pair follows the same inclusive convention as
/// [`SelectionRange`](tastty::Screen): `start` is the lead-half cell
/// of the first matched character; `end` is the lead-half cell of
/// the last matched character. A single-cell match has
/// `start == end`.
#[non_exhaustive]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SearchMatch {
    /// Absolute position of the first cell covered by the match.
    pub start: AbsolutePosition,
    /// Absolute position of the last cell covered by the match
    /// (inclusive).
    pub end: AbsolutePosition,
    /// The matched text, materialized from the cells covered by the
    /// match. Wide-cell continuations are skipped.
    pub text: String,
    /// Capture groups, in regex-group-number order starting at 1.
    /// Empty for [`SearchPattern::Literal`](crate::SearchPattern::Literal) patterns and for regex
    /// patterns without capture groups. Index 0 (the whole match) is
    /// represented by [`SearchMatch::start`] / [`SearchMatch::end`]
    /// and is never repeated here.
    pub captures: Vec<Capture>,
}

/// One regex capture group.
///
/// A capture group whose regex parse opted out of the match (e.g. an
/// alternation arm that did not fire) is omitted from
/// [`SearchMatch::captures`] entirely; absent groups are not
/// represented as `None`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Capture {
    /// Capture name when the group was named (`(?P<foo>...)` or
    /// `(?<foo>...)`); `None` for positional groups.
    pub name: Option<String>,
    /// Index of the capture group in the regex (1-based).
    pub index: usize,
    /// Absolute position of the first cell covered by the capture.
    pub start: AbsolutePosition,
    /// Absolute position of the last cell covered by the capture
    /// (inclusive).
    pub end: AbsolutePosition,
    /// The captured text, materialized from the cells covered by the
    /// capture.
    pub text: String,
}

/// Errors produced by [`SearchPattern`](crate::SearchPattern) construction and
/// [`Session::find`](crate::Session::find).
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum SearchError {
    /// The supplied regex failed to compile. Also surfaces the
    /// regex crate's `CompiledTooBig` when the configured
    /// [`SearchOptions::nfa_size_limit`](crate::SearchOptions::nfa_size_limit) rejects the pattern.
    #[error("invalid regex pattern '{pattern}': {source}")]
    InvalidRegex {
        /// The pattern that failed to compile.
        pattern: String,
        /// Diagnostic from the regex engine.
        #[source]
        source: crate::InvalidRegexSource,
    },
    /// The pattern was empty. Empty patterns match at every position
    /// and are rarely what the caller meant.
    #[error("search pattern is empty")]
    EmptyPattern,
    /// The configured [`SearchOptions::deadline`](crate::SearchOptions::deadline) elapsed before the
    /// scan finished. Any partial matches collected before expiration
    /// are dropped; a truncated result vector cannot be distinguished
    /// from a complete one.
    #[error("search budget of {budget:?} exceeded after {elapsed:?}")]
    DeadlineExceeded {
        /// The budget the caller configured via
        /// [`SearchOptions::deadline`](crate::SearchOptions::deadline).
        budget: Duration,
        /// Wall-clock time elapsed at the point of detection. May
        /// exceed `budget` slightly because the check is per
        /// logical-line scan, not per-byte.
        elapsed: Duration,
    },
}