refactor input system
This commit is contained in:
+261
@@ -0,0 +1,261 @@
|
|||||||
|
use std::cmp::min;
|
||||||
|
|
||||||
|
use crate::{BYTES_PER_LINE, app::{App, Mode, PartialAction}};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Action {
|
||||||
|
Quit,
|
||||||
|
|
||||||
|
NormalMode,
|
||||||
|
SelectMode,
|
||||||
|
|
||||||
|
GPartial,
|
||||||
|
ZPartial,
|
||||||
|
RPartial,
|
||||||
|
|
||||||
|
MoveByteUp,
|
||||||
|
MoveByteDown,
|
||||||
|
MoveByteLeft,
|
||||||
|
MoveByteRight,
|
||||||
|
|
||||||
|
GotoLineStart,
|
||||||
|
GotoLineEnd,
|
||||||
|
GotoFileStart,
|
||||||
|
GotoFileEnd,
|
||||||
|
|
||||||
|
ScrollDown,
|
||||||
|
ScrollUp,
|
||||||
|
|
||||||
|
PageCursorHalfDown,
|
||||||
|
PageCursorHalfUp,
|
||||||
|
|
||||||
|
PageDown,
|
||||||
|
PageUp,
|
||||||
|
|
||||||
|
MoveNextWordStart,
|
||||||
|
MoveNextWordEnd,
|
||||||
|
MovePreviousWordStart,
|
||||||
|
|
||||||
|
CollapseSelection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn execute(&mut self, action: Action) {
|
||||||
|
match action {
|
||||||
|
Action::Quit => self.quit(),
|
||||||
|
|
||||||
|
Action::NormalMode => self.normal_mode(),
|
||||||
|
Action::SelectMode => self.select_mode(),
|
||||||
|
|
||||||
|
Action::GPartial => self.g_partial(),
|
||||||
|
Action::ZPartial => self.z_partial(),
|
||||||
|
Action::RPartial => self.r_partial(),
|
||||||
|
|
||||||
|
Action::MoveByteUp => self.move_byte_up(),
|
||||||
|
Action::MoveByteDown => self.move_byte_down(),
|
||||||
|
Action::MoveByteLeft => self.move_byte_left(),
|
||||||
|
Action::MoveByteRight => self.move_byte_right(),
|
||||||
|
|
||||||
|
Action::GotoLineStart => self.goto_line_start(),
|
||||||
|
Action::GotoLineEnd => self.goto_line_end(),
|
||||||
|
Action::GotoFileStart => self.goto_file_start(),
|
||||||
|
Action::GotoFileEnd => self.goto_file_end(),
|
||||||
|
|
||||||
|
Action::ScrollDown => self.scroll_down(),
|
||||||
|
Action::ScrollUp => self.scroll_up(),
|
||||||
|
|
||||||
|
Action::PageCursorHalfDown => self.page_cursor_half_down(),
|
||||||
|
Action::PageCursorHalfUp => self.page_cursor_half_up(),
|
||||||
|
|
||||||
|
Action::PageDown => self.page_down(),
|
||||||
|
Action::PageUp => self.page_up(),
|
||||||
|
|
||||||
|
Action::MoveNextWordStart => self.move_next_word_start(),
|
||||||
|
Action::MoveNextWordEnd => self.move_next_word_end(),
|
||||||
|
Action::MovePreviousWordStart => self.move_previous_word_start(),
|
||||||
|
|
||||||
|
Action::CollapseSelection => self.collapse_selection(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn quit(&mut self) {
|
||||||
|
self.should_quit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn normal_mode(&mut self) {
|
||||||
|
self.mode = Mode::Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn select_mode(&mut self) {
|
||||||
|
self.mode = Mode::Select;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn g_partial(&mut self) {
|
||||||
|
self.partial_action = Some(PartialAction::Goto);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn z_partial(&mut self) {
|
||||||
|
self.partial_action = Some(PartialAction::Zview);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn r_partial(&mut self) {
|
||||||
|
self.partial_action = Some(PartialAction::Replace);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn move_byte_up(&mut self) {
|
||||||
|
if self.cursor.head >= BYTES_PER_LINE {
|
||||||
|
self.cursor.head -= BYTES_PER_LINE;
|
||||||
|
self.cursor.collapse();
|
||||||
|
|
||||||
|
self.clamp_screen_to_cursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn move_byte_down(&mut self) {
|
||||||
|
if self.contents.len() - 1 - self.cursor.head >= BYTES_PER_LINE {
|
||||||
|
self.cursor.head += BYTES_PER_LINE;
|
||||||
|
self.cursor.collapse();
|
||||||
|
|
||||||
|
self.clamp_screen_to_cursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn move_byte_left(&mut self) {
|
||||||
|
if self.cursor.head >= 1 {
|
||||||
|
self.cursor.head -= 1;
|
||||||
|
self.cursor.collapse();
|
||||||
|
|
||||||
|
self.clamp_screen_to_cursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn move_byte_right(&mut self) {
|
||||||
|
if self.contents.len() - 1 - self.cursor.head >= 1 {
|
||||||
|
self.cursor.head += 1;
|
||||||
|
self.cursor.collapse();
|
||||||
|
|
||||||
|
self.clamp_screen_to_cursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn goto_line_start(&mut self) {
|
||||||
|
self.cursor.head -= self.cursor.head % BYTES_PER_LINE;
|
||||||
|
self.cursor.collapse();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn goto_line_end(&mut self) {
|
||||||
|
self.cursor.head += BYTES_PER_LINE - 1 - (self.cursor.head % BYTES_PER_LINE);
|
||||||
|
self.cursor.collapse();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn goto_file_start(&mut self) {
|
||||||
|
self.cursor.head %= BYTES_PER_LINE;
|
||||||
|
self.cursor.collapse();
|
||||||
|
self.clamp_screen_to_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn goto_file_end(&mut self) {
|
||||||
|
self.cursor.head = previous_multiple_of(BYTES_PER_LINE, self.contents.len()) +
|
||||||
|
(self.cursor.head % BYTES_PER_LINE);
|
||||||
|
|
||||||
|
self.cursor.collapse();
|
||||||
|
self.clamp_screen_to_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_down(&mut self) {
|
||||||
|
self.scroll_position = min(
|
||||||
|
self.scroll_position + BYTES_PER_LINE,
|
||||||
|
self.contents.len() - (5 * BYTES_PER_LINE)
|
||||||
|
);
|
||||||
|
self.cursor.clamp(self.scroll_position, self.screen_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_up(&mut self) {
|
||||||
|
self.scroll_position = self.scroll_position.saturating_sub(BYTES_PER_LINE);
|
||||||
|
self.cursor.clamp(self.scroll_position, self.screen_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_cursor_half_down(&mut self) {
|
||||||
|
let head_offset = self.cursor.head - self.scroll_position;
|
||||||
|
let tail_offset = self.cursor.tail - self.scroll_position;
|
||||||
|
|
||||||
|
self.scroll_position = min(
|
||||||
|
self.scroll_position + (self.screen_size() / 2).next_multiple_of(BYTES_PER_LINE),
|
||||||
|
self.contents.len() - (5 * BYTES_PER_LINE)
|
||||||
|
);
|
||||||
|
|
||||||
|
self.cursor.head = (self.scroll_position + head_offset).min(self.contents.len() - 1);
|
||||||
|
self.cursor.tail = (self.scroll_position + tail_offset).min(self.contents.len() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_cursor_half_up(&mut self) {
|
||||||
|
let head_offset = self.cursor.head - self.scroll_position;
|
||||||
|
let tail_offset = self.cursor.tail - self.scroll_position;
|
||||||
|
|
||||||
|
self.scroll_position = self.scroll_position.saturating_sub(
|
||||||
|
(self.screen_size() / 2).next_multiple_of(BYTES_PER_LINE)
|
||||||
|
);
|
||||||
|
|
||||||
|
self.cursor.head = (self.scroll_position + head_offset).min(self.contents.len() - 1);
|
||||||
|
self.cursor.tail = (self.scroll_position + tail_offset).min(self.contents.len() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_down(&mut self) {
|
||||||
|
self.scroll_position = min(
|
||||||
|
self.scroll_position + self.screen_size(),
|
||||||
|
self.contents.len() - (5 * BYTES_PER_LINE)
|
||||||
|
);
|
||||||
|
self.cursor.clamp(self.scroll_position, self.screen_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_up(&mut self) {
|
||||||
|
self.scroll_position = self.scroll_position.saturating_sub(
|
||||||
|
self.screen_size()
|
||||||
|
);
|
||||||
|
self.cursor.clamp(self.scroll_position, self.screen_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_next_word_start(&mut self) {
|
||||||
|
self.cursor.move_to_next_word(self.contents.len() - 1);
|
||||||
|
self.clamp_screen_to_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_next_word_end(&mut self) {
|
||||||
|
self.cursor.move_to_next_end(self.contents.len() - 1);
|
||||||
|
self.clamp_screen_to_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn move_previous_word_start(&mut self) {
|
||||||
|
self.cursor.move_to_previous_beginning();
|
||||||
|
self.clamp_screen_to_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn collapse_selection(&mut self) {
|
||||||
|
self.cursor.collapse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helpers
|
||||||
|
impl App {
|
||||||
|
// in bytes
|
||||||
|
const fn screen_size(&self) -> usize {
|
||||||
|
self.window_rows * BYTES_PER_LINE
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn clamp_screen_to_cursor(&mut self) {
|
||||||
|
if self.cursor.head < self.scroll_position {
|
||||||
|
self.scroll_position -= (self.scroll_position - self.cursor.head).next_multiple_of(BYTES_PER_LINE);
|
||||||
|
} else if self.cursor.head > self.scroll_position + self.screen_size() - 1 {
|
||||||
|
let screen_edge_offset_to_cursor = self.cursor.head - (self.scroll_position + self.screen_size() - 1);
|
||||||
|
self.scroll_position += screen_edge_offset_to_cursor.next_multiple_of(BYTES_PER_LINE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn previous_multiple_of(multiple: usize, number: usize) -> usize {
|
||||||
|
if number == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
(number - 1) - ((number - 1) % multiple)
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
-217
@@ -1,13 +1,13 @@
|
|||||||
use std::{cmp::min, env, fs::File, io::Read, path::PathBuf, process::exit};
|
use std::{env, fs::File, io::Read, path::PathBuf, process::exit};
|
||||||
use crossterm::{event::{self, Event, KeyCode, KeyModifiers}, terminal::window_size};
|
use crossterm::{event::{self, Event, KeyEvent}, terminal::window_size};
|
||||||
use ratatui::style::Color;
|
use ratatui::style::Color;
|
||||||
|
|
||||||
use crate::{BYTES_PER_LINE, cursor::Cursor};
|
use crate::{config::Config, cursor::Cursor};
|
||||||
|
|
||||||
mod widget;
|
mod widget;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
pub config: Config,
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
pub contents: Vec<u8>,
|
pub contents: Vec<u8>,
|
||||||
pub window_rows: usize,
|
pub window_rows: usize,
|
||||||
@@ -19,12 +19,12 @@ pub struct App {
|
|||||||
pub logs: Vec<String>,
|
pub logs: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Hash, PartialEq, Eq)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
Normal, Select, Insert
|
Normal, Select, Insert
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Hash, PartialEq, Eq)]
|
||||||
pub enum PartialAction {
|
pub enum PartialAction {
|
||||||
Goto, Zview, Replace
|
Goto, Zview, Replace
|
||||||
}
|
}
|
||||||
@@ -75,6 +75,7 @@ impl App {
|
|||||||
file.unwrap().read_to_end(&mut contents).unwrap();
|
file.unwrap().read_to_end(&mut contents).unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
config: Config::default(),
|
||||||
file_name: file_path.file_name().unwrap().to_str().unwrap().to_owned(),
|
file_name: file_path.file_name().unwrap().to_str().unwrap().to_owned(),
|
||||||
contents,
|
contents,
|
||||||
// -1 because of the status line
|
// -1 because of the status line
|
||||||
@@ -88,228 +89,34 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// in bytes
|
|
||||||
const fn screen_size(&self) -> usize {
|
|
||||||
self.window_rows * BYTES_PER_LINE
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn handle_events(&mut self) {
|
pub fn handle_events(&mut self) {
|
||||||
#[allow(clippy::collapsible_match)]
|
#[allow(clippy::collapsible_match)]
|
||||||
match (&self.mode, event::read().unwrap(), &self.partial_action) {
|
match event::read().unwrap() {
|
||||||
(Mode::Normal, Event::Resize(_, height), _) => {
|
Event::Resize(_, height) => {
|
||||||
// -1 because of the status line
|
// -1 because of the status line
|
||||||
self.window_rows = height as usize - 1;
|
self.window_rows = height as usize - 1;
|
||||||
}
|
}
|
||||||
|
Event::Key(key_event) => self.handle_key(key_event),
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
// Event::Mouse(mouse_event) => {
|
||||||
if key_event.code == KeyCode::Char('q') => {
|
// mouse_event.kind
|
||||||
self.should_quit = true;
|
// },
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.modifiers.contains(KeyModifiers::CONTROL) &&
|
|
||||||
key_event.code == KeyCode::Char('e') => {
|
|
||||||
self.scroll_position = min(
|
|
||||||
self.scroll_position + BYTES_PER_LINE,
|
|
||||||
self.contents.len() - (5 * BYTES_PER_LINE)
|
|
||||||
);
|
|
||||||
self.cursor.clamp(self.scroll_position, self.screen_size());
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.modifiers.contains(KeyModifiers::CONTROL) &&
|
|
||||||
key_event.code == KeyCode::Char('y') => {
|
|
||||||
self.scroll_position = self.scroll_position.saturating_sub(BYTES_PER_LINE);
|
|
||||||
self.cursor.clamp(self.scroll_position, self.screen_size());
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.modifiers.contains(KeyModifiers::CONTROL) &&
|
|
||||||
key_event.code == KeyCode::Char('d') => {
|
|
||||||
let head_offset = self.cursor.head - self.scroll_position;
|
|
||||||
let tail_offset = self.cursor.tail - self.scroll_position;
|
|
||||||
|
|
||||||
self.scroll_position = min(
|
|
||||||
self.scroll_position + (self.screen_size() / 2).next_multiple_of(BYTES_PER_LINE),
|
|
||||||
self.contents.len() - (5 * BYTES_PER_LINE)
|
|
||||||
);
|
|
||||||
|
|
||||||
self.cursor.head = (self.scroll_position + head_offset).min(self.contents.len() - 1);
|
|
||||||
self.cursor.tail = (self.scroll_position + tail_offset).min(self.contents.len() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.modifiers.contains(KeyModifiers::CONTROL) &&
|
|
||||||
key_event.code == KeyCode::Char('u') => {
|
|
||||||
let head_offset = self.cursor.head - self.scroll_position;
|
|
||||||
let tail_offset = self.cursor.tail - self.scroll_position;
|
|
||||||
|
|
||||||
self.scroll_position = self.scroll_position.saturating_sub(
|
|
||||||
(self.screen_size() / 2).next_multiple_of(BYTES_PER_LINE)
|
|
||||||
);
|
|
||||||
|
|
||||||
self.cursor.head = (self.scroll_position + head_offset).min(self.contents.len() - 1);
|
|
||||||
self.cursor.tail = (self.scroll_position + tail_offset).min(self.contents.len() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.modifiers.contains(KeyModifiers::CONTROL) &&
|
|
||||||
key_event.code == KeyCode::Char('f') => {
|
|
||||||
self.scroll_position = min(
|
|
||||||
self.scroll_position + self.screen_size(),
|
|
||||||
self.contents.len() - (5 * BYTES_PER_LINE)
|
|
||||||
);
|
|
||||||
self.cursor.clamp(self.scroll_position, self.screen_size());
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.modifiers.contains(KeyModifiers::CONTROL) &&
|
|
||||||
key_event.code == KeyCode::Char('b') => {
|
|
||||||
self.scroll_position = self.scroll_position.saturating_sub(
|
|
||||||
self.screen_size()
|
|
||||||
);
|
|
||||||
self.cursor.clamp(self.scroll_position, self.screen_size());
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.code == KeyCode::Char('g') => {
|
|
||||||
self.partial_action = Some(PartialAction::Goto);
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), Some(PartialAction::Goto))
|
|
||||||
if key_event.code == KeyCode::Char('g') => {
|
|
||||||
self.partial_action = None;
|
|
||||||
self.cursor.head %= BYTES_PER_LINE;
|
|
||||||
self.cursor.collapse();
|
|
||||||
self.clamp_screen_to_cursor();
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.code == KeyCode::Char('G') => {
|
|
||||||
self.cursor.head = previous_multiple_of(BYTES_PER_LINE, self.contents.len()) +
|
|
||||||
(self.cursor.head % BYTES_PER_LINE);
|
|
||||||
|
|
||||||
self.cursor.collapse();
|
|
||||||
self.clamp_screen_to_cursor();
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.code == KeyCode::Char('z') => {
|
|
||||||
self.partial_action = Some(PartialAction::Zview);
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), Some(PartialAction::Zview))
|
|
||||||
if key_event.code == KeyCode::Char('z') => {
|
|
||||||
self.partial_action = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.code == KeyCode::Char('i') ||
|
|
||||||
key_event.code == KeyCode::Up => {
|
|
||||||
self.partial_action = None;
|
|
||||||
if self.cursor.head >= BYTES_PER_LINE {
|
|
||||||
self.cursor.head -= BYTES_PER_LINE;
|
|
||||||
self.cursor.collapse();
|
|
||||||
|
|
||||||
self.clamp_screen_to_cursor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.code == KeyCode::Char('j') ||
|
|
||||||
key_event.code == KeyCode::Left => {
|
|
||||||
if self.cursor.head >= 1 {
|
|
||||||
self.cursor.head -= 1;
|
|
||||||
self.cursor.collapse();
|
|
||||||
|
|
||||||
self.clamp_screen_to_cursor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), Some(PartialAction::Goto))
|
|
||||||
if key_event.code == KeyCode::Char('j') ||
|
|
||||||
key_event.code == KeyCode::Left => {
|
|
||||||
self.partial_action = None;
|
|
||||||
self.cursor.head -= self.cursor.head % BYTES_PER_LINE;
|
|
||||||
self.cursor.collapse();
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.code == KeyCode::Char('k') ||
|
|
||||||
key_event.code == KeyCode::Down => {
|
|
||||||
if self.contents.len() - 1 - self.cursor.head >= BYTES_PER_LINE {
|
|
||||||
self.cursor.head += BYTES_PER_LINE;
|
|
||||||
self.cursor.collapse();
|
|
||||||
|
|
||||||
self.clamp_screen_to_cursor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.code == KeyCode::Char('l') ||
|
|
||||||
key_event.code == KeyCode::Right => {
|
|
||||||
if self.contents.len() - 1 - self.cursor.head >= 1 {
|
|
||||||
self.cursor.head += 1;
|
|
||||||
self.cursor.collapse();
|
|
||||||
|
|
||||||
self.clamp_screen_to_cursor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), Some(PartialAction::Goto))
|
|
||||||
if key_event.code == KeyCode::Char('l') ||
|
|
||||||
key_event.code == KeyCode::Right => {
|
|
||||||
self.partial_action = None;
|
|
||||||
self.cursor.head += BYTES_PER_LINE - 1 - (self.cursor.head % BYTES_PER_LINE);
|
|
||||||
self.cursor.collapse();
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.code == KeyCode::Char('w') => {
|
|
||||||
self.cursor.move_to_next_word(self.contents.len() - 1);
|
|
||||||
self.clamp_screen_to_cursor();
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.code == KeyCode::Char('e') => {
|
|
||||||
self.cursor.move_to_next_end(self.contents.len() - 1);
|
|
||||||
self.clamp_screen_to_cursor();
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.code == KeyCode::Char('b') => {
|
|
||||||
self.cursor.move_to_previous_beginning();
|
|
||||||
self.clamp_screen_to_cursor();
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
|
||||||
if key_event.code == KeyCode::Char(';') => {
|
|
||||||
self.cursor.collapse();
|
|
||||||
}
|
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(_), Some(_)) => {
|
|
||||||
self.partial_action = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn clamp_screen_to_cursor(&mut self) {
|
fn handle_key(&mut self, event: KeyEvent) {
|
||||||
if self.cursor.head < self.scroll_position {
|
let should_reset_partial = self.partial_action.is_some();
|
||||||
self.scroll_position -= (self.scroll_position - self.cursor.head).next_multiple_of(BYTES_PER_LINE);
|
|
||||||
} else if self.cursor.head > self.scroll_position + self.screen_size() - 1 {
|
if let Some(mode_config) = self.config.0.get(&self.mode) &&
|
||||||
let screen_edge_offset_to_cursor = self.cursor.head - (self.scroll_position + self.screen_size() - 1);
|
let Some(keybinds) = mode_config.0.get(&self.partial_action) &&
|
||||||
self.scroll_position += screen_edge_offset_to_cursor.next_multiple_of(BYTES_PER_LINE);
|
let Some(action) = keybinds.0.get(&event.into())
|
||||||
}
|
{
|
||||||
}
|
self.execute(*action);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn previous_multiple_of(multiple: usize, number: usize) -> usize {
|
if should_reset_partial {
|
||||||
if number == 0 {
|
self.partial_action = None;
|
||||||
0
|
}
|
||||||
} else {
|
|
||||||
(number - 1) - ((number - 1) % multiple)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ impl Widget for &App {
|
|||||||
.map(|(bytes, address)| self.render_line(address, bytes));
|
.map(|(bytes, address)| self.render_line(address, bytes));
|
||||||
|
|
||||||
let remainder_address = bytes_end - remainder.len();
|
let remainder_address = bytes_end - remainder.len();
|
||||||
|
#[allow(clippy::if_not_else)]
|
||||||
let remainder_line = if !remainder.is_empty() {
|
let remainder_line = if !remainder.is_empty() {
|
||||||
Some(self.render_partial_line(remainder_address, remainder))
|
Some(self.render_partial_line(remainder_address, remainder))
|
||||||
} else {
|
} else {
|
||||||
@@ -113,6 +114,7 @@ mod hex {
|
|||||||
let (chunks, remainder) = bytes.as_chunks::<BYTES_PER_CHUNK>();
|
let (chunks, remainder) = bytes.as_chunks::<BYTES_PER_CHUNK>();
|
||||||
|
|
||||||
let remainder_address = address + chunks.len() * BYTES_PER_CHUNK;
|
let remainder_address = address + chunks.len() * BYTES_PER_CHUNK;
|
||||||
|
#[allow(clippy::if_not_else)]
|
||||||
let remainder_chunks: Option<Vec<_>> = if !remainder.is_empty() {
|
let remainder_chunks: Option<Vec<_>> = if !remainder.is_empty() {
|
||||||
Some(self.render_partial_chunk(remainder_address, remainder).collect())
|
Some(self.render_partial_chunk(remainder_address, remainder).collect())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+172
@@ -0,0 +1,172 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
use crate::{action::Action, app::{Mode, PartialAction}};
|
||||||
|
|
||||||
|
pub struct Config(
|
||||||
|
pub HashMap<Mode, ModeConfig>
|
||||||
|
);
|
||||||
|
|
||||||
|
pub struct ModeConfig(
|
||||||
|
pub HashMap<Option<PartialAction>, Keybinds>
|
||||||
|
);
|
||||||
|
|
||||||
|
pub struct Keybinds(
|
||||||
|
pub HashMap<Keypress, Action>
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash)]
|
||||||
|
pub struct Keypress {
|
||||||
|
code: KeyCode,
|
||||||
|
modifiers: KeyModifiers
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> From<[(Mode, ModeConfig); N]> for Config {
|
||||||
|
fn from(array: [(Mode, ModeConfig); N]) -> Self {
|
||||||
|
Self(array.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> From<[(Option<PartialAction>, Keybinds); N]> for ModeConfig {
|
||||||
|
fn from(array: [(Option<PartialAction>, Keybinds); N]) -> Self {
|
||||||
|
Self(array.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> From<[(Keypress, Action); N]> for Keybinds {
|
||||||
|
fn from(array: [(Keypress, Action); N]) -> Self {
|
||||||
|
Self(array.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<KeyCode> for Keypress {
|
||||||
|
fn from(key_code: KeyCode) -> Self {
|
||||||
|
Self {
|
||||||
|
code: key_code,
|
||||||
|
modifiers: KeyModifiers::NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn modifier_from_character(character: char) -> Option<KeyModifiers> {
|
||||||
|
match character {
|
||||||
|
'A' => Some(KeyModifiers::ALT),
|
||||||
|
'C' => Some(KeyModifiers::CONTROL),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Keypress {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(string: &str) -> Result<Self, ()> {
|
||||||
|
match string.len() {
|
||||||
|
3 => {
|
||||||
|
Ok(Self {
|
||||||
|
code: KeyCode::Char(
|
||||||
|
string.chars().nth(2).unwrap()
|
||||||
|
),
|
||||||
|
modifiers: modifier_from_character(
|
||||||
|
string.chars().nth(0).unwrap()
|
||||||
|
).ok_or(())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
Ok(
|
||||||
|
KeyCode::Char(
|
||||||
|
string.chars().nth(0).unwrap()
|
||||||
|
).into()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<KeyEvent> for Keypress {
|
||||||
|
fn from(event: KeyEvent) -> Self {
|
||||||
|
Self {
|
||||||
|
code: event.code,
|
||||||
|
modifiers: match event.modifiers {
|
||||||
|
KeyModifiers::SHIFT => KeyModifiers::NONE,
|
||||||
|
x => x,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
[
|
||||||
|
(Mode::Normal, [
|
||||||
|
(None, [
|
||||||
|
("q".try_into().unwrap(), Action::Quit),
|
||||||
|
|
||||||
|
("v".try_into().unwrap(), Action::SelectMode),
|
||||||
|
|
||||||
|
("g".try_into().unwrap(), Action::GPartial),
|
||||||
|
("z".try_into().unwrap(), Action::ZPartial),
|
||||||
|
("r".try_into().unwrap(), Action::RPartial),
|
||||||
|
|
||||||
|
("i".try_into().unwrap(), Action::MoveByteUp),
|
||||||
|
("k".try_into().unwrap(), Action::MoveByteDown),
|
||||||
|
("j".try_into().unwrap(), Action::MoveByteLeft),
|
||||||
|
("l".try_into().unwrap(), Action::MoveByteRight),
|
||||||
|
|
||||||
|
("G".try_into().unwrap(), Action::GotoFileEnd),
|
||||||
|
|
||||||
|
("C-e".try_into().unwrap(), Action::ScrollDown),
|
||||||
|
("C-y".try_into().unwrap(), Action::ScrollUp),
|
||||||
|
|
||||||
|
("C-d".try_into().unwrap(), Action::PageCursorHalfDown),
|
||||||
|
("C-u".try_into().unwrap(), Action::PageCursorHalfUp),
|
||||||
|
|
||||||
|
("C-f".try_into().unwrap(), Action::PageDown),
|
||||||
|
("C-b".try_into().unwrap(), Action::PageUp),
|
||||||
|
|
||||||
|
("w".try_into().unwrap(), Action::MoveNextWordStart),
|
||||||
|
("e".try_into().unwrap(), Action::MoveNextWordEnd),
|
||||||
|
("b".try_into().unwrap(), Action::MovePreviousWordStart),
|
||||||
|
|
||||||
|
(";".try_into().unwrap(), Action::CollapseSelection),
|
||||||
|
].into()),
|
||||||
|
(Some(PartialAction::Goto), [
|
||||||
|
("j".try_into().unwrap(), Action::GotoLineStart),
|
||||||
|
("l".try_into().unwrap(), Action::GotoLineEnd),
|
||||||
|
|
||||||
|
("g".try_into().unwrap(), Action::GotoFileStart),
|
||||||
|
].into())
|
||||||
|
].into()),
|
||||||
|
(Mode::Select, [
|
||||||
|
(None, [
|
||||||
|
("q".try_into().unwrap(), Action::Quit),
|
||||||
|
|
||||||
|
("v".try_into().unwrap(), Action::NormalMode),
|
||||||
|
|
||||||
|
("g".try_into().unwrap(), Action::GPartial),
|
||||||
|
("z".try_into().unwrap(), Action::ZPartial),
|
||||||
|
("r".try_into().unwrap(), Action::RPartial),
|
||||||
|
|
||||||
|
// ("i".try_into().unwrap(), Action::ExtendByteUp),
|
||||||
|
// ("k".try_into().unwrap(), Action::ExtendByteDown),
|
||||||
|
// ("j".try_into().unwrap(), Action::ExtendByteLeft),
|
||||||
|
// ("l".try_into().unwrap(), Action::ExtendByteRight),
|
||||||
|
|
||||||
|
("C-e".try_into().unwrap(), Action::ScrollDown),
|
||||||
|
("C-y".try_into().unwrap(), Action::ScrollUp),
|
||||||
|
|
||||||
|
("C-d".try_into().unwrap(), Action::PageCursorHalfDown),
|
||||||
|
("C-u".try_into().unwrap(), Action::PageCursorHalfUp),
|
||||||
|
|
||||||
|
("C-f".try_into().unwrap(), Action::PageDown),
|
||||||
|
("C-b".try_into().unwrap(), Action::PageUp),
|
||||||
|
|
||||||
|
// ("w".try_into().unwrap(), Action::ExtendNextWordStart),
|
||||||
|
// ("e".try_into().unwrap(), Action::ExtendNextWordEnd),
|
||||||
|
// ("b".try_into().unwrap(), Action::ExtendPreviousWordStart),
|
||||||
|
|
||||||
|
(";".try_into().unwrap(), Action::CollapseSelection),
|
||||||
|
].into())
|
||||||
|
].into())
|
||||||
|
].into()
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -7,14 +7,15 @@ mod cardinality;
|
|||||||
mod empty_span;
|
mod empty_span;
|
||||||
mod custom_greys;
|
mod custom_greys;
|
||||||
mod app;
|
mod app;
|
||||||
|
mod config;
|
||||||
mod cursor;
|
mod cursor;
|
||||||
|
mod action;
|
||||||
|
|
||||||
const BYTES_PER_LINE: usize = 0x10;
|
const BYTES_PER_LINE: usize = 0x10;
|
||||||
const BYTES_PER_CHUNK: usize = 4;
|
const BYTES_PER_CHUNK: usize = 4;
|
||||||
const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
|
const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - refactor input system
|
|
||||||
// - undo/redo
|
// - undo/redo
|
||||||
// - x/X
|
// - x/X
|
||||||
// - modes
|
// - modes
|
||||||
|
|||||||
Reference in New Issue
Block a user