nu-explore/ Use hex-dump for binary data (#12184)

Hi there

So as 2 minute thing we could show `hex-dump` as it is as a string
(no-coloring).

But I'd do some more things around,.
Probably will take a few days (WIP).

```
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

─────────────────────────────────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────
  00000000:   6d 6f 64 20  63 6f 6d 6d  61 6e 64 3b  0a 6d 6f 64   mod command;_mod  │
  00000010:   20 63 6f 6e  66 69 67 5f  66 69 6c 65  73 3b 0a 6d    config_files;_m  │
  00000020:   6f 64 20 69  64 65 3b 0a  6d 6f 64 20  6c 6f 67 67   od ide;_mod logg  │
  00000030:   65 72 3b 0a  6d 6f 64 20  72 75 6e 3b  0a 6d 6f 64   er;_mod run;_mod  │
  00000040:   20 73 69 67  6e 61 6c 73  3b 0a 23 5b  63 66 67 28    signals;_#[cfg(  │
  00000050:   75 6e 69 78  29 5d 0a 6d  6f 64 20 74  65 72 6d 69   unix)]_mod termi  │
  00000060:   6e 61 6c 3b  0a 6d 6f 64  20 74 65 73  74 5f 62 69   nal;_mod test_bi  │
  00000070:   6e 73 3b 0a  23 5b 63 66  67 28 74 65  73 74 29 5d   ns;_#[cfg(test)]  │
  00000080:   0a 6d 6f 64  20 74 65 73  74 73 3b 0a  0a 23 5b 63   _mod tests;__#[c  │
  00000090:   66 67 28 66  65 61 74 75  72 65 20 3d  20 22 6d 69   fg(feature = "mi  │
  000000a0:   6d 61 6c 6c  6f 63 22 29  5d 0a 23 5b  67 6c 6f 62   malloc")]_#[glob  │
  000000b0:   61 6c 5f 61  6c 6c 6f 63  61 74 6f 72  5d 0a 73 74   al_allocator]_st  │
  000000c0:   61 74 69 63  20 47 4c 4f  42 41 4c 3a  20 6d 69 6d   atic GLOBAL: mim  │
  000000d0:   61 6c 6c 6f  63 3a 3a 4d  69 4d 61 6c  6c 6f 63 20   alloc::MiMalloc   │
  000000e0:   3d 20 6d 69  6d 61 6c 6c  6f 63 3a 3a  4d 69 4d 61   = mimalloc::MiMa  │
  000000f0:   6c 6c 6f 63  3b 0a 0a 75  73 65 20 63  72 61 74 65   lloc;__use crate  │
  00000100:   3a 3a 7b 0a  20 20 20 20  63 6f 6d 6d  61 6e 64 3a   ::{_    command:  │
  00000110:   3a 70 61 72  73 65 5f 63  6f 6d 6d 61  6e 64 6c 69   :parse_commandli  │
  00000120:   6e 65 5f 61  72 67 73 2c  0a 20 20 20  20 63 6f 6e   ne_args,_    con  │
  00000130:   66 69 67 5f  66 69 6c 65  73 3a 3a 73  65 74 5f 63   fig_files::set_c  │
  00000140:   6f 6e 66 69  67 5f 70 61  74 68 2c 0a  20 20 20 20   onfig_path,_      │
  00000150:   6c 6f 67 67  65 72 3a 3a  7b 63 6f 6e  66 69 67 75   logger::{configu  │
  00000160:   72 65 2c 20  6c 6f 67 67  65 72 7d 2c  0a 7d 3b 0a   re, logger},_};_  │
  00000170:   75 73 65 20  63 6f 6d 6d  61 6e 64 3a  3a 67 61 74   use command::gat  │
  00000180:   68 65 72 5f  63 6f 6d 6d  61 6e 64 6c  69 6e 65 5f   her_commandline_  │
  00000190:   61 72 67 73  3b 0a 75 73  65 20 6c 6f  67 3a 3a 4c   args;_use log::L  │
  000001a0:   65 76 65 6c  3b 0a 75 73  65 20 6d 69  65 74 74 65   evel;_use miette  │
  000001b0:   3a 3a 52 65  73 75 6c 74  3b 0a 75 73  65 20 6e 75   ::Result;_use nu  │
  000001c0:   5f 63 6c 69  3a 3a 67 61  74 68 65 72  5f 70 61 72   _cli::gather_par  │

```

ref: #12157
cc: @fdncred @lrdickson
This commit is contained in:
Maxim Zhiburt 2024-03-22 03:02:03 +03:00 committed by GitHub
parent 6e2c41a5b5
commit cc8f2b6419
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 874 additions and 11 deletions

1
Cargo.lock generated
View File

@ -2983,6 +2983,7 @@ dependencies = [
"nu-engine",
"nu-json",
"nu-parser",
"nu-pretty-hex",
"nu-protocol",
"nu-table",
"nu-utils",

View File

@ -19,6 +19,7 @@ nu-table = { path = "../nu-table", version = "0.91.1" }
nu-json = { path = "../nu-json", version = "0.91.1" }
nu-utils = { path = "../nu-utils", version = "0.91.1" }
nu-ansi-term = { workspace = true }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.91.1" }
terminal_size = "0.3"
strip-ansi-escapes = "0.2.0"

View File

@ -188,27 +188,28 @@ fn prepare_default_config(config: &mut HashMap<String, Value>) {
Some(Color::Rgb(29, 31, 33)),
Some(Color::Rgb(196, 201, 198)),
);
const INPUT_BAR: Style = color(Some(Color::Rgb(196, 201, 198)), None);
const HIGHLIGHT: Style = color(Some(Color::Black), Some(Color::Yellow));
const STATUS_ERROR: Style = color(Some(Color::White), Some(Color::Red));
const STATUS_INFO: Style = color(None, None);
const STATUS_SUCCESS: Style = color(Some(Color::Black), Some(Color::Green));
const STATUS_WARN: Style = color(None, None);
const TABLE_SPLIT_LINE: Style = color(Some(Color::Rgb(64, 64, 64)), None);
const TABLE_SELECT_CELL: Style = color(None, None);
const TABLE_SELECT_ROW: Style = color(None, None);
const TABLE_SELECT_COLUMN: Style = color(None, None);
const HEXDUMP_INDEX: Style = color(Some(Color::Cyan), None);
const HEXDUMP_SEGMENT: Style = color(Some(Color::Cyan), None).bold();
const HEXDUMP_SEGMENT_ZERO: Style = color(Some(Color::Purple), None).bold();
const HEXDUMP_SEGMENT_UNKNOWN: Style = color(Some(Color::Green), None).bold();
const HEXDUMP_ASCII: Style = color(Some(Color::Cyan), None).bold();
const HEXDUMP_ASCII_ZERO: Style = color(Some(Color::Purple), None).bold();
const HEXDUMP_ASCII_UNKNOWN: Style = color(Some(Color::Green), None).bold();
insert_style(config, "status_bar_background", STATUS_BAR);
insert_style(config, "command_bar_text", INPUT_BAR);
insert_style(config, "highlight", HIGHLIGHT);
@ -242,6 +243,28 @@ fn prepare_default_config(config: &mut HashMap<String, Value>) {
config.insert(String::from("table"), map_into_value(hm));
}
{
let mut hm = config
.get("hex-dump")
.and_then(create_map)
.unwrap_or_default();
insert_style(&mut hm, "color_index", HEXDUMP_INDEX);
insert_style(&mut hm, "color_segment", HEXDUMP_SEGMENT);
insert_style(&mut hm, "color_segment_zero", HEXDUMP_SEGMENT_ZERO);
insert_style(&mut hm, "color_segment_unknown", HEXDUMP_SEGMENT_UNKNOWN);
insert_style(&mut hm, "color_ascii", HEXDUMP_ASCII);
insert_style(&mut hm, "color_ascii_zero", HEXDUMP_ASCII_ZERO);
insert_style(&mut hm, "color_ascii_unknown", HEXDUMP_ASCII_UNKNOWN);
insert_int(&mut hm, "segment_size", 2);
insert_int(&mut hm, "count_segments", 8);
insert_bool(&mut hm, "split", true);
config.insert(String::from("hex-dump"), map_into_value(hm));
}
}
fn parse_hash_map(value: &Value) -> Option<HashMap<String, Value>> {
@ -291,6 +314,14 @@ fn insert_bool(map: &mut HashMap<String, Value>, key: &str, value: bool) {
map.insert(String::from(key), Value::bool(value, Span::unknown()));
}
fn insert_int(map: &mut HashMap<String, Value>, key: &str, value: i64) {
if map.contains_key(key) {
return;
}
map.insert(String::from(key), Value::int(value, Span::unknown()));
}
fn include_nu_config(config: &mut HashMap<String, Value>, style_computer: &StyleComputer) {
let line_color = lookup_color(style_computer, "separator");
if line_color != nu_ansi_term::Style::default() {

View File

@ -20,7 +20,7 @@ use nu_protocol::{
use pager::{Page, Pager};
use registry::{Command, CommandRegistry};
use terminal_size::{Height, Width};
use views::{InformationView, Orientation, Preview, RecordView};
use views::{BinaryView, InformationView, Orientation, Preview, RecordView};
use pager::{PagerConfig, StyleConfig};
@ -36,11 +36,19 @@ fn run_pager(
config: PagerConfig,
) -> io::Result<Option<Value>> {
let mut p = Pager::new(config.clone());
let commands = create_command_registry();
let is_record = matches!(input, PipelineData::Value(Value::Record { .. }, ..));
let (columns, data) = collect_pipeline(input);
let is_binary = matches!(input, PipelineData::Value(Value::Binary { .. }, ..));
let commands = create_command_registry();
if is_binary {
p.show_message("For help type :help");
let view = binary_view(input);
return p.run(engine_state, stack, ctrlc, view, commands);
}
let (columns, data) = collect_pipeline(input);
let has_no_input = columns.is_empty() && data.is_empty();
if has_no_input {
@ -83,6 +91,17 @@ fn information_view() -> Option<Page> {
Some(Page::new(InformationView, true))
}
fn binary_view(input: PipelineData) -> Option<Page> {
let data = match input {
PipelineData::Value(Value::Binary { val, .. }, _) => val,
_ => unreachable!("checked beforehand"),
};
let view = BinaryView::new(data);
Some(Page::new(view, false))
}
fn create_command_registry() -> CommandRegistry {
let mut registry = CommandRegistry::new();
create_commands(&mut registry);

View File

@ -0,0 +1,507 @@
use nu_color_config::TextStyle;
use nu_pretty_hex::categorize_byte;
use ratatui::{
buffer::Buffer,
layout::Rect,
text::Span,
widgets::{Paragraph, StatefulWidget, Widget},
};
use crate::{
nu_common::NuStyle,
views::util::{nu_style_to_tui, text_style_to_tui_style},
};
use super::Layout;
type OptStyle = Option<NuStyle>;
#[derive(Debug, Clone)]
pub struct BinaryWidget<'a> {
data: &'a [u8],
opts: BinarySettings,
style: BinaryStyle,
}
impl<'a> BinaryWidget<'a> {
pub fn new(data: &'a [u8], opts: BinarySettings, style: BinaryStyle) -> Self {
Self { data, opts, style }
}
pub fn count_lines(&self) -> usize {
self.data.len() / self.count_elements()
}
pub fn count_elements(&self) -> usize {
self.opts.count_segments * self.opts.segment_size
}
pub fn set_index_offset(&mut self, offset: usize) {
self.opts.index_offset = offset;
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct BinarySettings {
disable_index: bool,
disable_ascii: bool,
disable_data: bool,
segment_size: usize,
count_segments: usize,
index_offset: usize,
}
impl BinarySettings {
pub fn new(
disable_index: bool,
disable_ascii: bool,
disable_data: bool,
segment_size: usize,
count_segments: usize,
index_offset: usize,
) -> Self {
Self {
disable_index,
disable_ascii,
disable_data,
segment_size,
count_segments,
index_offset,
}
}
}
#[derive(Debug, Default, Clone)]
pub struct BinaryStyle {
colors: BinaryStyleColors,
indent_index: Indent,
indent_data: Indent,
indent_ascii: Indent,
indent_segment: usize,
show_split: bool,
}
impl BinaryStyle {
pub fn new(
colors: BinaryStyleColors,
indent_index: Indent,
indent_data: Indent,
indent_ascii: Indent,
indent_segment: usize,
show_split: bool,
) -> Self {
Self {
colors,
indent_index,
indent_data,
indent_ascii,
indent_segment,
show_split,
}
}
}
#[derive(Debug, Default, Clone)]
pub struct Indent {
left: u16,
right: u16,
}
impl Indent {
pub fn new(left: u16, right: u16) -> Self {
Self { left, right }
}
}
#[derive(Debug, Default, Clone)]
pub struct BinaryStyleColors {
pub split_left: OptStyle,
pub split_right: OptStyle,
pub index: OptStyle,
pub data: SymbolColor,
pub ascii: SymbolColor,
}
#[derive(Debug, Default, Clone)]
pub struct SymbolColor {
pub default: OptStyle,
pub zero: OptStyle,
pub unknown: OptStyle,
}
impl SymbolColor {
pub fn new(default: OptStyle, zero: OptStyle, unknown: OptStyle) -> Self {
Self {
default,
zero,
unknown,
}
}
}
impl BinaryStyleColors {
pub fn new(
index: OptStyle,
data: SymbolColor,
ascii: SymbolColor,
split_left: OptStyle,
split_right: OptStyle,
) -> Self {
Self {
split_left,
split_right,
index,
data,
ascii,
}
}
}
#[derive(Debug, Default)]
pub struct BinaryWidgetState {
pub layout_index: Layout,
pub layout_data: Layout,
pub layout_ascii: Layout,
}
impl StatefulWidget for BinaryWidget<'_> {
type State = BinaryWidgetState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let min_width = get_widget_width(&self);
if (area.width as usize) < min_width {
return;
}
if self.opts.disable_index && self.opts.disable_data && self.opts.disable_ascii {
return;
}
render_hexdump(area, buf, state, self);
}
}
// todo: indent color
fn render_hexdump(area: Rect, buf: &mut Buffer, _state: &mut BinaryWidgetState, w: BinaryWidget) {
const MIN_INDEX_SIZE: usize = 8;
let show_index = !w.opts.disable_index;
let show_data = !w.opts.disable_data;
let show_ascii = !w.opts.disable_ascii;
let show_split = w.style.show_split;
let index_width = get_max_index_size(&w).max(MIN_INDEX_SIZE) as u16; // safe as it's checked before hand that we have enough space
let mut last_line = None;
for line in 0..area.height {
let data_line_length = w.opts.count_segments * w.opts.segment_size;
let start_index = line as usize * data_line_length;
let address = w.opts.index_offset + start_index;
if start_index > w.data.len() {
last_line = Some(line);
break;
}
let mut x = 0;
let y = line;
let line = &w.data[start_index..];
if show_index {
x += render_space(buf, x, y, 1, w.style.indent_index.left);
x += render_hex_usize(buf, x, y, address, index_width, false, get_index_style(&w));
x += render_space(buf, x, y, 1, w.style.indent_index.right);
}
if show_split {
x += render_split(buf, x, y);
}
if show_data {
x += render_space(buf, x, y, 1, w.style.indent_data.left);
x += render_data_line(buf, x, y, line, &w);
x += render_space(buf, x, y, 1, w.style.indent_data.right);
}
if show_split {
x += render_split(buf, x, y);
}
if show_ascii {
x += render_space(buf, x, y, 1, w.style.indent_ascii.left);
x += render_ascii_line(buf, x, y, line, &w);
render_space(buf, x, y, 1, w.style.indent_ascii.right);
}
}
let data_line_size = (w.opts.count_segments * (w.opts.segment_size * 2)
+ w.opts.count_segments.saturating_sub(1)) as u16;
let ascii_line_size = (w.opts.count_segments * w.opts.segment_size) as u16;
if let Some(last_line) = last_line {
for line in last_line..area.height {
let data_line_length = w.opts.count_segments * w.opts.segment_size;
let start_index = line as usize * data_line_length;
let address = w.opts.index_offset + start_index;
let mut x = 0;
let y = line;
if show_index {
x += render_space(buf, x, y, 1, w.style.indent_index.left);
x += render_hex_usize(buf, x, y, address, index_width, false, get_index_style(&w));
x += render_space(buf, x, y, 1, w.style.indent_index.right);
}
if show_split {
x += render_split(buf, x, y);
}
if show_data {
x += render_space(buf, x, y, 1, w.style.indent_data.left);
x += render_space(buf, x, y, 1, data_line_size);
x += render_space(buf, x, y, 1, w.style.indent_data.right);
}
if show_split {
x += render_split(buf, x, y);
}
if show_ascii {
x += render_space(buf, x, y, 1, w.style.indent_ascii.left);
x += render_space(buf, x, y, 1, ascii_line_size);
render_space(buf, x, y, 1, w.style.indent_ascii.right);
}
}
}
}
fn render_data_line(buf: &mut Buffer, x: u16, y: u16, line: &[u8], w: &BinaryWidget) -> u16 {
let mut size = 0;
let mut count = 0;
let count_max = w.opts.count_segments;
let segment_size = w.opts.segment_size;
size += render_segment(buf, x, y, line, w);
count += 1;
while count != count_max && count * segment_size < line.len() {
let data = &line[count * segment_size..];
size += render_space(buf, x + size, y, 1, w.style.indent_segment as u16);
size += render_segment(buf, x + size, y, data, w);
count += 1;
}
while count != count_max {
size += render_space(buf, x + size, y, 1, w.style.indent_segment as u16);
size += render_space(buf, x + size, y, 1, w.opts.segment_size as u16 * 2);
count += 1;
}
size
}
fn render_segment(buf: &mut Buffer, x: u16, y: u16, line: &[u8], w: &BinaryWidget) -> u16 {
let mut count = w.opts.segment_size;
let mut size = 0;
for &n in line {
if count == 0 {
break;
}
let (_, style) = get_segment_char(w, n);
size += render_hex_u8(buf, x + size, y, n, false, style);
count -= 1;
}
if count > 0 {
size += render_space(buf, x + size, y, 1, (count * 2) as u16);
}
size
}
fn render_ascii_line(buf: &mut Buffer, x: u16, y: u16, line: &[u8], w: &BinaryWidget) -> u16 {
let mut size = 0;
let mut count = 0;
let length = w.count_elements();
for &n in line {
if count == length {
break;
}
let (c, style) = get_ascii_char(w, n);
size += render_ascii_char(buf, x + size, y, c, style);
count += 1;
}
if count < length {
size += render_space(buf, x + size, y, 1, (length - count) as u16);
}
size
}
fn render_ascii_char(buf: &mut Buffer, x: u16, y: u16, n: char, style: OptStyle) -> u16 {
let text = n.to_string();
let mut p = Paragraph::new(text);
if let Some(style) = style {
let style = nu_style_to_tui(style);
p = p.style(style);
}
let area = Rect::new(x, y, 1, 1);
p.render(area, buf);
1
}
fn render_hex_u8(buf: &mut Buffer, x: u16, y: u16, n: u8, big: bool, style: OptStyle) -> u16 {
render_hex_usize(buf, x, y, n as usize, 2, big, style)
}
fn render_hex_usize(
buf: &mut Buffer,
x: u16,
y: u16,
n: usize,
width: u16,
big: bool,
style: OptStyle,
) -> u16 {
let text = usize_to_hex(n, width as usize, big);
let mut p = Paragraph::new(text);
if let Some(style) = style {
let style = nu_style_to_tui(style);
p = p.style(style);
}
let area = Rect::new(x, y, width, 1);
p.render(area, buf);
width
}
fn get_ascii_char(_w: &BinaryWidget, n: u8) -> (char, OptStyle) {
let (style, c) = categorize_byte(&n);
let c = c.unwrap_or(n as char);
let style = if style.is_plain() { None } else { Some(style) };
(c, style)
}
fn get_segment_char(_w: &BinaryWidget, n: u8) -> (char, OptStyle) {
let (style, c) = categorize_byte(&n);
let c = c.unwrap_or(n as char);
let style = if style.is_plain() { None } else { Some(style) };
(c, style)
}
fn get_index_style(w: &BinaryWidget) -> OptStyle {
w.style.colors.index
}
fn render_space(buf: &mut Buffer, x: u16, y: u16, height: u16, padding: u16) -> u16 {
repeat_vertical(buf, x, y, padding, height, ' ', TextStyle::default());
padding
}
fn render_split(buf: &mut Buffer, x: u16, y: u16) -> u16 {
repeat_vertical(buf, x, y, 1, 1, '│', TextStyle::default());
1
}
fn repeat_vertical(
buf: &mut Buffer,
x_offset: u16,
y_offset: u16,
width: u16,
height: u16,
c: char,
style: TextStyle,
) {
let text = std::iter::repeat(c)
.take(width as usize)
.collect::<String>();
let style = text_style_to_tui_style(style);
let span = Span::styled(text, style);
for row in 0..height {
buf.set_span(x_offset, y_offset + row, &span, width);
}
}
fn get_max_index_size(w: &BinaryWidget) -> usize {
let line_size = w.opts.count_segments * (w.opts.segment_size * 2);
let count_lines = w.data.len() / line_size;
let max_index = w.opts.index_offset + count_lines * line_size;
usize_to_hex(max_index, 0, false).len()
}
fn get_widget_width(w: &BinaryWidget) -> usize {
const MIN_INDEX_SIZE: usize = 8;
let line_size = w.opts.count_segments * (w.opts.segment_size * 2);
let count_lines = w.data.len() / line_size;
let max_index = w.opts.index_offset + count_lines * line_size;
let index_size = usize_to_hex(max_index, 0, false).len();
let index_size = index_size.max(MIN_INDEX_SIZE);
let data_split_size = w.opts.count_segments.saturating_sub(1) * w.style.indent_segment;
let data_size = line_size + data_split_size;
let ascii_size = w.opts.count_segments * w.opts.segment_size;
let split = w.style.show_split as usize;
#[allow(clippy::identity_op)]
let min_width = 0
+ w.style.indent_index.left as usize
+ index_size
+ w.style.indent_index.right as usize
+ split
+ w.style.indent_data.left as usize
+ data_size
+ w.style.indent_data.right as usize
+ split
+ w.style.indent_ascii.left as usize
+ ascii_size
+ w.style.indent_ascii.right as usize;
min_width
}
fn usize_to_hex(n: usize, width: usize, big: bool) -> String {
if width == 0 {
match big {
true => format!("{:X}", n),
false => format!("{:x}", n),
}
} else {
match big {
true => format!("{:0>width$X}", n, width = width),
false => format!("{:0>width$x}", n, width = width),
}
}
}
#[cfg(test)]
mod tests {
use crate::views::binary::binary_widget::usize_to_hex;
#[test]
fn test_to_hex() {
assert_eq!(usize_to_hex(1, 2, false), "01");
assert_eq!(usize_to_hex(16, 2, false), "10");
assert_eq!(usize_to_hex(29, 2, false), "1d");
assert_eq!(usize_to_hex(29, 2, true), "1D");
}
}

View File

@ -0,0 +1,302 @@
// todo: 3 cursor modes one for section
mod binary_widget;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use nu_color_config::get_color_map;
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
use ratatui::layout::Rect;
use crate::{
nu_common::NuText,
pager::{
report::{Report, Severity},
ConfigMap, Frame, Transition, ViewInfo,
},
util::create_map,
};
use self::binary_widget::{
BinarySettings, BinaryStyle, BinaryStyleColors, BinaryWidget, BinaryWidgetState, Indent,
SymbolColor,
};
use super::{cursor::XYCursor, Layout, View, ViewConfig};
#[derive(Debug, Clone)]
pub struct BinaryView {
data: Vec<u8>,
mode: Option<CursorMode>,
cursor: XYCursor,
settings: Settings,
}
#[allow(dead_code)] // todo:
#[derive(Debug, Clone, Copy)]
enum CursorMode {
Index,
Data,
Ascii,
}
#[derive(Debug, Default, Clone)]
struct Settings {
opts: BinarySettings,
style: BinaryStyle,
}
impl BinaryView {
pub fn new(data: Vec<u8>) -> Self {
Self {
data,
mode: None,
cursor: XYCursor::default(),
settings: Settings::default(),
}
}
}
impl View for BinaryView {
fn draw(&mut self, f: &mut Frame, area: Rect, _cfg: ViewConfig<'_>, _layout: &mut Layout) {
let mut state = BinaryWidgetState::default();
let widget = create_binary_widget(self);
f.render_stateful_widget(widget, area, &mut state);
}
fn handle_input(
&mut self,
_: &EngineState,
_: &mut Stack,
_: &Layout,
info: &mut ViewInfo,
key: KeyEvent,
) -> Option<Transition> {
let result = handle_event_view_mode(self, &key);
if matches!(&result, Some(Transition::Ok)) {
let report = create_report(self.mode, self.cursor);
info.status = Some(report);
}
None
}
fn collect_data(&self) -> Vec<NuText> {
// todo: impl to allow search
vec![]
}
fn show_data(&mut self, _pos: usize) -> bool {
// todo: impl to allow search
false
}
fn exit(&mut self) -> Option<Value> {
// todo: impl Cursor + peek of a value
None
}
fn setup(&mut self, cfg: ViewConfig<'_>) {
let hm = match cfg.config.get("hex-dump").and_then(create_map) {
Some(hm) => hm,
None => return,
};
self.settings = settings_from_config(&hm);
let count_rows =
BinaryWidget::new(&self.data, self.settings.opts, Default::default()).count_lines();
self.cursor = XYCursor::new(count_rows, 0);
}
}
fn create_binary_widget(v: &BinaryView) -> BinaryWidget<'_> {
let start_line = v.cursor.row_starts_at();
let count_elements =
BinaryWidget::new(&[], v.settings.opts, Default::default()).count_elements();
let index = start_line * count_elements;
let data = &v.data[index..];
let mut w = BinaryWidget::new(data, v.settings.opts, v.settings.style.clone());
w.set_index_offset(index);
w
}
fn handle_event_view_mode(view: &mut BinaryView, key: &KeyEvent) -> Option<Transition> {
match key {
KeyEvent {
code: KeyCode::Char('u'),
modifiers: KeyModifiers::CONTROL,
..
}
| KeyEvent {
code: KeyCode::PageUp,
..
} => {
view.cursor.prev_row_page();
return Some(Transition::Ok);
}
KeyEvent {
code: KeyCode::Char('d'),
modifiers: KeyModifiers::CONTROL,
..
}
| KeyEvent {
code: KeyCode::PageDown,
..
} => {
view.cursor.next_row_page();
return Some(Transition::Ok);
}
_ => {}
}
match key.code {
KeyCode::Esc => Some(Transition::Exit),
KeyCode::Up | KeyCode::Char('k') => {
view.cursor.prev_row_i();
Some(Transition::Ok)
}
KeyCode::Down | KeyCode::Char('j') => {
view.cursor.next_row_i();
Some(Transition::Ok)
}
KeyCode::Left | KeyCode::Char('h') => {
view.cursor.prev_column_i();
Some(Transition::Ok)
}
KeyCode::Right | KeyCode::Char('l') => {
view.cursor.next_column_i();
Some(Transition::Ok)
}
KeyCode::Home | KeyCode::Char('g') => {
view.cursor.row_move_to_start();
Some(Transition::Ok)
}
KeyCode::End | KeyCode::Char('G') => {
view.cursor.row_move_to_end();
Some(Transition::Ok)
}
_ => None,
}
}
fn settings_from_config(config: &ConfigMap) -> Settings {
let colors = get_color_map(config);
Settings {
opts: BinarySettings::new(
!config_get_bool(config, "show_index", true),
!config_get_bool(config, "show_ascii", true),
!config_get_bool(config, "show_data", true),
config_get_usize(config, "segment_size", 2),
config_get_usize(config, "count_segments", 8),
0,
),
style: BinaryStyle::new(
BinaryStyleColors::new(
colors.get("color_index").cloned(),
SymbolColor::new(
colors.get("color_segment").cloned(),
colors.get("color_segment_zero").cloned(),
colors.get("color_segment_unknown").cloned(),
),
SymbolColor::new(
colors.get("color_ascii").cloned(),
colors.get("color_ascii_zero").cloned(),
colors.get("color_ascii_unknown").cloned(),
),
colors.get("color_split_left").cloned(),
colors.get("color_split_right").cloned(),
),
Indent::new(
config_get_usize(config, "padding_index_left", 2) as u16,
config_get_usize(config, "padding_index_right", 2) as u16,
),
Indent::new(
config_get_usize(config, "padding_data_left", 2) as u16,
config_get_usize(config, "padding_data_right", 2) as u16,
),
Indent::new(
config_get_usize(config, "padding_ascii_left", 2) as u16,
config_get_usize(config, "padding_ascii_right", 2) as u16,
),
config_get_usize(config, "padding_segment", 1),
config_get_bool(config, "split", false),
),
}
}
fn config_get_bool(config: &ConfigMap, key: &str, default: bool) -> bool {
config
.get(key)
.and_then(|v| v.as_bool().ok())
.unwrap_or(default)
}
fn config_get_usize(config: &ConfigMap, key: &str, default: usize) -> usize {
config
.get(key)
.and_then(|v| v.coerce_str().ok())
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(default)
}
fn create_report(mode: Option<CursorMode>, cursor: XYCursor) -> Report {
let covered_percent = report_row_position(cursor);
let cursor = report_cursor_position(cursor);
let mode = report_mode_name(mode);
let msg = String::new();
Report::new(msg, Severity::Info, mode, cursor, covered_percent)
}
fn report_mode_name(cursor: Option<CursorMode>) -> String {
match cursor {
Some(CursorMode::Index) => String::from("ADDR"),
Some(CursorMode::Data) => String::from("DUMP"),
Some(CursorMode::Ascii) => String::from("TEXT"),
None => String::from("VIEW"),
}
}
fn report_row_position(cursor: XYCursor) -> String {
if cursor.row_starts_at() == 0 {
return String::from("Top");
}
// todo: there's some bug in XYCursor; when we hit PgDOWN/UP and general move it exceeds the limit
// not sure when it was introduced and if present in original view.
// but it just requires a refactoring as these method names are just ..... not perfect.
let row = cursor.row().min(cursor.row_limit());
let count_rows = cursor.row_limit();
let percent_rows = get_percentage(row, count_rows);
match percent_rows {
100 => String::from("All"),
value => format!("{value}%"),
}
}
fn report_cursor_position(cursor: XYCursor) -> String {
let rows_seen = cursor.row_starts_at();
let columns_seen = cursor.column_starts_at();
format!("{rows_seen},{columns_seen}")
}
fn get_percentage(value: usize, max: usize) -> usize {
debug_assert!(value <= max, "{value:?} {max:?}");
((value as f32 / max as f32) * 100.0).floor() as usize
}

View File

@ -1,3 +1,4 @@
mod binary;
mod coloredtextw;
mod cursor;
mod information;
@ -22,6 +23,7 @@ use super::{
pager::{Frame, Transition, ViewInfo},
};
pub use binary::BinaryView;
pub use information::InformationView;
pub use interactive::InteractiveView;
pub use preview::Preview;

View File

@ -110,7 +110,7 @@ impl HexConfig {
}
}
fn categorize_byte(byte: &u8) -> (Style, Option<char>) {
pub fn categorize_byte(byte: &u8) -> (Style, Option<char>) {
// This section is here so later we can configure these items
let null_char_style = Style::default().fg(Color::Fixed(242));
let null_char = Some('0');