From 1a16b9a2c4d3cb263fa5f0a11a4d41210f2cd300 Mon Sep 17 00:00:00 2001 From: Charles Dixon Date: Wed, 16 Mar 2022 18:17:06 +0000 Subject: [PATCH] Move repl loop and command/script execution to nu_cli (#4846) * Refactor usage of is_perf_true to be a parameter passed around * Move repl loop and command/script execution to nu_cli * Move config setup out of nu_cli * Update config_files.rs * Update main.rs Co-authored-by: JT <547158+jntrnr@users.noreply.github.com> --- Cargo.lock | 2 + Cargo.toml | 2 +- crates/nu-cli/Cargo.toml | 5 + {src => crates/nu-cli/src}/commands.rs | 39 +- crates/nu-cli/src/config_files.rs | 84 ++++ {src => crates/nu-cli/src}/eval_file.rs | 42 +- crates/nu-cli/src/lib.rs | 16 + {src => crates/nu-cli/src}/prompt_update.rs | 33 +- {src => crates/nu-cli/src}/reedline_config.rs | 0 {src => crates/nu-cli/src}/repl.rs | 93 ++-- crates/nu-cli/src/util.rs | 422 +++++++++++++++++ src/config_files.rs | 78 +--- src/main.rs | 76 +++- src/utils.rs | 425 ------------------ 14 files changed, 696 insertions(+), 621 deletions(-) rename {src => crates/nu-cli/src}/commands.rs (74%) create mode 100644 crates/nu-cli/src/config_files.rs rename {src => crates/nu-cli/src}/eval_file.rs (79%) rename {src => crates/nu-cli/src}/prompt_update.rs (89%) rename {src => crates/nu-cli/src}/reedline_config.rs (100%) rename {src => crates/nu-cli/src}/repl.rs (86%) delete mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 2eaa2a4a6..a92419017 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2196,6 +2196,8 @@ dependencies = [ name = "nu-cli" version = "0.59.1" dependencies = [ + "crossterm", + "crossterm_winapi", "is_executable", "log", "miette", diff --git a/Cargo.toml b/Cargo.toml index 427d386c5..246369cd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ itertools = "0.10.3" embed-resource = "1" [features] -plugin = ["nu-plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"] +plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"] default = ["plugin", "which", "zip-support", "trash-support"] stable = ["default"] extra = ["default", "dataframe"] diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 533463ad5..75028230f 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -13,9 +13,14 @@ nu-ansi-term = "0.42.0" nu-color-config = { path = "../nu-color-config" } +crossterm = "0.23.0" +crossterm_winapi = "0.9.0" miette = { version = "4.1.0", features = ["fancy"] } thiserror = "1.0.29" reedline = { git = "https://github.com/nushell/reedline", branch = "main" } log = "0.4" is_executable = "1.0.1" + +[features] +plugin = [] diff --git a/src/commands.rs b/crates/nu-cli/src/commands.rs similarity index 74% rename from src/commands.rs rename to crates/nu-cli/src/commands.rs index 540131227..850ac6e71 100644 --- a/src/commands.rs +++ b/crates/nu-cli/src/commands.rs @@ -1,24 +1,23 @@ -use crate::is_perf_true; -use crate::utils::{gather_parent_env_vars, report_error}; +use crate::util::report_error; use log::info; use miette::Result; use nu_engine::{convert_env_values, eval_block}; use nu_parser::{parse, trim_quotes}; +use nu_protocol::engine::Stack; use nu_protocol::{ engine::{EngineState, StateDelta, StateWorkingSet}, - Config, PipelineData, Span, Spanned, Value, CONFIG_VARIABLE_ID, + Config, PipelineData, Spanned, }; use std::path::Path; -pub(crate) fn evaluate( +pub fn evaluate_commands( commands: &Spanned, init_cwd: &Path, engine_state: &mut EngineState, + stack: &mut Stack, input: PipelineData, + is_perf_true: bool, ) -> Result<()> { - // First, set up env vars as strings only - gather_parent_env_vars(engine_state); - // Run a command (or commands) given to us by the user let (block, delta) = { let mut working_set = StateWorkingSet::new(engine_state); @@ -47,18 +46,6 @@ pub(crate) fn evaluate( report_error(&working_set, &err); } - let mut stack = nu_protocol::engine::Stack::new(); - - // Set up our initial config to start from - stack.vars.insert( - CONFIG_VARIABLE_ID, - Value::Record { - cols: vec![], - vals: vec![], - span: Span { start: 0, end: 0 }, - }, - ); - let config = match stack.get_config() { Ok(config) => config, Err(e) => { @@ -70,13 +57,13 @@ pub(crate) fn evaluate( }; // Make a note of the exceptions we see for externals that look like math expressions - let exceptions = crate::utils::external_exceptions(engine_state, &stack); + let exceptions = crate::util::external_exceptions(engine_state, stack); engine_state.external_exceptions = exceptions; // Merge the delta in case env vars changed in the config - match nu_engine::env::current_dir(engine_state, &stack) { + match nu_engine::env::current_dir(engine_state, stack) { Ok(cwd) => { - if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(&mut stack), cwd) { + if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(stack), cwd) { let working_set = StateWorkingSet::new(engine_state); report_error(&working_set, &e); std::process::exit(1); @@ -90,15 +77,15 @@ pub(crate) fn evaluate( } // Translate environment variables from Strings to Values - if let Some(e) = convert_env_values(engine_state, &stack) { + if let Some(e) = convert_env_values(engine_state, stack) { let working_set = StateWorkingSet::new(engine_state); report_error(&working_set, &e); std::process::exit(1); } - match eval_block(engine_state, &mut stack, &block, input, false, false) { + match eval_block(engine_state, stack, &block, input, false, false) { Ok(pipeline_data) => { - crate::eval_file::print_table_or_error(engine_state, &mut stack, pipeline_data, &config) + crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &config) } Err(err) => { let working_set = StateWorkingSet::new(engine_state); @@ -108,7 +95,7 @@ pub(crate) fn evaluate( } } - if is_perf_true() { + if is_perf_true { info!("evaluate {}:{}:{}", file!(), line!(), column!()); } diff --git a/crates/nu-cli/src/config_files.rs b/crates/nu-cli/src/config_files.rs new file mode 100644 index 000000000..676beff88 --- /dev/null +++ b/crates/nu-cli/src/config_files.rs @@ -0,0 +1,84 @@ +use crate::util::{eval_source, report_error}; +#[cfg(feature = "plugin")] +use log::info; +use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet}; +use nu_protocol::{PipelineData, Span}; +use std::path::PathBuf; + +#[cfg(feature = "plugin")] +const PLUGIN_FILE: &str = "plugin.nu"; + +#[cfg(feature = "plugin")] +pub fn read_plugin_file( + engine_state: &mut EngineState, + stack: &mut Stack, + storage_path: &str, + is_perf_true: bool, +) { + // Reading signatures from signature file + // The plugin.nu file stores the parsed signature collected from each registered plugin + add_plugin_file(engine_state, storage_path); + + let plugin_path = engine_state.plugin_signatures.clone(); + if let Some(plugin_path) = plugin_path { + let plugin_filename = plugin_path.to_string_lossy().to_owned(); + + if let Ok(contents) = std::fs::read(&plugin_path) { + eval_source( + engine_state, + stack, + &contents, + &plugin_filename, + PipelineData::new(Span::new(0, 0)), + ); + } + } + + if is_perf_true { + info!("read_plugin_file {}:{}:{}", file!(), line!(), column!()); + } +} + +#[cfg(feature = "plugin")] +pub fn add_plugin_file(engine_state: &mut EngineState, storage_path: &str) { + if let Some(mut plugin_path) = nu_path::config_dir() { + // Path to store plugins signatures + plugin_path.push(storage_path); + plugin_path.push(PLUGIN_FILE); + engine_state.plugin_signatures = Some(plugin_path.clone()); + } +} + +pub fn eval_config_contents( + config_path: PathBuf, + engine_state: &mut EngineState, + stack: &mut Stack, +) { + if config_path.exists() & config_path.is_file() { + let config_filename = config_path.to_string_lossy().to_owned(); + + if let Ok(contents) = std::fs::read(&config_path) { + eval_source( + engine_state, + stack, + &contents, + &config_filename, + PipelineData::new(Span::new(0, 0)), + ); + + // Merge the delta in case env vars changed in the config + match nu_engine::env::current_dir(engine_state, stack) { + Ok(cwd) => { + if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(stack), cwd) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + } + } + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + } + } + } + } +} diff --git a/src/eval_file.rs b/crates/nu-cli/src/eval_file.rs similarity index 79% rename from src/eval_file.rs rename to crates/nu-cli/src/eval_file.rs index 1f879c408..871fa6244 100644 --- a/src/eval_file.rs +++ b/crates/nu-cli/src/eval_file.rs @@ -1,5 +1,4 @@ -use crate::is_perf_true; -use crate::utils::{eval_source, gather_parent_env_vars, report_error}; +use crate::util::{eval_source, report_error}; use log::info; use log::trace; use miette::{IntoDiagnostic, Result}; @@ -8,41 +7,28 @@ use nu_parser::parse; use nu_protocol::{ ast::Call, engine::{EngineState, Stack, StateWorkingSet}, - Config, PipelineData, Span, Value, CONFIG_VARIABLE_ID, + Config, PipelineData, Span, Value, }; use std::io::Write; /// Main function used when a file path is found as argument for nu -pub(crate) fn evaluate( +pub fn evaluate_file( path: String, args: &[String], engine_state: &mut EngineState, + stack: &mut Stack, input: PipelineData, + is_perf_true: bool, ) -> Result<()> { - // First, set up env vars as strings only - gather_parent_env_vars(engine_state); - - let mut stack = nu_protocol::engine::Stack::new(); - - // Set up our initial config to start from - stack.vars.insert( - CONFIG_VARIABLE_ID, - Value::Record { - cols: vec![], - vals: vec![], - span: Span { start: 0, end: 0 }, - }, - ); - // Translate environment variables from Strings to Values - if let Some(e) = convert_env_values(engine_state, &stack) { + if let Some(e) = convert_env_values(engine_state, stack) { let working_set = StateWorkingSet::new(engine_state); report_error(&working_set, &e); std::process::exit(1); } // Make a note of the exceptions we see for externals that look like math expressions - let exceptions = crate::utils::external_exceptions(engine_state, &stack); + let exceptions = crate::util::external_exceptions(engine_state, stack); engine_state.external_exceptions = exceptions; let file = std::fs::read(&path).into_diagnostic()?; @@ -57,27 +43,21 @@ pub(crate) fn evaluate( if !eval_source( engine_state, - &mut stack, + stack, &file, &path, PipelineData::new(Span::new(0, 0)), ) { std::process::exit(1); } - if !eval_source( - engine_state, - &mut stack, - args.as_bytes(), - "", - input, - ) { + if !eval_source(engine_state, stack, args.as_bytes(), "", input) { std::process::exit(1); } - } else if !eval_source(engine_state, &mut stack, &file, &path, input) { + } else if !eval_source(engine_state, stack, &file, &path, input) { std::process::exit(1); } - if is_perf_true() { + if is_perf_true { info!("evaluate {}:{}:{}", file!(), line!(), column!()); } diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index 47fa86a85..d251ce10d 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -1,17 +1,33 @@ +mod commands; mod completions; +mod config_files; mod errors; +mod eval_file; mod nu_highlight; mod print; mod prompt; +mod prompt_update; +mod reedline_config; +mod repl; mod syntax_highlight; mod util; mod validation; +pub use commands::evaluate_commands; pub use completions::NuCompleter; +pub use config_files::eval_config_contents; pub use errors::CliError; +pub use eval_file::evaluate_file; pub use nu_highlight::NuHighlight; pub use print::Print; pub use prompt::NushellPrompt; +pub use repl::evaluate_repl; pub use syntax_highlight::NuHighlighter; pub use util::print_pipeline_data; +pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error}; pub use validation::NuValidator; + +#[cfg(feature = "plugin")] +pub use config_files::add_plugin_file; +#[cfg(feature = "plugin")] +pub use config_files::read_plugin_file; diff --git a/src/prompt_update.rs b/crates/nu-cli/src/prompt_update.rs similarity index 89% rename from src/prompt_update.rs rename to crates/nu-cli/src/prompt_update.rs index 06df95ddf..875ca45f6 100644 --- a/src/prompt_update.rs +++ b/crates/nu-cli/src/prompt_update.rs @@ -1,6 +1,6 @@ -use crate::{is_perf_true, utils::report_error}; +use crate::util::report_error; +use crate::NushellPrompt; use log::info; -use nu_cli::NushellPrompt; use nu_engine::eval_subexpression; use nu_parser::parse; use nu_protocol::{ @@ -21,6 +21,7 @@ pub(crate) fn get_prompt_indicators( config: &Config, engine_state: &EngineState, stack: &Stack, + is_perf_true: bool, ) -> (String, String, String, String) { let prompt_indicator = match stack.get_env_var(engine_state, PROMPT_INDICATOR) { Some(pi) => pi.into_string("", config), @@ -42,7 +43,7 @@ pub(crate) fn get_prompt_indicators( None => "::: ".to_string(), }; - if is_perf_true() { + if is_perf_true { info!( "get_prompt_indicators {}:{}:{}", file!(), @@ -64,6 +65,7 @@ fn get_prompt_string( config: &Config, engine_state: &EngineState, stack: &mut Stack, + is_perf_true: bool, ) -> Option { stack .get_env_var(engine_state, prompt) @@ -82,7 +84,7 @@ fn get_prompt_string( block, PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored ); - if is_perf_true() { + if is_perf_true { info!( "get_prompt_string (block) {}:{}:{}", file!(), @@ -111,7 +113,7 @@ fn get_prompt_string( PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored ) .ok(); - if is_perf_true() { + if is_perf_true { info!( "get_prompt_string (string) {}:{}:{}", file!(), @@ -149,6 +151,7 @@ pub(crate) fn update_prompt<'prompt>( engine_state: &EngineState, stack: &Stack, nu_prompt: &'prompt mut NushellPrompt, + is_perf_true: bool, ) -> &'prompt dyn Prompt { // get the other indicators let ( @@ -156,21 +159,33 @@ pub(crate) fn update_prompt<'prompt>( prompt_vi_insert_string, prompt_vi_normal_string, prompt_multiline_string, - ) = get_prompt_indicators(config, engine_state, stack); + ) = get_prompt_indicators(config, engine_state, stack, is_perf_true); let mut stack = stack.clone(); // apply the other indicators nu_prompt.update_all_prompt_strings( - get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack), - get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack), + get_prompt_string( + PROMPT_COMMAND, + config, + engine_state, + &mut stack, + is_perf_true, + ), + get_prompt_string( + PROMPT_COMMAND_RIGHT, + config, + engine_state, + &mut stack, + is_perf_true, + ), prompt_indicator_string, prompt_multiline_string, (prompt_vi_insert_string, prompt_vi_normal_string), ); let ret_val = nu_prompt as &dyn Prompt; - if is_perf_true() { + if is_perf_true { info!("update_prompt {}:{}:{}", file!(), line!(), column!()); } diff --git a/src/reedline_config.rs b/crates/nu-cli/src/reedline_config.rs similarity index 100% rename from src/reedline_config.rs rename to crates/nu-cli/src/reedline_config.rs diff --git a/src/repl.rs b/crates/nu-cli/src/repl.rs similarity index 86% rename from src/repl.rs rename to crates/nu-cli/src/repl.rs index 0d16edab6..c80364c05 100644 --- a/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -1,28 +1,31 @@ -use crate::is_perf_true; use crate::reedline_config::{add_completion_menu, add_history_menu}; -use crate::{config_files, prompt_update, reedline_config}; +use crate::{prompt_update, reedline_config}; use crate::{ reedline_config::KeybindingsMode, - utils::{eval_source, gather_parent_env_vars, report_error}, + util::{eval_source, report_error}, }; +use crate::{NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; use log::info; use log::trace; use miette::{IntoDiagnostic, Result}; -use nu_cli::{NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; use nu_color_config::get_color_config; use nu_engine::convert_env_values; use nu_parser::lex; +use nu_protocol::engine::Stack; +use nu_protocol::PipelineData; use nu_protocol::{ engine::{EngineState, StateWorkingSet}, Config, ShellError, Span, Value, CONFIG_VARIABLE_ID, }; -use nu_protocol::{PipelineData, Spanned}; use reedline::{DefaultHinter, Emacs, Vi}; +use std::path::PathBuf; use std::{sync::atomic::Ordering, time::Instant}; -pub(crate) fn evaluate( +pub fn evaluate_repl( engine_state: &mut EngineState, - config_file: Option>, + stack: &mut Stack, + history_path: Option, + is_perf_true: bool, ) -> Result<()> { // use crate::logger::{configure, logger}; use reedline::{FileBackedHistory, Reedline, Signal}; @@ -30,34 +33,34 @@ pub(crate) fn evaluate( let mut entry_num = 0; let mut nu_prompt = NushellPrompt::new(); - let mut stack = nu_protocol::engine::Stack::new(); + // let mut stack = nu_protocol::engine::Stack::new(); // First, set up env vars as strings only - gather_parent_env_vars(engine_state); + // gather_parent_env_vars(engine_state); // Set up our initial config to start from - stack.vars.insert( - CONFIG_VARIABLE_ID, - Value::Record { - cols: vec![], - vals: vec![], - span: Span::new(0, 0), - }, - ); + // stack.vars.insert( + // CONFIG_VARIABLE_ID, + // Value::Record { + // cols: vec![], + // vals: vec![], + // span: Span::new(0, 0), + // }, + // ); - if is_perf_true() { + if is_perf_true { info!("read_plugin_file {}:{}:{}", file!(), line!(), column!()); } - #[cfg(feature = "plugin")] - config_files::read_plugin_file(engine_state, &mut stack); - - if is_perf_true() { - info!("read_config_file {}:{}:{}", file!(), line!(), column!()); - } - - config_files::read_config_file(engine_state, &mut stack, config_file); - let history_path = config_files::create_history_path(); + // #[cfg(feature = "plugin")] + // config_files::read_plugin_file(engine_state, &mut stack, is_perf_true); + // + // if is_perf_true { + // info!("read_config_file {}:{}:{}", file!(), line!(), column!()); + // } + // + // config_files::read_config_file(engine_state, &mut stack, config_file, is_perf_true); + // let history_path = config_files::create_history_path(); // logger(|builder| { // configure(&config.log_level, builder)?; @@ -67,7 +70,7 @@ pub(crate) fn evaluate( // Ok(()) // })?; - if is_perf_true() { + if is_perf_true { info!( "translate environment vars {}:{}:{}", file!(), @@ -77,13 +80,13 @@ pub(crate) fn evaluate( } // Translate environment variables from Strings to Values - if let Some(e) = convert_env_values(engine_state, &stack) { + if let Some(e) = convert_env_values(engine_state, stack) { let working_set = StateWorkingSet::new(engine_state); report_error(&working_set, &e); } // Make a note of the exceptions we see for externals that look like math expressions - let exceptions = crate::utils::external_exceptions(engine_state, &stack); + let exceptions = crate::util::external_exceptions(engine_state, stack); engine_state.external_exceptions = exceptions; // seed env vars @@ -104,7 +107,7 @@ pub(crate) fn evaluate( ); loop { - if is_perf_true() { + if is_perf_true { info!( "load config each loop {}:{}:{}", file!(), @@ -128,7 +131,7 @@ pub(crate) fn evaluate( ctrlc.store(false, Ordering::SeqCst); } - if is_perf_true() { + if is_perf_true { info!("setup line editor {}:{}:{}", file!(), line!(), column!()); } @@ -150,14 +153,14 @@ pub(crate) fn evaluate( .with_partial_completions(config.partial_completions) .with_ansi_colors(config.use_ansi_coloring); - if is_perf_true() { + if is_perf_true { info!("setup reedline {}:{}:{}", file!(), line!(), column!()); } line_editor = add_completion_menu(line_editor, &config); line_editor = add_history_menu(line_editor, &config); - if is_perf_true() { + if is_perf_true { info!("setup colors {}:{}:{}", file!(), line!(), column!()); } //FIXME: if config.use_ansi_coloring is false then we should @@ -165,7 +168,7 @@ pub(crate) fn evaluate( let color_hm = get_color_config(&config); - if is_perf_true() { + if is_perf_true { info!( "setup history and hinter {}:{}:{}", file!(), @@ -196,7 +199,7 @@ pub(crate) fn evaluate( line_editor }; - if is_perf_true() { + if is_perf_true { info!("setup keybindings {}:{}:{}", file!(), line!(), column!()); } @@ -222,15 +225,21 @@ pub(crate) fn evaluate( } }; - if is_perf_true() { + if is_perf_true { info!("prompt_update {}:{}:{}", file!(), line!(), column!()); } - let prompt = prompt_update::update_prompt(&config, engine_state, &stack, &mut nu_prompt); + let prompt = prompt_update::update_prompt( + &config, + engine_state, + stack, + &mut nu_prompt, + is_perf_true, + ); entry_num += 1; - if is_perf_true() { + if is_perf_true { info!( "finished setup, starting repl {}:{}:{}", file!(), @@ -245,7 +254,7 @@ pub(crate) fn evaluate( let start_time = Instant::now(); let tokens = lex(s.as_bytes(), 0, &[], &[], false); // Check if this is a single call to a directory, if so auto-cd - let cwd = nu_engine::env::current_dir_str(engine_state, &stack)?; + let cwd = nu_engine::env::current_dir_str(engine_state, stack)?; let path = nu_path::expand_path_with(&s, &cwd); let orig = s.clone(); @@ -308,7 +317,7 @@ pub(crate) fn evaluate( eval_source( engine_state, - &mut stack, + stack, s.as_bytes(), &format!("entry #{}", entry_num), PipelineData::new(Span::new(0, 0)), @@ -331,7 +340,7 @@ pub(crate) fn evaluate( } // Make a note of the exceptions we see for externals that look like math expressions - let exceptions = crate::utils::external_exceptions(engine_state, &stack); + let exceptions = crate::util::external_exceptions(engine_state, stack); engine_state.external_exceptions = exceptions; } Ok(Signal::CtrlC) => { diff --git a/crates/nu-cli/src/util.rs b/crates/nu-cli/src/util.rs index 48ed227d5..04a81595b 100644 --- a/crates/nu-cli/src/util.rs +++ b/crates/nu-cli/src/util.rs @@ -1,5 +1,11 @@ +use log::trace; +use nu_engine::eval_block; +use nu_parser::{lex, parse, trim_quotes, Token, TokenContents}; use std::io::Write; +use std::path::PathBuf; +use crate::CliError; +use nu_protocol::engine::StateWorkingSet; use nu_protocol::{ ast::Call, engine::{EngineState, Stack}, @@ -84,3 +90,419 @@ pub fn print_pipeline_data( Ok(()) } + +// This will collect environment variables from std::env and adds them to a stack. +// +// In order to ensure the values have spans, it first creates a dummy file, writes the collected +// env vars into it (in a "NAME"="value" format, quite similar to the output of the Unix 'env' +// tool), then uses the file to get the spans. The file stays in memory, no filesystem IO is done. +pub fn gather_parent_env_vars(engine_state: &mut EngineState) { + // Some helper functions + fn get_surround_char(s: &str) -> Option { + if s.contains('"') { + if s.contains('\'') { + None + } else { + Some('\'') + } + } else { + Some('\'') + } + } + + fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) { + let working_set = StateWorkingSet::new(engine_state); + report_error( + &working_set, + &ShellError::LabeledError( + format!("Environment variable was not captured: {}", env_str), + msg.into(), + ), + ); + } + + fn put_env_to_fake_file( + name: &str, + val: &str, + fake_env_file: &mut String, + engine_state: &EngineState, + ) { + let (c_name, c_val) = + if let (Some(cn), Some(cv)) = (get_surround_char(name), get_surround_char(val)) { + (cn, cv) + } else { + // environment variable with its name or value containing both ' and " is ignored + report_capture_error( + engine_state, + &format!("{}={}", name, val), + "Name or value should not contain both ' and \" at the same time.", + ); + return; + }; + + fake_env_file.push(c_name); + fake_env_file.push_str(name); + fake_env_file.push(c_name); + fake_env_file.push('='); + fake_env_file.push(c_val); + fake_env_file.push_str(val); + fake_env_file.push(c_val); + fake_env_file.push('\n'); + } + + let mut fake_env_file = String::new(); + + // Make sure we always have PWD + if std::env::var("PWD").is_err() { + match std::env::current_dir() { + Ok(cwd) => { + put_env_to_fake_file( + "PWD", + &cwd.to_string_lossy(), + &mut fake_env_file, + engine_state, + ); + } + Err(e) => { + // Could not capture current working directory + let working_set = StateWorkingSet::new(engine_state); + report_error( + &working_set, + &ShellError::LabeledError( + "Current directory not found".to_string(), + format!("Retrieving current directory failed: {:?}", e), + ), + ); + } + } + } + + // Write all the env vars into a fake file + for (name, val) in std::env::vars() { + put_env_to_fake_file(&name, &val, &mut fake_env_file, engine_state); + } + + // Lex the fake file, assign spans to all environment variables and add them + // to stack + let span_offset = engine_state.next_span_start(); + + engine_state.add_file( + "Host Environment Variables".to_string(), + fake_env_file.as_bytes().to_vec(), + ); + + let (tokens, _) = lex(fake_env_file.as_bytes(), span_offset, &[], &[], true); + + for token in tokens { + if let Token { + contents: TokenContents::Item, + span: full_span, + } = token + { + let contents = engine_state.get_span_contents(&full_span); + let (parts, _) = lex(contents, full_span.start, &[], &[b'='], true); + + let name = if let Some(Token { + contents: TokenContents::Item, + span, + }) = parts.get(0) + { + let bytes = engine_state.get_span_contents(span); + + if bytes.len() < 2 { + report_capture_error( + engine_state, + &String::from_utf8_lossy(contents), + "Got empty name.", + ); + + continue; + } + + let bytes = trim_quotes(bytes); + String::from_utf8_lossy(bytes).to_string() + } else { + report_capture_error( + engine_state, + &String::from_utf8_lossy(contents), + "Got empty name.", + ); + + continue; + }; + + let value = if let Some(Token { + contents: TokenContents::Item, + span, + }) = parts.get(2) + { + let bytes = engine_state.get_span_contents(span); + + if bytes.len() < 2 { + report_capture_error( + engine_state, + &String::from_utf8_lossy(contents), + "Got empty value.", + ); + + continue; + } + + let bytes = trim_quotes(bytes); + + Value::String { + val: String::from_utf8_lossy(bytes).to_string(), + span: *span, + } + } else { + report_capture_error( + engine_state, + &String::from_utf8_lossy(contents), + "Got empty value.", + ); + + continue; + }; + + // stack.add_env_var(name, value); + engine_state.env_vars.insert(name, value); + } + } +} + +pub fn eval_source( + engine_state: &mut EngineState, + stack: &mut Stack, + source: &[u8], + fname: &str, + input: PipelineData, +) -> bool { + trace!("eval_source"); + + let (block, delta) = { + let mut working_set = StateWorkingSet::new(engine_state); + let (output, err) = parse( + &mut working_set, + Some(fname), // format!("entry #{}", entry_num) + source, + false, + ); + if let Some(err) = err { + report_error(&working_set, &err); + return false; + } + + (output, working_set.render()) + }; + + let cwd = match nu_engine::env::current_dir_str(engine_state, stack) { + Ok(p) => PathBuf::from(p), + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + get_init_cwd() + } + }; + + if let Err(err) = engine_state.merge_delta(delta, Some(stack), &cwd) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &err); + } + + match eval_block(engine_state, stack, &block, input, false, false) { + Ok(mut pipeline_data) => { + if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data { + if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) { + stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code); + } else { + stack.add_env_var( + "LAST_EXIT_CODE".to_string(), + Value::Int { + val: 0, + span: Span { start: 0, end: 0 }, + }, + ); + } + } else { + stack.add_env_var( + "LAST_EXIT_CODE".to_string(), + Value::Int { + val: 0, + span: Span { start: 0, end: 0 }, + }, + ); + } + + if let Err(err) = print_pipeline_data(pipeline_data, engine_state, stack) { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &err); + + return false; + } + + // reset vt processing, aka ansi because illbehaved externals can break it + #[cfg(windows)] + { + let _ = enable_vt_processing(); + } + } + Err(err) => { + stack.add_env_var( + "LAST_EXIT_CODE".to_string(), + Value::Int { + val: 1, + span: Span { start: 0, end: 0 }, + }, + ); + + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &err); + + return false; + } + } + + true +} + +fn seems_like_number(bytes: &[u8]) -> bool { + if bytes.is_empty() { + false + } else { + let b = bytes[0]; + + b == b'0' + || b == b'1' + || b == b'2' + || b == b'3' + || b == b'4' + || b == b'5' + || b == b'6' + || b == b'7' + || b == b'8' + || b == b'9' + || b == b'(' + || b == b'{' + || b == b'[' + || b == b'$' + || b == b'"' + || b == b'\'' + || b == b'-' + } +} + +/// Finds externals that have names that look like math expressions +pub fn external_exceptions(engine_state: &EngineState, stack: &Stack) -> Vec> { + let mut executables = vec![]; + + if let Some(path) = stack.get_env_var(engine_state, "PATH") { + match path { + Value::List { vals, .. } => { + for val in vals { + let path = val.as_string(); + + if let Ok(path) = path { + if let Ok(mut contents) = std::fs::read_dir(path) { + while let Some(Ok(item)) = contents.next() { + if is_executable::is_executable(&item.path()) { + if let Ok(name) = item.file_name().into_string() { + if seems_like_number(name.as_bytes()) { + let name = name.as_bytes().to_vec(); + executables.push(name); + } + } + + if let Some(name) = item.path().file_stem() { + let name = name.to_string_lossy(); + if seems_like_number(name.as_bytes()) { + let name = name.as_bytes().to_vec(); + executables.push(name); + } + } + } + } + } + } + } + } + Value::String { val, .. } => { + for path in std::env::split_paths(&val) { + let path = path.to_string_lossy().to_string(); + + if let Ok(mut contents) = std::fs::read_dir(path) { + while let Some(Ok(item)) = contents.next() { + if is_executable::is_executable(&item.path()) { + if let Ok(name) = item.file_name().into_string() { + if seems_like_number(name.as_bytes()) { + let name = name.as_bytes().to_vec(); + executables.push(name); + } + } + if let Some(name) = item.path().file_stem() { + let name = name.to_string_lossy(); + if seems_like_number(name.as_bytes()) { + let name = name.as_bytes().to_vec(); + executables.push(name); + } + } + } + } + } + } + } + _ => {} + } + } + + executables +} + +#[cfg(windows)] +pub fn enable_vt_processing() -> Result<(), ShellError> { + use crossterm_winapi::{ConsoleMode, Handle}; + + pub const ENABLE_PROCESSED_OUTPUT: u32 = 0x0001; + pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004; + // let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING; + + let console_mode = ConsoleMode::from(Handle::current_out_handle()?); + let old_mode = console_mode.mode()?; + + // researching odd ansi behavior in windows terminal repo revealed that + // enable_processed_output and enable_virtual_terminal_processing should be used + // also, instead of checking old_mode & mask, just set the mode already + + // if old_mode & mask == 0 { + console_mode + .set_mode(old_mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)?; + // } + + Ok(()) +} + +pub fn report_error( + working_set: &StateWorkingSet, + error: &(dyn miette::Diagnostic + Send + Sync + 'static), +) { + eprintln!("Error: {:?}", CliError(error, working_set)); + // reset vt processing, aka ansi because illbehaved externals can break it + #[cfg(windows)] + { + let _ = enable_vt_processing(); + } +} + +pub fn get_init_cwd() -> PathBuf { + match std::env::current_dir() { + Ok(cwd) => cwd, + Err(_) => match std::env::var("PWD") { + Ok(cwd) => PathBuf::from(cwd), + Err(_) => match nu_path::home_dir() { + Some(cwd) => cwd, + None => PathBuf::new(), + }, + }, + } +} diff --git a/src/config_files.rs b/src/config_files.rs index 05203016b..882a898eb 100644 --- a/src/config_files.rs +++ b/src/config_files.rs @@ -1,60 +1,22 @@ -use crate::is_perf_true; -use crate::utils::{eval_source, report_error}; use log::info; +use nu_cli::{eval_config_contents, eval_source, report_error}; use nu_parser::ParseError; use nu_path::canonicalize_with; -use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet}; +use nu_protocol::engine::{EngineState, Stack, StateWorkingSet}; use nu_protocol::{PipelineData, Span, Spanned}; use std::fs::File; use std::io::Write; use std::path::PathBuf; -const NUSHELL_FOLDER: &str = "nushell"; +pub(crate) const NUSHELL_FOLDER: &str = "nushell"; const CONFIG_FILE: &str = "config.nu"; const HISTORY_FILE: &str = "history.txt"; -#[cfg(feature = "plugin")] -const PLUGIN_FILE: &str = "plugin.nu"; - -#[cfg(feature = "plugin")] -pub(crate) fn read_plugin_file(engine_state: &mut EngineState, stack: &mut Stack) { - // Reading signatures from signature file - // The plugin.nu file stores the parsed signature collected from each registered plugin - add_plugin_file(engine_state); - - let plugin_path = engine_state.plugin_signatures.clone(); - if let Some(plugin_path) = plugin_path { - let plugin_filename = plugin_path.to_string_lossy().to_owned(); - - if let Ok(contents) = std::fs::read(&plugin_path) { - eval_source( - engine_state, - stack, - &contents, - &plugin_filename, - PipelineData::new(Span::new(0, 0)), - ); - } - } - - if is_perf_true() { - info!("read_plugin_file {}:{}:{}", file!(), line!(), column!()); - } -} - -#[cfg(feature = "plugin")] -pub(crate) fn add_plugin_file(engine_state: &mut EngineState) { - if let Some(mut plugin_path) = nu_path::config_dir() { - // Path to store plugins signatures - plugin_path.push(NUSHELL_FOLDER); - plugin_path.push(PLUGIN_FILE); - engine_state.plugin_signatures = Some(plugin_path.clone()); - } -} pub(crate) fn read_config_file( engine_state: &mut EngineState, stack: &mut Stack, config_file: Option>, + is_perf_true: bool, ) { // Load config startup file if let Some(file) = config_file { @@ -118,41 +80,11 @@ pub(crate) fn read_config_file( eval_config_contents(config_path, engine_state, stack); } - if is_perf_true() { + if is_perf_true { info!("read_config_file {}:{}:{}", file!(), line!(), column!()); } } -fn eval_config_contents(config_path: PathBuf, engine_state: &mut EngineState, stack: &mut Stack) { - if config_path.exists() & config_path.is_file() { - let config_filename = config_path.to_string_lossy().to_owned(); - - if let Ok(contents) = std::fs::read(&config_path) { - eval_source( - engine_state, - stack, - &contents, - &config_filename, - PipelineData::new(Span::new(0, 0)), - ); - - // Merge the delta in case env vars changed in the config - match nu_engine::env::current_dir(engine_state, stack) { - Ok(cwd) => { - if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(stack), cwd) { - let working_set = StateWorkingSet::new(engine_state); - report_error(&working_set, &e); - } - } - Err(e) => { - let working_set = StateWorkingSet::new(engine_state); - report_error(&working_set, &e); - } - } - } - } -} - pub(crate) fn create_history_path() -> Option { nu_path::config_dir().and_then(|mut history_path| { history_path.push(NUSHELL_FOLDER); diff --git a/src/main.rs b/src/main.rs index 4983b83f0..92ad27c93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,22 @@ -mod commands; mod config_files; -mod eval_file; mod logger; -mod prompt_update; -mod reedline_config; -mod repl; mod test_bins; #[cfg(test)] mod tests; -mod utils; +#[cfg(feature = "plugin")] +use crate::config_files::NUSHELL_FOLDER; use crate::logger::{configure, logger}; use log::info; use miette::Result; +#[cfg(feature = "plugin")] +use nu_cli::add_plugin_file; +#[cfg(feature = "plugin")] +use nu_cli::read_plugin_file; +use nu_cli::{ + evaluate_commands, evaluate_file, evaluate_repl, gather_parent_env_vars, get_init_cwd, + report_error, +}; use nu_command::{create_default_context, BufferedReader}; use nu_engine::{get_full_help, CallExt}; use nu_parser::parse; @@ -31,7 +35,6 @@ use std::{ Arc, }, }; -use utils::report_error; thread_local! { static IS_PERF: RefCell = RefCell::new(false) } @@ -44,7 +47,7 @@ fn main() -> Result<()> { })); // Get initial current working directory. - let init_cwd = utils::get_init_cwd(); + let init_cwd = get_init_cwd(); let mut engine_state = create_default_context(&init_cwd); // Custom additions @@ -186,11 +189,31 @@ fn main() -> Result<()> { info!("redirect_stdin {}:{}:{}", file!(), line!(), column!()); } + // First, set up env vars as strings only + gather_parent_env_vars(&mut engine_state); + let mut stack = nu_protocol::engine::Stack::new(); + + stack.vars.insert( + CONFIG_VARIABLE_ID, + Value::Record { + cols: vec![], + vals: vec![], + span: Span::new(0, 0), + }, + ); + if let Some(commands) = &binary_args.commands { #[cfg(feature = "plugin")] - config_files::add_plugin_file(&mut engine_state); + add_plugin_file(&mut engine_state, NUSHELL_FOLDER); - let ret_val = commands::evaluate(commands, &init_cwd, &mut engine_state, input); + let ret_val = evaluate_commands( + commands, + &init_cwd, + &mut engine_state, + &mut stack, + input, + is_perf_true(), + ); if is_perf_true() { info!("-c command execution {}:{}:{}", file!(), line!(), column!()); } @@ -198,17 +221,27 @@ fn main() -> Result<()> { ret_val } else if !script_name.is_empty() && binary_args.interactive_shell.is_none() { #[cfg(feature = "plugin")] - config_files::add_plugin_file(&mut engine_state); + add_plugin_file(&mut engine_state, NUSHELL_FOLDER); - let ret_val = - eval_file::evaluate(script_name, &args_to_script, &mut engine_state, input); + let ret_val = evaluate_file( + script_name, + &args_to_script, + &mut engine_state, + &mut stack, + input, + is_perf_true(), + ); if is_perf_true() { info!("eval_file execution {}:{}:{}", file!(), line!(), column!()); } ret_val } else { - let ret_val = repl::evaluate(&mut engine_state, binary_args.config_file); + setup_config(&mut engine_state, &mut stack, binary_args.config_file); + let history_path = config_files::create_history_path(); + + let ret_val = + evaluate_repl(&mut engine_state, &mut stack, history_path, is_perf_true()); if is_perf_true() { info!("repl eval {}:{}:{}", file!(), line!(), column!()); } @@ -220,6 +253,21 @@ fn main() -> Result<()> { } } +fn setup_config( + engine_state: &mut EngineState, + stack: &mut Stack, + config_file: Option>, +) { + #[cfg(feature = "plugin")] + read_plugin_file(engine_state, stack, NUSHELL_FOLDER, is_perf_true()); + + if is_perf_true() { + info!("read_config_file {}:{}:{}", file!(), line!(), column!()); + } + + config_files::read_config_file(engine_state, stack, config_file, is_perf_true()); +} + fn parse_commandline_args( commandline_args: &str, init_cwd: &Path, diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index bf0019030..000000000 --- a/src/utils.rs +++ /dev/null @@ -1,425 +0,0 @@ -use log::trace; -use nu_cli::{print_pipeline_data, CliError}; -use nu_engine::eval_block; -use nu_parser::{lex, parse, trim_quotes, Token, TokenContents}; -use nu_protocol::{ - engine::{EngineState, Stack, StateWorkingSet}, - PipelineData, ShellError, Span, Value, -}; -use std::path::PathBuf; - -// This will collect environment variables from std::env and adds them to a stack. -// -// In order to ensure the values have spans, it first creates a dummy file, writes the collected -// env vars into it (in a "NAME"="value" format, quite similar to the output of the Unix 'env' -// tool), then uses the file to get the spans. The file stays in memory, no filesystem IO is done. -pub(crate) fn gather_parent_env_vars(engine_state: &mut EngineState) { - // Some helper functions - fn get_surround_char(s: &str) -> Option { - if s.contains('"') { - if s.contains('\'') { - None - } else { - Some('\'') - } - } else { - Some('\'') - } - } - - fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) { - let working_set = StateWorkingSet::new(engine_state); - report_error( - &working_set, - &ShellError::LabeledError( - format!("Environment variable was not captured: {}", env_str), - msg.into(), - ), - ); - } - - fn put_env_to_fake_file( - name: &str, - val: &str, - fake_env_file: &mut String, - engine_state: &EngineState, - ) { - let (c_name, c_val) = - if let (Some(cn), Some(cv)) = (get_surround_char(name), get_surround_char(val)) { - (cn, cv) - } else { - // environment variable with its name or value containing both ' and " is ignored - report_capture_error( - engine_state, - &format!("{}={}", name, val), - "Name or value should not contain both ' and \" at the same time.", - ); - return; - }; - - fake_env_file.push(c_name); - fake_env_file.push_str(name); - fake_env_file.push(c_name); - fake_env_file.push('='); - fake_env_file.push(c_val); - fake_env_file.push_str(val); - fake_env_file.push(c_val); - fake_env_file.push('\n'); - } - - let mut fake_env_file = String::new(); - - // Make sure we always have PWD - if std::env::var("PWD").is_err() { - match std::env::current_dir() { - Ok(cwd) => { - put_env_to_fake_file( - "PWD", - &cwd.to_string_lossy(), - &mut fake_env_file, - engine_state, - ); - } - Err(e) => { - // Could not capture current working directory - let working_set = StateWorkingSet::new(engine_state); - report_error( - &working_set, - &ShellError::LabeledError( - "Current directory not found".to_string(), - format!("Retrieving current directory failed: {:?}", e), - ), - ); - } - } - } - - // Write all the env vars into a fake file - for (name, val) in std::env::vars() { - put_env_to_fake_file(&name, &val, &mut fake_env_file, engine_state); - } - - // Lex the fake file, assign spans to all environment variables and add them - // to stack - let span_offset = engine_state.next_span_start(); - - engine_state.add_file( - "Host Environment Variables".to_string(), - fake_env_file.as_bytes().to_vec(), - ); - - let (tokens, _) = lex(fake_env_file.as_bytes(), span_offset, &[], &[], true); - - for token in tokens { - if let Token { - contents: TokenContents::Item, - span: full_span, - } = token - { - let contents = engine_state.get_span_contents(&full_span); - let (parts, _) = lex(contents, full_span.start, &[], &[b'='], true); - - let name = if let Some(Token { - contents: TokenContents::Item, - span, - }) = parts.get(0) - { - let bytes = engine_state.get_span_contents(span); - - if bytes.len() < 2 { - report_capture_error( - engine_state, - &String::from_utf8_lossy(contents), - "Got empty name.", - ); - - continue; - } - - let bytes = trim_quotes(bytes); - String::from_utf8_lossy(bytes).to_string() - } else { - report_capture_error( - engine_state, - &String::from_utf8_lossy(contents), - "Got empty name.", - ); - - continue; - }; - - let value = if let Some(Token { - contents: TokenContents::Item, - span, - }) = parts.get(2) - { - let bytes = engine_state.get_span_contents(span); - - if bytes.len() < 2 { - report_capture_error( - engine_state, - &String::from_utf8_lossy(contents), - "Got empty value.", - ); - - continue; - } - - let bytes = trim_quotes(bytes); - - Value::String { - val: String::from_utf8_lossy(bytes).to_string(), - span: *span, - } - } else { - report_capture_error( - engine_state, - &String::from_utf8_lossy(contents), - "Got empty value.", - ); - - continue; - }; - - // stack.add_env_var(name, value); - engine_state.env_vars.insert(name, value); - } - } -} - -pub(crate) fn eval_source( - engine_state: &mut EngineState, - stack: &mut Stack, - source: &[u8], - fname: &str, - input: PipelineData, -) -> bool { - trace!("eval_source"); - - let (block, delta) = { - let mut working_set = StateWorkingSet::new(engine_state); - let (output, err) = parse( - &mut working_set, - Some(fname), // format!("entry #{}", entry_num) - source, - false, - ); - if let Some(err) = err { - report_error(&working_set, &err); - return false; - } - - (output, working_set.render()) - }; - - let cwd = match nu_engine::env::current_dir_str(engine_state, stack) { - Ok(p) => PathBuf::from(p), - Err(e) => { - let working_set = StateWorkingSet::new(engine_state); - report_error(&working_set, &e); - get_init_cwd() - } - }; - - if let Err(err) = engine_state.merge_delta(delta, Some(stack), &cwd) { - let working_set = StateWorkingSet::new(engine_state); - report_error(&working_set, &err); - } - - match eval_block(engine_state, stack, &block, input, false, false) { - Ok(mut pipeline_data) => { - if let PipelineData::ExternalStream { exit_code, .. } = &mut pipeline_data { - if let Some(exit_code) = exit_code.take().and_then(|it| it.last()) { - stack.add_env_var("LAST_EXIT_CODE".to_string(), exit_code); - } else { - stack.add_env_var( - "LAST_EXIT_CODE".to_string(), - Value::Int { - val: 0, - span: Span { start: 0, end: 0 }, - }, - ); - } - } else { - stack.add_env_var( - "LAST_EXIT_CODE".to_string(), - Value::Int { - val: 0, - span: Span { start: 0, end: 0 }, - }, - ); - } - - if let Err(err) = print_pipeline_data(pipeline_data, engine_state, stack) { - let working_set = StateWorkingSet::new(engine_state); - - report_error(&working_set, &err); - - return false; - } - - // reset vt processing, aka ansi because illbehaved externals can break it - #[cfg(windows)] - { - let _ = enable_vt_processing(); - } - } - Err(err) => { - stack.add_env_var( - "LAST_EXIT_CODE".to_string(), - Value::Int { - val: 1, - span: Span { start: 0, end: 0 }, - }, - ); - - let working_set = StateWorkingSet::new(engine_state); - - report_error(&working_set, &err); - - return false; - } - } - - true -} - -fn seems_like_number(bytes: &[u8]) -> bool { - if bytes.is_empty() { - false - } else { - let b = bytes[0]; - - b == b'0' - || b == b'1' - || b == b'2' - || b == b'3' - || b == b'4' - || b == b'5' - || b == b'6' - || b == b'7' - || b == b'8' - || b == b'9' - || b == b'(' - || b == b'{' - || b == b'[' - || b == b'$' - || b == b'"' - || b == b'\'' - || b == b'-' - } -} - -/// Finds externals that have names that look like math expressions -pub fn external_exceptions(engine_state: &EngineState, stack: &Stack) -> Vec> { - let mut executables = vec![]; - - if let Some(path) = stack.get_env_var(engine_state, "PATH") { - match path { - Value::List { vals, .. } => { - for val in vals { - let path = val.as_string(); - - if let Ok(path) = path { - if let Ok(mut contents) = std::fs::read_dir(path) { - while let Some(Ok(item)) = contents.next() { - if is_executable::is_executable(&item.path()) { - if let Ok(name) = item.file_name().into_string() { - if seems_like_number(name.as_bytes()) { - let name = name.as_bytes().to_vec(); - executables.push(name); - } - } - - if let Some(name) = item.path().file_stem() { - let name = name.to_string_lossy(); - if seems_like_number(name.as_bytes()) { - let name = name.as_bytes().to_vec(); - executables.push(name); - } - } - } - } - } - } - } - } - Value::String { val, .. } => { - for path in std::env::split_paths(&val) { - let path = path.to_string_lossy().to_string(); - - if let Ok(mut contents) = std::fs::read_dir(path) { - while let Some(Ok(item)) = contents.next() { - if is_executable::is_executable(&item.path()) { - if let Ok(name) = item.file_name().into_string() { - if seems_like_number(name.as_bytes()) { - let name = name.as_bytes().to_vec(); - executables.push(name); - } - } - if let Some(name) = item.path().file_stem() { - let name = name.to_string_lossy(); - if seems_like_number(name.as_bytes()) { - let name = name.as_bytes().to_vec(); - executables.push(name); - } - } - } - } - } - } - } - _ => {} - } - } - - executables -} - -#[cfg(windows)] -pub fn enable_vt_processing() -> Result<(), ShellError> { - use crossterm_winapi::{ConsoleMode, Handle}; - - pub const ENABLE_PROCESSED_OUTPUT: u32 = 0x0001; - pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004; - // let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING; - - let console_mode = ConsoleMode::from(Handle::current_out_handle()?); - let old_mode = console_mode.mode()?; - - // researching odd ansi behavior in windows terminal repo revealed that - // enable_processed_output and enable_virtual_terminal_processing should be used - // also, instead of checking old_mode & mask, just set the mode already - - // if old_mode & mask == 0 { - console_mode - .set_mode(old_mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)?; - // } - - Ok(()) -} - -pub fn report_error( - working_set: &StateWorkingSet, - error: &(dyn miette::Diagnostic + Send + Sync + 'static), -) { - eprintln!("Error: {:?}", CliError(error, working_set)); - // reset vt processing, aka ansi because illbehaved externals can break it - #[cfg(windows)] - { - let _ = enable_vt_processing(); - } -} - -pub(crate) fn get_init_cwd() -> PathBuf { - match std::env::current_dir() { - Ok(cwd) => cwd, - Err(_) => match std::env::var("PWD") { - Ok(cwd) => PathBuf::from(cwd), - Err(_) => match nu_path::home_dir() { - Some(cwd) => cwd, - None => PathBuf::new(), - }, - }, - } -}