It shows bytes I guess
This commit is contained in:
parent
936c296449
commit
1ed84e6dee
13 changed files with 3986 additions and 2 deletions
19
.direnv/bin/nix-direnv-reload
Executable file
19
.direnv/bin/nix-direnv-reload
Executable file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
if [[ ! -d "/mnt/removable/Projects/Rust/hexconv" ]]; then
|
||||||
|
echo "Cannot find source directory; Did you move it?"
|
||||||
|
echo "(Looking for "/mnt/removable/Projects/Rust/hexconv")"
|
||||||
|
echo 'Cannot force reload with this script - use "direnv reload" manually and then try again'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# rebuild the cache forcefully
|
||||||
|
_nix_direnv_force_reload=1 direnv exec "/mnt/removable/Projects/Rust/hexconv" true
|
||||||
|
|
||||||
|
# Update the mtime for .envrc.
|
||||||
|
# This will cause direnv to reload again - but without re-building.
|
||||||
|
touch "/mnt/removable/Projects/Rust/hexconv/.envrc"
|
||||||
|
|
||||||
|
# Also update the timestamp of whatever profile_rc we have.
|
||||||
|
# This makes sure that we know we are up to date.
|
||||||
|
touch -r "/mnt/removable/Projects/Rust/hexconv/.envrc" "/mnt/removable/Projects/Rust/hexconv/.direnv"/*.rc
|
||||||
1
.direnv/flake-inputs/316cjg3w8hhq1iv11lila4kswkscv833-source
Symbolic link
1
.direnv/flake-inputs/316cjg3w8hhq1iv11lila4kswkscv833-source
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
/nix/store/316cjg3w8hhq1iv11lila4kswkscv833-source
|
||||||
1
.direnv/flake-inputs/fjmkkw7yy5da27n94hyk0zcda538gf97-source
Symbolic link
1
.direnv/flake-inputs/fjmkkw7yy5da27n94hyk0zcda538gf97-source
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
/nix/store/fjmkkw7yy5da27n94hyk0zcda538gf97-source
|
||||||
1
.direnv/flake-inputs/yspm41avnsc4gr8cjamkv0cf10i3spsi-source
Symbolic link
1
.direnv/flake-inputs/yspm41avnsc4gr8cjamkv0cf10i3spsi-source
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
/nix/store/yspm41avnsc4gr8cjamkv0cf10i3spsi-source
|
||||||
1
.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa
Symbolic link
1
.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
/nix/store/swz4b263qq4jdxs4fk62cnrg1v7jvmgr-nix-shell-env
|
||||||
2182
.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa.rc
Normal file
2182
.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa.rc
Normal file
File diff suppressed because it is too large
Load diff
1511
Cargo.lock
generated
Normal file
1511
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -3,4 +3,13 @@ name = "hexconv"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
crossterm = "0.29.0"
|
||||||
|
ratatui = "0.30.0"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
codegen-units = 1
|
||||||
|
lto = true
|
||||||
|
opt-level = "s"
|
||||||
|
strip = true
|
||||||
|
|
|
||||||
56
src/app_state.rs
Normal file
56
src/app_state.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, Read};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use ratatui::layout::Rect;
|
||||||
|
|
||||||
|
pub struct AppState {
|
||||||
|
bytes_buffer: Vec<u8>,
|
||||||
|
top: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
pub fn get_bytes(&self, bytes: usize) -> &[u8] {
|
||||||
|
let offset = (self.bytes_buffer.len() - self.top).min(bytes);
|
||||||
|
&self.bytes_buffer[self.top..self.top + offset]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_top(&self) -> usize {
|
||||||
|
self.top
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(path: &Path) -> io::Result<Self> {
|
||||||
|
let mut file_buffer = [0; 4096];
|
||||||
|
let len;
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut file = File::open(path)?;
|
||||||
|
len = file.read(&mut file_buffer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
bytes_buffer: file_buffer[0..len].to_vec(),
|
||||||
|
top: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_up(&mut self, by: usize) {
|
||||||
|
self.top =
|
||||||
|
(self.top + 16 * by).min(self.bytes_buffer.len() - (self.bytes_buffer.len() % 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_down(&mut self, by: usize) {
|
||||||
|
self.top = self.top.saturating_sub(16 * by);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn correct_scroll(&mut self, area: Rect) {
|
||||||
|
let visible_lines = area.height as usize;
|
||||||
|
let lines = self.get_bytes(visible_lines * 16);
|
||||||
|
|
||||||
|
let loaded_lines = lines.len().div_ceil(16);
|
||||||
|
|
||||||
|
if loaded_lines < visible_lines {
|
||||||
|
self.scroll_down(visible_lines - loaded_lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/dump.rs
Normal file
46
src/dump.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
use ratatui::prelude::*;
|
||||||
|
use ratatui::widgets::Paragraph;
|
||||||
|
|
||||||
|
pub struct DumpWidget<'a>(pub &'a [u8]);
|
||||||
|
|
||||||
|
fn bytes_to_str<'a>(bytes: &[u8], out: &'a mut [u8; 49]) -> &'a str {
|
||||||
|
const HEX: &[u8; 16] = b"0123456789ABCDEF";
|
||||||
|
let len = bytes.len();
|
||||||
|
|
||||||
|
let mut min = 8.min(len);
|
||||||
|
let mut counter = 2 + (len - 1) * 3;
|
||||||
|
|
||||||
|
for (index, byte) in (bytes[0..min]).iter().enumerate() {
|
||||||
|
out[index * 3] = HEX[(byte >> 4) as usize];
|
||||||
|
out[index * 3 + 1] = HEX[(byte & 0x0f) as usize];
|
||||||
|
out[index * 3 + 2] = b' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.len() > 8 {
|
||||||
|
counter += 2;
|
||||||
|
min = 16.min(len);
|
||||||
|
|
||||||
|
for (index, byte) in (bytes[8..min]).iter().enumerate() {
|
||||||
|
out[25 + index * 3] = b' ';
|
||||||
|
out[25 + index * 3 + 1] = HEX[(byte >> 4) as usize];
|
||||||
|
out[25 + index * 3 + 2] = HEX[(byte & 0x0f) as usize];
|
||||||
|
}
|
||||||
|
|
||||||
|
out[24] = b'|';
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { std::str::from_utf8_unchecked(&out[..counter]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Widget for DumpWidget<'a> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let visible_lines = area.height as usize;
|
||||||
|
let constraints = Layout::vertical(vec![Constraint::Length(1); visible_lines]).split(area);
|
||||||
|
|
||||||
|
let mut out = [b' '; 49];
|
||||||
|
|
||||||
|
for (line, constraint) in self.0.chunks(16).zip(constraints.iter()) {
|
||||||
|
Paragraph::new(bytes_to_str(line, &mut out)).render(*constraint, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/line_number.rs
Normal file
34
src/line_number.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
use ratatui::prelude::*;
|
||||||
|
use ratatui::widgets::Widget;
|
||||||
|
|
||||||
|
pub struct LineNumberWidget(pub usize, pub usize);
|
||||||
|
|
||||||
|
fn usize_to_hex(mut value: usize, buffer: &mut [u8; 64]) -> &str {
|
||||||
|
const HEX: &[u8; 16] = b"0123456789ABCDEF";
|
||||||
|
let len = std::mem::size_of_val(&value);
|
||||||
|
|
||||||
|
for i in 0..len {
|
||||||
|
let current_byte = value & 0xff;
|
||||||
|
|
||||||
|
buffer[63 - 2 * i] = HEX[current_byte & 0x0f];
|
||||||
|
buffer[63 - 2 * i - 1] = HEX[current_byte >> 4];
|
||||||
|
|
||||||
|
value >>= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { std::str::from_utf8_unchecked(&buffer[64 - len * 2..]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for LineNumberWidget {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let visible_lines = area.height as usize;
|
||||||
|
let mut buffer = [b'0'; 64];
|
||||||
|
|
||||||
|
let areas =
|
||||||
|
Layout::vertical(vec![Constraint::Length(1); visible_lines.min(self.1)]).split(area);
|
||||||
|
|
||||||
|
for (i, a) in areas.iter().enumerate() {
|
||||||
|
Line::from(usize_to_hex(self.0 + i * 16, &mut buffer)).render(*a, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
97
src/main.rs
97
src/main.rs
|
|
@ -1,3 +1,96 @@
|
||||||
fn main() {
|
mod app_state;
|
||||||
println!("Hello, world!");
|
mod dump;
|
||||||
|
mod line_number;
|
||||||
|
mod text_preview;
|
||||||
|
|
||||||
|
use ratatui::{
|
||||||
|
DefaultTerminal, Frame,
|
||||||
|
crossterm::event::{self, Event, KeyCode, KeyEventKind},
|
||||||
|
layout::{Constraint, Direction, Layout},
|
||||||
|
};
|
||||||
|
use std::{io, path::Path};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app_state::AppState, dump::DumpWidget, line_number::LineNumberWidget, text_preview::TextPreview,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() -> io::Result<()> {
|
||||||
|
let mut terminal = ratatui::init();
|
||||||
|
let app_result = App::new()?.run(&mut terminal);
|
||||||
|
|
||||||
|
ratatui::restore();
|
||||||
|
app_result
|
||||||
|
}
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
app_state: AppState,
|
||||||
|
exit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn new() -> io::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
app_state: AppState::new(Path::new("/home/maxag/ree.txt"))?,
|
||||||
|
exit: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
|
||||||
|
while !self.exit {
|
||||||
|
terminal.draw(|frame| self.draw(frame))?;
|
||||||
|
let _ = self.handle_events();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_events(&mut self) -> io::Result<()> {
|
||||||
|
let Event::Key(current_event) = event::read()? else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
if current_event.kind != KeyEventKind::Press {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
match current_event.code {
|
||||||
|
KeyCode::Char('q') => {
|
||||||
|
self.exit = true;
|
||||||
|
}
|
||||||
|
KeyCode::Up => {
|
||||||
|
self.app_state.scroll_down(1);
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
self.app_state.scroll_up(1);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&mut self, frame: &mut Frame) {
|
||||||
|
let main_area = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints(vec![
|
||||||
|
Constraint::Length(18),
|
||||||
|
Constraint::Length(51),
|
||||||
|
Constraint::Length(20),
|
||||||
|
])
|
||||||
|
.split(frame.area());
|
||||||
|
|
||||||
|
if frame.area().width < 10 || frame.area().height < 30 {
|
||||||
|
self.exit = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let visible_lines = main_area[1].height as usize;
|
||||||
|
self.app_state.correct_scroll(main_area[1]);
|
||||||
|
|
||||||
|
let bytes = self.app_state.get_bytes(visible_lines * 16);
|
||||||
|
let top = self.app_state.get_top();
|
||||||
|
let lines = bytes.len().div_ceil(16);
|
||||||
|
|
||||||
|
frame.render_widget(DumpWidget(bytes), main_area[1]);
|
||||||
|
frame.render_widget(LineNumberWidget(top, lines), main_area[0]);
|
||||||
|
frame.render_widget(TextPreview(bytes), main_area[2]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
30
src/text_preview.rs
Normal file
30
src/text_preview.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
use ratatui::{prelude::*, widgets::Paragraph};
|
||||||
|
|
||||||
|
pub struct TextPreview<'a>(pub &'a [u8]);
|
||||||
|
|
||||||
|
fn bytes_to_str<'a>(bytes: &[u8], out: &'a mut [u8; 19]) -> &'a str {
|
||||||
|
let len = bytes.len() + 1;
|
||||||
|
out[0] = b'|';
|
||||||
|
|
||||||
|
for (index, byte) in bytes.iter().enumerate() {
|
||||||
|
out[index + 1] = if *byte > 31 && *byte < 128 {
|
||||||
|
*byte
|
||||||
|
} else {
|
||||||
|
b'.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { std::str::from_utf8_unchecked(&out[..len]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for TextPreview<'_> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let mut line_buffer = [0; 19];
|
||||||
|
let visible_lines = area.height as usize;
|
||||||
|
let constraints = Layout::vertical(vec![Constraint::Length(1); visible_lines]).split(area);
|
||||||
|
|
||||||
|
for (line, constraint) in self.0.chunks(16).zip(constraints.iter()) {
|
||||||
|
Paragraph::new(bytes_to_str(line, &mut line_buffer)).render(*constraint, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue