Skip to content

Commit

Permalink
explore: add more less key bindings and add Transition::None (#…
Browse files Browse the repository at this point in the history
…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 <[email protected]>
Co-authored-by: Darren Schroeder <[email protected]>
  • Loading branch information
3 people authored Nov 30, 2024
1 parent 3d5f853 commit 88d27fd
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 368 deletions.
7 changes: 4 additions & 3 deletions crates/nu-explore/src/commands/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion crates/nu-explore/src/commands/nu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
87 changes: 47 additions & 40 deletions crates/nu-explore/src/pager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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,
);

if let Some(value) = exit {
break Ok(value);
}
let (exit, cmd_name) = react_to_event_result(
transition,
engine_state,
&commands,
pager,
&mut view_stack,
stack,
info,
);

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 let Some(value) = exit {
break Ok(value);
}

let info = info.clone();
term.draw(|f| {
draw_info(f, pager, info);
})?;
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);
})?;
}

if pager.cmd_buf.run_cmd {
Expand Down Expand Up @@ -319,6 +324,7 @@ fn react_to_event_result(
}
}
}
Transition::None => (None, String::default()),
}
}

Expand Down Expand Up @@ -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 } => {
Expand Down Expand Up @@ -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;
}
};

Expand All @@ -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;
}
}
}
Expand All @@ -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 {
Expand Down
86 changes: 12 additions & 74 deletions crates/nu-explore/src/views/binary/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

mod binary_widget;

use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use crossterm::event::KeyEvent;
use nu_protocol::{
engine::{EngineState, Stack},
Value,
Expand All @@ -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.
Expand Down Expand Up @@ -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> {
Expand All @@ -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 =
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion crates/nu-explore/src/views/cursor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand Down
Loading

0 comments on commit 88d27fd

Please sign in to comment.