nu_engine/
scope.rs

1use nu_protocol::{
2    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                    "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 =
218                extract_custom_completion_from_arg(self.engine_state, &req.custom_completion);
219
220            sig_records.push(Value::record(
221                record! {
222                    "parameter_name" => Value::string(&req.name, span),
223                    "parameter_type" => Value::string("positional", span),
224                    "syntax_shape" => Value::string(req.shape.to_string(), span),
225                    "is_optional" => Value::bool(false, span),
226                    "short_flag" => Value::nothing(span),
227                    "description" => Value::string(&req.desc, span),
228                    "custom_completion" => Value::string(custom, span),
229                    "parameter_default" => Value::nothing(span),
230                },
231                span,
232            ));
233        }
234
235        // optional_positional
236        for opt in &signature.optional_positional {
237            let custom =
238                extract_custom_completion_from_arg(self.engine_state, &opt.custom_completion);
239            let default = if let Some(val) = &opt.default_value {
240                val.clone()
241            } else {
242                Value::nothing(span)
243            };
244
245            sig_records.push(Value::record(
246                record! {
247                    "parameter_name" => Value::string(&opt.name, span),
248                    "parameter_type" => Value::string("positional", span),
249                    "syntax_shape" => Value::string(opt.shape.to_string(), span),
250                    "is_optional" => Value::bool(true, span),
251                    "short_flag" => Value::nothing(span),
252                    "description" => Value::string(&opt.desc, span),
253                    "custom_completion" => Value::string(custom, span),
254                    "parameter_default" => default,
255                },
256                span,
257            ));
258        }
259
260        // rest_positional
261        if let Some(rest) = &signature.rest_positional {
262            let name = if rest.name == "rest" { "" } else { &rest.name };
263            let custom =
264                extract_custom_completion_from_arg(self.engine_state, &rest.custom_completion);
265
266            sig_records.push(Value::record(
267                record! {
268                    "parameter_name" => Value::string(name, span),
269                    "parameter_type" => Value::string("rest", span),
270                    "syntax_shape" => Value::string(rest.shape.to_string(), span),
271                    "is_optional" => Value::bool(true, span),
272                    "short_flag" => Value::nothing(span),
273                    "description" => Value::string(&rest.desc, span),
274                    "custom_completion" => Value::string(custom, span),
275                    // rest_positional does have default, but parser prohibits specifying it?!
276                    "parameter_default" => Value::nothing(span),
277                },
278                span,
279            ));
280        }
281
282        // named flags
283        for named in &signature.named {
284            let flag_type;
285
286            // Skip the help flag
287            if named.long == "help" {
288                continue;
289            }
290
291            let custom_completion_command_name: String =
292                extract_custom_completion_from_arg(self.engine_state, &named.custom_completion);
293            let shape = if let Some(arg) = &named.arg {
294                flag_type = Value::string("named", span);
295                Value::string(arg.to_string(), span)
296            } else {
297                flag_type = Value::string("switch", span);
298                Value::nothing(span)
299            };
300
301            let short_flag = if let Some(c) = named.short {
302                Value::string(c, span)
303            } else {
304                Value::nothing(span)
305            };
306
307            let default = if let Some(val) = &named.default_value {
308                val.clone()
309            } else {
310                Value::nothing(span)
311            };
312
313            sig_records.push(Value::record(
314                record! {
315                    "parameter_name" => Value::string(&named.long, span),
316                    "parameter_type" => flag_type,
317                    "syntax_shape" => shape,
318                    "is_optional" => Value::bool(!named.required, span),
319                    "short_flag" => short_flag,
320                    "description" => Value::string(&named.desc, span),
321                    "custom_completion" => Value::string(custom_completion_command_name, span),
322                    "parameter_default" => default,
323                },
324                span,
325            ));
326        }
327
328        // output
329        sig_records.push(Value::record(
330            record! {
331                "parameter_name" => Value::nothing(span),
332                "parameter_type" => Value::string("output", span),
333                "syntax_shape" => Value::string(output_type.to_shape().to_string(), span),
334                "is_optional" => Value::bool(false, span),
335                "short_flag" => Value::nothing(span),
336                "description" => Value::nothing(span),
337                "custom_completion" => Value::nothing(span),
338                "parameter_default" => Value::nothing(span),
339            },
340            span,
341        ));
342
343        sig_records
344    }
345
346    pub fn collect_externs(&self, span: Span) -> Vec<Value> {
347        let mut externals = vec![];
348
349        for (command_name, decl_id) in &self.decls_map {
350            let decl = self.engine_state.get_decl(**decl_id);
351
352            if decl.is_known_external() {
353                let record = record! {
354                    "name" => Value::string(String::from_utf8_lossy(command_name), span),
355                    "description" => Value::string(decl.description(), span),
356                    "decl_id" => Value::int(decl_id.get() as i64, span),
357                };
358
359                externals.push(Value::record(record, span))
360            }
361        }
362
363        sort_rows(&mut externals);
364        externals
365    }
366
367    pub fn collect_aliases(&self, span: Span) -> Vec<Value> {
368        let mut aliases = vec![];
369
370        for (decl_name, decl_id) in self.engine_state.get_decls_sorted(false) {
371            if self.visibility.is_decl_id_visible(&decl_id) {
372                let decl = self.engine_state.get_decl(decl_id);
373                if let Some(alias) = decl.as_alias() {
374                    let aliased_decl_id = if let Expr::Call(wrapped_call) = &alias.wrapped_call.expr
375                    {
376                        Value::int(wrapped_call.decl_id.get() as i64, span)
377                    } else {
378                        Value::nothing(span)
379                    };
380
381                    let expansion = String::from_utf8_lossy(
382                        self.engine_state.get_span_contents(alias.wrapped_call.span),
383                    );
384
385                    aliases.push(Value::record(
386                        record! {
387                            "name" => Value::string(String::from_utf8_lossy(&decl_name), span),
388                            "expansion" => Value::string(expansion, span),
389                            "description" => Value::string(alias.description(), span),
390                            "decl_id" => Value::int(decl_id.get() as i64, span),
391                            "aliased_decl_id" => aliased_decl_id,
392                        },
393                        span,
394                    ));
395                }
396            }
397        }
398
399        sort_rows(&mut aliases);
400        // aliases.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
401        aliases
402    }
403
404    fn collect_module(&self, module_name: &[u8], module_id: &ModuleId, span: Span) -> Value {
405        let module = self.engine_state.get_module(*module_id);
406
407        let all_decls = module.decls();
408
409        let mut export_commands: Vec<Value> = all_decls
410            .iter()
411            .filter_map(|(name_bytes, decl_id)| {
412                let decl = self.engine_state.get_decl(*decl_id);
413
414                if !decl.is_alias() && !decl.is_known_external() {
415                    Some(Value::record(
416                        record! {
417                            "name" => Value::string(String::from_utf8_lossy(name_bytes), span),
418                            "decl_id" => Value::int(decl_id.get() as i64, span),
419                        },
420                        span,
421                    ))
422                } else {
423                    None
424                }
425            })
426            .collect();
427
428        let mut export_aliases: 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() {
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_externs: 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_known_external() {
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_submodules: Vec<Value> = module
467            .submodules()
468            .iter()
469            .map(|(name_bytes, submodule_id)| self.collect_module(name_bytes, submodule_id, span))
470            .collect();
471
472        let mut export_consts: Vec<Value> = module
473            .consts()
474            .iter()
475            .map(|(name_bytes, var_id)| {
476                Value::record(
477                    record! {
478                        "name" => Value::string(String::from_utf8_lossy(name_bytes), span),
479                        "type" => Value::string(self.engine_state.get_var(*var_id).ty.to_string(), span),
480                        "var_id" => Value::int(var_id.get() as i64, span),
481                    },
482                    span,
483                )
484            })
485            .collect();
486
487        sort_rows(&mut export_commands);
488        sort_rows(&mut export_aliases);
489        sort_rows(&mut export_externs);
490        sort_rows(&mut export_submodules);
491        sort_rows(&mut export_consts);
492
493        let (module_desc, module_extra_desc) = self
494            .engine_state
495            .build_module_desc(*module_id)
496            .unwrap_or_default();
497
498        Value::record(
499            record! {
500                "name" => Value::string(String::from_utf8_lossy(module_name), span),
501                "commands" => Value::list(export_commands, span),
502                "aliases" => Value::list(export_aliases, span),
503                "externs" => Value::list(export_externs, span),
504                "submodules" => Value::list(export_submodules, span),
505                "constants" => Value::list(export_consts, span),
506                "has_env_block" => Value::bool(module.env_block.is_some(), span),
507                "description" => Value::string(module_desc, span),
508                "extra_description" => Value::string(module_extra_desc, span),
509                "module_id" => Value::int(module_id.get() as i64, span),
510                "file" => Value::string(module.file.clone().map_or("unknown".to_string(), |(p, _)| p.path().to_string_lossy().to_string()), span),
511            },
512            span,
513        )
514    }
515
516    pub fn collect_modules(&self, span: Span) -> Vec<Value> {
517        let mut modules = vec![];
518
519        for (module_name, module_id) in &self.modules_map {
520            modules.push(self.collect_module(module_name, module_id, span));
521        }
522
523        modules.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
524        modules
525    }
526
527    pub fn collect_engine_state(&self, span: Span) -> Value {
528        let num_env_vars = self
529            .engine_state
530            .env_vars
531            .values()
532            .map(|overlay| overlay.len() as i64)
533            .sum();
534
535        Value::record(
536            record! {
537                "source_bytes" => Value::int(self.engine_state.next_span_start() as i64, span),
538                "num_vars" => Value::int(self.engine_state.num_vars() as i64, span),
539                "num_decls" => Value::int(self.engine_state.num_decls() as i64, span),
540                "num_blocks" => Value::int(self.engine_state.num_blocks() as i64, span),
541                "num_modules" => Value::int(self.engine_state.num_modules() as i64, span),
542                "num_env_vars" => Value::int(num_env_vars, span),
543            },
544            span,
545        )
546    }
547}
548
549fn extract_custom_completion_from_arg(
550    engine_state: &EngineState,
551    decl_id: &Option<DeclId>,
552) -> String {
553    if let Some(decl_id) = decl_id {
554        let custom_completion_command = engine_state.get_decl(*decl_id);
555        let custom_completion_command_name: &str = custom_completion_command.name();
556        custom_completion_command_name.to_string()
557    } else {
558        "".to_string()
559    }
560}
561
562fn sort_rows(decls: &mut [Value]) {
563    decls.sort_by(|a, b| match (a, b) {
564        (Value::Record { val: rec_a, .. }, Value::Record { val: rec_b, .. }) => {
565            // Comparing the first value from the record
566            // It is expected that the first value is the name of the entry (command, module, alias, etc.)
567            match (rec_a.values().next(), rec_b.values().next()) {
568                (Some(val_a), Some(val_b)) => match (val_a, val_b) {
569                    (Value::String { val: str_a, .. }, Value::String { val: str_b, .. }) => {
570                        str_a.cmp(str_b)
571                    }
572                    _ => Ordering::Equal,
573                },
574                _ => Ordering::Equal,
575            }
576        }
577        _ => Ordering::Equal,
578    });
579}