#![deny(
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unused_import_braces,
unused_qualifications,
missing_docs,
rustdoc::missing_crate_level_docs,
rustdoc::invalid_html_tags
)]
extern crate cursive;
extern crate itertools;
use std::borrow::Borrow;
use std::cmp::min;
use cursive::direction::Direction;
use cursive::event::{Event, EventResult, Key, MouseEvent};
use cursive::theme::{ColorStyle, Effect};
use cursive::vec::Vec2;
use cursive::view::{CannotFocus, View};
use cursive::{Printer, With};
use itertools::Itertools;
use std::fmt::Write;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DisplayState {
Disabled,
Enabled,
Editable,
}
#[derive(Debug, Clone, Copy)]
pub struct HexViewConfig {
pub bytes_per_line: usize,
pub bytes_per_group: usize,
pub byte_group_separator: &'static str,
pub addr_hex_separator: &'static str,
pub hex_ascii_separator: &'static str,
pub show_ascii: bool,
}
impl Default for HexViewConfig {
fn default() -> Self {
Self {
bytes_per_line: 16,
bytes_per_group: 1,
byte_group_separator: " ",
addr_hex_separator: ": ",
hex_ascii_separator: " | ",
show_ascii: true,
}
}
}
pub struct HexView {
cursor: Vec2,
data: Vec<u8>,
state: DisplayState,
config: HexViewConfig,
}
impl Default for HexView {
fn default() -> Self {
Self::new()
}
}
impl HexView {
#[must_use]
pub fn new() -> Self {
Self::new_from_iter(Vec::<u8>::new())
}
pub fn new_from_iter<B: Borrow<u8>, I: IntoIterator<Item = B>>(data: I) -> Self {
Self {
cursor: Vec2::zero(),
data: data.into_iter().map(|u| *u.borrow()).collect(),
state: DisplayState::Disabled,
config: HexViewConfig::default(),
}
}
pub fn set_config(&mut self, config: HexViewConfig) {
self.config = config;
}
#[must_use]
pub fn config(self, config: HexViewConfig) -> Self {
self.with(|s| s.set_config(config))
}
#[must_use]
pub fn data(&self) -> &[u8] {
self.data.as_slice()
}
pub fn set_data<B: Borrow<u8>, I: IntoIterator<Item = B>>(&mut self, data: I) {
self.data = data.into_iter().map(|u| *u.borrow()).collect();
}
#[must_use]
pub fn display_state(self, state: DisplayState) -> Self {
self.with(|s| s.set_display_state(state))
}
pub fn set_display_state(&mut self, state: DisplayState) {
self.state = state;
}
#[must_use]
pub fn len(&self) -> usize {
self.data.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn set_len(&mut self, length: usize) {
if self.data.len() != length {
let oldlen = self.data.len();
self.data.resize(length, 0);
if oldlen > length {
self.cursor.y = min(self.cursor.y, self.get_widget_height());
self.cursor.x = min(self.cursor.x, (self.data.len() * 2).saturating_sub(1));
}
}
}
}
enum Field {
Addr,
AddrSep,
Hex,
AsciiSep,
Ascii,
}
fn get_cursor_offset(vec: Vec2, config: &HexViewConfig) -> Vec2 {
(
((vec.x as f32 / (2 * config.bytes_per_group) as f32).floor() as usize) * config.byte_group_separator.len(),
0,
)
.into()
}
fn get_elements_in_row(datalen: usize, row: usize, elements_per_line: usize) -> usize {
min(datalen.saturating_sub(elements_per_line * row), elements_per_line)
}
fn get_max_x_in_row(datalen: usize, row: usize, elements_per_line: usize) -> usize {
(get_elements_in_row(datalen, row, elements_per_line) * 2).saturating_sub(1)
}
fn make_printable<T: Borrow<u8>>(c: T) -> char {
let c = *c.borrow();
if c.is_ascii_graphic() {
c as char
} else {
'.'
}
}
impl HexView {
fn get_addr_digit_length(&self) -> usize {
match self.data.len() {
0..=1 => 1,
e => (e as f64).log(16.0).ceil() as usize,
}
}
fn get_widget_height(&self) -> usize {
match self.data.len() {
0 => 1,
e => (e as f64 / self.config.bytes_per_line as f64).ceil() as usize,
}
}
fn get_cursor_offset(&self) -> Vec2 {
self.cursor + get_cursor_offset(self.cursor, &self.config)
}
fn get_elements_in_current_row(&self) -> usize {
get_elements_in_row(self.data.len(), self.cursor.y, self.config.bytes_per_line)
}
fn get_max_x_in_current_row(&self) -> usize {
get_max_x_in_row(self.data.len(), self.cursor.y, self.config.bytes_per_line)
}
fn cursor_x_advance(&mut self) -> EventResult {
let max_pos = self.get_max_x_in_current_row();
if self.cursor.x == max_pos {
return EventResult::Ignored;
}
self.cursor.x = min(self.cursor.x + 1, max_pos);
EventResult::Consumed(None)
}
fn get_element_under_cursor(&self) -> Option<u8> {
let elem = self.cursor.y * self.config.bytes_per_line + self.cursor.x / 2;
self.data.get(elem).copied()
}
fn convert_visual_to_real_cursor(&self, pos: Vec2) -> Vec2 {
let mut res = pos;
let hex_offset = self.get_field_length(Field::Addr) + self.get_field_length(Field::AddrSep);
res.y = min(self.get_widget_height() - 1, pos.y);
res.x = res.x.saturating_sub(hex_offset);
res.x = res.x.saturating_sub(get_cursor_offset(res, &self.config).x);
res.x = min(
get_max_x_in_row(self.data.len(), res.y, self.config.bytes_per_line),
res.x,
);
res
}
#[allow(unknown_lints, clippy::needless_pass_by_value)]
fn get_field_length(&self, field: Field) -> usize {
match field {
Field::Addr => self.get_addr_digit_length(),
Field::AddrSep => self.config.addr_hex_separator.len(),
Field::Hex => {
(((2 * self.config.bytes_per_group) + self.config.byte_group_separator.len())
* (self.config.bytes_per_line / self.config.bytes_per_group))
- self.config.byte_group_separator.len()
}
Field::AsciiSep => self.config.hex_ascii_separator.len(),
Field::Ascii => self.config.bytes_per_line * 2,
}
}
}
impl HexView {
fn draw_addr(&self, printer: &Printer) {
let digits_len = self.get_addr_digit_length();
for lines in 0..self.get_widget_height() {
printer.print(
(0, lines),
&format!("{:0len$X}", lines * self.config.bytes_per_line, len = digits_len),
);
}
}
fn draw_addr_hex_sep(&self, printer: &Printer) {
printer.print_vline((0, 0), self.get_widget_height(), self.config.addr_hex_separator);
}
fn draw_hex(&self, printer: &Printer) {
for (i, c) in self.data.chunks(self.config.bytes_per_line).enumerate() {
let hex = c
.chunks(self.config.bytes_per_group)
.map(|c| {
let mut s = String::new();
for &b in c {
write!(&mut s, "{:02X}", b).expect("Unable to write hex values");
}
s
})
.format(self.config.byte_group_separator);
printer.print((0, i), &format!("{}", hex));
}
}
fn draw_ascii_sep(&self, printer: &Printer) {
printer.print_vline((0, 0), self.get_widget_height(), self.config.hex_ascii_separator);
}
fn draw_ascii(&self, printer: &Printer) {
for (i, c) in self.data.chunks(self.config.bytes_per_line).enumerate() {
let ascii: String = c.iter().map(make_printable).collect();
printer.print((0, i), &ascii);
}
}
#[allow(clippy::similar_names)]
fn highlight_current_hex(&self, printer: &Printer) {
if let Some(elem) = self.get_element_under_cursor() {
let high = self.cursor.x % 2 == 0;
let hpos = self.get_cursor_offset();
let dpos = hpos.map_x(|x| if high { x + 1 } else { x - 1 });
let fem = format!("{:02X}", elem);
let s = fem.split_at(1);
let ext = |hl| if hl { s.0 } else { s.1 };
printer.with_color(ColorStyle::highlight(), |p| p.print(hpos, ext(high)));
printer.with_color(ColorStyle::secondary(), |p| {
p.with_effect(Effect::Reverse, |p| p.print(dpos, ext(!high)));
});
}
}
fn highlight_current_ascii(&self, printer: &Printer) {
if let Some(elem) = self.get_element_under_cursor() {
let pos = self.cursor.map_x(|x| x / 2);
let ascii = make_printable(&elem);
printer.with_color(ColorStyle::highlight(), |p| p.print(pos, &ascii.to_string()));
}
}
}
impl View for HexView {
fn on_event(&mut self, event: Event) -> EventResult {
if self.state == DisplayState::Disabled {
return EventResult::Ignored;
}
match event {
Event::Key(k) => match k {
Key::Left => {
if self.cursor.x == 0 {
return EventResult::Ignored;
}
self.cursor.x = self.cursor.x.saturating_sub(1);
}
Key::Right => {
return self.cursor_x_advance();
}
Key::Up => {
if self.cursor.y == 0 {
return EventResult::Ignored;
}
self.cursor.y = self.cursor.y.saturating_sub(1);
}
Key::Down => {
if self.cursor.y == self.get_widget_height().saturating_sub(1) {
return EventResult::Ignored;
}
let max_pos = min(self.data.len(), self.cursor.y / 2 + 16).saturating_sub(1);
self.cursor.y = min(self.cursor.y + 1, max_pos);
self.cursor.x = min(self.cursor.x, self.get_elements_in_current_row().saturating_sub(1) * 2);
}
Key::Home => self.cursor.x = 0,
Key::End => self.cursor.x = self.get_max_x_in_current_row(),
_ => {
return EventResult::Ignored;
}
},
Event::Shift(Key::Home) => self.cursor = (0, 0).into(),
Event::Shift(Key::End) => {
self.cursor = (
get_max_x_in_row(self.data.len(), self.get_widget_height() - 1, 16),
self.get_widget_height() - 1,
)
.into();
}
Event::Char(c) => {
if self.state != DisplayState::Editable {
return EventResult::Ignored;
}
match c {
'+' => {
let datalen = self.data.len();
self.set_len(datalen + 1);
}
'-' => {
let datalen = self.data.len();
self.set_len(datalen.saturating_sub(1));
}
_ => {
if let Some(val) = c.to_digit(16) {
if let Some(dat) = self.get_element_under_cursor() {
let realpos = self.cursor;
let elem = realpos.y * self.config.bytes_per_line + realpos.x / 2;
let high = self.cursor.x % 2 == 0;
let mask = 0xF << if high { 4 } else { 0 };
self.data[elem] = (dat & !mask) | ((val as u8) << if high { 4 } else { 0 });
self.cursor_x_advance();
}
} else {
return EventResult::Ignored;
}
}
}
}
Event::Mouse {
offset,
position,
event: MouseEvent::Press(_),
} => {
if let Some(position) = position.checked_sub(offset) {
self.cursor = self.convert_visual_to_real_cursor(position);
} else {
return EventResult::Ignored;
}
}
_ => {
return EventResult::Ignored;
}
};
EventResult::Consumed(None)
}
fn required_size(&mut self, _: Vec2) -> Vec2 {
let length = self.get_field_length(Field::Addr)
+ self.get_field_length(Field::AddrSep)
+ self.get_field_length(Field::Hex)
+ self.get_field_length(Field::AsciiSep)
+ self.get_field_length(Field::Ascii);
(length, self.get_widget_height()).into()
}
fn draw(&self, printer: &Printer) {
let height = self.get_widget_height();
let addr = (0usize, self.get_field_length(Field::Addr));
let addr_sep = (addr.0 + addr.1, self.get_field_length(Field::AddrSep));
let hex = (addr_sep.0 + addr_sep.1, self.get_field_length(Field::Hex));
let ascii_sep = (hex.0 + hex.1, self.get_field_length(Field::AsciiSep));
let ascii = (ascii_sep.0 + ascii_sep.1, self.get_field_length(Field::Ascii));
self.draw_addr(&printer.offset((addr.0, 0)).cropped((addr.1, height)));
self.draw_addr_hex_sep(&printer.offset((addr_sep.0, 0)).cropped((addr_sep.1, height)));
self.draw_hex(&printer.offset((hex.0, 0)).cropped((hex.1, height)));
if self.config.show_ascii {
self.draw_ascii_sep(&printer.offset((ascii_sep.0, 0)).cropped((ascii_sep.1, height)));
self.draw_ascii(&printer.offset((ascii.0, 0)).cropped((ascii.1, height)));
}
if self.state != DisplayState::Disabled {
self.highlight_current_hex(&printer.offset((hex.0, 0)).cropped((hex.1, height)).focused(true));
if self.config.show_ascii {
self.highlight_current_ascii(&printer.offset((ascii.0, 0)).cropped((ascii.1, height)).focused(true));
}
}
}
fn take_focus(&mut self, _: Direction) -> Result<EventResult, CannotFocus> {
(self.state != DisplayState::Disabled)
.then(EventResult::consumed)
.ok_or(CannotFocus)
}
}