nushell/crates/nu-explore/src/views/binary/mod.rs
Reilly Wood f2f4b83886
Overhaul explore config (#13075)
Configuration in `explore` has always been confusing to me. This PR
overhauls (and simplifies, I think) how configuration is done.

# Details

1. Configuration is now strongly typed. In `Explore::run()` we create an
`ExploreConfig` struct from the more general Nu configuration and
arguments to `explore`, then pass that struct to other parts of
`explore` that need configuration. IMO this is a lot easier to reason
about and trace than the previous approach of creating a
`HashMap<String, Value>` and then using that to make various structs
elsewhere.
2. We now inherit more configuration from the config used for regular Nu
tables
1. Border/line styling now uses the `separator` style used for regular
Nu tables, the special `explore.split_line` config point has been
retired.
2. Cell padding in tables is now controlled by `table.padding` instead
of the undocumented `column_padding_left`/`column_padding_right` config
3. The (optional, previously not enabled by default) `selected_row` and
`selected_column` configuration has been removed. We now only highlight
the selected cell. I could re-add this if people really like the feature
but I'm guessing nobody uses it.

The interface still looks the same with a default/empty config.nu:


![image](https://github.com/nushell/nushell/assets/26268125/e40161ba-a8ec-407a-932d-5ece6f4dc616)
2024-06-06 08:46:43 -05:00

233 lines
6.2 KiB
Rust

// todo: 3 cursor modes one for section
mod binary_widget;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
use ratatui::layout::Rect;
use crate::{
explore::ExploreConfig,
nu_common::NuText,
pager::{
report::{Report, Severity},
Frame, Transition, ViewInfo,
},
views::cursor::Position,
};
use self::binary_widget::{BinarySettings, BinaryStyle, BinaryWidget};
use super::{cursor::WindowCursor2D, Layout, View, ViewConfig};
/// An interactive view that displays binary data in a hex dump format.
/// Not finished; many aspects are still WIP.
#[derive(Debug, Clone)]
pub struct BinaryView {
data: Vec<u8>,
// HACK: we are only using the vertical dimension of the cursor, should we use a plain old WindowCursor?
cursor: WindowCursor2D,
settings: Settings,
}
#[derive(Debug, Default, Clone)]
struct Settings {
opts: BinarySettings,
style: BinaryStyle,
}
impl BinaryView {
pub fn new(data: Vec<u8>) -> Self {
Self {
data,
cursor: WindowCursor2D::default(),
settings: Settings::default(),
}
}
}
impl View for BinaryView {
fn draw(&mut self, f: &mut Frame, area: Rect, _cfg: ViewConfig<'_>, _layout: &mut Layout) {
let widget = create_binary_widget(self);
f.render_widget(widget, area);
}
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.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<'_>) {
self.settings = settings_from_config(cfg.explore_config);
let count_rows =
BinaryWidget::new(&self.data, self.settings.opts, Default::default()).count_lines();
// TODO: refactor View so setup() is fallible and we don't have to panic here
self.cursor = WindowCursor2D::new(count_rows, 1).expect("Failed to create XYCursor");
}
}
fn create_binary_widget(v: &BinaryView) -> BinaryWidget<'_> {
let start_line = v.cursor.window_origin().row;
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_row_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: &ExploreConfig) -> Settings {
// Most of this is hardcoded for now, add it to the config later if needed
Settings {
opts: BinarySettings::new(2, 8),
style: BinaryStyle::new(
None,
config.table.column_padding_left as u16,
config.table.column_padding_right as u16,
),
}
}
fn create_report(cursor: WindowCursor2D) -> Report {
let covered_percent = report_row_position(cursor);
let cursor = report_cursor_position(cursor);
let mode = report_mode_name();
let msg = String::new();
Report::new(msg, Severity::Info, mode, cursor, covered_percent)
}
fn report_mode_name() -> String {
String::from("VIEW")
}
fn report_row_position(cursor: WindowCursor2D) -> String {
if cursor.window_origin().row == 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: WindowCursor2D) -> String {
let Position { row, column } = cursor.window_origin();
format!("{row},{column}")
}
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
}