diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fedd09..47480df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,14 @@ - `t` now truncates the file (it was `T` before). - `T` reverse truncates the file (deletes from offset 0 to current offset). - Both commands above need confirmation from the user as they can't be undone. - - Selection mode: + - Selection mode: - Bug fix: selection no longer disappears when selected range is bigger than page size. + - Users can now mark colored blocks with `Alt-m`. Press multiple times to change colors. `[` and `]` keys navigate to marked block boundaries. ## dz6 v0.6.0 - Hex view: - - `Ctrl+f` goes down one page (same as `PgDown`). + - `Ctrl+f` goes down one page (same as `PgDown`). - `Ctrl+b` goes up one page (same as `PgUp`). - `~` changes case when applicable (available in Normal, Replace, and Select modes). - `Ctrl+a`, `Ctrl+x`, `n`, and `z` work without putting the editor in Replace mode. @@ -57,25 +58,25 @@ - Basic support for multibyte selection. Press `v` (like visual mode in vim), then select a byte range. Then, `y` to copy bytes to clipboard, `z` to fill them with zeroes, or `n` to fill them with x86 NOPs. - Support for initialization file at `$HOME/.gdbinit`. Commands added to this file will be executed at startup (one per line). (https://github.com/mentebinaria/dz6/issues/12) - New `:` commands: - - `set db` turn on database loading/saving (default) - - `set nodb` turn off the above + - `set db` turn on database loading/saving (default) + - `set nodb` turn off the above ## dz6 v0.4.0 - Status bar now shows "COMMAND" when you press `:`. - New `:` commands: - - `cmt ` (programatic alternative to `;`) - - `set byteline ` sets the number of bytes per line in the hex dump - - `set ctrlchar `sets the character shown for ASCII non-graphical byte values - - `set dimzero` dim nullbytes - - `set dimctrl` dim all control characters - - `set nodim` turn off dimming - - `set theme` changes the theme - - `w` write changes to file - - `wq` or `x` write changes to file and quit + - `cmt ` (programatic alternative to `;`) + - `set byteline ` sets the number of bytes per line in the hex dump + - `set ctrlchar `sets the character shown for ASCII non-graphical byte values + - `set dimzero` dim nullbytes + - `set dimctrl` dim all control characters + - `set nodim` turn off dimming + - `set theme` changes the theme + - `w` write changes to file + - `wq` or `x` write changes to file and quit - In-memory buffer when patching bytes. Nothing is written to the file until you use some of the writing commands (`w`, `wq` or `x`), but truncating is an exception (`T` in replace mode). - - Light theme. -- dz6 beeps if you try to enter replace mode when editing a read-only file. +- Light theme. +- dz6 beeps if you try to enter replace mode when editing a read-only file. ## dz6 v0.3.1 diff --git a/Cargo.lock b/Cargo.lock index a7cf670..f850eba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,6 +180,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "clap" version = "4.6.1" @@ -267,6 +278,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crossterm" version = "0.29.0" @@ -448,6 +468,7 @@ dependencies = [ "hex", "memchr", "mmap-io", + "rand 0.10.1", "ratatui", "regex", "serde", @@ -559,6 +580,30 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -611,6 +656,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -637,9 +683,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -672,7 +718,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -722,10 +768,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -1143,7 +1191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand", + "rand 0.8.6", ] [[package]] @@ -1168,6 +1216,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "portable-atomic" version = "1.13.1" @@ -1226,7 +1280,18 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -1235,6 +1300,12 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "ratatui" version = "0.30.0" @@ -1474,7 +1545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -1517,9 +1588,15 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -1862,9 +1939,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", @@ -1875,9 +1952,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1885,9 +1962,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", @@ -1898,9 +1975,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] diff --git a/Cargo.toml b/Cargo.toml index 053fb68..fcab473 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ evalexpr = "13.1.*" hex = "0.4.3" memchr = "2.8.0" mmap-io = "0.9.4" +rand = "0.10.1" ratatui = "0.30.0" regex = "1.11.2" serde = { version = "1.0.228", features = ["derive"] } diff --git a/src/database.rs b/src/database.rs index 7716f4a..80e593c 100644 --- a/src/database.rs +++ b/src/database.rs @@ -14,7 +14,10 @@ impl App { let target_db: PathBuf = target_dir.join(&cwd_db); // if there's nothing to be saved, delete any existing db files and return - if self.hex_view.bookmarks.is_empty() && self.hex_view.comment_name_list.is_empty() { + if self.hex_view.bookmarks.is_empty() + && self.hex_view.comment_name_list.is_empty() + && self.hex_view.blocks.is_empty() + { let _ = fs::remove_file(target_db); let _ = fs::remove_file(cwd_db); return Ok(()); diff --git a/src/hex/blocks.rs b/src/hex/blocks.rs new file mode 100644 index 0000000..8b11712 --- /dev/null +++ b/src/hex/blocks.rs @@ -0,0 +1,32 @@ +use rand::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd)] +pub struct ColoredBlock { + pub start: usize, + pub end: usize, + pub bg_color: u32, + pub fg_color: u32, +} + +fn get_random_color() -> u32 { + let mut rng = rand::rng(); + + rng.random::() +} + +impl ColoredBlock { + pub fn new(start: usize, end: usize) -> Self { + ColoredBlock { + start, + end, + bg_color: get_random_color(), + fg_color: get_random_color(), + } + } + + pub fn set_random_color(&mut self) { + self.bg_color = get_random_color(); + self.fg_color = get_random_color(); + } +} diff --git a/src/hex/draw.rs b/src/hex/draw.rs index a784842..006174a 100644 --- a/src/hex/draw.rs +++ b/src/hex/draw.rs @@ -1,6 +1,7 @@ use ratatui::{ Frame, layout::{Constraint, Rect}, + style::{Color, Style}, widgets::{Cell, Clear, Row, Table}, }; @@ -48,6 +49,7 @@ pub fn draw_hex_contents(app: &mut App, frame: &mut Frame, area: Rect) { let mut byte_row: Vec = Vec::with_capacity(app.reader.page_current_size); let mut cell_hl_style = app.config.theme.highlight; let mut byte_style = app.config.theme.main; + let mut col_len = 2u16; let buffer = app.file_info.get_buffer(); for (i, byte) in buffer @@ -65,8 +67,6 @@ pub fn draw_hex_contents(app: &mut App, frame: &mut Frame, area: Rect) { byte_style = if app.state == UIState::HexSelection && app.hex_view.selection.contains(offset) { app.config.theme.highlight - } else if app.hex_view.highlights.contains(byte) { - app.config.theme.byte_highlight } else if *byte == b'\0' && app.config.dim_zeroes { app.config.theme.dimmed } else if !byte.is_ascii_graphic() && app.config.dim_control_chars { @@ -81,6 +81,15 @@ pub fn draw_hex_contents(app: &mut App, frame: &mut Frame, area: Rect) { cell_hl_style = app.config.theme.highlight; } + for b in &app.hex_view.blocks { + if offset >= b.start && offset <= b.end { + byte_style = Style::new() + .bg(Color::from_u32(b.bg_color)) + .fg(Color::from_u32(b.fg_color)); + col_len = 3; + } + } + if app.hex_view.changed_bytes.contains_key(&offset) { // typed chars in content instead of original ones byte_content = app.hex_view.changed_bytes[&offset].clone(); @@ -95,6 +104,11 @@ pub fn draw_hex_contents(app: &mut App, frame: &mut Frame, area: Rect) { } } + // byte highlight + if app.hex_view.highlights.contains(byte) { + byte_style = app.config.theme.byte_highlight; + } + // TODO: column size (2) keep the separator char from being shown :( // if i > 0 && i % 4 == 0 { // content.push(app.config.hex_mode_dword_separator); @@ -123,11 +137,9 @@ pub fn draw_hex_contents(app: &mut App, frame: &mut Frame, area: Rect) { .select_column(Some(app.hex_view.cursor.x)); // small trick to make selection looks better - let col_len = if app.state == UIState::HexSelection { - 3 - } else { - 2 - }; + if app.state == UIState::HexSelection { + col_len = 3; + } let constraints = vec![Constraint::Length(col_len); app.config.hex_mode_bytes_per_line]; diff --git a/src/hex/events.rs b/src/hex/events.rs index 3e9fdf3..a364650 100644 --- a/src/hex/events.rs +++ b/src/hex/events.rs @@ -21,7 +21,7 @@ pub fn hex_mode_events(app: &mut App, key: KeyEvent) -> Result { } ofs = ofs.saturating_add_signed(delta); // this is needed because it can start at 0, - // but it cannot be zero afterward + // but it cannot be zero afterwards. // without it, `O` doesn't work at offset 0 if ofs == 0 { app.goto(0); @@ -336,6 +336,41 @@ pub fn hex_mode_events(app: &mut App, key: KeyEvent) -> Result { crate::beep!(); // beep if there's nothing to undo } } + // set a new random color for a colored block + KeyCode::Char('m') => { + if key.modifiers.contains(KeyModifiers::ALT) { + for b in &mut app.hex_view.blocks { + if app.hex_view.offset >= b.start && app.hex_view.offset <= b.end { + b.set_random_color(); + break; + } + } + } + } + // go to the nearest previous block boundary + KeyCode::Char('[') => { + for b in app.hex_view.blocks.iter().rev() { + if b.end < app.hex_view.offset { + app.goto(b.end); + break; + } else if b.start < app.hex_view.offset { + app.goto(b.start); + break; + } + } + } + // go to the nearest next block boundary + KeyCode::Char(']') => { + for b in &app.hex_view.blocks { + if b.start > app.hex_view.offset { + app.goto(b.start); + break; + } else if b.end > app.hex_view.offset { + app.goto(b.end); + break; + } + } + } _ => {} } Ok(false) diff --git a/src/hex/hex_view.rs b/src/hex/hex_view.rs index fda574f..edd02df 100644 --- a/src/hex/hex_view.rs +++ b/src/hex/hex_view.rs @@ -4,7 +4,7 @@ use ratatui::widgets::{ListState, TableState}; use serde::{Deserialize, Serialize}; use tui_input::Input; -use crate::hex::comment::Comment; +use crate::hex::{blocks::ColoredBlock, comment::Comment}; // used in hex view struct to track the cursor position #[derive(Default, Debug)] @@ -13,10 +13,12 @@ pub struct Point { pub y: usize, } -#[derive(Default, Debug, Serialize, Deserialize)] +#[derive(Default, Serialize, Deserialize)] pub struct HexView { #[serde(skip)] pub ascii_state: TableState, + // blocks are ByteBlock structs -- ranges with different colors + pub blocks: Vec, pub bookmarks: Vec, #[serde(skip)] pub changed_bytes: HashMap, diff --git a/src/hex/mod.rs b/src/hex/mod.rs index 5750988..9f62a03 100644 --- a/src/hex/mod.rs +++ b/src/hex/mod.rs @@ -1,3 +1,4 @@ +pub mod blocks; pub mod comment; pub mod draw; pub mod edit; diff --git a/src/hex/selection.rs b/src/hex/selection.rs index 7ff6a5a..31f025d 100644 --- a/src/hex/selection.rs +++ b/src/hex/selection.rs @@ -1,8 +1,10 @@ +use crossterm::event::KeyModifiers; use ratatui::crossterm::event::{KeyCode, KeyEvent}; use std::io::Result; use crate::app::App; use crate::editor::UIState; +use crate::hex::blocks::ColoredBlock; #[derive(Debug, PartialEq, Clone, Copy)] pub enum Direction { @@ -197,6 +199,27 @@ pub fn select_events(app: &mut App, key: KeyEvent) -> Result { app.state = UIState::Normal; app.hex_view.selection.clear(); } + // set a random color for an existing block or create a new one + KeyCode::Char('m') => { + if key.modifiers.contains(KeyModifiers::ALT) { + for b in &mut app.hex_view.blocks { + if app.hex_view.offset >= b.start && app.hex_view.offset <= b.end { + b.set_random_color(); + app.state = UIState::Normal; + return Ok(true); + } + } + app.hex_view.blocks.push(ColoredBlock::new( + app.hex_view.selection.start, + app.hex_view.selection.end, + )); + + // sorting is needed to [] and {} keys work correctly + app.hex_view.blocks.sort_by_key(|k| k.start); + + app.state = UIState::Normal; + } + } _ => {} }