Undo/redo support in ViEditor
This commit is contained in:
parent
7830f4107c
commit
5352fdee94
5 changed files with 198 additions and 68 deletions
|
|
@ -21,6 +21,7 @@ self_cell = "1.0.1"
|
|||
swash = { version = "0.1.8", optional = true }
|
||||
syntect = { version = "5.1.0", optional = true }
|
||||
sys-locale = { version = "0.3.1", optional = true }
|
||||
undo_2 = { version = "0.2.0", optional = true }
|
||||
unicode-linebreak = "0.1.5"
|
||||
unicode-script = "0.5.5"
|
||||
unicode-segmentation = "1.10.1"
|
||||
|
|
@ -46,7 +47,7 @@ std = [
|
|||
"sys-locale",
|
||||
"unicode-bidi/std",
|
||||
]
|
||||
vi = ["modit", "syntect"]
|
||||
vi = ["modit", "syntect", "undo_2"]
|
||||
wasm-web = ["sys-locale?/js"]
|
||||
warn_on_missing_glyphs = []
|
||||
fontconfig = ["fontdb/fontconfig", "std"]
|
||||
|
|
|
|||
|
|
@ -124,7 +124,12 @@ impl Editor {
|
|||
}
|
||||
|
||||
if let Some(ref mut change) = self.change {
|
||||
let item = ChangeItem::Delete(start, change_text);
|
||||
let item = ChangeItem {
|
||||
start,
|
||||
end,
|
||||
text: change_text,
|
||||
insert: false,
|
||||
};
|
||||
change.items.push(item);
|
||||
}
|
||||
}
|
||||
|
|
@ -140,10 +145,8 @@ impl Editor {
|
|||
return cursor;
|
||||
}
|
||||
|
||||
if let Some(ref mut change) = self.change {
|
||||
let item = ChangeItem::Insert(cursor, data.to_string());
|
||||
change.items.push(item);
|
||||
}
|
||||
// Save cursor for change tracking
|
||||
let start = cursor;
|
||||
|
||||
let line: &mut BufferLine = &mut self.buffer.lines[cursor.line];
|
||||
let insert_line = cursor.line + 1;
|
||||
|
|
@ -208,6 +211,16 @@ impl Editor {
|
|||
// Append the text after insertion
|
||||
cursor.index = self.buffer.lines[cursor.line].text().len() - after_len;
|
||||
|
||||
if let Some(ref mut change) = self.change {
|
||||
let item = ChangeItem {
|
||||
start,
|
||||
end: cursor,
|
||||
text: data.to_string(),
|
||||
insert: true,
|
||||
};
|
||||
change.items.push(item);
|
||||
}
|
||||
|
||||
cursor
|
||||
}
|
||||
}
|
||||
|
|
@ -348,9 +361,36 @@ impl Edit for Editor {
|
|||
self.set_cursor(new_cursor);
|
||||
}
|
||||
|
||||
fn apply_change(&mut self, change: &Change) -> bool {
|
||||
// Cannot apply changes if there is a pending change
|
||||
match self.change.take() {
|
||||
Some(pending) => {
|
||||
if !pending.items.is_empty() {
|
||||
//TODO: is this a good idea?
|
||||
log::warn!("pending change caused apply_change to be ignored!");
|
||||
self.change = Some(pending);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
for item in change.items.iter() {
|
||||
//TODO: edit cursor if needed?
|
||||
if item.insert {
|
||||
self.cursor = self.insert_at(item.start, &item.text, None);
|
||||
} else {
|
||||
self.cursor = item.start;
|
||||
self.delete_range(item.start, item.end);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn start_change(&mut self) {
|
||||
//TODO: what to do if overwriting change?
|
||||
self.change = Some(Change::default());
|
||||
if self.change.is_none() {
|
||||
self.change = Some(Change::default());
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_change(&mut self) -> Option<Change> {
|
||||
|
|
|
|||
|
|
@ -93,15 +93,41 @@ pub enum Action {
|
|||
GotoLine(usize),
|
||||
}
|
||||
|
||||
/// A unique change to an editor
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ChangeItem {
|
||||
Delete(Cursor, String),
|
||||
Insert(Cursor, String),
|
||||
pub struct ChangeItem {
|
||||
/// Cursor indicating start of change
|
||||
pub start: Cursor,
|
||||
/// Cursor indicating end of change
|
||||
pub end: Cursor,
|
||||
/// Text to be inserted or deleted
|
||||
pub text: String,
|
||||
/// Insert if true, delete if false
|
||||
pub insert: bool,
|
||||
}
|
||||
|
||||
impl ChangeItem {
|
||||
// Reverse change item (in place)
|
||||
pub fn reverse(&mut self) {
|
||||
self.insert = !self.insert;
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of change items grouped into one logical change
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Change {
|
||||
items: Vec<ChangeItem>,
|
||||
/// Change items grouped into one change
|
||||
pub items: Vec<ChangeItem>,
|
||||
}
|
||||
|
||||
impl Change {
|
||||
// Reverse change (in place)
|
||||
pub fn reverse(&mut self) {
|
||||
self.items.reverse();
|
||||
for item in self.items.iter_mut() {
|
||||
item.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait to allow easy replacements of [`Editor`], like `SyntaxEditor`
|
||||
|
|
@ -158,6 +184,9 @@ pub trait Edit {
|
|||
/// attributes, or with the previous character's attributes if None is given.
|
||||
fn insert_string(&mut self, data: &str, attrs_list: Option<AttrsList>);
|
||||
|
||||
/// Apply a change
|
||||
fn apply_change(&mut self, change: &Change) -> bool;
|
||||
|
||||
/// Start collecting change
|
||||
fn start_change(&mut self);
|
||||
|
||||
|
|
|
|||
|
|
@ -304,6 +304,10 @@ impl<'a> Edit for SyntaxEditor<'a> {
|
|||
self.editor.insert_string(data, attrs_list);
|
||||
}
|
||||
|
||||
fn apply_change(&mut self, change: &Change) -> bool {
|
||||
self.editor.apply_change(change)
|
||||
}
|
||||
|
||||
fn start_change(&mut self) {
|
||||
self.editor.start_change();
|
||||
}
|
||||
|
|
|
|||
168
src/edit/vi.rs
168
src/edit/vi.rs
|
|
@ -1,6 +1,7 @@
|
|||
use alloc::string::String;
|
||||
use core::cmp;
|
||||
use modit::{Event, Key, Motion, Parser, TextObject, WordIter};
|
||||
use undo_2::Commands;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -10,6 +11,20 @@ use crate::{
|
|||
|
||||
pub use modit::{ViMode, ViParser};
|
||||
|
||||
fn undo_2_action<E: Edit>(editor: &mut E, action: undo_2::Action<&Change>) {
|
||||
match action {
|
||||
undo_2::Action::Do(change) => {
|
||||
editor.apply_change(change);
|
||||
}
|
||||
undo_2::Action::Undo(change) => {
|
||||
//TODO: make this more efficient
|
||||
let mut reversed = change.clone();
|
||||
reversed.reverse();
|
||||
editor.apply_change(&reversed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn search<E: Edit>(editor: &mut E, search: &str, forwards: bool) {
|
||||
let mut cursor = editor.cursor();
|
||||
let start_line = cursor.line;
|
||||
|
|
@ -124,6 +139,7 @@ pub struct ViEditor<'a> {
|
|||
parser: ViParser,
|
||||
passthrough: bool,
|
||||
search_opt: Option<(String, bool)>,
|
||||
commands: Commands<Change>,
|
||||
}
|
||||
|
||||
impl<'a> ViEditor<'a> {
|
||||
|
|
@ -133,6 +149,7 @@ impl<'a> ViEditor<'a> {
|
|||
parser: ViParser::new(),
|
||||
passthrough: false,
|
||||
search_opt: None,
|
||||
commands: Commands::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -179,66 +196,24 @@ impl<'a> ViEditor<'a> {
|
|||
pub fn parser(&self) -> &ViParser {
|
||||
&self.parser
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Edit for ViEditor<'a> {
|
||||
fn buffer(&self) -> &Buffer {
|
||||
self.editor.buffer()
|
||||
/// Redo a change
|
||||
pub fn redo(&mut self) {
|
||||
log::info!("Redo");
|
||||
for action in self.commands.redo() {
|
||||
undo_2_action(&mut self.editor, action);
|
||||
}
|
||||
}
|
||||
|
||||
fn buffer_mut(&mut self) -> &mut Buffer {
|
||||
self.editor.buffer_mut()
|
||||
/// Undo a change
|
||||
pub fn undo(&mut self) {
|
||||
log::info!("Undo");
|
||||
for action in self.commands.undo() {
|
||||
undo_2_action(&mut self.editor, action);
|
||||
}
|
||||
}
|
||||
|
||||
fn cursor(&self) -> Cursor {
|
||||
self.editor.cursor()
|
||||
}
|
||||
|
||||
fn set_cursor(&mut self, cursor: Cursor) {
|
||||
self.editor.set_cursor(cursor);
|
||||
}
|
||||
|
||||
fn select_opt(&self) -> Option<Cursor> {
|
||||
self.editor.select_opt()
|
||||
}
|
||||
|
||||
fn set_select_opt(&mut self, select_opt: Option<Cursor>) {
|
||||
self.editor.set_select_opt(select_opt);
|
||||
}
|
||||
|
||||
fn tab_width(&self) -> usize {
|
||||
self.editor.tab_width()
|
||||
}
|
||||
|
||||
fn set_tab_width(&mut self, tab_width: usize) {
|
||||
self.editor.set_tab_width(tab_width);
|
||||
}
|
||||
|
||||
fn shape_as_needed(&mut self, font_system: &mut FontSystem) {
|
||||
self.editor.shape_as_needed(font_system);
|
||||
}
|
||||
|
||||
fn copy_selection(&self) -> Option<String> {
|
||||
self.editor.copy_selection()
|
||||
}
|
||||
|
||||
fn delete_selection(&mut self) -> bool {
|
||||
self.editor.delete_selection()
|
||||
}
|
||||
|
||||
fn insert_string(&mut self, data: &str, attrs_list: Option<AttrsList>) {
|
||||
self.editor.insert_string(data, attrs_list);
|
||||
}
|
||||
|
||||
fn start_change(&mut self) {
|
||||
self.editor.start_change();
|
||||
}
|
||||
|
||||
fn finish_change(&mut self) -> Option<Change> {
|
||||
self.editor.finish_change()
|
||||
}
|
||||
|
||||
fn action(&mut self, font_system: &mut FontSystem, action: Action) {
|
||||
fn action_inner(&mut self, font_system: &mut FontSystem, action: Action) {
|
||||
let editor = &mut self.editor;
|
||||
log::info!("Action {:?}", action);
|
||||
|
||||
|
|
@ -268,6 +243,7 @@ impl<'a> Edit for ViEditor<'a> {
|
|||
return editor.action(font_system, action);
|
||||
}
|
||||
};
|
||||
|
||||
self.parser.parse(key, false, |event| {
|
||||
log::info!(" Event {:?}", event);
|
||||
let action = match event {
|
||||
|
|
@ -347,7 +323,9 @@ impl<'a> Edit for ViEditor<'a> {
|
|||
return;
|
||||
}
|
||||
Event::Undo => {
|
||||
log::info!("TODO");
|
||||
for action in self.commands.undo() {
|
||||
undo_2_action(editor, action);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Event::Motion(motion) => {
|
||||
|
|
@ -636,6 +614,84 @@ impl<'a> Edit for ViEditor<'a> {
|
|||
editor.action(font_system, action);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Edit for ViEditor<'a> {
|
||||
fn buffer(&self) -> &Buffer {
|
||||
self.editor.buffer()
|
||||
}
|
||||
|
||||
fn buffer_mut(&mut self) -> &mut Buffer {
|
||||
self.editor.buffer_mut()
|
||||
}
|
||||
|
||||
fn cursor(&self) -> Cursor {
|
||||
self.editor.cursor()
|
||||
}
|
||||
|
||||
fn set_cursor(&mut self, cursor: Cursor) {
|
||||
self.editor.set_cursor(cursor);
|
||||
}
|
||||
|
||||
fn select_opt(&self) -> Option<Cursor> {
|
||||
self.editor.select_opt()
|
||||
}
|
||||
|
||||
fn set_select_opt(&mut self, select_opt: Option<Cursor>) {
|
||||
self.editor.set_select_opt(select_opt);
|
||||
}
|
||||
|
||||
fn tab_width(&self) -> usize {
|
||||
self.editor.tab_width()
|
||||
}
|
||||
|
||||
fn set_tab_width(&mut self, tab_width: usize) {
|
||||
self.editor.set_tab_width(tab_width);
|
||||
}
|
||||
|
||||
fn shape_as_needed(&mut self, font_system: &mut FontSystem) {
|
||||
self.editor.shape_as_needed(font_system);
|
||||
}
|
||||
|
||||
fn copy_selection(&self) -> Option<String> {
|
||||
self.editor.copy_selection()
|
||||
}
|
||||
|
||||
fn delete_selection(&mut self) -> bool {
|
||||
self.editor.delete_selection()
|
||||
}
|
||||
|
||||
fn insert_string(&mut self, data: &str, attrs_list: Option<AttrsList>) {
|
||||
self.editor.insert_string(data, attrs_list);
|
||||
}
|
||||
|
||||
fn apply_change(&mut self, change: &Change) -> bool {
|
||||
self.editor.apply_change(change)
|
||||
}
|
||||
|
||||
fn start_change(&mut self) {
|
||||
self.editor.start_change();
|
||||
}
|
||||
|
||||
fn finish_change(&mut self) -> Option<Change> {
|
||||
self.editor.finish_change()
|
||||
}
|
||||
|
||||
fn action(&mut self, font_system: &mut FontSystem, action: Action) {
|
||||
self.start_change();
|
||||
|
||||
self.action_inner(font_system, action);
|
||||
|
||||
match self.finish_change() {
|
||||
Some(change) => {
|
||||
if !change.items.is_empty() {
|
||||
log::info!("{:?}", change);
|
||||
self.commands.push(change);
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "swash")]
|
||||
fn draw<F>(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue