mirror of
https://github.com/nushell/nushell.git
synced 2024-12-27 17:39:15 +01:00
40484966c3
* Make env var eval order during "use" deterministic Fixes #726. * Merge delta after getting config To make sure env vars are all in the engine state and not in the stack.
1056 lines
36 KiB
Rust
1056 lines
36 KiB
Rust
use crossterm::event::{KeyCode, KeyModifiers};
|
|
#[cfg(windows)]
|
|
use crossterm_winapi::{ConsoleMode, Handle};
|
|
use dialoguer::{
|
|
console::{Style, Term},
|
|
theme::ColorfulTheme,
|
|
Select,
|
|
};
|
|
use log::trace;
|
|
use miette::{IntoDiagnostic, Result};
|
|
use nu_cli::{CliError, NuCompleter, NuHighlighter, NuValidator, NushellPrompt};
|
|
use nu_color_config::get_color_config;
|
|
use nu_command::create_default_context;
|
|
use nu_engine::{convert_env_values, eval_block};
|
|
use nu_parser::{lex, parse, trim_quotes, Token, TokenContents};
|
|
use nu_protocol::{
|
|
ast::Call,
|
|
engine::{EngineState, Stack, StateDelta, StateWorkingSet},
|
|
Config, PipelineData, ShellError, Span, Value, CONFIG_VARIABLE_ID,
|
|
};
|
|
use reedline::{
|
|
default_emacs_keybindings, Completer, CompletionActionHandler, DefaultHinter, EditCommand,
|
|
Emacs, LineBuffer, Prompt, ReedlineEvent, Vi,
|
|
};
|
|
use std::{
|
|
io::Write,
|
|
path::PathBuf,
|
|
sync::{
|
|
atomic::{AtomicBool, Ordering},
|
|
Arc,
|
|
},
|
|
};
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
mod logger;
|
|
|
|
// Name of environment variable where the prompt could be stored
|
|
const PROMPT_COMMAND: &str = "PROMPT_COMMAND";
|
|
const PROMPT_COMMAND_RIGHT: &str = "PROMPT_COMMAND_RIGHT";
|
|
const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR";
|
|
const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
|
|
const PROMPT_INDICATOR_VI_VISUAL: &str = "PROMPT_INDICATOR_VI_VISUAL";
|
|
const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
|
|
|
|
struct FuzzyCompletion {
|
|
completer: Box<dyn Completer>,
|
|
}
|
|
|
|
impl CompletionActionHandler for FuzzyCompletion {
|
|
fn handle(&mut self, present_buffer: &mut LineBuffer) {
|
|
let completions = self
|
|
.completer
|
|
.complete(present_buffer.get_buffer(), present_buffer.offset());
|
|
|
|
if completions.is_empty() {
|
|
// do nothing
|
|
} else if completions.len() == 1 {
|
|
let span = completions[0].0;
|
|
|
|
let mut offset = present_buffer.offset();
|
|
offset += completions[0].1.len() - (span.end - span.start);
|
|
|
|
// TODO improve the support for multiline replace
|
|
present_buffer.replace(span.start..span.end, &completions[0].1);
|
|
present_buffer.set_insertion_point(offset);
|
|
} else {
|
|
let selections: Vec<_> = completions.iter().map(|(_, string)| string).collect();
|
|
|
|
let _ = crossterm::terminal::disable_raw_mode();
|
|
println!();
|
|
let theme = ColorfulTheme {
|
|
active_item_style: Style::new().for_stderr().on_green().black(),
|
|
..Default::default()
|
|
};
|
|
let result = Select::with_theme(&theme)
|
|
.default(0)
|
|
.items(&selections[..])
|
|
.interact_on_opt(&Term::stdout())
|
|
.unwrap_or(None);
|
|
let _ = crossterm::terminal::enable_raw_mode();
|
|
|
|
if let Some(result) = result {
|
|
let span = completions[result].0;
|
|
|
|
let mut offset = present_buffer.offset();
|
|
offset += completions[result].1.len() - (span.end - span.start);
|
|
|
|
// TODO improve the support for multiline replace
|
|
present_buffer.replace(span.start..span.end, &completions[result].1);
|
|
present_buffer.set_insertion_point(offset);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn main() -> Result<()> {
|
|
// miette::set_panic_hook();
|
|
let miette_hook = std::panic::take_hook();
|
|
std::panic::set_hook(Box::new(move |x| {
|
|
crossterm::terminal::disable_raw_mode().expect("unable to disable raw mode");
|
|
miette_hook(x);
|
|
}));
|
|
|
|
// Get initial current working directory.
|
|
let init_cwd = get_init_cwd();
|
|
let mut engine_state = create_default_context(&init_cwd);
|
|
|
|
// TODO: make this conditional in the future
|
|
// Ctrl-c protection section
|
|
let ctrlc = Arc::new(AtomicBool::new(false));
|
|
let handler_ctrlc = ctrlc.clone();
|
|
let engine_state_ctrlc = ctrlc.clone();
|
|
|
|
ctrlc::set_handler(move || {
|
|
handler_ctrlc.store(true, Ordering::SeqCst);
|
|
})
|
|
.expect("Error setting Ctrl-C handler");
|
|
|
|
engine_state.ctrlc = Some(engine_state_ctrlc);
|
|
// End ctrl-c protection section
|
|
|
|
if let Some(path) = std::env::args().nth(1) {
|
|
// First, set up env vars as strings only
|
|
gather_parent_env_vars(&mut engine_state);
|
|
|
|
let file = std::fs::read(&path).into_diagnostic()?;
|
|
|
|
let (block, delta) = {
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
trace!("parsing file: {}", path);
|
|
|
|
let (output, err) = parse(&mut working_set, Some(&path), &file, false);
|
|
if let Some(err) = err {
|
|
report_error(&working_set, &err);
|
|
|
|
std::process::exit(1);
|
|
}
|
|
(output, working_set.render())
|
|
};
|
|
|
|
if let Err(err) = engine_state.merge_delta(delta, None, &init_cwd) {
|
|
let working_set = StateWorkingSet::new(&engine_state);
|
|
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) => {
|
|
let working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
report_error(&working_set, &e);
|
|
Config::default()
|
|
}
|
|
};
|
|
|
|
// 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(&mut 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);
|
|
}
|
|
}
|
|
|
|
// Translate environment variables from Strings to Values
|
|
if let Some(e) = convert_env_values(&mut engine_state, &stack, &config) {
|
|
let working_set = StateWorkingSet::new(&engine_state);
|
|
report_error(&working_set, &e);
|
|
std::process::exit(1);
|
|
}
|
|
|
|
match eval_block(
|
|
&engine_state,
|
|
&mut stack,
|
|
&block,
|
|
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
|
) {
|
|
Ok(pipeline_data) => {
|
|
for item in pipeline_data {
|
|
if let Value::Error { error } = item {
|
|
let working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
report_error(&working_set, &error);
|
|
|
|
std::process::exit(1);
|
|
}
|
|
println!("{}", item.into_string("\n", &config));
|
|
}
|
|
|
|
// Next, let's check if there are any flags we want to pass to the main function
|
|
let args: Vec<String> = std::env::args().skip(2).collect();
|
|
|
|
if args.is_empty() && engine_state.find_decl(b"main").is_none() {
|
|
return Ok(());
|
|
}
|
|
|
|
let args = format!("main {}", args.join(" ")).as_bytes().to_vec();
|
|
|
|
let (block, delta) = {
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
let (output, err) = parse(&mut working_set, Some("<cmdline>"), &args, false);
|
|
if let Some(err) = err {
|
|
report_error(&working_set, &err);
|
|
|
|
std::process::exit(1);
|
|
}
|
|
(output, working_set.render())
|
|
};
|
|
|
|
let cwd = nu_engine::env::current_dir_str(&engine_state, &stack)?;
|
|
|
|
if let Err(err) = engine_state.merge_delta(delta, Some(&mut stack), &cwd) {
|
|
let working_set = StateWorkingSet::new(&engine_state);
|
|
report_error(&working_set, &err);
|
|
}
|
|
|
|
match eval_block(
|
|
&engine_state,
|
|
&mut stack,
|
|
&block,
|
|
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
|
) {
|
|
Ok(pipeline_data) => {
|
|
for item in pipeline_data {
|
|
if let Value::Error { error } = item {
|
|
let working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
report_error(&working_set, &error);
|
|
|
|
std::process::exit(1);
|
|
}
|
|
println!("{}", item.into_string("\n", &config));
|
|
}
|
|
}
|
|
Err(err) => {
|
|
let working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
report_error(&working_set, &err);
|
|
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
Err(err) => {
|
|
let working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
report_error(&working_set, &err);
|
|
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
} else {
|
|
use reedline::{FileBackedHistory, Reedline, Signal};
|
|
|
|
let mut entry_num = 0;
|
|
|
|
let mut nu_prompt = NushellPrompt::new();
|
|
let mut stack = nu_protocol::engine::Stack::new();
|
|
|
|
// First, set up env vars as strings only
|
|
gather_parent_env_vars(&mut 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),
|
|
},
|
|
);
|
|
|
|
#[cfg(feature = "plugin")]
|
|
{
|
|
// Reading signatures from signature file
|
|
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
|
if let Some(mut plugin_path) = nu_path::config_dir() {
|
|
// Path to store plugins signatures
|
|
plugin_path.push("nushell");
|
|
plugin_path.push("plugin.nu");
|
|
engine_state.plugin_signatures = Some(plugin_path.clone());
|
|
|
|
let plugin_filename = plugin_path.to_string_lossy().to_owned();
|
|
|
|
if let Ok(contents) = std::fs::read_to_string(&plugin_path) {
|
|
eval_source(&mut engine_state, &mut stack, &contents, &plugin_filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load config startup file
|
|
if let Some(mut config_path) = nu_path::config_dir() {
|
|
config_path.push("nushell");
|
|
|
|
// Create config directory if it does not exist
|
|
if !config_path.exists() {
|
|
if let Err(err) = std::fs::create_dir_all(&config_path) {
|
|
eprintln!("Failed to create config directory: {}", err);
|
|
}
|
|
} else {
|
|
config_path.push("config.nu");
|
|
|
|
if config_path.exists() {
|
|
// FIXME: remove this message when we're ready
|
|
println!("Loading config from: {:?}", config_path);
|
|
let config_filename = config_path.to_string_lossy().to_owned();
|
|
|
|
if let Ok(contents) = std::fs::read_to_string(&config_path) {
|
|
eval_source(&mut engine_state, &mut stack, &contents, &config_filename);
|
|
// 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(&mut 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get the config
|
|
let config = match stack.get_config() {
|
|
Ok(config) => config,
|
|
Err(e) => {
|
|
let working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
report_error(&working_set, &e);
|
|
Config::default()
|
|
}
|
|
};
|
|
|
|
use logger::{configure, logger};
|
|
|
|
logger(|builder| {
|
|
configure(&config.log_level, builder)?;
|
|
// trace_filters(self, builder)?;
|
|
// debug_filters(self, builder)?;
|
|
|
|
Ok(())
|
|
})?;
|
|
|
|
// Translate environment variables from Strings to Values
|
|
if let Some(e) = convert_env_values(&mut engine_state, &stack, &config) {
|
|
let working_set = StateWorkingSet::new(&engine_state);
|
|
report_error(&working_set, &e);
|
|
}
|
|
|
|
let history_path = nu_path::config_dir().and_then(|mut history_path| {
|
|
history_path.push("nushell");
|
|
history_path.push("history.txt");
|
|
|
|
if !history_path.exists() {
|
|
// Creating an empty file to store the history
|
|
match std::fs::File::create(&history_path) {
|
|
Ok(_) => Some(history_path),
|
|
Err(_) => None,
|
|
}
|
|
} else {
|
|
Some(history_path)
|
|
}
|
|
});
|
|
|
|
loop {
|
|
let config = match stack.get_config() {
|
|
Ok(config) => config,
|
|
Err(e) => {
|
|
let working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
report_error(&working_set, &e);
|
|
Config::default()
|
|
}
|
|
};
|
|
|
|
//Reset the ctrl-c handler
|
|
ctrlc.store(false, Ordering::SeqCst);
|
|
|
|
let mut keybindings = default_emacs_keybindings();
|
|
keybindings.add_binding(
|
|
KeyModifiers::SHIFT,
|
|
KeyCode::BackTab,
|
|
ReedlineEvent::Multiple(vec![
|
|
ReedlineEvent::Edit(vec![EditCommand::InsertChar('p')]),
|
|
ReedlineEvent::Enter,
|
|
]),
|
|
);
|
|
let edit_mode = Box::new(Emacs::new(keybindings));
|
|
|
|
let line_editor = Reedline::create()
|
|
.into_diagnostic()?
|
|
// .with_completion_action_handler(Box::new(FuzzyCompletion {
|
|
// completer: Box::new(NuCompleter::new(engine_state.clone())),
|
|
// }))
|
|
// .with_completion_action_handler(Box::new(
|
|
// ListCompletionHandler::default().with_completer(Box::new(completer)),
|
|
// ))
|
|
.with_highlighter(Box::new(NuHighlighter {
|
|
engine_state: engine_state.clone(),
|
|
config: config.clone(),
|
|
}))
|
|
.with_animation(config.animate_prompt)
|
|
.with_validator(Box::new(NuValidator {
|
|
engine_state: engine_state.clone(),
|
|
}))
|
|
.with_edit_mode(edit_mode)
|
|
.with_ansi_colors(config.use_ansi_coloring)
|
|
.with_menu_completer(Box::new(NuCompleter::new(engine_state.clone())));
|
|
//FIXME: if config.use_ansi_coloring is false then we should
|
|
// turn off the hinter but I don't see any way to do that yet.
|
|
|
|
let color_hm = get_color_config(&config);
|
|
|
|
let line_editor = if let Some(history_path) = history_path.clone() {
|
|
let history = std::fs::read_to_string(&history_path);
|
|
if history.is_ok() {
|
|
line_editor
|
|
.with_hinter(Box::new(
|
|
DefaultHinter::default()
|
|
.with_history()
|
|
.with_style(color_hm["hints"]),
|
|
))
|
|
.with_history(Box::new(
|
|
FileBackedHistory::with_file(
|
|
config.max_history_size as usize,
|
|
history_path.clone(),
|
|
)
|
|
.into_diagnostic()?,
|
|
))
|
|
.into_diagnostic()?
|
|
} else {
|
|
line_editor
|
|
}
|
|
} else {
|
|
line_editor
|
|
};
|
|
|
|
// The line editor default mode is emacs mode. For the moment we only
|
|
// need to check for vi mode
|
|
let mut line_editor = if config.edit_mode == "vi" {
|
|
let edit_mode = Box::new(Vi::default());
|
|
line_editor.with_edit_mode(edit_mode)
|
|
} else {
|
|
line_editor
|
|
};
|
|
|
|
let prompt = update_prompt(&config, &engine_state, &stack, &mut nu_prompt);
|
|
|
|
entry_num += 1;
|
|
|
|
let input = line_editor.read_line(prompt);
|
|
match input {
|
|
Ok(Signal::Success(s)) => {
|
|
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 path = nu_path::expand_path_with(&s, &cwd);
|
|
|
|
let orig = s.clone();
|
|
|
|
if (orig.starts_with('.')
|
|
|| orig.starts_with('~')
|
|
|| orig.starts_with('/')
|
|
|| orig.starts_with('\\'))
|
|
&& path.is_dir()
|
|
&& tokens.0.len() == 1
|
|
{
|
|
// We have an auto-cd
|
|
let (path, span) = {
|
|
if !path.exists() {
|
|
let working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
report_error(
|
|
&working_set,
|
|
&ShellError::DirectoryNotFound(tokens.0[0].span),
|
|
);
|
|
}
|
|
|
|
let path = nu_path::canonicalize_with(path, &cwd)
|
|
.expect("internal error: cannot canonicalize known path");
|
|
(path.to_string_lossy().to_string(), tokens.0[0].span)
|
|
};
|
|
|
|
//FIXME: this only changes the current scope, but instead this environment variable
|
|
//should probably be a block that loads the information from the state in the overlay
|
|
stack.add_env_var(
|
|
"PWD".into(),
|
|
Value::String {
|
|
val: path.clone(),
|
|
span: Span { start: 0, end: 0 },
|
|
},
|
|
);
|
|
let cwd = Value::String { val: cwd, span };
|
|
|
|
let shells = stack.get_env_var(&engine_state, "NUSHELL_SHELLS");
|
|
let mut shells = if let Some(v) = shells {
|
|
v.as_list()
|
|
.map(|x| x.to_vec())
|
|
.unwrap_or_else(|_| vec![cwd])
|
|
} else {
|
|
vec![cwd]
|
|
};
|
|
|
|
let current_shell =
|
|
stack.get_env_var(&engine_state, "NUSHELL_CURRENT_SHELL");
|
|
let current_shell = if let Some(v) = current_shell {
|
|
v.as_integer().unwrap_or_default() as usize
|
|
} else {
|
|
0
|
|
};
|
|
|
|
shells[current_shell] = Value::String { val: path, span };
|
|
|
|
stack.add_env_var(
|
|
"NUSHELL_SHELLS".into(),
|
|
Value::List { vals: shells, span },
|
|
);
|
|
} else {
|
|
trace!("eval source: {}", s);
|
|
|
|
eval_source(
|
|
&mut engine_state,
|
|
&mut stack,
|
|
&s,
|
|
&format!("entry #{}", entry_num),
|
|
);
|
|
}
|
|
// FIXME: permanent state changes like this hopefully in time can be removed
|
|
// and be replaced by just passing the cwd in where needed
|
|
if let Some(cwd) = stack.get_env_var(&engine_state, "PWD") {
|
|
let path = cwd.as_string()?;
|
|
let _ = std::env::set_current_dir(path);
|
|
engine_state.env_vars.insert("PWD".into(), cwd);
|
|
}
|
|
}
|
|
Ok(Signal::CtrlC) => {
|
|
// `Reedline` clears the line content. New prompt is shown
|
|
}
|
|
Ok(Signal::CtrlD) => {
|
|
// When exiting clear to a new line
|
|
println!();
|
|
break;
|
|
}
|
|
Ok(Signal::CtrlL) => {
|
|
line_editor.clear_screen().into_diagnostic()?;
|
|
}
|
|
Err(err) => {
|
|
let message = err.to_string();
|
|
if !message.contains("duration") {
|
|
println!("Error: {:?}", err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
// This fill 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.
|
|
fn gather_parent_env_vars(engine_state: &mut EngineState) {
|
|
// Some helper functions
|
|
fn get_surround_char(s: &str) -> Option<char> {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn print_pipeline_data(
|
|
input: PipelineData,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
) -> Result<(), ShellError> {
|
|
// If the table function is in the declarations, then we can use it
|
|
// to create the table value that will be printed in the terminal
|
|
|
|
let config = stack.get_config().unwrap_or_default();
|
|
|
|
match input {
|
|
PipelineData::StringStream(stream, _, _) => {
|
|
for s in stream {
|
|
print!("{}", s?);
|
|
let _ = std::io::stdout().flush();
|
|
}
|
|
return Ok(());
|
|
}
|
|
PipelineData::ByteStream(stream, _, _) => {
|
|
let mut address_offset = 0;
|
|
for v in stream {
|
|
let cfg = nu_pretty_hex::HexConfig {
|
|
title: false,
|
|
address_offset,
|
|
..Default::default()
|
|
};
|
|
|
|
let v = v?;
|
|
address_offset += v.len();
|
|
|
|
let s = if v.iter().all(|x| x.is_ascii()) {
|
|
format!("{}", String::from_utf8_lossy(&v))
|
|
} else {
|
|
nu_pretty_hex::config_hex(&v, cfg)
|
|
};
|
|
println!("{}", s);
|
|
}
|
|
return Ok(());
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
match engine_state.find_decl("table".as_bytes()) {
|
|
Some(decl_id) => {
|
|
let table =
|
|
engine_state
|
|
.get_decl(decl_id)
|
|
.run(engine_state, stack, &Call::new(), input)?;
|
|
|
|
for item in table {
|
|
let stdout = std::io::stdout();
|
|
|
|
if let Value::Error { error } = item {
|
|
return Err(error);
|
|
}
|
|
|
|
let mut out = item.into_string("\n", &config);
|
|
out.push('\n');
|
|
|
|
match stdout.lock().write_all(out.as_bytes()) {
|
|
Ok(_) => (),
|
|
Err(err) => eprintln!("{}", err),
|
|
};
|
|
}
|
|
}
|
|
None => {
|
|
for item in input {
|
|
let stdout = std::io::stdout();
|
|
|
|
if let Value::Error { error } = item {
|
|
return Err(error);
|
|
}
|
|
|
|
let mut out = item.into_string("\n", &config);
|
|
out.push('\n');
|
|
|
|
match stdout.lock().write_all(out.as_bytes()) {
|
|
Ok(_) => (),
|
|
Err(err) => eprintln!("{}", err),
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn get_prompt_indicators(
|
|
config: &Config,
|
|
engine_state: &EngineState,
|
|
stack: &Stack,
|
|
) -> (String, String, String, String) {
|
|
let prompt_indicator = match stack.get_env_var(engine_state, PROMPT_INDICATOR) {
|
|
Some(pi) => pi.into_string("", config),
|
|
None => "〉".to_string(),
|
|
};
|
|
|
|
let prompt_vi_insert = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_INSERT) {
|
|
Some(pvii) => pvii.into_string("", config),
|
|
None => ": ".to_string(),
|
|
};
|
|
|
|
let prompt_vi_visual = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_VISUAL) {
|
|
Some(pviv) => pviv.into_string("", config),
|
|
None => "v ".to_string(),
|
|
};
|
|
|
|
let prompt_multiline = match stack.get_env_var(engine_state, PROMPT_MULTILINE_INDICATOR) {
|
|
Some(pm) => pm.into_string("", config),
|
|
None => "::: ".to_string(),
|
|
};
|
|
|
|
(
|
|
prompt_indicator,
|
|
prompt_vi_insert,
|
|
prompt_vi_visual,
|
|
prompt_multiline,
|
|
)
|
|
}
|
|
|
|
fn update_prompt<'prompt>(
|
|
config: &Config,
|
|
engine_state: &EngineState,
|
|
stack: &Stack,
|
|
nu_prompt: &'prompt mut NushellPrompt,
|
|
) -> &'prompt dyn Prompt {
|
|
// get the other indicators
|
|
let (
|
|
prompt_indicator_string,
|
|
prompt_vi_insert_string,
|
|
prompt_vi_visual_string,
|
|
prompt_multiline_string,
|
|
) = get_prompt_indicators(config, engine_state, stack);
|
|
|
|
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),
|
|
prompt_indicator_string,
|
|
prompt_vi_insert_string,
|
|
prompt_vi_visual_string,
|
|
prompt_multiline_string,
|
|
);
|
|
|
|
nu_prompt as &dyn Prompt
|
|
}
|
|
|
|
fn get_prompt_string(
|
|
prompt: &str,
|
|
config: &Config,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
) -> Option<String> {
|
|
stack
|
|
.get_env_var(engine_state, prompt)
|
|
.and_then(|v| v.as_block().ok())
|
|
.and_then(|block_id| {
|
|
let block = engine_state.get_block(block_id);
|
|
eval_block(
|
|
engine_state,
|
|
stack,
|
|
block,
|
|
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
|
)
|
|
.ok()
|
|
})
|
|
.and_then(|pipeline_data| pipeline_data.collect_string("", config).ok())
|
|
}
|
|
|
|
fn eval_source(
|
|
engine_state: &mut EngineState,
|
|
stack: &mut Stack,
|
|
source: &str,
|
|
fname: &str,
|
|
) -> 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.as_bytes(),
|
|
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,
|
|
PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored
|
|
) {
|
|
Ok(pipeline_data) => {
|
|
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) => {
|
|
let working_set = StateWorkingSet::new(engine_state);
|
|
|
|
report_error(&working_set, &err);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
true
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
pub fn enable_vt_processing() -> Result<(), ShellError> {
|
|
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();
|
|
}
|
|
}
|
|
|
|
fn get_init_cwd() -> PathBuf {
|
|
match std::env::current_dir() {
|
|
Ok(cwd) => cwd,
|
|
Err(_) => match std::env::var("PWD".to_string()) {
|
|
Ok(cwd) => PathBuf::from(cwd),
|
|
Err(_) => match nu_path::home_dir() {
|
|
Some(cwd) => cwd,
|
|
None => PathBuf::new(),
|
|
},
|
|
},
|
|
}
|
|
}
|