mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +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",
|
||||
]
|
||||
|
||||
[[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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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<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<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -67,27 +44,37 @@ impl ViewCommand for ExpandCmd {
|
|||
stack: &mut Stack,
|
||||
value: Option<Value>,
|
||||
) -> Result<Self::View> {
|
||||
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<String> {
|
||||
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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<HelpManual>,
|
||||
aliases: HashMap<String, Vec<String>>,
|
||||
}
|
||||
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 "<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,
|
||||
}
|
||||
pub fn view() -> Preview {
|
||||
Preview::new(&HELP_MESSAGE)
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_aliases(aliases: &[(&str, &str)]) -> HashMap<String, Vec<String>> {
|
||||
let mut out_aliases: HashMap<String, Vec<String>> = 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<String> = 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 <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 {
|
||||
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<HelpManual> {
|
||||
#[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<Value>) -> Result<Self::View> {
|
||||
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<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),
|
||||
}
|
||||
Ok(HelpCmd::view())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<HelpManual>;
|
||||
|
||||
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<HelpManual>;
|
||||
|
||||
fn parse(&mut self, args: &str) -> Result<()>;
|
||||
|
||||
fn spawn(
|
||||
|
@ -56,116 +52,9 @@ pub trait ViewCommand {
|
|||
) -> 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)]
|
||||
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<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::{
|
||||
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<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<()> {
|
||||
args.trim().clone_into(&mut self.command);
|
||||
|
||||
|
@ -75,12 +51,11 @@ impl ViewCommand for NuCmd {
|
|||
) -> Result<Self::View> {
|
||||
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);
|
||||
|
|
|
@ -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<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<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -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<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<()> {
|
||||
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);
|
||||
|
||||
|
|
|
@ -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<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<()> {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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<PipelineData, ShellError> {
|
||||
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::<ShellError>() {
|
||||
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 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<Option<Value>> {
|
||||
) -> Result<Option<Value>> {
|
||||
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<String>,
|
||||
data: Vec<Vec<Value>>,
|
||||
// wait, why would we use RecordView for something that isn't a record?
|
||||
is_record: bool,
|
||||
config: PagerConfig,
|
||||
) -> Option<Page> {
|
||||
|
@ -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<Page> {
|
||||
Some(Page::new(InformationView, true))
|
||||
fn help_view() -> Option<Page> {
|
||||
Some(Page::new(HelpCmd::view(), false))
|
||||
}
|
||||
|
||||
fn binary_view(input: PipelineData) -> Option<Page> {
|
||||
|
@ -96,7 +96,7 @@ fn binary_view(input: PipelineData) -> Option<Page> {
|
|||
|
||||
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::<Vec<_>>();
|
||||
let aliases = registry.get_aliases().collect::<Vec<_>>();
|
||||
|
||||
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<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 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<String>, Vec<Vec<Value>>) {
|
||||
pub fn collect_pipeline(input: PipelineData) -> Result<(Vec<String>, Vec<Vec<Value>>)> {
|
||||
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<String>, Vec<Vec<Value>>) {
|
|||
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<String>, Vec<Vec<Value>>) {
|
||||
pub fn collect_input(value: Value) -> Result<(Vec<String>, Vec<Vec<Value>>)> {
|
||||
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<String>, Vec<Vec<Value>>) {
|
|||
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<String>, Vec<Vec<Value>>) {
|
|||
.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]])),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Transition> 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<Option<Value>>, 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<Page>, pager: &mut Pager<'_>) -> Option<Value> {
|
||||
let view = view.as_mut().map(|p| &mut p.view);
|
||||
try_to_peek_value(pager, view)
|
||||
fn peek_value_from_view(view: &mut Option<Page>, pager: &mut Pager<'_>) -> Option<Value> {
|
||||
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<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) {
|
||||
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<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 {
|
||||
pub fn raw(view: Box<dyn View>, is_light: bool) -> Self {
|
||||
Self { view, is_light }
|
||||
pub fn raw(view: Box<dyn View>, stackable: bool) -> Self {
|
||||
Self { view, stackable }
|
||||
}
|
||||
|
||||
pub fn new<V>(view: V, is_light: bool) -> Self
|
||||
pub fn new<V>(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<Page>,
|
||||
curr_view: Option<Page>,
|
||||
stack: Vec<Page>,
|
||||
}
|
||||
|
||||
impl ViewStack {
|
||||
fn new(view: Option<Page>, stack: Vec<Page>) -> Self {
|
||||
Self { view, stack }
|
||||
Self {
|
||||
curr_view: view,
|
||||
stack,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<dyn SCommand>),
|
||||
View {
|
||||
cmd: Box<dyn VCommand>,
|
||||
is_light: bool,
|
||||
stackable: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn view<C>(command: C, is_light: bool) -> Self
|
||||
pub fn view<C>(command: C, stackable: bool) -> Self
|
||||
where
|
||||
C: ViewCommand + Clone + 'static,
|
||||
C::View: View,
|
||||
{
|
||||
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
|
||||
|
@ -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<HelpManual> {
|
||||
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<nu_protocol::Value>,
|
||||
) -> std::io::Result<Self::View> {
|
||||
) -> Result<Self::View> {
|
||||
let view = self.0.spawn(engine_state, stack, value)?;
|
||||
Ok(Box::new(view) as Box<dyn View>)
|
||||
}
|
||||
|
|
|
@ -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<C>(&mut self, command: C, is_light: bool)
|
||||
pub fn register_command_view<C>(&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<std::io::Result<Command>> {
|
||||
pub fn find(&self, args: &str) -> Option<Result<Command>> {
|
||||
let cmd = args.split_once(' ').map_or(args, |(cmd, _)| cmd);
|
||||
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},
|
||||
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<RecordView<'static>, String> {
|
||||
let pipeline =
|
||||
run_command_with_value(command, input, engine_state, stack).map_err(|e| e.to_string())?;
|
||||
) -> Result<RecordView<'static>> {
|
||||
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 {
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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<Transition> {
|
||||
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<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 {
|
||||
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<RecordLayer<'static>> {
|
||||
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 {
|
||||
|
|
BIN
foo.db
Normal file
BIN
foo.db
Normal file
Binary file not shown.
Loading…
Reference in a new issue