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;
} }
// Enter insert mode at end of line Motion::Home => Action::Home,
'A' => { Motion::Left => Action::Left,
self.editor.action(font_system, Action::End); Motion::NextWordEnd(word) => {
self.mode = ViMode::Insert; let mut cursor = editor.cursor();
let buffer = editor.buffer_mut();
loop {
let text = buffer.lines[cursor.line].text();
if cursor.index < text.len() {
cursor.index = WordIter::new(text, word)
.map(|(i, w)| {
i + w
.char_indices()
.last()
.map(|(i, _)| i)
.unwrap_or(0)
})
.find(|&i| i > cursor.index)
.unwrap_or(text.len());
if cursor.index == text.len() {
// Try again, searching next line
continue;
} }
// Previous word } else if cursor.line + 1 < buffer.lines.len() {
'b' => { // Go to next line and rerun loop
//TODO: WORD vs word, iterate by vi word rules cursor.line += 1;
self.editor.action(font_system, Action::PreviousWord); cursor.index = 0;
continue;
} }
// Previous WORD break;
'B' => {
//TODO: WORD vs word, iterate by vi word rules
self.editor.action(font_system, Action::PreviousWord);
} }
// Change mode editor.set_cursor(cursor);
'c' => { continue;
if self.editor.select_opt().is_some() {
self.editor.action(font_system, Action::Delete);
self.mode = ViMode::Insert;
} else {
//TODO: change to next cursor movement
} }
Motion::NextWordStart(word) => {
let mut cursor = editor.cursor();
let buffer = editor.buffer_mut();
loop {
let text = buffer.lines[cursor.line].text();
if cursor.index < text.len() {
cursor.index = WordIter::new(text, word)
.map(|(i, _)| i)
.find(|&i| i > cursor.index)
.unwrap_or(text.len());
if cursor.index == text.len() {
// Try again, searching next line
continue;
} }
// Delete mode } else if cursor.line + 1 < buffer.lines.len() {
'd' => { // Go to next line and rerun loop
if self.editor.select_opt().is_some() { cursor.line += 1;
self.editor.action(font_system, Action::Delete); cursor.index = 0;
} else { continue;
//TODO: delete to next cursor movement
} }
break;
} }
// End of word editor.set_cursor(cursor);
'e' => { continue;
//TODO: WORD vs word, iterate by vi word rules
self.editor.action(font_system, Action::NextWord);
} }
// End of WORD Motion::PreviousWordEnd(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();
if cursor.index > 0 {
cursor.index = WordIter::new(text, word)
.map(|(i, w)| {
i + w
.char_indices()
.last()
.map(|(i, _)| i)
.unwrap_or(0)
})
.filter(|&i| i < cursor.index)
.last()
.unwrap_or(0);
if cursor.index == 0 {
// Try again, searching previous line
continue;
} }
// Enter insert mode at cursor } else if cursor.line > 0 {
'i' => { // Go to previous line and rerun loop
self.mode = ViMode::Insert; cursor.line -= 1;
cursor.index = buffer.lines[cursor.line].text().len();
continue;
} }
// Enter insert mode at start of line break;
'I' => {
self.editor.action(font_system, Action::SoftHome);
self.mode = ViMode::Insert;
} }
// Create line after and enter insert mode editor.set_cursor(cursor);
'o' => { continue;
self.editor.action(font_system, Action::End);
self.editor.action(font_system, Action::Enter);
self.mode = ViMode::Insert;
} }
// Create line before and enter insert mode Motion::PreviousWordStart(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, _)| i)
.filter(|&i| i < cursor.index)
.last()
.unwrap_or(0);
if cursor.index == 0 {
// Try again, searching previous line
continue;
} }
// Left } else if cursor.line > 0 {
'h' => self.editor.action(font_system, Action::Left), // Go to previous line and rerun loop
// Top of screen cursor.line -= 1;
//TODO: 'H' => self.editor.action(Action::ScreenHigh), cursor.index = buffer.lines[cursor.line].text().len();
// Down continue;
'j' => self.editor.action(font_system, Action::Down),
// Up
'k' => self.editor.action(font_system, Action::Up),
// Right
'l' | ' ' => self.editor.action(font_system, Action::Right),
// Bottom of screen
//TODO: 'L' => self.editor.action(Action::ScreenLow),
// Middle of screen
//TODO: 'M' => self.editor.action(Action::ScreenMiddle),
// Next search item
'n' => self.search(false),
// Previous search item
'N' => self.search(true),
// Enter visual mode
'v' => {
if self.editor.select_opt().is_some() {
self.editor.set_select_opt(None);
} else {
self.editor.set_select_opt(Some(self.editor.cursor()));
} }
break;
} }
// Enter line visual mode editor.set_cursor(cursor);
'V' => { continue;
if self.editor.select_opt().is_some() {
self.editor.set_select_opt(None);
} else {
self.editor.action(font_system, Action::Home);
self.editor.set_select_opt(Some(self.editor.cursor()));
//TODO: set cursor_x_opt to max
self.editor.action(font_system, Action::End);
} }
} Motion::Right => Action::Right,
// Next word Motion::SoftHome => Action::SoftHome,
'w' => { Motion::Up => Action::Up,
//TODO: WORD vs word, iterate by vi word rules
self.editor.action(font_system, Action::NextWord);
}
// Next WORD
'W' => {
//TODO: WORD vs word, iterate by vi word rules
self.editor.action(font_system, Action::NextWord);
}
// Remove character at cursor
'x' => self.editor.action(font_system, Action::Delete),
// Remove character before cursor
'X' => self.editor.action(font_system, Action::Backspace),
// Go to start of line
'0' => self.editor.action(font_system, Action::Home),
// Go to end of line
'$' => self.editor.action(font_system, Action::End),
// Go to start of line after whitespace
'^' => self.editor.action(font_system, Action::SoftHome),
// Enter command mode
':' => {
self.mode = ViMode::Command {
value: String::new(),
};
}
// 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); log::info!("TODO");
break;
} }
}, };
Action::Enter => { editor.action(font_system, action);
//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 {
match self.parser.mode {
ViMode::Normal(_) => true,
ViMode::Insert => false, ViMode::Insert => false,
_ => true, /*TODO: determine block cursor in other modes*/ _ => 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) {