highlight spaces between bytes when selected

This commit is contained in:
alice pellerin
2026-03-16 23:26:06 -05:00
parent 7a18c6cbfe
commit ce19c3eb9c
5 changed files with 106 additions and 55 deletions
+3 -3
View File
@@ -98,13 +98,13 @@ impl App {
} }
} }
Event::Key(key_event) if key_event.code == KeyCode::Char('w') => { Event::Key(key_event) if key_event.code == KeyCode::Char('w') => {
self.cursor.to_next_word(self.contents.len() - 1); self.cursor.move_to_next_word(self.contents.len() - 1);
} }
Event::Key(key_event) if key_event.code == KeyCode::Char('e') => { Event::Key(key_event) if key_event.code == KeyCode::Char('e') => {
self.cursor.to_next_end(self.contents.len() - 1); self.cursor.move_to_next_end(self.contents.len() - 1);
} }
Event::Key(key_event) if key_event.code == KeyCode::Char('b') => { Event::Key(key_event) if key_event.code == KeyCode::Char('b') => {
self.cursor.to_previous_beginning(); self.cursor.move_to_previous_beginning();
} }
Event::Key(key_event) if key_event.code == KeyCode::Char(';') => { Event::Key(key_event) if key_event.code == KeyCode::Char(';') => {
self.cursor.collapse(); self.cursor.collapse();
+38 -5
View File
@@ -53,11 +53,11 @@ mod address {
} }
mod hex { mod hex {
use std::{borrow::Cow, mem}; use std::{borrow::Cow, iter, mem};
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_LINE, BYTES_PER_CHUNK, app::App, cardinality::HasCardinality, empty_span::empty_span, cursor::InCursor}; use crate::{BYTES_PER_CHUNK, BYTES_PER_LINE, CHUNKS_PER_LINE, app::App, cardinality::HasCardinality, cursor::InCursor, empty_span::empty_span, select_grey::SelectGrey};
impl App { impl App {
pub fn render_chunks( pub fn render_chunks(
@@ -75,7 +75,13 @@ mod hex {
.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))
.intersperse(vec![" ".into()]) .interleave(
(address..)
.step_by(BYTES_PER_CHUNK)
.take(CHUNKS_PER_LINE)
.skip(1)
.map(|address| vec![self.render_large_space_before(address)])
)
.flatten() .flatten()
} }
@@ -90,7 +96,12 @@ mod hex {
.copied() .copied()
.zip(address..) .zip(address..)
.map(|(byte, address)| self.render_byte_at(address, byte)) .map(|(byte, address)| self.render_byte_at(address, byte))
.intersperse(" ".into()) // TODO: highlight if selected .interleave(
(address..)
.take(BYTES_PER_CHUNK)
.skip(1)
.map(|address| self.render_space_before(address))
)
.collect() .collect()
} }
@@ -105,10 +116,32 @@ mod hex {
match self.cursor.contains(address) { match self.cursor.contains(address) {
Some(InCursor::Head) => span.bg(Color::Gray), Some(InCursor::Head) => span.bg(Color::Gray),
Some(InCursor::Rest) => span.bg(Color::Indexed(242)), Some(InCursor::Rest) => span.bg(Color::select_grey()),
None => span, None => span,
} }
} }
fn render_large_space_before(&self, address: usize) -> Span<'static> {
if self.cursor.contains_space_before(address) {
Span {
style: Style::new().bg(Color::select_grey()),
content: " ".into()
}
} else {
" ".into()
}
}
fn render_space_before(&self, address: usize) -> Span<'static> {
if self.cursor.contains_space_before(address) {
Span {
style: Style::new().bg(Color::select_grey()),
content: " ".into()
}
} else {
" ".into()
}
}
} }
const fn create_byte_lookup_table() -> [Span<'static>; u8::CARDINALITY] { const fn create_byte_lookup_table() -> [Span<'static>; u8::CARDINALITY] {
+52 -47
View File
@@ -22,22 +22,27 @@ impl Cursor {
} }
} }
pub const fn contains_space_before(&self, index: usize) -> bool {
(self.head < index && index <= self.tail) ||
(self.tail < index && index <= self.head)
}
pub const fn collapse(&mut self) { pub const fn collapse(&mut self) {
self.tail = self.head; self.tail = self.head;
} }
pub fn to_next_word(&mut self, max: usize) { pub fn move_to_next_word(&mut self, max: usize) {
if self.head == max { return } if self.head == max { return }
if self.head % 4 == 0 { // at the beginning of a word if self.head.is_multiple_of(4) { // at the beginning of a word
self.head = (self.head + 4).min(max); self.head = (self.head + 4).min(max);
} { } else {
self.head = self.head.next_multiple_of(4).min(max); self.head = self.head.next_multiple_of(4).min(max);
} }
self.collapse(); self.collapse();
} }
pub fn to_next_end(&mut self, max: usize) { pub fn move_to_next_end(&mut self, max: usize) {
if self.head == max { return } if self.head == max { return }
self.collapse(); self.collapse();
@@ -49,11 +54,11 @@ impl Cursor {
} }
} }
pub const fn to_previous_beginning(&mut self) { pub const fn move_to_previous_beginning(&mut self) {
if self.head == 0 { return } if self.head == 0 { return }
self.collapse(); self.collapse();
if self.head % 4 == 0 { // at the beginning of a word if self.head.is_multiple_of(4) { // at the beginning of a word
self.tail = self.head - 1; self.tail = self.head - 1;
self.head -= 4; self.head -= 4;
} else { } else {
@@ -76,57 +81,57 @@ mod tests {
fn next_word() { fn next_word() {
// [a]bcd efgh -> abcd [e]fgh // [a]bcd efgh -> abcd [e]fgh
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.to_next_word(99); cursor.move_to_next_word(99);
assert_eq!(cursor, Cursor::at(4)); assert_eq!(cursor, Cursor::at(4));
// a[b]cd efgh -> abcd [e]fgh // a[b]cd efgh -> abcd [e]fgh
let mut cursor = Cursor::at(1); let mut cursor = Cursor::at(1);
cursor.to_next_word(99); cursor.move_to_next_word(99);
assert_eq!(cursor, Cursor::at(4)); assert_eq!(cursor, Cursor::at(4));
// ab[c]d efgh -> abcd [e]fgh // ab[c]d efgh -> abcd [e]fgh
let mut cursor = Cursor::at(2); let mut cursor = Cursor::at(2);
cursor.to_next_word(99); cursor.move_to_next_word(99);
assert_eq!(cursor, Cursor::at(4)); assert_eq!(cursor, Cursor::at(4));
// abc[d] efgh -> abcd [e]fgh // abc[d] efgh -> abcd [e]fgh
let mut cursor = Cursor::at(3); let mut cursor = Cursor::at(3);
cursor.to_next_word(99); cursor.move_to_next_word(99);
assert_eq!(cursor, Cursor::at(4)); assert_eq!(cursor, Cursor::at(4));
// [a]bcd -> abc[d] // [a]bcd -> abc[d]
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.to_next_word(3); cursor.move_to_next_word(3);
assert_eq!(cursor, Cursor::at(3)); assert_eq!(cursor, Cursor::at(3));
// [a]bc -> ab[c] // [a]bc -> ab[c]
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.to_next_word(2); cursor.move_to_next_word(2);
assert_eq!(cursor, Cursor::at(2)); assert_eq!(cursor, Cursor::at(2));
// [a]b -> a[b] // [a]b -> a[b]
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.to_next_word(1); cursor.move_to_next_word(1);
assert_eq!(cursor, Cursor::at(1)); assert_eq!(cursor, Cursor::at(1));
// [a] -> [a] // [a] -> [a]
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.to_next_word(0); cursor.move_to_next_word(0);
assert_eq!(cursor, Cursor::at(0)); assert_eq!(cursor, Cursor::at(0));
// ab[c]d -> abc[d] // ab[c]d -> abc[d]
let mut cursor = Cursor::at(2); let mut cursor = Cursor::at(2);
cursor.to_next_word(3); cursor.move_to_next_word(3);
assert_eq!(cursor, Cursor::at(3)); assert_eq!(cursor, Cursor::at(3));
// abc[d] -> abc[d] // abc[d] -> abc[d]
let mut cursor = Cursor::at(3); let mut cursor = Cursor::at(3);
cursor.to_next_word(3); cursor.move_to_next_word(3);
assert_eq!(cursor, Cursor::at(3)); assert_eq!(cursor, Cursor::at(3));
// ab[c[d] -> ab[c[d] // ab[c[d] -> ab[c[d]
let mut cursor = Cursor { tail: 2, head: 3 }; let mut cursor = Cursor { tail: 2, head: 3 };
cursor.to_next_word(3); cursor.move_to_next_word(3);
assert_eq!(cursor, Cursor { tail: 2, head: 3 }); assert_eq!(cursor, Cursor { tail: 2, head: 3 });
} }
@@ -134,97 +139,97 @@ mod tests {
fn next_end() { fn next_end() {
// [a]bcd -> [abcd] // [a]bcd -> [abcd]
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.to_next_end(99); cursor.move_to_next_end(99);
assert_eq!(cursor, Cursor { tail: 0, head: 3 }); assert_eq!(cursor, Cursor { tail: 0, head: 3 });
// a[b]cd -> [abcd] // a[b]cd -> [abcd]
let mut cursor = Cursor::at(1); let mut cursor = Cursor::at(1);
cursor.to_next_end(99); cursor.move_to_next_end(99);
assert_eq!(cursor, Cursor { tail: 1, head: 3 }); assert_eq!(cursor, Cursor { tail: 1, head: 3 });
// ab[c]d -> [abcd] // ab[c]d -> [abcd]
let mut cursor = Cursor::at(2); let mut cursor = Cursor::at(2);
cursor.to_next_end(99); cursor.move_to_next_end(99);
assert_eq!(cursor, Cursor { tail: 2, head: 3 }); assert_eq!(cursor, Cursor { tail: 2, head: 3 });
// abc[d] efgh -> abcd [efgh] // abc[d] efgh -> abcd [efgh]
let mut cursor = Cursor::at(3); let mut cursor = Cursor::at(3);
cursor.to_next_end(99); cursor.move_to_next_end(99);
assert_eq!(cursor, Cursor { tail: 4, head: 7 }); assert_eq!(cursor, Cursor { tail: 4, head: 7 });
// abcd [e]fgh -> abcd [efgh] // abcd [e]fgh -> abcd [efgh]
let mut cursor = Cursor::at(4); let mut cursor = Cursor::at(4);
cursor.to_next_end(99); cursor.move_to_next_end(99);
assert_eq!(cursor, Cursor { tail: 4, head: 7 }); assert_eq!(cursor, Cursor { tail: 4, head: 7 });
// abcd e[f]gh -> abcd e[fgh] // abcd e[f]gh -> abcd e[fgh]
let mut cursor = Cursor::at(5); let mut cursor = Cursor::at(5);
cursor.to_next_end(99); cursor.move_to_next_end(99);
assert_eq!(cursor, Cursor { tail: 5, head: 7 }); assert_eq!(cursor, Cursor { tail: 5, head: 7 });
// abcd ef[g]h -> abcd ef[gh] // abcd ef[g]h -> abcd ef[gh]
let mut cursor = Cursor::at(6); let mut cursor = Cursor::at(6);
cursor.to_next_end(99); cursor.move_to_next_end(99);
assert_eq!(cursor, Cursor { tail: 6, head: 7 }); assert_eq!(cursor, Cursor { tail: 6, head: 7 });
// abcd efg[h] ijkl -> abcd efgh [ijkl] // abcd efg[h] ijkl -> abcd efgh [ijkl]
let mut cursor = Cursor::at(7); let mut cursor = Cursor::at(7);
cursor.to_next_end(99); cursor.move_to_next_end(99);
assert_eq!(cursor, Cursor { tail: 8, head: 11 }); assert_eq!(cursor, Cursor { tail: 8, head: 11 });
// abcd efg[h] -> abcd efg[h] // abcd efg[h] -> abcd efg[h]
let mut cursor = Cursor::at(7); let mut cursor = Cursor::at(7);
cursor.to_next_end(7); cursor.move_to_next_end(7);
assert_eq!(cursor, Cursor { tail: 7, head: 7 }); assert_eq!(cursor, Cursor { tail: 7, head: 7 });
// abcd e[fgh] -> abcd e[fgh] // abcd e[fgh] -> abcd e[fgh]
let mut cursor = Cursor { tail: 5, head: 7 }; let mut cursor = Cursor { tail: 5, head: 7 };
cursor.to_next_end(7); cursor.move_to_next_end(7);
assert_eq!(cursor, Cursor { tail: 5, head: 7 }); assert_eq!(cursor, Cursor { tail: 5, head: 7 });
// a[b]c -> a[bc] // a[b]c -> a[bc]
let mut cursor = Cursor::at(1); let mut cursor = Cursor::at(1);
cursor.to_next_end(2); cursor.move_to_next_end(2);
assert_eq!(cursor, Cursor { tail: 1, head: 2 }); assert_eq!(cursor, Cursor { tail: 1, head: 2 });
// a[bc] -> a[bc] // a[bc] -> a[bc]
let mut cursor = Cursor { tail: 1, head: 2}; let mut cursor = Cursor { tail: 1, head: 2};
cursor.to_next_end(2); cursor.move_to_next_end(2);
assert_eq!(cursor, Cursor { tail: 1, head: 2 }); assert_eq!(cursor, Cursor { tail: 1, head: 2 });
// a[b] -> a[b] // a[b] -> a[b]
let mut cursor = Cursor::at(1); let mut cursor = Cursor::at(1);
cursor.to_next_end(1); cursor.move_to_next_end(1);
assert_eq!(cursor, Cursor::at(1)); assert_eq!(cursor, Cursor::at(1));
// [a]b -> [ab] // [a]b -> [ab]
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.to_next_end(1); cursor.move_to_next_end(1);
assert_eq!(cursor, Cursor { tail: 0, head: 1 }); assert_eq!(cursor, Cursor { tail: 0, head: 1 });
// [ab] -> [ab] // [ab] -> [ab]
let mut cursor = Cursor { tail: 0, head: 1}; let mut cursor = Cursor { tail: 0, head: 1};
cursor.to_next_end(1); cursor.move_to_next_end(1);
assert_eq!(cursor, Cursor { tail: 0, head: 1 }); assert_eq!(cursor, Cursor { tail: 0, head: 1 });
// [a] -> [a] // [a] -> [a]
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.to_next_end(0); cursor.move_to_next_end(0);
assert_eq!(cursor, Cursor::at(0)); assert_eq!(cursor, Cursor::at(0));
// [a]bcd] -> [abc[d] // [a]bcd] -> [abc[d]
let mut cursor = Cursor { head: 0, tail: 3 }; let mut cursor = Cursor { head: 0, tail: 3 };
cursor.to_next_end(99); cursor.move_to_next_end(99);
assert_eq!(cursor, Cursor { tail: 0, head: 3 }); assert_eq!(cursor, Cursor { tail: 0, head: 3 });
// [a[b]cd -> a[bc[d] // [a[b]cd -> a[bc[d]
let mut cursor = Cursor { tail: 0, head: 1 }; let mut cursor = Cursor { tail: 0, head: 1 };
cursor.to_next_end(99); cursor.move_to_next_end(99);
assert_eq!(cursor, Cursor { tail: 1, head: 3 }); assert_eq!(cursor, Cursor { tail: 1, head: 3 });
// abc[d] ef -> abcd [ef] // abc[d] ef -> abcd [ef]
let mut cursor = Cursor::at(3); let mut cursor = Cursor::at(3);
cursor.to_next_end(5); cursor.move_to_next_end(5);
assert_eq!(cursor, Cursor { tail: 4, head: 5 }); assert_eq!(cursor, Cursor { tail: 4, head: 5 });
} }
@@ -232,57 +237,57 @@ mod tests {
fn previous_beginning() { fn previous_beginning() {
// abcd efgh [i]jkl -> abcd [efgh] ijkl // abcd efgh [i]jkl -> abcd [efgh] ijkl
let mut cursor = Cursor::at(8); let mut cursor = Cursor::at(8);
cursor.to_previous_beginning(); cursor.move_to_previous_beginning();
assert_eq!(cursor, Cursor { head: 4, tail: 7 }); assert_eq!(cursor, Cursor { head: 4, tail: 7 });
// abcd efg[h] -> abcd [efgh] // abcd efg[h] -> abcd [efgh]
let mut cursor = Cursor::at(7); let mut cursor = Cursor::at(7);
cursor.to_previous_beginning(); cursor.move_to_previous_beginning();
assert_eq!(cursor, Cursor { head: 4, tail: 7 }); assert_eq!(cursor, Cursor { head: 4, tail: 7 });
// abcd ef[g]h -> abcd [efg]h // abcd ef[g]h -> abcd [efg]h
let mut cursor = Cursor::at(6); let mut cursor = Cursor::at(6);
cursor.to_previous_beginning(); cursor.move_to_previous_beginning();
assert_eq!(cursor, Cursor { head: 4, tail: 6 }); assert_eq!(cursor, Cursor { head: 4, tail: 6 });
// abcd e[f]gh -> abcd [ef]gh // abcd e[f]gh -> abcd [ef]gh
let mut cursor = Cursor::at(5); let mut cursor = Cursor::at(5);
cursor.to_previous_beginning(); cursor.move_to_previous_beginning();
assert_eq!(cursor, Cursor { head: 4, tail: 5 }); assert_eq!(cursor, Cursor { head: 4, tail: 5 });
// abcd [e]fgh -> [abcd] efgh // abcd [e]fgh -> [abcd] efgh
let mut cursor = Cursor::at(4); let mut cursor = Cursor::at(4);
cursor.to_previous_beginning(); cursor.move_to_previous_beginning();
assert_eq!(cursor, Cursor { head: 0, tail: 3 }); assert_eq!(cursor, Cursor { head: 0, tail: 3 });
// abc[d] -> [abcd] // abc[d] -> [abcd]
let mut cursor = Cursor::at(3); let mut cursor = Cursor::at(3);
cursor.to_previous_beginning(); cursor.move_to_previous_beginning();
assert_eq!(cursor, Cursor { head: 0, tail: 3 }); assert_eq!(cursor, Cursor { head: 0, tail: 3 });
// ab[c]d -> [abc]d // ab[c]d -> [abc]d
let mut cursor = Cursor::at(2); let mut cursor = Cursor::at(2);
cursor.to_previous_beginning(); cursor.move_to_previous_beginning();
assert_eq!(cursor, Cursor { head: 0, tail: 2 }); assert_eq!(cursor, Cursor { head: 0, tail: 2 });
// a[b]cd -> [ab]cd // a[b]cd -> [ab]cd
let mut cursor = Cursor::at(1); let mut cursor = Cursor::at(1);
cursor.to_previous_beginning(); cursor.move_to_previous_beginning();
assert_eq!(cursor, Cursor { head: 0, tail: 1 }); assert_eq!(cursor, Cursor { head: 0, tail: 1 });
// [a]bcd -> [a]bcd // [a]bcd -> [a]bcd
let mut cursor = Cursor::at(0); let mut cursor = Cursor::at(0);
cursor.to_previous_beginning(); cursor.move_to_previous_beginning();
assert_eq!(cursor, Cursor { head: 0, tail: 0 }); assert_eq!(cursor, Cursor { head: 0, tail: 0 });
// [abc[d] -> [a]bcd] // [abc[d] -> [a]bcd]
let mut cursor = Cursor { tail: 0, head: 3 }; let mut cursor = Cursor { tail: 0, head: 3 };
cursor.to_previous_beginning(); cursor.move_to_previous_beginning();
assert_eq!(cursor, Cursor { head: 0, tail: 3 }); assert_eq!(cursor, Cursor { head: 0, tail: 3 });
// ab[c]d] -> [a]bc]d // ab[c]d] -> [a]bc]d
let mut cursor = Cursor { head: 2, tail: 3 }; let mut cursor = Cursor { head: 2, tail: 3 };
cursor.to_previous_beginning(); cursor.move_to_previous_beginning();
assert_eq!(cursor, Cursor { head: 0, tail: 2 }); assert_eq!(cursor, Cursor { head: 0, tail: 2 });
} }
} }
+2
View File
@@ -5,11 +5,13 @@ use app::App;
mod cardinality; mod cardinality;
mod empty_span; mod empty_span;
mod select_grey;
mod app; mod app;
mod cursor; mod cursor;
const BYTES_PER_LINE: usize = 0x10; const BYTES_PER_LINE: usize = 0x10;
const BYTES_PER_CHUNK: usize = 4; const BYTES_PER_CHUNK: usize = 4;
const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK;
fn main() { fn main() {
let mut app = App::init(); let mut app = App::init();
+11
View File
@@ -0,0 +1,11 @@
use ratatui::style::Color;
pub trait SelectGrey {
fn select_grey() -> Self;
}
impl SelectGrey for Color {
fn select_grey() -> Self {
Self::Indexed(242)
}
}