Verified Commit b8d89a6b authored by Phil Booth's avatar Phil Booth

feat(cursor): implement basic paragraph movement

parent 74286067
Pipeline #32757798 passed with stage
in 3 minutes and 13 seconds
......@@ -304,4 +304,70 @@ impl Cursor {
self.ideal_grapheme_index = self.grapheme_index;
}
}
pub fn move_paragraph(&mut self, delta: isize) {
if delta == 0 {
return;
}
if let Some(line_id) = self.line_id() {
let line_ids = arena().read(line_id, &|line| {
if delta > 0 {
line.following_siblings()
} else {
line.preceding_siblings()
}
});
let next_delta = if delta > 0 {
self.line_index += self.move_paragraph_absolute(line_ids);
self.move_to_line_end();
delta - 1
} else {
self.line_index -= self.move_paragraph_absolute(line_ids.rev());
delta + 1
};
self.move_paragraph(next_delta);
}
}
fn move_paragraph_absolute<I>(&mut self, line_ids: I) -> usize
where
I: Iterator<Item = TokenId>,
{
let mut count = 0;
let mut new_line_id = None;
let mut started = true;
let mut finished = false;
if let Some(line_id) = self.line_id() {
if arena().read(line_id, &|line| line.is_newline_line()) {
// Seek past current blank lines before looking for the next one
started = false;
}
}
for line_id in line_ids {
count += 1;
new_line_id = Some(line_id);
if arena().read(line_id, &|line| line.is_newline_line()) {
if started {
finished = true;
break;
}
} else if !started {
started = true;
}
}
if let Some(new_line_id) = new_line_id {
let word_id = arena().read(new_line_id, &|line| line.first_child().unwrap());
self.grapheme_id = arena().read(word_id, &|word| word.first_child());
self.grapheme_index = 0;
}
count
}
}
......@@ -400,3 +400,62 @@ fn move_to_word_end() {
assert_eq!(cursor.grapheme_index(), 5);
assert_eq!(cursor.line_index(), 0);
}
#[test]
fn move_paragraph() {
let paragraph_ids = parse::paragraphs("000\n111\n2222\n\n333\n444\n\n\n555").unwrap();
let line_id = arena().read(paragraph_ids[0], &|paragraph| {
paragraph.first_child().unwrap()
});
let word_id = arena().read(line_id, &|line| line.first_child().unwrap());
let grapheme_id = arena().read(word_id, &|word| word.first_child());
let mut cursor = Cursor::new(grapheme_id, 0, 0);
cursor.move_paragraph(0);
assert_eq!(cursor.grapheme_index(), 0);
assert_eq!(cursor.line_index(), 0);
cursor.move_paragraph(1);
assert_eq!(cursor.grapheme_index(), 0);
assert_eq!(cursor.line_index(), 3);
cursor.move_paragraph(1);
assert_eq!(cursor.grapheme_index(), 0);
assert_eq!(cursor.line_index(), 6);
cursor.move_paragraph(1);
assert_eq!(cursor.grapheme_index(), 2);
assert_eq!(cursor.line_index(), 8);
cursor.move_paragraph(1);
assert_eq!(cursor.grapheme_index(), 2);
assert_eq!(cursor.line_index(), 8);
cursor.move_paragraph(-1);
assert_eq!(cursor.grapheme_index(), 0);
assert_eq!(cursor.line_index(), 7);
cursor.move_paragraph(-1);
assert_eq!(cursor.grapheme_index(), 0);
assert_eq!(cursor.line_index(), 3);
cursor.move_paragraph(-1);
assert_eq!(cursor.grapheme_index(), 0);
assert_eq!(cursor.line_index(), 0);
cursor.move_paragraph(-1);
assert_eq!(cursor.grapheme_index(), 0);
assert_eq!(cursor.line_index(), 0);
cursor.move_paragraph(2);
assert_eq!(cursor.grapheme_index(), 0);
assert_eq!(cursor.line_index(), 6);
cursor.move_paragraph(2);
assert_eq!(cursor.grapheme_index(), 2);
assert_eq!(cursor.line_index(), 8);
cursor.move_paragraph(2);
assert_eq!(cursor.grapheme_index(), 2);
assert_eq!(cursor.line_index(), 8);
}
......@@ -170,6 +170,19 @@ impl Token {
}
}
pub fn is_newline_line(&self) -> bool {
match *self {
Token::Line { ref children, .. } => {
if let Some(first_id) = children.first() {
arena().read(first_id, &|first| first.is_newline_word())
} else {
false
}
}
_ => false,
}
}
pub fn is_word_grapheme(&self) -> bool {
self.value_or(false, &|value| WORD_PATTERN.is_match(value.as_ref()))
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment