From 8127632a97c713a80fc9e8922f25b19443098b1f Mon Sep 17 00:00:00 2001 From: Antoine Stevan <44101798+amtoine@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:43:03 +0200 Subject: [PATCH] add missing configuration (#47) related to - https://github.com/amtoine/nu_plugin_explore/issues/45 ## changelog - add support for multi-keys bindings, such as `` for "control + d" - add bindings for - half page down - half page up - goto line - goto top - goto bottom - the half page size is computed as half the height of the TUI frame --- examples/config/default.nuon | 5 ++ src/config/mod.rs | 142 +++++++++++++++++++++++++---------- src/config/parsing.rs | 57 +++++++++----- src/handler.rs | 111 ++++++++++++--------------- src/lib.rs | 7 +- src/ui.rs | 54 ++++++------- 6 files changed, 229 insertions(+), 147 deletions(-) diff --git a/examples/config/default.nuon b/examples/config/default.nuon index 7bb605a..5713af5 100644 --- a/examples/config/default.nuon +++ b/examples/config/default.nuon @@ -68,6 +68,11 @@ down: 'j', # go one row down in the current level up: 'k', # go one row up in the current level right: 'l', # go one level deeper in the data or hit the bottom + half_page_down: "", # go one half page up in the data + half_page_up: "", # go one half page down in the data + goto_top: 'g', # go to the top of the data, i.e. the first element or the first key + goto_bottom: 'G', # go to the bottom of the data, i.e. the last element or the last key + goto_line: 'g', # go at a particular line in the data }, peek: 'p', # go to PEEKING mode to peek a value peeking: { # only in PEEKING mode diff --git a/src/config/mod.rs b/src/config/mod.rs index 1d86282..18580b7 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,7 +4,7 @@ //! 1. holds the data structure of the [`Config`] //! 1. gives default values to a [`Config`] with [`Config::default`] //! 1. parses a Nushell [`Value`](https://docs.rs/nu-protocol/0.83.1/nu_protocol/enum.Value.html) into a valid [`Config`] -use crossterm::event::KeyCode; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use ratatui::style::{Color, Modifier}; use nu_protocol::{LabeledError, Value}; @@ -71,41 +71,51 @@ pub struct BgFgColorConfig { #[derive(Clone, PartialEq, Debug)] pub struct NavigationBindingsMap { /// go one row up in the data - pub up: KeyCode, + pub up: KeyEvent, /// go one row down in the data - pub down: KeyCode, + pub down: KeyEvent, /// go one level higher in the data - pub left: KeyCode, + pub left: KeyEvent, /// go one level deeper in the data - pub right: KeyCode, + pub right: KeyEvent, + /// go one half page up in the data + pub half_page_up: KeyEvent, + /// go one half page down in the data + pub half_page_down: KeyEvent, + /// go to the top of the data, i.e. the first element or the first key + pub goto_top: KeyEvent, + /// go to the bottom of the data, i.e. the last element or the last key + pub goto_bottom: KeyEvent, + /// go at a particular line in the data + pub goto_line: KeyEvent, } /// the bindings in PEEKING mode (see [crate::app::Mode::Peeking]) #[derive(Clone, PartialEq, Debug)] pub struct PeekingBindingsMap { /// peek the whole data structure - pub all: KeyCode, + pub all: KeyEvent, /// peek the current cell path - pub cell_path: KeyCode, + pub cell_path: KeyEvent, /// peek the current level, but only the row under the cursor - pub under: KeyCode, + pub under: KeyEvent, /// peek the current view - pub view: KeyCode, + pub view: KeyEvent, } /// the keybindings mapping #[derive(Clone, PartialEq, Debug)] pub struct KeyBindingsMap { - pub quit: KeyCode, + pub quit: KeyEvent, /// go into INSERT mode (see [crate::app::Mode::Insert]) - pub insert: KeyCode, + pub insert: KeyEvent, /// go back into NORMAL mode (see [crate::app::Mode::Normal]) - pub normal: KeyCode, + pub normal: KeyEvent, pub navigation: NavigationBindingsMap, /// go into PEEKING mode (see [crate::app::Mode::Peeking]) - pub peek: KeyCode, + pub peek: KeyEvent, pub peeking: PeekingBindingsMap, - pub transpose: KeyCode, + pub transpose: KeyEvent, } /// the layout of the application @@ -190,23 +200,28 @@ impl Default for Config { }, }, keybindings: KeyBindingsMap { - quit: KeyCode::Char('q'), - insert: KeyCode::Char('i'), - normal: KeyCode::Esc, + quit: KeyEvent::new(KeyCode::Char('q'), KeyModifiers::NONE), + insert: KeyEvent::new(KeyCode::Char('i'), KeyModifiers::NONE), + normal: KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE), navigation: NavigationBindingsMap { - left: KeyCode::Char('h'), - down: KeyCode::Char('j'), - up: KeyCode::Char('k'), - right: KeyCode::Char('l'), + left: KeyEvent::new(KeyCode::Char('h'), KeyModifiers::NONE), + down: KeyEvent::new(KeyCode::Char('j'), KeyModifiers::NONE), + up: KeyEvent::new(KeyCode::Char('k'), KeyModifiers::NONE), + right: KeyEvent::new(KeyCode::Char('l'), KeyModifiers::NONE), + half_page_down: KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL), + half_page_up: KeyEvent::new(KeyCode::Char('u'), KeyModifiers::CONTROL), + goto_top: KeyEvent::new(KeyCode::Char('g'), KeyModifiers::NONE), + goto_bottom: KeyEvent::new(KeyCode::Char('G'), KeyModifiers::NONE), + goto_line: KeyEvent::new(KeyCode::Char('g'), KeyModifiers::NONE), }, - peek: KeyCode::Char('p'), + peek: KeyEvent::new(KeyCode::Char('p'), KeyModifiers::NONE), peeking: PeekingBindingsMap { - all: KeyCode::Char('a'), - cell_path: KeyCode::Char('c'), - under: KeyCode::Char('p'), - view: KeyCode::Char('v'), + all: KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE), + cell_path: KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE), + under: KeyEvent::new(KeyCode::Char('p'), KeyModifiers::NONE), + view: KeyEvent::new(KeyCode::Char('v'), KeyModifiers::NONE), }, - transpose: KeyCode::Char('t'), + transpose: KeyEvent::new(KeyCode::Char('t'), KeyModifiers::NONE), }, } } @@ -512,6 +527,46 @@ impl Config { config.keybindings.navigation.right = val } } + "half_page_up" => { + if let Some(val) = try_key( + &value, + &["keybindings", "navigation", "half_page_up"], + )? { + config.keybindings.navigation.half_page_up = val + } + } + "half_page_down" => { + if let Some(val) = try_key( + &value, + &["keybindings", "navigation", "half_page_down"], + )? { + config.keybindings.navigation.half_page_down = val + } + } + "goto_top" => { + if let Some(val) = try_key( + &value, + &["keybindings", "navigation", "goto_top"], + )? { + config.keybindings.navigation.goto_top = val + } + } + "goto_bottom" => { + if let Some(val) = try_key( + &value, + &["keybindings", "navigation", "goto_bottom"], + )? { + config.keybindings.navigation.goto_bottom = val + } + } + "goto_line" => { + if let Some(val) = try_key( + &value, + &["keybindings", "navigation", "goto_line"], + )? { + config.keybindings.navigation.goto_line = val + } + } x => { return Err(invalid_field( &["keybindings", "navigation", x], @@ -601,9 +656,9 @@ impl Config { } } -/// represent a [`KeyCode`] as a simple string -pub fn repr_keycode(keycode: &KeyCode) -> String { - match keycode { +/// represent a [`KeyEvent`] as a simple string +pub fn repr_key(key: &KeyEvent) -> String { + let code = match key.code { KeyCode::Char(c) => c.to_string(), KeyCode::Left => char::from_u32(0x2190).unwrap().into(), KeyCode::Up => char::from_u32(0x2191).unwrap().into(), @@ -614,26 +669,37 @@ pub fn repr_keycode(keycode: &KeyCode) -> String { KeyCode::Backspace => char::from_u32(0x232b).unwrap().into(), KeyCode::Delete => char::from_u32(0x2326).unwrap().into(), _ => "??".into(), + }; + + match key.modifiers { + KeyModifiers::NONE => code, + KeyModifiers::CONTROL => format!("", code), + _ => "??".into(), } } // TODO: add proper assert error messages #[cfg(test)] mod tests { - use crossterm::event::KeyCode; + use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use nu_protocol::{record, Record, Value}; use crate::nu::value::from_nuon; - use super::{repr_keycode, Config}; + use super::{repr_key, Config}; #[test] fn keycode_representation() { - assert_eq!(repr_keycode(&KeyCode::Char('x')), "x".to_string()); - assert_eq!(repr_keycode(&KeyCode::Left), "←".to_string()); - assert_eq!(repr_keycode(&KeyCode::Esc), "".to_string()); - assert_eq!(repr_keycode(&KeyCode::Enter), "⏎".to_string()); - assert_eq!(repr_keycode(&KeyCode::Home), "??".to_string()); + for (key, modifiers, expected) in [ + (KeyCode::Char('x'), KeyModifiers::NONE, "x"), + (KeyCode::Char('x'), KeyModifiers::CONTROL, ""), + (KeyCode::Left, KeyModifiers::NONE, "←"), + (KeyCode::Esc, KeyModifiers::NONE, ""), + (KeyCode::Enter, KeyModifiers::NONE, "⏎"), + (KeyCode::Home, KeyModifiers::NONE, "??"), + ] { + assert_eq!(repr_key(&KeyEvent::new(key, modifiers)), expected); + } } #[test] @@ -698,7 +764,7 @@ mod tests { }); let mut expected = Config::default(); - expected.keybindings.navigation.up = KeyCode::Char('x'); + expected.keybindings.navigation.up = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE); assert_eq!(Config::from_value(value), Ok(expected)); } diff --git a/src/config/parsing.rs b/src/config/parsing.rs index 26792a7..ea880c2 100755 --- a/src/config/parsing.rs +++ b/src/config/parsing.rs @@ -1,6 +1,6 @@ //! utilities to parse a [`Value`](https://docs.rs/nu-protocol/0.83.1/nu_protocol/enum.Value.html) //! into a configuration -use crossterm::event::KeyCode; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use ratatui::style::{Color, Modifier}; use nu_protocol::LabeledError; @@ -230,20 +230,31 @@ pub fn try_fg_bg_colors( } /// try to parse a key in the *value* at the given *cell path* -pub fn try_key(value: &Value, cell_path: &[&str]) -> Result, LabeledError> { +pub fn try_key(value: &Value, cell_path: &[&str]) -> Result, LabeledError> { match follow_cell_path(value, cell_path) { Some(Value::String { val, .. }) => match val.as_str() { - "up" => Ok(Some(KeyCode::Up)), - "down" => Ok(Some(KeyCode::Down)), - "left" => Ok(Some(KeyCode::Left)), - "right" => Ok(Some(KeyCode::Right)), - "escape" => Ok(Some(KeyCode::Esc)), + "up" => Ok(Some(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE))), + "down" => Ok(Some(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE))), + "left" => Ok(Some(KeyEvent::new(KeyCode::Left, KeyModifiers::NONE))), + "right" => Ok(Some(KeyEvent::new(KeyCode::Right, KeyModifiers::NONE))), + "escape" => Ok(Some(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE))), x => { if x.len() != 1 { + if x.len() == 5 + && (x.starts_with("') + { + #[allow(clippy::iter_nth_zero)] + return Ok(Some(KeyEvent::new( + KeyCode::Char(x.to_string().chars().nth(3).unwrap()), + KeyModifiers::CONTROL, + ))); + } + return Err(LabeledError::new( "invalid config") .with_label(format!( - r#"`$.{}` should be a character or one of [up, down, left, right, escape] , found {}"#, + r#"`$.{}` should be a character, possibly inside '' or '', or one of [up, down, left, right, escape] , found {}"#, cell_path.join("."), x ), @@ -252,7 +263,10 @@ pub fn try_key(value: &Value, cell_path: &[&str]) -> Result, Lab } #[allow(clippy::iter_nth_zero)] - Ok(Some(KeyCode::Char(x.to_string().chars().nth(0).unwrap()))) + Ok(Some(KeyEvent::new( + KeyCode::Char(x.to_string().chars().nth(0).unwrap()), + KeyModifiers::NONE, + ))) } }, Some(x) => Err(invalid_type(&x, cell_path, "string")), @@ -312,7 +326,7 @@ pub fn follow_cell_path(value: &Value, cell_path: &[&str]) -> Option { // TODO: add proper assert error messages #[cfg(test)] mod tests { - use crossterm::event::KeyCode; + use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use nu_protocol::LabeledError; use nu_protocol::{record, Record, Value}; use ratatui::style::{Color, Modifier}; @@ -421,18 +435,23 @@ mod tests { test_tried_error( try_key(&Value::test_string("enter"), &[]), "", - "should be a character or one of [up, down, left, right, escape] , found enter", + "should be a character, possibly inside '' or '', or one of [up, down, left, right, escape] , found enter", ); let cases = vec![ - ("up", KeyCode::Up), - ("down", KeyCode::Down), - ("left", KeyCode::Left), - ("right", KeyCode::Right), - ("escape", KeyCode::Esc), - ("a", KeyCode::Char('a')), - ("b", KeyCode::Char('b')), - ("x", KeyCode::Char('x')), + ("up", KeyEvent::new(KeyCode::Up, KeyModifiers::NONE)), + ("down", KeyEvent::new(KeyCode::Down, KeyModifiers::NONE)), + ("left", KeyEvent::new(KeyCode::Left, KeyModifiers::NONE)), + ("right", KeyEvent::new(KeyCode::Right, KeyModifiers::NONE)), + ("escape", KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)), + ("a", KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE)), + ("b", KeyEvent::new(KeyCode::Char('b'), KeyModifiers::NONE)), + ("x", KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE)), + ("x", KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE)), + ( + "", + KeyEvent::new(KeyCode::Char('x'), KeyModifiers::CONTROL), + ), ]; for (input, expected) in cases { diff --git a/src/handler.rs b/src/handler.rs index c4876cd..6973877 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,4 +1,4 @@ -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use crossterm::event::{KeyCode, KeyEvent}; use nu_protocol::{ ast::{CellPath, PathMember}, @@ -35,6 +35,7 @@ pub fn handle_key_events( key_event: KeyEvent, app: &mut App, config: &Config, + half_page: usize, ) -> Result { match app.mode { Mode::Normal => { @@ -53,49 +54,43 @@ pub fn handle_key_events( _ => unreachable!(), }); return Ok(TransitionResult::Continue); - } else if key_event.modifiers == KeyModifiers::CONTROL - && key_event.code == KeyCode::Char('d') - { - // FIXME: compute the real number of repetitions to go half a page down + } else if key_event == config.keybindings.navigation.half_page_down { // TODO: add a margin to the bottom - navigation::go_up_or_down_in_data(app, Direction::Down(10)); + navigation::go_up_or_down_in_data(app, Direction::Down(half_page)); return Ok(TransitionResult::Continue); - } else if key_event.modifiers == KeyModifiers::CONTROL - && key_event.code == KeyCode::Char('u') - { - // FIXME: compute the real number of repetitions to go half a page up + } else if key_event == config.keybindings.navigation.half_page_up { // TODO: add a margin to the top - navigation::go_up_or_down_in_data(app, Direction::Up(10)); + navigation::go_up_or_down_in_data(app, Direction::Up(half_page)); return Ok(TransitionResult::Continue); - } else if key_event.code == KeyCode::Char('G') { + } else if key_event == config.keybindings.navigation.goto_bottom { navigation::go_up_or_down_in_data(app, Direction::Bottom); return Ok(TransitionResult::Continue); - } else if key_event.code == KeyCode::Char('g') { + } else if key_event == config.keybindings.navigation.goto_top { navigation::go_up_or_down_in_data(app, Direction::Top); return Ok(TransitionResult::Continue); - } else if key_event.code == config.keybindings.quit { + } else if key_event == config.keybindings.quit { return Ok(TransitionResult::Quit); - } else if key_event.code == config.keybindings.insert { + } else if key_event == config.keybindings.insert { match app.enter_editor() { Ok(_) => return Ok(TransitionResult::Continue), Err(err) => return Ok(TransitionResult::Error(err)), } - } else if key_event.code == config.keybindings.peek { + } else if key_event == config.keybindings.peek { app.mode = Mode::Peeking; return Ok(TransitionResult::Continue); - } else if key_event.code == config.keybindings.navigation.down { + } else if key_event == config.keybindings.navigation.down { navigation::go_up_or_down_in_data(app, Direction::Down(1)); return Ok(TransitionResult::Continue); - } else if key_event.code == config.keybindings.navigation.up { + } else if key_event == config.keybindings.navigation.up { navigation::go_up_or_down_in_data(app, Direction::Up(1)); return Ok(TransitionResult::Continue); - } else if key_event.code == config.keybindings.navigation.right { + } else if key_event == config.keybindings.navigation.right { navigation::go_deeper_in_data(app); return Ok(TransitionResult::Continue); - } else if key_event.code == config.keybindings.navigation.left { + } else if key_event == config.keybindings.navigation.left { navigation::go_back_in_data(app); return Ok(TransitionResult::Continue); - } else if key_event.code == config.keybindings.transpose { + } else if key_event == config.keybindings.transpose { let mut path = app.position.clone(); path.members.pop(); @@ -149,22 +144,22 @@ pub fn handle_key_events( } else if key_event.code == KeyCode::Esc { app.mode = Mode::Normal; return Ok(TransitionResult::Continue); - } else if key_event.code == config.keybindings.navigation.down { + } else if key_event == config.keybindings.navigation.down { app.mode = Mode::Normal; navigation::go_up_or_down_in_data(app, Direction::Down(n)); return Ok(TransitionResult::Continue); - } else if key_event.code == config.keybindings.navigation.up { + } else if key_event == config.keybindings.navigation.up { app.mode = Mode::Normal; navigation::go_up_or_down_in_data(app, Direction::Up(n)); return Ok(TransitionResult::Continue); - } else if key_event.code == KeyCode::Char('g') { + } else if key_event == config.keybindings.navigation.goto_line { app.mode = Mode::Normal; navigation::go_up_or_down_in_data(app, Direction::At(n)); return Ok(TransitionResult::Continue); } } Mode::Insert => { - if key_event.code == config.keybindings.normal { + if key_event == config.keybindings.normal { app.mode = Mode::Normal; return Ok(TransitionResult::Continue); } @@ -182,27 +177,27 @@ pub fn handle_key_events( } } Mode::Peeking => { - if key_event.code == config.keybindings.quit { + if key_event == config.keybindings.quit { return Ok(TransitionResult::Quit); - } else if key_event.code == config.keybindings.normal { + } else if key_event == config.keybindings.normal { app.mode = Mode::Normal; return Ok(TransitionResult::Continue); - } else if key_event.code == config.keybindings.peeking.all { + } else if key_event == config.keybindings.peeking.all { return Ok(TransitionResult::Return(app.value.clone())); - } else if key_event.code == config.keybindings.peeking.view { + } else if key_event == config.keybindings.peeking.view { app.position.members.pop(); return Ok(TransitionResult::Return( app.value .clone() .follow_cell_path(&app.position.members, false)?, )); - } else if key_event.code == config.keybindings.peeking.under { + } else if key_event == config.keybindings.peeking.under { return Ok(TransitionResult::Return( app.value .clone() .follow_cell_path(&app.position.members, false)?, )); - } else if key_event.code == config.keybindings.peeking.cell_path { + } else if key_event == config.keybindings.peeking.cell_path { return Ok(TransitionResult::Return(Value::cell_path( app.position.clone(), Span::unknown(), @@ -210,12 +205,12 @@ pub fn handle_key_events( } } Mode::Bottom => { - if key_event.code == config.keybindings.quit { + if key_event == config.keybindings.quit { return Ok(TransitionResult::Quit); - } else if key_event.code == config.keybindings.navigation.left { + } else if key_event == config.keybindings.navigation.left { app.mode = Mode::Normal; return Ok(TransitionResult::Continue); - } else if key_event.code == config.keybindings.peek { + } else if key_event == config.keybindings.peek { return Ok(TransitionResult::Return( app.value .clone() @@ -230,7 +225,7 @@ pub fn handle_key_events( #[cfg(test)] mod tests { - use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; + use crossterm::event::KeyEvent; use nu_protocol::{ ast::{CellPath, PathMember}, record, Span, Value, @@ -239,7 +234,7 @@ mod tests { use super::{handle_key_events, App, TransitionResult}; use crate::{ app::Mode, - config::{repr_keycode, Config}, + config::{repr_key, Config}, nu::cell_path::{to_path_member_vec, PM}, }; @@ -289,21 +284,19 @@ mod tests { for (key, expected_mode) in transitions { let mode = app.mode.clone(); - let result = - handle_key_events(KeyEvent::new(key, KeyModifiers::empty()), &mut app, &config) - .unwrap(); + let result = handle_key_events(key, &mut app, &config, 0).unwrap(); assert!( !result.is_quit(), "unexpected exit after pressing {} in {}", - repr_keycode(&key), + repr_key(&key), mode, ); assert!( app.mode == expected_mode, "expected to be in {} after pressing {} in {}, found {}", expected_mode, - repr_keycode(&key), + repr_key(&key), mode, app.mode ); @@ -330,22 +323,20 @@ mod tests { for (key, exit) in transitions { let mode = app.mode.clone(); - let result = - handle_key_events(KeyEvent::new(key, KeyModifiers::empty()), &mut app, &config) - .unwrap(); + let result = handle_key_events(key, &mut app, &config, 0).unwrap(); if exit { assert!( result.is_quit(), "expected to quit after pressing {} in {} mode", - repr_keycode(&key), + repr_key(&key), mode ); } else { assert!( !result.is_quit(), "expected NOT to quit after pressing {} in {} mode", - repr_keycode(&key), + repr_key(&key), mode ); } @@ -435,20 +426,19 @@ mod tests { for (key, cell_path, bottom) in transitions { let expected = to_path_member_vec(&cell_path); - handle_key_events(KeyEvent::new(key, KeyModifiers::empty()), &mut app, &config) - .unwrap(); + handle_key_events(key, &mut app, &config, 0).unwrap(); if bottom { assert!( app.is_at_bottom(), "expected to be at the bottom after pressing {}", - repr_keycode(&key) + repr_key(&key) ); } else { assert!( !app.is_at_bottom(), "expected NOT to be at the bottom after pressing {}", - repr_keycode(&key) + repr_key(&key) ); } assert_eq!( @@ -462,7 +452,7 @@ mod tests { } fn run_peeking_scenario( - transitions: Vec<(KeyCode, bool, Option)>, + transitions: Vec<(KeyEvent, bool, Option)>, config: &Config, value: Value, ) { @@ -471,22 +461,20 @@ mod tests { for (key, exit, expected) in transitions { let mode = app.mode.clone(); - let result = - handle_key_events(KeyEvent::new(key, KeyModifiers::empty()), &mut app, config) - .unwrap(); + let result = handle_key_events(key, &mut app, config, 0).unwrap(); if exit { assert!( result.is_quit(), "expected to peek some data after pressing {} in {} mode", - repr_keycode(&key), + repr_key(&key), mode ); } else { assert!( !result.is_quit(), "expected NOT to peek some data after pressing {} in {} mode", - repr_keycode(&key), + repr_key(&key), mode ); } @@ -498,13 +486,13 @@ mod tests { value, val, "unexpected data after pressing {} in {} mode", - repr_keycode(&key), + repr_key(&key), mode ) } _ => panic!( "did expect output data after pressing {} in {} mode", - repr_keycode(&key), + repr_key(&key), mode ), }, @@ -512,7 +500,7 @@ mod tests { if let TransitionResult::Return(_) = result { panic!( "did NOT expect output data after pressing {} in {} mode", - repr_keycode(&key), + repr_key(&key), mode ) } @@ -627,8 +615,7 @@ mod tests { for (key, cell_path) in transitions { let expected = to_path_member_vec(&cell_path); if let TransitionResult::Mutate(cell, path) = - handle_key_events(KeyEvent::new(key, KeyModifiers::empty()), &mut app, &config) - .unwrap() + handle_key_events(key, &mut app, &config, 0).unwrap() { app.value = crate::nu::value::mutate_value_cell(&app.value, &path, &cell) } @@ -636,7 +623,7 @@ mod tests { assert!( !app.is_at_bottom(), "expected NOT to be at the bottom after pressing {}", - repr_keycode(&key) + repr_key(&key) ); assert_eq!( diff --git a/src/lib.rs b/src/lib.rs index 3e1f3f9..eaa16f4 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,12 @@ pub fn explore(config: &Value, input: Value) -> Result { Event::Tick => app.tick(), Event::Key(key_event) => { if key_event.kind == KeyEventKind::Press { - match handle_key_events(key_event, &mut app, &config)? { + match handle_key_events( + key_event, + &mut app, + &config, + (tui.size()?.height as usize - 5) / 2, + )? { TransitionResult::Quit => break, TransitionResult::Continue => {} TransitionResult::Mutate(cell, path) => { diff --git a/src/ui.rs b/src/ui.rs index d385b11..c5bd7aa 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,9 +1,9 @@ //! the module responsible for rendering the TUI use crate::nu::{strings::SpecialString, value::is_table}; -use super::config::{repr_keycode, Layout}; +use super::config::{repr_key, Layout}; use super::{App, Config, Mode}; -use crossterm::event::KeyCode; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use nu_protocol::ast::PathMember; use nu_protocol::{Record, Type, Value}; use ratatui::{ @@ -582,47 +582,47 @@ fn render_status_bar(frame: &mut Frame, app: &App, config: &Config) { let hints = match app.mode { Mode::Normal => format!( "{} to {} | {}{}{}{} to move around | {} to peek | {} to transpose | {} to quit", - repr_keycode(&config.keybindings.insert), + repr_key(&config.keybindings.insert), Mode::Insert, - repr_keycode(&config.keybindings.navigation.left), - repr_keycode(&config.keybindings.navigation.down), - repr_keycode(&config.keybindings.navigation.up), - repr_keycode(&config.keybindings.navigation.right), - repr_keycode(&config.keybindings.peek), - repr_keycode(&config.keybindings.transpose), - repr_keycode(&config.keybindings.quit), + repr_key(&config.keybindings.navigation.left), + repr_key(&config.keybindings.navigation.down), + repr_key(&config.keybindings.navigation.up), + repr_key(&config.keybindings.navigation.right), + repr_key(&config.keybindings.peek), + repr_key(&config.keybindings.transpose), + repr_key(&config.keybindings.quit), ), Mode::Waiting(n) => format!( "{} to quit | will run next motion {} times", - repr_keycode(&KeyCode::Esc), + repr_key(&KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)), n ), Mode::Insert => format!( "{} to quit | {}{}{}{} to move the cursor | {}{} to delete characters | {} to confirm", - repr_keycode(&KeyCode::Esc), - repr_keycode(&KeyCode::Left), - repr_keycode(&KeyCode::Right), - repr_keycode(&KeyCode::Up), - repr_keycode(&KeyCode::Down), - repr_keycode(&KeyCode::Backspace), - repr_keycode(&KeyCode::Delete), - repr_keycode(&KeyCode::Enter), + repr_key(&KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)), + repr_key(&KeyEvent::new(KeyCode::Left, KeyModifiers::NONE)), + repr_key(&KeyEvent::new(KeyCode::Right, KeyModifiers::NONE)), + repr_key(&KeyEvent::new(KeyCode::Up, KeyModifiers::NONE)), + repr_key(&KeyEvent::new(KeyCode::Down, KeyModifiers::NONE)), + repr_key(&KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE)), + repr_key(&KeyEvent::new(KeyCode::Delete, KeyModifiers::NONE)), + repr_key(&KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)), ), Mode::Peeking => format!( "{} to {} | {} to peek all | {} to peek current view | {} to peek under cursor | {} to peek the cell path", - repr_keycode(&config.keybindings.normal), + repr_key(&config.keybindings.normal), Mode::Normal, - repr_keycode(&config.keybindings.peeking.all), - repr_keycode(&config.keybindings.peeking.view), - repr_keycode(&config.keybindings.peeking.under), - repr_keycode(&config.keybindings.peeking.cell_path), + repr_key(&config.keybindings.peeking.all), + repr_key(&config.keybindings.peeking.view), + repr_key(&config.keybindings.peeking.under), + repr_key(&config.keybindings.peeking.cell_path), ), Mode::Bottom => format!( "{} to {} | {} to peek | {} to quit", - repr_keycode(&config.keybindings.navigation.left), + repr_key(&config.keybindings.navigation.left), Mode::Normal, - repr_keycode(&config.keybindings.peek), - repr_keycode(&config.keybindings.quit), + repr_key(&config.keybindings.peek), + repr_key(&config.keybindings.quit), ), };