use crate::explore_config::conversion::{
build_nu_type_map, build_original_value_map, json_to_nu_value_with_types, nu_value_to_json,
parse_config_documentation,
};
use crate::explore_config::example_data::get_example_json;
use crate::explore_config::tree::print_json_tree;
use crate::explore_config::tui::run_config_tui;
use crate::explore_config::types::NuValueType;
use nu_engine::command_prelude::*;
use nu_protocol::{PipelineData, report_shell_warning, shell_error::generic::GenericError};
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
type ConfigDataResult = (
Value,
bool,
Option<HashMap<String, NuValueType>>,
Option<HashMap<String, nu_protocol::Value>>,
Option<HashMap<String, String>>,
);
#[derive(Clone)]
pub struct ExploreConfigCommand;
impl Command for ExploreConfigCommand {
fn name(&self) -> &str {
"explore config"
}
fn description(&self) -> &str {
"Launch a TUI to view and edit the nushell configuration interactively."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("explore config")
.input_output_types(vec![
(Type::Nothing, Type::String),
(Type::String, Type::String),
])
.switch(
"use-example-data",
"Show the nushell configuration TUI using example data.",
Some('e'),
)
.switch(
"tree",
"Do not show the TUI, just show a tree structure of the data.",
Some('t'),
)
.named(
"output",
SyntaxShape::String,
"Optional output file to save changes to (default: output.json).",
Some('o'),
)
.category(Category::Viewers)
}
fn extra_description(&self) -> &str {
"By default, opens the current nushell configuration ($env.config) in the TUI.
Changes made in config mode are applied to the running session when you quit.
You can also pipe JSON data to explore arbitrary data structures, or use
--use-example-data to see sample configuration data.
TUI Keybindings:
Tab Switch between tree and editor panes
↑↓ Navigate tree / scroll editor
←→ Collapse/Expand tree nodes
Enter/Space Toggle tree node expansion
Enter/Space On leaf nodes, open editor pane and start editing
Enter/e Start editing (in editor pane)
Ctrl+S Apply edit
Alt+Enter Apply edit (alternative)
Esc Cancel edit
q Quit (applies config changes if modified)
Ctrl+C Force quit without saving"
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let input_span = input.span().unwrap_or(call.head);
let (string_input, _span, _metadata) = input.collect_string_strict(input_span)?;
let use_example = call.has_flag(engine_state, stack, "use-example-data")?;
let cli_mode = call.has_flag(engine_state, stack, "tree")?;
let output_file: Option<String> = call.get_flag(engine_state, stack, "output")?;
let (json_data, config_mode, nu_type_map, original_values, doc_map): ConfigDataResult =
if use_example {
(get_example_json(), false, None, None, None)
} else if !string_input.trim().is_empty() {
let data = serde_json::from_str(&string_input).map_err(|e| {
ShellError::Generic(
GenericError::new(
"Could not parse JSON from input",
format!("JSON parse error: {e}"),
call.head,
)
.with_help("Make sure the input is valid JSON"),
)
})?;
(data, false, None, None, None)
} else {
let nu_value = stack
.get_env_var(engine_state, "config")
.cloned()
.unwrap_or_else(|| {
let config = stack.get_config(engine_state);
config.as_ref().clone().into_value(call.head)
});
let json_data = nu_value_to_json(engine_state, &nu_value, call.head)?;
let mut nu_type_map = HashMap::new();
build_nu_type_map(&nu_value, Vec::new(), &mut nu_type_map);
let mut original_values = HashMap::new();
build_original_value_map(&nu_value, Vec::new(), &mut original_values);
let doc_map = parse_config_documentation();
(
json_data,
true,
Some(nu_type_map),
Some(original_values),
Some(doc_map),
)
};
if cli_mode {
print_json_tree(&json_data, "", true, None);
} else {
let type_map_for_conversion = nu_type_map.clone();
let original_values_for_conversion = original_values.clone();
let result = run_config_tui(
json_data,
output_file,
config_mode,
nu_type_map,
doc_map,
Arc::new(engine_state.clone()),
Arc::new(stack.clone()),
)?;
if config_mode && let Some(modified_json) = result {
let nu_value = json_to_nu_value_with_types(
&modified_json,
call.head,
&type_map_for_conversion,
&original_values_for_conversion,
Vec::new(),
)
.map_err(|e| {
ShellError::Generic(GenericError::new(
"Could not convert JSON to nu Value",
format!("conversion error: {e}"),
call.head,
))
})?;
stack.add_env_var("config".into(), nu_value.clone());
let old_config = stack.get_config(engine_state);
let mut new_config = (*old_config).clone();
let result = new_config.update_from_value(&old_config, &nu_value);
stack.config = Some(Arc::new(new_config));
if let Some(warning) = result? {
report_shell_warning(Some(stack), engine_state, &warning);
}
}
}
Ok(PipelineData::empty())
}
fn examples(&self) -> Vec<Example<'_>> {
vec![
Example {
description: "Open the nushell configuration in an interactive TUI editor",
example: "explore config",
result: None,
},
Example {
description: "Explore JSON data interactively",
example: "open --raw data.json | explore config",
result: None,
},
Example {
description: "Explore with example data to see TUI features",
example: "explore config --use-example-data",
result: None,
},
]
}
}