2021-01-06 18:47:36 +01:00
|
|
|
use crate::line_editor::configure_ctrl_c;
|
2021-01-12 05:59:53 +01:00
|
|
|
use nu_command::commands::default_context::create_default_context;
|
2021-03-12 06:20:54 +01:00
|
|
|
use nu_engine::{
|
|
|
|
print::maybe_print_errors, run_block, script::run_script_standalone, EvaluationContext,
|
|
|
|
};
|
2020-12-31 00:38:31 +01:00
|
|
|
|
|
|
|
#[allow(unused_imports)]
|
2021-03-12 06:20:54 +01:00
|
|
|
pub(crate) use nu_engine::script::{process_script, LineResult};
|
2020-12-31 00:38:31 +01:00
|
|
|
|
2020-09-17 08:02:30 +02:00
|
|
|
#[cfg(feature = "rustyline-support")]
|
2021-01-06 18:47:36 +01:00
|
|
|
use crate::line_editor::{
|
|
|
|
configure_rustyline_editor, convert_rustyline_result_to_string,
|
|
|
|
default_rustyline_editor_configuration, nu_line_editor_helper,
|
|
|
|
};
|
|
|
|
|
|
|
|
#[allow(unused_imports)]
|
|
|
|
use nu_data::config;
|
2021-03-15 08:26:30 +01:00
|
|
|
use nu_data::config::{Conf, NuConfig};
|
|
|
|
use nu_source::{AnchorLocation, Tag, Text};
|
2021-01-06 18:47:36 +01:00
|
|
|
use nu_stream::InputStream;
|
2021-03-15 08:26:30 +01:00
|
|
|
use std::ffi::{OsStr, OsString};
|
2021-01-06 18:47:36 +01:00
|
|
|
#[allow(unused_imports)]
|
|
|
|
use std::sync::atomic::Ordering;
|
|
|
|
|
|
|
|
#[cfg(feature = "rustyline-support")]
|
|
|
|
use rustyline::{self, error::ReadlineError};
|
|
|
|
|
2020-05-14 02:35:22 +02:00
|
|
|
use crate::EnvironmentSyncer;
|
2020-09-17 01:22:58 +02:00
|
|
|
use nu_errors::ShellError;
|
2020-12-18 08:53:49 +01:00
|
|
|
use nu_parser::ParserScope;
|
2021-02-15 08:14:16 +01:00
|
|
|
use nu_protocol::{hir::ExternalRedirection, UntaggedValue, Value};
|
2020-12-05 05:12:42 +01:00
|
|
|
|
2021-02-20 14:37:14 +01:00
|
|
|
use log::trace;
|
2019-05-23 06:30:43 +02:00
|
|
|
use std::error::Error;
|
2019-05-24 09:29:16 +02:00
|
|
|
use std::iter::Iterator;
|
2020-12-31 00:38:31 +01:00
|
|
|
use std::path::PathBuf;
|
2019-05-23 06:30:43 +02:00
|
|
|
|
2021-03-15 08:26:30 +01:00
|
|
|
pub struct Options {
|
|
|
|
pub config: Option<OsString>,
|
|
|
|
pub stdin: bool,
|
|
|
|
pub scripts: Vec<NuScript>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Options {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Options {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
config: None,
|
|
|
|
stdin: false,
|
|
|
|
scripts: vec![],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct NuScript {
|
|
|
|
pub filepath: Option<OsString>,
|
|
|
|
pub contents: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl NuScript {
|
|
|
|
pub fn code<'a>(content: impl Iterator<Item = &'a str>) -> Result<Self, ShellError> {
|
|
|
|
let text = content
|
|
|
|
.map(|x| x.to_string())
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join("\n");
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
filepath: None,
|
|
|
|
contents: text,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_code(&self) -> &str {
|
|
|
|
&self.contents
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn source_file(path: &OsStr) -> Result<Self, ShellError> {
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io::Read;
|
|
|
|
|
|
|
|
let path = path.to_os_string();
|
|
|
|
let mut file = File::open(&path)?;
|
|
|
|
let mut buffer = String::new();
|
|
|
|
|
|
|
|
file.read_to_string(&mut buffer)?;
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
filepath: Some(path),
|
|
|
|
contents: buffer,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-27 09:57:40 +02:00
|
|
|
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
2020-03-04 19:58:20 +01:00
|
|
|
use std::env;
|
2019-07-04 05:06:43 +02:00
|
|
|
|
2020-03-04 19:58:20 +01:00
|
|
|
let mut search_paths = Vec::new();
|
2019-09-13 01:49:29 +02:00
|
|
|
|
2020-03-04 19:58:20 +01:00
|
|
|
// Automatically add path `nu` is in as a search path
|
|
|
|
if let Ok(exe_path) = env::current_exe() {
|
|
|
|
if let Some(exe_dir) = exe_path.parent() {
|
|
|
|
search_paths.push(exe_dir.to_path_buf());
|
2019-09-13 01:49:29 +02:00
|
|
|
}
|
2019-08-27 03:46:38 +02:00
|
|
|
}
|
2019-07-03 19:37:09 +02:00
|
|
|
|
2020-08-18 09:00:02 +02:00
|
|
|
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
2021-02-12 11:13:14 +01:00
|
|
|
if let Some(Value {
|
|
|
|
value: UntaggedValue::Table(pipelines),
|
|
|
|
..
|
|
|
|
}) = config.get("plugin_dirs")
|
|
|
|
{
|
|
|
|
for pipeline in pipelines {
|
|
|
|
if let Ok(plugin_dir) = pipeline.as_string() {
|
|
|
|
search_paths.push(PathBuf::from(plugin_dir));
|
2020-05-28 21:14:32 +02:00
|
|
|
}
|
2019-11-10 04:44:05 +01:00
|
|
|
}
|
|
|
|
}
|
2019-09-12 05:20:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
search_paths
|
|
|
|
}
|
|
|
|
|
2021-03-15 08:26:30 +01:00
|
|
|
pub async fn run_script_file(options: Options) -> Result<(), Box<dyn Error>> {
|
2020-09-17 01:22:58 +02:00
|
|
|
let mut context = create_default_context(false)?;
|
2021-03-15 08:26:30 +01:00
|
|
|
let mut syncer = create_environment_syncer(&context, &options);
|
2020-09-17 01:22:58 +02:00
|
|
|
let config = syncer.get_config();
|
2020-04-15 19:50:35 +02:00
|
|
|
|
2020-09-17 01:22:58 +02:00
|
|
|
context.configure(&config, |_, ctx| {
|
|
|
|
syncer.load_environment();
|
|
|
|
syncer.sync_env_vars(ctx);
|
|
|
|
syncer.sync_path_vars(ctx);
|
2020-04-15 19:50:35 +02:00
|
|
|
|
2020-09-17 01:22:58 +02:00
|
|
|
if let Err(reason) = syncer.autoenv(ctx) {
|
2021-03-12 06:20:54 +01:00
|
|
|
ctx.host.lock().print_err(reason, &Text::from(""));
|
2020-07-18 03:59:23 +02:00
|
|
|
}
|
2020-04-15 19:50:35 +02:00
|
|
|
|
2020-09-17 01:22:58 +02:00
|
|
|
let _ = register_plugins(ctx);
|
|
|
|
let _ = configure_ctrl_c(ctx);
|
|
|
|
});
|
|
|
|
|
|
|
|
let _ = run_startup_commands(&mut context, &config).await;
|
2020-04-15 19:50:35 +02:00
|
|
|
|
2021-03-15 08:26:30 +01:00
|
|
|
let script = options
|
|
|
|
.scripts
|
|
|
|
.get(0)
|
|
|
|
.ok_or_else(|| ShellError::unexpected("Nu source code not available"))?;
|
|
|
|
|
|
|
|
run_script_standalone(script.get_code().to_string(), options.stdin, &context, true).await?;
|
2020-02-09 03:24:33 +01:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-03-15 08:26:30 +01:00
|
|
|
fn create_environment_syncer(context: &EvaluationContext, options: &Options) -> EnvironmentSyncer {
|
|
|
|
if let Some(config_file) = &options.config {
|
|
|
|
let location = Some(AnchorLocation::File(
|
|
|
|
config_file.to_string_lossy().to_string(),
|
|
|
|
));
|
|
|
|
let tag = Tag::unknown().anchored(location);
|
|
|
|
|
|
|
|
context.scope.add_var(
|
|
|
|
"config-path",
|
|
|
|
UntaggedValue::filepath(PathBuf::from(&config_file)).into_value(tag),
|
|
|
|
);
|
|
|
|
|
|
|
|
EnvironmentSyncer::with_config(Box::new(NuConfig::with(Some(config_file.into()))))
|
|
|
|
} else {
|
|
|
|
EnvironmentSyncer::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-17 08:02:30 +02:00
|
|
|
#[cfg(feature = "rustyline-support")]
|
2021-03-15 08:26:30 +01:00
|
|
|
pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(), Box<dyn Error>> {
|
|
|
|
let mut syncer = create_environment_syncer(&context, &options);
|
|
|
|
|
2020-09-17 01:22:58 +02:00
|
|
|
let configuration = syncer.get_config();
|
2019-12-31 05:06:36 +01:00
|
|
|
|
2020-09-17 01:22:58 +02:00
|
|
|
let mut rl = default_rustyline_editor_configuration();
|
2020-08-05 23:34:28 +02:00
|
|
|
|
2020-09-17 01:22:58 +02:00
|
|
|
context.configure(&configuration, |config, ctx| {
|
|
|
|
syncer.load_environment();
|
|
|
|
syncer.sync_env_vars(ctx);
|
|
|
|
syncer.sync_path_vars(ctx);
|
2020-07-15 09:51:59 +02:00
|
|
|
|
2020-09-17 01:22:58 +02:00
|
|
|
if let Err(reason) = syncer.autoenv(ctx) {
|
2021-03-12 06:20:54 +01:00
|
|
|
ctx.host.lock().print_err(reason, &Text::from(""));
|
2020-07-22 23:43:52 +02:00
|
|
|
}
|
|
|
|
|
2020-09-17 01:22:58 +02:00
|
|
|
let _ = configure_ctrl_c(ctx);
|
|
|
|
let _ = configure_rustyline_editor(&mut rl, config);
|
2020-07-22 23:43:52 +02:00
|
|
|
|
2020-09-17 01:22:58 +02:00
|
|
|
let helper = Some(nu_line_editor_helper(ctx, config));
|
|
|
|
rl.set_helper(helper);
|
|
|
|
});
|
2020-07-22 23:43:52 +02:00
|
|
|
|
2021-02-20 14:37:14 +01:00
|
|
|
// start time for command duration
|
|
|
|
let startup_commands_start_time = std::time::Instant::now();
|
|
|
|
// run the startup commands
|
2020-09-17 01:22:58 +02:00
|
|
|
let _ = run_startup_commands(&mut context, &configuration).await;
|
2021-02-20 14:37:14 +01:00
|
|
|
// Store cmd duration in an env var
|
|
|
|
context.scope.add_env_var(
|
|
|
|
"CMD_DURATION",
|
|
|
|
format!("{:?}", startup_commands_start_time.elapsed()),
|
|
|
|
);
|
|
|
|
trace!(
|
|
|
|
"startup commands took {:?}",
|
|
|
|
startup_commands_start_time.elapsed()
|
|
|
|
);
|
2020-08-27 13:06:25 +02:00
|
|
|
|
2020-12-18 08:53:49 +01:00
|
|
|
// Give ourselves a scope to work in
|
|
|
|
context.scope.enter_scope();
|
|
|
|
|
2021-03-23 10:38:07 +01:00
|
|
|
let env = context.get_env();
|
|
|
|
context.scope.add_env_to_base(env);
|
|
|
|
|
2021-01-10 03:50:49 +01:00
|
|
|
let history_path = nu_engine::history_path(&configuration);
|
2020-08-27 13:06:25 +02:00
|
|
|
let _ = rl.load_history(&history_path);
|
2020-08-09 01:38:21 +02:00
|
|
|
|
2020-12-18 08:53:49 +01:00
|
|
|
let mut session_text = String::new();
|
|
|
|
let mut line_start: usize = 0;
|
|
|
|
|
2020-09-17 01:22:58 +02:00
|
|
|
let skip_welcome_message = configuration
|
|
|
|
.var("skip_welcome_message")
|
2020-08-09 01:38:21 +02:00
|
|
|
.map(|x| x.is_true())
|
|
|
|
.unwrap_or(false);
|
|
|
|
if !skip_welcome_message {
|
|
|
|
println!(
|
|
|
|
"Welcome to Nushell {} (type 'help' for more info)",
|
|
|
|
clap::crate_version!()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-05-26 08:54:41 +02:00
|
|
|
#[cfg(windows)]
|
|
|
|
{
|
2021-02-22 19:33:34 +01:00
|
|
|
let _ = nu_ansi_term::enable_ansi_support();
|
2019-05-26 08:54:41 +02:00
|
|
|
}
|
|
|
|
|
2019-06-15 20:36:17 +02:00
|
|
|
let mut ctrlcbreak = false;
|
2020-04-15 07:43:23 +02:00
|
|
|
|
2019-05-23 06:30:43 +02:00
|
|
|
loop {
|
2019-10-13 06:12:43 +02:00
|
|
|
if context.ctrl_c.load(Ordering::SeqCst) {
|
|
|
|
context.ctrl_c.store(false, Ordering::SeqCst);
|
2019-06-07 02:31:22 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
Restructure and streamline token expansion (#1123)
Restructure and streamline token expansion
The purpose of this commit is to streamline the token expansion code, by
removing aspects of the code that are no longer relevant, removing
pointless duplication, and eliminating the need to pass the same
arguments to `expand_syntax`.
The first big-picture change in this commit is that instead of a handful
of `expand_` functions, which take a TokensIterator and ExpandContext, a
smaller number of methods on the `TokensIterator` do the same job.
The second big-picture change in this commit is fully eliminating the
coloring traits, making coloring a responsibility of the base expansion
implementations. This also means that the coloring tracer is merged into
the expansion tracer, so you can follow a single expansion and see how
the expansion process produced colored tokens.
One side effect of this change is that the expander itself is marginally
more error-correcting. The error correction works by switching from
structured expansion to `BackoffColoringMode` when an unexpected token
is found, which guarantees that all spans of the source are colored, but
may not be the most optimal error recovery strategy.
That said, because `BackoffColoringMode` only extends as far as a
closing delimiter (`)`, `]`, `}`) or pipe (`|`), it does result in
fairly granular correction strategy.
The current code still produces an `Err` (plus a complete list of
colored shapes) from the parsing process if any errors are encountered,
but this could easily be addressed now that the underlying expansion is
error-correcting.
This commit also colors any spans that are syntax errors in red, and
causes the parser to include some additional information about what
tokens were expected at any given point where an error was encountered,
so that completions and hinting could be more robust in the future.
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
Co-authored-by: Andrés N. Robalino <andres@androbtech.com>
2020-01-21 23:45:03 +01:00
|
|
|
let cwd = context.shell_manager.path();
|
2019-08-07 19:49:11 +02:00
|
|
|
|
2019-11-16 21:02:26 +01:00
|
|
|
let colored_prompt = {
|
2020-09-17 01:22:58 +02:00
|
|
|
if let Some(prompt) = configuration.var("prompt") {
|
2020-06-27 00:37:31 +02:00
|
|
|
let prompt_line = prompt.as_string()?;
|
|
|
|
|
2020-12-18 08:53:49 +01:00
|
|
|
context.scope.enter_scope();
|
2021-02-15 08:14:16 +01:00
|
|
|
let (mut prompt_block, err) = nu_parser::parse(&prompt_line, 0, &context.scope);
|
|
|
|
|
|
|
|
prompt_block.set_redirect(ExternalRedirection::Stdout);
|
2020-11-09 17:27:07 +01:00
|
|
|
|
|
|
|
if err.is_some() {
|
2020-12-18 08:53:49 +01:00
|
|
|
context.scope.exit_scope();
|
|
|
|
|
2021-01-15 19:06:29 +01:00
|
|
|
format!("\x1b[32m{}{}\x1b[m> ", cwd, current_branch())
|
2020-11-09 17:27:07 +01:00
|
|
|
} else {
|
2020-12-18 08:53:49 +01:00
|
|
|
// let env = context.get_env();
|
2020-11-09 17:27:07 +01:00
|
|
|
|
2020-12-18 08:53:49 +01:00
|
|
|
let run_result = run_block(&prompt_block, &context, InputStream::empty()).await;
|
|
|
|
context.scope.exit_scope();
|
2020-06-28 23:06:05 +02:00
|
|
|
|
2020-12-18 08:53:49 +01:00
|
|
|
match run_result {
|
2020-11-09 17:27:07 +01:00
|
|
|
Ok(result) => match result.collect_string(Tag::unknown()).await {
|
|
|
|
Ok(string_result) => {
|
|
|
|
let errors = context.get_errors();
|
2021-01-10 03:50:49 +01:00
|
|
|
maybe_print_errors(&context, Text::from(prompt_line));
|
2020-11-09 17:27:07 +01:00
|
|
|
context.clear_errors();
|
|
|
|
|
|
|
|
if !errors.is_empty() {
|
2020-06-28 23:06:05 +02:00
|
|
|
"> ".to_string()
|
2020-11-09 17:27:07 +01:00
|
|
|
} else {
|
|
|
|
string_result.item
|
2020-06-27 00:37:31 +02:00
|
|
|
}
|
2020-11-09 17:27:07 +01:00
|
|
|
}
|
2020-06-28 23:06:05 +02:00
|
|
|
Err(e) => {
|
2021-03-12 06:20:54 +01:00
|
|
|
context.host.lock().print_err(e, &Text::from(prompt_line));
|
2020-06-28 23:06:05 +02:00
|
|
|
context.clear_errors();
|
|
|
|
|
|
|
|
"> ".to_string()
|
2020-06-27 00:37:31 +02:00
|
|
|
}
|
2020-11-09 17:27:07 +01:00
|
|
|
},
|
|
|
|
Err(e) => {
|
2021-03-12 06:20:54 +01:00
|
|
|
context.host.lock().print_err(e, &Text::from(prompt_line));
|
2020-11-09 17:27:07 +01:00
|
|
|
context.clear_errors();
|
2020-06-27 00:37:31 +02:00
|
|
|
|
2020-11-09 17:27:07 +01:00
|
|
|
"> ".to_string()
|
|
|
|
}
|
2020-06-27 00:37:31 +02:00
|
|
|
}
|
|
|
|
}
|
2020-06-25 07:44:55 +02:00
|
|
|
} else {
|
2021-01-15 19:06:29 +01:00
|
|
|
format!("\x1b[32m{}{}\x1b[m> ", cwd, current_branch())
|
2019-10-08 15:47:30 +02:00
|
|
|
}
|
|
|
|
};
|
2019-11-16 21:02:26 +01:00
|
|
|
|
2021-03-04 18:22:14 +01:00
|
|
|
let prompt = {
|
|
|
|
if let Ok(bytes) = strip_ansi_escapes::strip(&colored_prompt) {
|
|
|
|
String::from_utf8_lossy(&bytes).to_string()
|
|
|
|
} else {
|
|
|
|
"> ".to_string()
|
2020-01-01 21:45:32 +01:00
|
|
|
}
|
2019-11-16 21:42:35 +01:00
|
|
|
};
|
|
|
|
|
2019-11-16 21:02:26 +01:00
|
|
|
rl.helper_mut().expect("No helper").colored_prompt = colored_prompt;
|
2019-09-18 00:21:39 +02:00
|
|
|
let mut initial_command = Some(String::new());
|
|
|
|
let mut readline = Err(ReadlineError::Eof);
|
|
|
|
while let Some(ref cmd) = initial_command {
|
2019-11-16 21:02:26 +01:00
|
|
|
readline = rl.readline_with_initial(&prompt, (&cmd, ""));
|
2019-11-22 09:25:09 +01:00
|
|
|
initial_command = None;
|
2019-09-18 00:21:39 +02:00
|
|
|
}
|
2019-05-23 06:30:43 +02:00
|
|
|
|
2020-12-18 08:53:49 +01:00
|
|
|
if let Ok(line) = &readline {
|
|
|
|
line_start = session_text.len();
|
|
|
|
session_text.push_str(line);
|
|
|
|
session_text.push('\n');
|
|
|
|
}
|
|
|
|
|
2021-02-20 14:37:14 +01:00
|
|
|
// start time for command duration
|
|
|
|
let cmd_start_time = std::time::Instant::now();
|
|
|
|
|
2020-09-17 08:02:30 +02:00
|
|
|
let line = match convert_rustyline_result_to_string(readline) {
|
2020-12-18 08:53:49 +01:00
|
|
|
LineResult::Success(_) => {
|
|
|
|
process_script(
|
|
|
|
&session_text[line_start..],
|
2020-12-19 08:47:34 +01:00
|
|
|
&context,
|
2020-12-18 08:53:49 +01:00
|
|
|
false,
|
|
|
|
line_start,
|
|
|
|
true,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
2020-09-17 08:02:30 +02:00
|
|
|
x => x,
|
|
|
|
};
|
2019-11-04 16:47:03 +01:00
|
|
|
|
2021-02-20 14:37:14 +01:00
|
|
|
// Store cmd duration in an env var
|
|
|
|
context
|
|
|
|
.scope
|
|
|
|
.add_env_var("CMD_DURATION", format!("{:?}", cmd_start_time.elapsed()));
|
|
|
|
|
2020-01-28 04:13:22 +01:00
|
|
|
// 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
|
|
|
|
// FIXME: we probably want to be a bit more graceful if we can't set the environment
|
2020-09-17 01:22:58 +02:00
|
|
|
|
|
|
|
context.configure(&configuration, |config, ctx| {
|
|
|
|
if syncer.did_config_change() {
|
|
|
|
syncer.reload();
|
|
|
|
syncer.sync_env_vars(ctx);
|
|
|
|
syncer.sync_path_vars(ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Err(reason) = syncer.autoenv(ctx) {
|
2021-03-12 06:20:54 +01:00
|
|
|
ctx.host.lock().print_err(reason, &Text::from(""));
|
2020-09-17 01:22:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
let _ = configure_rustyline_editor(&mut rl, config);
|
|
|
|
});
|
2020-01-28 04:13:22 +01:00
|
|
|
|
2019-11-04 16:47:03 +01:00
|
|
|
match line {
|
2019-05-23 06:30:43 +02:00
|
|
|
LineResult::Success(line) => {
|
2020-06-15 19:35:24 +02:00
|
|
|
rl.add_history_entry(&line);
|
2020-08-27 13:06:25 +02:00
|
|
|
let _ = rl.save_history(&history_path);
|
2021-01-10 03:50:49 +01:00
|
|
|
maybe_print_errors(&context, Text::from(session_text.clone()));
|
2019-11-04 16:47:03 +01:00
|
|
|
}
|
|
|
|
|
2020-11-09 17:23:41 +01:00
|
|
|
LineResult::ClearHistory => {
|
|
|
|
rl.clear_history();
|
|
|
|
let _ = rl.save_history(&history_path);
|
|
|
|
}
|
|
|
|
|
2019-11-04 16:47:03 +01:00
|
|
|
LineResult::Error(line, err) => {
|
2020-06-15 19:35:24 +02:00
|
|
|
rl.add_history_entry(&line);
|
2020-08-27 13:06:25 +02:00
|
|
|
let _ = rl.save_history(&history_path);
|
2019-11-04 16:47:03 +01:00
|
|
|
|
2021-03-12 06:20:54 +01:00
|
|
|
context
|
|
|
|
.host
|
|
|
|
.lock()
|
|
|
|
.print_err(err, &Text::from(session_text.clone()));
|
2019-11-04 16:47:03 +01:00
|
|
|
|
2021-01-10 03:50:49 +01:00
|
|
|
maybe_print_errors(&context, Text::from(session_text.clone()));
|
2019-05-23 06:30:43 +02:00
|
|
|
}
|
|
|
|
|
2019-06-15 20:36:17 +02:00
|
|
|
LineResult::CtrlC => {
|
2019-09-25 03:01:38 +02:00
|
|
|
let config_ctrlc_exit = config::config(Tag::unknown())?
|
|
|
|
.get("ctrlc_exit")
|
2020-09-15 16:59:51 +02:00
|
|
|
.map(|s| s.value.is_true())
|
2019-09-25 03:01:38 +02:00
|
|
|
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
|
|
|
|
|
|
|
|
if !config_ctrlc_exit {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-06-15 20:36:17 +02:00
|
|
|
if ctrlcbreak {
|
2020-08-27 13:06:25 +02:00
|
|
|
let _ = rl.save_history(&history_path);
|
2019-06-15 20:36:17 +02:00
|
|
|
std::process::exit(0);
|
|
|
|
} else {
|
Restructure and streamline token expansion (#1123)
Restructure and streamline token expansion
The purpose of this commit is to streamline the token expansion code, by
removing aspects of the code that are no longer relevant, removing
pointless duplication, and eliminating the need to pass the same
arguments to `expand_syntax`.
The first big-picture change in this commit is that instead of a handful
of `expand_` functions, which take a TokensIterator and ExpandContext, a
smaller number of methods on the `TokensIterator` do the same job.
The second big-picture change in this commit is fully eliminating the
coloring traits, making coloring a responsibility of the base expansion
implementations. This also means that the coloring tracer is merged into
the expansion tracer, so you can follow a single expansion and see how
the expansion process produced colored tokens.
One side effect of this change is that the expander itself is marginally
more error-correcting. The error correction works by switching from
structured expansion to `BackoffColoringMode` when an unexpected token
is found, which guarantees that all spans of the source are colored, but
may not be the most optimal error recovery strategy.
That said, because `BackoffColoringMode` only extends as far as a
closing delimiter (`)`, `]`, `}`) or pipe (`|`), it does result in
fairly granular correction strategy.
The current code still produces an `Err` (plus a complete list of
colored shapes) from the parsing process if any errors are encountered,
but this could easily be addressed now that the underlying expansion is
error-correcting.
This commit also colors any spans that are syntax errors in red, and
causes the parser to include some additional information about what
tokens were expected at any given point where an error was encountered,
so that completions and hinting could be more robust in the future.
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
Co-authored-by: Andrés N. Robalino <andres@androbtech.com>
2020-01-21 23:45:03 +01:00
|
|
|
context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)"));
|
2019-06-15 20:36:17 +02:00
|
|
|
ctrlcbreak = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-21 09:56:10 +02:00
|
|
|
LineResult::CtrlD => {
|
|
|
|
context.shell_manager.remove_at_current();
|
|
|
|
if context.shell_manager.is_empty() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-23 06:30:43 +02:00
|
|
|
LineResult::Break => {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-06-15 20:36:17 +02:00
|
|
|
ctrlcbreak = false;
|
2019-05-23 06:30:43 +02:00
|
|
|
}
|
2019-08-27 00:41:57 +02:00
|
|
|
|
|
|
|
// we are ok if we can not save history
|
2020-08-27 13:06:25 +02:00
|
|
|
let _ = rl.save_history(&history_path);
|
2019-05-23 06:30:43 +02:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-09-19 23:29:51 +02:00
|
|
|
pub fn register_plugins(context: &mut EvaluationContext) -> Result<(), ShellError> {
|
2021-01-10 03:50:49 +01:00
|
|
|
if let Ok(plugins) = nu_engine::plugin::build_plugin::scan(search_paths()) {
|
2020-09-17 01:22:58 +02:00
|
|
|
context.add_commands(
|
|
|
|
plugins
|
|
|
|
.into_iter()
|
|
|
|
.filter(|p| !context.is_command_registered(p.name()))
|
|
|
|
.collect(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn run_startup_commands(
|
2020-09-19 23:29:51 +02:00
|
|
|
context: &mut EvaluationContext,
|
2020-09-17 01:22:58 +02:00
|
|
|
config: &dyn nu_data::config::Conf,
|
|
|
|
) -> Result<(), ShellError> {
|
|
|
|
if let Some(commands) = config.var("startup") {
|
|
|
|
match commands {
|
|
|
|
Value {
|
|
|
|
value: UntaggedValue::Table(pipelines),
|
|
|
|
..
|
|
|
|
} => {
|
2021-01-08 07:36:31 +01:00
|
|
|
let mut script_file = String::new();
|
2020-09-17 01:22:58 +02:00
|
|
|
for pipeline in pipelines {
|
2021-01-08 07:36:31 +01:00
|
|
|
script_file.push_str(&pipeline.as_string()?);
|
|
|
|
script_file.push('\n');
|
2020-09-17 01:22:58 +02:00
|
|
|
}
|
2021-01-08 07:36:31 +01:00
|
|
|
let _ = run_script_standalone(script_file, false, context, false).await;
|
2020-09-17 01:22:58 +02:00
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
return Err(ShellError::untagged_runtime_error(
|
|
|
|
"expected a table of pipeline strings as startup commands",
|
2021-01-15 19:06:29 +01:00
|
|
|
));
|
2020-09-17 01:22:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-12-19 08:47:34 +01:00
|
|
|
pub async fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<String, ShellError> {
|
2020-12-18 08:53:49 +01:00
|
|
|
// FIXME: do we still need this?
|
2020-11-22 01:37:16 +01:00
|
|
|
let line = if let Some(s) = line.strip_suffix('\n') {
|
|
|
|
s
|
2020-07-18 03:59:23 +02:00
|
|
|
} else {
|
|
|
|
line
|
|
|
|
};
|
|
|
|
|
2020-12-18 08:53:49 +01:00
|
|
|
// TODO ensure the command whose examples we're testing is actually in the pipeline
|
|
|
|
ctx.scope.enter_scope();
|
|
|
|
let (classified_block, err) = nu_parser::parse(&line, 0, &ctx.scope);
|
2020-11-09 17:27:07 +01:00
|
|
|
if let Some(err) = err {
|
2020-12-18 08:53:49 +01:00
|
|
|
ctx.scope.exit_scope();
|
2020-11-09 17:27:07 +01:00
|
|
|
return Err(err.into());
|
|
|
|
}
|
2020-07-18 03:59:23 +02:00
|
|
|
|
|
|
|
let input_stream = InputStream::empty();
|
|
|
|
let env = ctx.get_env();
|
2020-12-18 08:53:49 +01:00
|
|
|
ctx.scope.add_env(env);
|
|
|
|
|
|
|
|
let result = run_block(&classified_block, ctx, input_stream).await;
|
|
|
|
ctx.scope.exit_scope();
|
2020-07-18 03:59:23 +02:00
|
|
|
|
2020-12-18 08:53:49 +01:00
|
|
|
result?.collect_string(Tag::unknown()).await.map(|x| x.item)
|
2020-07-18 03:59:23 +02:00
|
|
|
}
|
|
|
|
|
2021-01-15 19:06:29 +01:00
|
|
|
#[allow(dead_code)]
|
|
|
|
fn current_branch() -> String {
|
|
|
|
#[cfg(feature = "shadow-rs")]
|
|
|
|
{
|
|
|
|
Some(shadow_rs::branch())
|
|
|
|
.map(|x| x.trim().to_string())
|
|
|
|
.filter(|x| !x.is_empty())
|
|
|
|
.map(|x| format!("({})", x))
|
|
|
|
.unwrap_or_default()
|
|
|
|
}
|
|
|
|
#[cfg(not(feature = "shadow-rs"))]
|
|
|
|
{
|
|
|
|
"".to_string()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-11 21:05:59 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2021-01-11 05:58:15 +01:00
|
|
|
use nu_engine::basic_evaluation_context;
|
2020-12-18 08:53:49 +01:00
|
|
|
|
2020-04-11 21:05:59 +02:00
|
|
|
#[quickcheck]
|
|
|
|
fn quickcheck_parse(data: String) -> bool {
|
2020-12-18 08:53:49 +01:00
|
|
|
let (tokens, err) = nu_parser::lex(&data, 0);
|
2021-02-12 21:31:11 +01:00
|
|
|
let (lite_block, err2) = nu_parser::parse_block(tokens);
|
2020-12-18 08:53:49 +01:00
|
|
|
if err.is_none() && err2.is_none() {
|
2021-01-11 05:58:15 +01:00
|
|
|
let context = basic_evaluation_context().unwrap();
|
2020-12-18 08:53:49 +01:00
|
|
|
let _ = nu_parser::classify_block(&lite_block, &context.scope);
|
2020-04-11 21:05:59 +02:00
|
|
|
}
|
2020-04-20 08:41:51 +02:00
|
|
|
true
|
2020-04-11 21:05:59 +02:00
|
|
|
}
|
|
|
|
}
|