more working
This commit is contained in:
parent
3dfc3c9680
commit
2c4737999f
2 changed files with 48 additions and 53 deletions
|
|
@ -1,6 +1,5 @@
|
||||||
use ropey::Rope;
|
use ropey::Rope;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use similar::{Change, ChangeTag};
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
|
@ -36,7 +35,7 @@ impl Display for Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Operation {
|
impl Operation {
|
||||||
pub fn create(tag: ChangeTag, index: i64, text: &str) -> Result<Self, SyncLibError> {
|
pub fn create_insert(index: i64, text: &str) -> Result<Option<Self>, SyncLibError> {
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
return Err(SyncLibError::NegativeOperationIndexError(format!(
|
return Err(SyncLibError::NegativeOperationIndexError(format!(
|
||||||
"Index {} is negative",
|
"Index {} is negative",
|
||||||
|
|
@ -44,29 +43,17 @@ impl Operation {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(match tag {
|
if text.is_empty() {
|
||||||
ChangeTag::Insert => Operation::Insert {
|
return Ok(None);
|
||||||
index,
|
}
|
||||||
text: text.to_string(),
|
|
||||||
},
|
Ok(Some(Operation::Insert {
|
||||||
ChangeTag::Delete => Operation::Delete {
|
index,
|
||||||
index,
|
text: text.to_string(),
|
||||||
deleted_character_count: text.chars().count() as i64,
|
}))
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return Err(SyncLibError::OperationConversionError(format!(
|
|
||||||
"Cannot convert editing operation because {:?}",
|
|
||||||
tag
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_insert(index: i64, text: &str) -> Result<Self, SyncLibError> {
|
pub fn create_delete(index: i64, length: i64) -> Result<Option<Self>, SyncLibError> {
|
||||||
Self::create(ChangeTag::Insert, index, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_delete(index: i64, length: i64) -> Result<Self, SyncLibError> {
|
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
return Err(SyncLibError::NegativeOperationIndexError(format!(
|
return Err(SyncLibError::NegativeOperationIndexError(format!(
|
||||||
"Index {} is negative",
|
"Index {} is negative",
|
||||||
|
|
@ -81,16 +68,20 @@ impl Operation {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Operation::Delete {
|
if length == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(Operation::Delete {
|
||||||
index,
|
index,
|
||||||
deleted_character_count: length,
|
deleted_character_count: length,
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply<'a>(&self, rope_text: &'a mut Rope) -> Result<&'a mut Rope, SyncLibError> {
|
pub fn apply<'a>(&self, rope_text: &'a mut Rope) -> Result<&'a mut Rope, SyncLibError> {
|
||||||
let index: usize = self.start_index() as usize;
|
let index: usize = self.start_index() as usize;
|
||||||
match self {
|
match self {
|
||||||
Operation::Insert { text, .. } => rope_text.try_insert(index, &text).map_err(|err| {
|
Operation::Insert { text, .. } => rope_text.try_insert(index, text).map_err(|err| {
|
||||||
SyncLibError::OperationApplicationError(format!("Failed to insert text: {}", err))
|
SyncLibError::OperationApplicationError(format!("Failed to insert text: {}", err))
|
||||||
}),
|
}),
|
||||||
Operation::Delete {
|
Operation::Delete {
|
||||||
|
|
@ -133,6 +124,10 @@ impl Operation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the range of indices of characters that the operation affects, inclusive.
|
/// Returns the range of indices of characters that the operation affects, inclusive.
|
||||||
pub fn range(&self) -> std::ops::RangeInclusive<i64> {
|
pub fn range(&self) -> std::ops::RangeInclusive<i64> {
|
||||||
self.start_index()..=self.end_index()
|
self.start_index()..=self.end_index()
|
||||||
|
|
@ -144,6 +139,7 @@ impl Operation {
|
||||||
index,
|
index,
|
||||||
text: text.clone(),
|
text: text.clone(),
|
||||||
},
|
},
|
||||||
|
|
||||||
Operation::Delete {
|
Operation::Delete {
|
||||||
deleted_character_count,
|
deleted_character_count,
|
||||||
..
|
..
|
||||||
|
|
@ -177,7 +173,7 @@ impl Ord for Operation {
|
||||||
|
|
||||||
impl PartialOrd for Operation {
|
impl PartialOrd for Operation {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
Some(self.cmp(&other))
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,8 +183,6 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_creation_errors() {
|
fn test_creation_errors() {
|
||||||
insta::assert_debug_snapshot!(Operation::create(ChangeTag::Insert, -1, "hi"));
|
|
||||||
insta::assert_debug_snapshot!(Operation::create(ChangeTag::Equal, 0, "hi"));
|
|
||||||
insta::assert_debug_snapshot!(Operation::create_insert(-1, "hi"));
|
insta::assert_debug_snapshot!(Operation::create_insert(-1, "hi"));
|
||||||
insta::assert_debug_snapshot!(Operation::create_delete(0, -1));
|
insta::assert_debug_snapshot!(Operation::create_delete(0, -1));
|
||||||
insta::assert_debug_snapshot!(Operation::create_delete(-1, -1));
|
insta::assert_debug_snapshot!(Operation::create_delete(-1, -1));
|
||||||
|
|
@ -212,7 +206,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_delete_with_create() -> Result<(), SyncLibError> {
|
fn test_apply_delete_with_create() -> Result<(), SyncLibError> {
|
||||||
let mut rope = Rope::from_str("hello world");
|
let mut rope = Rope::from_str("hello world");
|
||||||
let operation = Operation::create(ChangeTag::Delete, 6, "world")?;
|
let operation = Operation::create_delete(5, 6)?.unwrap();
|
||||||
|
|
||||||
operation.apply(&mut rope)?;
|
operation.apply(&mut rope)?;
|
||||||
|
|
||||||
|
|
@ -239,7 +233,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_insert_with_create() -> Result<(), SyncLibError> {
|
fn test_apply_insert_with_create() -> Result<(), SyncLibError> {
|
||||||
let mut rope = Rope::from_str("hello");
|
let mut rope = Rope::from_str("hello");
|
||||||
let operation = Operation::create(ChangeTag::Insert, 5, " my friend")?;
|
let operation = Operation::create_insert(5, " my friend")?.unwrap();
|
||||||
|
|
||||||
operation.apply(&mut rope)?;
|
operation.apply(&mut rope)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
use super::{operation, Operation};
|
use super::Operation;
|
||||||
use crate::errors::SyncLibError;
|
use crate::errors::SyncLibError;
|
||||||
use log::info;
|
|
||||||
use ropey::Rope;
|
use ropey::Rope;
|
||||||
use serde::{de, Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use similar::utils::diff_graphemes;
|
use similar::Algorithm;
|
||||||
use similar::{utils::TextDiffRemapper, ChangeTag, TextDiff};
|
use similar::{utils::TextDiffRemapper, ChangeTag, TextDiff};
|
||||||
use similar::{Algorithm, DiffableStrRef};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
struct MergeContext {
|
struct MergeContext {
|
||||||
|
|
@ -43,7 +41,7 @@ impl OperationSequence {
|
||||||
if diff_ratio > diff_ratio_threshold {
|
if diff_ratio > diff_ratio_threshold {
|
||||||
return Err(SyncLibError::DiffTooLarge {
|
return Err(SyncLibError::DiffTooLarge {
|
||||||
diff_ratio,
|
diff_ratio,
|
||||||
diff_ratio_limit: diff_ratio_threshold as f32,
|
diff_ratio_limit: diff_ratio_threshold,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,16 +54,18 @@ impl OperationSequence {
|
||||||
.map(|(tag, text)| match tag {
|
.map(|(tag, text)| match tag {
|
||||||
ChangeTag::Equal => {
|
ChangeTag::Equal => {
|
||||||
index += text.chars().count();
|
index += text.chars().count();
|
||||||
None
|
Ok(None)
|
||||||
}
|
}
|
||||||
ChangeTag::Insert => {
|
ChangeTag::Insert => {
|
||||||
let result = Some(Operation::create(tag, index as i64, text));
|
let result = Operation::create_insert(index as i64, text);
|
||||||
index += text.chars().count();
|
index += text.chars().count();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
ChangeTag::Delete => Some(Operation::create(tag, index as i64, text)),
|
ChangeTag::Delete => {
|
||||||
|
Operation::create_delete(index as i64, text.chars().count() as i64)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.flat_map(Option::into_iter)
|
.flat_map(|result| result.transpose().into_iter())
|
||||||
.collect::<Result<Vec<_>, SyncLibError>>()
|
.collect::<Result<Vec<_>, SyncLibError>>()
|
||||||
.map(Self::new)
|
.map(Self::new)
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +93,7 @@ impl OperationSequence {
|
||||||
loop {
|
loop {
|
||||||
let left_op = self.operations.get(left_index);
|
let left_op = self.operations.get(left_index);
|
||||||
let right_op = other.operations.get(right_index);
|
let right_op = other.operations.get(right_index);
|
||||||
println!("");
|
println!();
|
||||||
println!("{:#?} <> {:#?}", left_op.cloned(), right_op.cloned());
|
println!("{:#?} <> {:#?}", left_op.cloned(), right_op.cloned());
|
||||||
|
|
||||||
println!("{:?} <> {:?}", left_delete_context, right_delete_context);
|
println!("{:?} <> {:?}", left_delete_context, right_delete_context);
|
||||||
|
|
@ -161,12 +161,12 @@ impl OperationSequence {
|
||||||
|
|
||||||
if last_delete
|
if last_delete
|
||||||
.range()
|
.range()
|
||||||
.contains(&(&operation.start_index() + affecting_context.shift))
|
.contains(&(operation.start_index() + affecting_context.shift))
|
||||||
{
|
{
|
||||||
affecting_context.last_delete = Some(Operation::create_delete(
|
affecting_context.last_delete = Operation::create_delete(
|
||||||
last_delete.start_index() + operation.len(),
|
last_delete.start_index() + operation.len(),
|
||||||
0.max(last_delete.len() - operation.len()),
|
0.max(last_delete.len() - operation.len()),
|
||||||
)?);
|
)?;
|
||||||
|
|
||||||
Some(operation.with_index(last_delete.start_index()))
|
Some(operation.with_index(last_delete.start_index()))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -188,15 +188,15 @@ impl OperationSequence {
|
||||||
{
|
{
|
||||||
affecting_context.shift -=
|
affecting_context.shift -=
|
||||||
shifted_operation.start_index() - last_delete.start_index();
|
shifted_operation.start_index() - last_delete.start_index();
|
||||||
affecting_context.last_delete = Some(Operation::create_delete(
|
affecting_context.last_delete = Operation::create_delete(
|
||||||
shifted_operation.end_index() + 1,
|
shifted_operation.start_index(),
|
||||||
last_delete.end_index() - shifted_operation.end_index(),
|
last_delete.len() - shifted_operation.len(),
|
||||||
)?);
|
)?;
|
||||||
|
|
||||||
None
|
None
|
||||||
} else if last_delete
|
} else if last_delete
|
||||||
.range()
|
.range()
|
||||||
.contains(&shifted_operation.start_index())
|
.contains(&(operation.start_index() + affecting_context.shift))
|
||||||
{
|
{
|
||||||
let overlap = last_delete.end_index()
|
let overlap = last_delete.end_index()
|
||||||
- (operation.start_index() + affecting_context.shift)
|
- (operation.start_index() + affecting_context.shift)
|
||||||
|
|
@ -204,10 +204,10 @@ impl OperationSequence {
|
||||||
affecting_context.last_delete = None;
|
affecting_context.last_delete = None;
|
||||||
affecting_context.shift -= last_delete.len() - overlap;
|
affecting_context.shift -= last_delete.len() - overlap;
|
||||||
|
|
||||||
let operation = Some(Operation::create_delete(
|
let operation = Operation::create_delete(
|
||||||
last_delete.start_index(),
|
last_delete.start_index(),
|
||||||
operation.len() - overlap,
|
operation.len() - overlap,
|
||||||
)?);
|
)?;
|
||||||
Self::replace_delete_in_produced_context(produced_context, operation.clone());
|
Self::replace_delete_in_produced_context(produced_context, operation.clone());
|
||||||
operation
|
operation
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -246,7 +246,6 @@ impl OperationSequence {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::operations::test;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
@ -296,6 +295,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merges() {
|
fn test_merges() {
|
||||||
|
test_merge_both_ways("a b c d e", "a e", "a c e", "a e");
|
||||||
|
|
||||||
test_merge_both_ways(
|
test_merge_both_ways(
|
||||||
"hello world",
|
"hello world",
|
||||||
"hi, world",
|
"hi, world",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue