Make insert deduplication smarter
This commit is contained in:
parent
253f0b95b3
commit
c566eb7ef6
3 changed files with 58 additions and 28 deletions
|
|
@ -161,13 +161,7 @@ where
|
|||
Side::Left => &mut last_right_op,
|
||||
Side::Right => &mut last_left_op,
|
||||
};
|
||||
let result = operation.merge_operations(previous_operation.as_ref());
|
||||
|
||||
if matches!(result, Operation::Insert { .. })
|
||||
&& matches!(previous_operation, Some(Operation::Insert { .. }))
|
||||
{
|
||||
*previous_operation = None;
|
||||
}
|
||||
let result = operation.merge_operations_consuming_insert(previous_operation.as_mut());
|
||||
|
||||
match side {
|
||||
Side::Left => {
|
||||
|
|
|
|||
|
|
@ -219,6 +219,34 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Merges operations while consuming the matched part of a cached insertion.
|
||||
/// Keeps its suffix for later matches until new text is emitted after it.
|
||||
pub(crate) fn merge_operations_consuming_insert(
|
||||
self,
|
||||
previous_operation: Option<&mut Self>,
|
||||
) -> Self {
|
||||
match (self, previous_operation) {
|
||||
(
|
||||
Operation::Insert { order, text },
|
||||
Some(Operation::Insert {
|
||||
text: previous_inserted_text,
|
||||
..
|
||||
}),
|
||||
) => {
|
||||
let overlap = find_longest_prefix_contained_within(previous_inserted_text, &text);
|
||||
if overlap.len() == text.len() {
|
||||
previous_inserted_text.drain(..overlap.end);
|
||||
} else {
|
||||
previous_inserted_text.clear();
|
||||
}
|
||||
Operation::create_insert(order, text[overlap.len()..].to_vec())
|
||||
}
|
||||
(operation, previous_operation) => {
|
||||
operation.merge_operations(previous_operation.as_deref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjusts this operation based on `previous_operation` from the other side
|
||||
/// to avoid duplicating or conflicting changes
|
||||
#[allow(clippy::too_many_lines)]
|
||||
|
|
@ -236,10 +264,9 @@ where
|
|||
// In case the current insert's prefix appears in the previously inserted text,
|
||||
// we can trim the current insert to only include the non-overlapping part.
|
||||
// This way, we don't end up duplicating text.
|
||||
let offset_in_tokens =
|
||||
find_longest_prefix_contained_within(previous_inserted_text, &text);
|
||||
let overlap = find_longest_prefix_contained_within(previous_inserted_text, &text);
|
||||
|
||||
Operation::create_insert(order, text[offset_in_tokens..].to_vec())
|
||||
Operation::create_insert(order, text[overlap.len()..].to_vec())
|
||||
}
|
||||
|
||||
(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use std::fmt::Debug;
|
||||
use std::{fmt::Debug, ops::Range};
|
||||
|
||||
use crate::Token;
|
||||
|
||||
/// Given two lists of tokens, returns `length` where the `old` list
|
||||
/// somewhere within contains the `length` prefix of the `new` list
|
||||
/// Returns the range in `old` containing the longest prefix of `new`.
|
||||
/// Uses the first occurrence when that prefix appears more than once,
|
||||
/// or `0..0` if there is no overlap.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
|
|
@ -11,21 +12,21 @@ use crate::Token;
|
|||
/// old: [0, 1, 9, 0, 2, 5]
|
||||
/// new: [9, 0, 2, 5, 1]
|
||||
/// ```
|
||||
/// > results in a length of 4
|
||||
/// > results in a range of 2..6
|
||||
///
|
||||
///
|
||||
/// ```not_rust
|
||||
/// old: [0, 1, 9, 0, 2, 5]
|
||||
/// new: [0, 2]
|
||||
/// ```
|
||||
/// > results in a length of 2
|
||||
/// > results in a range of 3..5
|
||||
///
|
||||
/// ```not_rust
|
||||
/// old: [0, 1, 9, 0, 2, 5]
|
||||
/// new: [0, 4]
|
||||
/// new: [0, 4]
|
||||
/// ```
|
||||
/// > results in a length of 1
|
||||
pub fn find_longest_prefix_contained_within<T>(old: &[Token<T>], new: &[Token<T>]) -> usize
|
||||
/// > results in a range of 0..1
|
||||
pub fn find_longest_prefix_contained_within<T>(old: &[Token<T>], new: &[Token<T>]) -> Range<usize>
|
||||
where
|
||||
T: PartialEq + Clone + Debug,
|
||||
{
|
||||
|
|
@ -33,12 +34,12 @@ where
|
|||
|
||||
for len in (1..=max_possible).rev() {
|
||||
let prefix = &new[..len];
|
||||
if old.windows(len).any(|window| window == prefix) {
|
||||
return len;
|
||||
if let Some(start) = old.windows(len).position(|window| window == prefix) {
|
||||
return start..start + len;
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
0..0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -51,7 +52,7 @@ mod tests {
|
|||
fn test_common_overlap() {
|
||||
assert_eq!(
|
||||
find_longest_prefix_contained_within(&["".into()], &["".into()]),
|
||||
1
|
||||
0..1
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -59,7 +60,7 @@ mod tests {
|
|||
&["a".into(), "b".into(), "c".into()],
|
||||
&["b".into(), "c".into(), "a".into()]
|
||||
),
|
||||
2
|
||||
1..3
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -67,7 +68,7 @@ mod tests {
|
|||
&["a".into(), "b".into(), "c".into()],
|
||||
&["b".into(), "c".into()]
|
||||
),
|
||||
2
|
||||
1..3
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -75,7 +76,7 @@ mod tests {
|
|||
&["a".into(), "b".into(), "c".into()],
|
||||
&["b".into()]
|
||||
),
|
||||
1
|
||||
1..2
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -83,7 +84,7 @@ mod tests {
|
|||
&["a".into(), "b".into(), "c".into(), "b".into(), "a".into()],
|
||||
&["b".into(), "a".into()]
|
||||
),
|
||||
2
|
||||
3..5
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -91,7 +92,15 @@ mod tests {
|
|||
&["a".into(), "a".into(), "a".into()],
|
||||
&["a".into(), "b".into(), "c".into()]
|
||||
),
|
||||
1
|
||||
0..1
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
find_longest_prefix_contained_within(
|
||||
&["a".into(), "b".into(), "a".into(), "b".into()],
|
||||
&["a".into(), "b".into()]
|
||||
),
|
||||
0..2
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -99,7 +108,7 @@ mod tests {
|
|||
&["a".into(), "b".into(), "c".into()],
|
||||
&["d".into(), "e".into(), "a".into()]
|
||||
),
|
||||
0
|
||||
0..0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue