1use crate::help::highlight_search_in_table;
2use nu_color_config::StyleComputer;
3use nu_engine::{command_prelude::*, get_full_help};
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 let style_computer = StyleComputer::from_config(engine_state, stack);
57 let string_style = style_computer.compute("string", &Value::string("search result", head));
61 let highlight_style =
62 style_computer.compute("search_result", &Value::string("search result", head));
63
64 if let Some(f) = find {
65 let all_cmds_vec = build_help_commands(engine_state, head);
66 let found_cmds_vec = highlight_search_in_table(
67 all_cmds_vec,
68 &f.item,
69 &["name", "description", "search_terms"],
70 &string_style,
71 &highlight_style,
72 )?;
73
74 return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
75 }
76
77 if rest.is_empty() {
78 let found_cmds_vec = build_help_commands(engine_state, head);
79 Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
80 } else {
81 let mut name = String::new();
82
83 for r in &rest {
84 if !name.is_empty() {
85 name.push(' ');
86 }
87 name.push_str(&r.item);
88 }
89
90 if let Some(decl) = engine_state.find_decl(name.as_bytes(), &[]) {
91 let cmd = engine_state.get_decl(decl);
92 let help_text = get_full_help(cmd, engine_state, stack);
93 Ok(Value::string(help_text, call.head).into_pipeline_data())
94 } else {
95 Err(ShellError::CommandNotFound {
96 span: Span::merge_many(rest.iter().map(|s| s.span)),
97 })
98 }
99 }
100}
101
102fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
103 let commands = engine_state.get_decls_sorted(false);
104 let mut found_cmds_vec = Vec::new();
105
106 for (_, decl_id) in commands {
107 let decl = engine_state.get_decl(decl_id);
108 let sig = decl.signature().update_from_command(decl);
109
110 let key = sig.name;
111 let description = sig.description;
112 let search_terms = sig.search_terms;
113
114 let command_type = decl.command_type().to_string();
115
116 let param_table = {
118 let mut vals = vec![];
119
120 for required_param in &sig.required_positional {
121 vals.push(Value::record(
122 record! {
123 "name" => Value::string(&required_param.name, span),
124 "type" => Value::string(required_param.shape.to_string(), span),
125 "required" => Value::bool(true, span),
126 "description" => Value::string(&required_param.desc, span),
127 },
128 span,
129 ));
130 }
131
132 for optional_param in &sig.optional_positional {
133 vals.push(Value::record(
134 record! {
135 "name" => Value::string(&optional_param.name, span),
136 "type" => Value::string(optional_param.shape.to_string(), span),
137 "required" => Value::bool(false, span),
138 "description" => Value::string(&optional_param.desc, span),
139 },
140 span,
141 ));
142 }
143
144 if let Some(rest_positional) = &sig.rest_positional {
145 vals.push(Value::record(
146 record! {
147 "name" => Value::string(format!("...{}", rest_positional.name), span),
148 "type" => Value::string(rest_positional.shape.to_string(), span),
149 "required" => Value::bool(false, span),
150 "description" => Value::string(&rest_positional.desc, span),
151 },
152 span,
153 ));
154 }
155
156 for named_param in &sig.named {
157 let name = if let Some(short) = named_param.short {
158 if named_param.long.is_empty() {
159 format!("-{}", short)
160 } else {
161 format!("--{}(-{})", named_param.long, short)
162 }
163 } else {
164 format!("--{}", named_param.long)
165 };
166
167 let typ = if let Some(arg) = &named_param.arg {
168 arg.to_string()
169 } else {
170 "switch".to_string()
171 };
172
173 vals.push(Value::record(
174 record! {
175 "name" => Value::string(name, span),
176 "type" => Value::string(typ, span),
177 "required" => Value::bool(named_param.required, span),
178 "description" => Value::string(&named_param.desc, span),
179 },
180 span,
181 ));
182 }
183
184 Value::list(vals, span)
185 };
186
187 let input_output_table = {
189 let mut vals = vec![];
190
191 for (input_type, output_type) in sig.input_output_types {
192 vals.push(Value::record(
193 record! {
194 "input" => Value::string(input_type.to_string(), span),
195 "output" => Value::string(output_type.to_string(), span),
196 },
197 span,
198 ));
199 }
200
201 Value::list(vals, span)
202 };
203
204 let record = record! {
205 "name" => Value::string(key, span),
206 "category" => Value::string(sig.category.to_string(), span),
207 "command_type" => Value::string(command_type, span),
208 "description" => Value::string(description, span),
209 "params" => param_table,
210 "input_output" => input_output_table,
211 "search_terms" => Value::string(search_terms.join(", "), span),
212 "is_const" => Value::bool(decl.is_const(), span),
213 };
214
215 found_cmds_vec.push(Value::record(record, span));
216 }
217
218 found_cmds_vec
219}
220
221#[cfg(test)]
222mod test {
223 #[test]
224 fn test_examples() {
225 use super::HelpCommands;
226 use crate::test_examples;
227 test_examples(HelpCommands {})
228 }
229}