diff --git a/src/operation_transformation.rs b/src/operation_transformation.rs index 5e00ae6..777c2f0 100644 --- a/src/operation_transformation.rs +++ b/src/operation_transformation.rs @@ -1,7 +1,6 @@ mod cursor; mod edited_text; mod operation; -mod ordered_operation; mod utils; pub use cursor::{CursorPosition, TextWithCursors}; diff --git a/src/operation_transformation/edited_text.rs b/src/operation_transformation/edited_text.rs index d081c27..090d965 100644 --- a/src/operation_transformation/edited_text.rs +++ b/src/operation_transformation/edited_text.rs @@ -1,13 +1,13 @@ #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use super::{CursorPosition, Operation, TextWithCursors, ordered_operation::OrderedOperation}; +use super::{CursorPosition, Operation, TextWithCursors}; use crate::{ diffs::{myers::diff, raw_operation::RawOperation}, operation_transformation::utils::{ cook_operations::cook_operations, elongate_operations::elongate_operations, }, - tokenizer::{Tokenizer, word_tokenizer::word_tokenizer}, + tokenizer::{word_tokenizer::word_tokenizer, Tokenizer}, utils::{side::Side, string_builder::StringBuilder}, }; @@ -30,7 +30,7 @@ where T: PartialEq + Clone + std::fmt::Debug, { text: &'a str, - operations: Vec>, + operations: Vec>, pub(crate) cursors: Vec, } @@ -76,11 +76,7 @@ where /// Create a new `EditedText` with the given operations. /// The operations must be in the order in which they are meant to be /// applied. The operations must not overlap. - fn new( - text: &'a str, - operations: Vec>, - mut cursors: Vec, - ) -> Self { + fn new(text: &'a str, operations: Vec>, mut cursors: Vec) -> Self { cursors.sort_by_key(|cursor| cursor.char_index); Self { @@ -92,7 +88,6 @@ where #[must_use] pub fn merge(self, other: Self) -> Self { - println!("\n\n\n\n\n\n----\n"); debug_assert_eq!( self.text, other.text, "`EditedText`-s must be derived from the same text to be mergable" @@ -102,7 +97,7 @@ where let mut left_cursors = self.cursors.into_iter().peekable(); let mut right_cursors = other.cursors.into_iter().peekable(); - let mut merged_operations: Vec> = + let mut merged_operations: Vec> = Vec::with_capacity(self.operations.len() + other.operations.len()); let mut left_iter = self.operations.into_iter(); @@ -119,38 +114,24 @@ where let mut last_right_op = None; loop { - let ( - side, - OrderedOperation { operation, order }, - maybe_other_operation, - mut last_other_op, - ) = match (maybe_left_op.clone(), maybe_right_op.clone()) { - (Some(left_op), Some(right_op)) => { - if left_op - .get_sort_key(seen_left_length) - .partial_cmp(&right_op.get_sort_key(seen_right_length)) - == Some(std::cmp::Ordering::Less) - { - ( - Side::Left, - left_op, - maybe_right_op.clone(), - last_right_op.clone(), - ) - } else { - ( - Side::Right, - right_op, - maybe_left_op.clone(), - last_left_op.clone(), - ) + let (side, operation, mut last_other_op) = + match (maybe_left_op.clone(), maybe_right_op.clone()) { + (Some(left_op), Some(right_op)) => { + if left_op + .get_sort_key(seen_left_length) + .partial_cmp(&right_op.get_sort_key(seen_right_length)) + == Some(std::cmp::Ordering::Less) + { + (Side::Left, left_op, last_right_op.clone()) + } else { + (Side::Right, right_op, last_left_op.clone()) + } } - } - (Some(left_op), None) => (Side::Left, left_op, None, last_right_op.clone()), - (None, Some(right_op)) => (Side::Right, right_op, None, last_left_op.clone()), - (None, None) => break, - }; + (Some(left_op), None) => (Side::Left, left_op, last_right_op.clone()), + (None, Some(right_op)) => (Side::Right, right_op, last_left_op.clone()), + (None, None) => break, + }; let is_advancing_operation = matches!( operation, @@ -161,39 +142,12 @@ where let original_length = operation.len() as i64; let result = match side { Side::Left => { - let result = operation.merge_operations_with_context(order, &mut last_other_op); + let result = operation.merge_operations(&mut last_other_op); - if let ref op @ (OrderedOperation { - operation: Operation::Insert { .. }, - .. - } - | OrderedOperation { - operation: Operation::Equal { .. }, - .. - }) = result - { - println!( - "merrged_length: {}, seen_left_length: {}, op len {}, original_length \ - {}", - merged_length, - seen_left_length, - op.operation.len(), - original_length - ); - let mut shift = merged_length as i64 - // - last_other_op - // .map(|op| op.operation.len() as i64) - // .unwrap_or(0) as i64 - - seen_left_length as i64; - // if !matches!( - // op, - // OrderedOperation { - // operation: Operation::Equal { .. }, - // .. - // } - // ) { - shift += op.operation.len() as i64 - original_length; - // } + if let ref op @ (Operation::Insert { .. } | Operation::Equal { .. }) = result { + let shift = merged_length as i64 - seen_left_length as i64 + + op.len() as i64 + - original_length; while let Some(cursor) = left_cursors.next_if(|cursor| { cursor.char_index <= seen_left_length + original_length as usize @@ -215,39 +169,12 @@ where result } Side::Right => { - let result = operation.merge_operations_with_context(order, &mut last_other_op); + let result = operation.merge_operations(&mut last_other_op); - if let ref op @ (OrderedOperation { - operation: Operation::Insert { .. }, - .. - } - | OrderedOperation { - operation: Operation::Equal { .. }, - .. - }) = result - { - println!( - "merrged_length: {}, seen_left_length: {}, op len {}, original_length \ - {}", - merged_length, - seen_left_length, - op.operation.len(), - original_length - ); - let mut shift = merged_length as i64 - // - last_other_op - // .map(|op| op.operation.len() as i64) - // .unwrap_or(0) as i64 - - seen_right_length as i64; - // if !matches!( - // op, - // OrderedOperation { - // operation: Operation::Equal { .. }, - // .. - // } - // ) { - shift += op.operation.len() as i64 - original_length; - // } + if let ref op @ (Operation::Insert { .. } | Operation::Equal { .. }) = result { + let shift = merged_length as i64 - seen_right_length as i64 + + op.len() as i64 + - original_length; while let Some(cursor) = right_cursors.next_if(|cursor| { cursor.char_index <= seen_right_length + original_length as usize @@ -273,12 +200,12 @@ where println!(" = {result:?}"); - if result.operation.len() == 0 { + if result.len() == 0 { continue; } if is_advancing_operation { - merged_length += result.operation.len(); + merged_length += result.len(); } merged_operations.push(result); @@ -296,9 +223,7 @@ where pub fn apply(&self) -> String { let mut builder: StringBuilder<'_> = StringBuilder::new(self.text); - for OrderedOperation { operation, .. } in &self.operations { - println!("applying operation {operation:?}"); - + for operation in &self.operations { builder = operation.apply(builder); } diff --git a/src/operation_transformation/operation.rs b/src/operation_transformation/operation.rs index c0b2f5c..63702cd 100644 --- a/src/operation_transformation/operation.rs +++ b/src/operation_transformation/operation.rs @@ -1,12 +1,10 @@ use core::fmt::{Debug, Display}; -use std::ops::Range; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::{ Token, - operation_transformation::ordered_operation::OrderedOperation, utils::{ find_longest_prefix_contained_within::find_longest_prefix_contained_within, string_builder::StringBuilder, @@ -21,6 +19,7 @@ where T: PartialEq + Clone + std::fmt::Debug, { Equal { + order: usize, length: usize, #[cfg(debug_assertions)] @@ -28,10 +27,12 @@ where }, Insert { + order: usize, text: Vec>, }, Delete { + order: usize, deleted_character_count: usize, #[cfg(debug_assertions)] @@ -46,8 +47,9 @@ where /// Creates an equal operation with the given index. /// This operation is used to indicate that the text at the given index /// is unchanged. - pub fn create_equal(length: usize) -> Self { + pub fn create_equal(order: usize, length: usize) -> Self { Operation::Equal { + order, length, #[cfg(debug_assertions)] @@ -55,8 +57,9 @@ where } } - pub fn create_equal_with_text(text: String) -> Self { + pub fn create_equal_with_text(order: usize, text: String) -> Self { Operation::Equal { + order, length: text.chars().count(), #[cfg(debug_assertions)] @@ -65,12 +68,15 @@ where } /// Creates an insert operation with the given index and text. - pub fn create_insert(text: Vec>) -> Self { Operation::Insert { text } } + pub fn create_insert(order: usize, text: Vec>) -> Self { + Operation::Insert { order, text } + } /// Creates a delete operation with the given index and number of /// to-be-deleted characters. - pub fn create_delete(deleted_character_count: usize) -> Self { + pub fn create_delete(order: usize, deleted_character_count: usize) -> Self { Operation::Delete { + order, deleted_character_count, #[cfg(debug_assertions)] @@ -78,8 +84,9 @@ where } } - pub fn create_delete_with_text(text: String) -> Self { + pub fn create_delete_with_text(order: usize, text: String) -> Self { Operation::Delete { + order, deleted_character_count: text.chars().count(), #[cfg(debug_assertions)] @@ -87,6 +94,38 @@ where } } + fn order(&self) -> usize { + match self { + Operation::Equal { order, .. } => *order, + Operation::Insert { order, .. } => *order, + Operation::Delete { order, .. } => *order, + } + } + + pub fn get_sort_key(&self, insertion_index: usize) -> (usize, usize, usize, String) { + ( + self.order(), + match self { + Operation::Delete { .. } => 1, + Operation::Insert { .. } => 2, + Operation::Equal { .. } => 3, + }, + insertion_index, + // Make sure that the ordering is deterministic regardless of which text + // is left or right. + match self { + Operation::Equal { length, .. } => length.to_string(), + Operation::Insert { text, .. } => { + text.iter().map(Token::original).collect::() + } + Operation::Delete { + deleted_character_count, + .. + } => deleted_character_count.to_string(), + }, + ) + } + /// Applies the operation to the given `StringBuilder`, returning the /// modified `StringBuilder`. /// @@ -156,23 +195,14 @@ where /// the merging of operations in a way that is consistent with the text. /// The contexts are updated in-place. #[allow(clippy::too_many_lines)] - pub fn merge_operations_with_context( - self, - order: usize, - previous_operation: &mut Option>, - ) -> OrderedOperation { - println!("mergin: {self} (order {order}) - previous: {previous_operation:?}"); + pub fn merge_operations(self, previous_operation: &mut Option) -> Operation { let operation = self; match (operation, previous_operation) { ( - Operation::Insert { text }, - Some(OrderedOperation { - operation: - Operation::Insert { - text: previous_inserted_text, - .. - }, + Operation::Insert { order, text }, + Some(Operation::Insert { + text: previous_inserted_text, .. }), ) => { @@ -182,29 +212,26 @@ where let offset_in_tokens = find_longest_prefix_contained_within(previous_inserted_text, &text); - let trimmed_operation = Operation::create_insert(text[offset_in_tokens..].to_vec()); - - OrderedOperation { - order, - operation: trimmed_operation, - } + Operation::create_insert(order, text[offset_in_tokens..].to_vec()) } ( Operation::Delete { + order, + deleted_character_count, + #[cfg(debug_assertions)] deleted_text, - deleted_character_count, }, - Some( - last_delete @ OrderedOperation { - operation: Operation::Delete { .. }, - .. - }, - ), + Some(Operation::Delete { + order: last_delete_order, + deleted_character_count: last_delete_deleted_character_count, + .. + }), ) => { let operation_end_index = order + deleted_character_count; - let last_delete_end_index = last_delete.order + last_delete.operation.len(); + let last_delete_end_index = + *last_delete_order + *last_delete_deleted_character_count; let new_length = deleted_character_count .min(0.max(operation_end_index as i64 - last_delete_end_index as i64) as usize); @@ -213,9 +240,10 @@ where #[cfg(debug_assertions)] let updated_delete = deleted_text.as_ref().map_or_else( - || Operation::create_delete(new_length), + || Operation::create_delete(order + overlap, new_length), |text| { Operation::create_delete_with_text( + order + overlap, text.chars() .skip((deleted_character_count - new_length) as usize) .collect::(), @@ -224,72 +252,72 @@ where ); #[cfg(not(debug_assertions))] - let updated_delete = Operation::create_delete(new_length); + let updated_delete = Operation::create_delete(order + overlap, new_length); - OrderedOperation { - order: order + overlap, - operation: updated_delete, - } + updated_delete } ( - ref operation @ Operation::Equal { + Operation::Equal { + order, length, + #[cfg(debug_assertions)] ref text, - .. }, - Some( - last_delete @ OrderedOperation { - operation: Operation::Delete { .. }, - .. - }, - ), + Some(Operation::Delete { + order: last_delete_order, + deleted_character_count: last_delete_deleted_character_count, + .. + }), ) => { - let last_delete_end_index = last_delete.order + last_delete.operation.len(); + let last_delete_end_index = + *last_delete_order + *last_delete_deleted_character_count; let overlap = 0.max((length as i64).min(last_delete_end_index as i64 - order as i64)); #[cfg(debug_assertions)] let updated_equal = text.as_ref().map_or_else( - || Operation::create_equal((length as i64 - overlap) as usize), + || { + Operation::create_equal( + order + overlap as usize, + (length as i64 - overlap) as usize, + ) + }, |text| { Operation::create_equal_with_text( + order + overlap as usize, text.chars().skip(overlap as usize).collect::(), ) }, ); #[cfg(not(debug_assertions))] - let updated_equal = Operation::create_equal((length as i64 - overlap) as usize); + let updated_equal = Operation::create_equal( + order + overlap as usize, + (length as i64 - overlap) as usize, + ); - OrderedOperation { - order: order + overlap as usize, - operation: updated_equal, - } + updated_equal } ( - operation @ Operation::Equal { .. }, - Some( - last_equal @ OrderedOperation { - operation: Operation::Equal { .. }, - .. - }, - ), - ) => OrderedOperation { - order, - operation: if operation.len() == last_equal.operation.len() - && order == last_equal.order - { - Operation::create_equal(0) + ref operation @ Operation::Equal { ref order, .. }, + Some(Operation::Equal { + order: last_equal_order, + length: last_equal_length, + .. + }), + ) => { + if operation.len() == *last_equal_length && *order == *last_equal_order { + Operation::create_equal(*order, 0) } else { - operation - }, - }, + operation.clone() + } + } - (operation, _) => OrderedOperation { order, operation }, + (operation, _) => operation, } } } @@ -301,6 +329,7 @@ where fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Operation::Equal { + order, length, #[cfg(debug_assertions)] @@ -309,21 +338,21 @@ where #[cfg(debug_assertions)] write!( f, - "", + "", text.as_ref() .map(|text| format!("'{}'", text.replace('\n', "\\n"))) .unwrap_or(format!("{length} characters")), )?; #[cfg(not(debug_assertions))] - write!(f, "")?; + write!(f, "")?; Ok(()) } - Operation::Insert { text } => { + Operation::Insert { order, text } => { write!( f, - "", + "", text.iter() .map(Token::original) .collect::() @@ -331,6 +360,7 @@ where ) } Operation::Delete { + order, deleted_character_count, #[cfg(debug_assertions)] @@ -339,7 +369,7 @@ where #[cfg(debug_assertions)] write!( f, - "", + "", deleted_text .as_ref() .map(|text| format!("'{}'", text.replace('\n', "\\n"))) @@ -347,7 +377,10 @@ where )?; #[cfg(not(debug_assertions))] - write!(f, "",)?; + write!( + f, + "", + )?; Ok(()) } @@ -371,8 +404,8 @@ mod tests { #[test] fn test_apply_delete_with_create() { let builder = StringBuilder::new("hello world"); - let delete_operation = Operation::<()>::create_delete_with_text("hello ".to_owned()); - let retain_operation = Operation::<()>::create_equal(5); + let delete_operation = Operation::<()>::create_delete_with_text(0, "hello ".to_owned()); + let retain_operation = Operation::<()>::create_equal(6, 5); let mut builder = delete_operation.apply(builder); builder = retain_operation.apply(builder); @@ -384,8 +417,8 @@ mod tests { fn test_apply_insert() { let builder = StringBuilder::new("hello"); - let retain_operation = Operation::<()>::create_equal(5); - let insert_operation = Operation::create_insert(vec![" my friend".into()]); + let retain_operation = Operation::<()>::create_equal(0, 5); + let insert_operation = Operation::create_insert(5, vec![" my friend".into()]); let mut builder = retain_operation.apply(builder); builder = insert_operation.apply(builder); diff --git a/src/operation_transformation/ordered_operation.rs b/src/operation_transformation/ordered_operation.rs deleted file mode 100644 index 7f8308a..0000000 --- a/src/operation_transformation/ordered_operation.rs +++ /dev/null @@ -1,43 +0,0 @@ -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use crate::{operation_transformation::Operation, Token}; - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq)] -pub struct OrderedOperation -where - T: PartialEq + Clone + std::fmt::Debug, -{ - pub order: usize, - pub operation: Operation, -} - -impl OrderedOperation -where - T: PartialEq + Clone + std::fmt::Debug, -{ - pub fn get_sort_key(&self, insertion_index: usize) -> (usize, usize, usize, String) { - ( - self.order, - match &self.operation { - Operation::Delete { .. } => 1, - Operation::Insert { .. } => 2, - Operation::Equal { .. } => 3, - }, - insertion_index, - // Make sure that the ordering is deterministic regardless of which text - // is left or right. - match &self.operation { - Operation::Equal { length, .. } => length.to_string(), - Operation::Insert { text, .. } => { - text.iter().map(Token::original).collect::() - } - Operation::Delete { - deleted_character_count, - .. - } => deleted_character_count.to_string(), - }, - ) - } -} diff --git a/src/operation_transformation/snapshots/reconcile__operation_transformation__edited_text__tests__calculate_operations.snap b/src/operation_transformation/snapshots/reconcile__operation_transformation__edited_text__tests__calculate_operations.snap index dc186e0..abbabbd 100644 --- a/src/operation_transformation/snapshots/reconcile__operation_transformation__edited_text__tests__calculate_operations.snap +++ b/src/operation_transformation/snapshots/reconcile__operation_transformation__edited_text__tests__calculate_operations.snap @@ -6,38 +6,14 @@ snapshot_kind: text EditedText { text: "hello world! How are you? Adam", operations: [ - OrderedOperation { - order: 0, - operation: , - }, - OrderedOperation { - order: 12, - operation: , - }, - OrderedOperation { - order: 12, - operation: , - }, - OrderedOperation { - order: 13, - operation: , - }, - OrderedOperation { - order: 16, - operation: , - }, - OrderedOperation { - order: 17, - operation: , - }, - OrderedOperation { - order: 20, - operation: , - }, - OrderedOperation { - order: 31, - operation: , - }, + , + , + , + , + , + , + , + , ], cursors: [], } diff --git a/src/operation_transformation/snapshots/reconcile__operation_transformation__edited_text__tests__calculate_operations_with_no_diff.snap b/src/operation_transformation/snapshots/reconcile__operation_transformation__edited_text__tests__calculate_operations_with_no_diff.snap index 4dad79d..275a552 100644 --- a/src/operation_transformation/snapshots/reconcile__operation_transformation__edited_text__tests__calculate_operations_with_no_diff.snap +++ b/src/operation_transformation/snapshots/reconcile__operation_transformation__edited_text__tests__calculate_operations_with_no_diff.snap @@ -6,18 +6,9 @@ snapshot_kind: text EditedText { text: "hello world!", operations: [ - OrderedOperation { - order: 0, - operation: , - }, - OrderedOperation { - order: 5, - operation: , - }, - OrderedOperation { - order: 6, - operation: , - }, + , + , + , ], cursors: [], } diff --git a/src/operation_transformation/utils/cook_operations.rs b/src/operation_transformation/utils/cook_operations.rs index 0e21de7..c642d3c 100644 --- a/src/operation_transformation/utils/cook_operations.rs +++ b/src/operation_transformation/utils/cook_operations.rs @@ -1,10 +1,7 @@ -use crate::{ - diffs::raw_operation::RawOperation, - operation_transformation::{Operation, ordered_operation::OrderedOperation}, -}; +use crate::{diffs::raw_operation::RawOperation, operation_transformation::Operation}; /// Turn raw operations into ordered operations while keeping track of indexes. -pub fn cook_operations(raw_operations: I) -> impl Iterator> +pub fn cook_operations(raw_operations: I) -> impl Iterator> where I: IntoIterator>, T: PartialEq + Clone + std::fmt::Debug, @@ -16,31 +13,22 @@ where match raw_operation { RawOperation::Equal(..) => { - let op = OrderedOperation { - order, - operation: if cfg!(debug_assertions) { - Operation::create_equal_with_text(raw_operation.get_original_text()) - } else { - Operation::create_equal(length) - }, + let op = if cfg!(debug_assertions) { + Operation::create_equal_with_text(order, raw_operation.get_original_text()) + } else { + Operation::create_equal(order, length) }; order += length; op } - RawOperation::Insert(tokens) => OrderedOperation { - order, - operation: Operation::create_insert(tokens), - }, + RawOperation::Insert(tokens) => Operation::create_insert(order, tokens), RawOperation::Delete(..) => { - let op = OrderedOperation { - order, - operation: if cfg!(debug_assertions) { - Operation::create_delete_with_text(raw_operation.get_original_text()) - } else { - Operation::create_delete(length) - }, + let op = if cfg!(debug_assertions) { + Operation::create_delete_with_text(order, raw_operation.get_original_text()) + } else { + Operation::create_delete(order, length) }; order += length;