Move Side to EditedText from Operation
This commit is contained in:
parent
8a52034426
commit
de89532880
6 changed files with 119 additions and 55 deletions
|
|
@ -3,13 +3,10 @@ mod operation;
|
||||||
mod utils;
|
mod utils;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
pub use edited_text::EditedText;
|
pub use edited_text::{ChangeSet, EditedText};
|
||||||
pub use operation::Operation;
|
pub use operation::Operation;
|
||||||
|
|
||||||
use crate::{
|
use crate::{Tokenizer, types::text_with_cursors::TextWithCursors};
|
||||||
Tokenizer,
|
|
||||||
types::{side::Side, text_with_cursors::TextWithCursors},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Given an `original` document and two concurrent edits to it,
|
/// Given an `original` document and two concurrent edits to it,
|
||||||
/// return a document containing all changes from both `left`
|
/// return a document containing all changes from both `left`
|
||||||
|
|
@ -48,10 +45,8 @@ pub fn reconcile<'a, T>(
|
||||||
where
|
where
|
||||||
T: PartialEq + Clone + Debug,
|
T: PartialEq + Clone + Debug,
|
||||||
{
|
{
|
||||||
let left_operations =
|
let left_operations = EditedText::from_strings_with_tokenizer(original, left, tokenizer);
|
||||||
EditedText::from_strings_with_tokenizer(original, left, tokenizer, Side::Left);
|
let right_operations = EditedText::from_strings_with_tokenizer(original, right, tokenizer);
|
||||||
let right_operations =
|
|
||||||
EditedText::from_strings_with_tokenizer(original, right, tokenizer, Side::Right);
|
|
||||||
|
|
||||||
left_operations.merge(right_operations)
|
left_operations.merge(right_operations)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::fmt::Debug;
|
use std::{fmt::Debug, vec};
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -35,9 +35,35 @@ where
|
||||||
{
|
{
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
operations: Vec<Operation<T>>,
|
operations: Vec<Operation<T>>,
|
||||||
|
operation_sides: Vec<Side>,
|
||||||
cursors: Vec<CursorPosition>,
|
cursors: Vec<CursorPosition>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A serializable representation of the changes made to a text document
|
||||||
|
/// without the original text.
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default)]
|
||||||
|
pub struct ChangeSet<T>
|
||||||
|
where
|
||||||
|
T: PartialEq + Clone + Debug,
|
||||||
|
{
|
||||||
|
operations: Vec<Operation<T>>,
|
||||||
|
cursors: Vec<CursorPosition>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> ChangeSet<T>
|
||||||
|
where
|
||||||
|
T: PartialEq + Clone + Debug,
|
||||||
|
{
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(operations: Vec<Operation<T>>, cursors: Vec<CursorPosition>) -> Self {
|
||||||
|
Self {
|
||||||
|
operations,
|
||||||
|
cursors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> EditedText<'a, String> {
|
impl<'a> EditedText<'a, String> {
|
||||||
/// Create an `EditedText` from the given original (old) and updated (new)
|
/// Create an `EditedText` from the given original (old) and updated (new)
|
||||||
/// strings. The returned `EditedText` represents the changes from the
|
/// strings. The returned `EditedText` represents the changes from the
|
||||||
|
|
@ -46,8 +72,8 @@ impl<'a> EditedText<'a, String> {
|
||||||
/// word tokenizer is used to tokenize the text which splits the text on
|
/// word tokenizer is used to tokenize the text which splits the text on
|
||||||
/// whitespaces.
|
/// whitespaces.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn from_strings(original: &'a str, updated: &TextWithCursors, side: Side) -> Self {
|
pub fn from_strings(original: &'a str, updated: &TextWithCursors) -> Self {
|
||||||
Self::from_strings_with_tokenizer(original, updated, &*BuiltinTokenizer::Word, side)
|
Self::from_strings_with_tokenizer(original, updated, &*BuiltinTokenizer::Word)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,16 +90,18 @@ where
|
||||||
original: &'a str,
|
original: &'a str,
|
||||||
updated: &TextWithCursors,
|
updated: &TextWithCursors,
|
||||||
tokenizer: &Tokenizer<T>,
|
tokenizer: &Tokenizer<T>,
|
||||||
side: Side,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let original_tokens = (tokenizer)(original);
|
let original_tokens = (tokenizer)(original);
|
||||||
let updated_tokens = (tokenizer)(&updated.text());
|
let updated_tokens = (tokenizer)(&updated.text());
|
||||||
|
|
||||||
let diff: Vec<RawOperation<T>> = RawOperation::vec_from(&original_tokens, &updated_tokens);
|
let diff: Vec<RawOperation<T>> = RawOperation::vec_from(&original_tokens, &updated_tokens);
|
||||||
|
let operations: Vec<Operation<T>> = cook_operations(elongate_operations(diff)).collect();
|
||||||
|
let operation_count = operations.len();
|
||||||
|
|
||||||
Self::new(
|
Self::new(
|
||||||
original,
|
original,
|
||||||
cook_operations(elongate_operations(diff), side).collect(),
|
operations,
|
||||||
|
vec![Side::Left; operation_count],
|
||||||
updated.cursors(),
|
updated.cursors(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -81,12 +109,18 @@ where
|
||||||
/// Create a new `EditedText` with the given operations.
|
/// Create a new `EditedText` with the given operations.
|
||||||
/// The operations must be in the order in which they are meant to be
|
/// The operations must be in the order in which they are meant to be
|
||||||
/// applied. The operations must not overlap.
|
/// applied. The operations must not overlap.
|
||||||
fn new(text: &'a str, operations: Vec<Operation<T>>, mut cursors: Vec<CursorPosition>) -> Self {
|
fn new(
|
||||||
|
text: &'a str,
|
||||||
|
operations: Vec<Operation<T>>,
|
||||||
|
operation_sides: Vec<Side>,
|
||||||
|
mut cursors: Vec<CursorPosition>,
|
||||||
|
) -> Self {
|
||||||
cursors.sort_by_key(|cursor| cursor.char_index);
|
cursors.sort_by_key(|cursor| cursor.char_index);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
text,
|
text,
|
||||||
operations,
|
operations,
|
||||||
|
operation_sides,
|
||||||
cursors,
|
cursors,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -109,6 +143,8 @@ where
|
||||||
|
|
||||||
let mut merged_operations: Vec<Operation<T>> =
|
let mut merged_operations: Vec<Operation<T>> =
|
||||||
Vec::with_capacity(self.operations.len() + other.operations.len());
|
Vec::with_capacity(self.operations.len() + other.operations.len());
|
||||||
|
let mut merged_operation_sides: Vec<Side> =
|
||||||
|
Vec::with_capacity(self.operations.len() + other.operations.len());
|
||||||
|
|
||||||
let mut left_iter = self.operations.into_iter();
|
let mut left_iter = self.operations.into_iter();
|
||||||
let mut right_iter = other.operations.into_iter();
|
let mut right_iter = other.operations.into_iter();
|
||||||
|
|
@ -149,7 +185,7 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
let original_length = operation.len();
|
let original_length = operation.len();
|
||||||
let result = match side {
|
let (side, result) = match side {
|
||||||
Side::Left => {
|
Side::Left => {
|
||||||
let result = operation.merge_operations(&mut last_other_op);
|
let result = operation.merge_operations(&mut last_other_op);
|
||||||
|
|
||||||
|
|
@ -181,7 +217,7 @@ where
|
||||||
maybe_left_op = left_iter.next();
|
maybe_left_op = left_iter.next();
|
||||||
last_left_op = Some(result.clone());
|
last_left_op = Some(result.clone());
|
||||||
|
|
||||||
result
|
(Side::Left, result)
|
||||||
}
|
}
|
||||||
Side::Right => {
|
Side::Right => {
|
||||||
let result = operation.merge_operations(&mut last_other_op);
|
let result = operation.merge_operations(&mut last_other_op);
|
||||||
|
|
@ -214,7 +250,7 @@ where
|
||||||
maybe_right_op = right_iter.next();
|
maybe_right_op = right_iter.next();
|
||||||
last_right_op = Some(result.clone());
|
last_right_op = Some(result.clone());
|
||||||
|
|
||||||
result
|
(Side::Right, result)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -227,13 +263,21 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
merged_operations.push(result);
|
merged_operations.push(result);
|
||||||
|
merged_operation_sides.push(side);
|
||||||
}
|
}
|
||||||
|
|
||||||
for cursor in left_cursors.chain(right_cursors) {
|
for cursor in left_cursors.chain(right_cursors) {
|
||||||
merged_cursors.push(cursor.with_index(merged_length));
|
merged_cursors.push(cursor.with_index(merged_length));
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::new(self.text, merged_operations, merged_cursors)
|
debug_assert_eq!(merged_operations.len(), merged_operation_sides.len());
|
||||||
|
|
||||||
|
Self::new(
|
||||||
|
self.text,
|
||||||
|
merged_operations,
|
||||||
|
merged_operation_sides,
|
||||||
|
merged_cursors,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply the operations to the text and return the resulting text.
|
/// Apply the operations to the text and return the resulting text.
|
||||||
|
|
@ -288,14 +332,14 @@ where
|
||||||
|
|
||||||
let mut history = Vec::with_capacity(self.operations.len());
|
let mut history = Vec::with_capacity(self.operations.len());
|
||||||
|
|
||||||
for operation in &self.operations {
|
for (operation, side) in self.operations.iter().zip(self.operation_sides.iter()) {
|
||||||
builder = operation.apply(builder);
|
builder = operation.apply(builder);
|
||||||
|
|
||||||
match operation {
|
match operation {
|
||||||
Operation::Equal { .. } => {
|
Operation::Equal { .. } => {
|
||||||
history.push(SpanWithHistory::new(builder.take(), History::Unchanged));
|
history.push(SpanWithHistory::new(builder.take(), History::Unchanged));
|
||||||
}
|
}
|
||||||
Operation::Insert { side, .. } => match side {
|
Operation::Insert { .. } => match side {
|
||||||
Side::Left => {
|
Side::Left => {
|
||||||
history.push(SpanWithHistory::new(builder.take(), History::AddedFromLeft));
|
history.push(SpanWithHistory::new(builder.take(), History::AddedFromLeft));
|
||||||
}
|
}
|
||||||
|
|
@ -307,7 +351,6 @@ where
|
||||||
Operation::Delete {
|
Operation::Delete {
|
||||||
deleted_character_count,
|
deleted_character_count,
|
||||||
order,
|
order,
|
||||||
side,
|
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let deleted = self.text[*order..*order + *deleted_character_count].to_string();
|
let deleted = self.text[*order..*order + *deleted_character_count].to_string();
|
||||||
|
|
@ -325,6 +368,29 @@ where
|
||||||
|
|
||||||
history
|
history
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Serialize the `EditedText` as a `ChangeSet`, which contains only
|
||||||
|
/// the operations and cursor positions, without the original text.
|
||||||
|
/// This is useful for sending changes over the network if there's
|
||||||
|
/// a clear consensus on the original text.
|
||||||
|
#[must_use]
|
||||||
|
pub fn serialise_as_change_set(&self) -> ChangeSet<T> {
|
||||||
|
ChangeSet::new(self.operations.clone(), self.cursors.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize an `EditedText` from a `ChangeSet` and the original text.
|
||||||
|
/// This is useful for reconstructing the `EditedText` on the receiving
|
||||||
|
/// end after sending only the `ChangeSet` over the network.
|
||||||
|
#[must_use]
|
||||||
|
pub fn from_change_set(text: &'a str, change_set: ChangeSet<T>) -> EditedText<'a, T> {
|
||||||
|
let operation_count = change_set.operations.len();
|
||||||
|
EditedText::new(
|
||||||
|
text,
|
||||||
|
change_set.operations,
|
||||||
|
vec![Side::Left; operation_count],
|
||||||
|
change_set.cursors,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -339,7 +405,7 @@ mod tests {
|
||||||
let left = "hello world! How are you? Adam";
|
let left = "hello world! How are you? Adam";
|
||||||
let right = "Hello, my friend! How are you doing? Albert";
|
let right = "Hello, my friend! How are you doing? Albert";
|
||||||
|
|
||||||
let operations = EditedText::from_strings(left, &right.into(), Side::Right);
|
let operations = EditedText::from_strings(left, &right.into());
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(operations);
|
insta::assert_debug_snapshot!(operations);
|
||||||
|
|
||||||
|
|
@ -351,7 +417,7 @@ mod tests {
|
||||||
fn test_calculate_operations_with_no_diff() {
|
fn test_calculate_operations_with_no_diff() {
|
||||||
let text = "hello world!";
|
let text = "hello world!";
|
||||||
|
|
||||||
let operations = EditedText::from_strings(text, &text.into(), Side::Right);
|
let operations = EditedText::from_strings(text, &text.into());
|
||||||
|
|
||||||
assert_debug_snapshot!(operations);
|
assert_debug_snapshot!(operations);
|
||||||
|
|
||||||
|
|
@ -366,8 +432,8 @@ mod tests {
|
||||||
let right = "Hello world! How are you?";
|
let right = "Hello world! How are you?";
|
||||||
let expected = "Hello world! How are you? I'm Andras.";
|
let expected = "Hello world! How are you? I'm Andras.";
|
||||||
|
|
||||||
let operations_1 = EditedText::from_strings(original, &left.into(), Side::Left);
|
let operations_1 = EditedText::from_strings(original, &left.into());
|
||||||
let operations_2 = EditedText::from_strings(original, &right.into(), Side::Right);
|
let operations_2 = EditedText::from_strings(original, &right.into());
|
||||||
|
|
||||||
let operations = operations_1.merge(operations_2);
|
let operations = operations_1.merge(operations_2);
|
||||||
assert_eq!(operations.apply().text(), expected);
|
assert_eq!(operations.apply().text(), expected);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use core::fmt::{Debug, Display};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Side, Token,
|
Token,
|
||||||
utils::{
|
utils::{
|
||||||
find_longest_prefix_contained_within::find_longest_prefix_contained_within,
|
find_longest_prefix_contained_within::find_longest_prefix_contained_within,
|
||||||
string_builder::StringBuilder,
|
string_builder::StringBuilder,
|
||||||
|
|
@ -23,23 +23,21 @@ where
|
||||||
length: usize,
|
length: usize,
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing))]
|
||||||
text: Option<String>,
|
text: Option<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
Insert {
|
Insert {
|
||||||
side: Side,
|
|
||||||
|
|
||||||
order: usize,
|
order: usize,
|
||||||
text: Vec<Token<T>>,
|
text: Vec<Token<T>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
Delete {
|
Delete {
|
||||||
side: Side,
|
|
||||||
|
|
||||||
order: usize,
|
order: usize,
|
||||||
deleted_character_count: usize,
|
deleted_character_count: usize,
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing))]
|
||||||
deleted_text: Option<String>,
|
deleted_text: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -72,15 +70,14 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an insert operation with the given index and text.
|
/// Creates an insert operation with the given index and text.
|
||||||
pub fn create_insert(order: usize, text: Vec<Token<T>>, side: Side) -> Self {
|
pub fn create_insert(order: usize, text: Vec<Token<T>>) -> Self {
|
||||||
Operation::Insert { side, order, text }
|
Operation::Insert { order, text }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a delete operation with the given index and number of
|
/// Creates a delete operation with the given index and number of
|
||||||
/// to-be-deleted characters.
|
/// to-be-deleted characters.
|
||||||
pub fn create_delete(order: usize, deleted_character_count: usize, side: Side) -> Self {
|
pub fn create_delete(order: usize, deleted_character_count: usize) -> Self {
|
||||||
Operation::Delete {
|
Operation::Delete {
|
||||||
side,
|
|
||||||
order,
|
order,
|
||||||
deleted_character_count,
|
deleted_character_count,
|
||||||
|
|
||||||
|
|
@ -89,9 +86,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_delete_with_text(order: usize, text: String, side: Side) -> Self {
|
pub fn create_delete_with_text(order: usize, text: String) -> Self {
|
||||||
Operation::Delete {
|
Operation::Delete {
|
||||||
side,
|
|
||||||
order,
|
order,
|
||||||
deleted_character_count: text.chars().count(),
|
deleted_character_count: text.chars().count(),
|
||||||
|
|
||||||
|
|
@ -206,7 +202,7 @@ where
|
||||||
|
|
||||||
match (operation, previous_operation) {
|
match (operation, previous_operation) {
|
||||||
(
|
(
|
||||||
Operation::Insert { side, order, text },
|
Operation::Insert { order, text },
|
||||||
Some(Operation::Insert {
|
Some(Operation::Insert {
|
||||||
text: previous_inserted_text,
|
text: previous_inserted_text,
|
||||||
..
|
..
|
||||||
|
|
@ -218,12 +214,11 @@ where
|
||||||
let offset_in_tokens =
|
let offset_in_tokens =
|
||||||
find_longest_prefix_contained_within(previous_inserted_text, &text);
|
find_longest_prefix_contained_within(previous_inserted_text, &text);
|
||||||
|
|
||||||
Operation::create_insert(order, text[offset_in_tokens..].to_vec(), side)
|
Operation::create_insert(order, text[offset_in_tokens..].to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
(
|
||||||
Operation::Delete {
|
Operation::Delete {
|
||||||
side,
|
|
||||||
order,
|
order,
|
||||||
deleted_character_count,
|
deleted_character_count,
|
||||||
|
|
||||||
|
|
@ -247,20 +242,19 @@ where
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let updated_delete = deleted_text.as_ref().map_or_else(
|
let updated_delete = deleted_text.as_ref().map_or_else(
|
||||||
|| Operation::create_delete(order + overlap, new_length, side),
|
|| Operation::create_delete(order + overlap, new_length),
|
||||||
|text| {
|
|text| {
|
||||||
Operation::create_delete_with_text(
|
Operation::create_delete_with_text(
|
||||||
order + overlap,
|
order + overlap,
|
||||||
text.chars()
|
text.chars()
|
||||||
.skip(deleted_character_count - new_length)
|
.skip(deleted_character_count - new_length)
|
||||||
.collect::<String>(),
|
.collect::<String>(),
|
||||||
side,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
let updated_delete = Operation::create_delete(order + overlap, new_length, side);
|
let updated_delete = Operation::create_delete(order + overlap, new_length);
|
||||||
|
|
||||||
updated_delete
|
updated_delete
|
||||||
}
|
}
|
||||||
|
|
@ -405,8 +399,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_delete_with_create() {
|
fn test_apply_delete_with_create() {
|
||||||
let builder = StringBuilder::new("hello world");
|
let builder = StringBuilder::new("hello world");
|
||||||
let delete_operation =
|
let delete_operation = Operation::<()>::create_delete_with_text(0, "hello ".to_owned());
|
||||||
Operation::<()>::create_delete_with_text(0, "hello ".to_owned(), Side::Left);
|
|
||||||
let retain_operation = Operation::<()>::create_equal(6, 5);
|
let retain_operation = Operation::<()>::create_equal(6, 5);
|
||||||
|
|
||||||
let mut builder = delete_operation.apply(builder);
|
let mut builder = delete_operation.apply(builder);
|
||||||
|
|
@ -420,7 +413,7 @@ mod tests {
|
||||||
let builder = StringBuilder::new("hello");
|
let builder = StringBuilder::new("hello");
|
||||||
|
|
||||||
let retain_operation = Operation::<()>::create_equal(0, 5);
|
let retain_operation = Operation::<()>::create_equal(0, 5);
|
||||||
let insert_operation = Operation::create_insert(5, vec![" my friend".into()], Side::Right);
|
let insert_operation = Operation::create_insert(5, vec![" my friend".into()]);
|
||||||
|
|
||||||
let mut builder = retain_operation.apply(builder);
|
let mut builder = retain_operation.apply(builder);
|
||||||
builder = insert_operation.apply(builder);
|
builder = insert_operation.apply(builder);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/operation_transformation/edited_text.rs
|
source: src/operation_transformation/edited_text.rs
|
||||||
expression: operations
|
expression: operations
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
EditedText {
|
EditedText {
|
||||||
text: "hello world! How are you? Adam",
|
text: "hello world! How are you? Adam",
|
||||||
|
|
@ -15,5 +14,15 @@ EditedText {
|
||||||
<delete ' you? Adam' from 20>,
|
<delete ' you? Adam' from 20>,
|
||||||
<insert ' you doing? Albert' at 31>,
|
<insert ' you doing? Albert' at 31>,
|
||||||
],
|
],
|
||||||
|
operation_sides: [
|
||||||
|
Left,
|
||||||
|
Left,
|
||||||
|
Left,
|
||||||
|
Left,
|
||||||
|
Left,
|
||||||
|
Left,
|
||||||
|
Left,
|
||||||
|
Left,
|
||||||
|
],
|
||||||
cursors: [],
|
cursors: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
---
|
---
|
||||||
source: src/operation_transformation/edited_text.rs
|
source: src/operation_transformation/edited_text.rs
|
||||||
expression: operations
|
expression: operations
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
EditedText {
|
EditedText {
|
||||||
text: "hello world!",
|
text: "hello world!",
|
||||||
|
|
@ -10,5 +9,10 @@ EditedText {
|
||||||
<equal ' ' from 5>,
|
<equal ' ' from 5>,
|
||||||
<equal 'world!' from 6>,
|
<equal 'world!' from 6>,
|
||||||
],
|
],
|
||||||
|
operation_sides: [
|
||||||
|
Left,
|
||||||
|
Left,
|
||||||
|
Left,
|
||||||
|
],
|
||||||
cursors: [],
|
cursors: [],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::{operation_transformation::Operation, raw_operation::RawOperation, types::side::Side};
|
use crate::{operation_transformation::Operation, raw_operation::RawOperation};
|
||||||
|
|
||||||
/// Turn raw operations into ordered operations while keeping track of the
|
/// Turn raw operations into ordered operations while keeping track of the
|
||||||
/// original token's indexes.
|
/// original token's indexes.
|
||||||
pub fn cook_operations<I, T>(raw_operations: I, side: Side) -> impl Iterator<Item = Operation<T>>
|
pub fn cook_operations<I, T>(raw_operations: I) -> impl Iterator<Item = Operation<T>>
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = RawOperation<T>>,
|
I: IntoIterator<Item = RawOperation<T>>,
|
||||||
T: PartialEq + Clone + Debug,
|
T: PartialEq + Clone + Debug,
|
||||||
|
|
@ -29,18 +29,15 @@ where
|
||||||
|
|
||||||
op
|
op
|
||||||
}
|
}
|
||||||
RawOperation::Insert(tokens) => {
|
RawOperation::Insert(tokens) => Operation::create_insert(original_text_index, tokens),
|
||||||
Operation::create_insert(original_text_index, tokens, side)
|
|
||||||
}
|
|
||||||
RawOperation::Delete(..) => {
|
RawOperation::Delete(..) => {
|
||||||
let op = if cfg!(debug_assertions) {
|
let op = if cfg!(debug_assertions) {
|
||||||
Operation::create_delete_with_text(
|
Operation::create_delete_with_text(
|
||||||
original_text_index,
|
original_text_index,
|
||||||
raw_operation.get_original_text(),
|
raw_operation.get_original_text(),
|
||||||
side,
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Operation::create_delete(original_text_index, length, side)
|
Operation::create_delete(original_text_index, length)
|
||||||
};
|
};
|
||||||
|
|
||||||
original_text_index += length;
|
original_text_index += length;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue