From 88d27fd607cabf860843981532921338264c7e30 Mon Sep 17 00:00:00 2001 From: paulie4 Date: Sat, 30 Nov 2024 09:22:52 -0500 Subject: [PATCH] `explore`: add more `less` key bindings and add `Transition::None` (#14468) # Description The `explore` command is `less`-like, but it's missing the `Emacs` keybindings for up/down and PageUp/PageDown as well as the "q" to quit out. When I looked into adding those additional keybindings, I noticed there was a lot of duplicated code in the various views, so I refactored the code into a new `trait CursorMoveHandler`. I also noticed that there was an existing `TODO: should we add a noop transition instead of doing Option everywhere?` comment in the code. I went ahead and implemented a new `Transition::None`, and that made the new `trait CursorMoveHandler` code MUCH cleaner, in addition to making some of the old code a little cleaner as well. # User-Facing Changes Users that are used to the keybindings for `less` should feel much more comfortable using `explore`. # Tests + Formatting Unfortunately, there aren't any existing tests for the `explore` command, so I didn't know where I should add new tests to cover my code changes. --------- Co-authored-by: paulie4 <203125+paulie4@users.noreply.github.com> Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> --- crates/nu-explore/src/commands/help.rs | 7 +- crates/nu-explore/src/commands/nu.rs | 2 +- crates/nu-explore/src/pager/mod.rs | 87 +++--- crates/nu-explore/src/views/binary/mod.rs | 86 +----- crates/nu-explore/src/views/cursor/mod.rs | 2 +- .../src/views/cursor/window_cursor_2d.rs | 133 +++++++++ crates/nu-explore/src/views/mod.rs | 4 +- crates/nu-explore/src/views/preview.rs | 82 ++---- crates/nu-explore/src/views/record/mod.rs | 265 ++++++------------ crates/nu-explore/src/views/try.rs | 26 +- 10 files changed, 324 insertions(+), 370 deletions(-) diff --git a/crates/nu-explore/src/commands/help.rs b/crates/nu-explore/src/commands/help.rs index 63c24969ec..3083b83a9b 100644 --- a/crates/nu-explore/src/commands/help.rs +++ b/crates/nu-explore/src/commands/help.rs @@ -33,13 +33,14 @@ Launch Explore by piping data into it: {} Move around: Use the cursor keys Drill down into records+tables: Press to select a cell, move around with cursor keys, press again - Go back/up a level: Press + Go back/up a level: Press or "q" Transpose (flip rows+columns): Press "t" Expand (show all nested data): Press "e" Open this help page : Type ":help" then Open an interactive REPL: Type ":try" then - Scroll up/down: Use the "Page Up" and "Page Down" keys - Exit Explore: Type ":q" then , or Ctrl+D. Alternately, press until Explore exits + Scroll up: Press "Page Up", Ctrl+B, or Alt+V + Scroll down: Press "Page Down", Ctrl+F, or Ctrl+V + Exit Explore: Type ":q" then , or Ctrl+D. Alternately, press or "q" until Explore exits {} Most commands support search via regular expressions. diff --git a/crates/nu-explore/src/commands/nu.rs b/crates/nu-explore/src/commands/nu.rs index 8b5bc71f69..4bdd55318b 100644 --- a/crates/nu-explore/src/commands/nu.rs +++ b/crates/nu-explore/src/commands/nu.rs @@ -93,7 +93,7 @@ impl View for NuView { layout: &Layout, info: &mut crate::pager::ViewInfo, key: crossterm::event::KeyEvent, - ) -> Option { + ) -> crate::pager::Transition { match self { NuView::Records(v) => v.handle_input(engine_state, stack, layout, info, key), NuView::Preview(v) => v.handle_input(engine_state, stack, layout, info, key), diff --git a/crates/nu-explore/src/pager/mod.rs b/crates/nu-explore/src/pager/mod.rs index 2141f89cec..2d4da2e1a7 100644 --- a/crates/nu-explore/src/pager/mod.rs +++ b/crates/nu-explore/src/pager/mod.rs @@ -128,10 +128,17 @@ impl<'a> Pager<'a> { #[derive(Debug, Clone)] pub enum Transition { - // TODO: should we add a noop transition instead of doing Option everywhere? Ok, Exit, Cmd(String), + None, +} + +#[derive(Debug, Clone)] +pub enum StatusTopOrEnd { + Top, + End, + None, } #[derive(Debug, Clone)] @@ -212,34 +219,32 @@ fn render_ui( view_stack.curr_view.as_mut().map(|p| &mut p.view), ); - if let Some(transition) = transition { - let (exit, cmd_name) = react_to_event_result( - transition, - engine_state, - &commands, - pager, - &mut view_stack, - stack, - info, - ); + let (exit, cmd_name) = react_to_event_result( + transition, + engine_state, + &commands, + pager, + &mut view_stack, + stack, + info, + ); - if let Some(value) = exit { - break Ok(value); + if let Some(value) = exit { + break Ok(value); + } + + if !cmd_name.is_empty() { + if let Some(r) = info.report.as_mut() { + r.message = cmd_name; + r.level = Severity::Success; + } else { + info.report = Some(Report::success(cmd_name)); } - if !cmd_name.is_empty() { - if let Some(r) = info.report.as_mut() { - r.message = cmd_name; - r.level = Severity::Success; - } else { - info.report = Some(Report::success(cmd_name)); - } - - let info = info.clone(); - term.draw(|f| { - draw_info(f, pager, info); - })?; - } + let info = info.clone(); + term.draw(|f| { + draw_info(f, pager, info); + })?; } if pager.cmd_buf.run_cmd { @@ -319,6 +324,7 @@ fn react_to_event_result( } } } + Transition::None => (None, String::default()), } } @@ -419,6 +425,7 @@ fn run_command( Transition::Ok => Ok(CmdResult::new(false, false, String::new())), Transition::Exit => Ok(CmdResult::new(true, false, String::new())), Transition::Cmd { .. } => todo!("not used so far"), + Transition::None => panic!("Transition::None not expected from command.react()"), } } Command::View { mut cmd, stackable } => { @@ -617,17 +624,17 @@ fn handle_events( search: &mut SearchBuf, command: &mut CommandBuf, mut view: Option<&mut V>, -) -> Option { +) -> Transition { // We are only interested in Pressed events; // It's crucial because there are cases where terminal MIGHT produce false events; // 2 events 1 for release 1 for press. // Want to react only on 1 of them so we do. let mut key = match events.next_key_press() { Ok(Some(key)) => key, - Ok(None) => return None, + Ok(None) => return Transition::None, Err(e) => { log::error!("Failed to read key event: {e}"); - return None; + return Transition::None; } }; @@ -647,15 +654,15 @@ fn handle_events( view.as_deref_mut(), key, ); - if result.is_some() { + if !matches!(result, Transition::None) { return result; } match events.try_next_key_press() { Ok(Some(next_key)) => key = next_key, - Ok(None) => return None, + Ok(None) => return Transition::None, Err(e) => { log::error!("Failed to peek key event: {e}"); - return None; + return Transition::None; } } } @@ -671,29 +678,29 @@ fn handle_event( command: &mut CommandBuf, mut view: Option<&mut V>, key: KeyEvent, -) -> Option { +) -> Transition { if handle_exit_key_event(&key) { - return Some(Transition::Exit); + return Transition::Exit; } if handle_general_key_events1(&key, search, command, view.as_deref_mut()) { - return None; + return Transition::None; } if let Some(view) = &mut view { let t = view.handle_input(engine_state, stack, layout, info, key); match t { - Some(Transition::Exit) => return Some(Transition::Ok), - Some(Transition::Cmd(cmd)) => return Some(Transition::Cmd(cmd)), - Some(Transition::Ok) => return None, - None => {} + Transition::Exit => return Transition::Ok, + Transition::Cmd(cmd) => return Transition::Cmd(cmd), + Transition::Ok => return Transition::None, + Transition::None => {} } } // was not handled so we must check our default controls handle_general_key_events2(&key, search, command, view, info); - None + Transition::None } fn handle_exit_key_event(key: &KeyEvent) -> bool { diff --git a/crates/nu-explore/src/views/binary/mod.rs b/crates/nu-explore/src/views/binary/mod.rs index 2a35b7c180..a9ba47d650 100644 --- a/crates/nu-explore/src/views/binary/mod.rs +++ b/crates/nu-explore/src/views/binary/mod.rs @@ -2,7 +2,7 @@ mod binary_widget; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use crossterm::event::KeyEvent; use nu_protocol::{ engine::{EngineState, Stack}, Value, @@ -21,7 +21,7 @@ use crate::{ use self::binary_widget::{BinarySettings, BinaryStyle, BinaryWidget}; -use super::{cursor::WindowCursor2D, Layout, View, ViewConfig}; +use super::{cursor::CursorMoveHandler, cursor::WindowCursor2D, Layout, View, ViewConfig}; /// An interactive view that displays binary data in a hex dump format. /// Not finished; many aspects are still WIP. @@ -66,15 +66,14 @@ impl View for BinaryView { _: &Layout, info: &mut ViewInfo, key: KeyEvent, - ) -> Option { - let result = handle_event_view_mode(self, &key); - - if matches!(&result, Some(Transition::Ok)) { + ) -> Transition { + // currently only handle_enter() in crates/nu-explore/src/views/record/mod.rs raises an Err() + if let Ok((Transition::Ok, ..)) = self.handle_input_key(&key) { let report = create_report(self.cursor); info.status = Some(report); } - None + Transition::None } fn collect_data(&self) -> Vec { @@ -93,6 +92,12 @@ impl View for BinaryView { } } +impl CursorMoveHandler for BinaryView { + fn get_cursor(&mut self) -> &mut WindowCursor2D { + &mut self.cursor + } +} + fn create_binary_widget(v: &BinaryView) -> BinaryWidget<'_> { let start_line = v.cursor.window_origin().row; let count_elements = @@ -106,73 +111,6 @@ fn create_binary_widget(v: &BinaryView) -> BinaryWidget<'_> { 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: &ExploreConfig) -> Settings { // Most of this is hardcoded for now, add it to the config later if needed Settings { diff --git a/crates/nu-explore/src/views/cursor/mod.rs b/crates/nu-explore/src/views/cursor/mod.rs index 8044b59aca..4c37b70977 100644 --- a/crates/nu-explore/src/views/cursor/mod.rs +++ b/crates/nu-explore/src/views/cursor/mod.rs @@ -3,7 +3,7 @@ mod window_cursor_2d; use anyhow::{bail, Result}; pub use window_cursor::WindowCursor; -pub use window_cursor_2d::{Position, WindowCursor2D}; +pub use window_cursor_2d::{CursorMoveHandler, Position, WindowCursor2D}; /// A 1-dimensional cursor to track a position from 0 to N /// diff --git a/crates/nu-explore/src/views/cursor/window_cursor_2d.rs b/crates/nu-explore/src/views/cursor/window_cursor_2d.rs index e22ec09ce3..8dfafcd1a4 100644 --- a/crates/nu-explore/src/views/cursor/window_cursor_2d.rs +++ b/crates/nu-explore/src/views/cursor/window_cursor_2d.rs @@ -1,3 +1,6 @@ +use crate::pager::{StatusTopOrEnd, Transition}; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; + use super::WindowCursor; use anyhow::Result; @@ -170,3 +173,133 @@ impl WindowCursor2D { .set_window_start_position(self.y.window_starts_at() - 1) } } + +pub trait CursorMoveHandler { + fn get_cursor(&mut self) -> &mut WindowCursor2D; + + // standard handle_EVENT handlers that can be overwritten + fn handle_enter(&mut self) -> Result { + Ok(Transition::None) + } + fn handle_esc(&mut self) -> Transition { + Transition::Exit + } + fn handle_expand(&mut self) -> Transition { + Transition::None + } + fn handle_left(&mut self) { + self.get_cursor().prev_column_i() + } + fn handle_right(&mut self) { + self.get_cursor().next_column_i() + } + fn handle_up(&mut self) { + self.get_cursor().prev_row_i() + } + fn handle_down(&mut self) { + self.get_cursor().next_row_i() + } + fn handle_transpose(&mut self) -> Transition { + Transition::None + } + + // top-level event handler should not be overwritten + fn handle_input_key(&mut self, key: &KeyEvent) -> Result<(Transition, StatusTopOrEnd)> { + let key_combo_status = match key { + // PageUp supports Vi (Ctrl+b) and Emacs (Alt+v) keybindings + KeyEvent { + modifiers: KeyModifiers::CONTROL, + code: KeyCode::Char('b'), + .. + } + | KeyEvent { + modifiers: KeyModifiers::ALT, + code: KeyCode::Char('v'), + .. + } + | KeyEvent { + code: KeyCode::PageUp, + .. + } => { + self.get_cursor().prev_row_page(); + StatusTopOrEnd::Top + } + // PageDown supports Vi (Ctrl+f) and Emacs (Ctrl+v) keybindings + KeyEvent { + modifiers: KeyModifiers::CONTROL, + code: KeyCode::Char('f'), + .. + } + | KeyEvent { + modifiers: KeyModifiers::CONTROL, + code: KeyCode::Char('v'), + .. + } + | KeyEvent { + code: KeyCode::PageDown, + .. + } => { + self.get_cursor().next_row_page(); + self.get_cursor().prev_row(); + StatusTopOrEnd::End + } + // Up support Emacs (Ctrl+p) keybinding + KeyEvent { + modifiers: KeyModifiers::CONTROL, + code: KeyCode::Char('p'), + .. + } => { + self.handle_up(); + StatusTopOrEnd::Top + } + // Down support Emacs (Ctrl+n) keybinding + KeyEvent { + modifiers: KeyModifiers::CONTROL, + code: KeyCode::Char('n'), + .. + } => { + self.handle_down(); + StatusTopOrEnd::End + } + _ => StatusTopOrEnd::None, + }; + match key_combo_status { + StatusTopOrEnd::Top | StatusTopOrEnd::End => { + return Ok((Transition::Ok, key_combo_status)); + } + _ => {} // not page up or page down, so don't return; continue to next match block + } + + match key.code { + KeyCode::Char('q') | KeyCode::Esc => Ok((self.handle_esc(), StatusTopOrEnd::None)), + KeyCode::Char('i') | KeyCode::Enter => Ok((self.handle_enter()?, StatusTopOrEnd::None)), + KeyCode::Char('t') => Ok((self.handle_transpose(), StatusTopOrEnd::None)), + KeyCode::Char('e') => Ok((self.handle_expand(), StatusTopOrEnd::None)), + KeyCode::Up | KeyCode::Char('k') => { + self.handle_up(); + Ok((Transition::Ok, StatusTopOrEnd::Top)) + } + KeyCode::Down | KeyCode::Char('j') => { + self.handle_down(); + Ok((Transition::Ok, StatusTopOrEnd::End)) + } + KeyCode::Left | KeyCode::Char('h') => { + self.handle_left(); + Ok((Transition::Ok, StatusTopOrEnd::None)) + } + KeyCode::Right | KeyCode::Char('l') => { + self.handle_right(); + Ok((Transition::Ok, StatusTopOrEnd::None)) + } + KeyCode::Home | KeyCode::Char('g') => { + self.get_cursor().row_move_to_start(); + Ok((Transition::Ok, StatusTopOrEnd::Top)) + } + KeyCode::End | KeyCode::Char('G') => { + self.get_cursor().row_move_to_end(); + Ok((Transition::Ok, StatusTopOrEnd::End)) + } + _ => Ok((Transition::None, StatusTopOrEnd::None)), + } + } +} diff --git a/crates/nu-explore/src/views/mod.rs b/crates/nu-explore/src/views/mod.rs index 589134016b..7147a74807 100644 --- a/crates/nu-explore/src/views/mod.rs +++ b/crates/nu-explore/src/views/mod.rs @@ -89,7 +89,7 @@ pub trait View { layout: &Layout, info: &mut ViewInfo, key: KeyEvent, - ) -> Option; + ) -> Transition; fn show_data(&mut self, _: usize) -> bool { false @@ -116,7 +116,7 @@ impl View for Box { layout: &Layout, info: &mut ViewInfo, key: KeyEvent, - ) -> Option { + ) -> Transition { self.as_mut() .handle_input(engine_state, stack, layout, info, key) } diff --git a/crates/nu-explore/src/views/preview.rs b/crates/nu-explore/src/views/preview.rs index de4d710113..6b62bc0d0f 100644 --- a/crates/nu-explore/src/views/preview.rs +++ b/crates/nu-explore/src/views/preview.rs @@ -1,11 +1,12 @@ use super::{ - colored_text_widget::ColoredTextWidget, cursor::WindowCursor2D, Layout, View, ViewConfig, + colored_text_widget::ColoredTextWidget, cursor::CursorMoveHandler, cursor::WindowCursor2D, + Layout, View, ViewConfig, }; use crate::{ nu_common::{NuSpan, NuText}, - pager::{report::Report, Frame, Transition, ViewInfo}, + pager::{report::Report, Frame, StatusTopOrEnd, Transition, ViewInfo}, }; -use crossterm::event::{KeyCode, KeyEvent}; +use crossterm::event::KeyEvent; use nu_color_config::TextStyle; use nu_protocol::{ engine::{EngineState, Stack}, @@ -65,58 +66,17 @@ impl View for Preview { _: &Layout, info: &mut ViewInfo, // add this arg to draw too? key: KeyEvent, - ) -> Option { - match key.code { - KeyCode::Left => { - self.cursor - .prev_column_by(max(1, self.cursor.window_width_in_columns() / 2)); - - Some(Transition::Ok) + ) -> Transition { + match self.handle_input_key(&key) { + Ok((transition, status_top_or_end)) => { + match status_top_or_end { + StatusTopOrEnd::Top => set_status_top(self, info), + StatusTopOrEnd::End => set_status_end(self, info), + _ => {} + } + transition } - KeyCode::Right => { - self.cursor - .next_column_by(max(1, self.cursor.window_width_in_columns() / 2)); - - Some(Transition::Ok) - } - KeyCode::Up => { - self.cursor.prev_row_i(); - set_status_top(self, info); - - Some(Transition::Ok) - } - KeyCode::Down => { - self.cursor.next_row_i(); - set_status_end(self, info); - - Some(Transition::Ok) - } - KeyCode::PageUp => { - self.cursor.prev_row_page(); - set_status_top(self, info); - - Some(Transition::Ok) - } - KeyCode::PageDown => { - self.cursor.next_row_page(); - set_status_end(self, info); - - Some(Transition::Ok) - } - KeyCode::Home => { - self.cursor.row_move_to_start(); - set_status_top(self, info); - - Some(Transition::Ok) - } - KeyCode::End => { - self.cursor.row_move_to_end(); - set_status_end(self, info); - - Some(Transition::Ok) - } - KeyCode::Esc => Some(Transition::Exit), - _ => None, + _ => Transition::None, // currently only handle_enter() in crates/nu-explore/src/views/record/mod.rs raises an Err() } } @@ -147,6 +107,20 @@ impl View for Preview { } } +impl CursorMoveHandler for Preview { + fn get_cursor(&mut self) -> &mut WindowCursor2D { + &mut self.cursor + } + fn handle_left(&mut self) { + self.cursor + .prev_column_by(max(1, self.cursor.window_width_in_columns() / 2)); + } + fn handle_right(&mut self) { + self.cursor + .next_column_by(max(1, self.cursor.window_width_in_columns() / 2)); + } +} + fn set_status_end(view: &Preview, info: &mut ViewInfo) { if view.cursor.row() + 1 == view.cursor.row_limit() { info.status = Some(Report::info("END")); diff --git a/crates/nu-explore/src/views/record/mod.rs b/crates/nu-explore/src/views/record/mod.rs index a4e86a8fad..e003348258 100644 --- a/crates/nu-explore/src/views/record/mod.rs +++ b/crates/nu-explore/src/views/record/mod.rs @@ -2,7 +2,7 @@ mod table_widget; use self::table_widget::{TableWidget, TableWidgetState}; use super::{ - cursor::{Position, WindowCursor2D}, + cursor::{CursorMoveHandler, Position, WindowCursor2D}, util::{make_styled_string, nu_style_to_tui}, Layout, View, ViewConfig, }; @@ -16,7 +16,7 @@ use crate::{ views::ElementInfo, }; use anyhow::Result; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use crossterm::event::KeyEvent; use nu_color_config::StyleComputer; use nu_protocol::{ engine::{EngineState, Stack}, @@ -213,26 +213,21 @@ impl View for RecordView { _: &Layout, info: &mut ViewInfo, key: KeyEvent, - ) -> Option { - let result = match self.mode { - UIMode::View => Ok(handle_key_event_view_mode(self, &key)), - UIMode::Cursor => handle_key_event_cursor_mode(self, &key), - }; - - match result { - Ok(result) => { - if matches!(&result, Some(Transition::Ok) | Some(Transition::Cmd { .. })) { + ) -> Transition { + match self.handle_input_key(&key) { + Ok((transition, ..)) => { + if matches!(&transition, Transition::Ok | Transition::Cmd { .. }) { let report = self.create_records_report(); info.status = Some(report); } - result + transition } Err(e) => { log::error!("Error handling input in RecordView: {e}"); let report = Report::message(e.to_string(), Severity::Err); info.status = Some(report); - None + Transition::None } } } @@ -372,188 +367,94 @@ impl RecordLayer { } } -fn handle_key_event_view_mode(view: &mut RecordView, key: &KeyEvent) -> Option { - match key { - KeyEvent { - code: KeyCode::Char('u'), - modifiers: KeyModifiers::CONTROL, - .. - } - | KeyEvent { - code: KeyCode::PageUp, - .. - } => { - view.get_top_layer_mut().cursor.prev_row_page(); - - return Some(Transition::Ok); - } - KeyEvent { - code: KeyCode::Char('d'), - modifiers: KeyModifiers::CONTROL, - .. - } - | KeyEvent { - code: KeyCode::PageDown, - .. - } => { - view.get_top_layer_mut().cursor.next_row_page(); - - return Some(Transition::Ok); - } - _ => {} +impl CursorMoveHandler for RecordView { + fn get_cursor(&mut self) -> &mut WindowCursor2D { + &mut self.get_top_layer_mut().cursor } + fn handle_enter(&mut self) -> Result { + match self.mode { + UIMode::View => self.set_cursor_mode(), + UIMode::Cursor => { + let value = self.get_current_value(); - match key.code { - KeyCode::Esc => { - if view.layer_stack.len() > 1 { - view.layer_stack.pop(); - view.mode = UIMode::Cursor; + // ...but it only makes sense to drill down into a few types of values + if !matches!( + value, + Value::Record { .. } | Value::List { .. } | Value::Custom { .. } + ) { + return Ok(Transition::None); + } - Some(Transition::Ok) - } else { - Some(Transition::Exit) + let is_record = matches!(value, Value::Record { .. }); + let next_layer = create_layer(value.clone())?; + push_layer(self, next_layer); + + if is_record { + self.set_top_layer_orientation(Orientation::Left); + } else { + self.set_top_layer_orientation(self.orientation); + } } } - KeyCode::Char('i') | KeyCode::Enter => { - view.set_cursor_mode(); - Some(Transition::Ok) - } - KeyCode::Char('t') => { - view.transpose(); - - Some(Transition::Ok) - } - KeyCode::Char('e') => Some(Transition::Cmd(String::from("expand"))), - KeyCode::Up | KeyCode::Char('k') => { - view.get_top_layer_mut().cursor.prev_row_i(); - - Some(Transition::Ok) - } - KeyCode::Down | KeyCode::Char('j') => { - view.get_top_layer_mut().cursor.next_row_i(); - - Some(Transition::Ok) - } - KeyCode::Left | KeyCode::Char('h') => { - view.get_top_layer_mut().cursor.prev_column_i(); - - Some(Transition::Ok) - } - KeyCode::Right | KeyCode::Char('l') => { - view.get_top_layer_mut().cursor.next_column_i(); - - Some(Transition::Ok) - } - KeyCode::Home | KeyCode::Char('g') => { - view.get_top_layer_mut().cursor.row_move_to_start(); - - Some(Transition::Ok) - } - KeyCode::End | KeyCode::Char('G') => { - view.get_top_layer_mut().cursor.row_move_to_end(); - - Some(Transition::Ok) - } - _ => None, + Ok(Transition::Ok) } -} - -fn handle_key_event_cursor_mode( - view: &mut RecordView, - key: &KeyEvent, -) -> Result> { - match key { - KeyEvent { - code: KeyCode::Char('u'), - modifiers: KeyModifiers::CONTROL, - .. + fn handle_esc(&mut self) -> Transition { + match self.mode { + UIMode::View => { + if self.layer_stack.len() > 1 { + self.layer_stack.pop(); + self.mode = UIMode::Cursor; + } else { + return Transition::Exit; + } + } + UIMode::Cursor => self.set_view_mode(), } - | KeyEvent { - code: KeyCode::PageUp, - .. - } => { - view.get_top_layer_mut().cursor.prev_row_page(); - - return Ok(Some(Transition::Ok)); - } - KeyEvent { - code: KeyCode::Char('d'), - modifiers: KeyModifiers::CONTROL, - .. - } - | KeyEvent { - code: KeyCode::PageDown, - .. - } => { - view.get_top_layer_mut().cursor.next_row_page(); - - return Ok(Some(Transition::Ok)); - } - _ => {} + Transition::Ok } - - match key.code { - KeyCode::Esc => { - view.set_view_mode(); - - Ok(Some(Transition::Ok)) + fn handle_expand(&mut self) -> Transition { + match self.mode { + UIMode::View => Transition::Cmd(String::from("expand")), + _ => Transition::None, } - KeyCode::Up | KeyCode::Char('k') => { - view.get_top_layer_mut().cursor.prev_row(); + } + fn handle_transpose(&mut self) -> Transition { + match self.mode { + UIMode::View => { + self.transpose(); - Ok(Some(Transition::Ok)) - } - KeyCode::Down | KeyCode::Char('j') => { - view.get_top_layer_mut().cursor.next_row(); - - Ok(Some(Transition::Ok)) - } - KeyCode::Left | KeyCode::Char('h') => { - view.get_top_layer_mut().cursor.prev_column(); - - Ok(Some(Transition::Ok)) - } - KeyCode::Right | KeyCode::Char('l') => { - view.get_top_layer_mut().cursor.next_column(); - - Ok(Some(Transition::Ok)) - } - KeyCode::Home | KeyCode::Char('g') => { - view.get_top_layer_mut().cursor.row_move_to_start(); - - Ok(Some(Transition::Ok)) - } - KeyCode::End | KeyCode::Char('G') => { - view.get_top_layer_mut().cursor.row_move_to_end(); - - Ok(Some(Transition::Ok)) - } - // Try to "drill down" into the selected value - KeyCode::Enter => { - let value = view.get_current_value(); - - // ...but it only makes sense to drill down into a few types of values - if !matches!( - value, - Value::Record { .. } | Value::List { .. } | Value::Custom { .. } - ) { - return Ok(None); + Transition::Ok } - - let is_record = matches!(value, Value::Record { .. }); - let next_layer = create_layer(value.clone())?; - push_layer(view, next_layer); - - if is_record { - view.set_top_layer_orientation(Orientation::Left); - } else { - view.set_top_layer_orientation(view.orientation); - } - - Ok(Some(Transition::Ok)) + _ => Transition::None, + } + } + // for these, copy standard CursorMoveHandler for UIMode::View, but use special handling for UIMode::Cursor + // NOTE: https://stackoverflow.com/a/31462293/2016290 says there's plans for Rust to allow calling super functions, + // but not yet, and since they're all one line, it seems simpler to copy than make a lot of helper functions + fn handle_left(&mut self) { + match self.mode { + UIMode::View => self.get_top_layer_mut().cursor.prev_column_i(), + _ => self.get_top_layer_mut().cursor.prev_column(), + } + } + fn handle_right(&mut self) { + match self.mode { + UIMode::View => self.get_top_layer_mut().cursor.next_column_i(), + _ => self.get_top_layer_mut().cursor.next_column(), + } + } + fn handle_up(&mut self) { + match self.mode { + UIMode::View => self.get_top_layer_mut().cursor.prev_row_i(), + _ => self.get_top_layer_mut().cursor.prev_row(), + } + } + fn handle_down(&mut self) { + match self.mode { + UIMode::View => self.get_top_layer_mut().cursor.next_row_i(), + _ => self.get_top_layer_mut().cursor.next_row(), } - _ => Ok(None), } } diff --git a/crates/nu-explore/src/views/try.rs b/crates/nu-explore/src/views/try.rs index 099e792e38..6438909b5b 100644 --- a/crates/nu-explore/src/views/try.rs +++ b/crates/nu-explore/src/views/try.rs @@ -149,7 +149,7 @@ impl View for TryView { layout: &Layout, info: &mut ViewInfo, key: KeyEvent, - ) -> Option { + ) -> Transition { if self.view_mode { let table = self .table @@ -160,28 +160,28 @@ impl View for TryView { if was_at_the_top && matches!(key.code, KeyCode::Up | KeyCode::PageUp) { self.view_mode = false; - return Some(Transition::Ok); + return Transition::Ok; } if matches!(key.code, KeyCode::Tab) { self.view_mode = false; - return Some(Transition::Ok); + return Transition::Ok; } let result = table.handle_input(engine_state, stack, layout, info, key); return match result { - Some(Transition::Ok | Transition::Cmd { .. }) => Some(Transition::Ok), - Some(Transition::Exit) => { + Transition::Ok | Transition::Cmd { .. } => Transition::Ok, + Transition::Exit => { self.view_mode = false; - Some(Transition::Ok) + Transition::Ok } - None => None, + Transition::None => Transition::None, }; } match &key.code { - KeyCode::Esc => Some(Transition::Exit), + KeyCode::Esc => Transition::Exit, KeyCode::Backspace => { if !self.command.is_empty() { self.command.pop(); @@ -194,7 +194,7 @@ impl View for TryView { } } - Some(Transition::Ok) + Transition::Ok } KeyCode::Char(c) => { self.command.push(*c); @@ -206,14 +206,14 @@ impl View for TryView { } } - Some(Transition::Ok) + Transition::Ok } KeyCode::Down | KeyCode::Tab => { if self.table.is_some() { self.view_mode = true; } - Some(Transition::Ok) + Transition::Ok } KeyCode::Enter => { match self.try_run(engine_state, stack) { @@ -221,9 +221,9 @@ impl View for TryView { Err(err) => info.report = Some(Report::error(format!("Error: {err}"))), } - Some(Transition::Ok) + Transition::Ok } - _ => None, + _ => Transition::None, } }