mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 04:15:08 +02:00
Expand Nushell's help system (#7611)
This commit is contained in:
@ -31,6 +31,51 @@ pub enum ReplOperation {
|
||||
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
|
||||
/// will persist for the whole session.
|
||||
///
|
||||
@ -82,6 +127,7 @@ pub struct EngineState {
|
||||
aliases: Vec<Vec<Span>>,
|
||||
blocks: Vec<Block>,
|
||||
modules: Vec<Module>,
|
||||
usage: Usage,
|
||||
pub scope: ScopeFrame,
|
||||
pub ctrlc: Option<Arc<AtomicBool>>,
|
||||
pub env_vars: EnvVars,
|
||||
@ -125,6 +171,7 @@ impl EngineState {
|
||||
aliases: vec![],
|
||||
blocks: vec![],
|
||||
modules: vec![Module::new()],
|
||||
usage: Usage::new(),
|
||||
// make sure we have some default overlay:
|
||||
scope: ScopeFrame::with_empty_overlay(
|
||||
DEFAULT_OVERLAY_NAME.as_bytes().to_vec(),
|
||||
@ -167,6 +214,7 @@ impl EngineState {
|
||||
self.vars.extend(delta.vars);
|
||||
self.blocks.extend(delta.blocks);
|
||||
self.modules.extend(delta.modules);
|
||||
self.usage.merge_with(delta.usage);
|
||||
|
||||
let first = delta.scope.remove(0);
|
||||
|
||||
@ -553,6 +601,14 @@ impl EngineState {
|
||||
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")]
|
||||
pub fn plugin_decls(&self) -> impl Iterator<Item = &Box<dyn Command + 'static>> {
|
||||
let mut unique_plugin_decls = HashMap::new();
|
||||
@ -580,24 +636,39 @@ impl EngineState {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn which_module_has_decl(&self, name: &[u8]) -> Option<&[u8]> {
|
||||
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[..]);
|
||||
}
|
||||
pub fn which_module_has_decl(
|
||||
&self,
|
||||
decl_name: &[u8],
|
||||
removed_overlays: &[Vec<u8>],
|
||||
) -> Option<&[u8]> {
|
||||
for overlay_frame in self.active_overlays(removed_overlays).iter().rev() {
|
||||
for (module_name, module_id) in overlay_frame.modules.iter() {
|
||||
let module = self.get_module(*module_id);
|
||||
if module.has_decl(decl_name) {
|
||||
return Some(module_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
@ -687,8 +758,39 @@ impl EngineState {
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
/// Get all IDs of all commands within scope, sorted by the commads' names
|
||||
pub fn get_decl_ids_sorted(&self, include_hidden: bool) -> impl Iterator<Item = DeclId> {
|
||||
/// Get all aliases within scope, sorted by the alias names
|
||||
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();
|
||||
|
||||
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.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.
|
||||
pub fn get_signatures(&self, include_hidden: bool) -> Vec<Signature> {
|
||||
self.get_decl_ids_sorted(include_hidden)
|
||||
.map(|id| {
|
||||
self.get_decls_sorted(include_hidden)
|
||||
.map(|(name_bytes, 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()
|
||||
}
|
||||
@ -733,11 +838,14 @@ impl EngineState {
|
||||
&self,
|
||||
include_hidden: bool,
|
||||
) -> Vec<(Signature, Vec<Example>, bool, bool, bool)> {
|
||||
self.get_decl_ids_sorted(include_hidden)
|
||||
.map(|id| {
|
||||
self.get_decls_sorted(include_hidden)
|
||||
.map(|(name_bytes, 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,
|
||||
@ -839,6 +947,24 @@ impl EngineState {
|
||||
pub fn get_config_path(&self, key: &str) -> Option<&PathBuf> {
|
||||
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
|
||||
@ -915,6 +1041,7 @@ pub struct StateDelta {
|
||||
aliases: Vec<Vec<Span>>, // indexed by AliasId
|
||||
pub blocks: Vec<Block>, // indexed by BlockId
|
||||
modules: Vec<Module>, // indexed by ModuleId
|
||||
usage: Usage,
|
||||
pub scope: Vec<ScopeFrame>,
|
||||
#[cfg(feature = "plugin")]
|
||||
plugins_changed: bool, // marks whether plugin file should be updated
|
||||
@ -938,6 +1065,7 @@ impl StateDelta {
|
||||
blocks: vec![],
|
||||
modules: vec![],
|
||||
scope: vec![scope_frame],
|
||||
usage: Usage::new(),
|
||||
#[cfg(feature = "plugin")]
|
||||
plugins_changed: false,
|
||||
}
|
||||
@ -1305,12 +1433,16 @@ impl<'a> StateWorkingSet<'a> {
|
||||
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();
|
||||
|
||||
self.delta.modules.push(module);
|
||||
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);
|
||||
|
||||
module_id
|
||||
@ -1633,10 +1765,14 @@ impl<'a> StateWorkingSet<'a> {
|
||||
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);
|
||||
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();
|
||||
|
||||
last.aliases.insert(name, alias_id);
|
||||
@ -2051,17 +2187,13 @@ impl<'a> StateWorkingSet<'a> {
|
||||
pub fn render(self) -> StateDelta {
|
||||
self.delta
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Visibility {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ScopeFrame {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)]
|
||||
mod engine_state_tests {
|
||||
use super::*;
|
||||
|
@ -44,14 +44,14 @@ impl Visibility {
|
||||
self.alias_ids.insert(*alias_id, true);
|
||||
}
|
||||
|
||||
/// Overwrite own values with the other
|
||||
pub fn merge_with(&mut self, other: Visibility) {
|
||||
// overwrite own values with the other
|
||||
self.decl_ids.extend(other.decl_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) {
|
||||
// take new values from the other but keep own values
|
||||
for (decl_id, visible) in other.decl_ids.iter() {
|
||||
if !self.decl_ids.contains_key(decl_id) {
|
||||
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.
|
||||
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
|
||||
pub removed_overlays: Vec<Vec<u8>>,
|
||||
|
||||
@ -281,3 +277,15 @@ impl<'a> Borrow<dyn DeclKey + 'a> for (Vec<u8>, Type) {
|
||||
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))]
|
||||
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.
|
||||
#[error("Flag not found")]
|
||||
#[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))]
|
||||
NonUnicodeInput,
|
||||
|
||||
// /// Path not found.
|
||||
// #[error("Path not found.")]
|
||||
// PathNotFound,
|
||||
/// Unexpected abbr component.
|
||||
///
|
||||
/// ## Resolution
|
||||
|
@ -265,7 +265,8 @@ impl Signature {
|
||||
}
|
||||
|
||||
/// 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
|
||||
.search_terms()
|
||||
.into_iter()
|
||||
@ -669,6 +670,10 @@ impl Command for Predeclaration {
|
||||
&self.signature.usage
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
&self.signature.extra_usage
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
@ -718,6 +723,10 @@ impl Command for BlockCommand {
|
||||
&self.signature.usage
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
&self.signature.extra_usage
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
|
Reference in New Issue
Block a user