nu-explore/ A few things (#7339)

ref #7332

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
This commit is contained in:
Maxim Zhiburt
2022-12-16 18:47:07 +03:00
committed by GitHub
parent 2d07c6eedb
commit 9c1a3aa244
41 changed files with 4498 additions and 1587 deletions

View File

@@ -0,0 +1,171 @@
use std::io::Result;
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
use crate::{
nu_common::{nu_str, NuSpan},
registry::Command,
views::{configuration, ConfigurationView, Preview},
};
use super::{default_color_list, ConfigOption, HelpManual, ViewCommand};
#[derive(Default, Clone)]
pub struct ConfigCmd {
commands: Vec<Command>,
groups: Vec<ConfigOption>,
}
impl ConfigCmd {
pub const NAME: &'static str = "config";
pub fn from_commands(commands: Vec<Command>) -> Self {
Self {
commands,
groups: Vec::new(),
}
}
pub fn register_group(&mut self, group: ConfigOption) {
self.groups.push(group);
}
}
impl ViewCommand for ConfigCmd {
type View = ConfigurationView;
fn name(&self) -> &'static str {
Self::NAME
}
fn usage(&self) -> &'static str {
""
}
fn help(&self) -> Option<HelpManual> {
let config_options = vec![
ConfigOption::new(
":config options",
"A border color of menus",
"config.border_color",
default_color_list(),
),
ConfigOption::new(
":config options",
"Set a color of entries in a list",
"config.list_color",
default_color_list(),
),
ConfigOption::new(
":config options",
"Set a color of a chosen entry in a list",
"config.cursor_color",
default_color_list(),
),
];
Some(HelpManual {
name: Self::NAME,
description:
"Interactive configuration manager.\nCan be used to set various explore settings.\n\nIt could be consired an interactive version of :tweak",
config_options,
arguments: vec![],
examples: vec![],
input: vec![],
})
}
fn parse(&mut self, _: &str) -> Result<()> {
Ok(())
}
fn display_config_option(&mut self, _: String, _: String, _: String) -> bool {
false
}
fn spawn(
&mut self,
engine_state: &EngineState,
stack: &mut Stack,
_: Option<Value>,
) -> Result<Self::View> {
let mut options = vec![];
let default_table = create_default_value();
for cmd in &self.commands {
let cmd = match cmd {
Command::Reactive(_) => continue,
Command::View { cmd, .. } => cmd,
};
let help = match cmd.help() {
Some(help) => help,
None => continue,
};
for opt in help.config_options {
let mut values = vec![];
for value in opt.values {
let mut cmd = cmd.clone();
let can_be_displayed = cmd.display_config_option(
opt.group.clone(),
opt.key.clone(),
value.example.to_string(),
);
let view = if can_be_displayed {
cmd.spawn(engine_state, stack, Some(default_table.clone()))?
} else {
Box::new(Preview::new(&opt.description))
};
let option = configuration::ConfigOption::new(value.example.to_string(), view);
values.push(option);
}
let group = configuration::ConfigGroup::new(opt.key, values, opt.description);
options.push((opt.group, group));
}
}
for opt in &self.groups {
let mut values = vec![];
for value in &opt.values {
let view = Box::new(Preview::new(&opt.description));
let option = configuration::ConfigOption::new(value.example.to_string(), view);
values.push(option);
}
let group =
configuration::ConfigGroup::new(opt.key.clone(), values, opt.description.clone());
options.push((opt.group.clone(), group));
}
options.sort_by(|(group1, opt1), (group2, opt2)| {
group1.cmp(group2).then(opt1.group().cmp(opt2.group()))
});
let options = options.into_iter().map(|(_, opt)| opt).collect();
Ok(ConfigurationView::new(options))
}
}
fn create_default_value() -> Value {
let span = NuSpan::unknown();
let record = |i: usize| Value::Record {
cols: vec![String::from("key"), String::from("value")],
vals: vec![nu_str(format!("key-{}", i)), nu_str(format!("{}", i))],
span,
};
Value::List {
vals: vec![record(0), record(1), record(2)],
span,
}
}

View File

@@ -0,0 +1,137 @@
use std::io::Result;
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
use tui::layout::Rect;
use crate::{
nu_common::try_build_table,
pager::Frame,
util::map_into_value,
views::{Layout, Preview, View, ViewConfig},
};
use super::{HelpExample, HelpManual, ViewCommand};
#[derive(Clone)]
pub struct ConfigShowCmd {
format: ConfigFormat,
}
#[derive(Clone)]
enum ConfigFormat {
Table,
Nu,
}
impl ConfigShowCmd {
pub fn new() -> Self {
ConfigShowCmd {
format: ConfigFormat::Table,
}
}
}
impl ConfigShowCmd {
pub const NAME: &'static str = "config-show";
}
impl ViewCommand for ConfigShowCmd {
type View = ConfigView;
fn name(&self) -> &'static str {
Self::NAME
}
fn usage(&self) -> &'static str {
""
}
fn help(&self) -> Option<HelpManual> {
Some(HelpManual {
name: Self::NAME,
description:
"Return a currently used configuration.\nSome default fields might be missing.",
arguments: vec![HelpExample::new("nu", "Use a nuon format instead")],
config_options: vec![],
input: vec![],
examples: vec![],
})
}
fn display_config_option(&mut self, _: String, _: String, _: String) -> bool {
false
}
fn parse(&mut self, args: &str) -> Result<()> {
if args.trim() == "nu" {
self.format = ConfigFormat::Nu;
}
Ok(())
}
fn spawn(&mut self, _: &EngineState, _: &mut Stack, _: Option<Value>) -> Result<Self::View> {
Ok(ConfigView {
preview: Preview::new(""),
format: self.format.clone(),
})
}
}
pub struct ConfigView {
preview: Preview,
format: ConfigFormat,
}
impl View for ConfigView {
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
self.preview.draw(f, area, cfg, layout)
}
fn handle_input(
&mut self,
engine_state: &EngineState,
stack: &mut Stack,
layout: &Layout,
info: &mut crate::pager::ViewInfo,
key: crossterm::event::KeyEvent,
) -> Option<crate::pager::Transition> {
self.preview
.handle_input(engine_state, stack, layout, info, key)
}
fn setup(&mut self, config: ViewConfig<'_>) {
let text = self.create_output_string(config);
self.preview = Preview::new(&text);
self.preview
.set_value(map_into_value(config.config.clone()));
}
fn exit(&mut self) -> Option<Value> {
self.preview.exit()
}
fn collect_data(&self) -> Vec<crate::nu_common::NuText> {
self.preview.collect_data()
}
fn show_data(&mut self, i: usize) -> bool {
self.preview.show_data(i)
}
}
impl ConfigView {
fn create_output_string(&mut self, config: ViewConfig) -> String {
match self.format {
ConfigFormat::Table => {
let value = map_into_value(config.config.clone());
try_build_table(None, config.nu_config, config.color_hm, value)
}
ConfigFormat::Nu => nu_json::to_string(&config.config).unwrap_or_default(),
}
}
}

View File

@@ -0,0 +1,100 @@
use std::{io::Result, vec};
use nu_color_config::get_color_config;
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
use crate::{
nu_common::{self, collect_input},
views::Preview,
};
use super::{HelpManual, Shortcode, ViewCommand};
#[derive(Default, Clone)]
pub struct ExpandCmd;
impl ExpandCmd {
pub fn new() -> Self {
Self
}
}
impl ExpandCmd {
pub const NAME: &'static str = "expand";
}
impl ViewCommand for ExpandCmd {
type View = Preview;
fn name(&self) -> &'static str {
Self::NAME
}
fn usage(&self) -> &'static str {
""
}
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 display_config_option(&mut self, _: String, _: String, _: String) -> bool {
false
}
fn parse(&mut self, _: &str) -> Result<()> {
Ok(())
}
fn spawn(
&mut self,
engine_state: &EngineState,
_stack: &mut Stack,
value: Option<Value>,
) -> Result<Self::View> {
let value = value
.map(|v| convert_value_to_string(v, engine_state))
.unwrap_or_default();
Ok(Preview::new(&value))
}
}
fn convert_value_to_string(value: Value, engine_state: &EngineState) -> 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].into_abbreviated_string(config)
} else {
let ctrlc = engine_state.ctrlc.clone();
let config = engine_state.get_config();
let color_hm = get_color_config(config);
nu_common::try_build_table(ctrlc, config, &color_hm, value)
}
}

View File

@@ -3,19 +3,24 @@ use std::{
io::{self, Result},
};
use crossterm::event::KeyEvent;
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
use tui::layout::Rect;
use crate::{nu_common::NuSpan, pager::TableConfig, views::RecordView};
use crate::{
nu_common::{collect_input, NuSpan},
pager::{Frame, Transition, ViewInfo},
views::{Layout, Preview, RecordView, View, ViewConfig},
};
use super::{HelpExample, HelpManual, ViewCommand};
#[derive(Debug, Default, Clone)]
pub struct HelpCmd {
input_command: String,
table_cfg: TableConfig,
supported_commands: Vec<HelpManual>,
aliases: HashMap<String, Vec<String>>,
}
@@ -23,18 +28,39 @@ pub struct HelpCmd {
impl HelpCmd {
pub const NAME: &'static str = "help";
pub fn new(
commands: Vec<HelpManual>,
aliases: &[(&str, &str)],
table_cfg: TableConfig,
) -> Self {
const HELP_MESSAGE: &'static str = r#" Explore - main help file
Move around: Use the cursor keys.
Close this window: Use "<Esc>".
Get out of Explore: Use ":q<Enter>" (or <Ctrl> + <D>).
Get specific help: It is possible to go directly to whatewer you want help on,
by adding an argument to the ":help" command.
Currently you can only get help on a few commands.
To obtain a list of supported commands run ":help :<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,
table_cfg,
}
}
}
@@ -51,7 +77,7 @@ fn collect_aliases(aliases: &[(&str, &str)]) -> HashMap<String, Vec<String>> {
}
impl ViewCommand for HelpCmd {
type View = RecordView<'static>;
type View = HelpView<'static>;
fn name(&self) -> &'static str {
Self::NAME
@@ -62,27 +88,32 @@ 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: vec![],
examples: vec![
HelpExample {
example: "help",
description: "Open the help page for all of `explore`",
},
HelpExample {
example: "help nu",
description: "Open the help page for the `nu` explore command",
},
HelpExample {
example: "help help",
description: "...It was supposed to be hidden....until...now...",
},
],
arguments,
examples,
input: vec![],
config_options: vec![],
})
}
fn display_config_option(&mut self, _: String, _: String, _: String) -> bool {
false
}
fn parse(&mut self, args: &str) -> Result<()> {
self.input_command = args.trim().to_owned();
@@ -91,15 +122,31 @@ impl ViewCommand for HelpCmd {
fn spawn(&mut self, _: &EngineState, _: &mut Stack, _: Option<Value>) -> Result<Self::View> {
if self.input_command.is_empty() {
let (headers, data) = help_frame_data(&self.supported_commands, &self.aliases);
let view = RecordView::new(headers, data, self.table_cfg);
return Ok(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 == self.input_command)
.find(|manual| manual.name == command)
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "a given command was not found"))?;
let aliases = self
@@ -108,9 +155,9 @@ impl ViewCommand for HelpCmd {
.map(|l| l.as_slice())
.unwrap_or(&[]);
let (headers, data) = help_manual_data(manual, aliases);
let view = RecordView::new(headers, data, self.table_cfg);
let view = RecordView::new(headers, data);
Ok(view)
Ok(HelpView::Records(view))
}
}
@@ -118,20 +165,6 @@ fn help_frame_data(
supported_commands: &[HelpManual],
aliases: &HashMap<String, Vec<String>>,
) -> (Vec<String>, Vec<Vec<Value>>) {
macro_rules! null {
() => {
Value::Nothing {
span: NuSpan::unknown(),
}
};
}
macro_rules! nu_str {
($text:expr) => {
Value::string($text.to_string(), NuSpan::unknown())
};
}
let commands = supported_commands
.iter()
.map(|manual| {
@@ -154,41 +187,13 @@ fn help_frame_data(
span: NuSpan::unknown(),
};
let headers = vec!["name", "mode", "information", "description"];
#[rustfmt::skip]
let shortcuts = [
(":", "view", commands, "Run an explore command (explore the 'information' cell of this row to list commands)"),
("/", "view", null!(), "Search for a pattern"),
("?", "view", null!(), "Search for a pattern, but the <n> key now scrolls to the previous result"),
("n", "view", null!(), "When searching, scroll to the next search result"),
("i", "view", null!(), "Enters cursor mode to inspect individual cells"),
("t", "view", null!(), "Transpose table, so that columns become rows and vice versa"),
("Up", "", null!(), "Moves the cursor or viewport one row up"),
("Down", "", null!(), "Moves the cursor or viewport one row down"),
("Left", "", null!(), "Moves the cursor or viewport one column left"),
("Right", "", null!(), "Moves the cursor or viewport one column right"),
("PgDown", "view", null!(), "Moves the cursor or viewport one page of rows down"),
("PgUp", "view", null!(), "Moves the cursor or viewport one page of rows up"),
("Esc", "", null!(), "Exits cursor mode. Exits the currently explored data."),
("Enter", "cursor", null!(), "In cursor mode, explore the data of the selected cell"),
];
let headers = headers.iter().map(|s| s.to_string()).collect();
let data = shortcuts
.iter()
.map(|(name, mode, info, desc)| {
vec![nu_str!(name), nu_str!(mode), info.clone(), nu_str!(desc)]
})
.collect();
(headers, data)
collect_input(commands)
}
fn help_manual_data(manual: &HelpManual, aliases: &[String]) -> (Vec<String>, Vec<Vec<Value>>) {
macro_rules! nu_str {
($text:expr) => {
Value::string($text, NuSpan::unknown())
Value::string($text.to_string(), NuSpan::unknown())
};
}
@@ -216,12 +221,69 @@ fn help_manual_data(manual: &HelpManual, aliases: &[String]) -> (Vec<String>, Ve
span: NuSpan::unknown(),
})
.collect();
let examples = Value::List {
vals: examples,
span: NuSpan::unknown(),
};
let inputs = manual
.input
.iter()
.map(|e| Value::Record {
cols: vec![
String::from("name"),
String::from("context"),
String::from("description"),
],
vals: vec![nu_str!(e.code), nu_str!(e.context), nu_str!(e.description)],
span: NuSpan::unknown(),
})
.collect();
let inputs = Value::List {
vals: inputs,
span: NuSpan::unknown(),
};
let configuration = manual
.config_options
.iter()
.map(|o| {
let values = o
.values
.iter()
.map(|v| Value::Record {
cols: vec![String::from("example"), String::from("description")],
vals: vec![nu_str!(v.example), nu_str!(v.description)],
span: NuSpan::unknown(),
})
.collect();
let values = Value::List {
vals: values,
span: NuSpan::unknown(),
};
Value::Record {
cols: vec![
String::from("name"),
String::from("context"),
String::from("description"),
String::from("values"),
],
vals: vec![
nu_str!(o.group),
nu_str!(o.key),
nu_str!(o.description),
values,
],
span: NuSpan::unknown(),
}
})
.collect();
let configuration = Value::List {
vals: configuration,
span: NuSpan::unknown(),
};
let name = nu_str!(manual.name);
let aliases = nu_str!(aliases.join(", "));
let desc = nu_str!(manual.description);
@@ -230,11 +292,76 @@ fn help_manual_data(manual: &HelpManual, aliases: &[String]) -> (Vec<String>, Ve
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, examples, desc]];
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),
}
}
}

View File

@@ -5,19 +5,27 @@ use nu_protocol::{
use super::pager::{Pager, Transition};
use std::io::Result;
use std::{borrow::Cow, io::Result};
mod expand;
mod help;
mod nu;
mod preview;
mod quit;
mod table;
mod r#try;
mod tweak;
pub mod config;
mod config_show;
pub use config_show::ConfigShowCmd;
pub use expand::ExpandCmd;
pub use help::HelpCmd;
pub use nu::NuCmd;
pub use preview::PreviewCmd;
pub use quit::QuitCmd;
pub use r#try::TryCmd;
pub use table::TableCmd;
pub use tweak::TweakCmd;
pub trait SimpleCommand {
fn name(&self) -> &'static str;
@@ -48,6 +56,8 @@ pub trait ViewCommand {
fn parse(&mut self, args: &str) -> Result<()>;
fn display_config_option(&mut self, group: String, key: String, value: String) -> bool;
fn spawn(
&mut self,
engine_state: &EngineState,
@@ -62,10 +72,110 @@ pub struct HelpManual {
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: &'static str,
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()
}

View File

@@ -4,11 +4,12 @@ use nu_protocol::{
engine::{EngineState, Stack},
PipelineData, Value,
};
use tui::layout::Rect;
use crate::{
nu_common::{collect_pipeline, has_simple_value, is_ignored_command, run_nu_command},
pager::TableConfig,
views::{Preview, RecordView, View},
nu_common::{collect_pipeline, has_simple_value, run_command_with_value},
pager::Frame,
views::{Layout, Orientation, Preview, RecordView, View, ViewConfig},
};
use super::{HelpExample, HelpManual, ViewCommand};
@@ -16,14 +17,12 @@ use super::{HelpExample, HelpManual, ViewCommand};
#[derive(Debug, Default, Clone)]
pub struct NuCmd {
command: String,
table_cfg: TableConfig,
}
impl NuCmd {
pub fn new(table_cfg: TableConfig) -> Self {
pub fn new() -> Self {
Self {
command: String::new(),
table_cfg,
}
}
@@ -42,28 +41,33 @@ 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![],
examples: vec![
HelpExample {
example: "where type == 'file'",
description: "Filter data to show only rows whose type is 'file'",
},
HelpExample {
example: "get scope.examples",
description: "Navigate to a deeper value inside the data",
},
HelpExample {
example: "open Cargo.toml",
description: "Open a Cargo.toml file",
},
],
input: vec![],
config_options: vec![],
})
}
fn display_config_option(&mut self, _: String, _: String, _: String) -> bool {
false
}
fn parse(&mut self, args: &str) -> Result<()> {
self.command = args.trim().to_owned();
@@ -76,28 +80,25 @@ impl ViewCommand for NuCmd {
stack: &mut Stack,
value: Option<Value>,
) -> Result<Self::View> {
if is_ignored_command(&self.command) {
return Err(io::Error::new(
io::ErrorKind::Other,
"The command is ignored",
));
}
let value = value.unwrap_or_default();
let pipeline = PipelineData::Value(value, None);
let pipeline = run_nu_command(engine_state, stack, &self.command, pipeline)
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 (columns, values) = collect_pipeline(pipeline);
if has_simple_value(&values) {
let config = &engine_state.config;
let text = values[0][0].into_abbreviated_string(config);
if let Some(value) = has_simple_value(&values) {
let text = value.into_abbreviated_string(&engine_state.config);
return Ok(NuView::Preview(Preview::new(&text)));
}
let view = RecordView::new(columns, values, self.table_cfg);
let mut view = RecordView::new(columns, values);
if is_record {
view.set_orientation_current(Orientation::Left);
}
Ok(NuView::Records(view))
}
@@ -109,13 +110,7 @@ pub enum NuView<'a> {
}
impl View for NuView<'_> {
fn draw(
&mut self,
f: &mut crate::pager::Frame,
area: tui::layout::Rect,
cfg: &crate::ViewConfig,
layout: &mut crate::views::Layout,
) {
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
match self {
NuView::Records(v) => v.draw(f, area, cfg, layout),
NuView::Preview(v) => v.draw(f, area, cfg, layout),
@@ -126,7 +121,7 @@ impl View for NuView<'_> {
&mut self,
engine_state: &EngineState,
stack: &mut Stack,
layout: &crate::views::Layout,
layout: &Layout,
info: &mut crate::pager::ViewInfo,
key: crossterm::event::KeyEvent,
) -> Option<crate::pager::Transition> {
@@ -156,4 +151,11 @@ impl View for NuView<'_> {
NuView::Preview(v) => v.exit(),
}
}
fn setup(&mut self, config: ViewConfig<'_>) {
match self {
NuView::Records(v) => v.setup(config),
NuView::Preview(v) => v.setup(config),
}
}
}

View File

@@ -1,82 +0,0 @@
use std::io::Result;
use nu_color_config::get_color_config;
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
use crate::{
nu_common::{self, collect_input},
views::Preview,
};
use super::{HelpManual, ViewCommand};
#[derive(Default, Clone)]
pub struct PreviewCmd;
impl PreviewCmd {
pub fn new() -> Self {
Self
}
}
impl PreviewCmd {
pub const NAME: &'static str = "preview";
}
impl ViewCommand for PreviewCmd {
type View = Preview;
fn name(&self) -> &'static str {
Self::NAME
}
fn usage(&self) -> &'static str {
""
}
fn help(&self) -> Option<HelpManual> {
Some(HelpManual {
name: "preview",
description:
"View the currently selected cell's data using the `table` Nushell command",
arguments: vec![],
examples: vec![],
})
}
fn parse(&mut self, _: &str) -> Result<()> {
Ok(())
}
fn spawn(
&mut self,
engine_state: &EngineState,
_stack: &mut Stack,
value: Option<Value>,
) -> Result<Self::View> {
let value = match value {
Some(value) => {
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].into_abbreviated_string(config)
} else {
let ctrlc = engine_state.ctrlc.clone();
let config = engine_state.get_config();
let color_hm = get_color_config(config);
nu_common::try_build_table(ctrlc, config, &color_hm, value)
}
}
None => String::new(),
};
Ok(Preview::new(&value))
}
}

View File

@@ -31,6 +31,8 @@ impl SimpleCommand for QuitCmd {
description: "Quit and return to Nushell",
arguments: vec![],
examples: vec![],
input: vec![],
config_options: vec![],
})
}

View File

@@ -0,0 +1,281 @@
use std::io::Result;
use nu_ansi_term::Style;
use nu_color_config::lookup_ansi_color_style;
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
use crate::{
nu_common::collect_input,
views::{Orientation, RecordView},
};
use super::{
default_color_list, default_int_list, ConfigOption, HelpExample, HelpManual, Shortcode,
ViewCommand,
};
#[derive(Debug, Default, Clone)]
pub struct TableCmd {
// todo: add arguments to override config right from CMD
settings: TableSettings,
}
#[derive(Debug, Default, Clone)]
struct TableSettings {
orientation: Option<Orientation>,
line_head_top: Option<bool>,
line_head_bottom: Option<bool>,
line_shift: Option<bool>,
line_index: Option<bool>,
split_line_s: Option<Style>,
selected_cell_s: Option<Style>,
selected_row_s: Option<Style>,
selected_column_s: Option<Style>,
show_cursor: Option<bool>,
padding_column_left: Option<usize>,
padding_column_right: Option<usize>,
padding_index_left: Option<usize>,
padding_index_right: Option<usize>,
turn_on_cursor_mode: bool,
}
impl TableCmd {
pub fn new() -> Self {
Self::default()
}
pub const NAME: &'static str = "table";
}
impl ViewCommand for TableCmd {
type View = RecordView<'static>;
fn name(&self) -> &'static str {
Self::NAME
}
fn usage(&self) -> &'static str {
""
}
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 (equvalent 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::boolean(":table group", "Lines are lines", "table.line_head_top"),
ConfigOption::boolean(":table group", "Lines are lines", "table.line_head_bottom"),
ConfigOption::boolean(":table group", "Lines are lines", "table.line_shift"),
ConfigOption::boolean(":table group", "Lines are lines", "table.line_index"),
ConfigOption::boolean(":table group", "Show cursor", "table.show_cursor"),
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 display_config_option(&mut self, _group: String, key: String, value: String) -> bool {
match key.as_str() {
"table.orientation" => self.settings.orientation = orientation_from_str(&value),
"table.line_head_top" => self.settings.line_head_top = bool_from_str(&value),
"table.line_head_bottom" => self.settings.line_head_bottom = bool_from_str(&value),
"table.line_shift" => self.settings.line_shift = bool_from_str(&value),
"table.line_index" => self.settings.line_index = bool_from_str(&value),
"table.show_cursor" => {
self.settings.show_cursor = bool_from_str(&value);
self.settings.turn_on_cursor_mode = true;
}
"table.split_line" => {
self.settings.split_line_s = Some(lookup_ansi_color_style(&value));
self.settings.turn_on_cursor_mode = true;
}
"table.selected_cell" => {
self.settings.selected_cell_s = Some(lookup_ansi_color_style(&value));
self.settings.turn_on_cursor_mode = true;
}
"table.selected_row" => {
self.settings.selected_row_s = Some(lookup_ansi_color_style(&value));
self.settings.turn_on_cursor_mode = true;
}
"table.selected_column" => {
self.settings.selected_column_s = Some(lookup_ansi_color_style(&value));
self.settings.turn_on_cursor_mode = true;
}
"table.padding_column_left" => {
self.settings.padding_column_left = usize_from_str(&value);
}
"table.padding_column_right" => {
self.settings.padding_column_right = usize_from_str(&value);
}
"table.padding_index_left" => {
self.settings.padding_index_left = usize_from_str(&value);
}
"table.padding_index_right" => {
self.settings.padding_index_right = usize_from_str(&value);
}
_ => return false,
}
true
}
fn parse(&mut self, _: &str) -> Result<()> {
Ok(())
}
fn spawn(
&mut self,
_: &EngineState,
_: &mut Stack,
value: Option<Value>,
) -> Result<Self::View> {
let value = value.unwrap_or_default();
let is_record = matches!(value, Value::Record { .. });
let (columns, data) = collect_input(value);
let mut view = RecordView::new(columns, data);
// todo: use setup instead ????
if is_record {
view.set_orientation_current(Orientation::Left);
}
if let Some(o) = self.settings.orientation {
view.set_orientation_current(o);
}
if self.settings.line_head_bottom.unwrap_or(false) {
view.set_line_head_bottom(true);
}
if self.settings.line_head_top.unwrap_or(false) {
view.set_line_head_top(true);
}
if self.settings.line_index.unwrap_or(false) {
view.set_line_index(true);
}
if self.settings.line_shift.unwrap_or(false) {
view.set_line_traling(true);
}
if self.settings.show_cursor.unwrap_or(false) {
view.show_cursor(true);
}
if let Some(style) = self.settings.selected_cell_s {
view.set_style_selected_cell(style);
}
if let Some(style) = self.settings.selected_column_s {
view.set_style_selected_column(style);
}
if let Some(style) = self.settings.selected_row_s {
view.set_style_selected_row(style);
}
if let Some(style) = self.settings.split_line_s {
view.set_style_split_line(style);
}
if let Some(p) = self.settings.padding_column_left {
let c = view.get_padding_column();
view.set_padding_column((p, c.1))
}
if let Some(p) = self.settings.padding_column_right {
let c = view.get_padding_column();
view.set_padding_column((c.0, p))
}
if let Some(p) = self.settings.padding_index_left {
let c = view.get_padding_index();
view.set_padding_index((p, c.1))
}
if let Some(p) = self.settings.padding_index_right {
let c = view.get_padding_index();
view.set_padding_index((c.0, p))
}
if self.settings.turn_on_cursor_mode {
view.set_cursor_mode();
}
Ok(view)
}
}
fn bool_from_str(s: &str) -> Option<bool> {
match s {
"true" => Some(true),
"false" => Some(false),
_ => None,
}
}
fn usize_from_str(s: &str) -> Option<usize> {
s.parse::<usize>().ok()
}
fn orientation_from_str(s: &str) -> Option<Orientation> {
match s {
"left" => Some(Orientation::Left),
"right" => Some(Orientation::Right),
"top" => Some(Orientation::Top),
"bottom" => Some(Orientation::Bottom),
_ => None,
}
}

View File

@@ -1,25 +1,23 @@
use std::io::Result;
use std::io::{Error, ErrorKind, Result};
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
use crate::{pager::TableConfig, views::InteractiveView};
use crate::views::InteractiveView;
use super::{HelpExample, HelpManual, ViewCommand};
use super::{default_color_list, ConfigOption, HelpExample, HelpManual, Shortcode, ViewCommand};
#[derive(Debug, Default, Clone)]
pub struct TryCmd {
command: String,
table_cfg: TableConfig,
}
impl TryCmd {
pub fn new(table_cfg: TableConfig) -> Self {
pub fn new() -> Self {
Self {
command: String::new(),
table_cfg,
}
}
@@ -38,17 +36,41 @@ 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", "Try makes running command on each input character", "try.reactive"),
ConfigOption::new(":try options", "Change a border color of the menus", "try.border_color", default_color_list()),
ConfigOption::new(":try options", "Change a highlighed 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",
description: "Opens a panel in which to run Nushell commands and explore their output. The exporer acts liek `:table`.",
arguments: vec![],
examples: vec![HelpExample {
example: "try open Cargo.toml",
description: "Optionally, you can provide a command which will be run immediately",
}],
examples,
input: shortcuts,
config_options,
})
}
fn display_config_option(&mut self, _: String, _: String, _: String) -> bool {
false
}
fn parse(&mut self, args: &str) -> Result<()> {
self.command = args.trim().to_owned();
@@ -57,13 +79,15 @@ impl ViewCommand for TryCmd {
fn spawn(
&mut self,
_: &EngineState,
_: &mut Stack,
engine_state: &EngineState,
stack: &mut Stack,
value: Option<Value>,
) -> Result<Self::View> {
let value = value.unwrap_or_default();
let mut view = InteractiveView::new(value, self.table_cfg);
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))?;
Ok(view)
}

View File

@@ -0,0 +1,96 @@
use std::io::{self, Result};
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
use crate::{
nu_common::NuSpan,
pager::{Pager, Transition},
};
use super::{HelpExample, HelpManual, SimpleCommand};
#[derive(Default, Clone)]
pub struct TweakCmd {
path: Vec<String>,
value: Value,
}
impl TweakCmd {
pub const NAME: &'static str = "tweak";
}
impl SimpleCommand for TweakCmd {
fn name(&self) -> &'static str {
Self::NAME
}
fn usage(&self) -> &'static str {
""
}
fn help(&self) -> Option<HelpManual> {
Some(HelpManual {
name: "tweak",
description:
"Set different settings.\nIt could be consired a not interactive version of :config",
arguments: vec![],
examples: vec![
HelpExample::new(":tweak table.show_index false", "Don't show index anymore"),
HelpExample::new(":tweak table.show_head false", "Don't show header anymore"),
HelpExample::new(
":tweak try.border_color {bg: '#FFFFFF', fg: '#F213F1'}",
"Make a different color for borders in :try",
),
],
config_options: vec![],
input: vec![],
})
}
fn parse(&mut self, input: &str) -> Result<()> {
let input = input.trim();
let args = input.split_once(' ');
let (key, value) = match args {
Some(args) => args,
None => {
return Err(io::Error::new(
io::ErrorKind::Other,
"expected to get 2 arguments 'key value'",
))
}
};
self.value = parse_value(value);
self.path = key
.split_terminator('.')
.map(|s| s.to_string())
.collect::<Vec<_>>();
Ok(())
}
fn react(
&mut self,
_: &EngineState,
_: &mut Stack,
p: &mut Pager<'_>,
_: Option<Value>,
) -> Result<Transition> {
p.set_config(&self.path, self.value.clone());
Ok(Transition::Ok)
}
}
fn parse_value(value: &str) -> Value {
match value {
"true" => Value::boolean(true, NuSpan::unknown()),
"false" => Value::boolean(false, NuSpan::unknown()),
s => Value::string(s.to_owned(), NuSpan::unknown()),
}
}