Use only $nu.env.PWD for getting the current directory (#587)

* Use only $nu.env.PWD for getting current directory

Because setting and reading to/from std::env changes the global state
shich is problematic if we call `cd` from multiple threads (e.g., in a
`par-each` block).

With this change, when engine-q starts, it will either inherit existing
PWD env var, or create a new one from `std::env::current_dir()`.
Otherwise, everything that needs the current directory will get it from
`$nu.env.PWD`. Each spawned external command will get its current
directory per-process which should be thread-safe.

One thing left to do is to patch nu-path for this as well since it uses
`std::env::current_dir()` in its expansions.

* Rename nu-path functions

*_with is not *_relative which should be more descriptive and frees
"with" for use in a followup commit.

* Clone stack every each iter; Fix some commands

Cloning the stack each iteration of `each` makes sure we're not reusing
PWD between iterations.

Some fixes in commands to make them use the new PWD.

* Post-rebase cleanup, fmt, clippy

* Change back _relative to _with in nu-path funcs

Didn't use the idea I had for the new "_with".

* Remove leftover current_dir from rebase

* Add cwd sync at merge_delta()

This makes sure the parser and completer always have up-to-date cwd.

* Always pass absolute path to glob in ls

* Do not allow PWD a relative path; Allow recovery

Makes it possible to recover PWD by proceeding with the REPL cycle.

* Clone stack in each also for byte/string stream

* (WIP) Start moving env variables to engine state

* (WIP) Move env vars to engine state (ugly)

Quick and dirty code.

* (WIP) Remove unused mut and args; Fmt

* (WIP) Fix dataframe tests

* (WIP) Fix missing args after rebase

* (WIP) Clone only env vars, not the whole stack

* (WIP) Add env var clone to `for` loop as well

* Minor edits

* Refactor merge_delta() to include stack merging.

Less error-prone than doing it manually.

* Clone env for each `update` command iteration

* Mark env var hidden only when found in eng. state

* Fix clippt warnings

* Add TODO about env var reading

* Do not clone empty environment in loops

* Remove extra cwd collection

* Split current_dir() into str and path; Fix autocd

* Make completions respect PWD env var
This commit is contained in:
Jakub Žádník
2022-01-05 00:30:34 +02:00
committed by GitHub
parent 8f6843c600
commit 74dcd91cc3
30 changed files with 424 additions and 177 deletions

View File

@ -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

View File

@ -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)| {

View File

@ -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::<Spanned<String>>(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<Value, ShellError> {
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,
});

View File

@ -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<PipelineData, ShellError> {
let path = current_dir()?;
let path = current_dir(engine_state, stack)?;
let mut directories = call
.rest::<String>(engine_state, stack, 0)?
.into_iter()

View File

@ -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());

View File

@ -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::<String>(engine_state, stack, 0)?
.into_iter()

View File

@ -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<String> = 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(

View File

@ -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::<Resource>::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 {