refactor input system
This commit is contained in:
+25
-218
@@ -1,13 +1,13 @@
|
||||
use std::{cmp::min, env, fs::File, io::Read, path::PathBuf, process::exit};
|
||||
use crossterm::{event::{self, Event, KeyCode, KeyModifiers}, terminal::window_size};
|
||||
use std::{env, fs::File, io::Read, path::PathBuf, process::exit};
|
||||
use crossterm::{event::{self, Event, KeyEvent}, terminal::window_size};
|
||||
use ratatui::style::Color;
|
||||
|
||||
use crate::{BYTES_PER_LINE, cursor::Cursor};
|
||||
use crate::{config::Config, cursor::Cursor};
|
||||
|
||||
mod widget;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct App {
|
||||
pub config: Config,
|
||||
pub file_name: String,
|
||||
pub contents: Vec<u8>,
|
||||
pub window_rows: usize,
|
||||
@@ -19,12 +19,12 @@ pub struct App {
|
||||
pub logs: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Hash, PartialEq, Eq)]
|
||||
pub enum Mode {
|
||||
Normal, Select, Insert
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Hash, PartialEq, Eq)]
|
||||
pub enum PartialAction {
|
||||
Goto, Zview, Replace
|
||||
}
|
||||
@@ -75,6 +75,7 @@ impl App {
|
||||
file.unwrap().read_to_end(&mut contents).unwrap();
|
||||
|
||||
Self {
|
||||
config: Config::default(),
|
||||
file_name: file_path.file_name().unwrap().to_str().unwrap().to_owned(),
|
||||
contents,
|
||||
// -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)]
|
||||
pub fn handle_events(&mut self) {
|
||||
#[allow(clippy::collapsible_match)]
|
||||
match (&self.mode, event::read().unwrap(), &self.partial_action) {
|
||||
(Mode::Normal, Event::Resize(_, height), _) => {
|
||||
match event::read().unwrap() {
|
||||
Event::Resize(_, height) => {
|
||||
// -1 because of the status line
|
||||
self.window_rows = height as usize - 1;
|
||||
}
|
||||
|
||||
(Mode::Normal, Event::Key(key_event), None)
|
||||
if key_event.code == KeyCode::Char('q') => {
|
||||
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;
|
||||
}
|
||||
|
||||
Event::Key(key_event) => self.handle_key(key_event),
|
||||
// Event::Mouse(mouse_event) => {
|
||||
// mouse_event.kind
|
||||
// },
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
fn handle_key(&mut self, event: KeyEvent) {
|
||||
let should_reset_partial = self.partial_action.is_some();
|
||||
|
||||
if let Some(mode_config) = self.config.0.get(&self.mode) &&
|
||||
let Some(keybinds) = mode_config.0.get(&self.partial_action) &&
|
||||
let Some(action) = keybinds.0.get(&event.into())
|
||||
{
|
||||
self.execute(*action);
|
||||
}
|
||||
|
||||
if should_reset_partial {
|
||||
self.partial_action = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn previous_multiple_of(multiple: usize, number: usize) -> usize {
|
||||
if number == 0 {
|
||||
0
|
||||
} else {
|
||||
(number - 1) - ((number - 1) % multiple)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ impl Widget for &App {
|
||||
.map(|(bytes, address)| self.render_line(address, bytes));
|
||||
|
||||
let remainder_address = bytes_end - remainder.len();
|
||||
#[allow(clippy::if_not_else)]
|
||||
let remainder_line = if !remainder.is_empty() {
|
||||
Some(self.render_partial_line(remainder_address, remainder))
|
||||
} else {
|
||||
@@ -113,6 +114,7 @@ mod hex {
|
||||
let (chunks, remainder) = bytes.as_chunks::<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() {
|
||||
Some(self.render_partial_chunk(remainder_address, remainder).collect())
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user