nu_cmd_base/
util.rs

1use nu_protocol::{
2    Range, ShellError, Span, Value,
3    engine::{EngineState, Stack},
4};
5use std::ops::Bound;
6
7type MakeRangeError = fn(&str, Span) -> ShellError;
8
9/// Returns a inclusive pair of boundary in given `range`.
10pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
11    match range {
12        Range::IntRange(range) => {
13            let start = range.start().try_into().unwrap_or(0);
14            let end = match range.end() {
15                Bound::Included(v) => v as isize,
16                Bound::Excluded(v) => (v - 1) as isize,
17                Bound::Unbounded => isize::MAX,
18            };
19            Ok((start, end))
20        }
21        Range::FloatRange(_) => Err(|msg, span| ShellError::TypeMismatch {
22            err_message: msg.to_string(),
23            span,
24        }),
25    }
26}
27
28const HELP_MSG: &str = "Nushell's config file can be found with the command: $nu.config-path. \
29For more help: (https://nushell.sh/book/configuration.html#configurations-with-built-in-commands)";
30
31fn get_editor_commandline(
32    value: &Value,
33    var_name: &str,
34) -> Result<(String, Vec<String>), ShellError> {
35    match value {
36        Value::String { val, .. } if !val.is_empty() => Ok((val.to_string(), Vec::new())),
37        Value::List { vals, .. } if !vals.is_empty() => {
38            let mut editor_cmd = vals.iter().map(|l| l.coerce_string());
39            match editor_cmd.next().transpose()? {
40                Some(editor) if !editor.is_empty() => {
41                    let params = editor_cmd.collect::<Result<_, ShellError>>()?;
42                    Ok((editor, params))
43                }
44                _ => Err(ShellError::GenericError {
45                    error: "Editor executable is missing".into(),
46                    msg: "Set the first element to an executable".into(),
47                    span: Some(value.span()),
48                    help: Some(HELP_MSG.into()),
49                    inner: vec![],
50                }),
51            }
52        }
53        Value::String { .. } | Value::List { .. } => Err(ShellError::GenericError {
54            error: format!("{var_name} should be a non-empty string or list<String>"),
55            msg: "Specify an executable here".into(),
56            span: Some(value.span()),
57            help: Some(HELP_MSG.into()),
58            inner: vec![],
59        }),
60        x => Err(ShellError::CantConvert {
61            to_type: "string or list<string>".into(),
62            from_type: x.get_type().to_string(),
63            span: value.span(),
64            help: None,
65        }),
66    }
67}
68
69pub fn get_editor(
70    engine_state: &EngineState,
71    stack: &Stack,
72    span: Span,
73) -> Result<(String, Vec<String>), ShellError> {
74    let config = engine_state.get_config();
75    let env_vars = stack.get_env_vars(engine_state);
76
77    if let Ok(buff_editor) =
78        get_editor_commandline(&config.buffer_editor, "$env.config.buffer_editor")
79    {
80        Ok(buff_editor)
81    } else if let Some(value) = env_vars.get("VISUAL") {
82        get_editor_commandline(value, "$env.VISUAL")
83    } else if let Some(value) = env_vars.get("EDITOR") {
84        get_editor_commandline(value, "$env.EDITOR")
85    } else {
86        Err(ShellError::GenericError {
87            error: "No editor configured".into(),
88            msg:
89                "Please specify one via `$env.config.buffer_editor` or `$env.EDITOR`/`$env.VISUAL`"
90                    .into(),
91            span: Some(span),
92            help: Some(HELP_MSG.into()),
93            inner: vec![],
94        })
95    }
96}