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<Transition> 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>
This commit is contained in:
paulie4 2024-11-30 09:22:52 -05:00 committed by GitHub
parent 3d5f853b03
commit 88d27fd607
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 324 additions and 370 deletions

View File

@ -33,13 +33,14 @@ Launch Explore by piping data into it: {}
Move around: Use the cursor keys
Drill down into records+tables: Press <Enter> to select a cell, move around with cursor keys, press <Enter> again
Go back/up a level: Press <Esc>
Go back/up a level: Press <Esc> or "q"
Transpose (flip rows+columns): Press "t"
Expand (show all nested data): Press "e"
Open this help page : Type ":help" then <Enter>
Open an interactive REPL: Type ":try" then <Enter>
Scroll up/down: Use the "Page Up" and "Page Down" keys
Exit Explore: Type ":q" then <Enter>, or Ctrl+D. Alternately, press <Esc> 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 <Enter>, or Ctrl+D. Alternately, press <Esc> or "q" until Explore exits
{}
Most commands support search via regular expressions.

View File

@ -93,7 +93,7 @@ impl View for NuView {
layout: &Layout,
info: &mut crate::pager::ViewInfo,
key: crossterm::event::KeyEvent,
) -> Option<crate::pager::Transition> {
) -> 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),

View File

@ -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<Transition> 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<V: View>(
search: &mut SearchBuf,
command: &mut CommandBuf,
mut view: Option<&mut V>,
) -> Option<Transition> {
) -> 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<V: View>(
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<V: View>(
command: &mut CommandBuf,
mut view: Option<&mut V>,
key: KeyEvent,
) -> Option<Transition> {
) -> 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 {

View File

@ -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<Transition> {
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<NuText> {
@ -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<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 {

View File

@ -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
///

View File

@ -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<Transition> {
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)),
}
}
}

View File

@ -89,7 +89,7 @@ pub trait View {
layout: &Layout,
info: &mut ViewInfo,
key: KeyEvent,
) -> Option<Transition>;
) -> Transition;
fn show_data(&mut self, _: usize) -> bool {
false
@ -116,7 +116,7 @@ impl View for Box<dyn View> {
layout: &Layout,
info: &mut ViewInfo,
key: KeyEvent,
) -> Option<Transition> {
) -> Transition {
self.as_mut()
.handle_input(engine_state, stack, layout, info, key)
}

View File

@ -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<Transition> {
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"));

View File

@ -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<Transition> {
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<Transition> {
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<Transition> {
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<Option<Transition>> {
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),
}
}

View File

@ -149,7 +149,7 @@ impl View for TryView {
layout: &Layout,
info: &mut ViewInfo,
key: KeyEvent,
) -> Option<Transition> {
) -> 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,
}
}