Apply more lints
This commit is contained in:
parent
0c92bf959b
commit
1e1dd7b877
24 changed files with 61 additions and 63 deletions
|
|
@ -1,4 +1,4 @@
|
|||
use std::iter;
|
||||
use core::iter;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -9,17 +9,17 @@ use crate::{
|
|||
operation_transformation::merge_context::MergeContext,
|
||||
tokenizer::{word_tokenizer::word_tokenizer, Tokenizer},
|
||||
utils::{
|
||||
merge_iters::MergeSorted, ordered_operation::OrderedOperation, side::Side,
|
||||
merge_iters::MergeSorted as _, ordered_operation::OrderedOperation, side::Side,
|
||||
string_builder::StringBuilder,
|
||||
},
|
||||
};
|
||||
|
||||
/// A sequence of operations that can be applied to a text document.
|
||||
/// EditedText supports merging two sequences of operations using the
|
||||
/// `EditedText` supports merging two sequences of operations using the
|
||||
/// principle of Operational Transformation.
|
||||
///
|
||||
/// It's mainly created through the from_strings method, then merged with
|
||||
/// another EditedText derived from the same original text and then applied to
|
||||
/// It's mainly created through the `from_strings` method, then merged with
|
||||
/// another `EditedText` derived from the same original text and then applied to
|
||||
/// the original text to get the reconciled text of concurrent edits.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
|
|
@ -32,13 +32,13 @@ where
|
|||
}
|
||||
|
||||
impl<'a> EditedText<'a, String> {
|
||||
/// Create an EditedText from the given original (old) and updated (new)
|
||||
/// strings. The returned EditedText represents the changes from the
|
||||
/// Create an `EditedText` from the given original (old) and updated (new)
|
||||
/// strings. The returned `EditedText` represents the changes from the
|
||||
/// original to the updated text. When the return value is applied to
|
||||
/// the original text, it will result in the updated text. The default
|
||||
/// word tokenizer is used to tokenize the text which splits the text on
|
||||
/// whitespaces.
|
||||
pub fn from_strings(original: &'a str, updated: &str) -> Self {
|
||||
#[must_use] pub fn from_strings(original: &'a str, updated: &str) -> Self {
|
||||
Self::from_strings_with_tokenizer(original, updated, &word_tokenizer)
|
||||
}
|
||||
}
|
||||
|
|
@ -47,8 +47,8 @@ impl<'a, T> EditedText<'a, T>
|
|||
where
|
||||
T: PartialEq + Clone,
|
||||
{
|
||||
/// Create an EditedText from the given original (old) and updated (new)
|
||||
/// strings. The returned EditedText represents the changes from the
|
||||
/// Create an `EditedText` from the given original (old) and updated (new)
|
||||
/// strings. The returned `EditedText` represents the changes from the
|
||||
/// original to the updated text. When the return value is applied to
|
||||
/// the original text, it will result in the updated text. The tokenizer
|
||||
/// function is used to tokenize the text.
|
||||
|
|
@ -167,7 +167,7 @@ where
|
|||
result
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// applied. The operations must not overlap.
|
||||
fn new(text: &'a str, operations: Vec<OrderedOperation<T>>) -> Self {
|
||||
|
|
@ -186,7 +186,7 @@ where
|
|||
Self { text, operations }
|
||||
}
|
||||
|
||||
pub fn merge(self, other: Self) -> Self {
|
||||
#[must_use] pub fn merge(self, other: Self) -> Self {
|
||||
debug_assert_eq!(
|
||||
self.text, other.text,
|
||||
"EditedTexts must be derived from the same text to be mergable"
|
||||
|
|
@ -207,7 +207,7 @@ where
|
|||
operation.order,
|
||||
// Operations on left and right must come in the same order so that
|
||||
// inserts can be merged with other inserts and deletes with deletes.
|
||||
matches!(operation.operation, Operation::Delete { .. }) as usize,
|
||||
usize::from(matches!(operation.operation, Operation::Delete { .. })),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -233,9 +233,9 @@ where
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an SyncLibError::OperationError if the operations cannot be
|
||||
/// Returns an `SyncLibError::OperationError` if the operations cannot be
|
||||
/// applied to the text.
|
||||
pub fn apply(&self) -> String {
|
||||
#[must_use] pub fn apply(&self) -> String {
|
||||
let mut builder: StringBuilder<'_> = StringBuilder::new(self.text);
|
||||
|
||||
for OrderedOperation { operation, .. } in &self.operations {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::fmt::Debug;
|
||||
use core::fmt::Debug;
|
||||
|
||||
use crate::operation_transformation::Operation;
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ impl<T> Debug for MergeContext<T>
|
|||
where
|
||||
T: PartialEq + Clone,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("MergeContext")
|
||||
.field("last_operation", &self.last_operation)
|
||||
.field("shift", &self.shift)
|
||||
|
|
|
|||
|
|
@ -1,198 +0,0 @@
|
|||
mod edited_text;
|
||||
mod merge_context;
|
||||
mod operation;
|
||||
|
||||
pub use edited_text::EditedText;
|
||||
pub use operation::Operation;
|
||||
|
||||
use crate::tokenizer::Tokenizer;
|
||||
|
||||
pub fn reconcile(original: &str, left: &str, right: &str) -> String {
|
||||
if left == right {
|
||||
return left.to_string();
|
||||
}
|
||||
|
||||
if original == left {
|
||||
return right.to_string();
|
||||
}
|
||||
|
||||
if original == right {
|
||||
return left.to_string();
|
||||
}
|
||||
|
||||
let left_operations = EditedText::from_strings(original, left);
|
||||
let right_operations = EditedText::from_strings(original, right);
|
||||
|
||||
let merged_operations = left_operations.merge(right_operations);
|
||||
merged_operations.apply()
|
||||
}
|
||||
|
||||
pub fn reconcile_with_tokenizer<F, T>(
|
||||
original: &str,
|
||||
left: &str,
|
||||
right: &str,
|
||||
tokenizer: &Tokenizer<T>,
|
||||
) -> String
|
||||
where
|
||||
T: PartialEq + Clone,
|
||||
{
|
||||
let left_operations = EditedText::from_strings_with_tokenizer(original, left, tokenizer);
|
||||
let right_operations = EditedText::from_strings_with_tokenizer(original, right, tokenizer);
|
||||
|
||||
let merged_operations = left_operations.merge(right_operations);
|
||||
merged_operations.apply()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::{fs, ops::Range, path::Path};
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use test_case::test_matrix;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_merges() {
|
||||
// Both replaced one token but different
|
||||
test_merge_both_ways(
|
||||
"original_1 original_2 original_3",
|
||||
"original_1 edit_1 original_3",
|
||||
"original_1 original_2 edit_2",
|
||||
"original_1 edit_1 edit_2",
|
||||
);
|
||||
|
||||
// Both replaced the same one token
|
||||
test_merge_both_ways(
|
||||
"original_1 original_2 original_3",
|
||||
"original_1 edit_1 original_3",
|
||||
"original_1 edit_1 original_3",
|
||||
"original_1 edit_1 original_3",
|
||||
);
|
||||
|
||||
// One deleted a large range, the other deleted subranges and inserted as well
|
||||
test_merge_both_ways(
|
||||
"original_1 original_2 original_3 original_4 original_5",
|
||||
"original_1 original_5",
|
||||
"original_1 edit_1 original_3 edit_2 original_5",
|
||||
"original_1 edit_1 edit_2 original_5",
|
||||
);
|
||||
|
||||
// One deleted a large range, the other inserted and deleted a partially
|
||||
// overlapping range
|
||||
test_merge_both_ways(
|
||||
"original_1 original_2 original_3 original_4 original_5",
|
||||
"original_1 original_5",
|
||||
"original_1 edit_1 original_3 edit_2",
|
||||
"original_1 edit_1 edit_2",
|
||||
);
|
||||
|
||||
// Merge a replace and an append
|
||||
test_merge_both_ways("a b ", "c d ", "a b c d ", "c d c d ");
|
||||
|
||||
test_merge_both_ways("a b c d e", "a e", "a c e", "a e");
|
||||
|
||||
test_merge_both_ways("a 0 1 2 b", "a b", "a E 1 F b", "a E F b");
|
||||
|
||||
test_merge_both_ways(
|
||||
"a this one delete b",
|
||||
"a b",
|
||||
"a my one change b",
|
||||
"a my change b",
|
||||
);
|
||||
|
||||
test_merge_both_ways(
|
||||
"this stays, this is one big delete, don't touch this",
|
||||
"this stays, don't touch this",
|
||||
"this stays, my one change, don't touch this",
|
||||
"this stays, my change, don't touch this",
|
||||
);
|
||||
|
||||
test_merge_both_ways("1 2 3 4 5 6", "1 6", "1 2 4 ", "1 ");
|
||||
|
||||
test_merge_both_ways(
|
||||
"hello world",
|
||||
"hi, world",
|
||||
"hello my friend!",
|
||||
"hi, my friend!",
|
||||
);
|
||||
|
||||
// test_merge_both_ways("hello world", "world !", "hi hello world", "hi world
|
||||
// !");
|
||||
|
||||
test_merge_both_ways(
|
||||
"both delete the same word",
|
||||
"both the same word",
|
||||
"both the same word",
|
||||
"both the same word",
|
||||
);
|
||||
|
||||
test_merge_both_ways(" ", "it’s utf-8!", " ", "it’s utf-8!");
|
||||
|
||||
test_merge_both_ways(
|
||||
"both delete the same word but one a bit more",
|
||||
"both the same word",
|
||||
"both same word",
|
||||
"both same word",
|
||||
);
|
||||
|
||||
test_merge_both_ways(
|
||||
"long text with one big delete and many small",
|
||||
"long small",
|
||||
"long with big and small",
|
||||
"long small",
|
||||
);
|
||||
}
|
||||
|
||||
#[test_matrix( [
|
||||
"pride_and_prejudice.txt",
|
||||
"romeo_and_juliet.txt",
|
||||
"room_with_a_view.txt",
|
||||
"kun_lu.txt",
|
||||
|
||||
], [
|
||||
"pride_and_prejudice.txt",
|
||||
"romeo_and_juliet.txt",
|
||||
"room_with_a_view.txt",
|
||||
"kun_lu.txt"
|
||||
], [
|
||||
"pride_and_prejudice.txt",
|
||||
"romeo_and_juliet.txt",
|
||||
"room_with_a_view.txt",
|
||||
"kun_lu.txt"
|
||||
], [0..10000, 10000..20000], [0..10000, 10000..20000], [0..10000, 10000..20000])]
|
||||
fn test_merge_files_without_panic(
|
||||
file_name_1: &str,
|
||||
file_name_2: &str,
|
||||
file_name_3: &str,
|
||||
range_1: Range<usize>,
|
||||
range_2: Range<usize>,
|
||||
range_3: Range<usize>,
|
||||
) {
|
||||
let files = [file_name_1, file_name_2, file_name_3];
|
||||
let permutations = [range_1, range_2, range_3];
|
||||
|
||||
let root = Path::new("test/resources/");
|
||||
|
||||
let contents = files
|
||||
.iter()
|
||||
.zip(permutations.iter())
|
||||
.map(|(file, range)| {
|
||||
let path = root.join(file);
|
||||
fs::read_to_string(&path)
|
||||
.unwrap()
|
||||
.chars()
|
||||
.skip(range.start)
|
||||
.take(range.end)
|
||||
.collect::<String>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
reconcile(&contents[0], &contents[1], &contents[2]);
|
||||
}
|
||||
|
||||
fn test_merge_both_ways(original: &str, edit_1: &str, edit_2: &str, expected: &str) {
|
||||
assert_eq!(reconcile(original, edit_1, edit_2), expected);
|
||||
assert_eq!(reconcile(original, edit_2, edit_1), expected);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{
|
||||
use core::{
|
||||
fmt::{Debug, Display},
|
||||
ops::Range,
|
||||
};
|
||||
|
|
@ -13,8 +13,8 @@ use crate::{
|
|||
};
|
||||
|
||||
/// Represents a change that can be applied to a text document.
|
||||
/// Operation is tied to a ropey::Rope and is mainly expected to be
|
||||
/// created by EditedText.
|
||||
/// Operation is tied to a `ropey::Rope` and is mainly expected to be
|
||||
/// created by `EditedText`.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Operation<T>
|
||||
|
|
@ -81,12 +81,12 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
/// Tries to apply the operation to the given ropey::Rope text, returning
|
||||
/// Tries to apply the operation to the given `ropey::Rope` text, returning
|
||||
/// the modified text.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a SyncLibError::OperationApplicationError if the operation
|
||||
/// Returns a `SyncLibError::OperationApplicationError` if the operation
|
||||
/// cannot be applied.
|
||||
///
|
||||
/// # Panics
|
||||
|
|
@ -99,7 +99,7 @@ where
|
|||
self.start_index(),
|
||||
&text
|
||||
.iter()
|
||||
.map(|token| token.original())
|
||||
.map(super::super::tokenizer::token::Token::original)
|
||||
.collect::<String>(),
|
||||
),
|
||||
Operation::Delete {
|
||||
|
|
@ -111,8 +111,7 @@ where
|
|||
debug_assert!(
|
||||
deleted_text
|
||||
.as_ref()
|
||||
.map(|text| builder.get_slice(self.range()) == *text)
|
||||
.unwrap_or(true),
|
||||
.map_or(true, |text| builder.get_slice(self.range()) == *text),
|
||||
"Text to delete does not match the text in the rope"
|
||||
);
|
||||
|
||||
|
|
@ -141,15 +140,16 @@ where
|
|||
}
|
||||
|
||||
/// Returns the range of indices of characters that the operation affects.
|
||||
pub fn range(&self) -> Range<usize> { self.start_index()..self.end_index() + 1 }
|
||||
pub fn range(&self) -> Range<usize> { self.start_index()..self.end_index() - 1 }
|
||||
|
||||
/// Returns the number of affected characters. It is always greater than 0
|
||||
/// because empty operations cannot be created.
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Operation::Insert { text, .. } => {
|
||||
text.iter().map(|token| token.get_original_length()).sum()
|
||||
}
|
||||
Operation::Insert { text, .. } => text
|
||||
.iter()
|
||||
.map(super::super::tokenizer::token::Token::get_original_length)
|
||||
.sum(),
|
||||
Operation::Delete {
|
||||
deleted_character_count,
|
||||
..
|
||||
|
|
@ -223,7 +223,7 @@ where
|
|||
let trimmed_length = previous_inserted_text
|
||||
.iter()
|
||||
.skip(offset_in_tokens)
|
||||
.map(|token| token.get_original_length())
|
||||
.map(super::super::tokenizer::token::Token::get_original_length)
|
||||
.sum::<usize>();
|
||||
let trimmed_operation =
|
||||
Operation::create_insert(index, text[trimmed_length_in_tokens..].to_vec());
|
||||
|
|
@ -231,7 +231,7 @@ where
|
|||
affecting_context.shift -= trimmed_length as i64;
|
||||
produced_context.shift += trimmed_operation
|
||||
.as_ref()
|
||||
.map(|op| op.len())
|
||||
.map(Operation::len)
|
||||
.unwrap_or_default() as i64;
|
||||
produced_context.consume_and_replace_last_operation(trimmed_operation.clone());
|
||||
|
||||
|
|
@ -305,14 +305,14 @@ impl<T> Display for Operation<T>
|
|||
where
|
||||
T: PartialEq + Clone,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Operation::Insert { index, text } => {
|
||||
write!(
|
||||
f,
|
||||
"<insert '{}' from index {}>",
|
||||
text.iter()
|
||||
.map(|token| token.original())
|
||||
.map(super::super::tokenizer::token::Token::original)
|
||||
.collect::<String>(),
|
||||
index
|
||||
)
|
||||
|
|
@ -351,7 +351,7 @@ impl<T> Debug for Operation<T>
|
|||
where
|
||||
T: PartialEq + Clone,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self) }
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{self}") }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue