add cursor, hjklweb;
This commit is contained in:
+65
-8
@@ -1,7 +1,7 @@
|
||||
use std::{cmp::min, env, fs::File, io::Read, process::exit};
|
||||
use crossterm::event::{self, Event, KeyCode, KeyModifiers};
|
||||
|
||||
use crate::BYTES_PER_LINE;
|
||||
use crate::{BYTES_PER_LINE, cursor::Cursor};
|
||||
|
||||
mod widget;
|
||||
|
||||
@@ -9,7 +9,7 @@ mod widget;
|
||||
pub struct App {
|
||||
pub contents: Vec<u8>,
|
||||
pub scroll_position: usize,
|
||||
// pub cursor_position: usize,
|
||||
pub cursor: Cursor,
|
||||
pub should_quit: bool
|
||||
}
|
||||
|
||||
@@ -33,25 +33,82 @@ impl App {
|
||||
Self {
|
||||
contents,
|
||||
scroll_position: 0,
|
||||
// cursor_position: 0,
|
||||
should_quit: false,
|
||||
cursor: Cursor::default(),
|
||||
should_quit: false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_events(&mut self) {
|
||||
#[allow(clippy::collapsible_match)]
|
||||
match event::read().unwrap() {
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('q') => {
|
||||
self.should_quit = true;
|
||||
}
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('e') &&
|
||||
key_event.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
Event::Key(key_event) if key_event.modifiers.contains(KeyModifiers::CONTROL) &&
|
||||
key_event.code == KeyCode::Char('e') => {
|
||||
let max_scroll_position = self.contents.len() - 0x50;
|
||||
self.scroll_position = min(self.scroll_position + BYTES_PER_LINE, max_scroll_position);
|
||||
}
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('y') &&
|
||||
key_event.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
Event::Key(key_event) if key_event.modifiers.contains(KeyModifiers::CONTROL) &&
|
||||
key_event.code == KeyCode::Char('y') => {
|
||||
self.scroll_position = self.scroll_position.saturating_sub(BYTES_PER_LINE);
|
||||
}
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('i') => {
|
||||
if self.cursor.head >= 0x10 {
|
||||
self.cursor.head -= 0x10;
|
||||
self.cursor.collapse();
|
||||
}
|
||||
}
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('j') => {
|
||||
if self.cursor.head >= 1 {
|
||||
self.cursor.head -= 1;
|
||||
self.cursor.collapse();
|
||||
}
|
||||
}
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('k') => {
|
||||
if self.contents.len() - 1 - self.cursor.head >= 0x10 {
|
||||
self.cursor.head += 0x10;
|
||||
self.cursor.collapse();
|
||||
}
|
||||
}
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('l') => {
|
||||
if self.contents.len() - 1 - self.cursor.head >= 1 {
|
||||
self.cursor.head += 1;
|
||||
self.cursor.collapse();
|
||||
}
|
||||
}
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('I') => {
|
||||
if self.cursor.head >= 0x10 {
|
||||
self.cursor.head -= 0x10;
|
||||
}
|
||||
}
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('J') => {
|
||||
if self.cursor.head >= 1 {
|
||||
self.cursor.head -= 1;
|
||||
}
|
||||
}
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('K') => {
|
||||
if self.contents.len() - 1 - self.cursor.head >= 0x10 {
|
||||
self.cursor.head += 0x10;
|
||||
}
|
||||
}
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('L') => {
|
||||
if self.contents.len() - 1 - self.cursor.head >= 1 {
|
||||
self.cursor.head += 1;
|
||||
}
|
||||
}
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('w') => {
|
||||
self.cursor.to_next_word(self.contents.len() - 1);
|
||||
}
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('e') => {
|
||||
self.cursor.to_next_end(self.contents.len() - 1);
|
||||
}
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char('b') => {
|
||||
self.cursor.to_previous_beginning();
|
||||
}
|
||||
Event::Key(key_event) if key_event.code == KeyCode::Char(';') => {
|
||||
self.cursor.collapse();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
+64
-38
@@ -19,7 +19,7 @@ impl Widget for &App {
|
||||
let lines: Vec<_> = chunks
|
||||
.iter()
|
||||
.zip((self.scroll_position..).step_by(BYTES_PER_LINE))
|
||||
.map(|(bytes, address)| render_line(address, bytes))
|
||||
.map(|(bytes, address)| self.render_line(address, bytes))
|
||||
.collect();
|
||||
|
||||
let text = Text::from(lines);
|
||||
@@ -28,15 +28,17 @@ impl Widget for &App {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(mismatched_lifetime_syntaxes)]
|
||||
fn render_line(address: usize, bytes: &[u8; BYTES_PER_LINE]) -> Line {
|
||||
let spans: Vec<Span<'_>> = iter::once(address::render_address(address))
|
||||
.chain(hex::render_chunks(bytes))
|
||||
.chain(iter::once(" ".into()))
|
||||
.chain(character_panel::render_character_panel(bytes))
|
||||
.collect();
|
||||
impl App {
|
||||
#[allow(mismatched_lifetime_syntaxes)]
|
||||
fn render_line(&self, address: usize, bytes: &[u8; BYTES_PER_LINE]) -> Line {
|
||||
let spans: Vec<Span<'_>> = iter::once(address::render_address(address))
|
||||
.chain(self.render_chunks(address, bytes))
|
||||
.chain(iter::once(" ".into()))
|
||||
.chain(character_panel::render_character_panel(bytes))
|
||||
.collect();
|
||||
|
||||
Line::from(spans)
|
||||
Line::from(spans)
|
||||
}
|
||||
}
|
||||
|
||||
mod address {
|
||||
@@ -53,38 +55,60 @@ mod address {
|
||||
mod hex {
|
||||
use std::{borrow::Cow, mem};
|
||||
use itertools::Itertools;
|
||||
use ratatui::{style::{Color, Style}, text::Span};
|
||||
use ratatui::{style::{Color, Style, Stylize}, text::Span};
|
||||
|
||||
use crate::{BYTES_PER_LINE, BYTES_PER_CHUNK, cardinality::HasCardinality, empty_span::empty_span};
|
||||
use crate::{BYTES_PER_LINE, BYTES_PER_CHUNK, app::App, cardinality::HasCardinality, empty_span::empty_span, cursor::InCursor};
|
||||
|
||||
pub fn render_chunks(bytes: &[u8; BYTES_PER_LINE]) -> impl IntoIterator<Item=Span<'static>> {
|
||||
let (chunks, remainder) = bytes.as_chunks::<BYTES_PER_CHUNK>();
|
||||
impl App {
|
||||
pub fn render_chunks(
|
||||
&self,
|
||||
address: usize,
|
||||
bytes: &[u8; BYTES_PER_LINE]
|
||||
) -> impl IntoIterator<Item=Span<'static>> {
|
||||
let (chunks, remainder) = bytes.as_chunks::<BYTES_PER_CHUNK>();
|
||||
|
||||
assert!(remainder.is_empty());
|
||||
|
||||
#[allow(unstable_name_collisions)]
|
||||
chunks
|
||||
.iter()
|
||||
.copied()
|
||||
.zip((address..).step_by(BYTES_PER_CHUNK))
|
||||
.map(|(chunk, address)| self.render_chunk(address, chunk))
|
||||
.intersperse(vec![" ".into()])
|
||||
.flatten()
|
||||
}
|
||||
|
||||
assert!(remainder.is_empty());
|
||||
fn render_chunk(
|
||||
&self,
|
||||
address: usize,
|
||||
bytes: [u8; BYTES_PER_CHUNK]
|
||||
) -> Vec<Span<'static>> {
|
||||
#[allow(unstable_name_collisions)]
|
||||
bytes
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(address..)
|
||||
.map(|(byte, address)| self.render_byte_at(address, byte))
|
||||
.intersperse(" ".into()) // TODO: highlight if selected
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[allow(unstable_name_collisions)]
|
||||
chunks
|
||||
.iter()
|
||||
.copied()
|
||||
.map(render_chunk)
|
||||
.intersperse(vec![" ".into()])
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn render_chunk(bytes: [u8; BYTES_PER_CHUNK]) -> Vec<Span<'static>> {
|
||||
#[allow(unstable_name_collisions)]
|
||||
bytes
|
||||
.iter()
|
||||
.copied()
|
||||
.map(render_byte)
|
||||
.intersperse(" ".into())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn render_byte(byte: u8) -> Span<'static> {
|
||||
const SPAN_FOR_BYTE: [Span; u8::CARDINALITY] = create_byte_lookup_table();
|
||||
|
||||
SPAN_FOR_BYTE[byte as usize].clone()
|
||||
fn render_byte_at(
|
||||
&self,
|
||||
address: usize,
|
||||
byte: u8
|
||||
) -> Span<'static> {
|
||||
const SPAN_FOR_BYTE: [Span; u8::CARDINALITY] = create_byte_lookup_table();
|
||||
|
||||
let span = SPAN_FOR_BYTE[byte as usize].clone();
|
||||
|
||||
match self.cursor.contains(address) {
|
||||
Some(InCursor::Head) => span.bg(Color::Gray),
|
||||
Some(InCursor::Rest) => span.bg(Color::Indexed(242)),
|
||||
None => span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn create_byte_lookup_table() -> [Span<'static>; u8::CARDINALITY] {
|
||||
@@ -144,7 +168,9 @@ mod character_panel {
|
||||
|
||||
use crate::{BYTES_PER_LINE, cardinality::HasCardinality, empty_span::empty_span};
|
||||
|
||||
pub fn render_character_panel(bytes: &[u8; BYTES_PER_LINE]) -> impl IntoIterator<Item=Span<'static>> {
|
||||
pub fn render_character_panel(
|
||||
bytes: &[u8; BYTES_PER_LINE]
|
||||
) -> impl IntoIterator<Item=Span<'static>> {
|
||||
bytes
|
||||
.iter()
|
||||
.copied()
|
||||
|
||||
Reference in New Issue
Block a user