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