ViEditor: switch to using modit

This commit is contained in:
Jeremy Soller 2023-11-07 15:57:00 -07:00
parent 659001dad8
commit 74c92e0419
2 changed files with 205 additions and 251 deletions

View file

@ -26,6 +26,12 @@ unicode-linebreak = "0.1.5"
unicode-script = "0.5.5" unicode-script = "0.5.5"
unicode-segmentation = "1.10.1" unicode-segmentation = "1.10.1"
#TODO: crates release
[dependencies.modit]
git = "https://github.com/pop-os/modit.git"
optional = true
#path = "../modit"
[dependencies.unicode-bidi] [dependencies.unicode-bidi]
version = "0.3.13" version = "0.3.13"
default-features = false default-features = false
@ -41,7 +47,7 @@ std = [
"sys-locale", "sys-locale",
"unicode-bidi/std", "unicode-bidi/std",
] ]
vi = ["syntect"] vi = ["modit", "syntect"]
wasm-web = ["sys-locale?/js"] wasm-web = ["sys-locale?/js"]
warn_on_missing_glyphs = [] warn_on_missing_glyphs = []
fontconfig = ["fontdb/fontconfig", "std"] fontconfig = ["fontdb/fontconfig", "std"]

View file

@ -1,5 +1,6 @@
use alloc::string::String; use alloc::string::String;
use core::cmp; use core::cmp;
use modit::{Event, Motion, Operator, Parser, WordIter};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use crate::{ use crate::{
@ -7,24 +8,13 @@ use crate::{
SyntaxEditor, SyntaxTheme, SyntaxEditor, SyntaxTheme,
}; };
#[derive(Clone, Debug, Eq, PartialEq)] pub use modit::{ViMode, ViParser};
pub enum ViMode {
/// Passthrough mode, disables vi features
Passthrough,
/// Normal mode
Normal,
/// Insert mode
Insert,
/// Command mode
Command { value: String },
/// Search mode
Search { value: String, forwards: bool },
}
#[derive(Debug)] #[derive(Debug)]
pub struct ViEditor<'a> { pub struct ViEditor<'a> {
editor: SyntaxEditor<'a>, editor: SyntaxEditor<'a>,
mode: ViMode, parser: ViParser,
passthrough: bool,
search_opt: Option<(String, bool)>, search_opt: Option<(String, bool)>,
} }
@ -32,7 +22,8 @@ impl<'a> ViEditor<'a> {
pub fn new(editor: SyntaxEditor<'a>) -> Self { pub fn new(editor: SyntaxEditor<'a>) -> Self {
Self { Self {
editor, editor,
mode: ViMode::Normal, parser: ViParser::new(),
passthrough: false,
search_opt: None, search_opt: None,
} }
} }
@ -70,19 +61,15 @@ impl<'a> ViEditor<'a> {
/// Set passthrough mode (true will turn off vi features) /// Set passthrough mode (true will turn off vi features)
pub fn set_passthrough(&mut self, passthrough: bool) { pub fn set_passthrough(&mut self, passthrough: bool) {
if passthrough != (self.mode == ViMode::Passthrough) { if passthrough != self.passthrough {
if passthrough { self.passthrough = passthrough;
self.mode = ViMode::Passthrough;
} else {
self.mode = ViMode::Normal;
}
self.buffer_mut().set_redraw(true); self.buffer_mut().set_redraw(true);
} }
} }
/// Get current vi editing mode /// Get current vi parser
pub fn mode(&self) -> &ViMode { pub fn parser(&self) -> &ViParser {
&self.mode &self.parser
} }
fn search(&mut self, inverted: bool) { fn search(&mut self, inverted: bool) {
@ -194,231 +181,189 @@ impl<'a> Edit for ViEditor<'a> {
} }
fn action(&mut self, font_system: &mut FontSystem, action: Action) { fn action(&mut self, font_system: &mut FontSystem, action: Action) {
let old_mode = self.mode.clone(); let editor = &mut self.editor;
let c = match action {
Action::Escape => modit::ESCAPE,
Action::Insert(c) => c,
Action::Enter => modit::ENTER,
Action::Backspace => modit::BACKSPACE,
Action::Delete => modit::DELETE,
_ => return editor.action(font_system, action),
};
//TODO: redraw on parser mode change
self.parser.parse(c, false, |event| {
log::info!("{:?}", event);
let action = match event {
Event::Backspace => Action::Backspace,
Event::Delete => Action::Delete,
Event::Down => Action::Down,
Event::End => Action::End,
Event::Escape => Action::Escape,
Event::Home => Action::Home,
Event::Insert(c) => Action::Insert(c),
Event::Left => Action::Left,
Event::NewLine => Action::Enter,
Event::Operator(count, operator, motion, text_object_opt) => {
let start = editor.cursor();
match self.mode { for _ in 0..count {
ViMode::Passthrough => self.editor.action(font_system, action), let action = match motion {
ViMode::Normal => match action { Motion::Down => Action::Down,
Action::Insert(c) => match c { Motion::End => Action::End,
// Enter insert mode after cursor Motion::GotoLine(line) => Action::GotoLine(line.saturating_sub(1)),
'a' => { Motion::GotoEof => {
self.editor.action(font_system, Action::Right); Action::GotoLine(editor.buffer().lines.len().saturating_sub(1))
self.mode = ViMode::Insert; }
} Motion::Home => Action::Home,
// Enter insert mode at end of line Motion::Left => Action::Left,
'A' => { Motion::NextWordEnd(word) => {
self.editor.action(font_system, Action::End); let mut cursor = editor.cursor();
self.mode = ViMode::Insert; let buffer = editor.buffer_mut();
} loop {
// Previous word let text = buffer.lines[cursor.line].text();
'b' => { if cursor.index < text.len() {
//TODO: WORD vs word, iterate by vi word rules cursor.index = WordIter::new(text, word)
self.editor.action(font_system, Action::PreviousWord); .map(|(i, w)| {
} i + w
// Previous WORD .char_indices()
'B' => { .last()
//TODO: WORD vs word, iterate by vi word rules .map(|(i, _)| i)
self.editor.action(font_system, Action::PreviousWord); .unwrap_or(0)
} })
// Change mode .find(|&i| i > cursor.index)
'c' => { .unwrap_or(text.len());
if self.editor.select_opt().is_some() { if cursor.index == text.len() {
self.editor.action(font_system, Action::Delete); // Try again, searching next line
self.mode = ViMode::Insert; continue;
} else { }
//TODO: change to next cursor movement } else if cursor.line + 1 < buffer.lines.len() {
} // Go to next line and rerun loop
} cursor.line += 1;
// Delete mode cursor.index = 0;
'd' => { continue;
if self.editor.select_opt().is_some() { }
self.editor.action(font_system, Action::Delete); break;
} else { }
//TODO: delete to next cursor movement editor.set_cursor(cursor);
} continue;
} }
// End of word Motion::NextWordStart(word) => {
'e' => { let mut cursor = editor.cursor();
//TODO: WORD vs word, iterate by vi word rules let buffer = editor.buffer_mut();
self.editor.action(font_system, Action::NextWord); loop {
} let text = buffer.lines[cursor.line].text();
// End of WORD if cursor.index < text.len() {
'E' => { cursor.index = WordIter::new(text, word)
//TODO: WORD vs word, iterate by vi word rules .map(|(i, _)| i)
self.editor.action(font_system, Action::NextWord); .find(|&i| i > cursor.index)
} .unwrap_or(text.len());
// Enter insert mode at cursor if cursor.index == text.len() {
'i' => { // Try again, searching next line
self.mode = ViMode::Insert; continue;
} }
// Enter insert mode at start of line } else if cursor.line + 1 < buffer.lines.len() {
'I' => { // Go to next line and rerun loop
self.editor.action(font_system, Action::SoftHome); cursor.line += 1;
self.mode = ViMode::Insert; cursor.index = 0;
} continue;
// Create line after and enter insert mode }
'o' => { break;
self.editor.action(font_system, Action::End); }
self.editor.action(font_system, Action::Enter); editor.set_cursor(cursor);
self.mode = ViMode::Insert; continue;
} }
// Create line before and enter insert mode Motion::PreviousWordEnd(word) => {
'O' => { let mut cursor = editor.cursor();
self.editor.action(font_system, Action::Home); let buffer = editor.buffer_mut();
self.editor.action(font_system, Action::Enter); loop {
self.editor.shape_as_needed(font_system); // TODO: do not require this? let text = buffer.lines[cursor.line].text();
self.editor.action(font_system, Action::Up); if cursor.index > 0 {
self.mode = ViMode::Insert; cursor.index = WordIter::new(text, word)
} .map(|(i, w)| {
// Left i + w
'h' => self.editor.action(font_system, Action::Left), .char_indices()
// Top of screen .last()
//TODO: 'H' => self.editor.action(Action::ScreenHigh), .map(|(i, _)| i)
// Down .unwrap_or(0)
'j' => self.editor.action(font_system, Action::Down), })
// Up .filter(|&i| i < cursor.index)
'k' => self.editor.action(font_system, Action::Up), .last()
// Right .unwrap_or(0);
'l' | ' ' => self.editor.action(font_system, Action::Right), if cursor.index == 0 {
// Bottom of screen // Try again, searching previous line
//TODO: 'L' => self.editor.action(Action::ScreenLow), continue;
// Middle of screen }
//TODO: 'M' => self.editor.action(Action::ScreenMiddle), } else if cursor.line > 0 {
// Next search item // Go to previous line and rerun loop
'n' => self.search(false), cursor.line -= 1;
// Previous search item cursor.index = buffer.lines[cursor.line].text().len();
'N' => self.search(true), continue;
// Enter visual mode }
'v' => { break;
if self.editor.select_opt().is_some() { }
self.editor.set_select_opt(None); editor.set_cursor(cursor);
} else { continue;
self.editor.set_select_opt(Some(self.editor.cursor())); }
} Motion::PreviousWordStart(word) => {
} let mut cursor = editor.cursor();
// Enter line visual mode let buffer = editor.buffer_mut();
'V' => { loop {
if self.editor.select_opt().is_some() { let text = buffer.lines[cursor.line].text();
self.editor.set_select_opt(None); if cursor.index > 0 {
} else { cursor.index = WordIter::new(text, word)
self.editor.action(font_system, Action::Home); .map(|(i, _)| i)
self.editor.set_select_opt(Some(self.editor.cursor())); .filter(|&i| i < cursor.index)
//TODO: set cursor_x_opt to max .last()
self.editor.action(font_system, Action::End); .unwrap_or(0);
} if cursor.index == 0 {
} // Try again, searching previous line
// Next word continue;
'w' => { }
//TODO: WORD vs word, iterate by vi word rules } else if cursor.line > 0 {
self.editor.action(font_system, Action::NextWord); // Go to previous line and rerun loop
} cursor.line -= 1;
// Next WORD cursor.index = buffer.lines[cursor.line].text().len();
'W' => { continue;
//TODO: WORD vs word, iterate by vi word rules }
self.editor.action(font_system, Action::NextWord); break;
} }
// Remove character at cursor editor.set_cursor(cursor);
'x' => self.editor.action(font_system, Action::Delete), continue;
// Remove character before cursor }
'X' => self.editor.action(font_system, Action::Backspace), Motion::Right => Action::Right,
// Go to start of line Motion::SoftHome => Action::SoftHome,
'0' => self.editor.action(font_system, Action::Home), Motion::Up => Action::Up,
// Go to end of line _ => {
'$' => self.editor.action(font_system, Action::End), log::info!("TODO");
// Go to start of line after whitespace break;
'^' => self.editor.action(font_system, Action::SoftHome), }
// Enter command mode
':' => {
self.mode = ViMode::Command {
value: String::new(),
}; };
editor.action(font_system, action);
} }
// Enter search mode
'/' => {
self.mode = ViMode::Search {
value: String::new(),
forwards: true,
};
}
// Enter search backwards mode
'?' => {
self.mode = ViMode::Search {
value: String::new(),
forwards: false,
};
}
_ => (),
},
// Go to start of next line
Action::Enter => {
self.editor.action(font_system, Action::Down);
self.editor.action(font_system, Action::SoftHome);
}
// Left
Action::Backspace => {
self.editor.action(font_system, Action::Left);
}
_ => self.editor.action(font_system, action),
},
ViMode::Insert => match action {
Action::Escape => {
let cursor = self.cursor();
let layout_cursor = self.buffer().layout_cursor(&cursor);
if layout_cursor.glyph > 0 {
self.editor.action(font_system, Action::Left);
}
self.mode = ViMode::Normal;
}
_ => self.editor.action(font_system, action),
},
ViMode::Command { ref mut value } => match action {
Action::Escape => {
self.mode = ViMode::Normal;
}
Action::Insert(c) => match c {
_ => {
value.push(c);
}
},
Action::Enter => {
//TODO: run command
self.mode = ViMode::Normal;
}
Action::Backspace => {
if value.pop().is_none() {
self.mode = ViMode::Normal;
}
}
_ => self.editor.action(font_system, action),
},
ViMode::Search {
ref mut value,
forwards,
} => match action {
Action::Escape => {
self.mode = ViMode::Normal;
}
Action::Insert(c) => {
value.push(c);
}
Action::Enter => {
//TODO: do not require clone?
self.search_opt = Some((value.clone(), forwards));
self.mode = ViMode::Normal;
self.search(false);
}
Action::Backspace => {
if value.pop().is_none() {
self.mode = ViMode::Normal;
}
}
_ => self.editor.action(font_system, action),
},
}
if self.mode != old_mode { let end = editor.cursor();
self.buffer_mut().set_redraw(true);
} println!("start {:?}, end {:?}", start, end);
return;
}
Event::Paste => {
log::info!("TODO");
return;
}
Event::Replace(char) => {
log::info!("TODO");
return;
}
Event::Right => Action::Right,
Event::SoftHome => Action::SoftHome,
Event::Undo => {
log::info!("TODO");
return;
}
Event::Up => Action::Up,
};
editor.action(font_system, action);
});
} }
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
@ -558,11 +503,14 @@ impl<'a> Edit for ViEditor<'a> {
if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) = if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) =
cursor_glyph_opt(&self.cursor()) cursor_glyph_opt(&self.cursor())
{ {
let block_cursor = match self.mode { let block_cursor = if self.passthrough {
ViMode::Passthrough => false, false
ViMode::Normal => true, } else {
ViMode::Insert => false, match self.parser.mode {
_ => true, /*TODO: determine block cursor in other modes*/ ViMode::Normal(_) => true,
ViMode::Insert => false,
_ => true, /*TODO: determine block cursor in other modes*/
}
}; };
let (start_x, end_x) = match run.glyphs.get(cursor_glyph) { let (start_x, end_x) = match run.glyphs.get(cursor_glyph) {