mirror of
https://github.com/nushell/nushell.git
synced 2024-11-25 01:43:47 +01:00
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:
parent
6e2c41a5b5
commit
cc8f2b6419
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2983,6 +2983,7 @@ dependencies = [
|
||||
"nu-engine",
|
||||
"nu-json",
|
||||
"nu-parser",
|
||||
"nu-pretty-hex",
|
||||
"nu-protocol",
|
||||
"nu-table",
|
||||
"nu-utils",
|
||||
|
@ -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"
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
507
crates/nu-explore/src/views/binary/binary_widget.rs
Normal file
507
crates/nu-explore/src/views/binary/binary_widget.rs
Normal 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");
|
||||
}
|
||||
}
|
302
crates/nu-explore/src/views/binary/mod.rs
Normal file
302
crates/nu-explore/src/views/binary/mod.rs
Normal 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
|
||||
}
|
@ -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;
|
||||
|
@ -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');
|
||||
|
Loading…
Reference in New Issue
Block a user