breed/src/actions.rs

288 lines
7.8 KiB
Rust

/*
* Copyright (c) 2023 Marceline Cramer
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
use std::collections::HashMap;
use crate::{
state::{InsertState, Mode, State},
Cursor, Direction,
};
pub type Action = fn(&mut State);
pub fn load_actions() -> HashMap<String, Action> {
let list: &'static [(&'static str, fn(&mut State))] = &[
("move_char_left", move_char_left),
("move_line_down", move_line_down),
("move_line_up", move_line_up),
("move_char_right", move_char_right),
("move_next_word_start", move_next_word_start),
("move_prev_word_start", move_prev_word_start),
("move_next_word_end", move_next_word_end),
("move_next_long_word_start", move_next_long_word_start),
("move_prev_long_word_start", move_prev_long_word_start),
("move_next_long_word_end", move_next_long_word_end),
("page_up", page_up),
("page_down", page_down),
("goto_line_start", goto_line_start),
("goto_line_end", goto_line_end),
("goto_first_nonwhitespace", goto_first_nonwhitespace),
("goto_file_start", goto_file_start),
("goto_file_end", goto_file_end),
("command_mode", command_mode),
("normal_mode", normal_mode),
("visual_mode", visual_mode),
("insert_mode", insert_mode),
("append_mode", append_mode),
("insert_at_line_start", insert_at_line_start),
("insert_at_line_end", insert_at_line_end),
("open_below", open_below),
("open_above", open_above),
("undo", undo),
("redo", redo),
("delete_char_backward", delete_char_backward),
("insert_newline", insert_newline),
];
let mut actions = HashMap::with_capacity(list.len());
for (name, action) in list.iter() {
actions.insert(name.to_string(), *action);
}
actions
}
pub fn move_char_left(state: &mut State) {
state.move_cursor(Direction::Left);
}
pub fn move_char_right(state: &mut State) {
state.move_cursor(Direction::Right);
}
pub fn move_line_down(state: &mut State) {
state.move_cursor(Direction::Down);
}
pub fn move_line_up(state: &mut State) {
state.move_cursor(Direction::Up);
}
pub fn move_next_word_start(state: &mut State) {
state.set_error("move_next_word_start is unimplemented");
}
pub fn move_prev_word_start(state: &mut State) {
state.set_error("move_prev_word_start is unimplemented");
}
pub fn move_next_word_end(state: &mut State) {
state.set_error("move_next_word_end is unimplemented");
}
pub fn move_next_long_word_start(state: &mut State) {
state.set_error("move_next_long_word_start is unimplemented");
}
pub fn move_prev_long_word_start(state: &mut State) {
state.set_error("move_prev_long_word_start is unimplemented");
}
pub fn move_next_long_word_end(state: &mut State) {
state.set_error("move_next_long_word_end is unimplemented");
}
pub fn page_up(state: &mut State) {
state.set_error("page_up is unimplemented");
}
pub fn page_down(state: &mut State) {
state.set_error("page_down is unimplemented");
}
pub fn goto_line_start(state: &mut State) {
state.cursor.column = 0;
}
pub fn goto_line_end(state: &mut State) {
state.cursor = state.buffer.cursor_at_line_end(state.cursor.line);
}
pub fn goto_first_nonwhitespace(state: &mut State) {
state.cursor = state
.buffer
.cursor_at_first_nonwhitespace(state.cursor.line);
}
pub fn goto_file_start(state: &mut State) {
state.cursor = Cursor { line: 0, column: 0 };
state.scroll_to_cursor();
}
pub fn goto_file_end(state: &mut State) {
state.cursor = state.buffer.cursor_at_file_end();
state.scroll_to_cursor();
}
pub fn command_mode(state: &mut State) {
state.mode = Mode::Command(Default::default());
}
pub fn normal_mode(state: &mut State) {
match state.mode {
Mode::Insert(InsertState { append: true }) => {
state.move_cursor(Direction::Left);
}
_ => {}
}
state.mode = Mode::Normal;
}
pub fn visual_mode(state: &mut State) {
state.mode = Mode::Visual;
}
pub fn insert_mode(state: &mut State) {
state.save_buffer();
state.mode = Mode::Insert(InsertState { append: false });
}
pub fn append_mode(state: &mut State) {
state.save_buffer();
state.move_cursor(Direction::Right);
state.mode = Mode::Insert(InsertState { append: true });
}
pub fn insert_at_line_start(state: &mut State) {
goto_first_nonwhitespace(state);
insert_mode(state);
}
pub fn insert_at_line_end(state: &mut State) {
goto_line_end(state);
insert_mode(state);
}
pub fn open_below(state: &mut State) {
insert_mode(state);
state.cursor.line += 1;
state.cursor.column = 0;
state.buffer.insert_char(state.cursor, '\n');
}
pub fn open_above(state: &mut State) {
insert_mode(state);
state.cursor.column = 0;
state.buffer.insert_char(state.cursor, '\n');
}
pub fn undo(state: &mut State) {
state.undo();
}
pub fn redo(state: &mut State) {
state.redo();
}
pub fn delete_char_backward(state: &mut State) {
state.move_cursor(Direction::Left);
state.buffer.remove_char(state.cursor);
}
pub fn delete_char_forward(state: &mut State) {
state.buffer.remove_char(state.cursor);
}
pub fn insert_newline(state: &mut State) {
state.buffer.insert_char(state.cursor, '\n');
state.cursor.line += 1;
state.cursor.column = 0;
}
#[cfg(test)]
mod tests {
use super::*;
fn state(src: &str) -> State {
State::from_str(None, src).unwrap()
}
#[test]
fn backspace_empty() {
let mut state = state("");
delete_char_backward(&mut state);
}
#[test]
fn backspace_start_of_buffer() {
let mut state = state("test\n");
let original = state.buffer.as_ref().clone();
delete_char_backward(&mut state);
assert_eq!(original, *state.buffer.as_ref());
}
#[test]
fn append_on_newline() {
let mut state = state("test\ntest\n");
state.cursor.column = 4;
append_mode(&mut state);
assert_eq!(state.cursor.line, 1);
assert_eq!(state.cursor.column, 0);
}
#[test]
fn append_at_eof() {
let mut state = state("test\n");
state.cursor.column = 4;
append_mode(&mut state);
assert_eq!(state.cursor.line, 1);
assert_eq!(state.cursor.column, 0);
}
#[test]
fn open_below_at_eof() {
let mut state = state("test\n");
state.cursor.line = 1;
open_below(&mut state);
assert_eq!(state.cursor.line, 1);
}
#[test]
fn move_line_up_preserve_column() {
let mut state = state("test\n\ntest\n");
state.cursor.line = 2;
state.cursor.column = 3;
move_line_up(&mut state);
move_line_up(&mut state);
assert_eq!(state.cursor.line, 0);
assert_eq!(state.cursor.column, 3);
}
#[test]
fn move_line_down_preserve_column() {
let mut state = state("test\n\ntest\n");
state.cursor.column = 3;
move_line_down(&mut state);
move_line_down(&mut state);
assert_eq!(state.cursor.line, 2);
assert_eq!(state.cursor.column, 3);
}
}