Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing undo / redo functionality #8

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ kibi --version # Print version information and exit
| Ctrl-C | Copies the entire line |
| Ctrl-X | Cuts the entire line |
| Ctrl-V | Will paste the copied line |
| Ctrl-Z | Will undo the last remove line, cut or paste |
| Ctrl-Y | Will redo the last undo |

### Configuration

Expand Down
69 changes: 66 additions & 3 deletions src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ const PASTE: u8 = ctrl_key(b'V');
const DUPLICATE: u8 = ctrl_key(b'D');
const EXECUTE: u8 = ctrl_key(b'E');
const REMOVE_LINE: u8 = ctrl_key(b'R');
const UNDO: u8 = ctrl_key(b'Z');
const REDO: u8 = ctrl_key(b'Y');
const BACKSPACE: u8 = 127;

const HELP_MESSAGE: &str =
"^S save | ^Q quit | ^F find | ^G go to | ^D duplicate | ^E execute | ^C copy | ^X cut | ^V paste";
"^S save | ^Q quit | ^F find | ^G go to | ^D duplicate | ^E execute | ^C copy | ^X cut | ^V paste | ^Z undo | ^Y redo";

/// `set_status!` sets a formatted status message for the editor.
/// Example usage: `set_status!(editor, "{} written to {}", file_size, file_name)`
Expand Down Expand Up @@ -122,6 +124,15 @@ pub struct Editor {
orig_term_mode: Option<sys::TermMode>,
/// The copied buffer of a row
copied_row: Vec<u8>,
/// The history buffer of latest deleted row
history_row: Vec<u8>,
/// The history buffer position of latest deleted tow
history_row_y: usize,
/// If undo/redo have to delete or restore line
/// true if restores line | false if deletes line
history_restore: bool,
/// If redo is available (when exists a previous undo)
redo_availability: bool,
}

/// Describes a status message, shown at the bottom at the screen.
Expand Down Expand Up @@ -433,6 +444,41 @@ impl Editor {
self.update_screen_cols();
}

fn undo(&mut self) {
if !self.redo_availability {
if self.history_restore == true {
self.n_bytes += self.history_row.len() as u64;
self.rows.insert(self.history_row_y, Row::new(self.history_row.clone()));
self.update_row(self.history_row_y, false);
(self.cursor.y, self.dirty) = (self.history_row_y, true);
// The line number has changed
self.update_screen_cols();
} else {
self.history_row = self.rows[self.history_row_y].chars.clone();
self.cursor.y = self.history_row_y;
self.delete_current_row();
}
self.redo_availability = true;
}
}

fn redo(&mut self) {
if self.redo_availability {
if self.history_restore {
self.cursor.y = self.history_row_y;
self.delete_current_row();
} else {
self.n_bytes += self.history_row.len() as u64;
self.rows.insert(self.history_row_y, Row::new(self.history_row.clone()));
self.update_row(self.history_row_y, false);
(self.cursor.y, self.dirty) = (self.history_row_y, true);
// The line number has changed
self.update_screen_cols();
}
}
self.redo_availability = false;
}

/// Try to load a file. If found, load the rows and update the render and syntax highlighting.
/// If not found, do not return an error.
fn load(&mut self, path: &Path) -> Result<(), Error> {
Expand Down Expand Up @@ -613,7 +659,13 @@ impl Editor {
Key::End => self.cursor.x = self.current_row().map_or(0, |row| row.chars.len()),
Key::Char(b'\r' | b'\n') => self.insert_new_line(), // Enter
Key::Char(BACKSPACE | DELETE_BIS) => self.delete_char(), // Backspace or Ctrl + H
Key::Char(REMOVE_LINE) => self.delete_current_row(),
Key::Char(REMOVE_LINE) => {
self.history_row_y = self.cursor.y;
self.history_row = self.rows[self.cursor.y].chars.clone();
self.history_restore = true;
self.redo_availability = false;
self.delete_current_row();
}
Key::Delete => {
self.move_cursor(&AKey::Right);
self.delete_char();
Expand All @@ -640,12 +692,23 @@ impl Editor {
Key::Char(GOTO) => prompt_mode = Some(PromptMode::GoTo(String::new())),
Key::Char(DUPLICATE) => self.duplicate_current_row(),
Key::Char(CUT) => {
self.history_row_y = self.cursor.y;
self.history_row = self.rows[self.cursor.y].chars.clone();
self.history_restore = true;
self.redo_availability = false;
self.copy_current_row();
self.delete_current_row();
}
Key::Char(COPY) => self.copy_current_row(),
Key::Char(PASTE) => self.paste_current_row(),
Key::Char(PASTE) => {
self.history_row_y = self.cursor.y + 1;
self.history_restore = false;
self.redo_availability = false;
self.paste_current_row();
}
Key::Char(EXECUTE) => prompt_mode = Some(PromptMode::Execute(String::new())),
Key::Char(UNDO) => self.undo(),
Key::Char(REDO) => self.redo(),
Key::Char(c) => self.insert_byte(*c),
}
self.quit_times = quit_times;
Expand Down