diff options
| author | Adandi <rwindowed@gmail.com> | 2025-03-21 15:57:22 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-21 15:57:22 +0200 |
| commit | 018d4287dc2a564b2daf8d595692024b91681ea3 (patch) | |
| tree | 8a84529a6f08138d2842718b21430475dca4b46a /src | |
| parent | 1fcd3aa2098de576f5551af0c332665f9233eb4c (diff) | |
Add files via upload
Diffstat (limited to 'src')
| -rw-r--r-- | src/hex.rs | 35 | ||||
| -rw-r--r-- | src/input.rs | 23 | ||||
| -rw-r--r-- | src/main.rs | 17 | ||||
| -rw-r--r-- | src/render.rs | 67 |
4 files changed, 142 insertions, 0 deletions
diff --git a/src/hex.rs b/src/hex.rs new file mode 100644 index 0000000..6dfbe7f --- /dev/null +++ b/src/hex.rs @@ -0,0 +1,35 @@ +use std::{fs, io}; +use std::io::stdout; +use ratatui::{backend::CrosstermBackend, Terminal}; +use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}}; +use crate::{input, render}; + +pub struct HexEditor { + filename: String, + data: Vec<u8>, + cursor: usize, + terminal: Terminal<CrosstermBackend<std::io::Stdout>>, +} + +impl HexEditor { + pub fn new(filename: String, data: Vec<u8>) -> io::Result<Self> { + enable_raw_mode()?; + execute!(stdout(), EnterAlternateScreen)?; + let terminal = Terminal::new(CrosstermBackend::new(stdout()))?; + Ok(Self { filename, data, cursor: 0, terminal }) + } + + pub fn run(&mut self) -> io::Result<()> { + while input::handle_input(&mut self.cursor, &mut self.data[..])? { + render::draw(&mut self.terminal, &self.data, self.cursor, &self.filename)?; + } + self.cleanup() + } + + pub fn cleanup(&mut self) -> io::Result<()> { + disable_raw_mode()?; + execute!(stdout(), LeaveAlternateScreen)?; + fs::write(&self.filename, &self.data)?; + Ok(()) + } +} diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 0000000..0db7e16 --- /dev/null +++ b/src/input.rs @@ -0,0 +1,23 @@ +use std::{io, time::Duration}; +use crossterm::{event::{self, KeyCode, KeyEvent}}; + +pub fn handle_input(cursor: &mut usize, data: &mut [u8]) -> io::Result<bool> { + if event::poll(Duration::from_millis(100))? { + if let event::Event::Key(KeyEvent { code, .. }) = event::read()? { + match code { + KeyCode::Esc => return Ok(false), + KeyCode::Left if *cursor > 0 => *cursor -= 1, + KeyCode::Right if *cursor < data.len() * 2 - 1 => *cursor += 1, + KeyCode::Up if *cursor >= 32 => *cursor -= 32, + KeyCode::Down if *cursor + 32 < data.len() * 2 => *cursor += 32, + KeyCode::Char(c) if c.is_ascii_hexdigit() => { + let byte_index = *cursor / 2; + let shift = if *cursor % 2 == 0 { 4 } else { 0 }; + data[byte_index] = (data[byte_index] & (0x0F << (4 - shift))) | (c.to_digit(16).unwrap() as u8) << shift; + } + _ => {} + } + } + } + Ok(true) +} diff --git a/src/main.rs b/src/main.rs index 8b13789..a79f7cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1 +1,18 @@ +mod hex; +mod input; +mod render; +use hex::HexEditor; +use std::{env, fs, io}; + +fn main() -> io::Result<()> { + let args: Vec<String> = env::args().collect(); + if args.len() < 2 { + return Ok(()); + } + + let filename = args[1].clone(); + let data = fs::read(&filename)?; + let mut editor = HexEditor::new(filename, data)?; + editor.run() +} diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 0000000..a0cb234 --- /dev/null +++ b/src/render.rs @@ -0,0 +1,67 @@ +use std::io; +use ratatui::{ + layout::{Constraint, Direction, Layout}, + style::{Color, Style}, + text::{Line, Span}, + widgets::{Block, Borders, Paragraph}, + Terminal, + prelude::Alignment, +}; + +pub fn draw(terminal: &mut Terminal<impl ratatui::backend::Backend>, data: &[u8], cursor: usize, filename: &str) -> io::Result<()> { + terminal.draw(|frame| { + let size = frame.size(); + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Min(1), Constraint::Length(1)]) + .split(size); + + let lines: Vec<Line> = data.chunks(16).enumerate().map(|(i, chunk)| { + let addr = Span::raw(format!("{:08x} │ ", i * 16)); + let mut hex: Vec<Span> = Vec::new(); + let mut ascii_repr = String::new(); + + for (j, &byte) in chunk.iter().enumerate() { + let hex_str = format!("{:02x}", byte); + let index = i * 32 + j * 2; + + for (k, ch) in hex_str.chars().enumerate() { + let cur_idx = index + k; + let style = if cur_idx == cursor { + Style::default().bg(Color::White).fg(Color::Black) + } else { + Style::default() + }; + hex.push(Span::styled(ch.to_string(), style)); + } + + if j < 15 { + let space = if j % 4 == 3 { " " } else { " " }; + hex.push(Span::raw(space)); + } + + ascii_repr.push(if byte.is_ascii_graphic() || byte == b' ' { byte as char } else { '.' }); + } + + let missing_bytes = 16 - chunk.len(); + let padding = missing_bytes * 3; + let spaces = " ".repeat(padding); + + let ascii = Span::raw(format!("{} │ {}", spaces, ascii_repr)); + + Line::from(vec![addr] + .into_iter() + .chain(hex) + .chain(std::iter::once(ascii)) + .collect::<Vec<_>>()) + }).collect(); + + frame.render_widget(Paragraph::new(lines).block(Block::default().borders(Borders::ALL)), layout[0]); + + let progress = if data.is_empty() { 100 } else { (cursor / 2 * 100) / data.len() }; + let status_text = format!("{} \t 0x{:08x} \t {:3}%", filename, cursor / 2, progress); + let status_bar = Paragraph::new(Line::from(Span::raw(status_text))).alignment(Alignment::Center); + frame.render_widget(status_bar, layout[1]); + })?; + Ok(()) +} |
