Move cursor motions to new Motion enum, move handling to Buffer

This commit is contained in:
Jeremy Soller 2023-12-15 09:13:14 -07:00
parent 6528e9f804
commit 018a2e9d2a
10 changed files with 567 additions and 505 deletions

View file

@ -8,8 +8,8 @@ use unicode_segmentation::UnicodeSegmentation;
#[cfg(feature = "swash")]
use crate::Color;
use crate::{
Action, Affinity, AttrsList, Buffer, BufferLine, Change, ChangeItem, Cursor, Edit, FontSystem,
LayoutCursor, Selection, Shaping,
Action, AttrsList, Buffer, BufferLine, Change, ChangeItem, Cursor, Edit, FontSystem, Selection,
Shaping,
};
/// A wrapper of [`Buffer`] for easy editing
@ -39,40 +39,6 @@ impl Editor {
change: None,
}
}
fn set_layout_cursor(&mut self, font_system: &mut FontSystem, cursor: LayoutCursor) {
let layout = self
.buffer
.line_layout(font_system, cursor.line)
.expect("layout not found");
let layout_line = match layout.get(cursor.layout) {
Some(some) => some,
None => match layout.last() {
Some(some) => some,
None => todo!("layout cursor in line with no layouts"),
},
};
let (new_index, new_affinity) = match layout_line.glyphs.get(cursor.glyph) {
Some(glyph) => (glyph.start, Affinity::After),
None => match layout_line.glyphs.last() {
Some(glyph) => (glyph.end, Affinity::Before),
//TODO: is this correct?
None => (0, Affinity::After),
},
};
if self.cursor.line != cursor.line
|| self.cursor.index != new_index
|| self.cursor.affinity != new_affinity
{
self.cursor.line = cursor.line;
self.cursor.index = new_index;
self.cursor.affinity = new_affinity;
self.buffer.set_redraw(true);
}
}
}
impl Edit for Editor {
@ -377,181 +343,11 @@ impl Edit for Editor {
let old_cursor = self.cursor;
match action {
Action::Previous => {
let line = &self.buffer.lines[self.cursor.line];
if self.cursor.index > 0 {
// Find previous character index
let mut prev_index = 0;
for (i, _) in line.text().grapheme_indices(true) {
if i < self.cursor.index {
prev_index = i;
} else {
break;
}
}
self.cursor.index = prev_index;
self.cursor.affinity = Affinity::After;
self.buffer.set_redraw(true);
} else if self.cursor.line > 0 {
self.cursor.line -= 1;
self.cursor.index = self.buffer.lines[self.cursor.line].text().len();
self.cursor.affinity = Affinity::After;
self.buffer.set_redraw(true);
}
self.cursor_x_opt = None;
}
Action::Next => {
let line = &self.buffer.lines[self.cursor.line];
if self.cursor.index < line.text().len() {
for (i, c) in line.text().grapheme_indices(true) {
if i == self.cursor.index {
self.cursor.index += c.len();
self.cursor.affinity = Affinity::Before;
self.buffer.set_redraw(true);
break;
}
}
} else if self.cursor.line + 1 < self.buffer.lines.len() {
self.cursor.line += 1;
self.cursor.index = 0;
self.cursor.affinity = Affinity::Before;
self.buffer.set_redraw(true);
}
self.cursor_x_opt = None;
}
Action::Left => {
let rtl_opt = self
.buffer
.line_shape(font_system, self.cursor.line)
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
self.action(font_system, Action::Next);
} else {
self.action(font_system, Action::Previous);
}
}
}
Action::Right => {
let rtl_opt = self
.buffer
.line_shape(font_system, self.cursor.line)
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
self.action(font_system, Action::Previous);
} else {
self.action(font_system, Action::Next);
}
}
}
Action::Up => {
//TODO: make this preserve X as best as possible!
let mut cursor = self.buffer.layout_cursor(&self.cursor);
if self.cursor_x_opt.is_none() {
self.cursor_x_opt = Some(
cursor.glyph as i32, //TODO: glyph x position
);
}
if cursor.layout > 0 {
cursor.layout -= 1;
} else if cursor.line > 0 {
cursor.line -= 1;
cursor.layout = usize::max_value();
}
if let Some(cursor_x) = self.cursor_x_opt {
cursor.glyph = cursor_x as usize; //TODO: glyph x position
}
self.set_layout_cursor(font_system, cursor);
}
Action::Down => {
//TODO: make this preserve X as best as possible!
let mut cursor = self.buffer.layout_cursor(&self.cursor);
let layout_len = self
.buffer
.line_layout(font_system, cursor.line)
.expect("layout not found")
.len();
if self.cursor_x_opt.is_none() {
self.cursor_x_opt = Some(
cursor.glyph as i32, //TODO: glyph x position
);
}
if cursor.layout + 1 < layout_len {
cursor.layout += 1;
} else if cursor.line + 1 < self.buffer.lines.len() {
cursor.line += 1;
cursor.layout = 0;
}
if let Some(cursor_x) = self.cursor_x_opt {
cursor.glyph = cursor_x as usize; //TODO: glyph x position
}
self.set_layout_cursor(font_system, cursor);
}
Action::Home => {
let mut cursor = self.buffer.layout_cursor(&self.cursor);
cursor.glyph = 0;
self.set_layout_cursor(font_system, cursor);
self.cursor_x_opt = None;
}
Action::SoftHome => {
let line = &self.buffer.lines[self.cursor.line];
self.cursor.index = line
.text()
.char_indices()
.filter_map(|(i, c)| if c.is_whitespace() { None } else { Some(i) })
.next()
.unwrap_or(0);
self.buffer.set_redraw(true);
self.cursor_x_opt = None;
}
Action::End => {
let mut cursor = self.buffer.layout_cursor(&self.cursor);
cursor.glyph = usize::max_value();
self.set_layout_cursor(font_system, cursor);
self.cursor_x_opt = None;
}
Action::ParagraphStart => {
self.cursor.index = 0;
self.cursor_x_opt = None;
self.buffer.set_redraw(true);
}
Action::ParagraphEnd => {
self.cursor.index = self.buffer.lines[self.cursor.line].text().len();
self.cursor_x_opt = None;
self.buffer.set_redraw(true);
}
Action::PageUp => {
self.action(font_system, Action::Vertical(-self.buffer.size().1 as i32));
}
Action::PageDown => {
self.action(font_system, Action::Vertical(self.buffer.size().1 as i32));
}
Action::Vertical(px) => {
// TODO more efficient
let lines = px / self.buffer.metrics().line_height as i32;
match lines.cmp(&0) {
cmp::Ordering::Less => {
for _ in 0..-lines {
self.action(font_system, Action::Up);
}
}
cmp::Ordering::Greater => {
for _ in 0..lines {
self.action(font_system, Action::Down);
}
}
cmp::Ordering::Equal => {}
Action::Motion(motion) => {
if let Some(new_cursor) =
self.buffer.cursor_motion(font_system, self.cursor, motion)
{
self.cursor = new_cursor;
}
}
Action::Escape => {
@ -837,88 +633,11 @@ impl Edit for Editor {
scroll += lines;
self.buffer.set_scroll(scroll);
}
Action::PreviousWord => {
let line = &self.buffer.lines[self.cursor.line];
if self.cursor.index > 0 {
self.cursor.index = line
.text()
.unicode_word_indices()
.rev()
.map(|(i, _)| i)
.find(|&i| i < self.cursor.index)
.unwrap_or(0);
self.buffer.set_redraw(true);
} else if self.cursor.line > 0 {
self.cursor.line -= 1;
self.cursor.index = self.buffer.lines[self.cursor.line].text().len();
self.buffer.set_redraw(true);
}
self.cursor_x_opt = None;
}
Action::NextWord => {
let line = &self.buffer.lines[self.cursor.line];
if self.cursor.index < line.text().len() {
self.cursor.index = line
.text()
.unicode_word_indices()
.map(|(i, word)| i + word.len())
.find(|&i| i > self.cursor.index)
.unwrap_or(line.text().len());
self.buffer.set_redraw(true);
} else if self.cursor.line + 1 < self.buffer.lines.len() {
self.cursor.line += 1;
self.cursor.index = 0;
self.buffer.set_redraw(true);
}
self.cursor_x_opt = None;
}
Action::LeftWord => {
let rtl_opt = self
.buffer
.line_shape(font_system, self.cursor.line)
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
self.action(font_system, Action::NextWord);
} else {
self.action(font_system, Action::PreviousWord);
}
}
}
Action::RightWord => {
let rtl_opt = self
.buffer
.line_shape(font_system, self.cursor.line)
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
self.action(font_system, Action::PreviousWord);
} else {
self.action(font_system, Action::NextWord);
}
}
}
Action::BufferStart => {
self.cursor.line = 0;
self.cursor.index = 0;
self.cursor_x_opt = None;
}
Action::BufferEnd => {
self.cursor.line = self.buffer.lines.len() - 1;
self.cursor.index = self.buffer.lines[self.cursor.line].text().len();
self.cursor_x_opt = None;
}
Action::GotoLine(line) => {
let mut cursor = self.buffer.layout_cursor(&self.cursor);
cursor.line = line;
self.set_layout_cursor(font_system, cursor);
}
}
if old_cursor != self.cursor {
self.cursor_moved = true;
self.buffer.set_redraw(true);
/*TODO
if let Some(glyph) = run.glyphs.get(new_cursor_glyph) {

View file

@ -5,7 +5,7 @@ use unicode_segmentation::UnicodeSegmentation;
#[cfg(feature = "swash")]
use crate::Color;
use crate::{AttrsList, BorrowedWithFontSystem, Buffer, Cursor, FontSystem};
use crate::{AttrsList, BorrowedWithFontSystem, Buffer, Cursor, FontSystem, Motion};
pub use self::editor::*;
mod editor;
@ -23,34 +23,8 @@ mod vi;
/// An action to perform on an [`Editor`]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Action {
/// Move cursor to previous character ([Self::Left] in LTR, [Self::Right] in RTL)
Previous,
/// Move cursor to next character ([Self::Right] in LTR, [Self::Left] in RTL)
Next,
/// Move cursor left
Left,
/// Move cursor right
Right,
/// Move cursor up
Up,
/// Move cursor down
Down,
/// Move cursor to start of line
Home,
/// Move cursor to start of line, skipping whitespace
SoftHome,
/// Move cursor to end of line
End,
/// Move cursor to start of paragraph
ParagraphStart,
/// Move cursor to end of paragraph
ParagraphEnd,
/// Move cursor up one page
PageUp,
/// Move cursor down one page
PageDown,
/// Move cursor up or down by a number of pixels
Vertical(i32),
/// Move the cursor with some motion
Motion(Motion),
/// Escape, clears selection
Escape,
/// Insert character at cursor
@ -89,20 +63,6 @@ pub enum Action {
Scroll {
lines: i32,
},
/// Move cursor to previous word boundary
PreviousWord,
/// Move cursor to next word boundary
NextWord,
/// Move cursor to next word boundary to the left
LeftWord,
/// Move cursor to next word boundary to the right
RightWord,
/// Move cursor to the start of the document
BufferStart,
/// Move cursor to the end of the document
BufferEnd,
/// Move cursor to specific line
GotoLine(usize),
}
/// A unique change to an editor

View file

@ -1,11 +1,11 @@
use alloc::{collections::BTreeMap, string::String};
use core::cmp;
use modit::{Event, Key, Motion, Parser, TextObject, WordIter};
use modit::{Event, Key, Parser, TextObject, WordIter};
use unicode_segmentation::UnicodeSegmentation;
use crate::{
Action, AttrsList, BorrowedWithFontSystem, Buffer, Change, Color, Cursor, Edit, FontSystem,
Selection, SyntaxEditor, SyntaxTheme,
Motion, Selection, SyntaxEditor, SyntaxTheme,
};
pub use modit::{ViMode, ViParser};
@ -342,19 +342,19 @@ impl<'a> Edit for ViEditor<'a> {
//TODO: this leaves lots of room for issues in translation, should we directly accept Key?
Action::Backspace => Key::Backspace,
Action::Delete => Key::Delete,
Action::Down => Key::Down,
Action::End => Key::End,
Action::Motion(Motion::Down) => Key::Down,
Action::Motion(Motion::End) => Key::End,
Action::Enter => Key::Enter,
Action::Escape => Key::Escape,
Action::Home => Key::Home,
Action::Motion(Motion::Home) => Key::Home,
Action::Indent => Key::Tab,
Action::Insert(c) => Key::Char(c),
Action::Left => Key::Left,
Action::PageDown => Key::PageDown,
Action::PageUp => Key::PageUp,
Action::Right => Key::Right,
Action::Motion(Motion::Left) => Key::Left,
Action::Motion(Motion::PageDown) => Key::PageDown,
Action::Motion(Motion::PageUp) => Key::PageUp,
Action::Motion(Motion::Right) => Key::Right,
Action::Unindent => Key::Backtab,
Action::Up => Key::Up,
Action::Motion(Motion::Up) => Key::Up,
_ => {
log::debug!("Pass through action {:?}", action);
editor.action(font_system, action);
@ -448,9 +448,9 @@ impl<'a> Edit for ViEditor<'a> {
// Move to inserted line, preserving cursor x position
if after {
editor.action(font_system, Action::Down);
editor.action(font_system, Action::Motion(Motion::Down));
} else {
editor.action(font_system, Action::Up);
editor.action(font_system, Action::Motion(Motion::Up));
}
}
}
@ -550,35 +550,37 @@ impl<'a> Edit for ViEditor<'a> {
}
Event::Motion(motion) => {
match motion {
Motion::Around => {
modit::Motion::Around => {
//TODO: what to do for this psuedo-motion?
return;
}
Motion::Down => Action::Down,
Motion::End => Action::End,
Motion::GotoLine(line) => Action::GotoLine(line.saturating_sub(1)),
Motion::GotoEof => {
Action::GotoLine(editor.buffer().lines.len().saturating_sub(1))
modit::Motion::Down => Action::Motion(Motion::Down),
modit::Motion::End => Action::Motion(Motion::End),
modit::Motion::GotoLine(line) => {
Action::Motion(Motion::GotoLine(line.saturating_sub(1)))
}
Motion::Home => Action::Home,
Motion::Inside => {
modit::Motion::GotoEof => Action::Motion(Motion::GotoLine(
editor.buffer().lines.len().saturating_sub(1),
)),
modit::Motion::Home => Action::Motion(Motion::Home),
modit::Motion::Inside => {
//TODO: what to do for this psuedo-motion?
return;
}
Motion::Left => Action::Left,
Motion::LeftInLine => {
modit::Motion::Left => Action::Motion(Motion::Left),
modit::Motion::LeftInLine => {
let cursor = editor.cursor();
if cursor.index > 0 {
Action::Left
Action::Motion(Motion::Left)
} else {
return;
}
}
Motion::Line => {
modit::Motion::Line => {
//TODO: what to do for this psuedo-motion?
return;
}
Motion::NextChar(find_c) => {
modit::Motion::NextChar(find_c) => {
let mut cursor = editor.cursor();
let buffer = editor.buffer();
let text = buffer.lines[cursor.line].text();
@ -597,7 +599,7 @@ impl<'a> Edit for ViEditor<'a> {
}
return;
}
Motion::NextCharTill(find_c) => {
modit::Motion::NextCharTill(find_c) => {
let mut cursor = editor.cursor();
let buffer = editor.buffer();
let text = buffer.lines[cursor.line].text();
@ -615,14 +617,14 @@ impl<'a> Edit for ViEditor<'a> {
}
return;
}
Motion::NextSearch => match &self.search_opt {
modit::Motion::NextSearch => match &self.search_opt {
Some((value, forwards)) => {
search(editor, value, *forwards);
return;
}
None => return,
},
Motion::NextWordEnd(word) => {
modit::Motion::NextWordEnd(word) => {
let mut cursor = editor.cursor();
let buffer = editor.buffer();
loop {
@ -649,7 +651,7 @@ impl<'a> Edit for ViEditor<'a> {
editor.set_cursor(cursor);
return;
}
Motion::NextWordStart(word) => {
modit::Motion::NextWordStart(word) => {
let mut cursor = editor.cursor();
let buffer = editor.buffer();
loop {
@ -674,9 +676,9 @@ impl<'a> Edit for ViEditor<'a> {
editor.set_cursor(cursor);
return;
}
Motion::PageDown => Action::PageDown,
Motion::PageUp => Action::PageUp,
Motion::PreviousChar(find_c) => {
modit::Motion::PageDown => Action::Motion(Motion::PageDown),
modit::Motion::PageUp => Action::Motion(Motion::PageUp),
modit::Motion::PreviousChar(find_c) => {
let mut cursor = editor.cursor();
let buffer = editor.buffer();
let text = buffer.lines[cursor.line].text();
@ -695,7 +697,7 @@ impl<'a> Edit for ViEditor<'a> {
}
return;
}
Motion::PreviousCharTill(find_c) => {
modit::Motion::PreviousCharTill(find_c) => {
let mut cursor = editor.cursor();
let buffer = editor.buffer();
let text = buffer.lines[cursor.line].text();
@ -722,14 +724,14 @@ impl<'a> Edit for ViEditor<'a> {
}
return;
}
Motion::PreviousSearch => match &self.search_opt {
modit::Motion::PreviousSearch => match &self.search_opt {
Some((value, forwards)) => {
search(editor, value, !*forwards);
return;
}
None => return,
},
Motion::PreviousWordEnd(word) => {
modit::Motion::PreviousWordEnd(word) => {
let mut cursor = editor.cursor();
let buffer = editor.buffer();
loop {
@ -757,7 +759,7 @@ impl<'a> Edit for ViEditor<'a> {
editor.set_cursor(cursor);
return;
}
Motion::PreviousWordStart(word) => {
modit::Motion::PreviousWordStart(word) => {
let mut cursor = editor.cursor();
let buffer = editor.buffer();
loop {
@ -783,38 +785,40 @@ impl<'a> Edit for ViEditor<'a> {
editor.set_cursor(cursor);
return;
}
Motion::Right => Action::Right,
Motion::RightInLine => {
modit::Motion::Right => Action::Motion(Motion::Right),
modit::Motion::RightInLine => {
let cursor = editor.cursor();
let buffer = editor.buffer();
if cursor.index < buffer.lines[cursor.line].text().len() {
Action::Right
Action::Motion(Motion::Right)
} else {
return;
}
}
Motion::ScreenHigh => {
modit::Motion::ScreenHigh => {
//TODO: is this efficient?
if let Some(first) = editor.buffer().layout_runs().next() {
Action::GotoLine(first.line_i)
Action::Motion(Motion::GotoLine(first.line_i))
} else {
return;
}
}
Motion::ScreenLow => {
modit::Motion::ScreenLow => {
//TODO: is this efficient?
if let Some(last) = editor.buffer().layout_runs().last() {
Action::GotoLine(last.line_i)
Action::Motion(Motion::GotoLine(last.line_i))
} else {
return;
}
}
Motion::ScreenMiddle => {
modit::Motion::ScreenMiddle => {
//TODO: is this efficient?
let mut layout_runs = editor.buffer().layout_runs();
if let Some(first) = layout_runs.next() {
if let Some(last) = layout_runs.last() {
Action::GotoLine((last.line_i + first.line_i) / 2)
Action::Motion(Motion::GotoLine(
(last.line_i + first.line_i) / 2,
))
} else {
return;
}
@ -822,12 +826,12 @@ impl<'a> Edit for ViEditor<'a> {
return;
}
}
Motion::Selection => {
modit::Motion::Selection => {
//TODO: what to do for this psuedo-motion?
return;
}
Motion::SoftHome => Action::SoftHome,
Motion::Up => Action::Up,
modit::Motion::SoftHome => Action::Motion(Motion::SoftHome),
modit::Motion::Up => Action::Motion(Motion::Up),
}
}
};