Add cursor moving #19
1 changed files with 205 additions and 20 deletions
Update API and add cursor position tests
commit
688fd2f1b1
|
|
@ -1,41 +1,42 @@
|
||||||
|
mod cursor;
|
||||||
mod edited_text;
|
mod edited_text;
|
||||||
mod merge_context;
|
mod merge_context;
|
||||||
mod operation;
|
mod operation;
|
||||||
|
|
||||||
|
pub use cursor::{CursorPosition, TextWithCursors};
|
||||||
pub use edited_text::EditedText;
|
pub use edited_text::EditedText;
|
||||||
pub use operation::Operation;
|
pub use operation::Operation;
|
||||||
|
|
||||||
use crate::tokenizer::Tokenizer;
|
use crate::Tokenizer;
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn reconcile(original: &str, left: &str, right: &str) -> String {
|
pub fn reconcile(original: &str, left: &str, right: &str) -> String {
|
||||||
// Common trivial cases
|
reconcile_with_cursors(original, left.into(), right.into())
|
||||||
if left == right {
|
.text
|
||||||
return left.to_owned();
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
if original == left {
|
#[must_use]
|
||||||
return right.to_owned();
|
pub fn reconcile_with_cursors<'a>(
|
||||||
}
|
original: &'a str,
|
||||||
|
left: TextWithCursors<'a>,
|
||||||
if original == right {
|
right: TextWithCursors<'a>,
|
||||||
return left.to_owned();
|
) -> TextWithCursors<'static> {
|
||||||
}
|
|
||||||
|
|
||||||
// 3-way merge
|
|
||||||
let left_operations = EditedText::from_strings(original, left);
|
let left_operations = EditedText::from_strings(original, left);
|
||||||
let right_operations = EditedText::from_strings(original, right);
|
let right_operations = EditedText::from_strings(original, right);
|
||||||
|
|
||||||
let merged_operations = left_operations.merge(right_operations);
|
let merged_operations = left_operations.merge(right_operations);
|
||||||
merged_operations.apply()
|
|
||||||
|
TextWithCursors::new_owned(merged_operations.apply(), merged_operations.cursors)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reconcile_with_tokenizer<F, T>(
|
#[must_use]
|
||||||
|
pub fn reconcile_with_tokenizer<'a, F, T>(
|
||||||
original: &str,
|
original: &str,
|
||||||
left: &str,
|
left: TextWithCursors<'a>,
|
||||||
right: &str,
|
right: TextWithCursors<'a>,
|
||||||
tokenizer: &Tokenizer<T>,
|
tokenizer: &Tokenizer<T>,
|
||||||
) -> String
|
) -> TextWithCursors<'static>
|
||||||
where
|
where
|
||||||
T: PartialEq + Clone + std::fmt::Debug,
|
T: PartialEq + Clone + std::fmt::Debug,
|
||||||
{
|
{
|
||||||
|
|
@ -43,7 +44,8 @@ where
|
||||||
let right_operations = EditedText::from_strings_with_tokenizer(original, right, tokenizer);
|
let right_operations = EditedText::from_strings_with_tokenizer(original, right, tokenizer);
|
||||||
|
|
||||||
let merged_operations = left_operations.merge(right_operations);
|
let merged_operations = left_operations.merge(right_operations);
|
||||||
merged_operations.apply()
|
|
||||||
|
TextWithCursors::new_owned(merged_operations.apply(), merged_operations.cursors)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -54,6 +56,7 @@ mod test {
|
||||||
use test_case::test_matrix;
|
use test_case::test_matrix;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::CursorPosition;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merges() {
|
fn test_merges() {
|
||||||
|
|
@ -172,6 +175,188 @@ mod test {
|
||||||
" |7ca2b36d-6ee7-49eb-8eb1-d77e4cc1a001| |cd9195cc-103a-4f13-90c8-4fba0ba421ee| |d39156cc-cfd6-42a8-b70a-75020896069d| |fbad794c-9c47-41f2-a343-490284ecb5a0| |dup| |dup| ");
|
" |7ca2b36d-6ee7-49eb-8eb1-d77e4cc1a001| |cd9195cc-103a-4f13-90c8-4fba0ba421ee| |d39156cc-cfd6-42a8-b70a-75020896069d| |fbad794c-9c47-41f2-a343-490284ecb5a0| |dup| |dup| ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cursor_position_no_updates() {
|
||||||
|
let original = "hello world";
|
||||||
|
let left = TextWithCursors::new(
|
||||||
|
"hello world",
|
||||||
|
vec![CursorPosition {
|
||||||
|
id: 0,
|
||||||
|
char_index: 0,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
let right = TextWithCursors::new(
|
||||||
|
"hello world",
|
||||||
|
vec![CursorPosition {
|
||||||
|
id: 1,
|
||||||
|
char_index: 5,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
|
||||||
|
let merged = reconcile_with_cursors(original, left, right);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
merged,
|
||||||
|
TextWithCursors::new(
|
||||||
|
"hello world",
|
||||||
|
vec![
|
||||||
|
CursorPosition {
|
||||||
|
id: 0,
|
||||||
|
char_index: 0
|
||||||
|
},
|
||||||
|
CursorPosition {
|
||||||
|
id: 1,
|
||||||
|
char_index: 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cursor_position_updates_with_inserts() {
|
||||||
|
let original = "hi";
|
||||||
|
let left = TextWithCursors::new(
|
||||||
|
"hi there",
|
||||||
|
vec![CursorPosition {
|
||||||
|
id: 0,
|
||||||
|
char_index: 7,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
let right = TextWithCursors::new(
|
||||||
|
"hi world!",
|
||||||
|
vec![
|
||||||
|
CursorPosition {
|
||||||
|
id: 1,
|
||||||
|
char_index: 9,
|
||||||
|
},
|
||||||
|
CursorPosition {
|
||||||
|
id: 2,
|
||||||
|
char_index: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let merged = reconcile_with_cursors(original, left, right);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
merged,
|
||||||
|
TextWithCursors::new(
|
||||||
|
"hi there world!",
|
||||||
|
vec![
|
||||||
|
CursorPosition {
|
||||||
|
id: 2,
|
||||||
|
char_index: 1,
|
||||||
|
},
|
||||||
|
CursorPosition {
|
||||||
|
id: 0,
|
||||||
|
char_index: 7
|
||||||
|
},
|
||||||
|
CursorPosition {
|
||||||
|
id: 1,
|
||||||
|
char_index: 15
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cursor_position_updates_with_deleted() {
|
||||||
|
let original = "a b c d";
|
||||||
|
let left = TextWithCursors::new(
|
||||||
|
"a b d",
|
||||||
|
vec![CursorPosition {
|
||||||
|
id: 0,
|
||||||
|
char_index: 1, // after a
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
let right = TextWithCursors::new(
|
||||||
|
"c d",
|
||||||
|
vec![CursorPosition {
|
||||||
|
id: 1,
|
||||||
|
char_index: 1, // after c
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
|
||||||
|
let merged = reconcile_with_cursors(original, left, right);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
merged,
|
||||||
|
TextWithCursors::new(
|
||||||
|
" d",
|
||||||
|
vec![
|
||||||
|
CursorPosition {
|
||||||
|
id: 0,
|
||||||
|
char_index: 0
|
||||||
|
},
|
||||||
|
CursorPosition {
|
||||||
|
id: 1,
|
||||||
|
char_index: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cursor_complex() {
|
||||||
|
let original = "this is some complex text to test cursor positions";
|
||||||
|
let left = TextWithCursors::new(
|
||||||
|
"this is really complex text for testing cursor positions",
|
||||||
|
vec![
|
||||||
|
CursorPosition {
|
||||||
|
id: 0,
|
||||||
|
char_index: 8,
|
||||||
|
}, // after "this is "
|
||||||
|
CursorPosition {
|
||||||
|
id: 1,
|
||||||
|
char_index: 22,
|
||||||
|
}, // after "this is really complex text"
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let right = TextWithCursors::new(
|
||||||
|
"that was some complex sample to test cursor movements",
|
||||||
|
vec![
|
||||||
|
CursorPosition {
|
||||||
|
id: 2,
|
||||||
|
char_index: 5,
|
||||||
|
}, // after "that "
|
||||||
|
CursorPosition {
|
||||||
|
id: 3,
|
||||||
|
char_index: 29,
|
||||||
|
}, // after "some complex sample "
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let merged = reconcile_with_cursors(original, left, right);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
merged,
|
||||||
|
TextWithCursors::new(
|
||||||
|
"that was really complex sample for testing cursor movements",
|
||||||
|
vec![
|
||||||
|
CursorPosition {
|
||||||
|
id: 0,
|
||||||
|
char_index: 9
|
||||||
|
}, // before "really"
|
||||||
|
CursorPosition {
|
||||||
|
id: 1,
|
||||||
|
char_index: 25
|
||||||
|
}, // inside of "s|ample" because "text" got replaced by "sample"
|
||||||
|
CursorPosition {
|
||||||
|
id: 2,
|
||||||
|
char_index: 5
|
||||||
|
}, // unchanged
|
||||||
|
CursorPosition {
|
||||||
|
id: 3,
|
||||||
|
char_index: 31
|
||||||
|
}, // before "for"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test_matrix( [
|
#[test_matrix( [
|
||||||
"pride_and_prejudice.txt",
|
"pride_and_prejudice.txt",
|
||||||
"romeo_and_juliet.txt",
|
"romeo_and_juliet.txt",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue