mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
explore
: adopt anyhow
, support CustomValue
, remove help system (#12692)
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.
This commit is contained in:
parent
bc18cc12d5
commit
3d340657b5
21 changed files with 283 additions and 842 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -165,6 +165,12 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"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]]
|
[[package]]
|
||||||
name = "arboard"
|
name = "arboard"
|
||||||
version = "3.3.2"
|
version = "3.3.2"
|
||||||
|
@ -398,7 +404,7 @@ dependencies = [
|
||||||
"bitflags 2.5.0",
|
"bitflags 2.5.0",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
"clang-sys",
|
"clang-sys",
|
||||||
"itertools 0.11.0",
|
"itertools 0.12.1",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lazycell",
|
"lazycell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
@ -3158,7 +3164,9 @@ name = "nu-explore"
|
||||||
version = "0.93.1"
|
version = "0.93.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi-str",
|
"ansi-str",
|
||||||
|
"anyhow",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"log",
|
||||||
"lscolors",
|
"lscolors",
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
"nu-color-config",
|
"nu-color-config",
|
||||||
|
@ -3169,6 +3177,7 @@ dependencies = [
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"nu-table",
|
"nu-table",
|
||||||
"nu-utils",
|
"nu-utils",
|
||||||
|
"once_cell",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"strip-ansi-escapes",
|
"strip-ansi-escapes",
|
||||||
"terminal_size",
|
"terminal_size",
|
||||||
|
|
|
@ -64,6 +64,7 @@ members = [
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
alphanumeric-sort = "1.5"
|
alphanumeric-sort = "1.5"
|
||||||
ansi-str = "0.8"
|
ansi-str = "0.8"
|
||||||
|
anyhow = "1.0.82"
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
bracoxide = "0.1.2"
|
bracoxide = "0.1.2"
|
||||||
brotli = "5.0"
|
brotli = "5.0"
|
||||||
|
|
|
@ -21,9 +21,12 @@ nu-utils = { path = "../nu-utils", version = "0.93.1" }
|
||||||
nu-ansi-term = { workspace = true }
|
nu-ansi-term = { workspace = true }
|
||||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.93.1" }
|
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.93.1" }
|
||||||
|
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
log = { workspace = true }
|
||||||
terminal_size = { workspace = true }
|
terminal_size = { workspace = true }
|
||||||
strip-ansi-escapes = { workspace = true }
|
strip-ansi-escapes = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
|
once_cell = { workspace = true }
|
||||||
ratatui = { workspace = true }
|
ratatui = { workspace = true }
|
||||||
ansi-str = { workspace = true }
|
ansi-str = { workspace = true }
|
||||||
unicode-width = { workspace = true }
|
unicode-width = { workspace = true }
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use super::{HelpManual, Shortcode, ViewCommand};
|
use super::ViewCommand;
|
||||||
use crate::{
|
use crate::{
|
||||||
nu_common::{self, collect_input},
|
nu_common::{self, collect_input},
|
||||||
views::Preview,
|
views::Preview,
|
||||||
};
|
};
|
||||||
|
use anyhow::Result;
|
||||||
use nu_color_config::StyleComputer;
|
use nu_color_config::StyleComputer;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
Value,
|
Value,
|
||||||
};
|
};
|
||||||
use std::{io::Result, vec};
|
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct ExpandCmd;
|
pub struct ExpandCmd;
|
||||||
|
@ -34,29 +34,6 @@ impl ViewCommand for ExpandCmd {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
fn help(&self) -> Option<HelpManual> {
|
|
||||||
#[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<()> {
|
fn parse(&mut self, _: &str) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -67,27 +44,37 @@ impl ViewCommand for ExpandCmd {
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
value: Option<Value>,
|
value: Option<Value>,
|
||||||
) -> Result<Self::View> {
|
) -> Result<Self::View> {
|
||||||
let value = value
|
if let Some(value) = value {
|
||||||
.map(|v| convert_value_to_string(v, engine_state, stack))
|
let value_as_string = convert_value_to_string(value, engine_state, stack)?;
|
||||||
.unwrap_or_default();
|
Ok(Preview::new(&value_as_string))
|
||||||
|
} else {
|
||||||
Ok(Preview::new(&value))
|
Ok(Preview::new(""))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_value_to_string(value: Value, engine_state: &EngineState, stack: &mut Stack) -> String {
|
fn convert_value_to_string(
|
||||||
let (cols, vals) = collect_input(value.clone());
|
value: Value,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
) -> Result<String> {
|
||||||
|
let (cols, vals) = collect_input(value.clone())?;
|
||||||
|
|
||||||
let has_no_head = cols.is_empty() || (cols.len() == 1 && cols[0].is_empty());
|
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;
|
let has_single_value = vals.len() == 1 && vals[0].len() == 1;
|
||||||
if !has_no_head && has_single_value {
|
if !has_no_head && has_single_value {
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
vals[0][0].to_abbreviated_string(config)
|
Ok(vals[0][0].to_abbreviated_string(config))
|
||||||
} else {
|
} else {
|
||||||
let ctrlc = engine_state.ctrlc.clone();
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
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,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,76 +1,91 @@
|
||||||
use super::{HelpExample, HelpManual, ViewCommand};
|
use super::ViewCommand;
|
||||||
use crate::{
|
use crate::views::Preview;
|
||||||
nu_common::{collect_input, NuSpan},
|
use anyhow::Result;
|
||||||
pager::{Frame, Transition, ViewInfo},
|
use nu_ansi_term::Color;
|
||||||
views::{Layout, Preview, RecordView, View, ViewConfig},
|
|
||||||
};
|
|
||||||
use crossterm::event::KeyEvent;
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
record, Value,
|
Value,
|
||||||
};
|
|
||||||
use ratatui::layout::Rect;
|
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
io::{self, Result},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct HelpCmd {
|
pub struct HelpCmd {}
|
||||||
input_command: String,
|
|
||||||
supported_commands: Vec<HelpManual>,
|
|
||||||
aliases: HashMap<String, Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HelpCmd {
|
impl HelpCmd {
|
||||||
pub const NAME: &'static str = "help";
|
pub const NAME: &'static str = "help";
|
||||||
|
pub fn view() -> Preview {
|
||||||
const HELP_MESSAGE: &'static str = r#" Explore - main help file
|
Preview::new(&HELP_MESSAGE)
|
||||||
|
|
||||||
Move around: Use the cursor keys.
|
|
||||||
Close help: Press "<Esc>".
|
|
||||||
Exit Explore: Type ":q" then then <Enter> (or press Ctrl+D).
|
|
||||||
Open an interactive REPL: Type ":try" then enter
|
|
||||||
List all sub-commands: Type ":help :" then <Enter>
|
|
||||||
|
|
||||||
------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Regular expressions
|
|
||||||
|
|
||||||
Most commands support regular expressions.
|
|
||||||
|
|
||||||
You can type "/" and type a pattern you want to search on.
|
|
||||||
Then hit <Enter> and you will see the search results.
|
|
||||||
|
|
||||||
To go to the next hit use "<n>" key.
|
|
||||||
|
|
||||||
You also can do a reverse search by using "?" instead of "/".
|
|
||||||
"#;
|
|
||||||
|
|
||||||
pub fn new(commands: Vec<HelpManual>, aliases: &[(&str, &str)]) -> Self {
|
|
||||||
let aliases = collect_aliases(aliases);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
input_command: String::new(),
|
|
||||||
supported_commands: commands,
|
|
||||||
aliases,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_aliases(aliases: &[(&str, &str)]) -> HashMap<String, Vec<String>> {
|
static HELP_MESSAGE: Lazy<String> = Lazy::new(|| {
|
||||||
let mut out_aliases: HashMap<String, Vec<String>> = HashMap::new();
|
let title = nu_ansi_term::Style::new().bold().underline();
|
||||||
for (name, cmd) in aliases {
|
let code = nu_ansi_term::Style::new().bold().fg(Color::Blue);
|
||||||
out_aliases
|
|
||||||
.entry(cmd.to_string())
|
// There is probably a nicer way to do this formatting inline
|
||||||
.and_modify(|list| list.push(name.to_string()))
|
format!(
|
||||||
.or_insert_with(|| vec![name.to_string()]);
|
r#"{}
|
||||||
}
|
Explore helps you dynamically navigate through your data!
|
||||||
out_aliases
|
|
||||||
}
|
{}
|
||||||
|
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>
|
||||||
|
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
|
||||||
|
|
||||||
|
{}
|
||||||
|
Most commands support search via regular expressions.
|
||||||
|
|
||||||
|
You can type "/" and type a pattern you want to search on. Then hit <Enter> and you will see the search results.
|
||||||
|
|
||||||
|
To go to the next hit use "<n>" 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 <Enter> to select a cell, move around with cursor keys, then press <Enter> again
|
||||||
|
// Go back/up a level: Press <Esc>
|
||||||
|
// Transpose data (flip rows and columns): Press "t"
|
||||||
|
// Expand data (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
|
||||||
|
|
||||||
|
// ## Search
|
||||||
|
|
||||||
|
// Most commands support search via regular expressions.
|
||||||
|
|
||||||
|
// You can type "/" and type a pattern you want to search on.
|
||||||
|
// Then hit <Enter> and you will see the search results.
|
||||||
|
|
||||||
|
// To go to the next hit use "<n>" key.
|
||||||
|
|
||||||
|
// You also can do a reverse search by using "?" instead of "/".
|
||||||
|
// "#;
|
||||||
|
|
||||||
impl ViewCommand for HelpCmd {
|
impl ViewCommand for HelpCmd {
|
||||||
type View = HelpView<'static>;
|
type View = Preview;
|
||||||
|
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
Self::NAME
|
Self::NAME
|
||||||
|
@ -80,260 +95,11 @@ impl ViewCommand for HelpCmd {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
fn help(&self) -> Option<HelpManual> {
|
fn parse(&mut self, _: &str) -> Result<()> {
|
||||||
#[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);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn(&mut self, _: &EngineState, _: &mut Stack, _: Option<Value>) -> Result<Self::View> {
|
fn spawn(&mut self, _: &EngineState, _: &mut Stack, _: Option<Value>) -> Result<Self::View> {
|
||||||
if self.input_command.is_empty() {
|
Ok(HelpCmd::view())
|
||||||
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<String, Vec<String>>,
|
|
||||||
) -> (Vec<String>, Vec<Vec<Value>>) {
|
|
||||||
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<String>, Vec<Vec<Value>>) {
|
|
||||||
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<Transition> {
|
|
||||||
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<crate::nu_common::NuText> {
|
|
||||||
match self {
|
|
||||||
HelpView::Records(v) => v.collect_data(),
|
|
||||||
HelpView::Preview(v) => v.collect_data(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exit(&mut self) -> Option<Value> {
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use super::pager::{Pager, Transition};
|
use super::pager::{Pager, Transition};
|
||||||
|
use anyhow::Result;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
Value,
|
Value,
|
||||||
};
|
};
|
||||||
use std::{borrow::Cow, io::Result};
|
|
||||||
|
|
||||||
mod expand;
|
mod expand;
|
||||||
mod help;
|
mod help;
|
||||||
|
@ -24,8 +24,6 @@ pub trait SimpleCommand {
|
||||||
|
|
||||||
fn usage(&self) -> &'static str;
|
fn usage(&self) -> &'static str;
|
||||||
|
|
||||||
fn help(&self) -> Option<HelpManual>;
|
|
||||||
|
|
||||||
fn parse(&mut self, args: &str) -> Result<()>;
|
fn parse(&mut self, args: &str) -> Result<()>;
|
||||||
|
|
||||||
fn react(
|
fn react(
|
||||||
|
@ -44,8 +42,6 @@ pub trait ViewCommand {
|
||||||
|
|
||||||
fn usage(&self) -> &'static str;
|
fn usage(&self) -> &'static str;
|
||||||
|
|
||||||
fn help(&self) -> Option<HelpManual>;
|
|
||||||
|
|
||||||
fn parse(&mut self, args: &str) -> Result<()>;
|
fn parse(&mut self, args: &str) -> Result<()>;
|
||||||
|
|
||||||
fn spawn(
|
fn spawn(
|
||||||
|
@ -56,116 +52,9 @@ pub trait ViewCommand {
|
||||||
) -> Result<Self::View>;
|
) -> Result<Self::View>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub struct HelpManual {
|
|
||||||
pub name: &'static str,
|
|
||||||
pub description: &'static str,
|
|
||||||
pub arguments: Vec<HelpExample>,
|
|
||||||
pub examples: Vec<HelpExample>,
|
|
||||||
pub config_options: Vec<ConfigOption>,
|
|
||||||
pub input: Vec<Shortcode>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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<Cow<'static, str>>,
|
|
||||||
description: impl Into<Cow<'static, str>>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
example: example.into(),
|
|
||||||
description: description.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct Shortcode {
|
pub struct Shortcode {
|
||||||
pub code: &'static str,
|
pub code: &'static str,
|
||||||
pub context: &'static str,
|
pub context: &'static str,
|
||||||
pub description: &'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<HelpExample>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigOption {
|
|
||||||
pub fn new<N, D, K>(group: N, description: D, key: K, values: Vec<HelpExample>) -> Self
|
|
||||||
where
|
|
||||||
N: Into<String>,
|
|
||||||
D: Into<String>,
|
|
||||||
K: Into<String>,
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
group: group.into(),
|
|
||||||
description: description.into(),
|
|
||||||
key: key.into(),
|
|
||||||
values,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn boolean<N, D, K>(group: N, description: D, key: K) -> Self
|
|
||||||
where
|
|
||||||
N: Into<String>,
|
|
||||||
D: Into<String>,
|
|
||||||
K: Into<String>,
|
|
||||||
{
|
|
||||||
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<HelpExample> {
|
|
||||||
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<HelpExample> {
|
|
||||||
(0..20)
|
|
||||||
.map(|i| HelpExample::new(i.to_string(), format!("A value equal to {i}")))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use super::{HelpExample, HelpManual, ViewCommand};
|
use super::ViewCommand;
|
||||||
use crate::{
|
use crate::{
|
||||||
nu_common::{collect_pipeline, has_simple_value, run_command_with_value},
|
nu_common::{collect_pipeline, has_simple_value, run_command_with_value},
|
||||||
pager::Frame,
|
pager::Frame,
|
||||||
views::{Layout, Orientation, Preview, RecordView, View, ViewConfig},
|
views::{Layout, Orientation, Preview, RecordView, View, ViewConfig},
|
||||||
};
|
};
|
||||||
|
use anyhow::Result;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
PipelineData, Value,
|
PipelineData, Value,
|
||||||
};
|
};
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use std::io::{self, Result};
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct NuCmd {
|
pub struct NuCmd {
|
||||||
|
@ -37,30 +37,6 @@ impl ViewCommand for NuCmd {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
fn help(&self) -> Option<HelpManual> {
|
|
||||||
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<()> {
|
fn parse(&mut self, args: &str) -> Result<()> {
|
||||||
args.trim().clone_into(&mut self.command);
|
args.trim().clone_into(&mut self.command);
|
||||||
|
|
||||||
|
@ -75,12 +51,11 @@ impl ViewCommand for NuCmd {
|
||||||
) -> Result<Self::View> {
|
) -> Result<Self::View> {
|
||||||
let value = value.unwrap_or_default();
|
let value = value.unwrap_or_default();
|
||||||
|
|
||||||
let pipeline = run_command_with_value(&self.command, &value, engine_state, stack)
|
let pipeline = run_command_with_value(&self.command, &value, engine_state, stack)?;
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
|
||||||
|
|
||||||
let is_record = matches!(pipeline, PipelineData::Value(Value::Record { .. }, ..));
|
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) {
|
if let Some(value) = has_simple_value(&values) {
|
||||||
let text = value.to_abbreviated_string(&engine_state.config);
|
let text = value.to_abbreviated_string(&engine_state.config);
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use super::{HelpManual, SimpleCommand};
|
use super::SimpleCommand;
|
||||||
use crate::pager::{Pager, Transition};
|
use crate::pager::{Pager, Transition};
|
||||||
|
use anyhow::Result;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
Value,
|
Value,
|
||||||
};
|
};
|
||||||
use std::io::Result;
|
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct QuitCmd;
|
pub struct QuitCmd;
|
||||||
|
@ -22,17 +22,6 @@ impl SimpleCommand for QuitCmd {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
fn help(&self) -> Option<HelpManual> {
|
|
||||||
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<()> {
|
fn parse(&mut self, _: &str) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
use super::{
|
use super::ViewCommand;
|
||||||
default_color_list, default_int_list, ConfigOption, HelpExample, HelpManual, Shortcode,
|
|
||||||
ViewCommand,
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
nu_common::collect_input,
|
nu_common::collect_input,
|
||||||
views::{Orientation, RecordView},
|
views::{Orientation, RecordView},
|
||||||
};
|
};
|
||||||
|
use anyhow::Result;
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
Value,
|
Value,
|
||||||
};
|
};
|
||||||
use std::io::Result;
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct TableCmd {
|
pub struct TableCmd {
|
||||||
|
@ -52,60 +49,6 @@ impl ViewCommand for TableCmd {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
fn help(&self) -> Option<HelpManual> {
|
|
||||||
#[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<()> {
|
fn parse(&mut self, _: &str) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -119,7 +62,7 @@ impl ViewCommand for TableCmd {
|
||||||
let value = value.unwrap_or_default();
|
let value = value.unwrap_or_default();
|
||||||
let is_record = matches!(value, Value::Record { .. });
|
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);
|
let mut view = RecordView::new(columns, data);
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use super::{default_color_list, ConfigOption, HelpExample, HelpManual, Shortcode, ViewCommand};
|
use super::ViewCommand;
|
||||||
use crate::views::InteractiveView;
|
use crate::views::InteractiveView;
|
||||||
|
use anyhow::Result;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
Value,
|
Value,
|
||||||
};
|
};
|
||||||
use std::io::{Error, ErrorKind, Result};
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct TryCmd {
|
pub struct TryCmd {
|
||||||
|
@ -32,37 +32,6 @@ impl ViewCommand for TryCmd {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
fn help(&self) -> Option<HelpManual> {
|
|
||||||
#[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<()> {
|
fn parse(&mut self, args: &str) -> Result<()> {
|
||||||
args.trim().clone_into(&mut self.command);
|
args.trim().clone_into(&mut self.command);
|
||||||
|
|
||||||
|
@ -78,8 +47,7 @@ impl ViewCommand for TryCmd {
|
||||||
let value = value.unwrap_or_default();
|
let value = value.unwrap_or_default();
|
||||||
let mut view = InteractiveView::new(value);
|
let mut view = InteractiveView::new(value);
|
||||||
view.init(self.command.clone());
|
view.init(self.command.clone());
|
||||||
view.try_run(engine_state, stack)
|
view.try_run(engine_state, stack)?;
|
||||||
.map_err(|e| Error::new(ErrorKind::Other, e))?;
|
|
||||||
|
|
||||||
Ok(view)
|
Ok(view)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,9 +36,9 @@ impl Command for Explore {
|
||||||
)
|
)
|
||||||
.switch("index", "Show row indexes when viewing a list", Some('i'))
|
.switch("index", "Show row indexes when viewing a list", Some('i'))
|
||||||
.switch(
|
.switch(
|
||||||
"reverse",
|
"tail",
|
||||||
"Start with the viewport scrolled to the bottom",
|
"Start with the viewport scrolled to the bottom",
|
||||||
Some('r'),
|
Some('t'),
|
||||||
)
|
)
|
||||||
.switch(
|
.switch(
|
||||||
"peek",
|
"peek",
|
||||||
|
@ -61,7 +61,7 @@ impl Command for Explore {
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let show_head: bool = call.get_flag(engine_state, stack, "head")?.unwrap_or(true);
|
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 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 peek_value: bool = call.has_flag(engine_state, stack, "peek")?;
|
||||||
|
|
||||||
let ctrlc = engine_state.ctrlc.clone();
|
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);
|
let mut config = PagerConfig::new(nu_config, &style_computer, &lscolors, config);
|
||||||
config.style = style;
|
config.style = style;
|
||||||
config.reverse = is_reverse;
|
|
||||||
config.peek_value = peek_value;
|
config.peek_value = peek_value;
|
||||||
config.reverse = is_reverse;
|
config.tail = tail;
|
||||||
|
|
||||||
let result = run_pager(engine_state, &mut stack.clone(), ctrlc, input, config);
|
let result = run_pager(engine_state, &mut stack.clone(), ctrlc, input, config);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(Some(value)) => Ok(PipelineData::Value(value, None)),
|
Ok(Some(value)) => Ok(PipelineData::Value(value, None)),
|
||||||
Ok(None) => Ok(PipelineData::Value(Value::default(), None)),
|
Ok(None) => Ok(PipelineData::Value(Value::default(), None)),
|
||||||
Err(err) => Ok(PipelineData::Value(
|
Err(err) => {
|
||||||
Value::error(err.into(), call.head),
|
let shell_error = match err.downcast::<ShellError>() {
|
||||||
None,
|
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,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,20 +6,19 @@ mod pager;
|
||||||
mod registry;
|
mod registry;
|
||||||
mod views;
|
mod views;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use commands::{ExpandCmd, HelpCmd, NuCmd, QuitCmd, TableCmd, TryCmd};
|
||||||
pub use default_context::add_explore_context;
|
pub use default_context::add_explore_context;
|
||||||
pub use explore::Explore;
|
pub use explore::Explore;
|
||||||
|
|
||||||
use commands::{ExpandCmd, HelpCmd, HelpManual, NuCmd, QuitCmd, TableCmd, TryCmd};
|
|
||||||
use nu_common::{collect_pipeline, has_simple_value, CtrlC};
|
use nu_common::{collect_pipeline, has_simple_value, CtrlC};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
PipelineData, Value,
|
PipelineData, Value,
|
||||||
};
|
};
|
||||||
use pager::{Page, Pager, PagerConfig, StyleConfig};
|
use pager::{Page, Pager, PagerConfig, StyleConfig};
|
||||||
use registry::{Command, CommandRegistry};
|
use registry::CommandRegistry;
|
||||||
use std::io;
|
|
||||||
use terminal_size::{Height, Width};
|
use terminal_size::{Height, Width};
|
||||||
use views::{BinaryView, InformationView, Orientation, Preview, RecordView};
|
use views::{BinaryView, Orientation, Preview, RecordView};
|
||||||
|
|
||||||
mod util {
|
mod util {
|
||||||
pub use super::nu_common::{create_lscolors, create_map, map_into_value};
|
pub use super::nu_common::{create_lscolors, create_map, map_into_value};
|
||||||
|
@ -31,7 +30,7 @@ fn run_pager(
|
||||||
ctrlc: CtrlC,
|
ctrlc: CtrlC,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
config: PagerConfig,
|
config: PagerConfig,
|
||||||
) -> io::Result<Option<Value>> {
|
) -> Result<Option<Value>> {
|
||||||
let mut p = Pager::new(config.clone());
|
let mut p = Pager::new(config.clone());
|
||||||
let commands = create_command_registry();
|
let commands = create_command_registry();
|
||||||
|
|
||||||
|
@ -45,18 +44,18 @@ fn run_pager(
|
||||||
return p.run(engine_state, stack, ctrlc, view, commands);
|
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();
|
let has_no_input = columns.is_empty() && data.is_empty();
|
||||||
if has_no_input {
|
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");
|
p.show_message("For help type :help");
|
||||||
|
|
||||||
if let Some(value) = has_simple_value(&data) {
|
if let Some(value) = has_simple_value(&data) {
|
||||||
let text = value.to_abbreviated_string(config.nu_config);
|
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);
|
return p.run(engine_state, stack, ctrlc, view, commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +66,7 @@ fn run_pager(
|
||||||
fn create_record_view(
|
fn create_record_view(
|
||||||
columns: Vec<String>,
|
columns: Vec<String>,
|
||||||
data: Vec<Vec<Value>>,
|
data: Vec<Vec<Value>>,
|
||||||
|
// wait, why would we use RecordView for something that isn't a record?
|
||||||
is_record: bool,
|
is_record: bool,
|
||||||
config: PagerConfig,
|
config: PagerConfig,
|
||||||
) -> Option<Page> {
|
) -> Option<Page> {
|
||||||
|
@ -75,17 +75,17 @@ fn create_record_view(
|
||||||
view.set_orientation_current(Orientation::Left);
|
view.set_orientation_current(Orientation::Left);
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.reverse {
|
if config.tail {
|
||||||
if let Some((Width(w), Height(h))) = terminal_size::terminal_size() {
|
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<Page> {
|
fn help_view() -> Option<Page> {
|
||||||
Some(Page::new(InformationView, true))
|
Some(Page::new(HelpCmd::view(), false))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn binary_view(input: PipelineData) -> Option<Page> {
|
fn binary_view(input: PipelineData) -> Option<Page> {
|
||||||
|
@ -96,7 +96,7 @@ fn binary_view(input: PipelineData) -> Option<Page> {
|
||||||
|
|
||||||
let view = BinaryView::new(data);
|
let view = BinaryView::new(data);
|
||||||
|
|
||||||
Some(Page::new(view, false))
|
Some(Page::new(view, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_command_registry() -> CommandRegistry {
|
fn create_command_registry() -> CommandRegistry {
|
||||||
|
@ -104,24 +104,16 @@ fn create_command_registry() -> CommandRegistry {
|
||||||
create_commands(&mut registry);
|
create_commands(&mut registry);
|
||||||
create_aliases(&mut registry);
|
create_aliases(&mut registry);
|
||||||
|
|
||||||
// reregister help && config commands
|
|
||||||
let commands = registry.get_commands().cloned().collect::<Vec<_>>();
|
|
||||||
let aliases = registry.get_aliases().collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let help_cmd = create_help_command(&commands, &aliases);
|
|
||||||
|
|
||||||
registry.register_command_view(help_cmd, true);
|
|
||||||
|
|
||||||
registry
|
registry
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_commands(registry: &mut CommandRegistry) {
|
fn create_commands(registry: &mut CommandRegistry) {
|
||||||
registry.register_command_view(NuCmd::new(), false);
|
registry.register_command_view(NuCmd::new(), true);
|
||||||
registry.register_command_view(TableCmd::new(), false);
|
registry.register_command_view(TableCmd::new(), true);
|
||||||
|
|
||||||
registry.register_command_view(ExpandCmd::new(), true);
|
registry.register_command_view(ExpandCmd::new(), false);
|
||||||
registry.register_command_view(TryCmd::new(), true);
|
registry.register_command_view(TryCmd::new(), false);
|
||||||
registry.register_command_view(HelpCmd::default(), true);
|
registry.register_command_view(HelpCmd::default(), false);
|
||||||
|
|
||||||
registry.register_command_reactive(QuitCmd);
|
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);
|
||||||
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<HelpManual> {
|
|
||||||
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<HelpManual>, name: &'static str) -> HelpManual {
|
|
||||||
manual.unwrap_or(HelpManual {
|
|
||||||
name,
|
|
||||||
..HelpManual::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
use super::NuSpan;
|
use super::NuSpan;
|
||||||
|
use anyhow::Result;
|
||||||
use nu_engine::get_columns;
|
use nu_engine::get_columns;
|
||||||
use nu_protocol::{record, ListStream, PipelineData, PipelineMetadata, RawStream, Value};
|
use nu_protocol::{record, ListStream, PipelineData, PipelineMetadata, RawStream, Value};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub fn collect_pipeline(input: PipelineData) -> (Vec<String>, Vec<Vec<Value>>) {
|
pub fn collect_pipeline(input: PipelineData) -> Result<(Vec<String>, Vec<Vec<Value>>)> {
|
||||||
match input {
|
match input {
|
||||||
PipelineData::Empty => (vec![], vec![]),
|
PipelineData::Empty => Ok((vec![], vec![])),
|
||||||
PipelineData::Value(value, ..) => collect_input(value),
|
PipelineData::Value(value, ..) => collect_input(value),
|
||||||
PipelineData::ListStream(stream, ..) => collect_list_stream(stream),
|
PipelineData::ListStream(stream, ..) => Ok(collect_list_stream(stream)),
|
||||||
PipelineData::ExternalStream {
|
PipelineData::ExternalStream {
|
||||||
stdout,
|
stdout,
|
||||||
stderr,
|
stderr,
|
||||||
|
@ -15,7 +16,9 @@ pub fn collect_pipeline(input: PipelineData) -> (Vec<String>, Vec<Vec<Value>>) {
|
||||||
metadata,
|
metadata,
|
||||||
span,
|
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.
|
/// Try to build column names and a table grid.
|
||||||
pub fn collect_input(value: Value) -> (Vec<String>, Vec<Vec<Value>>) {
|
pub fn collect_input(value: Value) -> Result<(Vec<String>, Vec<Vec<Value>>)> {
|
||||||
let span = value.span();
|
let span = value.span();
|
||||||
match value {
|
match value {
|
||||||
Value::Record { val: record, .. } => {
|
Value::Record { val: record, .. } => {
|
||||||
let (key, val) = record.into_owned().into_iter().unzip();
|
let (key, val) = record.into_owned().into_iter().unzip();
|
||||||
(key, vec![val])
|
Ok((key, vec![val]))
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
let mut columns = get_columns(&vals);
|
let mut columns = get_columns(&vals);
|
||||||
|
@ -98,7 +101,7 @@ pub fn collect_input(value: Value) -> (Vec<String>, Vec<Vec<Value>>) {
|
||||||
columns = vec![String::from("")];
|
columns = vec![String::from("")];
|
||||||
}
|
}
|
||||||
|
|
||||||
(columns, data)
|
Ok((columns, data))
|
||||||
}
|
}
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => {
|
||||||
let lines = val
|
let lines = val
|
||||||
|
@ -107,17 +110,18 @@ pub fn collect_input(value: Value) -> (Vec<String>, Vec<Vec<Value>>) {
|
||||||
.map(|val| vec![val])
|
.map(|val| vec![val])
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
(vec![String::from("")], lines)
|
Ok((vec![String::from("")], lines))
|
||||||
}
|
}
|
||||||
Value::LazyRecord { val, .. } => match val.collect() {
|
Value::LazyRecord { val, .. } => {
|
||||||
Ok(value) => collect_input(value),
|
let materialized = val.collect()?;
|
||||||
Err(_) => (
|
collect_input(materialized)
|
||||||
vec![String::from("")],
|
}
|
||||||
vec![vec![Value::lazy_record(val, span)]],
|
Value::Nothing { .. } => Ok((vec![], vec![])),
|
||||||
),
|
Value::Custom { val, .. } => {
|
||||||
},
|
let materialized = val.to_base_value(span)?;
|
||||||
Value::Nothing { .. } => (vec![], vec![]),
|
collect_input(materialized)
|
||||||
value => (vec![String::from("")], vec![vec![value]]),
|
}
|
||||||
|
value => Ok((vec![String::from("")], vec![vec![value]])),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ use crate::{
|
||||||
util::map_into_value,
|
util::map_into_value,
|
||||||
views::{util::nu_style_to_tui, ViewConfig},
|
views::{util::nu_style_to_tui, ViewConfig},
|
||||||
};
|
};
|
||||||
|
use anyhow::Result;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{KeyCode, KeyEvent, KeyModifiers},
|
event::{KeyCode, KeyEvent, KeyModifiers},
|
||||||
execute,
|
execute,
|
||||||
|
@ -34,7 +35,7 @@ use ratatui::{backend::CrosstermBackend, layout::Rect, widgets::Block};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::min,
|
cmp::min,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
io::{self, Result, Stdout},
|
io::{self, Stdout},
|
||||||
result,
|
result,
|
||||||
sync::atomic::Ordering,
|
sync::atomic::Ordering,
|
||||||
};
|
};
|
||||||
|
@ -143,6 +144,7 @@ impl<'a> Pager<'a> {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Transition {
|
pub enum Transition {
|
||||||
|
// TODO: should we add a noop transition instead of doing Option<Transition> everywhere?
|
||||||
Ok,
|
Ok,
|
||||||
Exit,
|
Exit,
|
||||||
Cmd(String),
|
Cmd(String),
|
||||||
|
@ -155,8 +157,9 @@ pub struct PagerConfig<'a> {
|
||||||
pub lscolors: &'a LsColors,
|
pub lscolors: &'a LsColors,
|
||||||
pub config: ConfigMap,
|
pub config: ConfigMap,
|
||||||
pub style: StyleConfig,
|
pub style: StyleConfig,
|
||||||
|
// If true, when quitting output the value of the cell the cursor was on
|
||||||
pub peek_value: bool,
|
pub peek_value: bool,
|
||||||
pub reverse: bool,
|
pub tail: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PagerConfig<'a> {
|
impl<'a> PagerConfig<'a> {
|
||||||
|
@ -172,7 +175,7 @@ impl<'a> PagerConfig<'a> {
|
||||||
config,
|
config,
|
||||||
lscolors,
|
lscolors,
|
||||||
peek_value: false,
|
peek_value: false,
|
||||||
reverse: false,
|
tail: false,
|
||||||
style: StyleConfig::default(),
|
style: StyleConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,7 +250,7 @@ fn render_ui(
|
||||||
{
|
{
|
||||||
let info = info.clone();
|
let info = info.clone();
|
||||||
term.draw(|f| {
|
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,
|
info,
|
||||||
&mut pager.search_buf,
|
&mut pager.search_buf,
|
||||||
&mut pager.cmd_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 {
|
if let Some(transition) = transition {
|
||||||
|
@ -302,7 +305,7 @@ fn render_ui(
|
||||||
match out {
|
match out {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
if result.exit {
|
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() {
|
if result.view_change && !result.cmd_name.is_empty() {
|
||||||
|
@ -336,21 +339,21 @@ fn react_to_event_result(
|
||||||
) -> (Option<Option<Value>>, String) {
|
) -> (Option<Option<Value>>, String) {
|
||||||
match status {
|
match status {
|
||||||
Transition::Exit => (
|
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(),
|
String::default(),
|
||||||
),
|
),
|
||||||
Transition::Ok => {
|
Transition::Ok => {
|
||||||
let exit = view_stack.stack.is_empty();
|
let exit = view_stack.stack.is_empty();
|
||||||
if exit {
|
if exit {
|
||||||
return (
|
return (
|
||||||
Some(peak_value_from_view(&mut view_stack.view, pager)),
|
Some(peek_value_from_view(&mut view_stack.curr_view, pager)),
|
||||||
String::default(),
|
String::default(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to pop the view stack
|
// try to pop the view stack
|
||||||
if let Some(v) = view_stack.stack.pop() {
|
if let Some(v) = view_stack.stack.pop() {
|
||||||
view_stack.view = Some(v);
|
view_stack.curr_view = Some(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
(None, String::default())
|
(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);
|
let out = pager_run_command(engine_state, stack, pager, view_stack, commands, cmd);
|
||||||
match out {
|
match out {
|
||||||
Ok(result) if result.exit => (
|
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(),
|
String::default(),
|
||||||
),
|
),
|
||||||
Ok(result) => (None, result.cmd_name),
|
Ok(result) => (None, result.cmd_name),
|
||||||
|
@ -372,9 +375,13 @@ fn react_to_event_result(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn peak_value_from_view(view: &mut Option<Page>, pager: &mut Pager<'_>) -> Option<Value> {
|
fn peek_value_from_view(view: &mut Option<Page>, pager: &mut Pager<'_>) -> Option<Value> {
|
||||||
let view = view.as_mut().map(|p| &mut p.view);
|
if pager.config.peek_value {
|
||||||
try_to_peek_value(pager, view)
|
let view = view.as_mut().map(|p| &mut p.view);
|
||||||
|
view.and_then(|v| v.exit())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_frame(
|
fn draw_frame(
|
||||||
|
@ -453,7 +460,7 @@ fn run_command(
|
||||||
match command {
|
match command {
|
||||||
Command::Reactive(mut command) => {
|
Command::Reactive(mut command) => {
|
||||||
// what we do we just replace the view.
|
// 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)?;
|
let transition = command.react(engine_state, stack, pager, value)?;
|
||||||
match transition {
|
match transition {
|
||||||
Transition::Ok => {
|
Transition::Ok => {
|
||||||
|
@ -470,18 +477,18 @@ fn run_command(
|
||||||
Transition::Cmd { .. } => todo!("not used so far"),
|
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.
|
// 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)?;
|
let mut new_view = cmd.spawn(engine_state, stack, value)?;
|
||||||
if let Some(view) = view_stack.view.take() {
|
if let Some(view) = view_stack.curr_view.take() {
|
||||||
if !view.is_light {
|
if !view.stackable {
|
||||||
view_stack.stack.push(view);
|
view_stack.stack.push(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update_view_setup(&mut new_view, &pager.config);
|
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()))
|
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<'_>) {
|
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);
|
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<V>(pager: &mut Pager, view: Option<&mut V>) -> Option<Value>
|
|
||||||
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) {
|
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 msg_style = report_msg_style(&report, theme, theme.status_bar_text);
|
||||||
let mut status_bar = create_status_bar(report);
|
let mut status_bar = create_status_bar(report);
|
||||||
|
@ -1092,30 +1088,35 @@ impl Position {
|
||||||
|
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
pub view: Box<dyn View>,
|
pub view: Box<dyn View>,
|
||||||
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 {
|
impl Page {
|
||||||
pub fn raw(view: Box<dyn View>, is_light: bool) -> Self {
|
pub fn raw(view: Box<dyn View>, stackable: bool) -> Self {
|
||||||
Self { view, is_light }
|
Self { view, stackable }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new<V>(view: V, is_light: bool) -> Self
|
pub fn new<V>(view: V, stackable: bool) -> Self
|
||||||
where
|
where
|
||||||
V: View + 'static,
|
V: View + 'static,
|
||||||
{
|
{
|
||||||
Self::raw(Box::new(view), is_light)
|
Self::raw(Box::new(view), stackable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ViewStack {
|
struct ViewStack {
|
||||||
view: Option<Page>,
|
curr_view: Option<Page>,
|
||||||
stack: Vec<Page>,
|
stack: Vec<Page>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViewStack {
|
impl ViewStack {
|
||||||
fn new(view: Option<Page>, stack: Vec<Page>) -> Self {
|
fn new(view: Option<Page>, stack: Vec<Page>) -> Self {
|
||||||
Self { view, stack }
|
Self {
|
||||||
|
curr_view: view,
|
||||||
|
stack,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,27 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{HelpManual, SimpleCommand, ViewCommand},
|
commands::{SimpleCommand, ViewCommand},
|
||||||
views::View,
|
views::View,
|
||||||
};
|
};
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
Reactive(Box<dyn SCommand>),
|
Reactive(Box<dyn SCommand>),
|
||||||
View {
|
View {
|
||||||
cmd: Box<dyn VCommand>,
|
cmd: Box<dyn VCommand>,
|
||||||
is_light: bool,
|
stackable: bool,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
pub fn view<C>(command: C, is_light: bool) -> Self
|
pub fn view<C>(command: C, stackable: bool) -> Self
|
||||||
where
|
where
|
||||||
C: ViewCommand + Clone + 'static,
|
C: ViewCommand + Clone + 'static,
|
||||||
C::View: View,
|
C::View: View,
|
||||||
{
|
{
|
||||||
let cmd = Box::new(ViewCmd(command)) as Box<dyn VCommand>;
|
let cmd = Box::new(ViewCmd(command)) as Box<dyn VCommand>;
|
||||||
|
|
||||||
Self::View { cmd, is_light }
|
Self::View { cmd, stackable }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reactive<C>(command: C) -> Self
|
pub fn reactive<C>(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 {
|
match self {
|
||||||
Command::Reactive(cmd) => cmd.parse(args),
|
Command::Reactive(cmd) => cmd.parse(args),
|
||||||
Command::View { cmd, .. } => cmd.parse(args),
|
Command::View { cmd, .. } => cmd.parse(args),
|
||||||
|
@ -68,11 +69,7 @@ where
|
||||||
self.0.usage()
|
self.0.usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn help(&self) -> Option<HelpManual> {
|
fn parse(&mut self, args: &str) -> Result<()> {
|
||||||
self.0.help()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse(&mut self, args: &str) -> std::io::Result<()> {
|
|
||||||
self.0.parse(args)
|
self.0.parse(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +78,7 @@ where
|
||||||
engine_state: &nu_protocol::engine::EngineState,
|
engine_state: &nu_protocol::engine::EngineState,
|
||||||
stack: &mut nu_protocol::engine::Stack,
|
stack: &mut nu_protocol::engine::Stack,
|
||||||
value: Option<nu_protocol::Value>,
|
value: Option<nu_protocol::Value>,
|
||||||
) -> std::io::Result<Self::View> {
|
) -> Result<Self::View> {
|
||||||
let view = self.0.spawn(engine_state, stack, value)?;
|
let view = self.0.spawn(engine_state, stack, value)?;
|
||||||
Ok(Box::new(view) as Box<dyn View>)
|
Ok(Box::new(view) as Box<dyn View>)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ use crate::{
|
||||||
commands::{SimpleCommand, ViewCommand},
|
commands::{SimpleCommand, ViewCommand},
|
||||||
views::View,
|
views::View,
|
||||||
};
|
};
|
||||||
|
use anyhow::Result;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
@ -25,14 +26,14 @@ impl CommandRegistry {
|
||||||
.insert(Cow::Owned(command.name().to_owned()), command);
|
.insert(Cow::Owned(command.name().to_owned()), command);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_command_view<C>(&mut self, command: C, is_light: bool)
|
pub fn register_command_view<C>(&mut self, command: C, stackable: bool)
|
||||||
where
|
where
|
||||||
C: ViewCommand + Clone + 'static,
|
C: ViewCommand + Clone + 'static,
|
||||||
C::View: View,
|
C::View: View,
|
||||||
{
|
{
|
||||||
self.commands.insert(
|
self.commands.insert(
|
||||||
Cow::Owned(command.name().to_owned()),
|
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<std::io::Result<Command>> {
|
pub fn find(&self, args: &str) -> Option<Result<Command>> {
|
||||||
let cmd = args.split_once(' ').map_or(args, |(cmd, _)| cmd);
|
let cmd = args.split_once(' ').map_or(args, |(cmd, _)| cmd);
|
||||||
let args = &args[cmd.len()..];
|
let args = &args[cmd.len()..];
|
||||||
|
|
||||||
|
|
|
@ -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<Enter> for help",
|
|
||||||
"type :q<Enter> to exit",
|
|
||||||
"type :try<Enter> 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<Transition> {
|
|
||||||
match event.code {
|
|
||||||
crossterm::event::KeyCode::Esc => Some(Transition::Exit),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_data(&self) -> Vec<NuText> {
|
|
||||||
Self::MESSAGE
|
|
||||||
.into_iter()
|
|
||||||
.map(|line| (line.to_owned(), TextStyle::default()))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,6 +8,7 @@ use crate::{
|
||||||
pager::{report::Report, Frame, Transition, ViewInfo},
|
pager::{report::Report, Frame, Transition, ViewInfo},
|
||||||
util::create_map,
|
util::create_map,
|
||||||
};
|
};
|
||||||
|
use anyhow::Result;
|
||||||
use crossterm::event::{KeyCode, KeyEvent};
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
use nu_color_config::get_color_map;
|
use nu_color_config::get_color_map;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
|
@ -50,7 +51,7 @@ impl<'a> InteractiveView<'a> {
|
||||||
self.command = command;
|
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)?;
|
let mut view = run_command(&self.command, &self.input, engine_state, stack)?;
|
||||||
view.set_theme(self.table_theme.clone());
|
view.set_theme(self.table_theme.clone());
|
||||||
|
|
||||||
|
@ -281,13 +282,12 @@ fn run_command(
|
||||||
input: &Value,
|
input: &Value,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
) -> Result<RecordView<'static>, String> {
|
) -> Result<RecordView<'static>> {
|
||||||
let pipeline =
|
let pipeline = run_command_with_value(command, input, engine_state, stack)?;
|
||||||
run_command_with_value(command, input, engine_state, stack).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let is_record = matches!(pipeline, PipelineData::Value(Value::Record { .. }, ..));
|
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);
|
let mut view = RecordView::new(columns, values);
|
||||||
if is_record {
|
if is_record {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
mod binary;
|
mod binary;
|
||||||
mod coloredtextw;
|
mod coloredtextw;
|
||||||
mod cursor;
|
mod cursor;
|
||||||
mod information;
|
|
||||||
mod interactive;
|
mod interactive;
|
||||||
mod preview;
|
mod preview;
|
||||||
mod record;
|
mod record;
|
||||||
|
@ -22,7 +21,6 @@ use nu_protocol::{
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
|
|
||||||
pub use binary::BinaryView;
|
pub use binary::BinaryView;
|
||||||
pub use information::InformationView;
|
|
||||||
pub use interactive::InteractiveView;
|
pub use interactive::InteractiveView;
|
||||||
pub use preview::Preview;
|
pub use preview::Preview;
|
||||||
pub use record::{Orientation, RecordView};
|
pub use record::{Orientation, RecordView};
|
||||||
|
|
|
@ -15,6 +15,7 @@ use crate::{
|
||||||
util::create_map,
|
util::create_map,
|
||||||
views::ElementInfo,
|
views::ElementInfo,
|
||||||
};
|
};
|
||||||
|
use anyhow::Result;
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
use nu_color_config::{get_color_map, StyleComputer};
|
use nu_color_config::{get_color_map, StyleComputer};
|
||||||
use nu_protocol::{
|
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 =
|
let page_size =
|
||||||
estimate_page_size(Rect::new(0, 0, width, height), self.theme.table.show_header);
|
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) {
|
pub fn set_style_split_line(&mut self, style: NuStyle) {
|
||||||
|
@ -266,16 +267,26 @@ impl View for RecordView<'_> {
|
||||||
key: KeyEvent,
|
key: KeyEvent,
|
||||||
) -> Option<Transition> {
|
) -> Option<Transition> {
|
||||||
let result = match self.mode {
|
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),
|
UIMode::Cursor => handle_key_event_cursor_mode(self, &key),
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches!(&result, Some(Transition::Ok) | Some(Transition::Cmd { .. })) {
|
match result {
|
||||||
let report = self.create_records_report();
|
Ok(result) => {
|
||||||
info.status = Some(report);
|
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<NuText> {
|
fn collect_data(&self) -> Vec<NuText> {
|
||||||
|
@ -508,7 +519,10 @@ fn handle_key_event_view_mode(view: &mut RecordView, key: &KeyEvent) -> Option<T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_key_event_cursor_mode(view: &mut RecordView, key: &KeyEvent) -> Option<Transition> {
|
fn handle_key_event_cursor_mode(
|
||||||
|
view: &mut RecordView,
|
||||||
|
key: &KeyEvent,
|
||||||
|
) -> Result<Option<Transition>> {
|
||||||
match key {
|
match key {
|
||||||
KeyEvent {
|
KeyEvent {
|
||||||
code: KeyCode::Char('u'),
|
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();
|
view.get_layer_last_mut().cursor.prev_row_page();
|
||||||
|
|
||||||
return Some(Transition::Ok);
|
return Ok(Some(Transition::Ok));
|
||||||
}
|
}
|
||||||
KeyEvent {
|
KeyEvent {
|
||||||
code: KeyCode::Char('d'),
|
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();
|
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 => {
|
KeyCode::Esc => {
|
||||||
view.set_view_mode();
|
view.set_view_mode();
|
||||||
|
|
||||||
Some(Transition::Ok)
|
Ok(Some(Transition::Ok))
|
||||||
}
|
}
|
||||||
KeyCode::Up | KeyCode::Char('k') => {
|
KeyCode::Up | KeyCode::Char('k') => {
|
||||||
view.get_layer_last_mut().cursor.prev_row();
|
view.get_layer_last_mut().cursor.prev_row();
|
||||||
|
|
||||||
Some(Transition::Ok)
|
Ok(Some(Transition::Ok))
|
||||||
}
|
}
|
||||||
KeyCode::Down | KeyCode::Char('j') => {
|
KeyCode::Down | KeyCode::Char('j') => {
|
||||||
view.get_layer_last_mut().cursor.next_row();
|
view.get_layer_last_mut().cursor.next_row();
|
||||||
|
|
||||||
Some(Transition::Ok)
|
Ok(Some(Transition::Ok))
|
||||||
}
|
}
|
||||||
KeyCode::Left | KeyCode::Char('h') => {
|
KeyCode::Left | KeyCode::Char('h') => {
|
||||||
view.get_layer_last_mut().cursor.prev_column();
|
view.get_layer_last_mut().cursor.prev_column();
|
||||||
|
|
||||||
Some(Transition::Ok)
|
Ok(Some(Transition::Ok))
|
||||||
}
|
}
|
||||||
KeyCode::Right | KeyCode::Char('l') => {
|
KeyCode::Right | KeyCode::Char('l') => {
|
||||||
view.get_layer_last_mut().cursor.next_column();
|
view.get_layer_last_mut().cursor.next_column();
|
||||||
|
|
||||||
Some(Transition::Ok)
|
Ok(Some(Transition::Ok))
|
||||||
}
|
}
|
||||||
KeyCode::Home | KeyCode::Char('g') => {
|
KeyCode::Home | KeyCode::Char('g') => {
|
||||||
view.get_layer_last_mut().cursor.row_move_to_start();
|
view.get_layer_last_mut().cursor.row_move_to_start();
|
||||||
|
|
||||||
Some(Transition::Ok)
|
Ok(Some(Transition::Ok))
|
||||||
}
|
}
|
||||||
KeyCode::End | KeyCode::Char('G') => {
|
KeyCode::End | KeyCode::Char('G') => {
|
||||||
view.get_layer_last_mut().cursor.row_move_to_end();
|
view.get_layer_last_mut().cursor.row_move_to_end();
|
||||||
|
|
||||||
Some(Transition::Ok)
|
Ok(Some(Transition::Ok))
|
||||||
}
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
let value = view.get_current_value();
|
let value = view.get_current_value();
|
||||||
let is_record = matches!(value, Value::Record { .. });
|
let is_record = matches!(value, Value::Record { .. });
|
||||||
let next_layer = create_layer(value);
|
let next_layer = create_layer(value)?;
|
||||||
|
|
||||||
push_layer(view, next_layer);
|
push_layer(view, next_layer);
|
||||||
|
|
||||||
if is_record {
|
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);
|
view.set_orientation_current(view.orientation);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Transition::Ok)
|
Ok(Some(Transition::Ok))
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_layer(value: Value) -> RecordLayer<'static> {
|
fn create_layer(value: Value) -> Result<RecordLayer<'static>> {
|
||||||
let (columns, values) = collect_input(value);
|
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>) {
|
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
|
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 layer = state.get_layer_last_mut();
|
||||||
let count_rows = layer.records.len();
|
let count_rows = layer.records.len();
|
||||||
if count_rows > page_size {
|
if count_rows > page_size {
|
||||||
|
|
BIN
foo.db
Normal file
BIN
foo.db
Normal file
Binary file not shown.
Loading…
Reference in a new issue