add status line
This commit is contained in:
+29
-6
@@ -1,5 +1,6 @@
|
|||||||
use std::{cmp::min, env, fs::File, io::Read, process::exit};
|
use std::{cmp::min, env, fs::File, io::Read, path::PathBuf, process::exit};
|
||||||
use crossterm::{event::{self, Event, KeyCode, KeyModifiers}, terminal::window_size};
|
use crossterm::{event::{self, Event, KeyCode, KeyModifiers}, terminal::window_size};
|
||||||
|
use ratatui::style::Color;
|
||||||
|
|
||||||
use crate::{BYTES_PER_LINE, cursor::Cursor};
|
use crate::{BYTES_PER_LINE, cursor::Cursor};
|
||||||
|
|
||||||
@@ -7,6 +8,7 @@ mod widget;
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
pub file_name: String,
|
||||||
pub contents: Vec<u8>,
|
pub contents: Vec<u8>,
|
||||||
pub window_rows: usize,
|
pub window_rows: usize,
|
||||||
pub scroll_position: usize,
|
pub scroll_position: usize,
|
||||||
@@ -19,7 +21,7 @@ pub struct App {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
Normal, Visual, Insert
|
Normal, Select, Insert
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -27,6 +29,24 @@ pub enum PartialShortcut {
|
|||||||
Goto, Zview
|
Goto, Zview
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Mode {
|
||||||
|
pub const fn label(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Normal => " NORMAL ",
|
||||||
|
Self::Select => " SELECT ",
|
||||||
|
Self::Insert => " INSERT ",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn color(&self) -> Color {
|
||||||
|
match self {
|
||||||
|
Self::Normal => Color::Blue,
|
||||||
|
Self::Select => Color::Yellow,
|
||||||
|
Self::Insert => Color::Green,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn init() -> Self {
|
pub fn init() -> Self {
|
||||||
let input_files: Vec<_> = env::args().skip(1).collect();
|
let input_files: Vec<_> = env::args().skip(1).collect();
|
||||||
@@ -38,15 +58,17 @@ impl App {
|
|||||||
|
|
||||||
assert!(input_files.len() == 1);
|
assert!(input_files.len() == 1);
|
||||||
|
|
||||||
let file_name = input_files.first().unwrap();
|
let file_path: PathBuf = input_files.first().unwrap().into();
|
||||||
|
|
||||||
let file = File::open(file_name);
|
let file = File::open(&file_path);
|
||||||
let mut contents = Vec::new();
|
let mut contents = Vec::new();
|
||||||
file.unwrap().read_to_end(&mut contents).unwrap();
|
file.unwrap().read_to_end(&mut contents).unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
file_name: file_path.file_name().unwrap().to_str().unwrap().to_owned(),
|
||||||
contents,
|
contents,
|
||||||
window_rows: window_size().unwrap().rows as usize,
|
// -1 because of the status line
|
||||||
|
window_rows: window_size().unwrap().rows as usize - 1,
|
||||||
scroll_position: 0,
|
scroll_position: 0,
|
||||||
cursor: Cursor::default(),
|
cursor: Cursor::default(),
|
||||||
should_quit: false,
|
should_quit: false,
|
||||||
@@ -66,7 +88,8 @@ impl App {
|
|||||||
#[allow(clippy::collapsible_match)]
|
#[allow(clippy::collapsible_match)]
|
||||||
match (&self.mode, event::read().unwrap(), &self.partial_shortcut) {
|
match (&self.mode, event::read().unwrap(), &self.partial_shortcut) {
|
||||||
(Mode::Normal, Event::Resize(_, height), _) => {
|
(Mode::Normal, Event::Resize(_, height), _) => {
|
||||||
self.window_rows = height as usize;
|
// -1 because of the status line
|
||||||
|
self.window_rows = height as usize - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
(Mode::Normal, Event::Key(key_event), None)
|
(Mode::Normal, Event::Key(key_event), None)
|
||||||
|
|||||||
+46
-20
@@ -1,14 +1,13 @@
|
|||||||
use std::{cmp::min, iter};
|
use std::{cmp::min, iter};
|
||||||
use ratatui::{text::{Line, Span, Text}, widgets::Widget};
|
use ratatui::{buffer::Buffer, layout::Rect, text::{Line, Text}, widgets::Widget};
|
||||||
|
|
||||||
use crate::{BYTES_PER_LINE, app::App};
|
use crate::{BYTES_PER_LINE, app::App};
|
||||||
|
|
||||||
impl Widget for &App {
|
impl Widget for &App {
|
||||||
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let screen_end = self.scroll_position + BYTES_PER_LINE * (area.height as usize);
|
let screen_end = self.scroll_position + BYTES_PER_LINE * (area.height as usize - 1);
|
||||||
let bytes_end = min(screen_end, self.contents.len());
|
let bytes_end = min(screen_end, self.contents.len());
|
||||||
|
|
||||||
// TODO: bounds check this
|
|
||||||
let bytes_to_render = &self.contents[self.scroll_position..bytes_end];
|
let bytes_to_render = &self.contents[self.scroll_position..bytes_end];
|
||||||
|
|
||||||
let (chunks, remainder) = bytes_to_render
|
let (chunks, remainder) = bytes_to_render
|
||||||
@@ -16,28 +15,28 @@ impl Widget for &App {
|
|||||||
|
|
||||||
assert!(remainder.is_empty());
|
assert!(remainder.is_empty());
|
||||||
|
|
||||||
let lines: Vec<_> = chunks
|
let hex_lines = chunks
|
||||||
.iter()
|
.iter()
|
||||||
.zip((self.scroll_position..).step_by(BYTES_PER_LINE))
|
.zip((self.scroll_position..).step_by(BYTES_PER_LINE))
|
||||||
.map(|(bytes, address)| self.render_line(address, bytes))
|
.map(|(bytes, address)| self.render_line(address, bytes));
|
||||||
.collect();
|
|
||||||
|
|
||||||
let text = Text::from(lines);
|
let hex_area_text: Text = hex_lines.collect();
|
||||||
|
let hex_area = Rect::new(area.x, area.y, area.width, area.height - 1);
|
||||||
|
hex_area_text.render(hex_area, buf);
|
||||||
|
|
||||||
text.render(area, buf);
|
let status_line_area = Rect::new(area.x, area.bottom() - 1, area.width, 1);
|
||||||
|
self.render_status_line().render(status_line_area, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
#[allow(mismatched_lifetime_syntaxes)]
|
#[allow(mismatched_lifetime_syntaxes)]
|
||||||
fn render_line(&self, address: usize, bytes: &[u8; BYTES_PER_LINE]) -> Line {
|
fn render_line(&self, address: usize, bytes: &[u8; BYTES_PER_LINE]) -> Line {
|
||||||
let spans: Vec<Span<'_>> = iter::once(address::render_address(address))
|
iter::once(address::render_address(address))
|
||||||
.chain(self.render_chunks(address, bytes))
|
.chain(self.render_chunks(address, bytes))
|
||||||
.chain(iter::once(" ".into()))
|
.chain(iter::once(" ".into()))
|
||||||
.chain(character_panel::render_character_panel(bytes))
|
.chain(character_panel::render_character_panel(bytes))
|
||||||
.collect();
|
.collect()
|
||||||
|
|
||||||
Line::from(spans)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,14 +56,14 @@ mod hex {
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ratatui::{style::{Color, Style, Stylize}, text::Span};
|
use ratatui::{style::{Color, Style, Stylize}, text::Span};
|
||||||
|
|
||||||
use crate::{BYTES_PER_CHUNK, BYTES_PER_LINE, CHUNKS_PER_LINE, app::App, cardinality::HasCardinality, cursor::InCursor, empty_span::empty_span, select_grey::SelectGrey};
|
use crate::{BYTES_PER_CHUNK, BYTES_PER_LINE, CHUNKS_PER_LINE, app::App, cardinality::HasCardinality, cursor::InCursor, empty_span::empty_span, custom_greys::CustomGreys};
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn render_chunks(
|
pub fn render_chunks(
|
||||||
&self,
|
&self,
|
||||||
address: usize,
|
address: usize,
|
||||||
bytes: &[u8; BYTES_PER_LINE]
|
bytes: &[u8; BYTES_PER_LINE]
|
||||||
) -> impl IntoIterator<Item=Span<'static>> {
|
) -> impl Iterator<Item=Span<'static>> {
|
||||||
let (chunks, remainder) = bytes.as_chunks::<BYTES_PER_CHUNK>();
|
let (chunks, remainder) = bytes.as_chunks::<BYTES_PER_CHUNK>();
|
||||||
|
|
||||||
assert!(remainder.is_empty());
|
assert!(remainder.is_empty());
|
||||||
@@ -74,7 +73,7 @@ mod hex {
|
|||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.zip((address..).step_by(BYTES_PER_CHUNK))
|
.zip((address..).step_by(BYTES_PER_CHUNK))
|
||||||
.map(|(chunk, address)| self.render_chunk(address, chunk))
|
.map(|(chunk, address)| self.render_chunk(address, &chunk).collect())
|
||||||
.interleave(
|
.interleave(
|
||||||
(address..)
|
(address..)
|
||||||
.step_by(BYTES_PER_CHUNK)
|
.step_by(BYTES_PER_CHUNK)
|
||||||
@@ -88,8 +87,8 @@ mod hex {
|
|||||||
fn render_chunk(
|
fn render_chunk(
|
||||||
&self,
|
&self,
|
||||||
address: usize,
|
address: usize,
|
||||||
bytes: [u8; BYTES_PER_CHUNK]
|
bytes: &[u8; BYTES_PER_CHUNK]
|
||||||
) -> Vec<Span<'static>> {
|
) -> impl Iterator<Item=Span<'static>> {
|
||||||
#[allow(unstable_name_collisions)]
|
#[allow(unstable_name_collisions)]
|
||||||
bytes
|
bytes
|
||||||
.iter()
|
.iter()
|
||||||
@@ -102,7 +101,6 @@ mod hex {
|
|||||||
.skip(1)
|
.skip(1)
|
||||||
.map(|address| self.render_space_before(address))
|
.map(|address| self.render_space_before(address))
|
||||||
)
|
)
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_byte_at(
|
fn render_byte_at(
|
||||||
@@ -203,7 +201,7 @@ mod character_panel {
|
|||||||
|
|
||||||
pub fn render_character_panel(
|
pub fn render_character_panel(
|
||||||
bytes: &[u8; BYTES_PER_LINE]
|
bytes: &[u8; BYTES_PER_LINE]
|
||||||
) -> impl IntoIterator<Item=Span<'static>> {
|
) -> impl Iterator<Item=Span<'static>> {
|
||||||
bytes
|
bytes
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
@@ -257,3 +255,31 @@ mod character_panel {
|
|||||||
LOOK_UP_TABLE[byte as usize]
|
LOOK_UP_TABLE[byte as usize]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod status_line {
|
||||||
|
use crate::{app::App, custom_greys::CustomGreys};
|
||||||
|
use ratatui::{style::{Color, Stylize}, text::{Line, Span, Text}};
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn render_status_line(&self) -> Text<'_> {
|
||||||
|
Text::from(
|
||||||
|
Line::from_iter([
|
||||||
|
self.render_mode(),
|
||||||
|
" ".into(),
|
||||||
|
self.render_file_name()
|
||||||
|
])
|
||||||
|
)
|
||||||
|
.bg(Color::ui_grey())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_mode(&self) -> Span<'static> {
|
||||||
|
Span::from(self.mode.label())
|
||||||
|
.fg(Color::Black)
|
||||||
|
.bg(self.mode.color())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_file_name(&self) -> Span<'_> {
|
||||||
|
Span::from(&self.file_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
use ratatui::style::Color;
|
||||||
|
|
||||||
|
pub trait CustomGreys {
|
||||||
|
fn select_grey() -> Self;
|
||||||
|
fn ui_grey() -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomGreys for Color {
|
||||||
|
fn select_grey() -> Self {
|
||||||
|
Self::Indexed(242)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui_grey() -> Self {
|
||||||
|
Self::Indexed(238)
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -5,7 +5,7 @@ use app::App;
|
|||||||
|
|
||||||
mod cardinality;
|
mod cardinality;
|
||||||
mod empty_span;
|
mod empty_span;
|
||||||
mod select_grey;
|
mod custom_greys;
|
||||||
mod app;
|
mod app;
|
||||||
mod cursor;
|
mod cursor;
|
||||||
|
|
||||||
@@ -16,6 +16,8 @@ const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
|
|||||||
// TODO:
|
// TODO:
|
||||||
// - modes
|
// - modes
|
||||||
// - g/v/z
|
// - g/v/z
|
||||||
|
// - search
|
||||||
|
// - jumplist
|
||||||
// - modifications
|
// - modifications
|
||||||
// - insert/append
|
// - insert/append
|
||||||
// - replace
|
// - replace
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
use ratatui::style::Color;
|
|
||||||
|
|
||||||
pub trait SelectGrey {
|
|
||||||
fn select_grey() -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SelectGrey for Color {
|
|
||||||
fn select_grey() -> Self {
|
|
||||||
Self::Indexed(242)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user