nu_engine/
scope.rs

1use nu_protocol::{
2    ast::Expr,
3    engine::{Command, EngineState, Stack, Visibility},
4    record, DeclId, ModuleId, Signature, Span, SyntaxShape, Type, Value, VarId,
5};
6use std::{cmp::Ordering, collections::HashMap};
7
8pub struct ScopeData<'e, 's> {
9    engine_state: &'e EngineState,
10    stack: &'s Stack,
11    vars_map: HashMap<&'e Vec<u8>, &'e VarId>,
12    decls_map: HashMap<&'e Vec<u8>, &'e DeclId>,
13    modules_map: HashMap<&'e Vec<u8>, &'e ModuleId>,
14    visibility: Visibility,
15}
16
17impl<'e, 's> ScopeData<'e, 's> {
18    pub fn new(engine_state: &'e EngineState, stack: &'s Stack) -> Self {
19        Self {
20            engine_state,
21            stack,
22            vars_map: HashMap::new(),
23            decls_map: HashMap::new(),
24            modules_map: HashMap::new(),
25            visibility: Visibility::new(),
26        }
27    }
28
29    pub fn populate_vars(&mut self) {
30        for overlay_frame in self.engine_state.active_overlays(&[]) {
31            self.vars_map.extend(&overlay_frame.vars);
32        }
33    }
34
35    // decls include all commands, i.e., normal commands, aliases, and externals
36    pub fn populate_decls(&mut self) {
37        for overlay_frame in self.engine_state.active_overlays(&[]) {
38            self.decls_map.extend(&overlay_frame.decls);
39            self.visibility.merge_with(overlay_frame.visibility.clone());
40        }
41    }
42
43    pub fn populate_modules(&mut self) {
44        for overlay_frame in self.engine_state.active_overlays(&[]) {
45            self.modules_map.extend(&overlay_frame.modules);
46        }
47    }
48
49    pub fn collect_vars(&self, span: Span) -> Vec<Value> {
50        let mut vars = vec![];
51
52        for (var_name, var_id) in &self.vars_map {
53            let var_name = Value::string(String::from_utf8_lossy(var_name).to_string(), span);
54
55            let var = self.engine_state.get_var(**var_id);
56            let var_type = Value::string(var.ty.to_string(), span);
57            let is_const = Value::bool(var.const_val.is_some(), span);
58
59            let var_value = self
60                .stack
61                .get_var(**var_id, span)
62                .ok()
63                .or(var.const_val.clone())
64                .unwrap_or(Value::nothing(span));
65
66            let var_id_val = Value::int(var_id.get() as i64, span);
67
68            vars.push(Value::record(
69                record! {
70                    "name" => var_name,
71                    "type" => var_type,
72                    "value" => var_value,
73                    "is_const" => is_const,
74                    "var_id" => var_id_val,
75                },
76                span,
77            ));
78        }
79
80        sort_rows(&mut vars);
81        vars
82    }
83
84    pub fn collect_commands(&self, span: Span) -> Vec<Value> {
85        let mut commands = vec![];
86
87        for (command_name, decl_id) in &self.decls_map {
88            if self.visibility.is_decl_id_visible(decl_id)
89                && !self.engine_state.get_decl(**decl_id).is_alias()
90            {
91                let decl = self.engine_state.get_decl(**decl_id);
92                let signature = decl.signature();
93
94                let examples = decl
95                    .examples()
96                    .into_iter()
97                    .map(|x| {
98                        Value::record(
99                            record! {
100                                "description" => Value::string(x.description, span),
101                                "example" => Value::string(x.example, span),
102                                "result" => x.result.unwrap_or(Value::nothing(span)),
103                            },
104                            span,
105                        )
106                    })
107                    .collect();
108
109                let attributes = decl
110                    .attributes()
111                    .into_iter()
112                    .map(|(name, value)| {
113                        Value::record(
114                            record! {
115                                "name" => Value::string(name, span),
116                                "value" => value,
117                            },
118                            span,
119                        )
120                    })
121                    .collect();
122
123                let record = record! {
124                    "name" => Value::string(String::from_utf8_lossy(command_name), span),
125                    "category" => Value::string(signature.category.to_string(), span),
126                    "signatures" => self.collect_signatures(&signature, span),
127                    "description" => Value::string(decl.description(), span),
128                    "examples" => Value::list(examples, span),
129                    "attributes" => Value::list(attributes, span),
130                    "type" => Value::string(decl.command_type().to_string(), span),
131                    "is_sub" => Value::bool(decl.is_sub(), span),
132                    "is_const" => Value::bool(decl.is_const(), span),
133                    "creates_scope" => Value::bool(signature.creates_scope, span),
134                    "extra_description" => Value::string(decl.extra_description(), span),
135                    "search_terms" => Value::string(decl.search_terms().join(", "), span),
136                    "decl_id" => Value::int(decl_id.get() as i64, span),
137                };
138
139                commands.push(Value::record(record, span))
140            }
141        }
142
143        sort_rows(&mut commands);
144
145        commands
146    }
147
148    fn collect_signatures(&self, signature: &Signature, span: Span) -> Value {
149        let mut sigs = signature
150            .input_output_types
151            .iter()
152            .map(|(input_type, output_type)| {
153                (
154                    input_type.to_shape().to_string(),
155                    Value::list(
156                        self.collect_signature_entries(input_type, output_type, signature, span),
157                        span,
158                    ),
159                )
160            })
161            .collect::<Vec<(String, Value)>>();
162
163        // Until we allow custom commands to have input and output types, let's just
164        // make them Type::Any Type::Any so they can show up in our `scope commands`
165        // a little bit better. If sigs is empty, we're pretty sure that we're dealing
166        // with a custom command.
167        if sigs.is_empty() {
168            let any_type = &Type::Any;
169            sigs.push((
170                any_type.to_shape().to_string(),
171                Value::list(
172                    self.collect_signature_entries(any_type, any_type, signature, span),
173                    span,
174                ),
175            ));
176        }
177        sigs.sort_unstable_by(|(k1, _), (k2, _)| k1.cmp(k2));
178        // For most commands, input types are not repeated in
179        // `input_output_types`, i.e. each input type has only one associated
180        // output type. Furthermore, we want this to always be true. However,
181        // there are currently some exceptions, such as `hash sha256` which
182        // takes in string but may output string or binary depending on the
183        // presence of the --binary flag. In such cases, the "special case"
184        // signature usually comes later in the input_output_types, so this will
185        // remove them from the record.
186        sigs.dedup_by(|(k1, _), (k2, _)| k1 == k2);
187        Value::record(sigs.into_iter().collect(), span)
188    }
189
190    fn collect_signature_entries(
191        &self,
192        input_type: &Type,
193        output_type: &Type,
194        signature: &Signature,
195        span: Span,
196    ) -> Vec<Value> {
197        let mut sig_records = vec![];
198
199        // input
200        sig_records.push(Value::record(
201            record! {
202                "parameter_name" => Value::nothing(span),
203                "parameter_type" => Value::string("input", span),
204                "syntax_shape" => Value::string(input_type.to_shape().to_string(), span),
205                "is_optional" => Value::bool(false, span),
206                "short_flag" => Value::nothing(span),
207                "description" => Value::nothing(span),
208                "custom_completion" => Value::nothing(span),
209                "parameter_default" => Value::nothing(span),
210            },
211            span,
212        ));
213
214        // required_positional
215        for req in &signature.required_positional {
216            let custom = extract_custom_completion_from_arg(self.engine_state, &req.shape);
217
218            sig_records.push(Value::record(
219                record! {
220                    "parameter_name" => Value::string(&req.name, span),
221                    "parameter_type" => Value::string("positional", span),
222                    "syntax_shape" => Value::string(req.shape.to_string(), span),
223                    "is_optional" => Value::bool(false, span),
224                    "short_flag" => Value::nothing(span),
225                    "description" => Value::string(&req.desc, span),
226                    "custom_completion" => Value::string(custom, span),
227                    "parameter_default" => Value::nothing(span),
228                },
229                span,
230            ));
231        }
232
233        // optional_positional
234        for opt in &signature.optional_positional {
235            let custom = extract_custom_completion_from_arg(self.engine_state, &opt.shape);
236            let default = if let Some(val) = &opt.default_value {
237                val.clone()
238            } else {
239                Value::nothing(span)
240            };
241
242            sig_records.push(Value::record(
243                record! {
244                    "parameter_name" => Value::string(&opt.name, span),
245                    "parameter_type" => Value::string("positional", span),
246                    "syntax_shape" => Value::string(opt.shape.to_string(), span),
247                    "is_optional" => Value::bool(true, span),
248                    "short_flag" => Value::nothing(span),
249                    "description" => Value::string(&opt.desc, span),
250                    "custom_completion" => Value::string(custom, span),
251                    "parameter_default" => default,
252                },
253                span,
254            ));
255        }
256
257        // rest_positional
258        if let Some(rest) = &signature.rest_positional {
259            let name = if rest.name == "rest" { "" } else { &rest.name };
260            let custom = extract_custom_completion_from_arg(self.engine_state, &rest.shape);
261
262            sig_records.push(Value::record(
263                record! {
264                    "parameter_name" => Value::string(name, span),
265                    "parameter_type" => Value::string("rest", span),
266                    "syntax_shape" => Value::string(rest.shape.to_string(), span),
267                    "is_optional" => Value::bool(true, span),
268                    "short_flag" => Value::nothing(span),
269                    "description" => Value::string(&rest.desc, span),
270                    "custom_completion" => Value::string(custom, span),
271                    // rest_positional does have default, but parser prohibits specifying it?!
272                    "parameter_default" => Value::nothing(span),
273                },
274                span,
275            ));
276        }
277
278        // named flags
279        for named in &signature.named {
280            let flag_type;
281
282            // Skip the help flag
283            if named.long == "help" {
284                continue;
285            }
286
287            let mut custom_completion_command_name: String = "".to_string();
288            let shape = if let Some(arg) = &named.arg {
289                flag_type = Value::string("named", span);
290                custom_completion_command_name =
291                    extract_custom_completion_from_arg(self.engine_state, arg);
292                Value::string(arg.to_string(), span)
293            } else {
294                flag_type = Value::string("switch", span);
295                Value::nothing(span)
296            };
297
298            let short_flag = if let Some(c) = named.short {
299                Value::string(c, span)
300            } else {
301                Value::nothing(span)
302            };
303
304            let default = if let Some(val) = &named.default_value {
305                val.clone()
306            } else {
307                Value::nothing(span)
308            };
309
310            sig_records.push(Value::record(
311                record! {
312                    "parameter_name" => Value::string(&named.long, span),
313                    "parameter_type" => flag_type,
314                    "syntax_shape" => shape,
315                    "is_optional" => Value::bool(!named.required, span),
316                    "short_flag" => short_flag,
317                    "description" => Value::string(&named.desc, span),
318                    "custom_completion" => Value::string(custom_completion_command_name, span),
319                    "parameter_default" => default,
320                },
321                span,
322            ));
323        }
324
325        // output
326        sig_records.push(Value::record(
327            record! {
328                "parameter_name" => Value::nothing(span),
329                "parameter_type" => Value::string("output", span),
330                "syntax_shape" => Value::string(output_type.to_shape().to_string(), span),
331                "is_optional" => Value::bool(false, span),
332                "short_flag" => Value::nothing(span),
333                "description" => Value::nothing(span),
334                "custom_completion" => Value::nothing(span),
335                "parameter_default" => Value::nothing(span),
336            },
337            span,
338        ));
339
340        sig_records
341    }
342
343    pub fn collect_externs(&self, span: Span) -> Vec<Value> {
344        let mut externals = vec![];
345
346        for (command_name, decl_id) in &self.decls_map {
347            let decl = self.engine_state.get_decl(**decl_id);
348
349            if decl.is_known_external() {
350                let record = record! {
351                    "name" => Value::string(String::from_utf8_lossy(command_name), span),
352                    "description" => Value::string(decl.description(), span),
353                    "decl_id" => Value::int(decl_id.get() as i64, span),
354                };
355
356                externals.push(Value::record(record, span))
357            }
358        }
359
360        sort_rows(&mut externals);
361        externals
362    }
363
364    pub fn collect_aliases(&self, span: Span) -> Vec<Value> {
365        let mut aliases = vec![];
366
367        for (decl_name, decl_id) in self.engine_state.get_decls_sorted(false) {
368            if self.visibility.is_decl_id_visible(&decl_id) {
369                let decl = self.engine_state.get_decl(decl_id);
370                if let Some(alias) = decl.as_alias() {
371                    let aliased_decl_id = if let Expr::Call(wrapped_call) = &alias.wrapped_call.expr
372                    {
373                        Value::int(wrapped_call.decl_id.get() as i64, span)
374                    } else {
375                        Value::nothing(span)
376                    };
377
378                    let expansion = String::from_utf8_lossy(
379                        self.engine_state.get_span_contents(alias.wrapped_call.span),
380                    );
381
382                    aliases.push(Value::record(
383                        record! {
384                            "name" => Value::string(String::from_utf8_lossy(&decl_name), span),
385                            "expansion" => Value::string(expansion, span),
386                            "description" => Value::string(alias.description(), span),
387                            "decl_id" => Value::int(decl_id.get() as i64, span),
388                            "aliased_decl_id" => aliased_decl_id,
389                        },
390                        span,
391                    ));
392                }
393            }
394        }
395
396        sort_rows(&mut aliases);
397        // aliases.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
398        aliases
399    }
400
401    fn collect_module(&self, module_name: &[u8], module_id: &ModuleId, span: Span) -> Value {
402        let module = self.engine_state.get_module(*module_id);
403
404        let all_decls = module.decls();
405
406        let mut export_commands: Vec<Value> = all_decls
407            .iter()
408            .filter_map(|(name_bytes, decl_id)| {
409                let decl = self.engine_state.get_decl(*decl_id);
410
411                if !decl.is_alias() && !decl.is_known_external() {
412                    Some(Value::record(
413                        record! {
414                            "name" => Value::string(String::from_utf8_lossy(name_bytes), span),
415                            "decl_id" => Value::int(decl_id.get() as i64, span),
416                        },
417                        span,
418                    ))
419                } else {
420                    None
421                }
422            })
423            .collect();
424
425        let mut export_aliases: Vec<Value> = all_decls
426            .iter()
427            .filter_map(|(name_bytes, decl_id)| {
428                let decl = self.engine_state.get_decl(*decl_id);
429
430                if decl.is_alias() {
431                    Some(Value::record(
432                        record! {
433                            "name" => Value::string(String::from_utf8_lossy(name_bytes), span),
434                            "decl_id" => Value::int(decl_id.get() as i64, span),
435                        },
436                        span,
437                    ))
438                } else {
439                    None
440                }
441            })
442            .collect();
443
444        let mut export_externs: Vec<Value> = all_decls
445            .iter()
446            .filter_map(|(name_bytes, decl_id)| {
447                let decl = self.engine_state.get_decl(*decl_id);
448
449                if decl.is_known_external() {
450                    Some(Value::record(
451                        record! {
452                            "name" => Value::string(String::from_utf8_lossy(name_bytes), span),
453                            "decl_id" => Value::int(decl_id.get() as i64, span),
454                        },
455                        span,
456                    ))
457                } else {
458                    None
459                }
460            })
461            .collect();
462
463        let mut export_submodules: Vec<Value> = module
464            .submodules()
465            .iter()
466            .map(|(name_bytes, submodule_id)| self.collect_module(name_bytes, submodule_id, span))
467            .collect();
468
469        let mut export_consts: Vec<Value> = module
470            .consts()
471            .iter()
472            .map(|(name_bytes, var_id)| {
473                Value::record(
474                    record! {
475                        "name" => Value::string(String::from_utf8_lossy(name_bytes), span),
476                        "type" => Value::string(self.engine_state.get_var(*var_id).ty.to_string(), span),
477                        "var_id" => Value::int(var_id.get() as i64, span),
478                    },
479                    span,
480                )
481            })
482            .collect();
483
484        sort_rows(&mut export_commands);
485        sort_rows(&mut export_aliases);
486        sort_rows(&mut export_externs);
487        sort_rows(&mut export_submodules);
488        sort_rows(&mut export_consts);
489
490        let (module_desc, module_extra_desc) = self
491            .engine_state
492            .build_module_desc(*module_id)
493            .unwrap_or_default();
494
495        Value::record(
496            record! {
497                "name" => Value::string(String::from_utf8_lossy(module_name), span),
498                "commands" => Value::list(export_commands, span),
499                "aliases" => Value::list(export_aliases, span),
500                "externs" => Value::list(export_externs, span),
501                "submodules" => Value::list(export_submodules, span),
502                "constants" => Value::list(export_consts, span),
503                "has_env_block" => Value::bool(module.env_block.is_some(), span),
504                "description" => Value::string(module_desc, span),
505                "extra_description" => Value::string(module_extra_desc, span),
506                "module_id" => Value::int(module_id.get() as i64, span),
507                "file" => Value::string(module.file.clone().map_or("unknown".to_string(), |(p, _)| p.path().to_string_lossy().to_string()), span),
508            },
509            span,
510        )
511    }
512
513    pub fn collect_modules(&self, span: Span) -> Vec<Value> {
514        let mut modules = vec![];
515
516        for (module_name, module_id) in &self.modules_map {
517            modules.push(self.collect_module(module_name, module_id, span));
518        }
519
520        modules.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
521        modules
522    }
523
524    pub fn collect_engine_state(&self, span: Span) -> Value {
525        let num_env_vars = self
526            .engine_state
527            .env_vars
528            .values()
529            .map(|overlay| overlay.len() as i64)
530            .sum();
531
532        Value::record(
533            record! {
534                "source_bytes" => Value::int(self.engine_state.next_span_start() as i64, span),
535                "num_vars" => Value::int(self.engine_state.num_vars() as i64, span),
536                "num_decls" => Value::int(self.engine_state.num_decls() as i64, span),
537                "num_blocks" => Value::int(self.engine_state.num_blocks() as i64, span),
538                "num_modules" => Value::int(self.engine_state.num_modules() as i64, span),
539                "num_env_vars" => Value::int(num_env_vars, span),
540            },
541            span,
542        )
543    }
544}
545
546fn extract_custom_completion_from_arg(engine_state: &EngineState, shape: &SyntaxShape) -> String {
547    match shape {
548        SyntaxShape::CompleterWrapper(_, custom_completion_decl_id) => {
549            let custom_completion_command = engine_state.get_decl(*custom_completion_decl_id);
550            let custom_completion_command_name: &str = custom_completion_command.name();
551            custom_completion_command_name.to_string()
552        }
553        _ => "".to_string(),
554    }
555}
556
557fn sort_rows(decls: &mut [Value]) {
558    decls.sort_by(|a, b| match (a, b) {
559        (Value::Record { val: rec_a, .. }, Value::Record { val: rec_b, .. }) => {
560            // Comparing the first value from the record
561            // It is expected that the first value is the name of the entry (command, module, alias, etc.)
562            match (rec_a.values().next(), rec_b.values().next()) {
563                (Some(val_a), Some(val_b)) => match (val_a, val_b) {
564                    (Value::String { val: str_a, .. }, Value::String { val: str_b, .. }) => {
565                        str_a.cmp(str_b)
566                    }
567                    _ => Ordering::Equal,
568                },
569                _ => Ordering::Equal,
570            }
571        }
572        _ => Ordering::Equal,
573    });
574}