use serde::{Deserialize, Serialize};
pub const UNDO_FILE_MAGIC: [u8; 4] = *b"RUND";
pub const UNDO_FILE_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UndoFileFormat {
pub version: u32,
pub original_path: String,
pub created_at: u64,
pub reovim_version: String,
pub tree: SerializableUndoTree,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableUndoTree {
pub nodes: Vec<SerializableUndoNode>,
pub current: usize,
pub seq_counter: u64,
pub max_nodes: usize,
pub active_branches: Vec<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerializableUndoNode {
pub edits: Vec<SerializableEdit>,
pub cursor_before: SerializablePosition,
pub cursor_after: SerializablePosition,
pub relative_time_secs: f64,
pub parent: Option<usize>,
pub children: Vec<usize>,
pub seq_num: u64,
#[serde(default)]
pub origin: SerializableEditOrigin,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum SerializableEditOrigin {
Client(usize),
#[default]
System,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct SerializablePosition {
pub line: usize,
pub column: usize,
}
impl SerializablePosition {
#[must_use]
pub const fn new(line: usize, column: usize) -> Self {
Self { line, column }
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum SerializableEdit {
Insert {
position: SerializablePosition,
text: String,
},
Delete {
position: SerializablePosition,
text: String,
},
}
impl UndoFileFormat {
#[must_use]
pub fn new(original_path: String, tree: SerializableUndoTree) -> Self {
use std::time::{SystemTime, UNIX_EPOCH};
Self {
version: UNDO_FILE_VERSION,
original_path,
created_at: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |d| d.as_secs()),
reovim_version: env!("CARGO_PKG_VERSION").to_string(),
tree,
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, rmp_serde::encode::Error> {
let mut bytes = UNDO_FILE_MAGIC.to_vec();
let payload = rmp_serde::to_vec(self)?;
bytes.extend(payload);
Ok(bytes)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, UndoFileError> {
if bytes.len() < 4 {
return Err(UndoFileError::TooShort);
}
if bytes[..4] != UNDO_FILE_MAGIC {
return Err(UndoFileError::InvalidMagic);
}
rmp_serde::from_slice(&bytes[4..]).map_err(UndoFileError::Deserialize)
}
}
#[derive(Debug)]
pub enum UndoFileError {
TooShort,
InvalidMagic,
Deserialize(rmp_serde::decode::Error),
Io(std::io::Error),
}
impl std::fmt::Display for UndoFileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::TooShort => write!(f, "File too short to be valid undo file"),
Self::InvalidMagic => write!(f, "Invalid undo file magic bytes"),
Self::Deserialize(e) => write!(f, "Failed to deserialize undo file: {e}"),
Self::Io(e) => write!(f, "I/O error: {e}"),
}
}
}
impl std::error::Error for UndoFileError {}
impl From<std::io::Error> for UndoFileError {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
#[cfg(test)]
#[path = "undo_tests.rs"]
mod tests;