1use crate::filters::find_internal;
2use nu_engine::{command_prelude::*, get_full_help};
3use nu_protocol::DeclId;
4
5#[derive(Clone)]
6pub struct HelpCommands;
7
8impl Command for HelpCommands {
9 fn name(&self) -> &str {
10 "help commands"
11 }
12
13 fn description(&self) -> &str {
14 "Show help on nushell commands."
15 }
16
17 fn signature(&self) -> Signature {
18 Signature::build("help commands")
19 .category(Category::Core)
20 .rest(
21 "rest",
22 SyntaxShape::String,
23 "The name of command to get help on.",
24 )
25 .named(
26 "find",
27 SyntaxShape::String,
28 "String to find in command names, descriptions, and search terms.",
29 Some('f'),
30 )
31 .input_output_types(vec![(Type::Nothing, Type::table())])
32 .allow_variants_without_examples(true)
33 }
34
35 fn run(
36 &self,
37 engine_state: &EngineState,
38 stack: &mut Stack,
39 call: &Call,
40 _input: PipelineData,
41 ) -> Result<PipelineData, ShellError> {
42 help_commands(engine_state, stack, call)
43 }
44}
45
46pub fn help_commands(
47 engine_state: &EngineState,
48 stack: &mut Stack,
49 call: &Call,
50) -> Result<PipelineData, ShellError> {
51 let head = call.head;
52 let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
53 let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
54
55 if let Some(f) = find {
56 let all_cmds_vec = build_help_commands(engine_state, head);
57 return find_internal(
58 all_cmds_vec,
59 engine_state,
60 stack,
61 &f.item,
62 &["name", "description", "search_terms"],
63 true,
64 head,
65 );
66 }
67
68 if rest.is_empty() {
69 Ok(build_help_commands(engine_state, head))
70 } else {
71 let mut name = String::new();
72
73 for r in &rest {
74 if !name.is_empty() {
75 name.push(' ');
76 }
77 name.push_str(&r.item);
78 }
79
80 let decl_id = find_decl_with_alias_resolution(engine_state, name.as_bytes());
82
83 if let Some(decl) = decl_id {
84 let cmd = engine_state.get_decl(decl);
85
86 let sig = engine_state.get_signature(cmd).update_from_command(cmd);
90
91 let mut help_text = get_full_help(cmd, engine_state, stack, head);
92
93 if name != sig.name {
97 let search = format!("> {}", sig.name);
98 let replace = format!("> {}", name);
99 help_text = help_text.replacen(&search, &replace, 1);
100 }
101
102 Ok(Value::string(help_text, call.head).into_pipeline_data())
103 } else {
104 Err(ShellError::CommandNotFound {
105 span: Span::merge_many(rest.iter().map(|s| s.span)),
106 })
107 }
108 }
109}
110
111fn find_decl_with_alias_resolution(engine_state: &EngineState, name: &[u8]) -> Option<DeclId> {
112 let name_str = String::from_utf8_lossy(name);
113 let parts: Vec<&str> = name_str.split_whitespace().collect();
114
115 if parts.is_empty() {
116 return None;
117 }
118
119 if let Some(decl_id) = engine_state.find_decl(name, &[]) {
120 return Some(decl_id);
121 }
122
123 if let Some(first_decl_id) = engine_state.find_decl(parts[0].as_bytes(), &[]) {
124 let first_decl = engine_state.get_decl(first_decl_id);
125
126 if let Some(alias) = first_decl.as_alias()
128 && let nu_protocol::ast::Expression {
129 expr: nu_protocol::ast::Expr::Call(call),
130 ..
131 } = &alias.wrapped_call
132 {
133 let aliased_decl = engine_state.get_decl(call.decl_id);
134 let aliased_name = aliased_decl.name();
135
136 if parts.len() > 1 {
138 let full_name = format!("{} {}", aliased_name, parts[1..].join(" "));
139 return find_decl_with_alias_resolution(engine_state, full_name.as_bytes());
140 } else {
141 return Some(call.decl_id);
143 }
144 }
145 }
146
147 None
148}
149
150fn build_help_commands(engine_state: &EngineState, span: Span) -> PipelineData {
151 let commands = engine_state.get_decls_sorted(false);
152 let mut found_cmds_vec = Vec::new();
153
154 for (decl_name_bytes, decl_id) in commands {
155 let decl = engine_state.get_decl(decl_id);
156 let sig = decl.signature().update_from_command(decl);
157
158 let key = String::from_utf8_lossy(&decl_name_bytes).to_string();
161 let description = sig.description;
162 let search_terms = sig.search_terms;
163
164 let command_type = decl.command_type().to_string();
165
166 let param_table = {
168 let mut vals = vec![];
169
170 for required_param in &sig.required_positional {
171 vals.push(Value::record(
172 record! {
173 "name" => Value::string(&required_param.name, span),
174 "type" => Value::string(required_param.shape.to_string(), span),
175 "required" => Value::bool(true, span),
176 "description" => Value::string(&required_param.desc, span),
177 },
178 span,
179 ));
180 }
181
182 for optional_param in &sig.optional_positional {
183 vals.push(Value::record(
184 record! {
185 "name" => Value::string(&optional_param.name, span),
186 "type" => Value::string(optional_param.shape.to_string(), span),
187 "required" => Value::bool(false, span),
188 "description" => Value::string(&optional_param.desc, span),
189 },
190 span,
191 ));
192 }
193
194 if let Some(rest_positional) = &sig.rest_positional {
195 vals.push(Value::record(
196 record! {
197 "name" => Value::string(format!("...{}", rest_positional.name), span),
198 "type" => Value::string(rest_positional.shape.to_string(), span),
199 "required" => Value::bool(false, span),
200 "description" => Value::string(&rest_positional.desc, span),
201 },
202 span,
203 ));
204 }
205
206 for named_param in &sig.named {
207 let name = if let Some(short) = named_param.short {
208 if named_param.long.is_empty() {
209 format!("-{short}")
210 } else {
211 format!("--{}(-{})", named_param.long, short)
212 }
213 } else {
214 format!("--{}", named_param.long)
215 };
216
217 let typ = if let Some(arg) = &named_param.arg {
218 arg.to_string()
219 } else {
220 "switch".to_string()
221 };
222
223 vals.push(Value::record(
224 record! {
225 "name" => Value::string(name, span),
226 "type" => Value::string(typ, span),
227 "required" => Value::bool(named_param.required, span),
228 "description" => Value::string(&named_param.desc, span),
229 },
230 span,
231 ));
232 }
233
234 Value::list(vals, span)
235 };
236
237 let input_output_table = {
239 let mut vals = vec![];
240
241 for (input_type, output_type) in sig.input_output_types {
242 vals.push(Value::record(
243 record! {
244 "input" => Value::string(input_type.to_string(), span),
245 "output" => Value::string(output_type.to_string(), span),
246 },
247 span,
248 ));
249 }
250
251 Value::list(vals, span)
252 };
253
254 let record = record! {
255 "name" => Value::string(key, span),
256 "category" => Value::string(sig.category.to_string(), span),
257 "command_type" => Value::string(command_type, span),
258 "description" => Value::string(description, span),
259 "params" => param_table,
260 "input_output" => input_output_table,
261 "search_terms" => Value::string(search_terms.join(", "), span),
262 "is_const" => Value::bool(decl.is_const(), span),
263 };
264
265 found_cmds_vec.push(Value::record(record, span));
266 }
267
268 Value::list(found_cmds_vec, span).into_pipeline_data()
269}
270
271#[cfg(test)]
272mod test {
273 #[test]
274 fn test_examples() -> nu_test_support::Result {
275 use super::HelpCommands;
276 nu_test_support::test().examples(HelpCommands)
277 }
278}