forked from extern/nushell
Slim down configuration readings and nu_cli clean up. (#2559)
We continue refactoring nu_cli and slim down a bit configuration readings with a naive metadata `modified` field check.
This commit is contained in:
parent
50cbf91bc5
commit
10d4edc7af
@ -7,7 +7,7 @@ use crate::prelude::*;
|
|||||||
use crate::shell::Helper;
|
use crate::shell::Helper;
|
||||||
use crate::EnvironmentSyncer;
|
use crate::EnvironmentSyncer;
|
||||||
use futures_codec::FramedRead;
|
use futures_codec::FramedRead;
|
||||||
use nu_errors::{ProximateShellError, ShellDiagnostic, ShellError};
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
||||||
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
|
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||||
|
|
||||||
@ -20,19 +20,6 @@ use std::iter::Iterator;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
pub fn register_plugins(context: &mut Context) -> Result<(), ShellError> {
|
|
||||||
if let Ok(plugins) = crate::plugin::scan(search_paths()) {
|
|
||||||
context.add_commands(
|
|
||||||
plugins
|
|
||||||
.into_iter()
|
|
||||||
.filter(|p| !context.is_command_registered(p.name()))
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
@ -64,15 +51,8 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
|
|||||||
search_paths
|
search_paths
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_default_context(
|
pub fn create_default_context(interactive: bool) -> Result<Context, Box<dyn Error>> {
|
||||||
syncer: &mut crate::EnvironmentSyncer,
|
|
||||||
interactive: bool,
|
|
||||||
) -> Result<Context, Box<dyn Error>> {
|
|
||||||
syncer.load_environment();
|
|
||||||
|
|
||||||
let mut context = Context::basic()?;
|
let mut context = Context::basic()?;
|
||||||
syncer.sync_env_vars(&mut context);
|
|
||||||
syncer.sync_path_vars(&mut context);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
use crate::commands::*;
|
use crate::commands::*;
|
||||||
@ -299,282 +279,62 @@ pub async fn run_vec_of_pipelines(
|
|||||||
pipelines: Vec<String>,
|
pipelines: Vec<String>,
|
||||||
redirect_stdin: bool,
|
redirect_stdin: bool,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let mut syncer = crate::EnvironmentSyncer::new();
|
let mut syncer = EnvironmentSyncer::new();
|
||||||
let mut context = create_default_context(&mut syncer, false)?;
|
let mut context = create_default_context(false)?;
|
||||||
|
let config = syncer.get_config();
|
||||||
|
|
||||||
let _ = register_plugins(&mut context);
|
context.configure(&config, |_, ctx| {
|
||||||
|
syncer.load_environment();
|
||||||
|
syncer.sync_env_vars(ctx);
|
||||||
|
syncer.sync_path_vars(ctx);
|
||||||
|
|
||||||
#[cfg(feature = "ctrlc")]
|
if let Err(reason) = syncer.autoenv(ctx) {
|
||||||
{
|
print_err(reason, &Text::from(""));
|
||||||
let cc = context.ctrl_c.clone();
|
|
||||||
|
|
||||||
ctrlc::set_handler(move || {
|
|
||||||
cc.store(true, Ordering::SeqCst);
|
|
||||||
})
|
|
||||||
.expect("Error setting Ctrl-C handler");
|
|
||||||
|
|
||||||
if context.ctrl_c.load(Ordering::SeqCst) {
|
|
||||||
context.ctrl_c.store(false, Ordering::SeqCst);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// before we start up, let's run our startup commands
|
let _ = register_plugins(ctx);
|
||||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
let _ = configure_ctrl_c(ctx);
|
||||||
if let Some(commands) = config.get("startup") {
|
});
|
||||||
match commands {
|
|
||||||
Value {
|
let _ = run_startup_commands(&mut context, &config).await;
|
||||||
value: UntaggedValue::Table(pipelines),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
for pipeline in pipelines {
|
|
||||||
if let Ok(pipeline_string) = pipeline.as_string() {
|
|
||||||
let _ = run_pipeline_standalone(
|
|
||||||
pipeline_string,
|
|
||||||
false,
|
|
||||||
&mut context,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
println!("warning: expected a table of pipeline strings as startup commands");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for pipeline in pipelines {
|
for pipeline in pipelines {
|
||||||
run_pipeline_standalone(pipeline, redirect_stdin, &mut context, true).await?;
|
run_pipeline_standalone(pipeline, redirect_stdin, &mut context, true).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run_pipeline_standalone(
|
|
||||||
pipeline: String,
|
|
||||||
redirect_stdin: bool,
|
|
||||||
context: &mut Context,
|
|
||||||
exit_on_error: bool,
|
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
let line = process_line(Ok(pipeline), context, redirect_stdin, false).await;
|
|
||||||
|
|
||||||
match line {
|
|
||||||
LineResult::Success(line) => {
|
|
||||||
let error_code = {
|
|
||||||
let errors = context.current_errors.clone();
|
|
||||||
let errors = errors.lock();
|
|
||||||
|
|
||||||
if errors.len() > 0 {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
context.maybe_print_errors(Text::from(line));
|
|
||||||
if error_code != 0 && exit_on_error {
|
|
||||||
std::process::exit(error_code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LineResult::Error(line, err) => {
|
|
||||||
context.with_host(|_host| {
|
|
||||||
print_err(err, &Text::from(line.clone()));
|
|
||||||
});
|
|
||||||
|
|
||||||
context.maybe_print_errors(Text::from(line));
|
|
||||||
if exit_on_error {
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Value>) {
|
|
||||||
#[cfg(windows)]
|
|
||||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
|
||||||
|
|
||||||
let config = Config::builder().color_mode(ColorMode::Forced).build();
|
|
||||||
let mut rl: Editor<_> = Editor::with_config(config);
|
|
||||||
|
|
||||||
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
|
|
||||||
rl.bind_sequence(
|
|
||||||
KeyPress::ControlLeft,
|
|
||||||
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
|
|
||||||
);
|
|
||||||
rl.bind_sequence(
|
|
||||||
KeyPress::ControlRight,
|
|
||||||
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Let's set the defaults up front and then override them later if the user indicates
|
|
||||||
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
|
|
||||||
rl.set_max_history_size(100);
|
|
||||||
rl.set_history_ignore_dups(true);
|
|
||||||
rl.set_history_ignore_space(false);
|
|
||||||
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
|
|
||||||
rl.set_completion_prompt_limit(100);
|
|
||||||
rl.set_keyseq_timeout(-1);
|
|
||||||
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
|
|
||||||
rl.set_auto_add_history(false);
|
|
||||||
rl.set_bell_style(rustyline::config::BellStyle::default());
|
|
||||||
rl.set_color_mode(rustyline::ColorMode::Enabled);
|
|
||||||
rl.set_tab_stop(8);
|
|
||||||
|
|
||||||
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
|
|
||||||
println!("Error loading keybindings: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = match config::config(Tag::unknown()) {
|
|
||||||
Ok(config) => config,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Config could not be loaded.");
|
|
||||||
if let ShellError {
|
|
||||||
error: ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }),
|
|
||||||
..
|
|
||||||
} = e
|
|
||||||
{
|
|
||||||
eprintln!("{}", diagnostic.message);
|
|
||||||
}
|
|
||||||
IndexMap::new()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(config) = config::config(Tag::unknown()) {
|
|
||||||
if let Some(line_editor_vars) = config.get("line_editor") {
|
|
||||||
for (idx, value) in line_editor_vars.row_entries() {
|
|
||||||
match idx.as_ref() {
|
|
||||||
"max_history_size" => {
|
|
||||||
if let Ok(max_history_size) = value.as_u64() {
|
|
||||||
rl.set_max_history_size(max_history_size as usize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"history_duplicates" => {
|
|
||||||
// history_duplicates = match value.as_string() {
|
|
||||||
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
|
|
||||||
// rustyline::config::HistoryDuplicates::AlwaysAdd
|
|
||||||
// }
|
|
||||||
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
|
|
||||||
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
|
|
||||||
// }
|
|
||||||
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
|
|
||||||
// };
|
|
||||||
if let Ok(history_duplicates) = value.as_bool() {
|
|
||||||
rl.set_history_ignore_dups(history_duplicates);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"history_ignore_space" => {
|
|
||||||
if let Ok(history_ignore_space) = value.as_bool() {
|
|
||||||
rl.set_history_ignore_space(history_ignore_space);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"completion_type" => {
|
|
||||||
let completion_type = match value.as_string() {
|
|
||||||
Ok(s) if s.to_lowercase() == "circular" => {
|
|
||||||
rustyline::config::CompletionType::Circular
|
|
||||||
}
|
|
||||||
Ok(s) if s.to_lowercase() == "list" => {
|
|
||||||
rustyline::config::CompletionType::List
|
|
||||||
}
|
|
||||||
#[cfg(all(unix, feature = "with-fuzzy"))]
|
|
||||||
Ok(s) if s.to_lowercase() == "fuzzy" => {
|
|
||||||
rustyline::config::CompletionType::Fuzzy
|
|
||||||
}
|
|
||||||
_ => DEFAULT_COMPLETION_MODE,
|
|
||||||
};
|
|
||||||
rl.set_completion_type(completion_type);
|
|
||||||
}
|
|
||||||
"completion_prompt_limit" => {
|
|
||||||
if let Ok(completion_prompt_limit) = value.as_u64() {
|
|
||||||
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"keyseq_timeout_ms" => {
|
|
||||||
if let Ok(keyseq_timeout_ms) = value.as_u64() {
|
|
||||||
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"edit_mode" => {
|
|
||||||
let edit_mode = match value.as_string() {
|
|
||||||
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
|
|
||||||
Ok(s) if s.to_lowercase() == "emacs" => {
|
|
||||||
rustyline::config::EditMode::Emacs
|
|
||||||
}
|
|
||||||
_ => rustyline::config::EditMode::Emacs,
|
|
||||||
};
|
|
||||||
rl.set_edit_mode(edit_mode);
|
|
||||||
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
|
|
||||||
// no matter what you may have configured. This is so that key chords
|
|
||||||
// can be applied without having to do them in a given timeout. So,
|
|
||||||
// it essentially turns off the keyseq timeout.
|
|
||||||
}
|
|
||||||
"auto_add_history" => {
|
|
||||||
if let Ok(auto_add_history) = value.as_bool() {
|
|
||||||
rl.set_auto_add_history(auto_add_history);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"bell_style" => {
|
|
||||||
let bell_style = match value.as_string() {
|
|
||||||
Ok(s) if s.to_lowercase() == "audible" => {
|
|
||||||
rustyline::config::BellStyle::Audible
|
|
||||||
}
|
|
||||||
Ok(s) if s.to_lowercase() == "none" => {
|
|
||||||
rustyline::config::BellStyle::None
|
|
||||||
}
|
|
||||||
Ok(s) if s.to_lowercase() == "visible" => {
|
|
||||||
rustyline::config::BellStyle::Visible
|
|
||||||
}
|
|
||||||
_ => rustyline::config::BellStyle::default(),
|
|
||||||
};
|
|
||||||
rl.set_bell_style(bell_style);
|
|
||||||
}
|
|
||||||
"color_mode" => {
|
|
||||||
let color_mode = match value.as_string() {
|
|
||||||
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
|
|
||||||
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
|
|
||||||
Ok(s) if s.to_lowercase() == "disabled" => {
|
|
||||||
rustyline::ColorMode::Disabled
|
|
||||||
}
|
|
||||||
_ => rustyline::ColorMode::Enabled,
|
|
||||||
};
|
|
||||||
rl.set_color_mode(color_mode);
|
|
||||||
}
|
|
||||||
"tab_stop" => {
|
|
||||||
if let Ok(tab_stop) = value.as_u64() {
|
|
||||||
rl.set_tab_stop(tab_stop as usize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(rl, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
|
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
|
||||||
pub async fn cli(
|
pub async fn cli(mut context: Context) -> Result<(), Box<dyn Error>> {
|
||||||
mut syncer: EnvironmentSyncer,
|
let mut syncer = EnvironmentSyncer::new();
|
||||||
mut context: Context,
|
let configuration = syncer.get_config();
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
let configuration = nu_data::config::NuConfig::new();
|
let mut rl = default_rustyline_editor_configuration();
|
||||||
|
|
||||||
|
context.configure(&configuration, |config, ctx| {
|
||||||
|
syncer.load_environment();
|
||||||
|
syncer.sync_env_vars(ctx);
|
||||||
|
syncer.sync_path_vars(ctx);
|
||||||
|
|
||||||
|
if let Err(reason) = syncer.autoenv(ctx) {
|
||||||
|
print_err(reason, &Text::from(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = configure_ctrl_c(ctx);
|
||||||
|
let _ = configure_rustyline_editor(&mut rl, config);
|
||||||
|
|
||||||
|
let helper = Some(nu_line_editor_helper(ctx, config));
|
||||||
|
rl.set_helper(helper);
|
||||||
|
});
|
||||||
|
|
||||||
|
let _ = run_startup_commands(&mut context, &configuration).await;
|
||||||
|
|
||||||
let history_path = crate::commands::history::history_path(&configuration);
|
let history_path = crate::commands::history::history_path(&configuration);
|
||||||
|
|
||||||
let (mut rl, config) = create_rustyline_configuration();
|
|
||||||
|
|
||||||
// we are ok if history does not exist
|
|
||||||
let _ = rl.load_history(&history_path);
|
let _ = rl.load_history(&history_path);
|
||||||
|
|
||||||
let skip_welcome_message = config
|
let skip_welcome_message = configuration
|
||||||
.get("skip_welcome_message")
|
.var("skip_welcome_message")
|
||||||
.map(|x| x.is_true())
|
.map(|x| x.is_true())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
if !skip_welcome_message {
|
if !skip_welcome_message {
|
||||||
@ -589,44 +349,8 @@ pub async fn cli(
|
|||||||
let _ = ansi_term::enable_ansi_support();
|
let _ = ansi_term::enable_ansi_support();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ctrlc")]
|
|
||||||
{
|
|
||||||
let cc = context.ctrl_c.clone();
|
|
||||||
|
|
||||||
ctrlc::set_handler(move || {
|
|
||||||
cc.store(true, Ordering::SeqCst);
|
|
||||||
})
|
|
||||||
.expect("Error setting Ctrl-C handler");
|
|
||||||
}
|
|
||||||
let mut ctrlcbreak = false;
|
let mut ctrlcbreak = false;
|
||||||
|
|
||||||
// before we start up, let's run our startup commands
|
|
||||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
|
||||||
if let Some(commands) = config.get("startup") {
|
|
||||||
match commands {
|
|
||||||
Value {
|
|
||||||
value: UntaggedValue::Table(pipelines),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
for pipeline in pipelines {
|
|
||||||
if let Ok(pipeline_string) = pipeline.as_string() {
|
|
||||||
let _ = run_pipeline_standalone(
|
|
||||||
pipeline_string,
|
|
||||||
false,
|
|
||||||
&mut context,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
println!("warning: expected a table of pipeline strings as startup commands");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if context.ctrl_c.load(Ordering::SeqCst) {
|
if context.ctrl_c.load(Ordering::SeqCst) {
|
||||||
context.ctrl_c.store(false, Ordering::SeqCst);
|
context.ctrl_c.store(false, Ordering::SeqCst);
|
||||||
@ -635,12 +359,8 @@ pub async fn cli(
|
|||||||
|
|
||||||
let cwd = context.shell_manager.path();
|
let cwd = context.shell_manager.path();
|
||||||
|
|
||||||
let hinter = init_hinter(&config);
|
|
||||||
|
|
||||||
rl.set_helper(Some(crate::shell::Helper::new(context.clone(), hinter)));
|
|
||||||
|
|
||||||
let colored_prompt = {
|
let colored_prompt = {
|
||||||
if let Some(prompt) = config.get("prompt") {
|
if let Some(prompt) = configuration.var("prompt") {
|
||||||
let prompt_line = prompt.as_string()?;
|
let prompt_line = prompt.as_string()?;
|
||||||
|
|
||||||
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
|
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
|
||||||
@ -729,9 +449,20 @@ pub async fn cli(
|
|||||||
// Check the config to see if we need to update the path
|
// Check the config to see if we need to update the path
|
||||||
// TODO: make sure config is cached so we don't path this load every call
|
// TODO: make sure config is cached so we don't path this load every call
|
||||||
// FIXME: we probably want to be a bit more graceful if we can't set the environment
|
// FIXME: we probably want to be a bit more graceful if we can't set the environment
|
||||||
syncer.reload();
|
|
||||||
syncer.sync_env_vars(&mut context);
|
context.configure(&configuration, |config, ctx| {
|
||||||
syncer.sync_path_vars(&mut context);
|
if syncer.did_config_change() {
|
||||||
|
syncer.reload();
|
||||||
|
syncer.sync_env_vars(ctx);
|
||||||
|
syncer.sync_path_vars(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(reason) = syncer.autoenv(ctx) {
|
||||||
|
print_err(reason, &Text::from(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = configure_rustyline_editor(&mut rl, config);
|
||||||
|
});
|
||||||
|
|
||||||
match line {
|
match line {
|
||||||
LineResult::Success(line) => {
|
LineResult::Success(line) => {
|
||||||
@ -784,15 +515,279 @@ pub async fn cli(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_hinter(config: &IndexMap<String, Value>) -> Option<rustyline::hint::HistoryHinter> {
|
pub fn register_plugins(context: &mut Context) -> Result<(), ShellError> {
|
||||||
// Show hints unless explicitly disabled in config
|
if let Ok(plugins) = crate::plugin::scan(search_paths()) {
|
||||||
if let Some(line_editor_vars) = config.get("line_editor") {
|
context.add_commands(
|
||||||
|
plugins
|
||||||
|
.into_iter()
|
||||||
|
.filter(|p| !context.is_command_registered(p.name()))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_ctrl_c(_context: &mut Context) -> Result<(), Box<dyn Error>> {
|
||||||
|
#[cfg(feature = "ctrlc")]
|
||||||
|
{
|
||||||
|
let cc = _context.ctrl_c.clone();
|
||||||
|
|
||||||
|
ctrlc::set_handler(move || {
|
||||||
|
cc.store(true, Ordering::SeqCst);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if _context.ctrl_c.load(Ordering::SeqCst) {
|
||||||
|
_context.ctrl_c.store(false, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_startup_commands(
|
||||||
|
context: &mut Context,
|
||||||
|
config: &dyn nu_data::config::Conf,
|
||||||
|
) -> Result<(), ShellError> {
|
||||||
|
if let Some(commands) = config.var("startup") {
|
||||||
|
match commands {
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Table(pipelines),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
for pipeline in pipelines {
|
||||||
|
if let Ok(pipeline_string) = pipeline.as_string() {
|
||||||
|
let _ =
|
||||||
|
run_pipeline_standalone(pipeline_string, false, context, false).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(ShellError::untagged_runtime_error(
|
||||||
|
"expected a table of pipeline strings as startup commands",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_pipeline_standalone(
|
||||||
|
pipeline: String,
|
||||||
|
redirect_stdin: bool,
|
||||||
|
context: &mut Context,
|
||||||
|
exit_on_error: bool,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let line = process_line(Ok(pipeline), context, redirect_stdin, false).await;
|
||||||
|
|
||||||
|
match line {
|
||||||
|
LineResult::Success(line) => {
|
||||||
|
let error_code = {
|
||||||
|
let errors = context.current_errors.clone();
|
||||||
|
let errors = errors.lock();
|
||||||
|
|
||||||
|
if errors.len() > 0 {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
context.maybe_print_errors(Text::from(line));
|
||||||
|
if error_code != 0 && exit_on_error {
|
||||||
|
std::process::exit(error_code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LineResult::Error(line, err) => {
|
||||||
|
context.with_host(|_host| {
|
||||||
|
print_err(err, &Text::from(line.clone()));
|
||||||
|
});
|
||||||
|
|
||||||
|
context.maybe_print_errors(Text::from(line));
|
||||||
|
if exit_on_error {
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||||
|
|
||||||
|
let config = Config::builder().color_mode(ColorMode::Forced).build();
|
||||||
|
let mut rl: Editor<_> = Editor::with_config(config);
|
||||||
|
|
||||||
|
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
|
||||||
|
rl.bind_sequence(
|
||||||
|
KeyPress::ControlLeft,
|
||||||
|
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
|
||||||
|
);
|
||||||
|
rl.bind_sequence(
|
||||||
|
KeyPress::ControlRight,
|
||||||
|
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Let's set the defaults up front and then override them later if the user indicates
|
||||||
|
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
|
||||||
|
rl.set_max_history_size(100);
|
||||||
|
rl.set_history_ignore_dups(true);
|
||||||
|
rl.set_history_ignore_space(false);
|
||||||
|
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
|
||||||
|
rl.set_completion_prompt_limit(100);
|
||||||
|
rl.set_keyseq_timeout(-1);
|
||||||
|
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
|
||||||
|
rl.set_auto_add_history(false);
|
||||||
|
rl.set_bell_style(rustyline::config::BellStyle::default());
|
||||||
|
rl.set_color_mode(rustyline::ColorMode::Enabled);
|
||||||
|
rl.set_tab_stop(8);
|
||||||
|
|
||||||
|
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
|
||||||
|
println!("Error loading keybindings: {:?}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
rl
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_rustyline_editor(
|
||||||
|
rl: &mut Editor<Helper>,
|
||||||
|
config: &dyn nu_data::config::Conf,
|
||||||
|
) -> Result<(), ShellError> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||||
|
|
||||||
|
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||||
|
for (idx, value) in line_editor_vars.row_entries() {
|
||||||
|
match idx.as_ref() {
|
||||||
|
"max_history_size" => {
|
||||||
|
if let Ok(max_history_size) = value.as_u64() {
|
||||||
|
rl.set_max_history_size(max_history_size as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"history_duplicates" => {
|
||||||
|
// history_duplicates = match value.as_string() {
|
||||||
|
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
|
||||||
|
// rustyline::config::HistoryDuplicates::AlwaysAdd
|
||||||
|
// }
|
||||||
|
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
|
||||||
|
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
|
||||||
|
// }
|
||||||
|
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
|
||||||
|
// };
|
||||||
|
if let Ok(history_duplicates) = value.as_bool() {
|
||||||
|
rl.set_history_ignore_dups(history_duplicates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"history_ignore_space" => {
|
||||||
|
if let Ok(history_ignore_space) = value.as_bool() {
|
||||||
|
rl.set_history_ignore_space(history_ignore_space);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"completion_type" => {
|
||||||
|
let completion_type = match value.as_string() {
|
||||||
|
Ok(s) if s.to_lowercase() == "circular" => {
|
||||||
|
rustyline::config::CompletionType::Circular
|
||||||
|
}
|
||||||
|
Ok(s) if s.to_lowercase() == "list" => {
|
||||||
|
rustyline::config::CompletionType::List
|
||||||
|
}
|
||||||
|
#[cfg(all(unix, feature = "with-fuzzy"))]
|
||||||
|
Ok(s) if s.to_lowercase() == "fuzzy" => {
|
||||||
|
rustyline::config::CompletionType::Fuzzy
|
||||||
|
}
|
||||||
|
_ => DEFAULT_COMPLETION_MODE,
|
||||||
|
};
|
||||||
|
rl.set_completion_type(completion_type);
|
||||||
|
}
|
||||||
|
"completion_prompt_limit" => {
|
||||||
|
if let Ok(completion_prompt_limit) = value.as_u64() {
|
||||||
|
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"keyseq_timeout_ms" => {
|
||||||
|
if let Ok(keyseq_timeout_ms) = value.as_u64() {
|
||||||
|
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"edit_mode" => {
|
||||||
|
let edit_mode = match value.as_string() {
|
||||||
|
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
|
||||||
|
Ok(s) if s.to_lowercase() == "emacs" => rustyline::config::EditMode::Emacs,
|
||||||
|
_ => rustyline::config::EditMode::Emacs,
|
||||||
|
};
|
||||||
|
rl.set_edit_mode(edit_mode);
|
||||||
|
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
|
||||||
|
// no matter what you may have configured. This is so that key chords
|
||||||
|
// can be applied without having to do them in a given timeout. So,
|
||||||
|
// it essentially turns off the keyseq timeout.
|
||||||
|
}
|
||||||
|
"auto_add_history" => {
|
||||||
|
if let Ok(auto_add_history) = value.as_bool() {
|
||||||
|
rl.set_auto_add_history(auto_add_history);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"bell_style" => {
|
||||||
|
let bell_style = match value.as_string() {
|
||||||
|
Ok(s) if s.to_lowercase() == "audible" => {
|
||||||
|
rustyline::config::BellStyle::Audible
|
||||||
|
}
|
||||||
|
Ok(s) if s.to_lowercase() == "none" => rustyline::config::BellStyle::None,
|
||||||
|
Ok(s) if s.to_lowercase() == "visible" => {
|
||||||
|
rustyline::config::BellStyle::Visible
|
||||||
|
}
|
||||||
|
_ => rustyline::config::BellStyle::default(),
|
||||||
|
};
|
||||||
|
rl.set_bell_style(bell_style);
|
||||||
|
}
|
||||||
|
"color_mode" => {
|
||||||
|
let color_mode = match value.as_string() {
|
||||||
|
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
|
||||||
|
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
|
||||||
|
Ok(s) if s.to_lowercase() == "disabled" => rustyline::ColorMode::Disabled,
|
||||||
|
_ => rustyline::ColorMode::Enabled,
|
||||||
|
};
|
||||||
|
rl.set_color_mode(color_mode);
|
||||||
|
}
|
||||||
|
"tab_stop" => {
|
||||||
|
if let Ok(tab_stop) = value.as_u64() {
|
||||||
|
rl.set_tab_stop(tab_stop as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nu_line_editor_helper(
|
||||||
|
context: &mut Context,
|
||||||
|
config: &dyn nu_data::config::Conf,
|
||||||
|
) -> crate::shell::Helper {
|
||||||
|
let hinter = rustyline_hinter(config);
|
||||||
|
crate::shell::Helper::new(context.clone(), hinter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rustyline_hinter(config: &dyn nu_data::config::Conf) -> Option<rustyline::hint::HistoryHinter> {
|
||||||
|
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||||
for (idx, value) in line_editor_vars.row_entries() {
|
for (idx, value) in line_editor_vars.row_entries() {
|
||||||
if idx == "show_hints" && value.expect_string() == "false" {
|
if idx == "show_hints" && value.expect_string() == "false" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(rustyline::hint::HistoryHinter {})
|
Some(rustyline::hint::HistoryHinter {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ pub trait ConfigExtensions: Debug + Send {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn pivot_mode(config: &NuConfig) -> AutoPivotMode {
|
pub fn pivot_mode(config: &NuConfig) -> AutoPivotMode {
|
||||||
let vars = config.vars.lock();
|
let vars = &config.vars;
|
||||||
|
|
||||||
if let Some(mode) = vars.get("pivot_mode") {
|
if let Some(mode) = vars.get("pivot_mode") {
|
||||||
let mode = match mode.as_string() {
|
let mode = match mode.as_string() {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::WholeStreamCommand;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_data::config::NuConfig;
|
use nu_data::config::{Conf, NuConfig};
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@ -9,9 +9,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
const DEFAULT_LOCATION: &str = "history.txt";
|
const DEFAULT_LOCATION: &str = "history.txt";
|
||||||
|
|
||||||
pub fn history_path(config: &NuConfig) -> PathBuf {
|
pub fn history_path(config: &dyn Conf) -> PathBuf {
|
||||||
let vars = config.vars.lock();
|
|
||||||
|
|
||||||
let default_path = nu_data::config::user_data()
|
let default_path = nu_data::config::user_data()
|
||||||
.map(|mut p| {
|
.map(|mut p| {
|
||||||
p.push(DEFAULT_LOCATION);
|
p.push(DEFAULT_LOCATION);
|
||||||
@ -19,7 +17,8 @@ pub fn history_path(config: &NuConfig) -> PathBuf {
|
|||||||
})
|
})
|
||||||
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
|
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
|
||||||
|
|
||||||
vars.get("history-path")
|
config
|
||||||
|
.var("history-path")
|
||||||
.map_or(default_path.clone(), |custom_path| {
|
.map_or(default_path.clone(), |custom_path| {
|
||||||
match custom_path.as_string() {
|
match custom_path.as_string() {
|
||||||
Ok(path) => PathBuf::from(path),
|
Ok(path) => PathBuf::from(path),
|
||||||
@ -54,7 +53,7 @@ impl WholeStreamCommand for History {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||||
let config = NuConfig::new();
|
let config: Box<dyn Conf> = Box::new(NuConfig::new());
|
||||||
let tag = args.call_info.name_tag;
|
let tag = args.call_info.name_tag;
|
||||||
let path = history_path(&config);
|
let path = history_path(&config);
|
||||||
let file = File::open(path);
|
let file = File::open(path);
|
||||||
|
@ -27,7 +27,7 @@ pub fn header_alignment_from_value(align_value: Option<&Value>) -> nu_table::Ali
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_color_from_key_and_subkey(config: &NuConfig, key: &str, subkey: &str) -> Option<Value> {
|
pub fn get_color_from_key_and_subkey(config: &NuConfig, key: &str, subkey: &str) -> Option<Value> {
|
||||||
let vars = config.vars.lock();
|
let vars = &config.vars;
|
||||||
|
|
||||||
if let Some(config_vars) = vars.get(key) {
|
if let Some(config_vars) = vars.get(key) {
|
||||||
for (kee, value) in config_vars.row_entries() {
|
for (kee, value) in config_vars.row_entries() {
|
||||||
@ -47,7 +47,7 @@ pub fn header_bold_from_value(bold_value: Option<&Value>) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn table_mode(config: &NuConfig) -> nu_table::Theme {
|
pub fn table_mode(config: &NuConfig) -> nu_table::Theme {
|
||||||
let vars = config.vars.lock();
|
let vars = &config.vars;
|
||||||
|
|
||||||
vars.get("table_mode")
|
vars.get("table_mode")
|
||||||
.map_or(nu_table::Theme::compact(), |mode| match mode.as_string() {
|
.map_or(nu_table::Theme::compact(), |mode| match mode.as_string() {
|
||||||
@ -62,7 +62,7 @@ pub fn table_mode(config: &NuConfig) -> nu_table::Theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn disabled_indexes(config: &NuConfig) -> bool {
|
pub fn disabled_indexes(config: &NuConfig) -> bool {
|
||||||
let vars = config.vars.lock();
|
let vars = &config.vars;
|
||||||
|
|
||||||
vars.get("disable_table_indexes")
|
vars.get("disable_table_indexes")
|
||||||
.map_or(false, |x| x.as_bool().unwrap_or(false))
|
.map_or(false, |x| x.as_bool().unwrap_or(false))
|
||||||
|
@ -210,6 +210,14 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn configure<T>(
|
||||||
|
&mut self,
|
||||||
|
config: &dyn nu_data::config::Conf,
|
||||||
|
block: impl FnOnce(&dyn nu_data::config::Conf, &mut Self) -> T,
|
||||||
|
) {
|
||||||
|
block(config, &mut *self);
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn with_host<T>(&mut self, block: impl FnOnce(&mut dyn Host) -> T) -> T {
|
pub(crate) fn with_host<T>(&mut self, block: impl FnOnce(&mut dyn Host) -> T) -> T {
|
||||||
let mut host = self.host.lock();
|
let mut host = self.host.lock();
|
||||||
|
|
||||||
|
152
crates/nu-cli/src/env/environment_syncer.rs
vendored
152
crates/nu-cli/src/env/environment_syncer.rs
vendored
@ -1,14 +1,13 @@
|
|||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use nu_data::config::{Conf, NuConfig};
|
|
||||||
|
|
||||||
use crate::env::environment::{Env, Environment};
|
use crate::env::environment::{Env, Environment};
|
||||||
use nu_source::Text;
|
use nu_data::config::{Conf, NuConfig};
|
||||||
|
use nu_errors::ShellError;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct EnvironmentSyncer {
|
pub struct EnvironmentSyncer {
|
||||||
pub env: Arc<Mutex<Box<Environment>>>,
|
pub env: Arc<Mutex<Box<Environment>>>,
|
||||||
pub config: Arc<Box<dyn Conf>>,
|
pub config: Arc<Mutex<Box<dyn Conf>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for EnvironmentSyncer {
|
impl Default for EnvironmentSyncer {
|
||||||
@ -18,42 +17,60 @@ impl Default for EnvironmentSyncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EnvironmentSyncer {
|
impl EnvironmentSyncer {
|
||||||
|
pub fn with_config(config: Box<dyn Conf>) -> Self {
|
||||||
|
EnvironmentSyncer {
|
||||||
|
env: Arc::new(Mutex::new(Box::new(Environment::new()))),
|
||||||
|
config: Arc::new(Mutex::new(config)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new() -> EnvironmentSyncer {
|
pub fn new() -> EnvironmentSyncer {
|
||||||
EnvironmentSyncer {
|
EnvironmentSyncer {
|
||||||
env: Arc::new(Mutex::new(Box::new(Environment::new()))),
|
env: Arc::new(Mutex::new(Box::new(Environment::new()))),
|
||||||
config: Arc::new(Box::new(NuConfig::new())),
|
config: Arc::new(Mutex::new(Box::new(NuConfig::new()))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn set_config(&mut self, config: Box<dyn Conf>) {
|
pub fn set_config(&mut self, config: Box<dyn Conf>) {
|
||||||
self.config = Arc::new(config);
|
self.config = Arc::new(Mutex::new(config));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_config(&self) -> Box<dyn Conf> {
|
pub fn get_config(&self) -> Box<dyn Conf> {
|
||||||
self.config.clone().clone_box()
|
let config = self.config.lock();
|
||||||
|
|
||||||
|
config.clone_box()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_environment(&mut self) {
|
pub fn load_environment(&mut self) {
|
||||||
let config = self.config.clone();
|
let config = self.config.lock();
|
||||||
|
|
||||||
self.env = Arc::new(Mutex::new(Box::new(Environment::from_config(&*config))));
|
self.env = Arc::new(Mutex::new(Box::new(Environment::from_config(&*config))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn did_config_change(&mut self) -> bool {
|
||||||
|
let config = self.config.lock();
|
||||||
|
config.is_modified().unwrap_or_else(|_| false)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reload(&mut self) {
|
pub fn reload(&mut self) {
|
||||||
self.config.reload();
|
let mut config = self.config.lock();
|
||||||
|
config.reload();
|
||||||
|
|
||||||
let mut environment = self.env.lock();
|
let mut environment = self.env.lock();
|
||||||
environment.morph(&*self.config);
|
environment.morph(&*config);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn autoenv(&self, ctx: &mut Context) -> Result<(), ShellError> {
|
||||||
|
let mut environment = self.env.lock();
|
||||||
|
let auto = environment.autoenv(ctx.user_recently_used_autoenv_untrust);
|
||||||
|
ctx.user_recently_used_autoenv_untrust = false;
|
||||||
|
auto
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sync_env_vars(&mut self, ctx: &mut Context) {
|
pub fn sync_env_vars(&mut self, ctx: &mut Context) {
|
||||||
let mut environment = self.env.lock();
|
let mut environment = self.env.lock();
|
||||||
|
|
||||||
if let Err(e) = environment.autoenv(ctx.user_recently_used_autoenv_untrust) {
|
|
||||||
crate::cli::print_err(e, &Text::from(""));
|
|
||||||
}
|
|
||||||
ctx.user_recently_used_autoenv_untrust = false;
|
|
||||||
if environment.env().is_some() {
|
if environment.env().is_some() {
|
||||||
for (name, value) in ctx.with_host(|host| host.vars()) {
|
for (name, value) in ctx.with_host(|host| host.vars()) {
|
||||||
if name != "path" && name != "PATH" {
|
if name != "path" && name != "PATH" {
|
||||||
@ -142,6 +159,113 @@ mod tests {
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
// This test fails on Linux.
|
||||||
|
// It's possible it has something to do with the fake configuration
|
||||||
|
// TODO: More tests.
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
#[test]
|
||||||
|
fn syncs_env_if_new_env_entry_is_added_to_an_existing_configuration() -> Result<(), ShellError>
|
||||||
|
{
|
||||||
|
let mut ctx = Context::basic()?;
|
||||||
|
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
|
||||||
|
|
||||||
|
let mut expected = IndexMap::new();
|
||||||
|
expected.insert(
|
||||||
|
"SHELL".to_string(),
|
||||||
|
"/usr/bin/you_already_made_the_nu_choice".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Playground::setup("syncs_env_from_config_updated_test_1", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![
|
||||||
|
FileWithContent(
|
||||||
|
"configuration.toml",
|
||||||
|
r#"
|
||||||
|
[env]
|
||||||
|
SHELL = "/usr/bin/you_already_made_the_nu_choice"
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
FileWithContent(
|
||||||
|
"updated_configuration.toml",
|
||||||
|
r#"
|
||||||
|
[env]
|
||||||
|
SHELL = "/usr/bin/you_already_made_the_nu_choice"
|
||||||
|
USER = "NUNO"
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let file = dirs.test().join("configuration.toml");
|
||||||
|
let new_file = dirs.test().join("updated_configuration.toml");
|
||||||
|
|
||||||
|
let fake_config = FakeConfig::new(&file);
|
||||||
|
let mut actual = EnvironmentSyncer::with_config(Box::new(fake_config.clone()));
|
||||||
|
|
||||||
|
// Here, the environment variables from the current session
|
||||||
|
// are cleared since we will load and set them from the
|
||||||
|
// configuration file
|
||||||
|
actual.clear_env_vars(&mut ctx);
|
||||||
|
|
||||||
|
// Nu loads the environment variables from the configuration file
|
||||||
|
actual.load_environment();
|
||||||
|
actual.sync_env_vars(&mut ctx);
|
||||||
|
|
||||||
|
{
|
||||||
|
let environment = actual.env.lock();
|
||||||
|
let mut vars = IndexMap::new();
|
||||||
|
environment
|
||||||
|
.env()
|
||||||
|
.expect("No variables in the environment.")
|
||||||
|
.row_entries()
|
||||||
|
.for_each(|(name, value)| {
|
||||||
|
vars.insert(
|
||||||
|
name.to_string(),
|
||||||
|
value.as_string().expect("Couldn't convert to string"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
for k in expected.keys() {
|
||||||
|
assert!(vars.contains_key(k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(!actual.did_config_change());
|
||||||
|
|
||||||
|
// Replacing the newer configuration file to the existing one.
|
||||||
|
let new_config_contents = std::fs::read_to_string(new_file).expect("Failed");
|
||||||
|
std::fs::write(&file, &new_config_contents).expect("Failed");
|
||||||
|
|
||||||
|
// A change has happened
|
||||||
|
assert!(actual.did_config_change());
|
||||||
|
|
||||||
|
// Syncer should reload and add new envs
|
||||||
|
actual.reload();
|
||||||
|
actual.sync_env_vars(&mut ctx);
|
||||||
|
|
||||||
|
expected.insert("USER".to_string(), "NUNO".to_string());
|
||||||
|
|
||||||
|
{
|
||||||
|
let environment = actual.env.lock();
|
||||||
|
let mut vars = IndexMap::new();
|
||||||
|
environment
|
||||||
|
.env()
|
||||||
|
.expect("No variables in the environment.")
|
||||||
|
.row_entries()
|
||||||
|
.for_each(|(name, value)| {
|
||||||
|
vars.insert(
|
||||||
|
name.to_string(),
|
||||||
|
value.as_string().expect("Couldn't convert to string"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
for k in expected.keys() {
|
||||||
|
assert!(vars.contains_key(k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn syncs_env_if_new_env_entry_in_session_is_not_in_configuration_file() -> Result<(), ShellError>
|
fn syncs_env_if_new_env_entry_in_session_is_not_in_configuration_file() -> Result<(), ShellError>
|
||||||
{
|
{
|
||||||
|
@ -47,7 +47,8 @@ pub fn nu(env: &IndexMap<String, String>, tag: impl Into<Tag>) -> Result<Value,
|
|||||||
UntaggedValue::path(keybinding_path).into_value(&tag),
|
UntaggedValue::path(keybinding_path).into_value(&tag),
|
||||||
);
|
);
|
||||||
|
|
||||||
let history = crate::commands::history::history_path(&nu_data::config::NuConfig::new());
|
let config: Box<dyn nu_data::config::Conf> = Box::new(nu_data::config::NuConfig::new());
|
||||||
|
let history = crate::commands::history::history_path(&config);
|
||||||
nu_dict.insert_value(
|
nu_dict.insert_value(
|
||||||
"history-path",
|
"history-path",
|
||||||
UntaggedValue::path(history).into_value(&tag),
|
UntaggedValue::path(history).into_value(&tag),
|
||||||
|
@ -204,6 +204,33 @@ pub fn user_data() -> Result<PathBuf, ShellError> {
|
|||||||
Ok(std::path::PathBuf::from("/"))
|
Ok(std::path::PathBuf::from("/"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Status {
|
||||||
|
LastModified(std::time::SystemTime),
|
||||||
|
Unavailable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Status {
|
||||||
|
fn default() -> Self {
|
||||||
|
Status::Unavailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn last_modified(at: &Option<PathBuf>) -> Result<Status, Box<dyn std::error::Error>> {
|
||||||
|
let filename = default_path()?;
|
||||||
|
|
||||||
|
let filename = match at {
|
||||||
|
None => filename,
|
||||||
|
Some(ref file) => file.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(time) = filename.metadata()?.modified() {
|
||||||
|
return Ok(Status::LastModified(time));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Status::Unavailable)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read(
|
pub fn read(
|
||||||
tag: impl Into<Tag>,
|
tag: impl Into<Tag>,
|
||||||
at: &Option<PathBuf>,
|
at: &Option<PathBuf>,
|
||||||
|
@ -2,13 +2,23 @@ use nu_protocol::Value;
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
pub trait Conf: Debug + Send {
|
pub trait Conf: Debug + Send {
|
||||||
|
fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>>;
|
||||||
|
fn var(&self, key: &str) -> Option<Value>;
|
||||||
fn env(&self) -> Option<Value>;
|
fn env(&self) -> Option<Value>;
|
||||||
fn path(&self) -> Option<Value>;
|
fn path(&self) -> Option<Value>;
|
||||||
fn reload(&self);
|
fn reload(&mut self);
|
||||||
fn clone_box(&self) -> Box<dyn Conf>;
|
fn clone_box(&self) -> Box<dyn Conf>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Conf for Box<dyn Conf> {
|
impl Conf for Box<dyn Conf> {
|
||||||
|
fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
|
(**self).is_modified()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn var(&self, key: &str) -> Option<Value> {
|
||||||
|
(**self).var(key)
|
||||||
|
}
|
||||||
|
|
||||||
fn env(&self) -> Option<Value> {
|
fn env(&self) -> Option<Value> {
|
||||||
(**self).env()
|
(**self).env()
|
||||||
}
|
}
|
||||||
@ -17,7 +27,7 @@ impl Conf for Box<dyn Conf> {
|
|||||||
(**self).path()
|
(**self).path()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reload(&self) {
|
fn reload(&mut self) {
|
||||||
(**self).reload();
|
(**self).reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
use crate::config::{read, Conf};
|
use crate::config::{last_modified, read, Conf, Status};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use nu_protocol::Value;
|
use nu_protocol::Value;
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
use parking_lot::Mutex;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct NuConfig {
|
pub struct NuConfig {
|
||||||
pub vars: Arc<Mutex<IndexMap<String, Value>>>,
|
pub vars: IndexMap<String, Value>,
|
||||||
|
pub modified_at: Status,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Conf for NuConfig {
|
impl Conf for NuConfig {
|
||||||
|
fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
|
self.is_modified()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn var(&self, key: &str) -> Option<Value> {
|
||||||
|
self.var(key)
|
||||||
|
}
|
||||||
|
|
||||||
fn env(&self) -> Option<Value> {
|
fn env(&self) -> Option<Value> {
|
||||||
self.env()
|
self.env()
|
||||||
}
|
}
|
||||||
@ -20,11 +27,17 @@ impl Conf for NuConfig {
|
|||||||
self.path()
|
self.path()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reload(&self) {
|
fn reload(&mut self) {
|
||||||
let mut vars = self.vars.lock();
|
let vars = &mut self.vars;
|
||||||
|
|
||||||
if let Ok(variables) = read(Tag::unknown(), &None) {
|
if let Ok(variables) = read(Tag::unknown(), &None) {
|
||||||
vars.extend(variables);
|
vars.extend(variables);
|
||||||
|
|
||||||
|
self.modified_at = if let Ok(status) = last_modified(&None) {
|
||||||
|
status
|
||||||
|
} else {
|
||||||
|
Status::Unavailable
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,6 +47,24 @@ impl Conf for NuConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl NuConfig {
|
impl NuConfig {
|
||||||
|
pub fn with(config_file: Option<std::path::PathBuf>) -> NuConfig {
|
||||||
|
match &config_file {
|
||||||
|
None => NuConfig::new(),
|
||||||
|
Some(_) => {
|
||||||
|
let vars = if let Ok(variables) = read(Tag::unknown(), &config_file) {
|
||||||
|
variables
|
||||||
|
} else {
|
||||||
|
IndexMap::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
NuConfig {
|
||||||
|
vars,
|
||||||
|
modified_at: NuConfig::get_last_modified(&config_file),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new() -> NuConfig {
|
pub fn new() -> NuConfig {
|
||||||
let vars = if let Ok(variables) = read(Tag::unknown(), &None) {
|
let vars = if let Ok(variables) = read(Tag::unknown(), &None) {
|
||||||
variables
|
variables
|
||||||
@ -42,12 +73,45 @@ impl NuConfig {
|
|||||||
};
|
};
|
||||||
|
|
||||||
NuConfig {
|
NuConfig {
|
||||||
vars: Arc::new(Mutex::new(vars)),
|
vars,
|
||||||
|
modified_at: NuConfig::get_last_modified(&None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_last_modified(config_file: &Option<std::path::PathBuf>) -> Status {
|
||||||
|
if let Ok(status) = last_modified(config_file) {
|
||||||
|
status
|
||||||
|
} else {
|
||||||
|
Status::Unavailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
|
let modified_at = &self.modified_at;
|
||||||
|
|
||||||
|
Ok(match (NuConfig::get_last_modified(&None), modified_at) {
|
||||||
|
(Status::LastModified(left), Status::LastModified(right)) => {
|
||||||
|
let left = left.duration_since(std::time::UNIX_EPOCH)?;
|
||||||
|
let right = (*right).duration_since(std::time::UNIX_EPOCH)?;
|
||||||
|
|
||||||
|
left != right
|
||||||
|
}
|
||||||
|
(_, _) => false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn var(&self, key: &str) -> Option<Value> {
|
||||||
|
let vars = &self.vars;
|
||||||
|
|
||||||
|
if let Some(value) = vars.get(key) {
|
||||||
|
return Some(value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn env(&self) -> Option<Value> {
|
pub fn env(&self) -> Option<Value> {
|
||||||
let vars = self.vars.lock();
|
let vars = &self.vars;
|
||||||
|
|
||||||
if let Some(env_vars) = vars.get("env") {
|
if let Some(env_vars) = vars.get("env") {
|
||||||
return Some(env_vars.clone());
|
return Some(env_vars.clone());
|
||||||
@ -57,7 +121,7 @@ impl NuConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn path(&self) -> Option<Value> {
|
pub fn path(&self) -> Option<Value> {
|
||||||
let vars = self.vars.lock();
|
let vars = &self.vars;
|
||||||
|
|
||||||
if let Some(env_vars) = vars.get("path") {
|
if let Some(env_vars) = vars.get("path") {
|
||||||
return Some(env_vars.clone());
|
return Some(env_vars.clone());
|
||||||
|
@ -1,17 +1,22 @@
|
|||||||
use crate::config::{read, Conf, NuConfig};
|
use crate::config::{Conf, NuConfig, Status};
|
||||||
use indexmap::IndexMap;
|
|
||||||
use nu_protocol::Value;
|
use nu_protocol::Value;
|
||||||
use nu_source::Tag;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FakeConfig {
|
pub struct FakeConfig {
|
||||||
pub config: NuConfig,
|
pub config: NuConfig,
|
||||||
|
source_file: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Conf for FakeConfig {
|
impl Conf for FakeConfig {
|
||||||
|
fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
|
self.is_modified()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn var(&self, key: &str) -> Option<Value> {
|
||||||
|
self.config.var(key)
|
||||||
|
}
|
||||||
|
|
||||||
fn env(&self) -> Option<Value> {
|
fn env(&self) -> Option<Value> {
|
||||||
self.config.env()
|
self.config.env()
|
||||||
}
|
}
|
||||||
@ -20,8 +25,8 @@ impl Conf for FakeConfig {
|
|||||||
self.config.path()
|
self.config.path()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reload(&self) {
|
fn reload(&mut self) {
|
||||||
// no-op
|
self.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone_box(&self) -> Box<dyn Conf> {
|
fn clone_box(&self) -> Box<dyn Conf> {
|
||||||
@ -31,18 +36,31 @@ impl Conf for FakeConfig {
|
|||||||
|
|
||||||
impl FakeConfig {
|
impl FakeConfig {
|
||||||
pub fn new(config_file: &Path) -> FakeConfig {
|
pub fn new(config_file: &Path) -> FakeConfig {
|
||||||
let config_file = PathBuf::from(config_file);
|
let config_file = Some(PathBuf::from(config_file));
|
||||||
|
|
||||||
let vars = if let Ok(variables) = read(Tag::unknown(), &Some(config_file)) {
|
|
||||||
variables
|
|
||||||
} else {
|
|
||||||
IndexMap::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
FakeConfig {
|
FakeConfig {
|
||||||
config: NuConfig {
|
config: NuConfig::with(config_file.clone()),
|
||||||
vars: Arc::new(Mutex::new(vars)),
|
source_file: config_file,
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
|
let modified_at = &self.config.modified_at;
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
match (NuConfig::get_last_modified(&self.source_file), modified_at) {
|
||||||
|
(Status::LastModified(left), Status::LastModified(right)) => {
|
||||||
|
let left = left.duration_since(std::time::UNIX_EPOCH)?;
|
||||||
|
let right = (*right).duration_since(std::time::UNIX_EPOCH)?;
|
||||||
|
|
||||||
|
left != right
|
||||||
|
}
|
||||||
|
(_, _) => false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reload(&mut self) {
|
||||||
|
self.config = NuConfig::with(self.source_file.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
|
use nu_cli::create_default_context;
|
||||||
use nu_cli::utils::test_bins as binaries;
|
use nu_cli::utils::test_bins as binaries;
|
||||||
use nu_cli::{create_default_context, EnvironmentSyncer};
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{prelude::*, BufReader};
|
use std::io::{prelude::*, BufReader};
|
||||||
@ -160,14 +160,13 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
None => {
|
None => {
|
||||||
let mut syncer = EnvironmentSyncer::new();
|
let mut context = create_default_context(true)?;
|
||||||
let mut context = create_default_context(&mut syncer, true)?;
|
|
||||||
|
|
||||||
if !matches.is_present("skip-plugins") {
|
if !matches.is_present("skip-plugins") {
|
||||||
let _ = nu_cli::register_plugins(&mut context);
|
let _ = nu_cli::register_plugins(&mut context);
|
||||||
}
|
}
|
||||||
|
|
||||||
futures::executor::block_on(nu_cli::cli(syncer, context))?;
|
futures::executor::block_on(nu_cli::cli(context))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user