diff --git a/Cargo.lock b/Cargo.lock index d88b6ff02b..299e67a51e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2983,6 +2983,7 @@ dependencies = [ "nu-engine", "nu-json", "nu-parser", + "nu-pretty-hex", "nu-protocol", "nu-table", "nu-utils", diff --git a/crates/nu-explore/Cargo.toml b/crates/nu-explore/Cargo.toml index 49ff19e418..82e42bcbe7 100644 --- a/crates/nu-explore/Cargo.toml +++ b/crates/nu-explore/Cargo.toml @@ -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" diff --git a/crates/nu-explore/src/explore.rs b/crates/nu-explore/src/explore.rs index 0e3331b86e..decfa1f389 100644 --- a/crates/nu-explore/src/explore.rs +++ b/crates/nu-explore/src/explore.rs @@ -188,27 +188,28 @@ fn prepare_default_config(config: &mut HashMap) { 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) { 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> { @@ -291,6 +314,14 @@ fn insert_bool(map: &mut HashMap, key: &str, value: bool) { map.insert(String::from(key), Value::bool(value, Span::unknown())); } +fn insert_int(map: &mut HashMap, 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, style_computer: &StyleComputer) { let line_color = lookup_color(style_computer, "separator"); if line_color != nu_ansi_term::Style::default() { diff --git a/crates/nu-explore/src/lib.rs b/crates/nu-explore/src/lib.rs index 3a7e5f652a..7f9d134c65 100644 --- a/crates/nu-explore/src/lib.rs +++ b/crates/nu-explore/src/lib.rs @@ -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> { 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 { Some(Page::new(InformationView, true)) } +fn binary_view(input: PipelineData) -> Option { + 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); diff --git a/crates/nu-explore/src/views/binary/binary_widget.rs b/crates/nu-explore/src/views/binary/binary_widget.rs new file mode 100644 index 0000000000..c0f9f49e47 --- /dev/null +++ b/crates/nu-explore/src/views/binary/binary_widget.rs @@ -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; + +#[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::(); + 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"); + } +} diff --git a/crates/nu-explore/src/views/binary/mod.rs b/crates/nu-explore/src/views/binary/mod.rs new file mode 100644 index 0000000000..119c852031 --- /dev/null +++ b/crates/nu-explore/src/views/binary/mod.rs @@ -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, + mode: Option, + 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) -> 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 { + 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 { + // 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 { + // 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 { + 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::().ok()) + .unwrap_or(default) +} + +fn create_report(mode: Option, 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) -> 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 +} diff --git a/crates/nu-explore/src/views/mod.rs b/crates/nu-explore/src/views/mod.rs index a94d6cde1b..25f60e3d35 100644 --- a/crates/nu-explore/src/views/mod.rs +++ b/crates/nu-explore/src/views/mod.rs @@ -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; diff --git a/crates/nu-pretty-hex/src/pretty_hex.rs b/crates/nu-pretty-hex/src/pretty_hex.rs index a61eb3dd24..e2684b1583 100644 --- a/crates/nu-pretty-hex/src/pretty_hex.rs +++ b/crates/nu-pretty-hex/src/pretty_hex.rs @@ -110,7 +110,7 @@ impl HexConfig { } } -fn categorize_byte(byte: &u8) -> (Style, Option) { +pub fn categorize_byte(byte: &u8) -> (Style, Option) { // 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');