diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index 454d5e51a..028dd9332 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -104,9 +104,18 @@ impl NuCompleter { | nu_parser::FlatShape::String => { let prefix = working_set.get_span_contents(flat.0); let results = working_set.find_commands_by_prefix(prefix); + let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") + { + match d.as_string() { + Ok(s) => s, + Err(_) => "".to_string(), + } + } else { + "".to_string() + }; let prefix = String::from_utf8_lossy(prefix).to_string(); - let results2 = file_path_completion(flat.0, &prefix) + let results2 = file_path_completion(flat.0, &prefix, &cwd) .into_iter() .map(move |x| { ( @@ -137,8 +146,17 @@ impl NuCompleter { | nu_parser::FlatShape::ExternalArg => { let prefix = working_set.get_span_contents(flat.0); let prefix = String::from_utf8_lossy(prefix).to_string(); + let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") + { + match d.as_string() { + Ok(s) => s, + Err(_) => "".to_string(), + } + } else { + "".to_string() + }; - let results = file_path_completion(flat.0, &prefix); + let results = file_path_completion(flat.0, &prefix, &cwd); return results .into_iter() @@ -212,6 +230,7 @@ impl Completer for NuCompleter { fn file_path_completion( span: nu_protocol::Span, partial: &str, + cwd: &str, ) -> Vec<(nu_protocol::Span, String)> { use std::path::{is_separator, Path}; @@ -238,7 +257,7 @@ fn file_path_completion( (base, rest) }; - let base_dir = nu_path::expand_path(&base_dir_name); + let base_dir = nu_path::expand_path_with(&base_dir_name, cwd); // This check is here as base_dir.read_dir() with base_dir == "" will open the current dir // which we don't want in this case (if we did, base_dir would already be ".") if base_dir == Path::new("") { diff --git a/crates/nu-command/src/core_commands/for_.rs b/crates/nu-command/src/core_commands/for_.rs index 574b24f25..2e7171821 100644 --- a/crates/nu-command/src/core_commands/for_.rs +++ b/crates/nu-command/src/core_commands/for_.rs @@ -71,12 +71,16 @@ impl Command for For { let engine_state = engine_state.clone(); let block = engine_state.get_block(block_id).clone(); let mut stack = stack.collect_captures(&block.captures); + let orig_env_vars = stack.env_vars.clone(); + let orig_env_hidden = stack.env_hidden.clone(); match values { Value::List { vals, .. } => Ok(vals .into_iter() .enumerate() .map(move |(idx, x)| { + stack.with_env(&orig_env_vars, &orig_env_hidden); + stack.add_var( var_id, if numbered { @@ -107,6 +111,8 @@ impl Command for For { .into_range_iter()? .enumerate() .map(move |(idx, x)| { + stack.with_env(&orig_env_vars, &orig_env_hidden); + stack.add_var( var_id, if numbered { diff --git a/crates/nu-command/src/core_commands/hide.rs b/crates/nu-command/src/core_commands/hide.rs index 26d143030..5362f9d3a 100644 --- a/crates/nu-command/src/core_commands/hide.rs +++ b/crates/nu-command/src/core_commands/hide.rs @@ -98,12 +98,12 @@ impl Command for Hide { return Err(ShellError::NonUtf8(import_pattern.span())); }; - if stack.remove_env_var(&name).is_none() { + if stack.remove_env_var(engine_state, &name).is_none() { return Err(ShellError::NotFound(call.positional[0].span)); } } } else if !import_pattern.hidden.contains(&import_pattern.head.name) - && stack.remove_env_var(&head_name_str).is_none() + && stack.remove_env_var(engine_state, &head_name_str).is_none() { return Err(ShellError::NotFound(call.positional[0].span)); } diff --git a/crates/nu-command/src/dataframe/test_dataframe.rs b/crates/nu-command/src/dataframe/test_dataframe.rs index 5fc4cf79a..3b78750a2 100644 --- a/crates/nu-command/src/dataframe/test_dataframe.rs +++ b/crates/nu-command/src/dataframe/test_dataframe.rs @@ -32,7 +32,8 @@ pub fn test_dataframe(cmds: Vec>) { working_set.render() }; - let _ = engine_state.merge_delta(delta); + let cwd = std::env::current_dir().expect("Could not get current working directory."); + let _ = engine_state.merge_delta(delta, None, &cwd); for example in examples { // Skip tests that don't have results to compare to @@ -52,7 +53,7 @@ pub fn test_dataframe(cmds: Vec>) { (output, working_set.render()) }; - let _ = engine_state.merge_delta(delta); + let _ = engine_state.merge_delta(delta, None, &cwd); let mut stack = Stack::new(); diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 85197f3a6..8e66a4c60 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -1,8 +1,10 @@ use nu_protocol::engine::{EngineState, StateWorkingSet}; +use std::path::Path; + use crate::*; -pub fn create_default_context() -> EngineState { +pub fn create_default_context(cwd: impl AsRef) -> EngineState { let mut engine_state = EngineState::new(); let delta = { @@ -306,7 +308,7 @@ pub fn create_default_context() -> EngineState { working_set.render() }; - let _ = engine_state.merge_delta(delta); + let _ = engine_state.merge_delta(delta, None, &cwd); engine_state } diff --git a/crates/nu-command/src/env/env_command.rs b/crates/nu-command/src/env/env_command.rs index 3dc1fc3de..5d1f34fa8 100644 --- a/crates/nu-command/src/env/env_command.rs +++ b/crates/nu-command/src/env/env_command.rs @@ -29,7 +29,8 @@ impl Command for Env { let span = call.head; let config = stack.get_config().unwrap_or_default(); - let mut env_vars: Vec<(String, Value)> = stack.get_env_vars().into_iter().collect(); + let mut env_vars: Vec<(String, Value)> = + stack.get_env_vars(engine_state).into_iter().collect(); env_vars.sort_by(|(name1, _), (name2, _)| name1.cmp(name2)); let mut values = vec![]; diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index b44f6a097..a6570eeb9 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -47,7 +47,8 @@ pub fn test_examples(cmd: impl Command + 'static) { working_set.render() }; - let _ = engine_state.merge_delta(delta); + let cwd = std::env::current_dir().expect("Could not get current working directory."); + let _ = engine_state.merge_delta(delta, None, &cwd); for example in examples { // Skip tests that don't have results to compare to @@ -67,10 +68,19 @@ pub fn test_examples(cmd: impl Command + 'static) { (output, working_set.render()) }; - let _ = engine_state.merge_delta(delta); + let _ = engine_state.merge_delta(delta, None, &cwd); let mut stack = Stack::new(); + // Set up PWD + stack.add_env_var( + "PWD".to_string(), + Value::String { + val: cwd.to_string_lossy().to_string(), + span: Span::test_data(), + }, + ); + // Set up our initial config to start from stack.vars.insert( CONFIG_VARIABLE_ID, diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index 062512795..5ebdb60c0 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -1,3 +1,4 @@ +use nu_engine::env::current_dir_str; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -32,7 +33,10 @@ impl Command for Cd { let (path, span) = match path_val { Some(v) => { - let path = nu_path::expand_path(v.as_string()?); + let path = nu_path::canonicalize_with( + v.as_string()?, + current_dir_str(engine_state, stack)?, + )?; (path.to_string_lossy().to_string(), v.span()?) } None => { @@ -40,7 +44,6 @@ impl Command for Cd { (path.to_string_lossy().to_string(), call.head) } }; - let _ = std::env::set_current_dir(&path); //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 diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 011c89e67..060246b33 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -1,7 +1,7 @@ -use std::env::current_dir; use std::path::PathBuf; use super::util::get_interactive_confirmation; +use nu_engine::env::current_dir; use nu_engine::CallExt; use nu_path::canonicalize_with; use nu_protocol::ast::Call; @@ -49,7 +49,7 @@ impl Command for Cp { let interactive = call.has_flag("interactive"); let force = call.has_flag("force"); - let path = current_dir()?; + let path = current_dir(engine_state, stack)?; let source = path.join(source.as_str()); let destination = path.join(destination.as_str()); @@ -135,7 +135,7 @@ impl Command for Cp { for entry in sources.into_iter().flatten() { let mut sources = FileStructure::new(); - sources.walk_decorate(&entry)?; + sources.walk_decorate(&entry, engine_state, stack)?; if entry.is_file() { let sources = sources.paths_applying_with(|(source_file, _depth_level)| { diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 785d42252..a6c37bb39 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use nu_engine::env::current_dir; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -10,6 +11,7 @@ use nu_protocol::{ use std::io::ErrorKind; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; +use std::path::PathBuf; #[derive(Clone)] pub struct Ls; @@ -63,10 +65,17 @@ impl Command for Ls { let call_span = call.head; - let pattern = if let Some(mut result) = + let (pattern, prefix) = if let Some(result) = call.opt::>(engine_state, stack, 0)? { - let path = std::path::Path::new(&result.item); + let path = PathBuf::from(&result.item); + + let (mut path, prefix) = if path.is_relative() { + let cwd = current_dir(engine_state, stack)?; + (cwd.join(path), Some(cwd)) + } else { + (path, None) + }; if path.is_dir() { if permission_denied(&path) { @@ -92,16 +101,14 @@ impl Command for Ls { } if path.is_dir() { - if !result.item.ends_with(std::path::MAIN_SEPARATOR) { - result.item.push(std::path::MAIN_SEPARATOR); - } - result.item.push('*'); + path = path.join("*"); } } - result.item + (path.to_string_lossy().to_string(), prefix) } else { - "*".into() + let cwd = current_dir(engine_state, stack)?; + (cwd.join("*").to_string_lossy().to_string(), Some(cwd)) }; let glob = glob::glob(&pattern).map_err(|err| { @@ -144,11 +151,34 @@ impl Command for Ls { return None; } - let entry = - dir_entry_dict(&path, metadata.as_ref(), call_span, long, short_names); + let display_name = if short_names { + path.file_name().and_then(|s| s.to_str()) + } else if let Some(pre) = &prefix { + match path.strip_prefix(pre) { + Ok(stripped) => stripped.to_str(), + Err(_) => path.to_str(), + } + } else { + path.to_str() + } + .ok_or_else(|| { + ShellError::SpannedLabeledError( + format!("Invalid file name: {:}", path.to_string_lossy()), + "invalid file name".into(), + call_span, + ) + }); - match entry { - Ok(value) => Some(value), + match display_name { + Ok(name) => { + let entry = + dir_entry_dict(&path, name, metadata.as_ref(), call_span, long); + + match entry { + Ok(value) => Some(value), + Err(err) => Some(Value::Error { error: err }), + } + } Err(err) => Some(Value::Error { error: err }), } } @@ -213,7 +243,7 @@ fn path_contains_hidden_folder(path: &Path, folders: &[PathBuf]) -> bool { #[cfg(unix)] use std::os::unix::fs::FileTypeExt; -use std::path::{Path, PathBuf}; +use std::path::Path; pub fn get_file_type(md: &std::fs::Metadata) -> &str { let ft = md.file_type(); @@ -243,31 +273,18 @@ pub fn get_file_type(md: &std::fs::Metadata) -> &str { #[allow(clippy::too_many_arguments)] pub(crate) fn dir_entry_dict( - filename: &std::path::Path, + filename: &std::path::Path, // absolute path + display_name: &str, // gile name to be displayed metadata: Option<&std::fs::Metadata>, span: Span, long: bool, - short_name: bool, ) -> Result { let mut cols = vec![]; let mut vals = vec![]; - let name = if short_name { - filename.file_name().and_then(|s| s.to_str()) - } else { - filename.to_str() - } - .ok_or_else(|| { - ShellError::SpannedLabeledError( - format!("Invalid file name: {:}", filename.to_string_lossy()), - "invalid file name".into(), - span, - ) - })?; - cols.push("name".into()); vals.push(Value::String { - val: name.to_string(), + val: display_name.to_string(), span, }); diff --git a/crates/nu-command/src/filesystem/mkdir.rs b/crates/nu-command/src/filesystem/mkdir.rs index 741dff715..fdf05bf1e 100644 --- a/crates/nu-command/src/filesystem/mkdir.rs +++ b/crates/nu-command/src/filesystem/mkdir.rs @@ -1,6 +1,6 @@ use std::collections::VecDeque; -use std::env::current_dir; +use nu_engine::env::current_dir; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -39,7 +39,7 @@ impl Command for Mkdir { call: &Call, _input: PipelineData, ) -> Result { - let path = current_dir()?; + let path = current_dir(engine_state, stack)?; let mut directories = call .rest::(engine_state, stack, 0)? .into_iter() diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index c0f2cfe97..737143172 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -1,7 +1,7 @@ -use std::env::current_dir; -use std::path::{Path, PathBuf}; +use std::path::Path; use super::util::get_interactive_confirmation; +use nu_engine::env::current_dir; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -50,7 +50,7 @@ impl Command for Mv { let interactive = call.has_flag("interactive"); let force = call.has_flag("force"); - let path: PathBuf = current_dir()?; + let path = current_dir(engine_state, stack)?; let source = path.join(spanned_source.item.as_str()); let destination = path.join(destination.as_str()); diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index 6ffddde15..b49ce0629 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -1,10 +1,10 @@ -use std::env::current_dir; #[cfg(unix)] use std::os::unix::prelude::FileTypeExt; use std::path::PathBuf; use super::util::get_interactive_confirmation; +use nu_engine::env::current_dir; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -86,7 +86,7 @@ fn rm( )); } - let current_path = current_dir()?; + let current_path = current_dir(engine_state, stack)?; let mut paths = call .rest::(engine_state, stack, 0)? .into_iter() diff --git a/crates/nu-command/src/filesystem/touch.rs b/crates/nu-command/src/filesystem/touch.rs index ece811c97..21c6b9b49 100644 --- a/crates/nu-command/src/filesystem/touch.rs +++ b/crates/nu-command/src/filesystem/touch.rs @@ -1,6 +1,8 @@ use std::fs::OpenOptions; +use nu_engine::env::current_dir_str; use nu_engine::CallExt; +use nu_path::expand_path_with; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape}; @@ -39,7 +41,8 @@ impl Command for Touch { let rest: Vec = call.rest(engine_state, stack, 1)?; for (index, item) in vec![target].into_iter().chain(rest).enumerate() { - match OpenOptions::new().write(true).create(true).open(&item) { + let path = expand_path_with(&item, current_dir_str(engine_state, stack)?); + match OpenOptions::new().write(true).create(true).open(&path) { Ok(_) => continue, Err(err) => { return Err(ShellError::CreateNotPossible( diff --git a/crates/nu-command/src/filesystem/util.rs b/crates/nu-command/src/filesystem/util.rs index 8910314fa..a15c5a461 100644 --- a/crates/nu-command/src/filesystem/util.rs +++ b/crates/nu-command/src/filesystem/util.rs @@ -1,6 +1,8 @@ use std::path::{Path, PathBuf}; +use nu_engine::env::current_dir_str; use nu_path::canonicalize_with; +use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::ShellError; use dialoguer::Input; @@ -39,16 +41,27 @@ impl FileStructure { .collect() } - pub fn walk_decorate(&mut self, start_path: &Path) -> Result<(), ShellError> { + pub fn walk_decorate( + &mut self, + start_path: &Path, + engine_state: &EngineState, + stack: &Stack, + ) -> Result<(), ShellError> { self.resources = Vec::::new(); - self.build(start_path, 0)?; + self.build(start_path, 0, engine_state, stack)?; self.resources.sort(); Ok(()) } - fn build(&mut self, src: &Path, lvl: usize) -> Result<(), ShellError> { - let source = canonicalize_with(src, std::env::current_dir()?)?; + fn build( + &mut self, + src: &Path, + lvl: usize, + engine_state: &EngineState, + stack: &Stack, + ) -> Result<(), ShellError> { + let source = canonicalize_with(src, current_dir_str(engine_state, stack)?)?; if source.is_dir() { for entry in std::fs::read_dir(src)? { @@ -56,7 +69,7 @@ impl FileStructure { let path = entry.path(); if path.is_dir() { - self.build(&path, lvl + 1)?; + self.build(&path, lvl + 1, engine_state, stack)?; } self.resources.push(Resource { diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index 334bbb2c8..84ca53596 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -71,6 +71,8 @@ impl Command for Each { let engine_state = engine_state.clone(); let block = engine_state.get_block(block_id).clone(); let mut stack = stack.collect_captures(&block.captures); + let orig_env_vars = stack.env_vars.clone(); + let orig_env_hidden = stack.env_hidden.clone(); let span = call.head; match input { @@ -80,6 +82,8 @@ impl Command for Each { .into_iter() .enumerate() .map(move |(idx, x)| { + stack.with_env(&orig_env_vars, &orig_env_hidden); + if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { if numbered { @@ -113,6 +117,8 @@ impl Command for Each { .into_iter() .enumerate() .map(move |(idx, x)| { + stack.with_env(&orig_env_vars, &orig_env_hidden); + let x = match x { Ok(x) => Value::Binary { val: x, span }, Err(err) => return Value::Error { error: err }, @@ -151,6 +157,8 @@ impl Command for Each { .into_iter() .enumerate() .map(move |(idx, x)| { + stack.with_env(&orig_env_vars, &orig_env_hidden); + let x = match x { Ok(x) => Value::String { val: x, span }, Err(err) => return Value::Error { error: err }, @@ -192,7 +200,7 @@ impl Command for Each { for (col, val) in cols.into_iter().zip(vals.into_iter()) { let block = engine_state.get_block(block_id); - let mut stack = stack.clone(); + stack.with_env(&orig_env_vars, &orig_env_hidden); if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { diff --git a/crates/nu-command/src/filters/update.rs b/crates/nu-command/src/filters/update.rs index 78d4d9d75..2ee9cc9f6 100644 --- a/crates/nu-command/src/filters/update.rs +++ b/crates/nu-command/src/filters/update.rs @@ -74,9 +74,13 @@ fn update( let block = engine_state.get_block(block_id).clone(); let mut stack = stack.collect_captures(&block.captures); + let orig_env_vars = stack.env_vars.clone(); + let orig_env_hidden = stack.env_hidden.clone(); input.map( move |mut input| { + stack.with_env(&orig_env_vars, &orig_env_hidden); + if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { stack.add_var(*var_id, input.clone()) diff --git a/crates/nu-command/src/path/expand.rs b/crates/nu-command/src/path/expand.rs index 2cae2a164..8600556a0 100644 --- a/crates/nu-command/src/path/expand.rs +++ b/crates/nu-command/src/path/expand.rs @@ -1,7 +1,8 @@ use std::path::Path; +use nu_engine::env::current_dir_str; use nu_engine::CallExt; -use nu_path::{canonicalize, expand_path}; +use nu_path::{canonicalize_with, expand_path}; use nu_protocol::{engine::Command, Example, ShellError, Signature, Span, SyntaxShape, Value}; use super::PathSubcommandArguments; @@ -9,6 +10,7 @@ use super::PathSubcommandArguments; struct Arguments { strict: bool, columns: Option>, + cwd: String, } impl PathSubcommandArguments for Arguments { @@ -55,6 +57,7 @@ impl Command for SubCommand { let args = Arguments { strict: call.has_flag("strict"), columns: call.get_flag(engine_state, stack, "columns")?, + cwd: current_dir_str(engine_state, stack)?, }; input.map( @@ -107,7 +110,7 @@ impl Command for SubCommand { } fn expand(path: &Path, span: Span, args: &Arguments) -> Value { - if let Ok(p) = canonicalize(path) { + if let Ok(p) = canonicalize_with(path, &args.cwd) { Value::string(p.to_string_lossy(), span) } else if args.strict { Value::Error { diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 96d5f9c21..bf5f93f95 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::env; use std::io::{BufRead, BufReader, Write}; use std::process::{Command as CommandSys, Stdio}; use std::sync::atomic::Ordering; @@ -113,9 +112,19 @@ impl<'call> ExternalCommand<'call> { // TODO. We don't have a way to know the current directory // This should be information from the EvaluationContex or EngineState - let path = env::current_dir()?; - - process.current_dir(path); + if let Some(d) = self.env_vars.get("PWD") { + process.current_dir(d); + } else { + return Err(ShellError::SpannedLabeledErrorHelp( + "Current directory not found".to_string(), + "did not find PWD environment variable".to_string(), + head, + concat!( + "The environment variable 'PWD' was not found. ", + "It is required to define the current directory when running an external command." + ).to_string(), + )); + } process.envs(&self.env_vars); diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index 85f7aac30..bb9c4694f 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -62,7 +62,7 @@ prints out the list properly."# let color_param: bool = call.has_flag("color"); let separator_param: Option = call.get_flag(engine_state, stack, "separator")?; let config = stack.get_config().unwrap_or_default(); - let env_str = match stack.get_env_var("LS_COLORS") { + let env_str = match stack.get_env_var(engine_state, "LS_COLORS") { Some(v) => Some(env_to_string("LS_COLORS", v, engine_state, stack, &config)?), None => None, }; diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index b2b1a153c..90bc7d0f4 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -112,7 +112,7 @@ impl Command for Table { let config = config.clone(); let ctrlc = ctrlc.clone(); - let ls_colors = match stack.get_env_var("LS_COLORS") { + let ls_colors = match stack.get_env_var(engine_state, "LS_COLORS") { Some(v) => LsColors::from_string(&env_to_string( "LS_COLORS", v, diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index 68407034f..2f07d8d07 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::path::{Path, PathBuf}; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{Config, PipelineData, ShellError, Value}; @@ -17,69 +18,62 @@ const ENV_SEP: &str = ":"; /// skip errors. This function is called in the main() so we want to keep running, we cannot just /// exit. pub fn convert_env_values( - engine_state: &EngineState, - stack: &mut Stack, + engine_state: &mut EngineState, + stack: &Stack, config: &Config, ) -> Option { - let mut new_env_vars = vec![]; let mut error = None; - for scope in &stack.env_vars { - let mut new_scope = HashMap::new(); + let mut new_scope = HashMap::new(); - for (name, val) in scope { - if let Some(env_conv) = config.env_conversions.get(name) { - if let Some((block_id, from_span)) = env_conv.from_string { - let val_span = match val.span() { - Ok(sp) => sp, - Err(e) => { - error = error.or(Some(e)); - continue; + for (name, val) in &engine_state.env_vars { + if let Some(env_conv) = config.env_conversions.get(name) { + if let Some((block_id, from_span)) = env_conv.from_string { + let val_span = match val.span() { + Ok(sp) => sp, + Err(e) => { + error = error.or(Some(e)); + continue; + } + }; + + let block = engine_state.get_block(block_id); + + if let Some(var) = block.signature.get_positional(0) { + let mut stack = stack.collect_captures(&block.captures); + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, val.clone()); + } + + let result = + eval_block(engine_state, &mut stack, block, PipelineData::new(val_span)); + + match result { + Ok(data) => { + let val = data.into_value(val_span); + new_scope.insert(name.to_string(), val); } - }; - - let block = engine_state.get_block(block_id); - - if let Some(var) = block.signature.get_positional(0) { - let mut stack = stack.collect_captures(&block.captures); - if let Some(var_id) = &var.var_id { - stack.add_var(*var_id, val.clone()); - } - - let result = eval_block( - engine_state, - &mut stack, - block, - PipelineData::new(val_span), - ); - - match result { - Ok(data) => { - let val = data.into_value(val_span); - new_scope.insert(name.to_string(), val); - } - Err(e) => error = error.or(Some(e)), - } - } else { - error = error.or_else(|| { - Some(ShellError::MissingParameter( - "block input".into(), - from_span, - )) - }); + Err(e) => error = error.or(Some(e)), } } else { - new_scope.insert(name.to_string(), val.clone()); + error = error.or_else(|| { + Some(ShellError::MissingParameter( + "block input".into(), + from_span, + )) + }); } } else { new_scope.insert(name.to_string(), val.clone()); } + } else { + new_scope.insert(name.to_string(), val.clone()); } - - new_env_vars.push(new_scope); } - stack.env_vars = new_env_vars; + for (k, v) in new_scope { + engine_state.env_vars.insert(k, v); + } error } @@ -89,7 +83,7 @@ pub fn env_to_string( env_name: &str, value: Value, engine_state: &EngineState, - stack: &mut Stack, + stack: &Stack, config: &Config, ) -> Result { if let Some(env_conv) = config.env_conversions.get(env_name) { @@ -128,10 +122,10 @@ pub fn env_to_string( /// Translate all environment variables from Values to Strings pub fn env_to_strings( engine_state: &EngineState, - stack: &mut Stack, + stack: &Stack, config: &Config, ) -> Result, ShellError> { - let env_vars = stack.get_env_vars(); + let env_vars = stack.get_env_vars(engine_state); let mut env_vars_str = HashMap::new(); for (env_name, val) in env_vars { let val_str = env_to_string(&env_name, val, engine_state, stack, config)?; @@ -140,3 +134,33 @@ pub fn env_to_strings( Ok(env_vars_str) } + +/// Shorthand for env_to_string() for PWD with custom error +pub fn current_dir_str(engine_state: &EngineState, stack: &Stack) -> Result { + let config = stack.get_config()?; + if let Some(pwd) = stack.get_env_var(engine_state, "PWD") { + match env_to_string("PWD", pwd, engine_state, stack, &config) { + Ok(cwd) => { + if Path::new(&cwd).is_absolute() { + Ok(cwd) + } else { + Err(ShellError::LabeledError( + "Invalid current directory".to_string(), + format!("The 'PWD' environment variable must be set to an absolute path. Found: '{}'", cwd) + )) + } + } + Err(e) => Err(e), + } + } else { + Err(ShellError::LabeledError( + "Current directory not found".to_string(), + "The environment variable 'PWD' was not found. It is required to define the current directory.".to_string(), + )) + } +} + +/// Calls current_dir_str() and returns the current directory as a PathBuf +pub fn current_dir(engine_state: &EngineState, stack: &Stack) -> Result { + current_dir_str(engine_state, stack).map(PathBuf::from) +} diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 325631398..8216e2979 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -8,7 +8,7 @@ use nu_protocol::{ Spanned, Type, Unit, Value, VarId, ENV_VARIABLE_ID, }; -use crate::get_full_help; +use crate::{current_dir_str, get_full_help}; pub fn eval_operator(op: &Expression) -> Result { match op { @@ -575,15 +575,9 @@ pub fn eval_variable( // since the env var PWD doesn't exist on all platforms // lets just get the current directory - if let Ok(current_dir) = std::env::current_dir() { - if let Some(cwd) = current_dir.to_str() { - output_cols.push("cwd".into()); - output_vals.push(Value::String { - val: cwd.into(), - span, - }) - } - } + let cwd = current_dir_str(engine_state, stack)?; + output_cols.push("cwd".into()); + output_vals.push(Value::String { val: cwd, span }); if let Some(home_path) = nu_path::home_dir() { if let Some(home_path_str) = home_path.to_str() { @@ -886,7 +880,7 @@ pub fn eval_variable( span, }) } else if var_id == ENV_VARIABLE_ID { - let env_vars = stack.get_env_vars(); + let env_vars = stack.get_env_vars(engine_state); let env_columns = env_vars.keys(); let env_values = env_vars.values(); diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index 2e6864726..7a8095f78 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -1,7 +1,7 @@ mod call_ext; pub mod column; mod documentation; -mod env; +pub mod env; mod eval; pub use call_ext::CallExt; diff --git a/crates/nu-path/src/tilde.rs b/crates/nu-path/src/tilde.rs index e1c7ec56a..2ee1ccf45 100644 --- a/crates/nu-path/src/tilde.rs +++ b/crates/nu-path/src/tilde.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -fn expand_tilde_with(path: impl AsRef, home: Option) -> PathBuf { +fn expand_tilde_with_home(path: impl AsRef, home: Option) -> PathBuf { let path = path.as_ref(); if !path.starts_with("~") { @@ -27,7 +27,7 @@ fn expand_tilde_with(path: impl AsRef, home: Option) -> PathBuf { /// Expand tilde ("~") into a home directory if it is the first path component pub fn expand_tilde(path: impl AsRef) -> PathBuf { // TODO: Extend this to work with "~user" style of home paths - expand_tilde_with(path, dirs_next::home_dir()) + expand_tilde_with_home(path, dirs_next::home_dir()) } #[cfg(test)] @@ -37,17 +37,17 @@ mod tests { fn check_expanded(s: &str) { let home = Path::new("/home"); let buf = Some(PathBuf::from(home)); - assert!(expand_tilde_with(Path::new(s), buf).starts_with(&home)); + assert!(expand_tilde_with_home(Path::new(s), buf).starts_with(&home)); // Tests the special case in expand_tilde for "/" as home let home = Path::new("/"); let buf = Some(PathBuf::from(home)); - assert!(!expand_tilde_with(Path::new(s), buf).starts_with("//")); + assert!(!expand_tilde_with_home(Path::new(s), buf).starts_with("//")); } fn check_not_expanded(s: &str) { let home = PathBuf::from("/home"); - let expanded = expand_tilde_with(Path::new(s), Some(home)); + let expanded = expand_tilde_with_home(Path::new(s), Some(home)); assert!(expanded == Path::new(s)); } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 347ea0b2a..f1ac009f1 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -1,4 +1,4 @@ -use super::Command; +use super::{Command, Stack}; use crate::{ ast::Block, BlockId, DeclId, Example, Overlay, OverlayId, ShellError, Signature, Span, Type, VarId, @@ -9,6 +9,10 @@ use std::{ sync::{atomic::AtomicBool, Arc}, }; +use crate::Value; + +use std::path::Path; + #[cfg(feature = "plugin")] use std::path::PathBuf; @@ -140,6 +144,7 @@ pub struct EngineState { overlays: im::Vector, pub scope: im::Vector, pub ctrlc: Option>, + pub env_vars: im::HashMap, #[cfg(feature = "plugin")] pub plugin_signatures: Option, } @@ -167,6 +172,7 @@ impl EngineState { overlays: im::vector![], scope: im::vector![ScopeFrame::new()], ctrlc: None, + env_vars: im::HashMap::new(), #[cfg(feature = "plugin")] plugin_signatures: None, } @@ -179,7 +185,12 @@ impl EngineState { /// /// When we want to preserve what the parser has created, we can take its output (the `StateDelta`) and /// use this function to merge it into the global state. - pub fn merge_delta(&mut self, mut delta: StateDelta) -> Result<(), ShellError> { + pub fn merge_delta( + &mut self, + mut delta: StateDelta, + stack: Option<&mut Stack>, + cwd: impl AsRef, + ) -> Result<(), ShellError> { // Take the mutable reference and extend the permanent state from the working set self.files.extend(delta.files); self.file_contents.extend(delta.file_contents); @@ -216,6 +227,16 @@ impl EngineState { } } + if let Some(stack) = stack { + for mut env_scope in stack.env_vars.drain(..) { + for (k, v) in env_scope.drain() { + self.env_vars.insert(k, v); + } + } + } + + std::env::set_current_dir(cwd)?; + Ok(()) } @@ -1209,7 +1230,8 @@ mod engine_state_tests { working_set.render() }; - engine_state.merge_delta(delta)?; + let cwd = std::env::current_dir().expect("Could not get current working directory."); + engine_state.merge_delta(delta, None, &cwd)?; assert_eq!(engine_state.num_files(), 2); assert_eq!(&engine_state.files[0].0, "test.nu"); diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 32312ec7a..c9e277e89 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -1,5 +1,6 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use crate::engine::EngineState; use crate::{Config, ShellError, Value, VarId, CONFIG_VARIABLE_ID}; /// A runtime value stack used during evaluation @@ -25,6 +26,9 @@ pub struct Stack { pub vars: HashMap, /// Environment variables arranged as a stack to be able to recover values from parent scopes pub env_vars: Vec>, + /// Tells which environment variables from engine state are hidden. We don't need to track the + /// env vars in the stack since we can just delete them. + pub env_hidden: HashSet, } impl Default for Stack { @@ -38,6 +42,17 @@ impl Stack { Stack { vars: HashMap::new(), env_vars: vec![], + env_hidden: HashSet::new(), + } + } + + pub fn with_env(&mut self, env_vars: &[HashMap], env_hidden: &HashSet) { + if env_vars.iter().any(|scope| !scope.is_empty()) { + self.env_vars = env_vars.to_owned(); + } + + if !env_hidden.is_empty() { + self.env_hidden = env_hidden.clone(); } } @@ -54,6 +69,9 @@ impl Stack { } pub fn add_env_var(&mut self, var: String, value: Value) { + // if the env var was hidden, let's activate it again + self.env_hidden.remove(&var); + if let Some(scope) = self.env_vars.last_mut() { scope.insert(var, value); } else { @@ -85,8 +103,15 @@ impl Stack { } /// Flatten the env var scope frames into one frame - pub fn get_env_vars(&self) -> HashMap { - let mut result = HashMap::new(); + pub fn get_env_vars(&self, engine_state: &EngineState) -> HashMap { + // TODO: We're collecting im::HashMap to HashMap here. It might make sense to make these + // the same data structure. + let mut result: HashMap = engine_state + .env_vars + .iter() + .filter(|(k, _)| !self.env_hidden.contains(*k)) + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); for scope in &self.env_vars { result.extend(scope.clone()); @@ -95,24 +120,37 @@ impl Stack { result } - pub fn get_env_var(&self, name: &str) -> Option { + pub fn get_env_var(&self, engine_state: &EngineState, name: &str) -> Option { for scope in self.env_vars.iter().rev() { if let Some(v) = scope.get(name) { return Some(v.clone()); } } - None + if self.env_hidden.contains(name) { + None + } else { + engine_state.env_vars.get(name).cloned() + } } - pub fn remove_env_var(&mut self, name: &str) -> Option { + pub fn remove_env_var(&mut self, engine_state: &EngineState, name: &str) -> Option { for scope in self.env_vars.iter_mut().rev() { if let Some(v) = scope.remove(name) { return Some(v); } } - None + if self.env_hidden.contains(name) { + // the environment variable is already hidden + None + } else if let Some(val) = engine_state.env_vars.get(name) { + // the environment variable was found in the engine state => mark it as hidden + self.env_hidden.insert(name.to_string()); + Some(val.clone()) + } else { + None + } } pub fn get_config(&self) -> Result { diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index d5a78c0ea..0d3a005bb 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -96,12 +96,9 @@ pub enum ShellError { VariableNotFoundAtRuntime(#[label = "variable not found"] Span), #[error("Environment variable not found")] - #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))] + #[diagnostic(code(nu::shell::env_variable_not_found), url(docsrs))] EnvVarNotFoundAtRuntime(#[label = "environment variable not found"] Span), - // #[error("Environment variable is not a string")] - // #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))] - // EnvVarNotAString(#[label = "does not evaluate to a string"] Span), #[error("Not found.")] #[diagnostic(code(nu::parser::not_found), url(docsrs))] NotFound(#[label = "did not find anything under this name"] Span), @@ -185,7 +182,7 @@ pub enum ShellError { PluginFailedToDecode(String), #[error("I/O error")] - #[diagnostic(code(nu::shell::io_error), url(docsrs))] + #[diagnostic(code(nu::shell::io_error), url(docsrs), help("{0}"))] IOError(String), #[error("Directory not found")] diff --git a/crates/nu_plugin_gstat/src/gstat.rs b/crates/nu_plugin_gstat/src/gstat.rs index 53c46b51f..d799302e4 100644 --- a/crates/nu_plugin_gstat/src/gstat.rs +++ b/crates/nu_plugin_gstat/src/gstat.rs @@ -65,6 +65,9 @@ impl GStat { } // This path has to exist + // TODO: If the path is relative, it will be expanded using `std::env::current_dir` and not + // the "PWD" environment variable. We would need a way to read the engine's environment + // variables here. if !std::path::Path::new(&a_path.item).exists() { return Err(LabeledError { label: "error with path".to_string(), diff --git a/src/main.rs b/src/main.rs index 99c5d3135..7d899821d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ use nu_protocol::{ use reedline::{Completer, CompletionActionHandler, DefaultHinter, LineBuffer, Prompt, Vi}; use std::{ io::Write, + path::PathBuf, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -97,7 +98,9 @@ fn main() -> Result<()> { miette_hook(x); })); - let mut engine_state = create_default_context(); + // 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 @@ -129,7 +132,7 @@ fn main() -> Result<()> { (output, working_set.render()) }; - if let Err(err) = engine_state.merge_delta(delta) { + if let Err(err) = engine_state.merge_delta(delta, None, &init_cwd) { let working_set = StateWorkingSet::new(&engine_state); report_error(&working_set, &err); } @@ -137,7 +140,7 @@ fn main() -> Result<()> { let mut stack = nu_protocol::engine::Stack::new(); // First, set up env vars as strings only - gather_parent_env_vars(&mut engine_state, &mut stack); + gather_parent_env_vars(&mut engine_state); // Set up our initial config to start from stack.vars.insert( @@ -160,7 +163,7 @@ fn main() -> Result<()> { }; // Translate environment variables from Strings to Values - if let Some(e) = convert_env_values(&engine_state, &mut stack, &config) { + 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); @@ -204,7 +207,9 @@ fn main() -> Result<()> { (output, working_set.render()) }; - if let Err(err) = engine_state.merge_delta(delta) { + 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); } @@ -255,7 +260,7 @@ fn main() -> Result<()> { let mut stack = nu_protocol::engine::Stack::new(); // First, set up env vars as strings only - gather_parent_env_vars(&mut engine_state, &mut stack); + gather_parent_env_vars(&mut engine_state); // Set up our initial config to start from stack.vars.insert( @@ -331,7 +336,7 @@ fn main() -> Result<()> { })?; // Translate environment variables from Strings to Values - if let Some(e) = convert_env_values(&engine_state, &mut stack, &config) { + if let Some(e) = convert_env_values(&mut engine_state, &stack, &config) { let working_set = StateWorkingSet::new(&engine_state); report_error(&working_set, &e); } @@ -429,7 +434,8 @@ fn main() -> Result<()> { 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 path = nu_path::expand_path(&s); + 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('.') @@ -440,8 +446,6 @@ fn main() -> Result<()> { && tokens.0.len() == 1 { // We have an auto-cd - let _ = std::env::set_current_dir(&path); - //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( @@ -493,7 +497,8 @@ fn main() -> Result<()> { // 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, stack: &mut Stack) { +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('\'') { @@ -517,10 +522,14 @@ fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { ); } - let mut fake_env_file = String::new(); - for (name, val) in std::env::vars() { + 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)) { + 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 @@ -529,19 +538,53 @@ fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { &format!("{}={}", name, val), "Name or value should not contain both ' and \" at the same time.", ); - continue; + return; }; fake_env_file.push(c_name); - fake_env_file.push_str(&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_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( @@ -622,7 +665,8 @@ fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { continue; }; - stack.add_env_var(name, value); + // stack.add_env_var(name, value); + engine_state.env_vars.insert(name, value); } } } @@ -714,23 +758,27 @@ fn print_pipeline_data( Ok(()) } -fn get_prompt_indicators(config: &Config, stack: &Stack) -> (String, String, String, String) { - let prompt_indicator = match stack.get_env_var(PROMPT_INDICATOR) { +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(PROMPT_INDICATOR_VI_INSERT) { + 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(PROMPT_INDICATOR_VI_VISUAL) { + 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(PROMPT_MULTILINE_INDICATOR) { + let prompt_multiline = match stack.get_env_var(engine_state, PROMPT_MULTILINE_INDICATOR) { Some(pm) => pm.into_string("", config), None => "::: ".to_string(), }; @@ -755,9 +803,9 @@ fn update_prompt<'prompt>( prompt_vi_insert_string, prompt_vi_visual_string, prompt_multiline_string, - ) = get_prompt_indicators(config, stack); + ) = get_prompt_indicators(config, engine_state, stack); - let prompt_command_block_id = match stack.get_env_var(PROMPT_COMMAND) { + let prompt_command_block_id = match stack.get_env_var(engine_state, PROMPT_COMMAND) { Some(v) => match v.as_block() { Ok(b) => b, Err(_) => { @@ -859,7 +907,16 @@ fn eval_source( (output, working_set.render()) }; - if let Err(err) = engine_state.merge_delta(delta) { + 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); } @@ -929,3 +986,16 @@ pub fn report_error( 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(), + }, + }, + } +}