Skip to main content

nu_command/env/config/
config_.rs

1use nu_cmd_base::util::get_editor;
2use nu_engine::{command_prelude::*, env_to_strings, get_full_help};
3use nu_protocol::{PipelineMetadata, shell_error::generic::GenericError, shell_error::io::IoError};
4use nu_system::ForegroundChild;
5use nu_utils::ConfigFileKind;
6
7#[cfg(feature = "os")]
8use nu_protocol::process::PostWaitCallback;
9
10#[derive(Clone)]
11pub struct ConfigMeta;
12
13impl Command for ConfigMeta {
14    fn name(&self) -> &str {
15        "config"
16    }
17
18    fn signature(&self) -> Signature {
19        Signature::build(self.name())
20            .category(Category::Env)
21            .input_output_types(vec![(Type::Nothing, Type::String)])
22    }
23
24    fn description(&self) -> &str {
25        "Edit nushell configuration files."
26    }
27
28    fn extra_description(&self) -> &str {
29        "You must use one of the following subcommands. Using this command as-is will only produce this help message."
30    }
31
32    fn run(
33        &self,
34        engine_state: &EngineState,
35        stack: &mut Stack,
36        call: &Call,
37        _input: PipelineData,
38    ) -> Result<PipelineData, ShellError> {
39        Ok(Value::string(
40            get_full_help(self, engine_state, stack, call.head),
41            call.head,
42        )
43        .into_pipeline_data())
44    }
45
46    fn search_terms(&self) -> Vec<&str> {
47        vec!["options", "setup"]
48    }
49}
50
51#[cfg(not(feature = "os"))]
52pub(super) fn start_editor(
53    _: ConfigFileKind,
54    _: &EngineState,
55    _: &mut Stack,
56    call: &Call,
57) -> Result<PipelineData, ShellError> {
58    Err(ShellError::DisabledOsSupport {
59        msg: "Running external commands is not available without OS support.".to_string(),
60        span: call.head,
61    })
62}
63
64#[cfg(feature = "os")]
65pub(super) fn start_editor(
66    kind: ConfigFileKind,
67    engine_state: &EngineState,
68    stack: &mut Stack,
69    call: &Call,
70) -> Result<PipelineData, ShellError> {
71    // Find the editor executable.
72
73    let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
74    let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
75    let cwd = engine_state.cwd(Some(stack))?;
76    let editor_executable =
77        crate::which(&editor_name, &paths, cwd.as_ref()).ok_or(ShellError::ExternalCommand {
78            label: format!("`{editor_name}` not found"),
79            help: "Failed to find the editor executable".into(),
80            span: call.head,
81        })?;
82
83    let nu_const_path = kind.nu_const_path();
84    let Some(config_path) = engine_state.get_config_path(nu_const_path) else {
85        return Err(ShellError::Generic(GenericError::new_internal(
86            format!("Could not find $nu.{nu_const_path}"),
87            format!("Could not find $nu.{nu_const_path}"),
88        )));
89    };
90    let config_path = config_path.to_string_lossy().to_string();
91
92    // Create the command.
93    let mut command = std::process::Command::new(editor_executable);
94
95    // Configure PWD.
96    command.current_dir(cwd);
97
98    // Configure environment variables.
99    let envs = env_to_strings(engine_state, stack)?;
100    command.env_clear();
101    command.envs(envs);
102
103    // Configure args.
104    command.args(editor_args);
105    command.arg(config_path);
106
107    // Spawn the child process. On Unix, also put the child process to
108    // foreground if we're in an interactive session.
109    #[cfg(windows)]
110    let child = ForegroundChild::spawn(command);
111    #[cfg(unix)]
112    let child = ForegroundChild::spawn(
113        command,
114        engine_state.is_interactive,
115        engine_state.is_background_job(),
116        &engine_state.pipeline_externals_state,
117    );
118
119    let child = child.map_err(|err| {
120        IoError::new_with_additional_context(
121            err,
122            call.head,
123            None,
124            "Could not spawn foreground child",
125        )
126    })?;
127
128    let post_wait_callback = PostWaitCallback::for_job_control(engine_state, None, None);
129
130    // Wrap the output into a `PipelineData::byte_stream`.
131    let child = nu_protocol::process::ChildProcess::new(
132        child,
133        None,
134        false,
135        call.head,
136        Some(post_wait_callback),
137    )?;
138
139    Ok(PipelineData::byte_stream(
140        ByteStream::child(child, call.head),
141        None,
142    ))
143}
144
145pub(super) fn handle_call(
146    kind: ConfigFileKind,
147    engine_state: &EngineState,
148    stack: &mut Stack,
149    call: &Call,
150) -> Result<PipelineData, ShellError> {
151    let default_flag = call.has_flag(engine_state, stack, "default")?;
152    let doc_flag = call.has_flag(engine_state, stack, "doc")?;
153
154    Ok(match (default_flag, doc_flag) {
155        (false, false) => {
156            return super::config_::start_editor(kind, engine_state, stack, call);
157        }
158        (true, true) => {
159            return Err(ShellError::IncompatibleParameters {
160                left_message: "can't use `--default` at the same time".into(),
161                left_span: call.get_flag_span(stack, "default").expect("has flag"),
162                right_message: "because of `--doc`".into(),
163                right_span: call.get_flag_span(stack, "doc").expect("has flag"),
164            });
165        }
166        (true, false) => kind.default(),
167        (false, true) => kind.doc(),
168    }
169    .into_value(call.head)
170    .into_pipeline_data_with_metadata(
171        PipelineMetadata::default().with_content_type(Some("application/x-nuscript".into())),
172    ))
173}