From 3d340657b5b155334c130b6cbd1a080fc6358415 Mon Sep 17 00:00:00 2001 From: Reilly Wood <26268125+rgwood@users.noreply.github.com> Date: Wed, 1 May 2024 15:34:37 -0700 Subject: [PATCH] `explore`: adopt `anyhow`, support `CustomValue`, remove help system (#12692) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR: 1. Adds basic support for `CustomValue` to `explore`. Previously `open foo.db | explore` didn't really work, now we "materialize" the whole database to a `Value` before loading it 2. Adopts `anyhow` for error handling in `explore`. Previously we were kind of rolling our own version of `anyhow` by shoving all errors into a `std::io::Error`; I think this is much nicer. This was necessary because as part of 1), collecting input is now fallible... 3. Removes a lot of `explore`'s fancy command help system. - Previously each command (`:help`, `:try`, etc.) had a sophisticated help system with examples etc... but this was not very visible to users. You had to know to run `:help :try` or view a list of commands with `:help :` - As discussed previously, we eventually want to move to a less modal approach for `explore`, without the Vim-like commands. And so I don't think it's worth keeping this command help system around (it's intertwined with other stuff, and making these changes would have been harder if keeping it). 4. Rename the `--reverse` flag to `--tail`. The flag scrolls to the end of the data, which IMO is described better by "tail" 5. Does some renaming+commenting to clear up things I found difficult to understand when navigating the `explore` code I initially thought 1) would be just a few lines, and then this PR blew up into much more extensive changes 😅 ## Before The whole database was being displayed as a single Nuon/JSON line 🤔 ![image](https://github.com/nushell/nushell/assets/26268125/6383f43b-fdff-48b4-9604-398438ad1499) ## After The database gets displayed like a record ![image](https://github.com/nushell/nushell/assets/26268125/2f00ed7b-a3c4-47f4-a08c-98d07efc7bb4) ## Future work It is sort of annoying that we have to load a whole SQLite database into memory to make this work; it will be impractical for large databases. I'd like to explore improvements to `CustomValue` that can make this work more efficiently. --- Cargo.lock | 11 +- Cargo.toml | 1 + crates/nu-explore/Cargo.toml | 3 + crates/nu-explore/src/commands/expand.rs | 55 ++- crates/nu-explore/src/commands/help.rs | 388 ++++----------------- crates/nu-explore/src/commands/mod.rs | 113 +----- crates/nu-explore/src/commands/nu.rs | 33 +- crates/nu-explore/src/commands/quit.rs | 15 +- crates/nu-explore/src/commands/table.rs | 63 +--- crates/nu-explore/src/commands/try.rs | 38 +- crates/nu-explore/src/explore.rs | 30 +- crates/nu-explore/src/lib.rs | 79 ++--- crates/nu-explore/src/nu_common/value.rs | 38 +- crates/nu-explore/src/pager/mod.rs | 77 ++-- crates/nu-explore/src/registry/command.rs | 19 +- crates/nu-explore/src/registry/mod.rs | 7 +- crates/nu-explore/src/views/information.rs | 77 ---- crates/nu-explore/src/views/interactive.rs | 10 +- crates/nu-explore/src/views/mod.rs | 2 - crates/nu-explore/src/views/record/mod.rs | 66 ++-- foo.db | Bin 0 -> 20480 bytes 21 files changed, 283 insertions(+), 842 deletions(-) delete mode 100644 crates/nu-explore/src/views/information.rs create mode 100644 foo.db diff --git a/Cargo.lock b/Cargo.lock index 83ddd8ceba..059fcf35b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -165,6 +165,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" + [[package]] name = "arboard" version = "3.3.2" @@ -398,7 +404,7 @@ dependencies = [ "bitflags 2.5.0", "cexpr", "clang-sys", - "itertools 0.11.0", + "itertools 0.12.1", "lazy_static", "lazycell", "proc-macro2", @@ -3158,7 +3164,9 @@ name = "nu-explore" version = "0.93.1" dependencies = [ "ansi-str", + "anyhow", "crossterm", + "log", "lscolors", "nu-ansi-term", "nu-color-config", @@ -3169,6 +3177,7 @@ dependencies = [ "nu-protocol", "nu-table", "nu-utils", + "once_cell", "ratatui", "strip-ansi-escapes", "terminal_size", diff --git a/Cargo.toml b/Cargo.toml index fe7b7e0fac..22d9b876bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ members = [ [workspace.dependencies] alphanumeric-sort = "1.5" ansi-str = "0.8" +anyhow = "1.0.82" base64 = "0.22" bracoxide = "0.1.2" brotli = "5.0" diff --git a/crates/nu-explore/Cargo.toml b/crates/nu-explore/Cargo.toml index e5791dd9e3..46756e4101 100644 --- a/crates/nu-explore/Cargo.toml +++ b/crates/nu-explore/Cargo.toml @@ -21,9 +21,12 @@ nu-utils = { path = "../nu-utils", version = "0.93.1" } nu-ansi-term = { workspace = true } nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.93.1" } +anyhow = { workspace = true } +log = { workspace = true } terminal_size = { workspace = true } strip-ansi-escapes = { workspace = true } crossterm = { workspace = true } +once_cell = { workspace = true } ratatui = { workspace = true } ansi-str = { workspace = true } unicode-width = { workspace = true } diff --git a/crates/nu-explore/src/commands/expand.rs b/crates/nu-explore/src/commands/expand.rs index c9c22fbdb5..d7d75af6c5 100644 --- a/crates/nu-explore/src/commands/expand.rs +++ b/crates/nu-explore/src/commands/expand.rs @@ -1,14 +1,14 @@ -use super::{HelpManual, Shortcode, ViewCommand}; +use super::ViewCommand; use crate::{ nu_common::{self, collect_input}, views::Preview, }; +use anyhow::Result; use nu_color_config::StyleComputer; use nu_protocol::{ engine::{EngineState, Stack}, Value, }; -use std::{io::Result, vec}; #[derive(Default, Clone)] pub struct ExpandCmd; @@ -34,29 +34,6 @@ impl ViewCommand for ExpandCmd { "" } - fn help(&self) -> Option { - #[rustfmt::skip] - let shortcodes = vec![ - Shortcode::new("Up", "", "Moves the viewport one row up"), - Shortcode::new("Down", "", "Moves the viewport one row down"), - Shortcode::new("Left", "", "Moves the viewport one column left"), - Shortcode::new("Right", "", "Moves the viewport one column right"), - Shortcode::new("PgDown", "", "Moves the viewport one page of rows down"), - Shortcode::new("PgUp", "", "Moves the cursor or viewport one page of rows up"), - Shortcode::new("Esc", "", "Exits cursor mode. Exits the currently explored data."), - ]; - - Some(HelpManual { - name: "expand", - description: - "View the currently selected cell's data using the `table` Nushell command", - arguments: vec![], - examples: vec![], - config_options: vec![], - input: shortcodes, - }) - } - fn parse(&mut self, _: &str) -> Result<()> { Ok(()) } @@ -67,27 +44,37 @@ impl ViewCommand for ExpandCmd { stack: &mut Stack, value: Option, ) -> Result { - let value = value - .map(|v| convert_value_to_string(v, engine_state, stack)) - .unwrap_or_default(); - - Ok(Preview::new(&value)) + if let Some(value) = value { + let value_as_string = convert_value_to_string(value, engine_state, stack)?; + Ok(Preview::new(&value_as_string)) + } else { + Ok(Preview::new("")) + } } } -fn convert_value_to_string(value: Value, engine_state: &EngineState, stack: &mut Stack) -> String { - let (cols, vals) = collect_input(value.clone()); +fn convert_value_to_string( + value: Value, + engine_state: &EngineState, + stack: &mut Stack, +) -> Result { + let (cols, vals) = collect_input(value.clone())?; let has_no_head = cols.is_empty() || (cols.len() == 1 && cols[0].is_empty()); let has_single_value = vals.len() == 1 && vals[0].len() == 1; if !has_no_head && has_single_value { let config = engine_state.get_config(); - vals[0][0].to_abbreviated_string(config) + Ok(vals[0][0].to_abbreviated_string(config)) } else { let ctrlc = engine_state.ctrlc.clone(); let config = engine_state.get_config(); let style_computer = StyleComputer::from_config(engine_state, stack); - nu_common::try_build_table(ctrlc, config, &style_computer, value) + Ok(nu_common::try_build_table( + ctrlc, + config, + &style_computer, + value, + )) } } diff --git a/crates/nu-explore/src/commands/help.rs b/crates/nu-explore/src/commands/help.rs index 09be8e3939..8be468383e 100644 --- a/crates/nu-explore/src/commands/help.rs +++ b/crates/nu-explore/src/commands/help.rs @@ -1,76 +1,91 @@ -use super::{HelpExample, HelpManual, ViewCommand}; -use crate::{ - nu_common::{collect_input, NuSpan}, - pager::{Frame, Transition, ViewInfo}, - views::{Layout, Preview, RecordView, View, ViewConfig}, -}; -use crossterm::event::KeyEvent; +use super::ViewCommand; +use crate::views::Preview; +use anyhow::Result; +use nu_ansi_term::Color; use nu_protocol::{ engine::{EngineState, Stack}, - record, Value, -}; -use ratatui::layout::Rect; -use std::{ - collections::HashMap, - io::{self, Result}, + Value, }; +use once_cell::sync::Lazy; + #[derive(Debug, Default, Clone)] -pub struct HelpCmd { - input_command: String, - supported_commands: Vec, - aliases: HashMap>, -} +pub struct HelpCmd {} impl HelpCmd { pub const NAME: &'static str = "help"; - - const HELP_MESSAGE: &'static str = r#" Explore - main help file - - Move around: Use the cursor keys. - Close help: Press "". - Exit Explore: Type ":q" then then (or press Ctrl+D). - Open an interactive REPL: Type ":try" then enter - List all sub-commands: Type ":help :" then - ------------------------------------------------------------------------------------- - -# Regular expressions - -Most commands support regular expressions. - -You can type "/" and type a pattern you want to search on. -Then hit and you will see the search results. - -To go to the next hit use "" key. - -You also can do a reverse search by using "?" instead of "/". -"#; - - pub fn new(commands: Vec, aliases: &[(&str, &str)]) -> Self { - let aliases = collect_aliases(aliases); - - Self { - input_command: String::new(), - supported_commands: commands, - aliases, - } + pub fn view() -> Preview { + Preview::new(&HELP_MESSAGE) } } -fn collect_aliases(aliases: &[(&str, &str)]) -> HashMap> { - let mut out_aliases: HashMap> = HashMap::new(); - for (name, cmd) in aliases { - out_aliases - .entry(cmd.to_string()) - .and_modify(|list| list.push(name.to_string())) - .or_insert_with(|| vec![name.to_string()]); - } - out_aliases -} +static HELP_MESSAGE: Lazy = Lazy::new(|| { + let title = nu_ansi_term::Style::new().bold().underline(); + let code = nu_ansi_term::Style::new().bold().fg(Color::Blue); + + // There is probably a nicer way to do this formatting inline + format!( + r#"{} +Explore helps you dynamically navigate through your data! + +{} +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 + 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 + +{} +Most commands support search via regular expressions. + +You can type "/" and type a pattern you want to search on. Then hit and you will see the search results. + +To go to the next hit use "" key. You also can do a reverse search by using "?" instead of "/". +"#, + title.paint("Explore"), + title.paint("Basics"), + code.paint("ls | explore"), + title.paint("Search") + ) +}); + +// TODO: search help could use some updating... search results get shown immediately after typing, don't need to press Enter +// const HELP_MESSAGE: &str = r#"# Explore + +// Explore helps you dynamically navigate through your data + +// ## Basics + +// Move around: Use the cursor keys +// Drill down into records+tables: Press to select a cell, move around with cursor keys, then press again +// Go back/up a level: Press +// Transpose data (flip rows and columns): Press "t" +// Expand data (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 + +// ## Search + +// Most commands support search via regular expressions. + +// You can type "/" and type a pattern you want to search on. +// Then hit and you will see the search results. + +// To go to the next hit use "" key. + +// You also can do a reverse search by using "?" instead of "/". +// "#; impl ViewCommand for HelpCmd { - type View = HelpView<'static>; + type View = Preview; fn name(&self) -> &'static str { Self::NAME @@ -80,260 +95,11 @@ impl ViewCommand for HelpCmd { "" } - fn help(&self) -> Option { - #[rustfmt::skip] - let examples = vec![ - HelpExample::new("help", "Open the help page for all of `explore`"), - HelpExample::new("help :nu", "Open the help page for the `nu` explore command"), - HelpExample::new("help :help", "...It was supposed to be hidden....until...now..."), - ]; - - #[rustfmt::skip] - let arguments = vec![ - HelpExample::new("help :command", "you can provide a command and a help information for it will be displayed") - ]; - - Some(HelpManual { - name: "help", - description: "Explore the help page for `explore`", - arguments, - examples, - input: vec![], - config_options: vec![], - }) - } - - fn parse(&mut self, args: &str) -> Result<()> { - args.trim().clone_into(&mut self.input_command); - + fn parse(&mut self, _: &str) -> Result<()> { Ok(()) } fn spawn(&mut self, _: &EngineState, _: &mut Stack, _: Option) -> Result { - if self.input_command.is_empty() { - return Ok(HelpView::Preview(Preview::new(Self::HELP_MESSAGE))); - } - - if !self.input_command.starts_with(':') { - return Err(io::Error::new( - io::ErrorKind::Other, - "unexpected help argument", - )); - } - - if self.input_command == ":" { - let (headers, data) = help_frame_data(&self.supported_commands, &self.aliases); - let view = RecordView::new(headers, data); - return Ok(HelpView::Records(view)); - } - - let command = self - .input_command - .strip_prefix(':') - .expect("we just checked the prefix"); - - let manual = self - .supported_commands - .iter() - .find(|manual| manual.name == command) - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "a given command was not found"))?; - - let aliases = self - .aliases - .get(manual.name) - .map(|l| l.as_slice()) - .unwrap_or(&[]); - let (headers, data) = help_manual_data(manual, aliases); - let view = RecordView::new(headers, data); - - Ok(HelpView::Records(view)) - } -} - -fn help_frame_data( - supported_commands: &[HelpManual], - aliases: &HashMap>, -) -> (Vec, Vec>) { - let commands = supported_commands - .iter() - .map(|manual| { - let aliases = aliases - .get(manual.name) - .map(|l| l.as_slice()) - .unwrap_or(&[]); - - let (cols, mut vals) = help_manual_data(manual, aliases); - let vals = vals.remove(0); - Value::record(cols.into_iter().zip(vals).collect(), NuSpan::unknown()) - }) - .collect(); - let commands = Value::list(commands, NuSpan::unknown()); - - collect_input(commands) -} - -fn help_manual_data(manual: &HelpManual, aliases: &[String]) -> (Vec, Vec>) { - fn nu_str(s: &impl ToString) -> Value { - Value::string(s.to_string(), NuSpan::unknown()) - } - - let arguments = manual - .arguments - .iter() - .map(|e| { - Value::record( - record! { - "example" => nu_str(&e.example), - "description" => nu_str(&e.description), - }, - NuSpan::unknown(), - ) - }) - .collect(); - - let arguments = Value::list(arguments, NuSpan::unknown()); - - let examples = manual - .examples - .iter() - .map(|e| { - Value::record( - record! { - "example" => nu_str(&e.example), - "description" => nu_str(&e.description), - }, - NuSpan::unknown(), - ) - }) - .collect(); - let examples = Value::list(examples, NuSpan::unknown()); - - let inputs = manual - .input - .iter() - .map(|e| { - Value::record( - record! { - "name" => nu_str(&e.code), - "context" => nu_str(&e.context), - "description" => nu_str(&e.description), - }, - NuSpan::unknown(), - ) - }) - .collect(); - let inputs = Value::list(inputs, NuSpan::unknown()); - - let configuration = manual - .config_options - .iter() - .map(|o| { - let values = o - .values - .iter() - .map(|v| { - Value::record( - record! { - "example" => nu_str(&v.example), - "description" => nu_str(&v.description), - }, - NuSpan::unknown(), - ) - }) - .collect(); - let values = Value::list(values, NuSpan::unknown()); - - Value::record( - record! { - "name" => nu_str(&o.group), - "context" => nu_str(&o.key), - "description" => nu_str(&o.description), - "values" => values, - }, - NuSpan::unknown(), - ) - }) - .collect(); - let configuration = Value::list(configuration, NuSpan::unknown()); - - let name = nu_str(&manual.name); - let aliases = nu_str(&aliases.join(", ")); - let desc = nu_str(&manual.description); - - let headers = vec![ - String::from("name"), - String::from("aliases"), - String::from("arguments"), - String::from("input"), - String::from("examples"), - String::from("configuration"), - String::from("description"), - ]; - - let data = vec![vec![ - name, - aliases, - arguments, - inputs, - examples, - configuration, - desc, - ]]; - - (headers, data) -} -pub enum HelpView<'a> { - Records(RecordView<'a>), - Preview(Preview), -} - -impl View for HelpView<'_> { - fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) { - match self { - HelpView::Records(v) => v.draw(f, area, cfg, layout), - HelpView::Preview(v) => v.draw(f, area, cfg, layout), - } - } - - fn handle_input( - &mut self, - engine_state: &EngineState, - stack: &mut Stack, - layout: &Layout, - info: &mut ViewInfo, - key: KeyEvent, - ) -> Option { - match self { - HelpView::Records(v) => v.handle_input(engine_state, stack, layout, info, key), - HelpView::Preview(v) => v.handle_input(engine_state, stack, layout, info, key), - } - } - - fn show_data(&mut self, i: usize) -> bool { - match self { - HelpView::Records(v) => v.show_data(i), - HelpView::Preview(v) => v.show_data(i), - } - } - - fn collect_data(&self) -> Vec { - match self { - HelpView::Records(v) => v.collect_data(), - HelpView::Preview(v) => v.collect_data(), - } - } - - fn exit(&mut self) -> Option { - match self { - HelpView::Records(v) => v.exit(), - HelpView::Preview(v) => v.exit(), - } - } - - fn setup(&mut self, config: ViewConfig<'_>) { - match self { - HelpView::Records(v) => v.setup(config), - HelpView::Preview(v) => v.setup(config), - } + Ok(HelpCmd::view()) } } diff --git a/crates/nu-explore/src/commands/mod.rs b/crates/nu-explore/src/commands/mod.rs index 3755746127..89a528a254 100644 --- a/crates/nu-explore/src/commands/mod.rs +++ b/crates/nu-explore/src/commands/mod.rs @@ -1,9 +1,9 @@ use super::pager::{Pager, Transition}; +use anyhow::Result; use nu_protocol::{ engine::{EngineState, Stack}, Value, }; -use std::{borrow::Cow, io::Result}; mod expand; mod help; @@ -24,8 +24,6 @@ pub trait SimpleCommand { fn usage(&self) -> &'static str; - fn help(&self) -> Option; - fn parse(&mut self, args: &str) -> Result<()>; fn react( @@ -44,8 +42,6 @@ pub trait ViewCommand { fn usage(&self) -> &'static str; - fn help(&self) -> Option; - fn parse(&mut self, args: &str) -> Result<()>; fn spawn( @@ -56,116 +52,9 @@ pub trait ViewCommand { ) -> Result; } -#[derive(Debug, Default, Clone)] -pub struct HelpManual { - pub name: &'static str, - pub description: &'static str, - pub arguments: Vec, - pub examples: Vec, - pub config_options: Vec, - pub input: Vec, -} - -#[derive(Debug, Default, Clone)] -pub struct HelpExample { - pub example: Cow<'static, str>, - pub description: Cow<'static, str>, -} - -impl HelpExample { - pub fn new( - example: impl Into>, - description: impl Into>, - ) -> Self { - Self { - example: example.into(), - description: description.into(), - } - } -} - #[derive(Debug, Default, Clone)] pub struct Shortcode { pub code: &'static str, pub context: &'static str, pub description: &'static str, } - -impl Shortcode { - pub fn new(code: &'static str, context: &'static str, description: &'static str) -> Self { - Self { - code, - context, - description, - } - } -} - -#[derive(Debug, Default, Clone)] -pub struct ConfigOption { - pub group: String, - pub description: String, - pub key: String, - pub values: Vec, -} - -impl ConfigOption { - pub fn new(group: N, description: D, key: K, values: Vec) -> Self - where - N: Into, - D: Into, - K: Into, - { - Self { - group: group.into(), - description: description.into(), - key: key.into(), - values, - } - } - - pub fn boolean(group: N, description: D, key: K) -> Self - where - N: Into, - D: Into, - K: Into, - { - Self { - group: group.into(), - description: description.into(), - key: key.into(), - values: vec![ - HelpExample::new("true", "Turn the flag on"), - HelpExample::new("false", "Turn the flag on"), - ], - } - } -} - -#[rustfmt::skip] -pub fn default_color_list() -> Vec { - vec![ - HelpExample::new("red", "Red foreground"), - HelpExample::new("blue", "Blue foreground"), - HelpExample::new("green", "Green foreground"), - HelpExample::new("yellow", "Yellow foreground"), - HelpExample::new("magenta", "Magenta foreground"), - HelpExample::new("black", "Black foreground"), - HelpExample::new("white", "White foreground"), - HelpExample::new("#AA4433", "#AA4433 HEX foreground"), - HelpExample::new(r#"{bg: "red"}"#, "Red background"), - HelpExample::new(r#"{bg: "blue"}"#, "Blue background"), - HelpExample::new(r#"{bg: "green"}"#, "Green background"), - HelpExample::new(r#"{bg: "yellow"}"#, "Yellow background"), - HelpExample::new(r#"{bg: "magenta"}"#, "Magenta background"), - HelpExample::new(r#"{bg: "black"}"#, "Black background"), - HelpExample::new(r#"{bg: "white"}"#, "White background"), - HelpExample::new(r##"{bg: "#AA4433"}"##, "#AA4433 HEX background"), - ] -} - -pub fn default_int_list() -> Vec { - (0..20) - .map(|i| HelpExample::new(i.to_string(), format!("A value equal to {i}"))) - .collect() -} diff --git a/crates/nu-explore/src/commands/nu.rs b/crates/nu-explore/src/commands/nu.rs index d7240075db..1310dfbd1f 100644 --- a/crates/nu-explore/src/commands/nu.rs +++ b/crates/nu-explore/src/commands/nu.rs @@ -1,15 +1,15 @@ -use super::{HelpExample, HelpManual, ViewCommand}; +use super::ViewCommand; use crate::{ nu_common::{collect_pipeline, has_simple_value, run_command_with_value}, pager::Frame, views::{Layout, Orientation, Preview, RecordView, View, ViewConfig}, }; +use anyhow::Result; use nu_protocol::{ engine::{EngineState, Stack}, PipelineData, Value, }; use ratatui::layout::Rect; -use std::io::{self, Result}; #[derive(Debug, Default, Clone)] pub struct NuCmd { @@ -37,30 +37,6 @@ impl ViewCommand for NuCmd { "" } - fn help(&self) -> Option { - let examples = vec![ - HelpExample::new( - "where type == 'file'", - "Filter data to show only rows whose type is 'file'", - ), - HelpExample::new( - "get scope.examples", - "Navigate to a deeper value inside the data", - ), - HelpExample::new("open Cargo.toml", "Open a Cargo.toml file"), - ]; - - Some(HelpManual { - name: "nu", - description: - "Run a Nushell command. The data currently being explored is piped into it.", - examples, - arguments: vec![], - input: vec![], - config_options: vec![], - }) - } - fn parse(&mut self, args: &str) -> Result<()> { args.trim().clone_into(&mut self.command); @@ -75,12 +51,11 @@ impl ViewCommand for NuCmd { ) -> Result { let value = value.unwrap_or_default(); - let pipeline = run_command_with_value(&self.command, &value, engine_state, stack) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + let pipeline = run_command_with_value(&self.command, &value, engine_state, stack)?; let is_record = matches!(pipeline, PipelineData::Value(Value::Record { .. }, ..)); - let (columns, values) = collect_pipeline(pipeline); + let (columns, values) = collect_pipeline(pipeline)?; if let Some(value) = has_simple_value(&values) { let text = value.to_abbreviated_string(&engine_state.config); diff --git a/crates/nu-explore/src/commands/quit.rs b/crates/nu-explore/src/commands/quit.rs index 8d232b795b..e93a6a2dd1 100644 --- a/crates/nu-explore/src/commands/quit.rs +++ b/crates/nu-explore/src/commands/quit.rs @@ -1,10 +1,10 @@ -use super::{HelpManual, SimpleCommand}; +use super::SimpleCommand; use crate::pager::{Pager, Transition}; +use anyhow::Result; use nu_protocol::{ engine::{EngineState, Stack}, Value, }; -use std::io::Result; #[derive(Default, Clone)] pub struct QuitCmd; @@ -22,17 +22,6 @@ impl SimpleCommand for QuitCmd { "" } - fn help(&self) -> Option { - Some(HelpManual { - name: "quit", - description: "Quit and return to Nushell", - arguments: vec![], - examples: vec![], - input: vec![], - config_options: vec![], - }) - } - fn parse(&mut self, _: &str) -> Result<()> { Ok(()) } diff --git a/crates/nu-explore/src/commands/table.rs b/crates/nu-explore/src/commands/table.rs index daac313a70..7b778a8971 100644 --- a/crates/nu-explore/src/commands/table.rs +++ b/crates/nu-explore/src/commands/table.rs @@ -1,17 +1,14 @@ -use super::{ - default_color_list, default_int_list, ConfigOption, HelpExample, HelpManual, Shortcode, - ViewCommand, -}; +use super::ViewCommand; use crate::{ nu_common::collect_input, views::{Orientation, RecordView}, }; +use anyhow::Result; use nu_ansi_term::Style; use nu_protocol::{ engine::{EngineState, Stack}, Value, }; -use std::io::Result; #[derive(Debug, Default, Clone)] pub struct TableCmd { @@ -52,60 +49,6 @@ impl ViewCommand for TableCmd { "" } - fn help(&self) -> Option { - #[rustfmt::skip] - let shortcuts = vec![ - Shortcode::new("Up", "", "Moves the cursor or viewport one row up"), - Shortcode::new("Down", "", "Moves the cursor or viewport one row down"), - Shortcode::new("Left", "", "Moves the cursor or viewport one column left"), - Shortcode::new("Right", "", "Moves the cursor or viewport one column right"), - Shortcode::new("PgDown", "view", "Moves the cursor or viewport one page of rows down"), - Shortcode::new("PgUp", "view", "Moves the cursor or viewport one page of rows up"), - Shortcode::new("Esc", "", "Exits cursor mode. Exits the just explored dataset."), - Shortcode::new("i", "view", "Enters cursor mode to inspect individual cells"), - Shortcode::new("t", "view", "Transpose table, so that columns become rows and vice versa"), - Shortcode::new("e", "view", "Open expand view (equivalent of :expand)"), - Shortcode::new("Enter", "cursor", "In cursor mode, explore the data of the selected cell"), - ]; - - #[rustfmt::skip] - let config_options = vec![ - ConfigOption::new( - ":table group", - "Used to move column header", - "table.orientation", - vec![ - HelpExample::new("top", "Sticks column header to the top"), - HelpExample::new("bottom", "Sticks column header to the bottom"), - HelpExample::new("left", "Sticks column header to the left"), - HelpExample::new("right", "Sticks column header to the right"), - ], - ), - ConfigOption::boolean(":table group", "Show index", "table.show_index"), - ConfigOption::boolean(":table group", "Show header", "table.show_head"), - - ConfigOption::new(":table group", "Color of selected cell", "table.selected_cell", default_color_list()), - ConfigOption::new(":table group", "Color of selected row", "table.selected_row", default_color_list()), - ConfigOption::new(":table group", "Color of selected column", "table.selected_column", default_color_list()), - - ConfigOption::new(":table group", "Color of split line", "table.split_line", default_color_list()), - - ConfigOption::new(":table group", "Padding column left", "table.padding_column_left", default_int_list()), - ConfigOption::new(":table group", "Padding column right", "table.padding_column_right", default_int_list()), - ConfigOption::new(":table group", "Padding index left", "table.padding_index_left", default_int_list()), - ConfigOption::new(":table group", "Padding index right", "table.padding_index_right", default_int_list()), - ]; - - Some(HelpManual { - name: "table", - description: "Display a table view", - arguments: vec![], - examples: vec![], - config_options, - input: shortcuts, - }) - } - fn parse(&mut self, _: &str) -> Result<()> { Ok(()) } @@ -119,7 +62,7 @@ impl ViewCommand for TableCmd { let value = value.unwrap_or_default(); let is_record = matches!(value, Value::Record { .. }); - let (columns, data) = collect_input(value); + let (columns, data) = collect_input(value)?; let mut view = RecordView::new(columns, data); diff --git a/crates/nu-explore/src/commands/try.rs b/crates/nu-explore/src/commands/try.rs index 6f303589db..cb230908d4 100644 --- a/crates/nu-explore/src/commands/try.rs +++ b/crates/nu-explore/src/commands/try.rs @@ -1,10 +1,10 @@ -use super::{default_color_list, ConfigOption, HelpExample, HelpManual, Shortcode, ViewCommand}; +use super::ViewCommand; use crate::views::InteractiveView; +use anyhow::Result; use nu_protocol::{ engine::{EngineState, Stack}, Value, }; -use std::io::{Error, ErrorKind, Result}; #[derive(Debug, Default, Clone)] pub struct TryCmd { @@ -32,37 +32,6 @@ impl ViewCommand for TryCmd { "" } - fn help(&self) -> Option { - #[rustfmt::skip] - let shortcuts = vec![ - Shortcode::new("Up", "", "Switches between input and a output panes"), - Shortcode::new("Down", "", "Switches between input and a output panes"), - Shortcode::new("Esc", "", "Switches between input and a output panes"), - Shortcode::new("Tab", "", "Switches between input and a output panes"), - ]; - - #[rustfmt::skip] - let config_options = vec![ - ConfigOption::boolean(":try options", "In the `:try` REPL, attempt to run the command on every keypress", "try.reactive"), - ConfigOption::new(":try options", "Change a highlighted menu color", "try.highlighted_color", default_color_list()), - ]; - - #[rustfmt::skip] - let examples = vec![ - HelpExample::new("try", "Open a interactive :try command"), - HelpExample::new("try open Cargo.toml", "Optionally, you can provide a command which will be run immediately"), - ]; - - Some(HelpManual { - name: "try", - description: "Opens a panel in which to run Nushell commands and explore their output. The explorer acts like `:table`.", - arguments: vec![], - examples, - input: shortcuts, - config_options, - }) - } - fn parse(&mut self, args: &str) -> Result<()> { args.trim().clone_into(&mut self.command); @@ -78,8 +47,7 @@ impl ViewCommand for TryCmd { let value = value.unwrap_or_default(); let mut view = InteractiveView::new(value); view.init(self.command.clone()); - view.try_run(engine_state, stack) - .map_err(|e| Error::new(ErrorKind::Other, e))?; + view.try_run(engine_state, stack)?; Ok(view) } diff --git a/crates/nu-explore/src/explore.rs b/crates/nu-explore/src/explore.rs index 3b2912dbc4..6b9797bfa5 100644 --- a/crates/nu-explore/src/explore.rs +++ b/crates/nu-explore/src/explore.rs @@ -36,9 +36,9 @@ impl Command for Explore { ) .switch("index", "Show row indexes when viewing a list", Some('i')) .switch( - "reverse", + "tail", "Start with the viewport scrolled to the bottom", - Some('r'), + Some('t'), ) .switch( "peek", @@ -61,7 +61,7 @@ impl Command for Explore { ) -> Result { let show_head: bool = call.get_flag(engine_state, stack, "head")?.unwrap_or(true); let show_index: bool = call.has_flag(engine_state, stack, "index")?; - let is_reverse: bool = call.has_flag(engine_state, stack, "reverse")?; + let tail: bool = call.has_flag(engine_state, stack, "tail")?; let peek_value: bool = call.has_flag(engine_state, stack, "peek")?; let ctrlc = engine_state.ctrlc.clone(); @@ -79,19 +79,31 @@ impl Command for Explore { let mut config = PagerConfig::new(nu_config, &style_computer, &lscolors, config); config.style = style; - config.reverse = is_reverse; config.peek_value = peek_value; - config.reverse = is_reverse; + config.tail = tail; let result = run_pager(engine_state, &mut stack.clone(), ctrlc, input, config); match result { Ok(Some(value)) => Ok(PipelineData::Value(value, None)), Ok(None) => Ok(PipelineData::Value(Value::default(), None)), - Err(err) => Ok(PipelineData::Value( - Value::error(err.into(), call.head), - None, - )), + Err(err) => { + let shell_error = match err.downcast::() { + Ok(e) => e, + Err(e) => ShellError::GenericError { + error: e.to_string(), + msg: "".into(), + span: None, + help: None, + inner: vec![], + }, + }; + + Ok(PipelineData::Value( + Value::error(shell_error, call.head), + None, + )) + } } } diff --git a/crates/nu-explore/src/lib.rs b/crates/nu-explore/src/lib.rs index 3492eb0533..f339f006ee 100644 --- a/crates/nu-explore/src/lib.rs +++ b/crates/nu-explore/src/lib.rs @@ -6,20 +6,19 @@ mod pager; mod registry; mod views; +use anyhow::Result; +use commands::{ExpandCmd, HelpCmd, NuCmd, QuitCmd, TableCmd, TryCmd}; pub use default_context::add_explore_context; pub use explore::Explore; - -use commands::{ExpandCmd, HelpCmd, HelpManual, NuCmd, QuitCmd, TableCmd, TryCmd}; use nu_common::{collect_pipeline, has_simple_value, CtrlC}; use nu_protocol::{ engine::{EngineState, Stack}, PipelineData, Value, }; use pager::{Page, Pager, PagerConfig, StyleConfig}; -use registry::{Command, CommandRegistry}; -use std::io; +use registry::CommandRegistry; use terminal_size::{Height, Width}; -use views::{BinaryView, InformationView, Orientation, Preview, RecordView}; +use views::{BinaryView, Orientation, Preview, RecordView}; mod util { pub use super::nu_common::{create_lscolors, create_map, map_into_value}; @@ -31,7 +30,7 @@ fn run_pager( ctrlc: CtrlC, input: PipelineData, config: PagerConfig, -) -> io::Result> { +) -> Result> { let mut p = Pager::new(config.clone()); let commands = create_command_registry(); @@ -45,18 +44,18 @@ fn run_pager( return p.run(engine_state, stack, ctrlc, view, commands); } - let (columns, data) = collect_pipeline(input); + let (columns, data) = collect_pipeline(input)?; let has_no_input = columns.is_empty() && data.is_empty(); if has_no_input { - return p.run(engine_state, stack, ctrlc, information_view(), commands); + return p.run(engine_state, stack, ctrlc, help_view(), commands); } p.show_message("For help type :help"); if let Some(value) = has_simple_value(&data) { let text = value.to_abbreviated_string(config.nu_config); - let view = Some(Page::new(Preview::new(&text), true)); + let view = Some(Page::new(Preview::new(&text), false)); return p.run(engine_state, stack, ctrlc, view, commands); } @@ -67,6 +66,7 @@ fn run_pager( fn create_record_view( columns: Vec, data: Vec>, + // wait, why would we use RecordView for something that isn't a record? is_record: bool, config: PagerConfig, ) -> Option { @@ -75,17 +75,17 @@ fn create_record_view( view.set_orientation_current(Orientation::Left); } - if config.reverse { + if config.tail { if let Some((Width(w), Height(h))) = terminal_size::terminal_size() { - view.reverse(w, h); + view.tail(w, h); } } - Some(Page::new(view, false)) + Some(Page::new(view, true)) } -fn information_view() -> Option { - Some(Page::new(InformationView, true)) +fn help_view() -> Option { + Some(Page::new(HelpCmd::view(), false)) } fn binary_view(input: PipelineData) -> Option { @@ -96,7 +96,7 @@ fn binary_view(input: PipelineData) -> Option { let view = BinaryView::new(data); - Some(Page::new(view, false)) + Some(Page::new(view, true)) } fn create_command_registry() -> CommandRegistry { @@ -104,24 +104,16 @@ fn create_command_registry() -> CommandRegistry { create_commands(&mut registry); create_aliases(&mut registry); - // reregister help && config commands - let commands = registry.get_commands().cloned().collect::>(); - let aliases = registry.get_aliases().collect::>(); - - let help_cmd = create_help_command(&commands, &aliases); - - registry.register_command_view(help_cmd, true); - registry } fn create_commands(registry: &mut CommandRegistry) { - registry.register_command_view(NuCmd::new(), false); - registry.register_command_view(TableCmd::new(), false); + registry.register_command_view(NuCmd::new(), true); + registry.register_command_view(TableCmd::new(), true); - registry.register_command_view(ExpandCmd::new(), true); - registry.register_command_view(TryCmd::new(), true); - registry.register_command_view(HelpCmd::default(), true); + registry.register_command_view(ExpandCmd::new(), false); + registry.register_command_view(TryCmd::new(), false); + registry.register_command_view(HelpCmd::default(), false); registry.register_command_reactive(QuitCmd); } @@ -132,34 +124,3 @@ fn create_aliases(registry: &mut CommandRegistry) { registry.create_aliases("q", QuitCmd::NAME); registry.create_aliases("q!", QuitCmd::NAME); } - -fn create_help_command(commands: &[Command], aliases: &[(&str, &str)]) -> HelpCmd { - let help_manuals = create_help_manuals(commands); - - HelpCmd::new(help_manuals, aliases) -} - -fn create_help_manuals(cmd_list: &[Command]) -> Vec { - cmd_list.iter().map(create_help_manual).collect() -} - -fn create_help_manual(cmd: &Command) -> HelpManual { - let name = match cmd { - Command::Reactive(cmd) => cmd.name(), - Command::View { cmd, .. } => cmd.name(), - }; - - let manual = match cmd { - Command::Reactive(cmd) => cmd.help(), - Command::View { cmd, .. } => cmd.help(), - }; - - __create_help_manual(manual, name) -} - -fn __create_help_manual(manual: Option, name: &'static str) -> HelpManual { - manual.unwrap_or(HelpManual { - name, - ..HelpManual::default() - }) -} diff --git a/crates/nu-explore/src/nu_common/value.rs b/crates/nu-explore/src/nu_common/value.rs index 5c63c9e780..2c2c858db0 100644 --- a/crates/nu-explore/src/nu_common/value.rs +++ b/crates/nu-explore/src/nu_common/value.rs @@ -1,13 +1,14 @@ use super::NuSpan; +use anyhow::Result; use nu_engine::get_columns; use nu_protocol::{record, ListStream, PipelineData, PipelineMetadata, RawStream, Value}; use std::collections::HashMap; -pub fn collect_pipeline(input: PipelineData) -> (Vec, Vec>) { +pub fn collect_pipeline(input: PipelineData) -> Result<(Vec, Vec>)> { match input { - PipelineData::Empty => (vec![], vec![]), + PipelineData::Empty => Ok((vec![], vec![])), PipelineData::Value(value, ..) => collect_input(value), - PipelineData::ListStream(stream, ..) => collect_list_stream(stream), + PipelineData::ListStream(stream, ..) => Ok(collect_list_stream(stream)), PipelineData::ExternalStream { stdout, stderr, @@ -15,7 +16,9 @@ pub fn collect_pipeline(input: PipelineData) -> (Vec, Vec>) { metadata, span, .. - } => collect_external_stream(stdout, stderr, exit_code, metadata, span), + } => Ok(collect_external_stream( + stdout, stderr, exit_code, metadata, span, + )), } } @@ -83,12 +86,12 @@ fn collect_external_stream( } /// Try to build column names and a table grid. -pub fn collect_input(value: Value) -> (Vec, Vec>) { +pub fn collect_input(value: Value) -> Result<(Vec, Vec>)> { let span = value.span(); match value { Value::Record { val: record, .. } => { let (key, val) = record.into_owned().into_iter().unzip(); - (key, vec![val]) + Ok((key, vec![val])) } Value::List { vals, .. } => { let mut columns = get_columns(&vals); @@ -98,7 +101,7 @@ pub fn collect_input(value: Value) -> (Vec, Vec>) { columns = vec![String::from("")]; } - (columns, data) + Ok((columns, data)) } Value::String { val, .. } => { let lines = val @@ -107,17 +110,18 @@ pub fn collect_input(value: Value) -> (Vec, Vec>) { .map(|val| vec![val]) .collect(); - (vec![String::from("")], lines) + Ok((vec![String::from("")], lines)) } - Value::LazyRecord { val, .. } => match val.collect() { - Ok(value) => collect_input(value), - Err(_) => ( - vec![String::from("")], - vec![vec![Value::lazy_record(val, span)]], - ), - }, - Value::Nothing { .. } => (vec![], vec![]), - value => (vec![String::from("")], vec![vec![value]]), + Value::LazyRecord { val, .. } => { + let materialized = val.collect()?; + collect_input(materialized) + } + Value::Nothing { .. } => Ok((vec![], vec![])), + Value::Custom { val, .. } => { + let materialized = val.to_base_value(span)?; + collect_input(materialized) + } + value => Ok((vec![String::from("")], vec![vec![value]])), } } diff --git a/crates/nu-explore/src/pager/mod.rs b/crates/nu-explore/src/pager/mod.rs index 67aac1f312..4f43078846 100644 --- a/crates/nu-explore/src/pager/mod.rs +++ b/crates/nu-explore/src/pager/mod.rs @@ -15,6 +15,7 @@ use crate::{ util::map_into_value, views::{util::nu_style_to_tui, ViewConfig}, }; +use anyhow::Result; use crossterm::{ event::{KeyCode, KeyEvent, KeyModifiers}, execute, @@ -34,7 +35,7 @@ use ratatui::{backend::CrosstermBackend, layout::Rect, widgets::Block}; use std::{ cmp::min, collections::HashMap, - io::{self, Result, Stdout}, + io::{self, Stdout}, result, sync::atomic::Ordering, }; @@ -143,6 +144,7 @@ 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), @@ -155,8 +157,9 @@ pub struct PagerConfig<'a> { pub lscolors: &'a LsColors, pub config: ConfigMap, pub style: StyleConfig, + // If true, when quitting output the value of the cell the cursor was on pub peek_value: bool, - pub reverse: bool, + pub tail: bool, } impl<'a> PagerConfig<'a> { @@ -172,7 +175,7 @@ impl<'a> PagerConfig<'a> { config, lscolors, peek_value: false, - reverse: false, + tail: false, style: StyleConfig::default(), } } @@ -247,7 +250,7 @@ fn render_ui( { let info = info.clone(); term.draw(|f| { - draw_frame(f, &mut view_stack.view, pager, &mut layout, info); + draw_frame(f, &mut view_stack.curr_view, pager, &mut layout, info); })?; } @@ -259,7 +262,7 @@ fn render_ui( info, &mut pager.search_buf, &mut pager.cmd_buf, - view_stack.view.as_mut().map(|p| &mut p.view), + view_stack.curr_view.as_mut().map(|p| &mut p.view), ); if let Some(transition) = transition { @@ -302,7 +305,7 @@ fn render_ui( match out { Ok(result) => { if result.exit { - break Ok(peak_value_from_view(&mut view_stack.view, pager)); + break Ok(peek_value_from_view(&mut view_stack.curr_view, pager)); } if result.view_change && !result.cmd_name.is_empty() { @@ -336,21 +339,21 @@ fn react_to_event_result( ) -> (Option>, String) { match status { Transition::Exit => ( - Some(peak_value_from_view(&mut view_stack.view, pager)), + Some(peek_value_from_view(&mut view_stack.curr_view, pager)), String::default(), ), Transition::Ok => { let exit = view_stack.stack.is_empty(); if exit { return ( - Some(peak_value_from_view(&mut view_stack.view, pager)), + Some(peek_value_from_view(&mut view_stack.curr_view, pager)), String::default(), ); } // try to pop the view stack if let Some(v) = view_stack.stack.pop() { - view_stack.view = Some(v); + view_stack.curr_view = Some(v); } (None, String::default()) @@ -359,7 +362,7 @@ fn react_to_event_result( let out = pager_run_command(engine_state, stack, pager, view_stack, commands, cmd); match out { Ok(result) if result.exit => ( - Some(peak_value_from_view(&mut view_stack.view, pager)), + Some(peek_value_from_view(&mut view_stack.curr_view, pager)), String::default(), ), Ok(result) => (None, result.cmd_name), @@ -372,9 +375,13 @@ fn react_to_event_result( } } -fn peak_value_from_view(view: &mut Option, pager: &mut Pager<'_>) -> Option { - let view = view.as_mut().map(|p| &mut p.view); - try_to_peek_value(pager, view) +fn peek_value_from_view(view: &mut Option, pager: &mut Pager<'_>) -> Option { + if pager.config.peek_value { + let view = view.as_mut().map(|p| &mut p.view); + view.and_then(|v| v.exit()) + } else { + None + } } fn draw_frame( @@ -453,7 +460,7 @@ fn run_command( match command { Command::Reactive(mut command) => { // what we do we just replace the view. - let value = view_stack.view.as_mut().and_then(|p| p.view.exit()); + let value = view_stack.curr_view.as_mut().and_then(|p| p.view.exit()); let transition = command.react(engine_state, stack, pager, value)?; match transition { Transition::Ok => { @@ -470,18 +477,18 @@ fn run_command( Transition::Cmd { .. } => todo!("not used so far"), } } - Command::View { mut cmd, is_light } => { + Command::View { mut cmd, stackable } => { // what we do we just replace the view. - let value = view_stack.view.as_mut().and_then(|p| p.view.exit()); + let value = view_stack.curr_view.as_mut().and_then(|p| p.view.exit()); let mut new_view = cmd.spawn(engine_state, stack, value)?; - if let Some(view) = view_stack.view.take() { - if !view.is_light { + if let Some(view) = view_stack.curr_view.take() { + if !view.stackable { view_stack.stack.push(view); } } update_view_setup(&mut new_view, &pager.config); - view_stack.view = Some(Page::raw(new_view, is_light)); + view_stack.curr_view = Some(Page::raw(new_view, stackable)); Ok(CmdResult::new(false, true, cmd.name().to_owned())) } @@ -489,7 +496,7 @@ fn run_command( } fn update_view_stack_setup(view_stack: &mut ViewStack, cfg: &PagerConfig<'_>) { - if let Some(page) = view_stack.view.as_mut() { + if let Some(page) = view_stack.curr_view.as_mut() { update_view_setup(&mut page.view, cfg); } @@ -521,17 +528,6 @@ fn set_cursor_cmd_bar(f: &mut Frame, area: Rect, pager: &Pager) { } } -fn try_to_peek_value(pager: &mut Pager, view: Option<&mut V>) -> Option -where - V: View, -{ - if pager.config.peek_value { - view.and_then(|v| v.exit()) - } else { - None - } -} - fn render_status_bar(f: &mut Frame, area: Rect, report: Report, theme: &StyleConfig) { let msg_style = report_msg_style(&report, theme, theme.status_bar_text); let mut status_bar = create_status_bar(report); @@ -1092,30 +1088,35 @@ impl Position { pub struct Page { pub view: Box, - pub is_light: bool, + /// Controls what happens when this view is the current view and a new view is created. + /// If true, view will be pushed to the stack, otherwise, it will be deleted. + pub stackable: bool, } impl Page { - pub fn raw(view: Box, is_light: bool) -> Self { - Self { view, is_light } + pub fn raw(view: Box, stackable: bool) -> Self { + Self { view, stackable } } - pub fn new(view: V, is_light: bool) -> Self + pub fn new(view: V, stackable: bool) -> Self where V: View + 'static, { - Self::raw(Box::new(view), is_light) + Self::raw(Box::new(view), stackable) } } struct ViewStack { - view: Option, + curr_view: Option, stack: Vec, } impl ViewStack { fn new(view: Option, stack: Vec) -> Self { - Self { view, stack } + Self { + curr_view: view, + stack, + } } } diff --git a/crates/nu-explore/src/registry/command.rs b/crates/nu-explore/src/registry/command.rs index ce06fb17d1..7e0bc131b5 100644 --- a/crates/nu-explore/src/registry/command.rs +++ b/crates/nu-explore/src/registry/command.rs @@ -1,26 +1,27 @@ use crate::{ - commands::{HelpManual, SimpleCommand, ViewCommand}, + commands::{SimpleCommand, ViewCommand}, views::View, }; +use anyhow::Result; #[derive(Clone)] pub enum Command { Reactive(Box), View { cmd: Box, - is_light: bool, + stackable: bool, }, } impl Command { - pub fn view(command: C, is_light: bool) -> Self + pub fn view(command: C, stackable: bool) -> Self where C: ViewCommand + Clone + 'static, C::View: View, { let cmd = Box::new(ViewCmd(command)) as Box; - Self::View { cmd, is_light } + Self::View { cmd, stackable } } pub fn reactive(command: C) -> Self @@ -41,7 +42,7 @@ impl Command { } } - pub fn parse(&mut self, args: &str) -> std::io::Result<()> { + pub fn parse(&mut self, args: &str) -> Result<()> { match self { Command::Reactive(cmd) => cmd.parse(args), Command::View { cmd, .. } => cmd.parse(args), @@ -68,11 +69,7 @@ where self.0.usage() } - fn help(&self) -> Option { - self.0.help() - } - - fn parse(&mut self, args: &str) -> std::io::Result<()> { + fn parse(&mut self, args: &str) -> Result<()> { self.0.parse(args) } @@ -81,7 +78,7 @@ where engine_state: &nu_protocol::engine::EngineState, stack: &mut nu_protocol::engine::Stack, value: Option, - ) -> std::io::Result { + ) -> Result { let view = self.0.spawn(engine_state, stack, value)?; Ok(Box::new(view) as Box) } diff --git a/crates/nu-explore/src/registry/mod.rs b/crates/nu-explore/src/registry/mod.rs index 0a189d34f2..a0d5bde753 100644 --- a/crates/nu-explore/src/registry/mod.rs +++ b/crates/nu-explore/src/registry/mod.rs @@ -4,6 +4,7 @@ use crate::{ commands::{SimpleCommand, ViewCommand}, views::View, }; +use anyhow::Result; use std::borrow::Cow; use std::collections::HashMap; @@ -25,14 +26,14 @@ impl CommandRegistry { .insert(Cow::Owned(command.name().to_owned()), command); } - pub fn register_command_view(&mut self, command: C, is_light: bool) + pub fn register_command_view(&mut self, command: C, stackable: bool) where C: ViewCommand + Clone + 'static, C::View: View, { self.commands.insert( Cow::Owned(command.name().to_owned()), - Command::view(command, is_light), + Command::view(command, stackable), ); } @@ -53,7 +54,7 @@ impl CommandRegistry { ); } - pub fn find(&self, args: &str) -> Option> { + pub fn find(&self, args: &str) -> Option> { let cmd = args.split_once(' ').map_or(args, |(cmd, _)| cmd); let args = &args[cmd.len()..]; diff --git a/crates/nu-explore/src/views/information.rs b/crates/nu-explore/src/views/information.rs deleted file mode 100644 index ec99f875a1..0000000000 --- a/crates/nu-explore/src/views/information.rs +++ /dev/null @@ -1,77 +0,0 @@ -use super::{Layout, View, ViewConfig}; -use crate::{ - nu_common::NuText, - pager::{Frame, Transition, ViewInfo}, -}; -use crossterm::event::KeyEvent; -use nu_color_config::TextStyle; -use nu_protocol::engine::{EngineState, Stack}; -use ratatui::{layout::Rect, widgets::Paragraph}; - -#[derive(Debug, Default)] -pub struct InformationView; - -impl InformationView { - const MESSAGE: [&'static str; 7] = [ - "Explore", - "", - "Explore helps you dynamically navigate through your data", - "", - "type :help for help", - "type :q to exit", - "type :try to enter a REPL", - ]; -} - -impl View for InformationView { - fn draw(&mut self, f: &mut Frame, area: Rect, _: ViewConfig<'_>, layout: &mut Layout) { - let count_lines = Self::MESSAGE.len() as u16; - - if area.height < count_lines { - return; - } - - let centerh = area.height / 2; - let centerw = area.width / 2; - - let mut y = centerh.saturating_sub(count_lines); - for mut line in Self::MESSAGE { - let mut line_width = line.len() as u16; - if line_width > area.width { - line_width = area.width; - line = &line[..area.width as usize]; - } - - let x = centerw.saturating_sub(line_width / 2); - let area = Rect::new(area.x + x, area.y + y, line_width, 1); - - let paragraph = Paragraph::new(line); - f.render_widget(paragraph, area); - - layout.push(line, area.x, area.y, area.width, area.height); - - y += 1; - } - } - - fn handle_input( - &mut self, - _: &EngineState, - _: &mut Stack, - _: &Layout, - _: &mut ViewInfo, - event: KeyEvent, - ) -> Option { - match event.code { - crossterm::event::KeyCode::Esc => Some(Transition::Exit), - _ => None, - } - } - - fn collect_data(&self) -> Vec { - Self::MESSAGE - .into_iter() - .map(|line| (line.to_owned(), TextStyle::default())) - .collect::>() - } -} diff --git a/crates/nu-explore/src/views/interactive.rs b/crates/nu-explore/src/views/interactive.rs index aeb164b722..3e9de30676 100644 --- a/crates/nu-explore/src/views/interactive.rs +++ b/crates/nu-explore/src/views/interactive.rs @@ -8,6 +8,7 @@ use crate::{ pager::{report::Report, Frame, Transition, ViewInfo}, util::create_map, }; +use anyhow::Result; use crossterm::event::{KeyCode, KeyEvent}; use nu_color_config::get_color_map; use nu_protocol::{ @@ -50,7 +51,7 @@ impl<'a> InteractiveView<'a> { self.command = command; } - pub fn try_run(&mut self, engine_state: &EngineState, stack: &mut Stack) -> Result<(), String> { + pub fn try_run(&mut self, engine_state: &EngineState, stack: &mut Stack) -> Result<()> { let mut view = run_command(&self.command, &self.input, engine_state, stack)?; view.set_theme(self.table_theme.clone()); @@ -281,13 +282,12 @@ fn run_command( input: &Value, engine_state: &EngineState, stack: &mut Stack, -) -> Result, String> { - let pipeline = - run_command_with_value(command, input, engine_state, stack).map_err(|e| e.to_string())?; +) -> Result> { + let pipeline = run_command_with_value(command, input, engine_state, stack)?; let is_record = matches!(pipeline, PipelineData::Value(Value::Record { .. }, ..)); - let (columns, values) = collect_pipeline(pipeline); + let (columns, values) = collect_pipeline(pipeline)?; let mut view = RecordView::new(columns, values); if is_record { diff --git a/crates/nu-explore/src/views/mod.rs b/crates/nu-explore/src/views/mod.rs index 1a6e0c9942..3226f546d2 100644 --- a/crates/nu-explore/src/views/mod.rs +++ b/crates/nu-explore/src/views/mod.rs @@ -1,7 +1,6 @@ mod binary; mod coloredtextw; mod cursor; -mod information; mod interactive; mod preview; mod record; @@ -22,7 +21,6 @@ use nu_protocol::{ use ratatui::layout::Rect; pub use binary::BinaryView; -pub use information::InformationView; pub use interactive::InteractiveView; pub use preview::Preview; pub use record::{Orientation, RecordView}; diff --git a/crates/nu-explore/src/views/record/mod.rs b/crates/nu-explore/src/views/record/mod.rs index 1576abcaf4..6534ba3589 100644 --- a/crates/nu-explore/src/views/record/mod.rs +++ b/crates/nu-explore/src/views/record/mod.rs @@ -15,6 +15,7 @@ use crate::{ util::create_map, views::ElementInfo, }; +use anyhow::Result; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use nu_color_config::{get_color_map, StyleComputer}; use nu_protocol::{ @@ -47,10 +48,10 @@ impl<'a> RecordView<'a> { } } - pub fn reverse(&mut self, width: u16, height: u16) { + pub fn tail(&mut self, width: u16, height: u16) { let page_size = estimate_page_size(Rect::new(0, 0, width, height), self.theme.table.show_header); - state_reverse_data(self, page_size as usize); + tail_data(self, page_size as usize); } pub fn set_style_split_line(&mut self, style: NuStyle) { @@ -266,16 +267,26 @@ impl View for RecordView<'_> { key: KeyEvent, ) -> Option { let result = match self.mode { - UIMode::View => handle_key_event_view_mode(self, &key), + UIMode::View => Ok(handle_key_event_view_mode(self, &key)), UIMode::Cursor => handle_key_event_cursor_mode(self, &key), }; - if matches!(&result, Some(Transition::Ok) | Some(Transition::Cmd { .. })) { - let report = self.create_records_report(); - info.status = Some(report); - } + match result { + Ok(result) => { + if matches!(&result, Some(Transition::Ok) | Some(Transition::Cmd { .. })) { + let report = self.create_records_report(); + info.status = Some(report); + } - result + result + } + Err(e) => { + log::error!("Error handling input in RecordView: {e}"); + let report = Report::message(e.to_string(), Severity::Err); + info.status = Some(report); + None + } + } } fn collect_data(&self) -> Vec { @@ -508,7 +519,10 @@ fn handle_key_event_view_mode(view: &mut RecordView, key: &KeyEvent) -> Option Option { +fn handle_key_event_cursor_mode( + view: &mut RecordView, + key: &KeyEvent, +) -> Result> { match key { KeyEvent { code: KeyCode::Char('u'), @@ -521,7 +535,7 @@ fn handle_key_event_cursor_mode(view: &mut RecordView, key: &KeyEvent) -> Option } => { view.get_layer_last_mut().cursor.prev_row_page(); - return Some(Transition::Ok); + return Ok(Some(Transition::Ok)); } KeyEvent { code: KeyCode::Char('d'), @@ -534,7 +548,7 @@ fn handle_key_event_cursor_mode(view: &mut RecordView, key: &KeyEvent) -> Option } => { view.get_layer_last_mut().cursor.next_row_page(); - return Some(Transition::Ok); + return Ok(Some(Transition::Ok)); } _ => {} } @@ -543,43 +557,42 @@ fn handle_key_event_cursor_mode(view: &mut RecordView, key: &KeyEvent) -> Option KeyCode::Esc => { view.set_view_mode(); - Some(Transition::Ok) + Ok(Some(Transition::Ok)) } KeyCode::Up | KeyCode::Char('k') => { view.get_layer_last_mut().cursor.prev_row(); - Some(Transition::Ok) + Ok(Some(Transition::Ok)) } KeyCode::Down | KeyCode::Char('j') => { view.get_layer_last_mut().cursor.next_row(); - Some(Transition::Ok) + Ok(Some(Transition::Ok)) } KeyCode::Left | KeyCode::Char('h') => { view.get_layer_last_mut().cursor.prev_column(); - Some(Transition::Ok) + Ok(Some(Transition::Ok)) } KeyCode::Right | KeyCode::Char('l') => { view.get_layer_last_mut().cursor.next_column(); - Some(Transition::Ok) + Ok(Some(Transition::Ok)) } KeyCode::Home | KeyCode::Char('g') => { view.get_layer_last_mut().cursor.row_move_to_start(); - Some(Transition::Ok) + Ok(Some(Transition::Ok)) } KeyCode::End | KeyCode::Char('G') => { view.get_layer_last_mut().cursor.row_move_to_end(); - Some(Transition::Ok) + Ok(Some(Transition::Ok)) } KeyCode::Enter => { let value = view.get_current_value(); let is_record = matches!(value, Value::Record { .. }); - let next_layer = create_layer(value); - + let next_layer = create_layer(value)?; push_layer(view, next_layer); if is_record { @@ -590,16 +603,16 @@ fn handle_key_event_cursor_mode(view: &mut RecordView, key: &KeyEvent) -> Option view.set_orientation_current(view.orientation); } - Some(Transition::Ok) + Ok(Some(Transition::Ok)) } - _ => None, + _ => Ok(None), } } -fn create_layer(value: Value) -> RecordLayer<'static> { - let (columns, values) = collect_input(value); +fn create_layer(value: Value) -> Result> { + let (columns, values) = collect_input(value)?; - RecordLayer::new(columns, values) + Ok(RecordLayer::new(columns, values)) } fn push_layer(view: &mut RecordView<'_>, mut next_layer: RecordLayer<'static>) { @@ -624,7 +637,8 @@ fn estimate_page_size(area: Rect, show_head: bool) -> u16 { available_height } -fn state_reverse_data(state: &mut RecordView<'_>, page_size: usize) { +/// scroll to the end of the data +fn tail_data(state: &mut RecordView<'_>, page_size: usize) { let layer = state.get_layer_last_mut(); let count_rows = layer.records.len(); if count_rows > page_size { diff --git a/foo.db b/foo.db new file mode 100644 index 0000000000000000000000000000000000000000..7fadc66cfa9740c335f51d6b8a4ef015251e4ed8 GIT binary patch literal 20480 zcmeHOd2kzN6@R-cS%-DyXw$^8wXvPpPQ1D=yG^UaQPQ-E>?BSbr!=-Kt!>#lw%V1C z6lelN2{2HXa)jGZ2Bs;8+d@m}OfZFkGA%Ps;SUCw;=+_E(+&y4(J6(2x2x>xsO*@4 zXZT|^V@vY;z3;vE`@VhOe*2z}9*(92mLAFFGjW0Tf(-})!ZZy48vN1XkNhI>gZMZ3 zMaUmkc-Meq@~jyFq_q{SU&deROJzW1KxIH>KxIH>KxIH>KxIH>KxIH>KxN?nl!14X zx=riW5obn)_;8x#c#fxfjzsqKN9g`Nd!rHhE{?yOZs$@-`e3X-av*Y~ljbVEW#bu^ z?vEVr@1%JlE)@7HUlLq_#(|=pbcW58lqXYpp%72Yf9N z!VIU_OPylnfzV7YnHov4NqQf8-+%B>q(eNLZ>;|eST9)bv+lDREsK^PuxnWk6*>Wk6*>Wk6*>Wk6*>Wne8BaBb9X*gzhg zN=+wIc^i0K9v|cKF`gjp4t2YO-JYPs?+fBzU9J%03U=e$2JN=%$fJ1spW_`uE|VTf zrCH+mYBbjuHFLT2L`raE3!>)DpQx$n4Y<2pf#4OJo^{$y=%v8&0>8YUl{#LVcC)Ax z&yTV~nUWX7pxuFPzq{MzbA&b0)7{0W}|eADs~OU4qlY%srO{-ODtdBS|7S!?>O=}FV9Y1|Ys zU0?sF`p?wQ)c4nKuLt9Aj0?su8sB4d8*PRK!xs$q8tyRo3>)=-*1umrs^72srS7}B zPw5z)x$gD4({<~$U(q&c{-Swa^CiuUrj2@-%28pemC}>1lP{2uk{>2>NVP_H>SSr9Lr@`XP!-^(=$%TB%jD7S%zoxlWg8OoXR>`@#^q< zMuGtkCc`9~2qfa3L?X}?AC9~IL01qz`IFu*XBHKYrdgItWk)yn(nxfEeh9{Im#t~0 ze!fQ5G#v}z3(mv%Z6#aZo_cIeBGa)n-1r6uTMz0C`Yg%_f|j zw38-f4{r~HrOySPFv54^64Tt>Q3;PHCgP(kZoY~NVLc7&jk9ouxJ8`4W1?2?6S1+% z=@a*<0_RkAD&FaK1cMF_GaU*H27F8*JCV&zWtns;TbO1>vjxRNX@`qvADQmxg}`ZjU#TCM!bLOyu^igRh=r$dZ-WJ`?Jt?lLCBSZD33UE<)Rl3NTw6*oPP2 z;|tKwcS_8r+&*0*>{qRkHZxJos*~K{_bg(#9fd zWNY2UB}{|kjbRuM$ksHmn`66IBKVq3#0c0bO$lpkWdV>eY?$%G9s2{ifS z1QNTcDuG~vS%7hG$yra#y*AFCqhb4Y0J}$V)*W-za@MUl>#FIjOK~=OPn25|XUU}n zXnp4_*qySoQ7)RR<*Y+?HkzyHEK_p!uIP!iarR|&cIGtLI~8Y7Lh^>J%1(A=!8zP6*(UYE zEC4+C^boXcQ35_j^j8PGX&#Jy81QB#;Qo~Y&SSf<5$~;TPy+6%5wIUXScKM1IHtJa zQ}BwD7n3>O?+C(-?BY5OhE7B4^$O~DHBl}&F$X)ZLlh&fdRVHCS{{$+7=pTQorY~2 z5o?$1z9daXIn~Wez`MY=0U&W2!9*`;$IwW zw<1=$m4b8Os!c9Kvm)UIyG*4WIz_hrw=?0Y|CHCj%nY0Fo0;c(e%EBhF*6COrl5 z5sp0h$LD|;2tz=FIufTXHFd^wg*@JgD{64j2Gnbq-Y7j$Jkr#W9NtlO$>Wlkj8DT> z5{H%UXe#%q4X2@H4w?zQL63PzqbXiV6(^<#G5>)kD%Ss_c+9r`8an{Kpq~HL^ZzwI z*-+2_>iJ*lZBozw>iPd_k3rS*|FTbrDfRrXp8rP)_581%|JCzIDi>&L7+EdQ|l%yQmx#^N%6)qKAhOb?j^Q@_b)+F1XU z`VZ7M7{6}3#kkS%2g3!!Lx!-SN&lw)Mg2MbN&R-+>$-30PU!}80p0q#OLZ^QJyLf^ z9aCr1{#pB?b{>aUUn&DC11bY516N_7>0T_5NbNJ!C62n3D^-Qeb7B(=eG27LK@f#+ zFVMAI2K$VjDn2qsaphxSY$m}fcEhxuDz?E;pZ#n_MG@rwG9yPyVwC0uL+# zJde(Bh>@PVBY-&h_X?s2o7ik*&&kt(^2%u_b}NOlI0lvx(9TYlXzx}^NO4YZW>_p4 zCwV6?W{5xg33ED=2^wDpdV>g^ zhmK(d+I(RdX!AVwN6kS;yafFs(fpxW4%|5husDnE8YoJtnP?D5H2W*#uk4T#Pjmi+ zmknUoWWdV?yFzh)JmDGfx<^Kmu4E|S4Fs?|DwIt45*1yO13PXs{pCQ)1IBwi0~e_tU$x1vy)hSXS@5-v(#u`bAQtMnv>+ zWl~n%iHQGDQfv#IzDg(JyZ?rn0iOEN0`wlm_7N%HO=qU}R&w