nu_engine/
scope.rs

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