use crate::position::Position;
#[derive(Debug, Clone, PartialEq)]
pub struct IncrementalEdit {
pub start_byte: usize,
pub old_end_byte: usize,
pub new_text: String,
pub start_position: Position,
pub old_end_position: Position,
}
impl IncrementalEdit {
pub fn new(start_byte: usize, old_end_byte: usize, new_text: String) -> Self {
IncrementalEdit {
start_byte,
old_end_byte,
new_text,
start_position: Position::new(start_byte, 0, 0),
old_end_position: Position::new(old_end_byte, 0, 0),
}
}
pub fn with_positions(
start_byte: usize,
old_end_byte: usize,
new_text: String,
start_position: Position,
old_end_position: Position,
) -> Self {
IncrementalEdit { start_byte, old_end_byte, new_text, start_position, old_end_position }
}
pub fn new_end_byte(&self) -> usize {
self.start_byte + self.new_text.len()
}
pub fn byte_shift(&self) -> isize {
self.new_text.len() as isize - (self.old_end_byte - self.start_byte) as isize
}
pub fn overlaps(&self, start: usize, end: usize) -> bool {
self.start_byte < end && self.old_end_byte > start
}
pub fn is_before(&self, pos: usize) -> bool {
self.old_end_byte <= pos
}
pub fn is_after(&self, pos: usize) -> bool {
self.start_byte >= pos
}
}
#[derive(Debug, Clone, Default)]
pub struct IncrementalEditSet {
pub edits: Vec<IncrementalEdit>,
}
impl IncrementalEditSet {
pub fn new() -> Self {
IncrementalEditSet { edits: Vec::new() }
}
pub fn add(&mut self, edit: IncrementalEdit) {
self.edits.push(edit);
}
pub fn sort(&mut self) {
self.edits.sort_by_key(|e| e.start_byte);
}
pub fn sort_reverse(&mut self) {
self.edits.sort_by_key(|e| std::cmp::Reverse(e.start_byte));
}
pub fn is_empty(&self) -> bool {
self.edits.is_empty()
}
pub fn total_byte_shift(&self) -> isize {
self.edits.iter().map(|e| e.byte_shift()).sum()
}
pub fn apply_to_string(&self, source: &str) -> String {
if self.edits.is_empty() {
return source.to_string();
}
let mut sorted_edits = self.edits.clone();
sorted_edits.sort_by_key(|e| std::cmp::Reverse(e.start_byte));
let mut result = source.to_string();
for edit in &sorted_edits {
result.replace_range(edit.start_byte..edit.old_end_byte, &edit.new_text);
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_incremental_edit_basic() {
let edit = IncrementalEdit::new(5, 10, "hello".to_string());
assert_eq!(edit.new_end_byte(), 10);
assert_eq!(edit.byte_shift(), 0);
}
#[test]
fn test_incremental_edit_insertion() {
let edit = IncrementalEdit::new(5, 5, "inserted".to_string());
assert_eq!(edit.new_end_byte(), 13);
assert_eq!(edit.byte_shift(), 8);
}
#[test]
fn test_incremental_edit_deletion() {
let edit = IncrementalEdit::new(5, 15, "".to_string());
assert_eq!(edit.new_end_byte(), 5);
assert_eq!(edit.byte_shift(), -10);
}
#[test]
fn test_incremental_edit_replacement() {
let edit = IncrementalEdit::new(5, 10, "replaced".to_string());
assert_eq!(edit.new_end_byte(), 13);
assert_eq!(edit.byte_shift(), 3);
}
#[test]
fn test_edit_set_apply() {
let mut edits = IncrementalEditSet::new();
edits.add(IncrementalEdit::new(0, 5, "Hello".to_string()));
edits.add(IncrementalEdit::new(6, 11, "Perl".to_string()));
let source = "hello world";
let result = edits.apply_to_string(source);
assert_eq!(result, "Hello Perl");
}
}