nushell/crates/nu-explore/src/commands/help.rs
Ian Manske fb4251aba7
Remove Record::from_raw_cols_vals_unchecked (#11810)
# Description
Follows from #11718 and replaces all usages of
`Record::from_raw_cols_vals_unchecked` with iterator or `record!`
equivalents.
2024-02-18 14:20:22 +02:00

341 lines
9.6 KiB
Rust

use std::collections::HashMap;
use std::io::{self, Result};
use crossterm::event::KeyEvent;
use nu_protocol::{
engine::{EngineState, Stack},
record, Value,
};
use ratatui::layout::Rect;
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,
supported_commands: Vec<HelpManual>,
aliases: HashMap<String, Vec<String>>,
}
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,
}
}
}
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
}
impl ViewCommand for HelpCmd {
type View = HelpView<'static>;
fn name(&self) -> &'static str {
Self::NAME
}
fn usage(&self) -> &'static str {
""
}
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<()> {
self.input_command = args.trim().to_owned();
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),
}
}
}