/* * Copyright (c) 2023 Marceline Cramer * Copyright (c) 2023 Emma Tebibyte * 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 { 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); } }