use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Confidence {
Authoritative,
Heuristic,
}
impl Confidence {
pub(crate) fn label(self) -> &'static str {
match self {
Confidence::Authoritative => "AUTHORITATIVE",
Confidence::Heuristic => "HEURISTIC",
}
}
pub(crate) fn json(self) -> &'static str {
match self {
Confidence::Authoritative => "authoritative",
Confidence::Heuristic => "heuristic",
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Boundary {
pub(crate) line_no: usize,
pub(crate) turn_index: usize,
pub(crate) timestamp_utc: Option<String>,
pub(crate) kind: &'static str,
pub(crate) confidence: Confidence,
pub(crate) detail: String,
}
#[derive(Debug, Default, Clone)]
pub(crate) struct EventCounts {
pub(crate) read_full: usize,
pub(crate) read_windowed: usize,
pub(crate) edit: usize,
pub(crate) edit_unanchorable: usize,
pub(crate) write: usize,
pub(crate) bash: usize,
pub(crate) external_edit: usize,
pub(crate) history_snapshot: usize,
pub(crate) integrity_error: usize,
pub(crate) stale_hint: usize,
pub(crate) stale_recovered: usize,
pub(crate) bash_read_anchor: usize,
pub(crate) bash_write_anchor: usize,
pub(crate) snapshot_rebase: usize,
pub(crate) blanked_read: usize,
}
#[derive(Debug, Clone)]
pub(crate) struct Segment {
pub(crate) index: usize,
pub(crate) line_no_start: usize,
pub(crate) line_no_end: usize,
pub(crate) turn_start: usize,
pub(crate) turn_end: usize,
pub(crate) ts_start: Option<String>,
pub(crate) ts_end: Option<String>,
pub(crate) end_buffer: SparseBuffer,
pub(crate) start_buffer: SparseBuffer,
pub(crate) pre_state_known: bool,
pub(crate) anchor_source: Option<SnapSource>,
}
#[derive(Debug, Default)]
pub(crate) struct Replay {
pub(crate) segments: Vec<Segment>,
pub(crate) boundaries: Vec<Boundary>,
pub(crate) counts: EventCounts,
pub(crate) final_buffer: SparseBuffer,
pub(crate) coverage_holes: Vec<(usize, usize, usize)>, }
pub(crate) fn blanked_read_boundary(e: &FileEvent, total_lines: Option<usize>) -> Boundary {
Boundary {
line_no: e.line_no,
turn_index: e.turn_index,
timestamp_utc: e.timestamp_utc.clone(),
kind: "blanked_read",
confidence: Confidence::Authoritative,
detail: match total_lines {
Some(n) => format!(
"a Read echo with its content blanked by the harness ({n} line(s) at \
the time); nothing is replayed from it"
),
None => {
"a contentless Read echo (no text arm); nothing is replayed from it".to_string()
}
},
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SnapAction {
Rebase,
ContentlessBoundary,
Nothing,
}
pub(crate) fn snapshot_action(
last_version: Option<u64>,
version: Option<u64>,
content: Option<&str>,
buf: &SparseBuffer,
writes_since_marker: bool,
) -> SnapAction {
if let Some(snap_content) = content {
let snap_lines = split_lines(snap_content);
let known_disagrees = buf
.known
.iter()
.any(|(n, cell)| snap_lines.get(n - 1).is_none_or(|l| *l != cell.text));
let complete_len_differs = buf
.seen_total_lines
.is_some_and(|t| buf.known.len() == t && t != snap_lines.len());
if !buf.known.is_empty() && (known_disagrees || complete_len_differs) {
return SnapAction::Rebase;
}
return SnapAction::Nothing;
}
let jumped = matches!((last_version, version), (Some(p), Some(v)) if v > p);
let reset = matches!((last_version, version), (Some(p), Some(v)) if v < p);
if jumped && !reset && !writes_since_marker {
SnapAction::ContentlessBoundary
} else {
SnapAction::Nothing
}
}