nu_engine/
scope.rs

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