From 13e411a637b6706ede1f6f7fcff55f5a5fa04c5b Mon Sep 17 00:00:00 2001 From: Ahmed <> Date: Sun, 15 Sep 2024 00:57:35 +0200 Subject: [PATCH] Initial Support for Visual mode Signed-off-by: Ahmed <> --- Cargo.toml | 5 +- rair/Cargo.toml | 1 + rair/src/rair.rs | 2 + visual/Cargo.toml | 11 ++ visual/src/lib.rs | 14 +++ visual/src/visual_hex/addresses.rs | 33 +++++ visual/src/visual_hex/ascii.rs | 70 +++++++++++ visual/src/visual_hex/command.rs | 51 ++++++++ visual/src/visual_hex/editor.rs | 192 +++++++++++++++++++++++++++++ visual/src/visual_hex/events.rs | 63 ++++++++++ visual/src/visual_hex/help.rs | 53 ++++++++ visual/src/visual_hex/hex.rs | 57 +++++++++ visual/src/visual_hex/mod.rs | 77 ++++++++++++ visual/src/visual_hex/mode.rs | 15 +++ visual/src/visual_hex/render.rs | 74 +++++++++++ 15 files changed, 717 insertions(+), 1 deletion(-) create mode 100644 visual/Cargo.toml create mode 100644 visual/src/lib.rs create mode 100644 visual/src/visual_hex/addresses.rs create mode 100644 visual/src/visual_hex/ascii.rs create mode 100644 visual/src/visual_hex/command.rs create mode 100644 visual/src/visual_hex/editor.rs create mode 100644 visual/src/visual_hex/events.rs create mode 100644 visual/src/visual_hex/help.rs create mode 100644 visual/src/visual_hex/hex.rs create mode 100644 visual/src/visual_hex/mod.rs create mode 100644 visual/src/visual_hex/mode.rs create mode 100644 visual/src/visual_hex/render.rs diff --git a/Cargo.toml b/Cargo.toml index 677bd0f..c61305a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "rair", "test_file", "trees", + "visual", ] [workspace.dependencies] @@ -24,6 +25,7 @@ nom = "7.1.3" parking_lot="0.12.3" pest = "2.7.12" pest_derive = "2.7.12" +ratatui = "0.28.1" rustyline = "14.0.0" rustyline-derive = "0.10.0" serde = "1.0" @@ -32,6 +34,7 @@ serde_json = "1.0" tempfile = "3.12.0" yansi = "1.0.1" +rair = {path = "./rair"} rair-cmd = {path = "./cmd"} rair-core = {path = "./core"} rair-env = {path = "./env"} @@ -39,7 +42,7 @@ rair-eval = {path = "./eval"} test_file = {path = "./test_file"} rair-io = {path = "./io"} rair-trees = {path = "./trees"} - +rair-visual = {path = "./visual"} [profile.release] codegen-units = 1 lto = "fat" diff --git a/rair/Cargo.toml b/rair/Cargo.toml index 15e4412..91c67d1 100644 --- a/rair/Cargo.toml +++ b/rair/Cargo.toml @@ -23,6 +23,7 @@ rair-core = {workspace = true} rair-eval = {workspace = true} rair-io = {workspace = true} rair-trees = {workspace = true} +rair-visual = {workspace = true} rustyline = {workspace = true} rustyline-derive = {workspace = true} yansi = {workspace = true} diff --git a/rair/src/rair.rs b/rair/src/rair.rs index fb8e557..4c9a317 100644 --- a/rair/src/rair.rs +++ b/rair/src/rair.rs @@ -11,10 +11,12 @@ use cli::Args; use core::mem; use init::init_editor_from_core; use rair_core::{panic_msg, Core, Writer}; +use rair_visual::register_visual; use rpel::prompt_read_parse_evaluate_loop; fn main() { let mut core = Core::new(); + register_visual(&mut core); let editor = init_editor_from_core(&mut core); let args = Args::parse().unwrap_or_else(|e| panic_msg(&mut core, &e, "")); match args { diff --git a/visual/Cargo.toml b/visual/Cargo.toml new file mode 100644 index 0000000..80f44bd --- /dev/null +++ b/visual/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "rair-visual" +version = "0.1.0" +edition = "2021" + +[dependencies] +parking_lot = {workspace = true} +rair-core ={workspace = true} +rair-eval = {workspace = true} +rair-io = {workspace = true} +ratatui = {workspace = true} diff --git a/visual/src/lib.rs b/visual/src/lib.rs new file mode 100644 index 0000000..fd951ba --- /dev/null +++ b/visual/src/lib.rs @@ -0,0 +1,14 @@ +mod visual_hex; + +use parking_lot::Mutex; +use rair_core::Core; +use std::sync::Arc; +use visual_hex::VisualHex; + +pub fn register_visual(core: &mut Core) { + core.add_command( + "visualHex", + "vx", + Arc::new(Mutex::new(VisualHex::default())), + ); +} diff --git a/visual/src/visual_hex/addresses.rs b/visual/src/visual_hex/addresses.rs new file mode 100644 index 0000000..7f62630 --- /dev/null +++ b/visual/src/visual_hex/addresses.rs @@ -0,0 +1,33 @@ +use super::VisualHex; +use rair_core::Core; +use ratatui::prelude::*; +use std::iter::once; + +impl VisualHex { + /// Finds the maximum width of any address that will be currently rendered + pub(super) fn max_address_str_width(&mut self, core: &mut Core, chunk: &Rect) -> u16 { + let loc = core.get_loc(); + let size = self.calculate_bytes_to_render(chunk); + // we don't put the address of the last bytes, but we put the address + // and then the last byte is 16 + that address. + let max_loc = loc.saturating_add(size).saturating_sub(16); + format!("0x{:08x} ", max_loc).len() as u16 + } + /// renders the column of all addresses + header + pub(super) fn render_addresses(&mut self, f: &mut Frame, chunk: Rect, core: &mut Core) { + // TODO center the header + let header = format!("{}", core.mode); + let loc = core.get_loc(); + // TODO overflow here + let addresses = (1..chunk.height as u64).map(|i| format!("0x{:08x} ", loc + 16 * (i - 1))); + let env = core.env.read(); + let color = env.get_str("printHex.headerColor").unwrap(); + let (r, g, b) = env.get_color(color).unwrap(); + let lines: Vec<_> = once(header) + .chain(addresses) + .map(|s| Line::from(Span::styled(s, Style::default().fg(Color::Rgb(r, g, b))))) + .collect(); + let text = Text::from(lines); + f.render_widget(text, chunk); + } +} diff --git a/visual/src/visual_hex/ascii.rs b/visual/src/visual_hex/ascii.rs new file mode 100644 index 0000000..0ac1245 --- /dev/null +++ b/visual/src/visual_hex/ascii.rs @@ -0,0 +1,70 @@ +use super::{mode::Mode, VisualHex}; +use rair_core::Core; +use ratatui::prelude::*; +use std::collections::BTreeMap; + +type Rgb = (u8, u8, u8); + +impl VisualHex { + fn ascii_banner<'a>(&self, core: &Core) -> Line<'a> { + let env = core.env.read(); + let color = env.get_str("printHex.headerColor").unwrap(); + let (r, g, b) = env.get_color(color).unwrap(); + Line::from(Span::styled( + "0123456789ABCDEF", + Style::default().fg(Color::Rgb(r, g, b)), + )) + } + fn ascii_line<'a>( + &self, + loc: u64, + data: &BTreeMap, + gap: &'a str, + no_print: &'a str, + na: Rgb, + ) -> Line<'a> { + let mut spans = Vec::with_capacity(16); + for j in 0..16 { + let span = if let Some(c) = data.get(&(j + loc)) { + if *c >= 0x21 && *c <= 0x7E { + Span::from(format!("{}", *c as char)) + } else { + Span::styled(no_print, Style::default().fg(Color::Rgb(na.0, na.1, na.2))) + } + } else { + Span::from(gap) + }; + let span = if let Mode::Edit(editor) = &self.mode { + editor.span_ascii(span, loc + j) + } else { + span + }; + spans.push(span); + } + Line::default().spans(spans) + } + pub(super) fn render_ascii( + &self, + f: &mut Frame, + chunk: Rect, + core: &Core, + data: &BTreeMap, + ) { + let mut lines: Vec> = Vec::with_capacity(chunk.height as usize + 1); + lines.push(self.ascii_banner(core)); + + let env = core.env.read(); + let gap: &str = env.get_str("printHex.gapReplace").unwrap(); + let color = env.get_str("printHex.nonPrintColor").unwrap(); + let na = core.env.read().get_color(color).unwrap(); + let no_print = env.get_str("printHex.nonPrintReplace").unwrap(); + + let loc = core.get_loc(); + + for i in 0..chunk.height as u64 { + lines.push(self.ascii_line(loc + i * 16, data, gap, no_print, na)); + } + let txt = Text::from(lines); + f.render_widget(txt, chunk); + } +} diff --git a/visual/src/visual_hex/command.rs b/visual/src/visual_hex/command.rs new file mode 100644 index 0000000..2872f9f --- /dev/null +++ b/visual/src/visual_hex/command.rs @@ -0,0 +1,51 @@ +use ratatui::{ + layout::{Position, Rect}, + text::Line, + Frame, +}; +use std::cmp::max; + +#[derive(Default)] +pub struct Command { + input: String, + cursor: usize, +} + +impl Command { + pub fn move_cursor_left(&mut self) { + self.cursor = self.cursor.saturating_sub(1); + } + pub fn move_cursor_right(&mut self) { + self.cursor = max(self.cursor.saturating_add(1), self.input.len()) + } + pub fn delete_char(&mut self) { + if self.cursor == 0 { + return; + } + let from_left_to_custor = self.cursor - 1; + // Getting all characters before the selected character. + let before_char_to_delete = self.input.chars().take(from_left_to_custor); + // Getting all characters after selected character. + let after_char_to_delete = self.input.chars().skip(self.cursor); + self.input = before_char_to_delete.chain(after_char_to_delete).collect(); + self.move_cursor_left(); + } + pub fn enter_char(&mut self, c: char) { + let index = self + .input + .char_indices() + .map(|(i, _)| i) + .nth(self.cursor) + .unwrap_or(self.input.len()); + self.input.insert(index, c); + self.move_cursor_right(); + } + pub fn get_str(&self) -> &str { + &self.input + } + pub fn render(&self, f: &mut Frame, chunk: Rect) { + let input = Line::from(format!(":{}", self.input)); + f.render_widget(input, chunk); + f.set_cursor_position(Position::new(chunk.x + self.cursor as u16 + 1, chunk.y)); + } +} diff --git a/visual/src/visual_hex/editor.rs b/visual/src/visual_hex/editor.rs new file mode 100644 index 0000000..ba58cc1 --- /dev/null +++ b/visual/src/visual_hex/editor.rs @@ -0,0 +1,192 @@ +use rair_core::{AddrMode, Core}; +use ratatui::{ + style::{Style, Stylize}, + text::Span, +}; + +enum EditorMode { + Hex { first_char: bool }, + Ascii, +} +pub struct Editor { + loc: u64, + mode: EditorMode, +} + +impl Editor { + pub fn new(core: &Core) -> Self { + Editor { + loc: core.get_loc(), + mode: EditorMode::Hex { first_char: true }, + } + } + pub fn switch_mode(&mut self) { + match self.mode { + EditorMode::Hex { .. } => self.mode = EditorMode::Ascii, + EditorMode::Ascii => self.mode = EditorMode::Hex { first_char: true }, + } + } + /// invariant hex must be exactly two characters! + pub fn span_hex<'a>(&self, hex: String, loc: u64) -> Vec> { + if hex.len() != 2 { + panic!("Invariant broken: hex must be exactly two characters") + } + if loc != self.loc { + return vec![Span::from(hex)]; + } + match self.mode { + EditorMode::Ascii => vec![Span::styled(hex, Style::default().bold())], + EditorMode::Hex { first_char } => { + let chars: Vec<_> = hex.chars().collect(); + if first_char { + vec![ + Span::styled(String::from(chars[0]), Style::default().reversed()), + Span::from(String::from(chars[1])), + ] + } else { + vec![ + Span::from(String::from(chars[0])), + Span::styled(String::from(chars[1]), Style::default().reversed()), + ] + } + } + } + } + + pub fn span_ascii<'a>(&self, ascii: Span<'a>, loc: u64) -> Span<'a> { + if loc != self.loc { + return ascii; + } + match self.mode { + EditorMode::Ascii => ascii.patch_style(Style::default().reversed()), + EditorMode::Hex { .. } => ascii.patch_style(Style::default().bold()), + } + } + pub fn left(&mut self, core: &mut Core) { + match self.mode { + EditorMode::Hex { first_char: false } => { + self.mode = EditorMode::Hex { first_char: true }; + return; + } + EditorMode::Hex { first_char: true } => { + if self.loc == u64::MIN { + return; + } + self.mode = EditorMode::Hex { first_char: false }; + } + EditorMode::Ascii => { + if self.loc == u64::MIN { + return; + } + } + } + self.loc -= 1; + let loc = core.get_loc(); + if self.loc < loc { + core.set_loc(loc - 16); + } + } + pub fn right(&mut self, core: &mut Core, max_bytes: u64) { + match self.mode { + EditorMode::Hex { first_char: true } => { + self.mode = EditorMode::Hex { first_char: false }; + return; + } + EditorMode::Hex { first_char: false } => { + if self.loc == u64::MAX { + return; + } + self.mode = EditorMode::Hex { first_char: true }; + } + EditorMode::Ascii => { + if self.loc == u64::MAX { + return; + } + } + } + self.loc += 1; + let loc = core.get_loc(); + if self.loc >= loc + max_bytes { + core.set_loc(loc + 16); + } + } + pub fn down(&mut self, core: &mut Core, max_bytes: u64) { + if self.loc.wrapping_add(16) < self.loc { + return; + } + self.loc += 16; + let loc = core.get_loc(); + if self.loc >= loc + max_bytes { + core.set_loc(loc + 16); + } + } + pub fn up(&mut self, core: &mut Core) { + if self.loc.wrapping_sub(16) > self.loc { + return; + } + self.loc -= 16; + let loc = core.get_loc(); + if self.loc < loc { + core.set_loc(loc - 16); + } + } + pub fn write_char_ascii(&self, core: &mut Core, c: char) { + let byte = [c as u8]; + match core.mode { + AddrMode::Phy => drop(core.io.pwrite(self.loc, &byte)), + AddrMode::Vir => drop(core.io.vwrite(self.loc, &byte)), + }; + } + fn char_to_hex(c: char) -> Option { + match c { + '0' => Some(0x0), + '1' => Some(0x1), + '2' => Some(0x2), + '3' => Some(0x3), + '4' => Some(0x4), + '5' => Some(0x5), + '6' => Some(0x6), + '7' => Some(0x7), + '8' => Some(0x8), + '9' => Some(0x9), + 'a' => Some(0xa), + 'b' => Some(0xb), + 'c' => Some(0xc), + 'd' => Some(0xd), + 'e' => Some(0xe), + 'f' => Some(0xf), + 'A' => Some(0xA), + 'B' => Some(0xB), + 'C' => Some(0xC), + 'D' => Some(0xD), + 'E' => Some(0xE), + 'F' => Some(0xF), + _ => None, + } + } + pub fn write_char_hex(&self, core: &mut Core, c: char, mask: u8, shift: u8) { + let Some(c) = Self::char_to_hex(c) else { + return; + }; + let mut byte = [0]; + match core.mode { + AddrMode::Phy => { + let _ = core.io.pread(self.loc, &mut byte); + byte[0] = (byte[0] & mask) | (c << shift); + let _ = core.io.pwrite(self.loc, &byte); + } + AddrMode::Vir => { + let _ = core.io.vread(self.loc, &mut byte); + byte[0] = (byte[0] & mask) | (c << shift); + let _ = core.io.vwrite(self.loc, &byte); + } + }; + } + pub fn write_char(&self, core: &mut Core, c: char) { + match self.mode { + EditorMode::Hex { first_char: true } => self.write_char_hex(core, c, 0xf, 4), + EditorMode::Hex { first_char: false } => self.write_char_hex(core, c, 0xf0, 0), + EditorMode::Ascii => self.write_char_ascii(core, c), + } + } +} diff --git a/visual/src/visual_hex/events.rs b/visual/src/visual_hex/events.rs new file mode 100644 index 0000000..f502233 --- /dev/null +++ b/visual/src/visual_hex/events.rs @@ -0,0 +1,63 @@ +use rair_core::Core; +use rair_eval::rair_eval; +use ratatui::crossterm::event::{KeyCode, KeyEvent}; + +use super::{editor::Editor, mode::Mode, NextAction, VisualHex}; + +impl VisualHex { + fn handle_input_view(&mut self, input: KeyEvent, core: &mut Core) -> NextAction { + match input.code { + KeyCode::Down => core.set_loc(core.get_loc().saturating_add(16)), + KeyCode::Up => core.set_loc(core.get_loc().saturating_sub(16)), + KeyCode::Right => core.set_loc(core.get_loc().saturating_add(1)), + KeyCode::Left => core.set_loc(core.get_loc().saturating_sub(1)), + KeyCode::Char(':') => self.mode = Mode::new_command(), + KeyCode::Char('e') => self.mode = Mode::Edit(Editor::new(core)), + KeyCode::Char('q') => { + return NextAction::Break; + } + _ => (), + } + NextAction::Continue + } + + pub(super) fn handle_input(&mut self, input: KeyEvent, core: &mut Core) -> NextAction { + match &mut self.mode { + super::Mode::View => return self.handle_input_view(input, core), + + super::Mode::Edit(editor) => match input.code { + KeyCode::Esc => self.mode = Mode::View, + KeyCode::Down => editor.down(core, self.rendered_bytes), + KeyCode::Up => editor.up(core), + KeyCode::Right => editor.right(core, self.rendered_bytes), + KeyCode::Left => editor.left(core), + KeyCode::Tab => editor.switch_mode(), + KeyCode::Char(c) => { + editor.write_char(core, c); + editor.right(core, self.rendered_bytes); + } + _ => (), + }, + super::Mode::Command(cmd) => match input.code { + KeyCode::Esc => self.mode = Mode::View, + KeyCode::Left => cmd.move_cursor_left(), + KeyCode::Right => cmd.move_cursor_right(), + KeyCode::Backspace => cmd.delete_char(), + KeyCode::Delete => { + cmd.move_cursor_right(); + cmd.delete_char(); + } + KeyCode::Char(c) => { + cmd.enter_char(c); + } + KeyCode::Enter => { + let cmd = cmd.get_str(); + rair_eval(core, cmd); + self.mode = Mode::View; + } + _ => (), + }, + } + NextAction::Continue + } +} diff --git a/visual/src/visual_hex/help.rs b/visual/src/visual_hex/help.rs new file mode 100644 index 0000000..03fe052 --- /dev/null +++ b/visual/src/visual_hex/help.rs @@ -0,0 +1,53 @@ +use super::{Mode, VisualHex}; + +use ratatui::{ + prelude::*, + widgets::{Paragraph, Wrap}, +}; + +impl VisualHex { + fn view_help<'a>(&self) -> Line<'a> { + Line::from(vec![ + Span::styled("View", Style::default().bold()), + Span::from(" mode: Arrow keys nagivate. Pressing <"), + Span::styled("e", Style::default().bold()), + Span::from("> enters edit mode, <"), + Span::styled(":", Style::default().bold()), + Span::from("> enters command mode, <"), + Span::styled("q", Style::default().bold()), + Span::from("> quits visual mode. Everything else is ignored."), + ]) + } + fn command_help<'a>(&self) -> Line<'a> { + Line::from(vec![ + Span::styled("Command", Style::default().bold()), + Span::from(" mode: Use the keyboard to type commands. Pressing <"), + Span::styled("Enter", Style::default().bold()), + Span::from("> runs the command and return to view mode, <"), + Span::styled("ESC", Style::default().bold()), + Span::from("> returns to view mode without running the command."), + ]) + } + fn edit_help<'a>(&self) -> Line<'a> { + Line::from(vec![ + Span::styled("Edit", Style::default().bold()), + Span::from(" mode: Arrow keys nagivate. Pressing <"), + Span::styled("Tab", Style::default().bold()), + Span::from("> switches between ascii and hex edit, <"), + Span::styled("ESC", Style::default().bold()), + Span::from("> returns to view mode. Use the keyboard to edit"), + ]) + } + fn help<'a>(&self) -> Line<'a> { + match self.mode { + Mode::View => self.view_help(), + Mode::Edit(_) => self.edit_help(), + Mode::Command(_) => self.command_help(), + } + } + /// display help based on mode + pub(super) fn render_help(&self, f: &mut Frame, chunk: Rect) { + let p = Paragraph::new(self.help()).wrap(Wrap { trim: true }); + f.render_widget(p, chunk); + } +} diff --git a/visual/src/visual_hex/hex.rs b/visual/src/visual_hex/hex.rs new file mode 100644 index 0000000..775831f --- /dev/null +++ b/visual/src/visual_hex/hex.rs @@ -0,0 +1,57 @@ +use super::{mode::Mode, VisualHex}; +use rair_core::Core; +use ratatui::prelude::*; +use std::collections::BTreeMap; + +impl VisualHex { + fn hex_banner<'a>(&self, core: &Core) -> Line<'a> { + let env = core.env.read(); + let color = env.get_str("printHex.headerColor").unwrap(); + let (r, g, b) = env.get_color(color).unwrap(); + Line::from(Span::styled( + " 0 1 2 3 4 5 6 7 8 9 A B C D E F", + Style::default().fg(Color::Rgb(r, g, b)), + )) + } + + fn hex_line<'a>(&self, loc: u64, data: &BTreeMap, gap: &str) -> Line<'a> { + let mut spans = Vec::with_capacity(16); + for j in 0..16 { + let hex = if let Some(c) = data.get(&(j + loc)) { + format!("{:02x}", c) + } else { + format!("{}{}", gap, gap) + }; + if let Mode::Edit(editor) = &self.mode { + spans.extend_from_slice(&editor.span_hex(hex, loc + j)); + } else { + spans.push(Span::from(hex)); + } + if j % 2 == 1 { + spans.push(Span::from(" ")); + } + } + Line::default().spans(spans) + } + pub(super) fn render_hex( + &self, + f: &mut Frame, + chunk: Rect, + core: &Core, + data: &BTreeMap, + ) { + let mut lines: Vec> = Vec::with_capacity(chunk.height as usize + 1); + lines.push(self.hex_banner(core)); + + let env = core.env.read(); + let gap: &str = env.get_str("printHex.gapReplace").unwrap(); + + let loc = core.get_loc(); + + for i in 0..chunk.height as u64 { + lines.push(self.hex_line(loc + i * 16, data, gap)); + } + let txt = Text::from(lines); + f.render_widget(txt, chunk); + } +} diff --git a/visual/src/visual_hex/mod.rs b/visual/src/visual_hex/mod.rs new file mode 100644 index 0000000..d9c876e --- /dev/null +++ b/visual/src/visual_hex/mod.rs @@ -0,0 +1,77 @@ +mod addresses; +mod ascii; +mod command; +mod editor; +mod events; +mod help; +mod hex; +mod mode; +mod render; + +use mode::Mode; +use rair_core::{error_msg, expect, help, Cmd, Core}; +use ratatui::{ + crossterm::event::{self, Event}, + prelude::*, + Terminal, +}; +use std::io; + +enum NextAction { + Continue, + Break, +} + +#[derive(Default)] +pub struct VisualHex { + internal_error: Option, + mode: Mode, + rendered_bytes: u64, +} + +impl VisualHex { + fn render_event_loop( + &mut self, + mut term: Terminal, + core: &mut Core, + ) -> io::Result<()> { + loop { + term.draw(|f| self.render(f, core))?; + if let Some(e) = self.internal_error.take() { + return Err(io::Error::other(e)); + } + if let Event::Key(key) = event::read()? { + if let NextAction::Break = self.handle_input(key, core) { + break; + }; + } + } + Ok(()) + } +} +impl Cmd for VisualHex { + fn run(&mut self, core: &mut Core, args: &[String]) { + if !args.is_empty() { + expect(core, args.len() as u64, 1); + return; + } + let terminal = ratatui::init(); + let loc = core.get_loc(); + if let Err(e) = self.render_event_loop(terminal, core) { + core.set_loc(loc); + ratatui::restore(); + error_msg(core, "Error in Visual mode event loop", &e.to_string()); + return; + } + core.set_loc(loc); + ratatui::restore(); + } + fn help(&self, core: &mut Core) { + help( + core, + "visualHex", + "vw", + vec![("", "Open hex editor in visual mode.")], + ); + } +} diff --git a/visual/src/visual_hex/mode.rs b/visual/src/visual_hex/mode.rs new file mode 100644 index 0000000..67f4f82 --- /dev/null +++ b/visual/src/visual_hex/mode.rs @@ -0,0 +1,15 @@ +use super::{command::Command, editor::Editor}; + +#[derive(Default)] +pub enum Mode { + #[default] + View, + Edit(Editor), + Command(Command), +} + +impl Mode { + pub fn new_command() -> Self { + Mode::Command(Default::default()) + } +} diff --git a/visual/src/visual_hex/render.rs b/visual/src/visual_hex/render.rs new file mode 100644 index 0000000..c0f3944 --- /dev/null +++ b/visual/src/visual_hex/render.rs @@ -0,0 +1,74 @@ +use super::{mode::Mode, VisualHex}; +use rair_core::{AddrMode, Core}; +use rair_io::IoError; +use ratatui::{prelude::*, Frame}; +use std::collections::BTreeMap; + +impl VisualHex { + /// returns total number of bytes to displays + pub(super) fn calculate_bytes_to_render(&mut self, chunk: &Rect) -> u64 { + // we multiply the height by 16 because each line will + // have 16 bytes in total + let height = chunk.height as u64 - 1; + self.rendered_bytes = height * 16; + self.rendered_bytes + } + + fn read_data(loc: u64, size: u64, core: &mut Core) -> Result, IoError> { + match core.mode { + AddrMode::Phy => core.io.pread_sparce(loc, size), + AddrMode::Vir => core.io.vread_sparce(loc, size), + } + } + + fn render_body(&mut self, f: &mut Frame, chunk: Rect, core: &mut Core) { + let max_addr = self.max_address_str_width(core, &chunk); + let chunks = Layout::horizontal([ + Constraint::Length(max_addr), // address + Constraint::Length(39), // hex + Constraint::Length(16), // ascii + ]) + .spacing(2) + .split(chunk); + self.render_addresses(f, chunks[0], core); + + let size = self.calculate_bytes_to_render(&chunk); + let loc = core.get_loc(); + + let data = match Self::read_data(loc, size, core) { + Ok(d) => d, + Err(e) => { + self.internal_error = Some(format!("Read Failed, {}", &e.to_string())); + return; + } + }; + self.render_hex(f, chunks[1], core, &data); + self.render_ascii(f, chunks[2], core, &data); + } + + /// render 1 frame, on error this function populates `self.internal_error`I + pub(super) fn render(&mut self, f: &mut Frame, core: &mut Core) { + let area = f.area(); + if area.height < 5 || area.width < 70 { + self.internal_error = Some("Terminal must be at least 5x70".to_owned()); + } + let layout: &[_] = if let Mode::Command { .. } = self.mode { + &[ + Constraint::Max(2), // help + Constraint::Min(2), // body + Constraint::Max(1), // commands + ] + } else { + &[ + Constraint::Max(2), // help + Constraint::Min(2), // body + ] + }; + let chunks = Layout::vertical(layout).split(area); + self.render_help(f, chunks[0]); + self.render_body(f, chunks[1], core); + if let Mode::Command(cmd) = &self.mode { + cmd.render(f, chunks[2]); + } + } +}