use core::iter;
use std::fmt::Debug;
use crate::raw_operation::RawOperation;
pub fn elongate_operations<I, T>(raw_operations: I) -> Vec<RawOperation<T>>
where
I: IntoIterator<Item = RawOperation<T>>,
T: PartialEq + Clone + Debug,
{
let mut maybe_previous_insert: Option<RawOperation<T>> = None;
let mut maybe_previous_delete: Option<RawOperation<T>> = None;
let mut result: Vec<RawOperation<T>> = raw_operations
.into_iter()
.flat_map(|next| match next {
RawOperation::Insert(..) => match maybe_previous_insert.take() {
Some(prev) if prev.is_right_joinable() && next.is_left_joinable() => {
maybe_previous_insert = Some(prev.join(next));
Box::new(iter::empty()) as Box<dyn Iterator<Item = RawOperation<T>>>
}
prev => {
maybe_previous_insert = Some(next);
Box::new(prev.into_iter())
}
},
RawOperation::Delete(..) => match maybe_previous_delete.take() {
Some(prev) if prev.is_right_joinable() && next.is_left_joinable() => {
maybe_previous_delete = Some(prev.join(next));
Box::new(iter::empty()) as Box<dyn Iterator<Item = RawOperation<T>>>
}
prev => {
maybe_previous_delete = Some(next);
Box::new(prev.into_iter())
}
},
RawOperation::Equal(..) => Box::new(
maybe_previous_delete
.take()
.into_iter()
.chain(maybe_previous_insert.take())
.chain(iter::once(next)),
) as Box<dyn Iterator<Item = RawOperation<T>>>,
})
.collect();
if let Some(prev) = maybe_previous_delete {
result.push(prev);
}
if let Some(prev) = maybe_previous_insert {
result.push(prev);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tokenizer::token::Token;
fn ins(texts: &[&str]) -> RawOperation<String> {
RawOperation::Insert(texts.iter().map(|t| Token::from(*t)).collect())
}
fn del(texts: &[&str]) -> RawOperation<String> {
RawOperation::Delete(texts.iter().map(|t| Token::from(*t)).collect())
}
fn ins_custom(text: &str, lj: bool, rj: bool) -> RawOperation<String> {
RawOperation::Insert(vec![Token::new(text.to_owned(), text.to_owned(), lj, rj)])
}
#[test]
fn merges_adjacent_joinable_inserts() {
let ops = vec![ins(&["a"]), ins(&["b"]), ins(&["c"])];
let result = elongate_operations(ops);
assert_eq!(result.len(), 1);
match &result[0] {
RawOperation::Insert(tokens) => {
let originals: String = tokens
.iter()
.map(crate::tokenizer::token::Token::original)
.collect();
assert_eq!(originals, "abc");
}
_ => panic!("Expected single Insert operation"),
}
}
#[test]
fn does_not_merge_when_not_joinable() {
let ops = vec![
ins_custom("a", true, false), ins_custom("b", true, true), ];
let result = elongate_operations(ops);
assert_eq!(
result.len(),
2,
"Operations should remain separate when not joinable"
);
}
#[test]
fn merges_interleaved_insert_delete_sequences() {
let ops = vec![ins(&["i1"]), del(&["d1"]), ins(&["i2"]), del(&["d2"])];
let result = elongate_operations(ops);
assert_eq!(result.len(), 2);
assert!(matches!(result[0], RawOperation::Delete(_)));
assert!(matches!(result[1], RawOperation::Insert(_)));
}
}