Fix cursors
This commit is contained in:
parent
f95bd43240
commit
5759b6bf66
8 changed files with 157 additions and 175 deletions
|
|
@ -110,8 +110,8 @@ mod test {
|
|||
}, // inside of "s|ample" because "text" got replaced by "sample"
|
||||
CursorPosition {
|
||||
id: 3,
|
||||
char_index: 42
|
||||
}, // before "cursor movements"
|
||||
char_index: 30
|
||||
}, // after "complex sample"
|
||||
]
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -161,41 +161,47 @@ 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,
|
||||
maybe_other_operation,
|
||||
);
|
||||
let result = operation.merge_operations_with_context(order, &mut last_other_op);
|
||||
|
||||
if let Some(
|
||||
ref op @ (OrderedOperation {
|
||||
operation: Operation::Insert { .. },
|
||||
..
|
||||
}
|
||||
| OrderedOperation {
|
||||
operation: Operation::Equal { .. },
|
||||
..
|
||||
}),
|
||||
) = result
|
||||
if let ref op @ (OrderedOperation {
|
||||
operation: Operation::Insert { .. },
|
||||
..
|
||||
}
|
||||
| OrderedOperation {
|
||||
operation: Operation::Equal { .. },
|
||||
..
|
||||
}) = result
|
||||
{
|
||||
let mut shift = merged_length as i64 - seen_left_length as i64;
|
||||
if !matches!(
|
||||
op,
|
||||
OrderedOperation {
|
||||
operation: Operation::Equal { .. },
|
||||
..
|
||||
}
|
||||
) {
|
||||
shift += op.operation.len() as i64 - original_length;
|
||||
}
|
||||
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;
|
||||
// }
|
||||
|
||||
while let Some(cursor) = left_cursors.next_if(|cursor| {
|
||||
cursor.char_index <= seen_left_length + original_length as usize
|
||||
}) {
|
||||
merged_cursors.push(cursor.with_index(
|
||||
(merged_length as i64).max(cursor.char_index as i64 + shift)
|
||||
as usize,
|
||||
));
|
||||
println!("cursor {}", cursor.char_index);
|
||||
merged_cursors.push(
|
||||
cursor.with_index((cursor.char_index as i64 + shift) as usize),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -204,46 +210,53 @@ where
|
|||
}
|
||||
|
||||
maybe_left_op = left_iter.next();
|
||||
last_left_op = result.clone();
|
||||
last_left_op = Some(result.clone());
|
||||
|
||||
result
|
||||
}
|
||||
Side::Right => {
|
||||
let result = operation.merge_operations_with_context(
|
||||
order,
|
||||
&mut last_other_op,
|
||||
maybe_other_operation,
|
||||
);
|
||||
let result = operation.merge_operations_with_context(order, &mut last_other_op);
|
||||
|
||||
if let Some(
|
||||
ref op @ (OrderedOperation {
|
||||
operation: Operation::Insert { .. },
|
||||
..
|
||||
}
|
||||
| OrderedOperation {
|
||||
operation: Operation::Equal { .. },
|
||||
..
|
||||
}),
|
||||
) = result
|
||||
if let ref op @ (OrderedOperation {
|
||||
operation: Operation::Insert { .. },
|
||||
..
|
||||
}
|
||||
| OrderedOperation {
|
||||
operation: Operation::Equal { .. },
|
||||
..
|
||||
}) = result
|
||||
{
|
||||
let mut shift = merged_length as i64 - seen_right_length as i64;
|
||||
if !matches!(
|
||||
op,
|
||||
OrderedOperation {
|
||||
operation: Operation::Equal { .. },
|
||||
..
|
||||
}
|
||||
) {
|
||||
shift += op.operation.len() as i64 - original_length;
|
||||
}
|
||||
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;
|
||||
// }
|
||||
|
||||
while let Some(cursor) = right_cursors.next_if(|cursor| {
|
||||
cursor.char_index <= seen_right_length + original_length as usize
|
||||
}) {
|
||||
merged_cursors.push(cursor.with_index(
|
||||
(merged_length as i64).max(cursor.char_index as i64 + shift)
|
||||
as usize,
|
||||
));
|
||||
println!("cursor {}", cursor.char_index);
|
||||
|
||||
merged_cursors.push(
|
||||
cursor.with_index((cursor.char_index as i64 + shift) as usize),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +265,7 @@ where
|
|||
}
|
||||
|
||||
maybe_right_op = right_iter.next();
|
||||
last_right_op = result.clone();
|
||||
last_right_op = Some(result.clone());
|
||||
|
||||
result
|
||||
}
|
||||
|
|
@ -260,17 +273,15 @@ where
|
|||
|
||||
println!(" = {result:?}");
|
||||
|
||||
if let Some(operation) = result {
|
||||
if operation.operation.len() == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if is_advancing_operation {
|
||||
merged_length += operation.operation.len();
|
||||
}
|
||||
|
||||
merged_operations.push(operation);
|
||||
if result.operation.len() == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if is_advancing_operation {
|
||||
merged_length += result.operation.len();
|
||||
}
|
||||
|
||||
merged_operations.push(result);
|
||||
}
|
||||
|
||||
for cursor in left_cursors.chain(right_cursors) {
|
||||
|
|
|
|||
|
|
@ -46,66 +46,45 @@ 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) -> Option<Self> {
|
||||
Some(Operation::Equal {
|
||||
pub fn create_equal(length: usize) -> Self {
|
||||
Operation::Equal {
|
||||
length,
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
text: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_equal_with_text(text: String) -> Option<Self> {
|
||||
if text.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Operation::Equal {
|
||||
pub fn create_equal_with_text(text: String) -> Self {
|
||||
Operation::Equal {
|
||||
length: text.chars().count(),
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
text: Some(text),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an insert operation with the given index and text.
|
||||
/// If the text is empty (meaning that the operation would be a no-op),
|
||||
/// returns None.
|
||||
pub fn create_insert(text: Vec<Token<T>>) -> Option<Self> {
|
||||
if text.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Operation::Insert { text })
|
||||
}
|
||||
pub fn create_insert(text: Vec<Token<T>>) -> Self { Operation::Insert { text } }
|
||||
|
||||
/// Creates a delete operation with the given index and number of
|
||||
/// to-be-deleted characters. If the operation would delete 0 (meaning
|
||||
/// that the operation would be a no-op), returns None.
|
||||
pub fn create_delete(deleted_character_count: usize) -> Option<Self> {
|
||||
if deleted_character_count == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Operation::Delete {
|
||||
/// to-be-deleted characters.
|
||||
pub fn create_delete(deleted_character_count: usize) -> Self {
|
||||
Operation::Delete {
|
||||
deleted_character_count,
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
deleted_text: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_delete_with_text(text: String) -> Option<Self> {
|
||||
if text.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Operation::Delete {
|
||||
pub fn create_delete_with_text(text: String) -> Self {
|
||||
Operation::Delete {
|
||||
deleted_character_count: text.chars().count(),
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
deleted_text: Some(text),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies the operation to the given `StringBuilder`, returning the
|
||||
|
|
@ -181,12 +160,8 @@ where
|
|||
self,
|
||||
order: usize,
|
||||
previous_operation: &mut Option<OrderedOperation<T>>,
|
||||
other_operation: Option<OrderedOperation<T>>,
|
||||
) -> Option<OrderedOperation<T>> {
|
||||
println!(
|
||||
"mergin: {self} (order {order}) - previous: {previous_operation:?} - other: \
|
||||
{other_operation:?}"
|
||||
);
|
||||
) -> OrderedOperation<T> {
|
||||
println!("mergin: {self} (order {order}) - previous: {previous_operation:?}");
|
||||
let operation = self;
|
||||
|
||||
match (operation, previous_operation) {
|
||||
|
|
@ -209,7 +184,10 @@ where
|
|||
|
||||
let trimmed_operation = Operation::create_insert(text[offset_in_tokens..].to_vec());
|
||||
|
||||
trimmed_operation.map(|operation| OrderedOperation { order, operation })
|
||||
OrderedOperation {
|
||||
order,
|
||||
operation: trimmed_operation,
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
|
|
@ -248,10 +226,10 @@ where
|
|||
#[cfg(not(debug_assertions))]
|
||||
let updated_delete = Operation::create_delete(new_length);
|
||||
|
||||
updated_delete.map(|operation| OrderedOperation {
|
||||
OrderedOperation {
|
||||
order: order + overlap,
|
||||
operation,
|
||||
})
|
||||
operation: updated_delete,
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
|
|
@ -268,19 +246,6 @@ where
|
|||
},
|
||||
),
|
||||
) => {
|
||||
if let Some(other_operation) = other_operation {
|
||||
if matches!(other_operation.operation, Operation::Equal { .. })
|
||||
&& operation.len() == other_operation.operation.len()
|
||||
&& order == other_operation.order
|
||||
{
|
||||
println!(" !!!equal to next");
|
||||
return Some(OrderedOperation {
|
||||
order,
|
||||
operation: Operation::create_equal(0).unwrap(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let last_delete_end_index = last_delete.order + last_delete.operation.len();
|
||||
|
||||
let overlap =
|
||||
|
|
@ -299,30 +264,32 @@ where
|
|||
#[cfg(not(debug_assertions))]
|
||||
let updated_equal = Operation::create_equal((length as i64 - overlap) as usize);
|
||||
|
||||
updated_equal.map(|operation| OrderedOperation {
|
||||
OrderedOperation {
|
||||
order: order + overlap as usize,
|
||||
operation,
|
||||
})
|
||||
}
|
||||
|
||||
(operation @ Operation::Equal { .. }, _) => {
|
||||
if let Some(other_operation) = other_operation {
|
||||
if matches!(other_operation.operation, Operation::Equal { .. })
|
||||
&& operation.len() == other_operation.operation.len()
|
||||
&& order == other_operation.order
|
||||
{
|
||||
println!(" !!!equal to next");
|
||||
Operation::create_equal(0)
|
||||
} else {
|
||||
Some(operation)
|
||||
}
|
||||
} else {
|
||||
Some(operation)
|
||||
operation: updated_equal,
|
||||
}
|
||||
.map(|operation| OrderedOperation { order, operation })
|
||||
}
|
||||
|
||||
(operation, _) => Some(OrderedOperation { order, operation }),
|
||||
(
|
||||
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)
|
||||
} else {
|
||||
operation
|
||||
},
|
||||
},
|
||||
|
||||
(operation, _) => OrderedOperation { order, operation },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -404,9 +371,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()).unwrap();
|
||||
let retain_operation = Operation::<()>::create_equal(5).unwrap();
|
||||
let delete_operation = Operation::<()>::create_delete_with_text("hello ".to_owned());
|
||||
let retain_operation = Operation::<()>::create_equal(5);
|
||||
|
||||
let mut builder = delete_operation.apply(builder);
|
||||
builder = retain_operation.apply(builder);
|
||||
|
|
@ -418,8 +384,8 @@ mod tests {
|
|||
fn test_apply_insert() {
|
||||
let builder = StringBuilder::new("hello");
|
||||
|
||||
let retain_operation = Operation::<()>::create_equal(5).unwrap();
|
||||
let insert_operation = Operation::create_insert(vec![" my friend".into()]).unwrap();
|
||||
let retain_operation = Operation::<()>::create_equal(5);
|
||||
let insert_operation = Operation::create_insert(vec![" my friend".into()]);
|
||||
|
||||
let mut builder = retain_operation.apply(builder);
|
||||
builder = insert_operation.apply(builder);
|
||||
|
|
|
|||
|
|
@ -11,31 +11,37 @@ where
|
|||
{
|
||||
let mut order = 0; // this is the start index of the operation on the original text
|
||||
|
||||
raw_operations.into_iter().filter_map(move |raw_operation| {
|
||||
raw_operations.into_iter().map(move |raw_operation| {
|
||||
let length = raw_operation.original_text_length();
|
||||
|
||||
match raw_operation {
|
||||
RawOperation::Equal(..) => {
|
||||
let op = if cfg!(debug_assertions) {
|
||||
Operation::create_equal_with_text(raw_operation.get_original_text())
|
||||
} else {
|
||||
Operation::create_equal(length)
|
||||
}
|
||||
.map(|operation| OrderedOperation { order, operation });
|
||||
let op = OrderedOperation {
|
||||
order,
|
||||
operation: if cfg!(debug_assertions) {
|
||||
Operation::create_equal_with_text(raw_operation.get_original_text())
|
||||
} else {
|
||||
Operation::create_equal(length)
|
||||
},
|
||||
};
|
||||
|
||||
order += length;
|
||||
|
||||
op
|
||||
}
|
||||
RawOperation::Insert(tokens) => Operation::create_insert(tokens)
|
||||
.map(|operation| OrderedOperation { order, operation }),
|
||||
RawOperation::Insert(tokens) => OrderedOperation {
|
||||
order,
|
||||
operation: Operation::create_insert(tokens),
|
||||
},
|
||||
RawOperation::Delete(..) => {
|
||||
let op = if cfg!(debug_assertions) {
|
||||
Operation::create_delete_with_text(raw_operation.get_original_text())
|
||||
} else {
|
||||
Operation::create_delete(length)
|
||||
}
|
||||
.map(|operation| OrderedOperation { order, operation });
|
||||
let op = OrderedOperation {
|
||||
order,
|
||||
operation: if cfg!(debug_assertions) {
|
||||
Operation::create_delete_with_text(raw_operation.get_original_text())
|
||||
} else {
|
||||
Operation::create_delete(length)
|
||||
},
|
||||
};
|
||||
|
||||
order += length;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
use core::ops::Range;
|
||||
use std::iter::Iterator;
|
||||
|
||||
/// A helper for building a string in-order based on an original string and a
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ expected: long small
|
|||
parent: long run of text where one barely has no changes but has cursors
|
||||
left: long| run of tex|t where one barely has no |changes but has |cursors
|
||||
right: long run one barely has no changes cursors
|
||||
expected: long| run| one barely has no |changes |cursors
|
||||
expected: long| ru|n one barely has no |changes |cursors
|
||||
|
||||
---
|
||||
parent: long text where the cursor has to be clamped after delete
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
parent: original_1 original_2 original_3
|
||||
left: original_1 edit_1| original_3
|
||||
right: original_1 original_2| edit_2
|
||||
expected: original_1 edit_1|| edit_2
|
||||
expected: original_1| edit_1| edit_2
|
||||
|
||||
---
|
||||
# Both replace the same token with the same value
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ expected: Send the |detailed |quarterly |detailed report to the |entire |team
|
|||
parent: Ready, Set go
|
||||
left: Ready! Set go|
|
||||
right: Ready, Set, go!|
|
||||
expected: Ready! Set, go!||
|
||||
expected: Ready!| Set, go!|
|
||||
|
||||
---
|
||||
parent: "Total: $100"
|
||||
|
|
@ -67,7 +67,7 @@ expected: market| placemarket|space
|
|||
parent: A B C D
|
||||
left: A X B D|
|
||||
right: A B Y|
|
||||
expected: A X B Y||
|
||||
expected: A X B| Y|
|
||||
|
||||
---
|
||||
parent: Please submit your assignment by Friday
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue