mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 00:13:21 +01:00
Expand Nushell's help system (#7611)
This commit is contained in:
parent
f3d2be7a56
commit
8bfcea8054
@ -1,17 +1,18 @@
|
|||||||
|
use crate::help_aliases::help_aliases;
|
||||||
|
use crate::help_commands::help_commands;
|
||||||
|
use crate::help_modules::help_modules;
|
||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
use nu_ansi_term::{
|
use nu_ansi_term::{
|
||||||
Color::{Red, White},
|
Color::{Red, White},
|
||||||
Style,
|
Style,
|
||||||
};
|
};
|
||||||
use nu_color_config::StyleComputer;
|
use nu_engine::CallExt;
|
||||||
use nu_engine::{get_full_help, CallExt};
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
span, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||||
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use std::borrow::Borrow;
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Help;
|
pub struct Help;
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ impl Command for Help {
|
|||||||
.rest(
|
.rest(
|
||||||
"rest",
|
"rest",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
"the name of command to get help on",
|
"the name of command, alias or module to get help on",
|
||||||
)
|
)
|
||||||
.named(
|
.named(
|
||||||
"find",
|
"find",
|
||||||
@ -38,7 +39,11 @@ impl Command for Help {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Display help information about commands."
|
"Display help information about different parts of Nushell."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"`help word` searches for "word" in commands, aliases and modules, in that order."#
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@ -48,269 +53,18 @@ impl Command for Help {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
help(engine_state, stack, call)
|
let head = call.head;
|
||||||
}
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||||
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
if rest.is_empty() && find.is_none() {
|
||||||
vec![
|
let msg = r#"Welcome to Nushell.
|
||||||
Example {
|
|
||||||
description: "show all commands and sub-commands",
|
|
||||||
example: "help commands",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "show help for single command",
|
|
||||||
example: "help match",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "show help for single sub-command",
|
|
||||||
example: "help str lpad",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
Example {
|
|
||||||
description: "search for string in command names, usage and search terms",
|
|
||||||
example: "help --find char",
|
|
||||||
result: None,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn help(
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let head = call.head;
|
|
||||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
|
||||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
|
||||||
let commands = engine_state.get_decl_ids_sorted(false);
|
|
||||||
|
|
||||||
// 🚩The following two-lines are copied from filters/find.rs:
|
|
||||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
|
||||||
// Currently, search results all use the same style.
|
|
||||||
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
|
||||||
// defined for "string").
|
|
||||||
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
|
||||||
|
|
||||||
if let Some(f) = find {
|
|
||||||
let org_search_string = f.item.clone();
|
|
||||||
let search_string = f.item.to_lowercase();
|
|
||||||
let mut found_cmds_vec = Vec::new();
|
|
||||||
|
|
||||||
for decl_id in commands {
|
|
||||||
let mut cols = vec![];
|
|
||||||
let mut vals = vec![];
|
|
||||||
let decl = engine_state.get_decl(decl_id);
|
|
||||||
let sig = decl.signature().update_from_command(decl.borrow());
|
|
||||||
let signatures = sig.to_string();
|
|
||||||
let key = sig.name;
|
|
||||||
let usage = sig.usage;
|
|
||||||
let search_terms = sig.search_terms;
|
|
||||||
|
|
||||||
let matches_term = if !search_terms.is_empty() {
|
|
||||||
search_terms
|
|
||||||
.iter()
|
|
||||||
.any(|term| term.to_lowercase().contains(&search_string))
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let key_match = key.to_lowercase().contains(&search_string);
|
|
||||||
let usage_match = usage.to_lowercase().contains(&search_string);
|
|
||||||
if key_match || usage_match || matches_term {
|
|
||||||
cols.push("name".into());
|
|
||||||
vals.push(Value::String {
|
|
||||||
val: if key_match {
|
|
||||||
highlight_search_string(&key, &org_search_string, &string_style)?
|
|
||||||
} else {
|
|
||||||
key
|
|
||||||
},
|
|
||||||
span: head,
|
|
||||||
});
|
|
||||||
|
|
||||||
cols.push("category".into());
|
|
||||||
vals.push(Value::string(sig.category.to_string(), head));
|
|
||||||
|
|
||||||
cols.push("command_type".into());
|
|
||||||
vals.push(Value::String {
|
|
||||||
val: format!("{:?}", decl.command_type()).to_lowercase(),
|
|
||||||
span: head,
|
|
||||||
});
|
|
||||||
|
|
||||||
cols.push("usage".into());
|
|
||||||
vals.push(Value::String {
|
|
||||||
val: if usage_match {
|
|
||||||
highlight_search_string(&usage, &org_search_string, &string_style)?
|
|
||||||
} else {
|
|
||||||
usage
|
|
||||||
},
|
|
||||||
span: head,
|
|
||||||
});
|
|
||||||
|
|
||||||
cols.push("signatures".into());
|
|
||||||
vals.push(Value::String {
|
|
||||||
val: if decl.is_parser_keyword() {
|
|
||||||
"".to_string()
|
|
||||||
} else {
|
|
||||||
signatures
|
|
||||||
},
|
|
||||||
span: head,
|
|
||||||
});
|
|
||||||
|
|
||||||
cols.push("search_terms".into());
|
|
||||||
vals.push(if search_terms.is_empty() {
|
|
||||||
Value::nothing(head)
|
|
||||||
} else {
|
|
||||||
Value::String {
|
|
||||||
val: if matches_term {
|
|
||||||
search_terms
|
|
||||||
.iter()
|
|
||||||
.map(|term| {
|
|
||||||
if term.to_lowercase().contains(&search_string) {
|
|
||||||
match highlight_search_string(
|
|
||||||
term,
|
|
||||||
&org_search_string,
|
|
||||||
&string_style,
|
|
||||||
) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => {
|
|
||||||
string_style.paint(term.to_string()).to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
string_style.paint(term.to_string()).to_string()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", ")
|
|
||||||
} else {
|
|
||||||
search_terms.join(", ")
|
|
||||||
},
|
|
||||||
span: head,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
found_cmds_vec.push(Value::Record {
|
|
||||||
cols,
|
|
||||||
vals,
|
|
||||||
span: head,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(found_cmds_vec
|
|
||||||
.into_iter()
|
|
||||||
.into_pipeline_data(engine_state.ctrlc.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !rest.is_empty() {
|
|
||||||
let mut found_cmds_vec = Vec::new();
|
|
||||||
|
|
||||||
if rest[0].item == "commands" {
|
|
||||||
for decl_id in commands {
|
|
||||||
let mut cols = vec![];
|
|
||||||
let mut vals = vec![];
|
|
||||||
|
|
||||||
let decl = engine_state.get_decl(decl_id);
|
|
||||||
let sig = decl.signature().update_from_command(decl.borrow());
|
|
||||||
|
|
||||||
let signatures = sig.to_string();
|
|
||||||
let key = sig.name;
|
|
||||||
let usage = sig.usage;
|
|
||||||
let search_terms = sig.search_terms;
|
|
||||||
|
|
||||||
cols.push("name".into());
|
|
||||||
vals.push(Value::String {
|
|
||||||
val: key,
|
|
||||||
span: head,
|
|
||||||
});
|
|
||||||
|
|
||||||
cols.push("category".into());
|
|
||||||
vals.push(Value::string(sig.category.to_string(), head));
|
|
||||||
|
|
||||||
cols.push("command_type".into());
|
|
||||||
vals.push(Value::String {
|
|
||||||
val: format!("{:?}", decl.command_type()).to_lowercase(),
|
|
||||||
span: head,
|
|
||||||
});
|
|
||||||
|
|
||||||
cols.push("usage".into());
|
|
||||||
vals.push(Value::String {
|
|
||||||
val: usage,
|
|
||||||
span: head,
|
|
||||||
});
|
|
||||||
|
|
||||||
cols.push("signatures".into());
|
|
||||||
vals.push(Value::String {
|
|
||||||
val: if decl.is_parser_keyword() {
|
|
||||||
"".to_string()
|
|
||||||
} else {
|
|
||||||
signatures
|
|
||||||
},
|
|
||||||
span: head,
|
|
||||||
});
|
|
||||||
|
|
||||||
cols.push("search_terms".into());
|
|
||||||
vals.push(if search_terms.is_empty() {
|
|
||||||
Value::nothing(head)
|
|
||||||
} else {
|
|
||||||
Value::String {
|
|
||||||
val: search_terms.join(", "),
|
|
||||||
span: head,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
found_cmds_vec.push(Value::Record {
|
|
||||||
cols,
|
|
||||||
vals,
|
|
||||||
span: head,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(found_cmds_vec
|
|
||||||
.into_iter()
|
|
||||||
.into_pipeline_data(engine_state.ctrlc.clone()))
|
|
||||||
} else {
|
|
||||||
let mut name = String::new();
|
|
||||||
|
|
||||||
for r in &rest {
|
|
||||||
if !name.is_empty() {
|
|
||||||
name.push(' ');
|
|
||||||
}
|
|
||||||
name.push_str(&r.item);
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = engine_state
|
|
||||||
.get_signatures_with_examples(false)
|
|
||||||
.iter()
|
|
||||||
.filter(|(signature, _, _, _, _)| signature.name == name)
|
|
||||||
.map(|(signature, examples, _, _, is_parser_keyword)| {
|
|
||||||
get_full_help(signature, examples, engine_state, stack, *is_parser_keyword)
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
if !output.is_empty() {
|
|
||||||
Ok(Value::String {
|
|
||||||
val: output.join("======================\n\n"),
|
|
||||||
span: call.head,
|
|
||||||
}
|
|
||||||
.into_pipeline_data())
|
|
||||||
} else {
|
|
||||||
Err(ShellError::CommandNotFound(span(&[
|
|
||||||
rest[0].span,
|
|
||||||
rest[rest.len() - 1].span,
|
|
||||||
])))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let msg = r#"Welcome to Nushell.
|
|
||||||
|
|
||||||
Here are some tips to help you get started.
|
Here are some tips to help you get started.
|
||||||
|
* help -h or help help - show available `help` subcommands and examples
|
||||||
* help commands - list all available commands
|
* help commands - list all available commands
|
||||||
* help <command name> - display help about a particular command
|
* help <name> - display help about a particular command, alias, or module
|
||||||
* help --find <text to search> - search through all of help
|
* help --find <text to search> - search through all help commands table
|
||||||
|
|
||||||
Nushell works on the idea of a "pipeline". Pipelines are commands connected with the '|' character.
|
Nushell works on the idea of a "pipeline". Pipelines are commands connected with the '|' character.
|
||||||
Each stage in the pipeline works together to load, parse, and display information to you.
|
Each stage in the pipeline works together to load, parse, and display information to you.
|
||||||
@ -328,8 +82,111 @@ Get the processes on your system actively using CPU:
|
|||||||
|
|
||||||
You can also learn more at https://www.nushell.sh/book/"#;
|
You can also learn more at https://www.nushell.sh/book/"#;
|
||||||
|
|
||||||
Ok(Value::string(msg, head).into_pipeline_data())
|
Ok(Value::string(msg, head).into_pipeline_data())
|
||||||
|
} else if find.is_some() {
|
||||||
|
help_commands(engine_state, stack, call)
|
||||||
|
} else {
|
||||||
|
let result = help_commands(engine_state, stack, call);
|
||||||
|
|
||||||
|
let result = if let Err(ShellError::CommandNotFound(_)) = result {
|
||||||
|
help_aliases(engine_state, stack, call)
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = if let Err(ShellError::AliasNotFound(_)) = result {
|
||||||
|
help_modules(engine_state, stack, call)
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(ShellError::ModuleNotFoundAtRuntime(_, _)) = result {
|
||||||
|
let rest_spans: Vec<Span> = rest.iter().map(|arg| arg.span).collect();
|
||||||
|
Err(ShellError::NotFound(span(&rest_spans)))
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "show help for single command, alias, or module",
|
||||||
|
example: "help match",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "show help for single sub-command, alias, or module",
|
||||||
|
example: "help str lpad",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "search for string in command names, usage and search terms",
|
||||||
|
example: "help --find char",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn highlight_search_in_table(
|
||||||
|
table: Vec<Value>, // list of records
|
||||||
|
search_string: &str,
|
||||||
|
searched_cols: &[&str],
|
||||||
|
string_style: &Style,
|
||||||
|
) -> Result<Vec<Value>, ShellError> {
|
||||||
|
let orig_search_string = search_string;
|
||||||
|
let search_string = search_string.to_lowercase();
|
||||||
|
let mut matches = vec![];
|
||||||
|
|
||||||
|
for record in table {
|
||||||
|
let (cols, mut vals, record_span) = if let Value::Record { cols, vals, span } = record {
|
||||||
|
(cols, vals, span)
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::NushellFailedSpanned(
|
||||||
|
"Expected record".to_string(),
|
||||||
|
format!("got {}", record.get_type()),
|
||||||
|
record.span()?,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let has_match = cols.iter().zip(vals.iter_mut()).fold(
|
||||||
|
Ok(false),
|
||||||
|
|acc: Result<bool, ShellError>, (col, val)| {
|
||||||
|
if searched_cols.contains(&col.as_str()) {
|
||||||
|
if let Value::String { val: s, span } = val {
|
||||||
|
if s.to_lowercase().contains(&search_string) {
|
||||||
|
*val = Value::String {
|
||||||
|
val: highlight_search_string(s, orig_search_string, string_style)?,
|
||||||
|
span: *span,
|
||||||
|
};
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
// column does not contain the searched string
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ignore non-string values
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// don't search this column
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if has_match {
|
||||||
|
matches.push(Value::Record {
|
||||||
|
cols,
|
||||||
|
vals,
|
||||||
|
span: record_span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Highlight the search string using ANSI escape sequences and regular expressions.
|
// Highlight the search string using ANSI escape sequences and regular expressions.
|
||||||
|
181
crates/nu-command/src/core_commands/help_aliases.rs
Normal file
181
crates/nu-command/src/core_commands/help_aliases.rs
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
use crate::help::highlight_search_in_table;
|
||||||
|
use nu_color_config::StyleComputer;
|
||||||
|
use nu_engine::{scope::ScopeData, CallExt};
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
||||||
|
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HelpAliases;
|
||||||
|
|
||||||
|
impl Command for HelpAliases {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"help aliases"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Show help on nushell aliases."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("help aliases")
|
||||||
|
.category(Category::Core)
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the name of alias to get help on",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"find",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"string to find in alias names and usage",
|
||||||
|
Some('f'),
|
||||||
|
)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "show all aliases",
|
||||||
|
example: "help aliases",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "show help for single alias",
|
||||||
|
example: "help aliases my-alias",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "search for string in alias names and usages",
|
||||||
|
example: "help aliases --find my-alias",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
help_aliases(engine_state, stack, call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn help_aliases(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||||
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
// 🚩The following two-lines are copied from filters/find.rs:
|
||||||
|
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||||
|
// Currently, search results all use the same style.
|
||||||
|
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||||
|
// defined for "string").
|
||||||
|
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
||||||
|
|
||||||
|
if let Some(f) = find {
|
||||||
|
let all_cmds_vec = build_help_aliases(engine_state, stack, head);
|
||||||
|
let found_cmds_vec =
|
||||||
|
highlight_search_in_table(all_cmds_vec, &f.item, &["name", "usage"], &string_style)?;
|
||||||
|
|
||||||
|
return Ok(found_cmds_vec
|
||||||
|
.into_iter()
|
||||||
|
.into_pipeline_data(engine_state.ctrlc.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if rest.is_empty() {
|
||||||
|
let found_cmds_vec = build_help_aliases(engine_state, stack, head);
|
||||||
|
|
||||||
|
Ok(found_cmds_vec
|
||||||
|
.into_iter()
|
||||||
|
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||||
|
} else {
|
||||||
|
let mut name = String::new();
|
||||||
|
|
||||||
|
for r in &rest {
|
||||||
|
if !name.is_empty() {
|
||||||
|
name.push(' ');
|
||||||
|
}
|
||||||
|
name.push_str(&r.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let alias_id = if let Some(id) = engine_state.find_alias(name.as_bytes(), &[]) {
|
||||||
|
id
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::AliasNotFound(span(
|
||||||
|
&rest.iter().map(|r| r.span).collect::<Vec<Span>>(),
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let alias_expansion = engine_state
|
||||||
|
.get_alias(alias_id)
|
||||||
|
.iter()
|
||||||
|
.map(|span| String::from_utf8_lossy(engine_state.get_span_contents(span)))
|
||||||
|
.collect::<Vec<Cow<str>>>()
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
let alias_usage = engine_state.build_alias_usage(alias_id);
|
||||||
|
|
||||||
|
// TODO: merge this into documentation.rs at some point
|
||||||
|
const G: &str = "\x1b[32m"; // green
|
||||||
|
const C: &str = "\x1b[36m"; // cyan
|
||||||
|
const RESET: &str = "\x1b[0m"; // reset
|
||||||
|
|
||||||
|
let mut long_desc = String::new();
|
||||||
|
|
||||||
|
if let Some((usage, extra_usage)) = alias_usage {
|
||||||
|
long_desc.push_str(&usage);
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
|
||||||
|
if !extra_usage.is_empty() {
|
||||||
|
long_desc.push_str(&extra_usage);
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long_desc.push_str(&format!("{G}Alias{RESET}: {C}{name}{RESET}"));
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
long_desc.push_str(&format!("{G}Expansion{RESET}:\n {alias_expansion}"));
|
||||||
|
|
||||||
|
let config = engine_state.get_config();
|
||||||
|
if !config.use_ansi_coloring {
|
||||||
|
long_desc = nu_utils::strip_ansi_string_likely(long_desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::String {
|
||||||
|
val: long_desc,
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_help_aliases(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
||||||
|
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||||
|
scope_data.populate_aliases();
|
||||||
|
|
||||||
|
scope_data.collect_aliases(span)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use super::HelpAliases;
|
||||||
|
use crate::test_examples;
|
||||||
|
test_examples(HelpAliases {})
|
||||||
|
}
|
||||||
|
}
|
189
crates/nu-command/src/core_commands/help_commands.rs
Normal file
189
crates/nu-command/src/core_commands/help_commands.rs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
use crate::help::highlight_search_in_table;
|
||||||
|
use nu_color_config::StyleComputer;
|
||||||
|
use nu_engine::{get_full_help, CallExt};
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
span, Category, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
|
||||||
|
Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HelpCommands;
|
||||||
|
|
||||||
|
impl Command for HelpCommands {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"help commands"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Show help on nushell commands."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("help commands")
|
||||||
|
.category(Category::Core)
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the name of command to get help on",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"find",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"string to find in command names, usage, and search terms",
|
||||||
|
Some('f'),
|
||||||
|
)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
help_commands(engine_state, stack, call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn help_commands(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||||
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
// 🚩The following two-lines are copied from filters/find.rs:
|
||||||
|
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||||
|
// Currently, search results all use the same style.
|
||||||
|
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||||
|
// defined for "string").
|
||||||
|
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
||||||
|
|
||||||
|
if let Some(f) = find {
|
||||||
|
let all_cmds_vec = build_help_commands(engine_state, head);
|
||||||
|
let found_cmds_vec = highlight_search_in_table(
|
||||||
|
all_cmds_vec,
|
||||||
|
&f.item,
|
||||||
|
&["name", "usage", "search_terms"],
|
||||||
|
&string_style,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
return Ok(found_cmds_vec
|
||||||
|
.into_iter()
|
||||||
|
.into_pipeline_data(engine_state.ctrlc.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if rest.is_empty() {
|
||||||
|
let found_cmds_vec = build_help_commands(engine_state, head);
|
||||||
|
|
||||||
|
Ok(found_cmds_vec
|
||||||
|
.into_iter()
|
||||||
|
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||||
|
} else {
|
||||||
|
let mut name = String::new();
|
||||||
|
|
||||||
|
for r in &rest {
|
||||||
|
if !name.is_empty() {
|
||||||
|
name.push(' ');
|
||||||
|
}
|
||||||
|
name.push_str(&r.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = engine_state
|
||||||
|
.get_signatures_with_examples(false)
|
||||||
|
.iter()
|
||||||
|
.filter(|(signature, _, _, _, _)| signature.name == name)
|
||||||
|
.map(|(signature, examples, _, _, is_parser_keyword)| {
|
||||||
|
get_full_help(signature, examples, engine_state, stack, *is_parser_keyword)
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
if !output.is_empty() {
|
||||||
|
Ok(Value::String {
|
||||||
|
val: output.join("======================\n\n"),
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
Err(ShellError::CommandNotFound(span(&[
|
||||||
|
rest[0].span,
|
||||||
|
rest[rest.len() - 1].span,
|
||||||
|
])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
|
||||||
|
let commands = engine_state.get_decls_sorted(false);
|
||||||
|
let mut found_cmds_vec = Vec::new();
|
||||||
|
|
||||||
|
for (name_bytes, decl_id) in commands {
|
||||||
|
let mut cols = vec![];
|
||||||
|
let mut vals = vec![];
|
||||||
|
|
||||||
|
let name = String::from_utf8_lossy(&name_bytes).to_string();
|
||||||
|
let decl = engine_state.get_decl(decl_id);
|
||||||
|
let sig = decl.signature().update_from_command(name, decl.borrow());
|
||||||
|
|
||||||
|
let signatures = sig.to_string();
|
||||||
|
let key = sig.name;
|
||||||
|
let usage = sig.usage;
|
||||||
|
let search_terms = sig.search_terms;
|
||||||
|
|
||||||
|
cols.push("name".into());
|
||||||
|
vals.push(Value::String { val: key, span });
|
||||||
|
|
||||||
|
cols.push("category".into());
|
||||||
|
vals.push(Value::string(sig.category.to_string(), span));
|
||||||
|
|
||||||
|
cols.push("command_type".into());
|
||||||
|
vals.push(Value::String {
|
||||||
|
val: format!("{:?}", decl.command_type()).to_lowercase(),
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
|
||||||
|
cols.push("usage".into());
|
||||||
|
vals.push(Value::String { val: usage, span });
|
||||||
|
|
||||||
|
cols.push("signatures".into());
|
||||||
|
vals.push(Value::String {
|
||||||
|
val: if decl.is_parser_keyword() {
|
||||||
|
"".to_string()
|
||||||
|
} else {
|
||||||
|
signatures
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
|
||||||
|
cols.push("search_terms".into());
|
||||||
|
vals.push(if search_terms.is_empty() {
|
||||||
|
Value::nothing(span)
|
||||||
|
} else {
|
||||||
|
Value::String {
|
||||||
|
val: search_terms.join(", "),
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
found_cmds_vec.push(Value::Record { cols, vals, span });
|
||||||
|
}
|
||||||
|
|
||||||
|
found_cmds_vec
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use super::HelpCommands;
|
||||||
|
use crate::test_examples;
|
||||||
|
test_examples(HelpCommands {})
|
||||||
|
}
|
||||||
|
}
|
258
crates/nu-command/src/core_commands/help_modules.rs
Normal file
258
crates/nu-command/src/core_commands/help_modules.rs
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
use crate::help::highlight_search_in_table;
|
||||||
|
use nu_color_config::StyleComputer;
|
||||||
|
use nu_engine::{scope::ScopeData, CallExt};
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
span, AliasId, Category, DeclId, Example, IntoInterruptiblePipelineData, IntoPipelineData,
|
||||||
|
PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HelpModules;
|
||||||
|
|
||||||
|
impl Command for HelpModules {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"help modules"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Show help on nushell modules."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"When requesting help for a single module, its commands and aliases will be highlighted if they
|
||||||
|
are also available in the current scope. Commands/aliases that were imported under a different name
|
||||||
|
(such as with a prefix after `use some-module`) will be highlighted in parentheses."#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("help modules")
|
||||||
|
.category(Category::Core)
|
||||||
|
.rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the name of module to get help on",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"find",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"string to find in module names and usage",
|
||||||
|
Some('f'),
|
||||||
|
)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "show all modules",
|
||||||
|
example: "help modules",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "show help for single module",
|
||||||
|
example: "help modules my-module",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "search for string in module names and usages",
|
||||||
|
example: "help modules --find my-module",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
help_modules(engine_state, stack, call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn help_modules(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||||
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
// 🚩The following two-lines are copied from filters/find.rs:
|
||||||
|
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||||
|
// Currently, search results all use the same style.
|
||||||
|
// Also note that this sample string is passed into user-written code (the closure that may or may not be
|
||||||
|
// defined for "string").
|
||||||
|
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
||||||
|
|
||||||
|
if let Some(f) = find {
|
||||||
|
let all_cmds_vec = build_help_modules(engine_state, stack, head);
|
||||||
|
let found_cmds_vec =
|
||||||
|
highlight_search_in_table(all_cmds_vec, &f.item, &["name", "usage"], &string_style)?;
|
||||||
|
|
||||||
|
return Ok(found_cmds_vec
|
||||||
|
.into_iter()
|
||||||
|
.into_pipeline_data(engine_state.ctrlc.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if rest.is_empty() {
|
||||||
|
let found_cmds_vec = build_help_modules(engine_state, stack, head);
|
||||||
|
|
||||||
|
Ok(found_cmds_vec
|
||||||
|
.into_iter()
|
||||||
|
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||||
|
} else {
|
||||||
|
let mut name = String::new();
|
||||||
|
|
||||||
|
for r in &rest {
|
||||||
|
if !name.is_empty() {
|
||||||
|
name.push(' ');
|
||||||
|
}
|
||||||
|
name.push_str(&r.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let module_id = if let Some(id) = engine_state.find_module(name.as_bytes(), &[]) {
|
||||||
|
id
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::ModuleNotFoundAtRuntime(
|
||||||
|
name,
|
||||||
|
span(&rest.iter().map(|r| r.span).collect::<Vec<Span>>()),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let module = engine_state.get_module(module_id);
|
||||||
|
|
||||||
|
let module_usage = engine_state.build_module_usage(module_id);
|
||||||
|
|
||||||
|
// TODO: merge this into documentation.rs at some point
|
||||||
|
const G: &str = "\x1b[32m"; // green
|
||||||
|
const C: &str = "\x1b[36m"; // cyan
|
||||||
|
const CB: &str = "\x1b[1;36m"; // cyan bold
|
||||||
|
const RESET: &str = "\x1b[0m"; // reset
|
||||||
|
|
||||||
|
let mut long_desc = String::new();
|
||||||
|
|
||||||
|
if let Some((usage, extra_usage)) = module_usage {
|
||||||
|
long_desc.push_str(&usage);
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
|
||||||
|
if !extra_usage.is_empty() {
|
||||||
|
long_desc.push_str(&extra_usage);
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long_desc.push_str(&format!("{G}Module{RESET}: {C}{name}{RESET}"));
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
|
||||||
|
if !module.decls.is_empty() {
|
||||||
|
let commands: Vec<(Vec<u8>, DeclId)> = engine_state.get_decls_sorted(false).collect();
|
||||||
|
|
||||||
|
let mut module_commands: Vec<(&[u8], DeclId)> = module
|
||||||
|
.decls
|
||||||
|
.iter()
|
||||||
|
.map(|(name, id)| (name.as_ref(), *id))
|
||||||
|
.collect();
|
||||||
|
module_commands.sort_by(|a, b| a.0.cmp(b.0));
|
||||||
|
|
||||||
|
let commands_str = module_commands
|
||||||
|
.iter()
|
||||||
|
.map(|(name_bytes, id)| {
|
||||||
|
let name = String::from_utf8_lossy(name_bytes);
|
||||||
|
if let Some((used_name_bytes, _)) =
|
||||||
|
commands.iter().find(|(_, decl_id)| id == decl_id)
|
||||||
|
{
|
||||||
|
if engine_state.find_decl(name.as_bytes(), &[]).is_some() {
|
||||||
|
format!("{CB}{name}{RESET}")
|
||||||
|
} else {
|
||||||
|
let command_name = String::from_utf8_lossy(used_name_bytes);
|
||||||
|
format!("{name} ({CB}{command_name}{RESET})")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
format!("{name}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
long_desc.push_str(&format!("{G}Exported commands{RESET}:\n {commands_str}"));
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !module.aliases.is_empty() {
|
||||||
|
let aliases: Vec<(Vec<u8>, AliasId)> = engine_state.get_aliases_sorted(false).collect();
|
||||||
|
|
||||||
|
let mut module_aliases: Vec<(&[u8], AliasId)> = module
|
||||||
|
.aliases
|
||||||
|
.iter()
|
||||||
|
.map(|(name, id)| (name.as_ref(), *id))
|
||||||
|
.collect();
|
||||||
|
module_aliases.sort_by(|a, b| a.0.cmp(b.0));
|
||||||
|
|
||||||
|
let aliases_str = module_aliases
|
||||||
|
.iter()
|
||||||
|
.map(|(name_bytes, id)| {
|
||||||
|
let name = String::from_utf8_lossy(name_bytes);
|
||||||
|
if let Some((used_name_bytes, _)) =
|
||||||
|
aliases.iter().find(|(_, alias_id)| id == alias_id)
|
||||||
|
{
|
||||||
|
if engine_state.find_alias(name.as_bytes(), &[]).is_some() {
|
||||||
|
format!("{CB}{name}{RESET}")
|
||||||
|
} else {
|
||||||
|
let alias_name = String::from_utf8_lossy(used_name_bytes);
|
||||||
|
format!("{name} ({CB}{alias_name}{RESET})")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
format!("{name}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
long_desc.push_str(&format!("{G}Exported aliases{RESET}:\n {aliases_str}"));
|
||||||
|
long_desc.push_str("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if module.env_block.is_some() {
|
||||||
|
long_desc.push_str(&format!("This module {C}exports{RESET} environment."));
|
||||||
|
} else {
|
||||||
|
long_desc.push_str(&format!(
|
||||||
|
"This module {C}does not export{RESET} environment."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = engine_state.get_config();
|
||||||
|
if !config.use_ansi_coloring {
|
||||||
|
long_desc = nu_utils::strip_ansi_string_likely(long_desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::String {
|
||||||
|
val: long_desc,
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_help_modules(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
||||||
|
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||||
|
scope_data.populate_modules();
|
||||||
|
|
||||||
|
scope_data.collect_modules(span)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use super::HelpModules;
|
||||||
|
use crate::test_examples;
|
||||||
|
test_examples(HelpModules {})
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,9 @@ mod export_use;
|
|||||||
mod extern_;
|
mod extern_;
|
||||||
mod for_;
|
mod for_;
|
||||||
pub mod help;
|
pub mod help;
|
||||||
|
pub mod help_aliases;
|
||||||
|
pub mod help_commands;
|
||||||
|
pub mod help_modules;
|
||||||
mod help_operators;
|
mod help_operators;
|
||||||
mod hide;
|
mod hide;
|
||||||
mod hide_env;
|
mod hide_env;
|
||||||
@ -59,6 +62,9 @@ pub use export_use::ExportUse;
|
|||||||
pub use extern_::Extern;
|
pub use extern_::Extern;
|
||||||
pub use for_::For;
|
pub use for_::For;
|
||||||
pub use help::Help;
|
pub use help::Help;
|
||||||
|
pub use help_aliases::HelpAliases;
|
||||||
|
pub use help_commands::HelpCommands;
|
||||||
|
pub use help_modules::HelpModules;
|
||||||
pub use help_operators::HelpOperators;
|
pub use help_operators::HelpOperators;
|
||||||
pub use hide::Hide;
|
pub use hide::Hide;
|
||||||
pub use hide_env::HideEnv;
|
pub use hide_env::HideEnv;
|
||||||
|
@ -50,6 +50,9 @@ pub fn create_default_context() -> EngineState {
|
|||||||
Extern,
|
Extern,
|
||||||
For,
|
For,
|
||||||
Help,
|
Help,
|
||||||
|
HelpAliases,
|
||||||
|
HelpCommands,
|
||||||
|
HelpModules,
|
||||||
HelpOperators,
|
HelpOperators,
|
||||||
Hide,
|
Hide,
|
||||||
HideEnv,
|
HideEnv,
|
||||||
|
@ -307,7 +307,7 @@ fn parse_module(
|
|||||||
let end = working_set.next_span_start();
|
let end = working_set.next_span_start();
|
||||||
|
|
||||||
let new_span = Span::new(start, end);
|
let new_span = Span::new(start, end);
|
||||||
let (_, _, err) = parse_module_block(working_set, new_span, &[]);
|
let (_, _, _, err) = parse_module_block(working_set, new_span, &[]);
|
||||||
|
|
||||||
if err.is_some() {
|
if err.is_some() {
|
||||||
if is_debug {
|
if is_debug {
|
||||||
|
@ -296,16 +296,25 @@ impl ExternalCommand {
|
|||||||
"'{}' was not found; did you mean '{s}'?",
|
"'{}' was not found; did you mean '{s}'?",
|
||||||
self.name.item
|
self.name.item
|
||||||
)
|
)
|
||||||
} else if self.name.item == s {
|
} else {
|
||||||
let sugg = engine_state.which_module_has_decl(s.as_bytes());
|
let cmd_name = &self.name.item;
|
||||||
if let Some(sugg) = sugg {
|
let maybe_module = engine_state
|
||||||
let sugg = String::from_utf8_lossy(sugg);
|
.which_module_has_decl(cmd_name.as_bytes(), &[]);
|
||||||
format!("command '{s}' was not found but it exists in module '{sugg}'; try using `{sugg} {s}`")
|
if let Some(module_name) = maybe_module {
|
||||||
|
let module_name = String::from_utf8_lossy(module_name);
|
||||||
|
let new_name = &[module_name.as_ref(), cmd_name].join(" ");
|
||||||
|
|
||||||
|
if engine_state
|
||||||
|
.find_decl(new_name.as_bytes(), &[])
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
format!("command '{cmd_name}' was not found but it was imported from module '{module_name}'; try using `{new_name}`")
|
||||||
|
} else {
|
||||||
|
format!("command '{cmd_name}' was not found but it exists in module '{module_name}'; try importing it with `use`")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
format!("did you mean '{s}'?")
|
format!("did you mean '{s}'?")
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
format!("did you mean '{s}'?")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
@ -47,7 +47,7 @@ fn alias_fails_with_invalid_name() {
|
|||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".", pipeline(
|
cwd: ".", pipeline(
|
||||||
r#"
|
r#"
|
||||||
alias 1234 = echo "test"
|
alias 1234 = echo "test"
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
assert!(actual.err.contains(err_msg));
|
assert!(actual.err.contains(err_msg));
|
||||||
@ -55,7 +55,7 @@ fn alias_fails_with_invalid_name() {
|
|||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".", pipeline(
|
cwd: ".", pipeline(
|
||||||
r#"
|
r#"
|
||||||
alias 5gib = echo "test"
|
alias 5gib = echo "test"
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
assert!(actual.err.contains(err_msg));
|
assert!(actual.err.contains(err_msg));
|
||||||
@ -63,7 +63,7 @@ fn alias_fails_with_invalid_name() {
|
|||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".", pipeline(
|
cwd: ".", pipeline(
|
||||||
r#"
|
r#"
|
||||||
alias "te#t" = echo "test"
|
alias "te#t" = echo "test"
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
assert!(actual.err.contains(err_msg));
|
assert!(actual.err.contains(err_msg));
|
||||||
@ -85,5 +85,5 @@ fn alias_alone_lists_aliases() {
|
|||||||
alias a = 3; alias
|
alias a = 3; alias
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
assert!(actual.out.contains("alias") && actual.out.contains("expansion"));
|
assert!(actual.out.contains("name") && actual.out.contains("expansion"));
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use nu_test_support::{nu, pipeline};
|
use nu_test_support::fs::Stub::FileWithContent;
|
||||||
|
use nu_test_support::playground::Playground;
|
||||||
|
use nu_test_support::{nu, nu_repl_code, pipeline};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn help_commands_length() {
|
fn help_commands_length() {
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".", pipeline(
|
cwd: ".", pipeline(
|
||||||
r#"
|
r#"
|
||||||
help commands | length
|
help commands | length
|
||||||
"#
|
"#
|
||||||
@ -26,3 +28,289 @@ fn help_shows_signature() {
|
|||||||
let actual = nu!(cwd: ".", pipeline("help alias"));
|
let actual = nu!(cwd: ".", pipeline("help alias"));
|
||||||
assert!(!actual.out.contains("Signatures"));
|
assert!(!actual.out.contains("Signatures"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_aliases() {
|
||||||
|
let code = &[
|
||||||
|
"alias SPAM = print 'spam'",
|
||||||
|
"help aliases | where name == SPAM | length",
|
||||||
|
];
|
||||||
|
let actual = nu!(cwd: ".", nu_repl_code(code));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_alias_usage_1() {
|
||||||
|
Playground::setup("help_alias_usage_1", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContent(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
# line1
|
||||||
|
alias SPAM = print 'spam'
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let code = &[
|
||||||
|
"source spam.nu",
|
||||||
|
"help aliases | where name == SPAM | get 0.usage",
|
||||||
|
];
|
||||||
|
let actual = nu!(cwd: dirs.test(), nu_repl_code(code));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "line1");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_alias_usage_2() {
|
||||||
|
let code = &[
|
||||||
|
"alias SPAM = print 'spam' # line2",
|
||||||
|
"help aliases | where name == SPAM | get 0.usage",
|
||||||
|
];
|
||||||
|
let actual = nu!(cwd: ".", nu_repl_code(code));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "line2");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_alias_usage_3() {
|
||||||
|
Playground::setup("help_alias_usage_3", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContent(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
# line1
|
||||||
|
alias SPAM = print 'spam' # line2
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let code = &[
|
||||||
|
"source spam.nu",
|
||||||
|
"help aliases | where name == SPAM | get 0.usage",
|
||||||
|
];
|
||||||
|
let actual = nu!(cwd: dirs.test(), nu_repl_code(code));
|
||||||
|
|
||||||
|
assert!(actual.out.contains("line1"));
|
||||||
|
assert!(actual.out.contains("line2"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_alias_name() {
|
||||||
|
Playground::setup("help_alias_name", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContent(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
# line1
|
||||||
|
alias SPAM = print 'spam' # line2
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let code = &["source spam.nu", "help aliases SPAM"];
|
||||||
|
let actual = nu!(cwd: dirs.test(), nu_repl_code(code));
|
||||||
|
|
||||||
|
assert!(actual.out.contains("line1"));
|
||||||
|
assert!(actual.out.contains("line2"));
|
||||||
|
assert!(actual.out.contains("SPAM"));
|
||||||
|
assert!(actual.out.contains("print 'spam'"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_alias_name_f() {
|
||||||
|
Playground::setup("help_alias_name_f", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContent(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
# line1
|
||||||
|
alias SPAM = print 'spam' # line2
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let code = &["source spam.nu", "help aliases -f SPAM | get 0.usage"];
|
||||||
|
let actual = nu!(cwd: dirs.test(), nu_repl_code(code));
|
||||||
|
|
||||||
|
assert!(actual.out.contains("line1"));
|
||||||
|
assert!(actual.out.contains("line2"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_export_alias_name_single_word() {
|
||||||
|
Playground::setup("help_export_alias_name_single_word", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContent(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
# line1
|
||||||
|
export alias SPAM = print 'spam' # line2
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let code = &["use spam.nu SPAM", "help aliases SPAM"];
|
||||||
|
let actual = nu!(cwd: dirs.test(), nu_repl_code(code));
|
||||||
|
|
||||||
|
assert!(actual.out.contains("line1"));
|
||||||
|
assert!(actual.out.contains("line2"));
|
||||||
|
assert!(actual.out.contains("SPAM"));
|
||||||
|
assert!(actual.out.contains("print 'spam'"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_export_alias_name_multi_word() {
|
||||||
|
Playground::setup("help_export_alias_name_multi_word", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContent(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
# line1
|
||||||
|
export alias SPAM = print 'spam' # line2
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let code = &["use spam.nu", "help aliases spam SPAM"];
|
||||||
|
let actual = nu!(cwd: dirs.test(), nu_repl_code(code));
|
||||||
|
|
||||||
|
assert!(actual.out.contains("line1"));
|
||||||
|
assert!(actual.out.contains("line2"));
|
||||||
|
assert!(actual.out.contains("SPAM"));
|
||||||
|
assert!(actual.out.contains("print 'spam'"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_module_usage_1() {
|
||||||
|
Playground::setup("help_module_usage", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContent(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
# line1
|
||||||
|
module SPAM {
|
||||||
|
# line2
|
||||||
|
} #line3
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let code = &[
|
||||||
|
"source spam.nu",
|
||||||
|
"help modules | where name == SPAM | get 0.usage",
|
||||||
|
];
|
||||||
|
let actual = nu!(cwd: dirs.test(), nu_repl_code(code));
|
||||||
|
|
||||||
|
assert!(actual.out.contains("line1"));
|
||||||
|
assert!(actual.out.contains("line2"));
|
||||||
|
assert!(actual.out.contains("line3"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_module_name() {
|
||||||
|
Playground::setup("help_module_name", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContent(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
# line1
|
||||||
|
module SPAM {
|
||||||
|
# line2
|
||||||
|
} #line3
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let code = &["source spam.nu", "help modules SPAM"];
|
||||||
|
let actual = nu!(cwd: dirs.test(), nu_repl_code(code));
|
||||||
|
|
||||||
|
assert!(actual.out.contains("line1"));
|
||||||
|
assert!(actual.out.contains("line2"));
|
||||||
|
assert!(actual.out.contains("line3"));
|
||||||
|
assert!(actual.out.contains("SPAM"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_module_sorted_decls() {
|
||||||
|
Playground::setup("help_module_sorted_decls", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContent(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
module SPAM {
|
||||||
|
export def z [] {}
|
||||||
|
export def a [] {}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let code = &["source spam.nu", "help modules SPAM"];
|
||||||
|
let actual = nu!(cwd: dirs.test(), nu_repl_code(code));
|
||||||
|
|
||||||
|
assert!(actual.out.contains("a, z"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_module_sorted_aliases() {
|
||||||
|
Playground::setup("help_module_sorted_aliases", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContent(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
module SPAM {
|
||||||
|
export alias z = 'z'
|
||||||
|
export alias a = 'a'
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let code = &["source spam.nu", "help modules SPAM"];
|
||||||
|
let actual = nu!(cwd: dirs.test(), nu_repl_code(code));
|
||||||
|
|
||||||
|
assert!(actual.out.contains("a, z"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_usage_extra_usage() {
|
||||||
|
Playground::setup("help_usage_extra_usage", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContent(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
# module_line1
|
||||||
|
#
|
||||||
|
# module_line2
|
||||||
|
|
||||||
|
# def_line1
|
||||||
|
#
|
||||||
|
# def_line2
|
||||||
|
export def foo [] {}
|
||||||
|
|
||||||
|
# alias_line1
|
||||||
|
#
|
||||||
|
# alias_line2
|
||||||
|
export alias bar = 'bar'
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline("use spam.nu *; help modules spam"));
|
||||||
|
assert!(actual.out.contains("module_line1"));
|
||||||
|
assert!(actual.out.contains("module_line2"));
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(),
|
||||||
|
pipeline("use spam.nu *; help modules | where name == spam | get 0.usage"));
|
||||||
|
assert!(actual.out.contains("module_line1"));
|
||||||
|
assert!(!actual.out.contains("module_line2"));
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline("use spam.nu *; help commands foo"));
|
||||||
|
assert!(actual.out.contains("def_line1"));
|
||||||
|
assert!(actual.out.contains("def_line2"));
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(),
|
||||||
|
pipeline("use spam.nu *; help commands | where name == foo | get 0.usage"));
|
||||||
|
assert!(actual.out.contains("def_line1"));
|
||||||
|
assert!(!actual.out.contains("def_line2"));
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline("use spam.nu *; help aliases bar"));
|
||||||
|
assert!(actual.out.contains("alias_line1"));
|
||||||
|
assert!(actual.out.contains("alias_line2"));
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(),
|
||||||
|
pipeline("use spam.nu *; help aliases | where name == bar | get 0.usage"));
|
||||||
|
assert!(actual.out.contains("alias_line1"));
|
||||||
|
assert!(!actual.out.contains("alias_line2"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -185,15 +185,29 @@ fn use_export_env_combined() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn use_module_creates_accurate_did_you_mean() {
|
fn use_module_creates_accurate_did_you_mean_1() {
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".", pipeline(
|
cwd: ".", pipeline(
|
||||||
r#"
|
r#"
|
||||||
module spam { export def foo [] { "foo" } }; use spam; foo
|
module spam { export def foo [] { "foo" } }; use spam; foo
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert!(actual.err.contains(
|
assert!(actual.err.contains(
|
||||||
"command 'foo' was not found but it exists in module 'spam'; try using `spam foo`"
|
"command 'foo' was not found but it was imported from module 'spam'; try using `spam foo`"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_module_creates_accurate_did_you_mean_2() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: ".", pipeline(
|
||||||
|
r#"
|
||||||
|
module spam { export def foo [] { "foo" } }; foo
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert!(actual.err.contains(
|
||||||
|
"command 'foo' was not found but it exists in module 'spam'; try importing it with `use`"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -26,12 +26,12 @@ fn quickcheck_parse(data: String) -> bool {
|
|||||||
#[test]
|
#[test]
|
||||||
fn signature_name_matches_command_name() {
|
fn signature_name_matches_command_name() {
|
||||||
let ctx = crate::create_default_context();
|
let ctx = crate::create_default_context();
|
||||||
let decls = ctx.get_decl_ids_sorted(true);
|
let decls = ctx.get_decls_sorted(true);
|
||||||
let mut failures = Vec::new();
|
let mut failures = Vec::new();
|
||||||
|
|
||||||
for decl_id in decls {
|
for (name_bytes, decl_id) in decls {
|
||||||
let cmd = ctx.get_decl(decl_id);
|
let cmd = ctx.get_decl(decl_id);
|
||||||
let cmd_name = cmd.name();
|
let cmd_name = String::from_utf8_lossy(&name_bytes);
|
||||||
let sig_name = cmd.signature().name;
|
let sig_name = cmd.signature().name;
|
||||||
let category = cmd.signature().category;
|
let category = cmd.signature().category;
|
||||||
|
|
||||||
@ -52,10 +52,10 @@ fn signature_name_matches_command_name() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn commands_declare_input_output_types() {
|
fn commands_declare_input_output_types() {
|
||||||
let ctx = crate::create_default_context();
|
let ctx = crate::create_default_context();
|
||||||
let decls = ctx.get_decl_ids_sorted(true);
|
let decls = ctx.get_decls_sorted(true);
|
||||||
let mut failures = Vec::new();
|
let mut failures = Vec::new();
|
||||||
|
|
||||||
for decl_id in decls {
|
for (_, decl_id) in decls {
|
||||||
let cmd = ctx.get_decl(decl_id);
|
let cmd = ctx.get_decl(decl_id);
|
||||||
let sig_name = cmd.signature().name;
|
let sig_name = cmd.signature().name;
|
||||||
let category = cmd.signature().category;
|
let category = cmd.signature().category;
|
||||||
@ -83,12 +83,12 @@ fn commands_declare_input_output_types() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn no_search_term_duplicates() {
|
fn no_search_term_duplicates() {
|
||||||
let ctx = crate::create_default_context();
|
let ctx = crate::create_default_context();
|
||||||
let decls = ctx.get_decl_ids_sorted(true);
|
let decls = ctx.get_decls_sorted(true);
|
||||||
let mut failures = Vec::new();
|
let mut failures = Vec::new();
|
||||||
|
|
||||||
for decl_id in decls {
|
for (name_bytes, decl_id) in decls {
|
||||||
let cmd = ctx.get_decl(decl_id);
|
let cmd = ctx.get_decl(decl_id);
|
||||||
let cmd_name = cmd.name();
|
let cmd_name = String::from_utf8_lossy(&name_bytes);
|
||||||
let search_terms = cmd.search_terms();
|
let search_terms = cmd.search_terms();
|
||||||
let category = cmd.signature().category;
|
let category = cmd.signature().category;
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ pub mod documentation;
|
|||||||
pub mod env;
|
pub mod env;
|
||||||
mod eval;
|
mod eval;
|
||||||
mod glob_from;
|
mod glob_from;
|
||||||
mod scope;
|
pub mod scope;
|
||||||
|
|
||||||
pub use call_ext::CallExt;
|
pub use call_ext::CallExt;
|
||||||
pub use column::get_columns;
|
pub use column::get_columns;
|
||||||
|
@ -12,7 +12,7 @@ pub fn create_scope(
|
|||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
let mut scope_data = ScopeData::new(engine_state, stack);
|
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||||
|
|
||||||
scope_data.populate_from_overlays();
|
scope_data.populate_all();
|
||||||
|
|
||||||
let mut cols = vec![];
|
let mut cols = vec![];
|
||||||
let mut vals = vec![];
|
let mut vals = vec![];
|
||||||
@ -31,15 +31,7 @@ pub fn create_scope(
|
|||||||
|
|
||||||
cols.push("aliases".to_string());
|
cols.push("aliases".to_string());
|
||||||
vals.push(Value::List {
|
vals.push(Value::List {
|
||||||
vals: scope_data
|
vals: scope_data.collect_aliases(span),
|
||||||
.collect_aliases(span)
|
|
||||||
.into_iter()
|
|
||||||
.map(|(alias, value)| Value::Record {
|
|
||||||
cols: vec!["alias".into(), "expansion".into()],
|
|
||||||
vals: vec![alias, value],
|
|
||||||
span,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
span,
|
span,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -55,7 +47,7 @@ pub fn create_scope(
|
|||||||
Ok(Value::Record { cols, vals, span })
|
Ok(Value::Record { cols, vals, span })
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ScopeData<'e, 's> {
|
pub struct ScopeData<'e, 's> {
|
||||||
engine_state: &'e EngineState,
|
engine_state: &'e EngineState,
|
||||||
stack: &'s Stack,
|
stack: &'s Stack,
|
||||||
vars_map: HashMap<&'e Vec<u8>, &'e usize>,
|
vars_map: HashMap<&'e Vec<u8>, &'e usize>,
|
||||||
@ -78,7 +70,7 @@ impl<'e, 's> ScopeData<'e, 's> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn populate_from_overlays(&mut self) {
|
pub fn populate_all(&mut self) {
|
||||||
for overlay_frame in self.engine_state.active_overlays(&[]) {
|
for overlay_frame in self.engine_state.active_overlays(&[]) {
|
||||||
self.vars_map.extend(&overlay_frame.vars);
|
self.vars_map.extend(&overlay_frame.vars);
|
||||||
self.commands_map.extend(&overlay_frame.decls);
|
self.commands_map.extend(&overlay_frame.decls);
|
||||||
@ -88,7 +80,19 @@ impl<'e, 's> ScopeData<'e, 's> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_vars(&mut self, span: Span) -> Vec<Value> {
|
pub fn populate_aliases(&mut self) {
|
||||||
|
for overlay_frame in self.engine_state.active_overlays(&[]) {
|
||||||
|
self.aliases_map.extend(&overlay_frame.aliases);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn populate_modules(&mut self) {
|
||||||
|
for overlay_frame in self.engine_state.active_overlays(&[]) {
|
||||||
|
self.modules_map.extend(&overlay_frame.modules);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collect_vars(&self, span: Span) -> Vec<Value> {
|
||||||
let mut vars = vec![];
|
let mut vars = vec![];
|
||||||
for var in &self.vars_map {
|
for var in &self.vars_map {
|
||||||
let var_name = Value::string(String::from_utf8_lossy(var.0).to_string(), span);
|
let var_name = Value::string(String::from_utf8_lossy(var.0).to_string(), span);
|
||||||
@ -110,7 +114,7 @@ impl<'e, 's> ScopeData<'e, 's> {
|
|||||||
vars
|
vars
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_commands(&mut self, span: Span) -> Vec<Value> {
|
pub fn collect_commands(&self, span: Span) -> Vec<Value> {
|
||||||
let mut commands = vec![];
|
let mut commands = vec![];
|
||||||
for ((command_name, _), decl_id) in &self.commands_map {
|
for ((command_name, _), decl_id) in &self.commands_map {
|
||||||
if self.visibility.is_decl_id_visible(decl_id) {
|
if self.visibility.is_decl_id_visible(decl_id) {
|
||||||
@ -457,12 +461,13 @@ impl<'e, 's> ScopeData<'e, 's> {
|
|||||||
sig_records
|
sig_records
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_aliases(&mut self, span: Span) -> Vec<(Value, Value)> {
|
pub fn collect_aliases(&self, span: Span) -> Vec<Value> {
|
||||||
let mut aliases = vec![];
|
let mut aliases = vec![];
|
||||||
for (alias_name, alias_id) in &self.aliases_map {
|
for (alias_name, alias_id) in &self.aliases_map {
|
||||||
if self.visibility.is_alias_id_visible(alias_id) {
|
if self.visibility.is_alias_id_visible(alias_id) {
|
||||||
let alias = self.engine_state.get_alias(**alias_id);
|
let alias = self.engine_state.get_alias(**alias_id);
|
||||||
let mut alias_text = String::new();
|
let mut alias_text = String::new();
|
||||||
|
|
||||||
for span in alias {
|
for span in alias {
|
||||||
let contents = self.engine_state.get_span_contents(span);
|
let contents = self.engine_state.get_span_contents(span);
|
||||||
if !alias_text.is_empty() {
|
if !alias_text.is_empty() {
|
||||||
@ -470,13 +475,22 @@ impl<'e, 's> ScopeData<'e, 's> {
|
|||||||
}
|
}
|
||||||
alias_text.push_str(&String::from_utf8_lossy(contents));
|
alias_text.push_str(&String::from_utf8_lossy(contents));
|
||||||
}
|
}
|
||||||
aliases.push((
|
|
||||||
Value::String {
|
let alias_usage = self
|
||||||
val: String::from_utf8_lossy(alias_name).to_string(),
|
.engine_state
|
||||||
span,
|
.build_alias_usage(**alias_id)
|
||||||
},
|
.map(|(usage, _)| usage)
|
||||||
Value::string(alias_text, span),
|
.unwrap_or_default();
|
||||||
));
|
|
||||||
|
aliases.push(Value::Record {
|
||||||
|
cols: vec!["name".into(), "expansion".into(), "usage".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::string(String::from_utf8_lossy(alias_name), span),
|
||||||
|
Value::string(alias_text, span),
|
||||||
|
Value::string(alias_usage, span),
|
||||||
|
],
|
||||||
|
span,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -484,12 +498,59 @@ impl<'e, 's> ScopeData<'e, 's> {
|
|||||||
aliases
|
aliases
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_modules(&mut self, span: Span) -> Vec<Value> {
|
pub fn collect_modules(&self, span: Span) -> Vec<Value> {
|
||||||
let mut modules = vec![];
|
let mut modules = vec![];
|
||||||
|
|
||||||
for module in &self.modules_map {
|
for (module_name, module_id) in &self.modules_map {
|
||||||
modules.push(Value::String {
|
let module = self.engine_state.get_module(**module_id);
|
||||||
val: String::from_utf8_lossy(module.0).to_string(),
|
|
||||||
|
let export_commands: Vec<Value> = module
|
||||||
|
.decls
|
||||||
|
.keys()
|
||||||
|
.map(|bytes| Value::string(String::from_utf8_lossy(bytes), span))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let export_aliases: Vec<Value> = module
|
||||||
|
.aliases
|
||||||
|
.keys()
|
||||||
|
.map(|bytes| Value::string(String::from_utf8_lossy(bytes), span))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let export_env_block = module.env_block.map_or_else(
|
||||||
|
|| Value::nothing(span),
|
||||||
|
|block_id| Value::Block {
|
||||||
|
val: block_id,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let module_usage = self
|
||||||
|
.engine_state
|
||||||
|
.build_module_usage(**module_id)
|
||||||
|
.map(|(usage, _)| usage)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
modules.push(Value::Record {
|
||||||
|
cols: vec![
|
||||||
|
"name".into(),
|
||||||
|
"commands".into(),
|
||||||
|
"aliases".into(),
|
||||||
|
"env_block".into(),
|
||||||
|
"usage".into(),
|
||||||
|
],
|
||||||
|
vals: vec![
|
||||||
|
Value::string(String::from_utf8_lossy(module_name), span),
|
||||||
|
Value::List {
|
||||||
|
vals: export_commands,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
Value::List {
|
||||||
|
vals: export_aliases,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
export_env_block,
|
||||||
|
Value::string(module_usage, span),
|
||||||
|
],
|
||||||
span,
|
span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -497,7 +558,7 @@ impl<'e, 's> ScopeData<'e, 's> {
|
|||||||
modules
|
modules
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_engine_state(&mut self, span: Span) -> Value {
|
pub fn collect_engine_state(&self, span: Span) -> Value {
|
||||||
let engine_state_cols = vec![
|
let engine_state_cols = vec![
|
||||||
"source_bytes".to_string(),
|
"source_bytes".to_string(),
|
||||||
"num_vars".to_string(),
|
"num_vars".to_string(),
|
||||||
|
@ -25,7 +25,7 @@ use crate::{
|
|||||||
parse_internal_call, parse_multispan_value, parse_signature, parse_string, parse_value,
|
parse_internal_call, parse_multispan_value, parse_signature, parse_string, parse_value,
|
||||||
parse_var_with_opt_type, trim_quotes, ParsedInternalCall,
|
parse_var_with_opt_type, trim_quotes, ParsedInternalCall,
|
||||||
},
|
},
|
||||||
unescape_unquote_string, ParseError,
|
unescape_unquote_string, ParseError, Token, TokenContents,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn parse_def_predecl(
|
pub fn parse_def_predecl(
|
||||||
@ -229,57 +229,6 @@ pub fn parse_for(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_usage(working_set: &StateWorkingSet, spans: &[Span]) -> String {
|
|
||||||
let mut usage = String::new();
|
|
||||||
|
|
||||||
let mut num_spaces = 0;
|
|
||||||
let mut first = true;
|
|
||||||
|
|
||||||
// Use the comments to build the usage
|
|
||||||
for comment_part in spans {
|
|
||||||
let contents = working_set.get_span_contents(*comment_part);
|
|
||||||
|
|
||||||
let comment_line = if first {
|
|
||||||
// Count the number of spaces still at the front, skipping the '#'
|
|
||||||
let mut pos = 1;
|
|
||||||
while pos < contents.len() {
|
|
||||||
if let Some(b' ') = contents.get(pos) {
|
|
||||||
// continue
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
num_spaces = pos;
|
|
||||||
|
|
||||||
first = false;
|
|
||||||
|
|
||||||
String::from_utf8_lossy(&contents[pos..]).to_string()
|
|
||||||
} else {
|
|
||||||
let mut pos = 1;
|
|
||||||
|
|
||||||
while pos < contents.len() && pos < num_spaces {
|
|
||||||
if let Some(b' ') = contents.get(pos) {
|
|
||||||
// continue
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
String::from_utf8_lossy(&contents[pos..]).to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
if !usage.is_empty() {
|
|
||||||
usage.push('\n');
|
|
||||||
}
|
|
||||||
usage.push_str(&comment_line);
|
|
||||||
}
|
|
||||||
|
|
||||||
usage
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_def(
|
pub fn parse_def(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
lite_command: &LiteCommand,
|
lite_command: &LiteCommand,
|
||||||
@ -287,7 +236,7 @@ pub fn parse_def(
|
|||||||
) -> (Pipeline, Option<ParseError>) {
|
) -> (Pipeline, Option<ParseError>) {
|
||||||
let spans = &lite_command.parts[..];
|
let spans = &lite_command.parts[..];
|
||||||
|
|
||||||
let usage = build_usage(working_set, &lite_command.comments);
|
let (usage, extra_usage) = working_set.build_usage(&lite_command.comments);
|
||||||
|
|
||||||
// Checking that the function is used with the correct name
|
// Checking that the function is used with the correct name
|
||||||
// Maybe this is not necessary but it is a sanity check
|
// Maybe this is not necessary but it is a sanity check
|
||||||
@ -397,6 +346,7 @@ pub fn parse_def(
|
|||||||
signature.name = name.clone();
|
signature.name = name.clone();
|
||||||
*signature = signature.add_help();
|
*signature = signature.add_help();
|
||||||
signature.usage = usage;
|
signature.usage = usage;
|
||||||
|
signature.extra_usage = extra_usage;
|
||||||
|
|
||||||
*declaration = signature.clone().into_block_command(block_id);
|
*declaration = signature.clone().into_block_command(block_id);
|
||||||
|
|
||||||
@ -444,7 +394,7 @@ pub fn parse_extern(
|
|||||||
let spans = &lite_command.parts;
|
let spans = &lite_command.parts;
|
||||||
let mut error = None;
|
let mut error = None;
|
||||||
|
|
||||||
let usage = build_usage(working_set, &lite_command.comments);
|
let (usage, extra_usage) = working_set.build_usage(&lite_command.comments);
|
||||||
|
|
||||||
// Checking that the function is used with the correct name
|
// Checking that the function is used with the correct name
|
||||||
// Maybe this is not necessary but it is a sanity check
|
// Maybe this is not necessary but it is a sanity check
|
||||||
@ -515,11 +465,12 @@ pub fn parse_extern(
|
|||||||
|
|
||||||
signature.name = name.clone();
|
signature.name = name.clone();
|
||||||
signature.usage = usage.clone();
|
signature.usage = usage.clone();
|
||||||
|
signature.extra_usage = extra_usage.clone();
|
||||||
signature.allows_unknown_args = true;
|
signature.allows_unknown_args = true;
|
||||||
|
|
||||||
let decl = KnownExternal {
|
let decl = KnownExternal {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
usage,
|
usage: [usage, extra_usage].join("\n"),
|
||||||
signature,
|
signature,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -559,9 +510,11 @@ pub fn parse_extern(
|
|||||||
|
|
||||||
pub fn parse_alias(
|
pub fn parse_alias(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
spans: &[Span],
|
lite_command: &LiteCommand,
|
||||||
expand_aliases_denylist: &[usize],
|
expand_aliases_denylist: &[usize],
|
||||||
) -> (Pipeline, Option<ParseError>) {
|
) -> (Pipeline, Option<ParseError>) {
|
||||||
|
let spans = &lite_command.parts;
|
||||||
|
|
||||||
// if the call is "alias", turn it into "print $nu.scope.aliases"
|
// if the call is "alias", turn it into "print $nu.scope.aliases"
|
||||||
if spans.len() == 1 {
|
if spans.len() == 1 {
|
||||||
let head = Expression {
|
let head = Expression {
|
||||||
@ -672,7 +625,7 @@ pub fn parse_alias(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
working_set.add_alias(alias_name, replacement);
|
working_set.add_alias(alias_name, replacement, lite_command.comments.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let err = if spans.len() < 4 {
|
let err = if spans.len() < 4 {
|
||||||
@ -785,7 +738,7 @@ pub fn parse_export_in_block(
|
|||||||
}
|
}
|
||||||
|
|
||||||
match full_name.as_slice() {
|
match full_name.as_slice() {
|
||||||
b"export alias" => parse_alias(working_set, &lite_command.parts, expand_aliases_denylist),
|
b"export alias" => parse_alias(working_set, lite_command, expand_aliases_denylist),
|
||||||
b"export def" | b"export def-env" => {
|
b"export def" | b"export def-env" => {
|
||||||
parse_def(working_set, lite_command, expand_aliases_denylist)
|
parse_def(working_set, lite_command, expand_aliases_denylist)
|
||||||
}
|
}
|
||||||
@ -1075,7 +1028,7 @@ pub fn parse_export_in_module(
|
|||||||
parts: spans[1..].to_vec(),
|
parts: spans[1..].to_vec(),
|
||||||
};
|
};
|
||||||
let (pipeline, err) =
|
let (pipeline, err) =
|
||||||
parse_alias(working_set, &lite_command.parts, expand_aliases_denylist);
|
parse_alias(working_set, &lite_command, expand_aliases_denylist);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
let export_alias_decl_id =
|
let export_alias_decl_id =
|
||||||
@ -1328,11 +1281,41 @@ pub fn parse_export_env(
|
|||||||
(pipeline, Some(block_id), None)
|
(pipeline, Some(block_id), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn collect_first_comments(tokens: &[Token]) -> Vec<Span> {
|
||||||
|
let mut comments = vec![];
|
||||||
|
|
||||||
|
let mut tokens_iter = tokens.iter().peekable();
|
||||||
|
while let Some(token) = tokens_iter.next() {
|
||||||
|
match token.contents {
|
||||||
|
TokenContents::Comment => {
|
||||||
|
comments.push(token.span);
|
||||||
|
}
|
||||||
|
TokenContents::Eol => {
|
||||||
|
if let Some(Token {
|
||||||
|
contents: TokenContents::Eol,
|
||||||
|
..
|
||||||
|
}) = tokens_iter.peek()
|
||||||
|
{
|
||||||
|
if !comments.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
comments.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
comments
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_module_block(
|
pub fn parse_module_block(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
span: Span,
|
span: Span,
|
||||||
expand_aliases_denylist: &[usize],
|
expand_aliases_denylist: &[usize],
|
||||||
) -> (Block, Module, Option<ParseError>) {
|
) -> (Block, Module, Vec<Span>, Option<ParseError>) {
|
||||||
let mut error = None;
|
let mut error = None;
|
||||||
|
|
||||||
working_set.enter_scope();
|
working_set.enter_scope();
|
||||||
@ -1342,6 +1325,8 @@ pub fn parse_module_block(
|
|||||||
let (output, err) = lex(source, span.start, &[], &[], false);
|
let (output, err) = lex(source, span.start, &[], &[], false);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
|
let module_comments = collect_first_comments(&output);
|
||||||
|
|
||||||
let (output, err) = lite_parse(&output);
|
let (output, err) = lite_parse(&output);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
@ -1378,11 +1363,8 @@ pub fn parse_module_block(
|
|||||||
(pipeline, err)
|
(pipeline, err)
|
||||||
}
|
}
|
||||||
b"alias" => {
|
b"alias" => {
|
||||||
let (pipeline, err) = parse_alias(
|
let (pipeline, err) =
|
||||||
working_set,
|
parse_alias(working_set, command, expand_aliases_denylist);
|
||||||
&command.parts,
|
|
||||||
expand_aliases_denylist,
|
|
||||||
);
|
|
||||||
|
|
||||||
(pipeline, err)
|
(pipeline, err)
|
||||||
}
|
}
|
||||||
@ -1452,17 +1434,20 @@ pub fn parse_module_block(
|
|||||||
|
|
||||||
working_set.exit_scope();
|
working_set.exit_scope();
|
||||||
|
|
||||||
(block, module, error)
|
(block, module, module_comments, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_module(
|
pub fn parse_module(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
spans: &[Span],
|
lite_command: &LiteCommand,
|
||||||
expand_aliases_denylist: &[usize],
|
expand_aliases_denylist: &[usize],
|
||||||
) -> (Pipeline, Option<ParseError>) {
|
) -> (Pipeline, Option<ParseError>) {
|
||||||
// TODO: Currently, module is closing over its parent scope (i.e., defs in the parent scope are
|
// TODO: Currently, module is closing over its parent scope (i.e., defs in the parent scope are
|
||||||
// visible and usable in this module's scope). We want to disable that for files.
|
// visible and usable in this module's scope). We want to disable that for files.
|
||||||
|
|
||||||
|
let spans = &lite_command.parts;
|
||||||
|
let mut module_comments = lite_command.comments.clone();
|
||||||
|
|
||||||
let mut error = None;
|
let mut error = None;
|
||||||
let bytes = working_set.get_span_contents(spans[0]);
|
let bytes = working_set.get_span_contents(spans[0]);
|
||||||
|
|
||||||
@ -1496,12 +1481,14 @@ pub fn parse_module(
|
|||||||
|
|
||||||
let block_span = Span::new(start, end);
|
let block_span = Span::new(start, end);
|
||||||
|
|
||||||
let (block, module, err) =
|
let (block, module, inner_comments, err) =
|
||||||
parse_module_block(working_set, block_span, expand_aliases_denylist);
|
parse_module_block(working_set, block_span, expand_aliases_denylist);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
let block_id = working_set.add_block(block);
|
let block_id = working_set.add_block(block);
|
||||||
let _ = working_set.add_module(&module_name, module);
|
|
||||||
|
module_comments.extend(inner_comments);
|
||||||
|
let _ = working_set.add_module(&module_name, module, module_comments);
|
||||||
|
|
||||||
let block_expr = Expression {
|
let block_expr = Expression {
|
||||||
expr: Expr::Block(block_id),
|
expr: Expr::Block(block_id),
|
||||||
@ -1734,7 +1721,7 @@ pub fn parse_use(
|
|||||||
working_set.parsed_module_files.push(module_path);
|
working_set.parsed_module_files.push(module_path);
|
||||||
|
|
||||||
// Parse the module
|
// Parse the module
|
||||||
let (block, module, err) = parse_module_block(
|
let (block, module, module_comments, err) = parse_module_block(
|
||||||
working_set,
|
working_set,
|
||||||
Span::new(span_start, span_end),
|
Span::new(span_start, span_end),
|
||||||
expand_aliases_denylist,
|
expand_aliases_denylist,
|
||||||
@ -1748,7 +1735,8 @@ pub fn parse_use(
|
|||||||
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
||||||
|
|
||||||
let _ = working_set.add_block(block);
|
let _ = working_set.add_block(block);
|
||||||
let module_id = working_set.add_module(&module_name, module.clone());
|
let module_id =
|
||||||
|
working_set.add_module(&module_name, module.clone(), module_comments);
|
||||||
|
|
||||||
(
|
(
|
||||||
ImportPattern {
|
ImportPattern {
|
||||||
@ -2320,7 +2308,7 @@ pub fn parse_overlay_new(
|
|||||||
custom_completion: None,
|
custom_completion: None,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
let module_id = working_set.add_module(&overlay_name, Module::new());
|
let module_id = working_set.add_module(&overlay_name, Module::new(), vec![]);
|
||||||
|
|
||||||
working_set.add_overlay(
|
working_set.add_overlay(
|
||||||
overlay_name.as_bytes().to_vec(),
|
overlay_name.as_bytes().to_vec(),
|
||||||
@ -2557,7 +2545,7 @@ pub fn parse_overlay_use(
|
|||||||
working_set.currently_parsed_cwd.clone()
|
working_set.currently_parsed_cwd.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (block, module, err) = parse_module_block(
|
let (block, module, module_comments, err) = parse_module_block(
|
||||||
working_set,
|
working_set,
|
||||||
Span::new(span_start, span_end),
|
Span::new(span_start, span_end),
|
||||||
expand_aliases_denylist,
|
expand_aliases_denylist,
|
||||||
@ -2568,7 +2556,8 @@ pub fn parse_overlay_use(
|
|||||||
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
||||||
|
|
||||||
let _ = working_set.add_block(block);
|
let _ = working_set.add_block(block);
|
||||||
let module_id = working_set.add_module(&overlay_name, module.clone());
|
let module_id =
|
||||||
|
working_set.add_module(&overlay_name, module.clone(), module_comments);
|
||||||
|
|
||||||
(
|
(
|
||||||
new_name.map(|spanned| spanned.item).unwrap_or(overlay_name),
|
new_name.map(|spanned| spanned.item).unwrap_or(overlay_name),
|
||||||
|
@ -5248,8 +5248,8 @@ pub fn parse_builtin_commands(
|
|||||||
let (expr, err) = parse_for(working_set, &lite_command.parts, expand_aliases_denylist);
|
let (expr, err) = parse_for(working_set, &lite_command.parts, expand_aliases_denylist);
|
||||||
(Pipeline::from_vec(vec![expr]), err)
|
(Pipeline::from_vec(vec![expr]), err)
|
||||||
}
|
}
|
||||||
b"alias" => parse_alias(working_set, &lite_command.parts, expand_aliases_denylist),
|
b"alias" => parse_alias(working_set, lite_command, expand_aliases_denylist),
|
||||||
b"module" => parse_module(working_set, &lite_command.parts, expand_aliases_denylist),
|
b"module" => parse_module(working_set, lite_command, expand_aliases_denylist),
|
||||||
b"use" => {
|
b"use" => {
|
||||||
let (pipeline, _, err) =
|
let (pipeline, _, err) =
|
||||||
parse_use(working_set, &lite_command.parts, expand_aliases_denylist);
|
parse_use(working_set, &lite_command.parts, expand_aliases_denylist);
|
||||||
|
@ -31,6 +31,51 @@ pub enum ReplOperation {
|
|||||||
Replace(String),
|
Replace(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Organizes usage messages for various primitives
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Usage {
|
||||||
|
// TODO: Move decl usages here
|
||||||
|
alias_comments: HashMap<AliasId, Vec<Span>>,
|
||||||
|
module_comments: HashMap<ModuleId, Vec<Span>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Usage {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Usage {
|
||||||
|
alias_comments: HashMap::new(),
|
||||||
|
module_comments: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_alias_comments(&mut self, alias_id: AliasId, comments: Vec<Span>) {
|
||||||
|
self.alias_comments.insert(alias_id, comments);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_module_comments(&mut self, module_id: ModuleId, comments: Vec<Span>) {
|
||||||
|
self.module_comments.insert(module_id, comments);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_alias_comments(&self, alias_id: AliasId) -> Option<&[Span]> {
|
||||||
|
self.alias_comments.get(&alias_id).map(|v| v.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_module_comments(&self, module_id: ModuleId) -> Option<&[Span]> {
|
||||||
|
self.module_comments.get(&module_id).map(|v| v.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Overwrite own values with the other
|
||||||
|
pub fn merge_with(&mut self, other: Usage) {
|
||||||
|
self.alias_comments.extend(other.alias_comments);
|
||||||
|
self.module_comments.extend(other.module_comments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Usage {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The core global engine state. This includes all global definitions as well as any global state that
|
/// The core global engine state. This includes all global definitions as well as any global state that
|
||||||
/// will persist for the whole session.
|
/// will persist for the whole session.
|
||||||
///
|
///
|
||||||
@ -82,6 +127,7 @@ pub struct EngineState {
|
|||||||
aliases: Vec<Vec<Span>>,
|
aliases: Vec<Vec<Span>>,
|
||||||
blocks: Vec<Block>,
|
blocks: Vec<Block>,
|
||||||
modules: Vec<Module>,
|
modules: Vec<Module>,
|
||||||
|
usage: Usage,
|
||||||
pub scope: ScopeFrame,
|
pub scope: ScopeFrame,
|
||||||
pub ctrlc: Option<Arc<AtomicBool>>,
|
pub ctrlc: Option<Arc<AtomicBool>>,
|
||||||
pub env_vars: EnvVars,
|
pub env_vars: EnvVars,
|
||||||
@ -125,6 +171,7 @@ impl EngineState {
|
|||||||
aliases: vec![],
|
aliases: vec![],
|
||||||
blocks: vec![],
|
blocks: vec![],
|
||||||
modules: vec![Module::new()],
|
modules: vec![Module::new()],
|
||||||
|
usage: Usage::new(),
|
||||||
// make sure we have some default overlay:
|
// make sure we have some default overlay:
|
||||||
scope: ScopeFrame::with_empty_overlay(
|
scope: ScopeFrame::with_empty_overlay(
|
||||||
DEFAULT_OVERLAY_NAME.as_bytes().to_vec(),
|
DEFAULT_OVERLAY_NAME.as_bytes().to_vec(),
|
||||||
@ -167,6 +214,7 @@ impl EngineState {
|
|||||||
self.vars.extend(delta.vars);
|
self.vars.extend(delta.vars);
|
||||||
self.blocks.extend(delta.blocks);
|
self.blocks.extend(delta.blocks);
|
||||||
self.modules.extend(delta.modules);
|
self.modules.extend(delta.modules);
|
||||||
|
self.usage.merge_with(delta.usage);
|
||||||
|
|
||||||
let first = delta.scope.remove(0);
|
let first = delta.scope.remove(0);
|
||||||
|
|
||||||
@ -553,6 +601,14 @@ impl EngineState {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_alias_comments(&self, alias_id: AliasId) -> Option<&[Span]> {
|
||||||
|
self.usage.get_alias_comments(alias_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_module_comments(&self, module_id: ModuleId) -> Option<&[Span]> {
|
||||||
|
self.usage.get_module_comments(module_id)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub fn plugin_decls(&self) -> impl Iterator<Item = &Box<dyn Command + 'static>> {
|
pub fn plugin_decls(&self) -> impl Iterator<Item = &Box<dyn Command + 'static>> {
|
||||||
let mut unique_plugin_decls = HashMap::new();
|
let mut unique_plugin_decls = HashMap::new();
|
||||||
@ -580,24 +636,39 @@ impl EngineState {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn which_module_has_decl(&self, name: &[u8]) -> Option<&[u8]> {
|
pub fn which_module_has_decl(
|
||||||
for (module_id, m) in self.modules.iter().enumerate() {
|
&self,
|
||||||
if m.has_decl(name) {
|
decl_name: &[u8],
|
||||||
for overlay_frame in self.active_overlays(&[]).iter() {
|
removed_overlays: &[Vec<u8>],
|
||||||
let module_name = overlay_frame.modules.iter().find_map(|(key, &val)| {
|
) -> Option<&[u8]> {
|
||||||
if val == module_id {
|
for overlay_frame in self.active_overlays(removed_overlays).iter().rev() {
|
||||||
Some(key)
|
for (module_name, module_id) in overlay_frame.modules.iter() {
|
||||||
} else {
|
let module = self.get_module(*module_id);
|
||||||
None
|
if module.has_decl(decl_name) {
|
||||||
}
|
return Some(module_name);
|
||||||
});
|
|
||||||
if let Some(final_name) = module_name {
|
|
||||||
return Some(&final_name[..]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|
||||||
|
// for (module_id, m) in self.modules.iter().enumerate() {
|
||||||
|
// if m.has_decl(name) {
|
||||||
|
// for overlay_frame in self.active_overlays(&[]).iter() {
|
||||||
|
// let module_name = overlay_frame.modules.iter().find_map(|(key, &val)| {
|
||||||
|
// if val == module_id {
|
||||||
|
// Some(key)
|
||||||
|
// } else {
|
||||||
|
// None
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// if let Some(final_name) = module_name {
|
||||||
|
// return Some(&final_name[..]);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_overlay(&self, name: &[u8]) -> Option<OverlayId> {
|
pub fn find_overlay(&self, name: &[u8]) -> Option<OverlayId> {
|
||||||
@ -687,8 +758,39 @@ impl EngineState {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all IDs of all commands within scope, sorted by the commads' names
|
/// Get all aliases within scope, sorted by the alias names
|
||||||
pub fn get_decl_ids_sorted(&self, include_hidden: bool) -> impl Iterator<Item = DeclId> {
|
pub fn get_aliases_sorted(
|
||||||
|
&self,
|
||||||
|
include_hidden: bool,
|
||||||
|
) -> impl Iterator<Item = (Vec<u8>, DeclId)> {
|
||||||
|
let mut aliases_map = HashMap::new();
|
||||||
|
|
||||||
|
for overlay_frame in self.active_overlays(&[]) {
|
||||||
|
let new_aliases = if include_hidden {
|
||||||
|
overlay_frame.aliases.clone()
|
||||||
|
} else {
|
||||||
|
overlay_frame
|
||||||
|
.aliases
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(_, id)| overlay_frame.visibility.is_alias_id_visible(id))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
aliases_map.extend(new_aliases);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut aliases: Vec<(Vec<u8>, DeclId)> = aliases_map.into_iter().collect();
|
||||||
|
|
||||||
|
aliases.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
|
aliases.into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all commands within scope, sorted by the commads' names
|
||||||
|
pub fn get_decls_sorted(
|
||||||
|
&self,
|
||||||
|
include_hidden: bool,
|
||||||
|
) -> impl Iterator<Item = (Vec<u8>, DeclId)> {
|
||||||
let mut decls_map = HashMap::new();
|
let mut decls_map = HashMap::new();
|
||||||
|
|
||||||
for overlay_frame in self.active_overlays(&[]) {
|
for overlay_frame in self.active_overlays(&[]) {
|
||||||
@ -710,16 +812,19 @@ impl EngineState {
|
|||||||
decls_map.into_iter().map(|(v, k)| (v.0, k)).collect();
|
decls_map.into_iter().map(|(v, k)| (v.0, k)).collect();
|
||||||
|
|
||||||
decls.sort_by(|a, b| a.0.cmp(&b.0));
|
decls.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
decls.into_iter().map(|(_, id)| id)
|
decls.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get signatures of all commands within scope.
|
/// Get signatures of all commands within scope.
|
||||||
pub fn get_signatures(&self, include_hidden: bool) -> Vec<Signature> {
|
pub fn get_signatures(&self, include_hidden: bool) -> Vec<Signature> {
|
||||||
self.get_decl_ids_sorted(include_hidden)
|
self.get_decls_sorted(include_hidden)
|
||||||
.map(|id| {
|
.map(|(name_bytes, id)| {
|
||||||
let decl = self.get_decl(id);
|
let decl = self.get_decl(id);
|
||||||
|
// the reason to create the name this way is because the command could be renamed
|
||||||
|
// during module imports but the signature still contains the old name
|
||||||
|
let name = String::from_utf8_lossy(&name_bytes).to_string();
|
||||||
|
|
||||||
(*decl).signature().update_from_command(decl.borrow())
|
(*decl).signature().update_from_command(name, decl.borrow())
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
@ -733,11 +838,14 @@ impl EngineState {
|
|||||||
&self,
|
&self,
|
||||||
include_hidden: bool,
|
include_hidden: bool,
|
||||||
) -> Vec<(Signature, Vec<Example>, bool, bool, bool)> {
|
) -> Vec<(Signature, Vec<Example>, bool, bool, bool)> {
|
||||||
self.get_decl_ids_sorted(include_hidden)
|
self.get_decls_sorted(include_hidden)
|
||||||
.map(|id| {
|
.map(|(name_bytes, id)| {
|
||||||
let decl = self.get_decl(id);
|
let decl = self.get_decl(id);
|
||||||
|
// the reason to create the name this way is because the command could be renamed
|
||||||
|
// during module imports but the signature still contains the old name
|
||||||
|
let name = String::from_utf8_lossy(&name_bytes).to_string();
|
||||||
|
|
||||||
let signature = (*decl).signature().update_from_command(decl.borrow());
|
let signature = (*decl).signature().update_from_command(name, decl.borrow());
|
||||||
|
|
||||||
(
|
(
|
||||||
signature,
|
signature,
|
||||||
@ -839,6 +947,24 @@ impl EngineState {
|
|||||||
pub fn get_config_path(&self, key: &str) -> Option<&PathBuf> {
|
pub fn get_config_path(&self, key: &str) -> Option<&PathBuf> {
|
||||||
self.config_path.get(key)
|
self.config_path.get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn build_usage(&self, spans: &[Span]) -> (String, String) {
|
||||||
|
let comment_lines: Vec<&[u8]> = spans
|
||||||
|
.iter()
|
||||||
|
.map(|span| self.get_span_contents(span))
|
||||||
|
.collect();
|
||||||
|
build_usage(&comment_lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_alias_usage(&self, alias_id: AliasId) -> Option<(String, String)> {
|
||||||
|
self.get_alias_comments(alias_id)
|
||||||
|
.map(|comment_spans| self.build_usage(comment_spans))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_module_usage(&self, module_id: ModuleId) -> Option<(String, String)> {
|
||||||
|
self.get_module_comments(module_id)
|
||||||
|
.map(|comment_spans| self.build_usage(comment_spans))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A temporary extension to the global state. This handles bridging between the global state and the
|
/// A temporary extension to the global state. This handles bridging between the global state and the
|
||||||
@ -915,6 +1041,7 @@ pub struct StateDelta {
|
|||||||
aliases: Vec<Vec<Span>>, // indexed by AliasId
|
aliases: Vec<Vec<Span>>, // indexed by AliasId
|
||||||
pub blocks: Vec<Block>, // indexed by BlockId
|
pub blocks: Vec<Block>, // indexed by BlockId
|
||||||
modules: Vec<Module>, // indexed by ModuleId
|
modules: Vec<Module>, // indexed by ModuleId
|
||||||
|
usage: Usage,
|
||||||
pub scope: Vec<ScopeFrame>,
|
pub scope: Vec<ScopeFrame>,
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
plugins_changed: bool, // marks whether plugin file should be updated
|
plugins_changed: bool, // marks whether plugin file should be updated
|
||||||
@ -938,6 +1065,7 @@ impl StateDelta {
|
|||||||
blocks: vec![],
|
blocks: vec![],
|
||||||
modules: vec![],
|
modules: vec![],
|
||||||
scope: vec![scope_frame],
|
scope: vec![scope_frame],
|
||||||
|
usage: Usage::new(),
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
plugins_changed: false,
|
plugins_changed: false,
|
||||||
}
|
}
|
||||||
@ -1305,12 +1433,16 @@ impl<'a> StateWorkingSet<'a> {
|
|||||||
self.num_blocks() - 1
|
self.num_blocks() - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_module(&mut self, name: &str, module: Module) -> ModuleId {
|
pub fn add_module(&mut self, name: &str, module: Module, comments: Vec<Span>) -> ModuleId {
|
||||||
let name = name.as_bytes().to_vec();
|
let name = name.as_bytes().to_vec();
|
||||||
|
|
||||||
self.delta.modules.push(module);
|
self.delta.modules.push(module);
|
||||||
let module_id = self.num_modules() - 1;
|
let module_id = self.num_modules() - 1;
|
||||||
|
|
||||||
|
if !comments.is_empty() {
|
||||||
|
self.delta.usage.add_module_comments(module_id, comments);
|
||||||
|
}
|
||||||
|
|
||||||
self.last_overlay_mut().modules.insert(name, module_id);
|
self.last_overlay_mut().modules.insert(name, module_id);
|
||||||
|
|
||||||
module_id
|
module_id
|
||||||
@ -1633,10 +1765,14 @@ impl<'a> StateWorkingSet<'a> {
|
|||||||
next_id
|
next_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_alias(&mut self, name: Vec<u8>, replacement: Vec<Span>) {
|
pub fn add_alias(&mut self, name: Vec<u8>, replacement: Vec<Span>, comments: Vec<Span>) {
|
||||||
self.delta.aliases.push(replacement);
|
self.delta.aliases.push(replacement);
|
||||||
let alias_id = self.num_aliases() - 1;
|
let alias_id = self.num_aliases() - 1;
|
||||||
|
|
||||||
|
if !comments.is_empty() {
|
||||||
|
self.delta.usage.add_alias_comments(alias_id, comments);
|
||||||
|
}
|
||||||
|
|
||||||
let last = self.last_overlay_mut();
|
let last = self.last_overlay_mut();
|
||||||
|
|
||||||
last.aliases.insert(name, alias_id);
|
last.aliases.insert(name, alias_id);
|
||||||
@ -2051,17 +2187,13 @@ impl<'a> StateWorkingSet<'a> {
|
|||||||
pub fn render(self) -> StateDelta {
|
pub fn render(self) -> StateDelta {
|
||||||
self.delta
|
self.delta
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Visibility {
|
pub fn build_usage(&self, spans: &[Span]) -> (String, String) {
|
||||||
fn default() -> Self {
|
let comment_lines: Vec<&[u8]> = spans
|
||||||
Self::new()
|
.iter()
|
||||||
}
|
.map(|span| self.get_span_contents(*span))
|
||||||
}
|
.collect();
|
||||||
|
build_usage(&comment_lines)
|
||||||
impl Default for ScopeFrame {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2148,6 +2280,59 @@ impl<'a> miette::SourceCode for &StateWorkingSet<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_usage(comment_lines: &[&[u8]]) -> (String, String) {
|
||||||
|
let mut usage = String::new();
|
||||||
|
|
||||||
|
let mut num_spaces = 0;
|
||||||
|
let mut first = true;
|
||||||
|
|
||||||
|
// Use the comments to build the usage
|
||||||
|
for contents in comment_lines {
|
||||||
|
let comment_line = if first {
|
||||||
|
// Count the number of spaces still at the front, skipping the '#'
|
||||||
|
let mut pos = 1;
|
||||||
|
while pos < contents.len() {
|
||||||
|
if let Some(b' ') = contents.get(pos) {
|
||||||
|
// continue
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
num_spaces = pos;
|
||||||
|
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
String::from_utf8_lossy(&contents[pos..]).to_string()
|
||||||
|
} else {
|
||||||
|
let mut pos = 1;
|
||||||
|
|
||||||
|
while pos < contents.len() && pos < num_spaces {
|
||||||
|
if let Some(b' ') = contents.get(pos) {
|
||||||
|
// continue
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::from_utf8_lossy(&contents[pos..]).to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
if !usage.is_empty() {
|
||||||
|
usage.push('\n');
|
||||||
|
}
|
||||||
|
usage.push_str(&comment_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((brief_usage, extra_usage)) = usage.split_once("\n\n") {
|
||||||
|
(brief_usage.to_string(), extra_usage.to_string())
|
||||||
|
} else {
|
||||||
|
(usage, String::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod engine_state_tests {
|
mod engine_state_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -44,14 +44,14 @@ impl Visibility {
|
|||||||
self.alias_ids.insert(*alias_id, true);
|
self.alias_ids.insert(*alias_id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Overwrite own values with the other
|
||||||
pub fn merge_with(&mut self, other: Visibility) {
|
pub fn merge_with(&mut self, other: Visibility) {
|
||||||
// overwrite own values with the other
|
|
||||||
self.decl_ids.extend(other.decl_ids);
|
self.decl_ids.extend(other.decl_ids);
|
||||||
self.alias_ids.extend(other.alias_ids);
|
self.alias_ids.extend(other.alias_ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take new values from the other but keep own values
|
||||||
pub fn append(&mut self, other: &Visibility) {
|
pub fn append(&mut self, other: &Visibility) {
|
||||||
// take new values from the other but keep own values
|
|
||||||
for (decl_id, visible) in other.decl_ids.iter() {
|
for (decl_id, visible) in other.decl_ids.iter() {
|
||||||
if !self.decl_ids.contains_key(decl_id) {
|
if !self.decl_ids.contains_key(decl_id) {
|
||||||
self.decl_ids.insert(*decl_id, *visible);
|
self.decl_ids.insert(*decl_id, *visible);
|
||||||
@ -79,10 +79,6 @@ pub struct ScopeFrame {
|
|||||||
/// Order is significant: The last item points at the last activated overlay.
|
/// Order is significant: The last item points at the last activated overlay.
|
||||||
pub active_overlays: Vec<OverlayId>,
|
pub active_overlays: Vec<OverlayId>,
|
||||||
|
|
||||||
/// Deactivated overlays from permanent state.
|
|
||||||
/// ! Stores OverlayIds from the permanent state, not from this frame. !
|
|
||||||
// removed_overlays: Vec<OverlayId>,
|
|
||||||
|
|
||||||
/// Removed overlays from previous scope frames / permanent state
|
/// Removed overlays from previous scope frames / permanent state
|
||||||
pub removed_overlays: Vec<Vec<u8>>,
|
pub removed_overlays: Vec<Vec<u8>>,
|
||||||
|
|
||||||
@ -281,3 +277,15 @@ impl<'a> Borrow<dyn DeclKey + 'a> for (Vec<u8>, Type) {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Visibility {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ScopeFrame {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -542,6 +542,15 @@ Either make sure {0} is a string, or add a 'to_string' entry for it in ENV_CONVE
|
|||||||
#[diagnostic(code(nu::shell::command_not_found), url(docsrs))]
|
#[diagnostic(code(nu::shell::command_not_found), url(docsrs))]
|
||||||
CommandNotFound(#[label("command not found")] Span),
|
CommandNotFound(#[label("command not found")] Span),
|
||||||
|
|
||||||
|
/// This alias could not be found
|
||||||
|
///
|
||||||
|
/// ## Resolution
|
||||||
|
///
|
||||||
|
/// The alias does not exist in the current scope. It might exist in another scope or overlay or be hidden.
|
||||||
|
#[error("Alias not found")]
|
||||||
|
#[diagnostic(code(nu::shell::alias_not_found), url(docsrs))]
|
||||||
|
AliasNotFound(#[label("alias not found")] Span),
|
||||||
|
|
||||||
/// A flag was not found.
|
/// A flag was not found.
|
||||||
#[error("Flag not found")]
|
#[error("Flag not found")]
|
||||||
#[diagnostic(code(nu::shell::flag_not_found), url(docsrs))]
|
#[diagnostic(code(nu::shell::flag_not_found), url(docsrs))]
|
||||||
@ -868,9 +877,6 @@ Either make sure {0} is a string, or add a 'to_string' entry for it in ENV_CONVE
|
|||||||
#[diagnostic(code(nu::shell::non_unicode_input), url(docsrs))]
|
#[diagnostic(code(nu::shell::non_unicode_input), url(docsrs))]
|
||||||
NonUnicodeInput,
|
NonUnicodeInput,
|
||||||
|
|
||||||
// /// Path not found.
|
|
||||||
// #[error("Path not found.")]
|
|
||||||
// PathNotFound,
|
|
||||||
/// Unexpected abbr component.
|
/// Unexpected abbr component.
|
||||||
///
|
///
|
||||||
/// ## Resolution
|
/// ## Resolution
|
||||||
|
@ -265,7 +265,8 @@ impl Signature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Update signature's fields from a Command trait implementation
|
/// Update signature's fields from a Command trait implementation
|
||||||
pub fn update_from_command(mut self, command: &dyn Command) -> Signature {
|
pub fn update_from_command(mut self, name: String, command: &dyn Command) -> Signature {
|
||||||
|
self.name = name;
|
||||||
self.search_terms = command
|
self.search_terms = command
|
||||||
.search_terms()
|
.search_terms()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -669,6 +670,10 @@ impl Command for Predeclaration {
|
|||||||
&self.signature.usage
|
&self.signature.usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
&self.signature.extra_usage
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
_engine_state: &EngineState,
|
_engine_state: &EngineState,
|
||||||
@ -718,6 +723,10 @@ impl Command for BlockCommand {
|
|||||||
&self.signature.usage
|
&self.signature.usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
&self.signature.extra_usage
|
||||||
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
_engine_state: &EngineState,
|
_engine_state: &EngineState,
|
||||||
|
@ -130,7 +130,7 @@ fn help_present_in_def() -> TestResult {
|
|||||||
#[test]
|
#[test]
|
||||||
fn help_not_present_in_extern() -> TestResult {
|
fn help_not_present_in_extern() -> TestResult {
|
||||||
run_test(
|
run_test(
|
||||||
"module test {export extern \"git fetch\" []}; use test; help git fetch | ansi strip",
|
"module test {export extern \"git fetch\" []}; use test `git fetch`; help git fetch | ansi strip",
|
||||||
"Usage:\n > git fetch",
|
"Usage:\n > git fetch",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -292,7 +292,7 @@ fn module_nested_imports_in_dirs_prefixed() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn module_import_env_1() {
|
fn module_import_env_1() {
|
||||||
Playground::setup("module_imprt_env_1", |dirs, sandbox| {
|
Playground::setup("module_import_env_1", |dirs, sandbox| {
|
||||||
sandbox
|
sandbox
|
||||||
.with_files(vec![FileWithContentToBeTrimmed(
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
"main.nu",
|
"main.nu",
|
||||||
|
@ -795,8 +795,8 @@ fn overlay_remove_renamed_overlay() {
|
|||||||
let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; ")));
|
let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; ")));
|
||||||
let actual_repl = nu!(cwd: "tests/overlays", nu_repl_code(inp));
|
let actual_repl = nu!(cwd: "tests/overlays", nu_repl_code(inp));
|
||||||
|
|
||||||
assert!(actual.err.contains("did you mean 'for'?"));
|
assert!(actual.err.contains("external_command"));
|
||||||
assert!(actual_repl.err.contains("did you mean 'for'?"));
|
assert!(actual_repl.err.contains("external_command"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
Loading…
Reference in New Issue
Block a user