mirror of
https://github.com/nushell/nushell.git
synced 2024-12-22 15:13:01 +01:00
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:
parent
3d5f853b03
commit
88d27fd607
@ -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.
|
||||
|
@ -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),
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
///
|
||||
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"));
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user