1
0
mirror of https://github.com/nushell/nushell.git synced 2025-06-03 08:36:32 +02:00
YizhePKU 13df0af514
Set current working directory at startup ()
This PR sets the current working directory to the location of the
Nushell executable at startup, using `std::env::set_current_dir()`. This
is desirable because after PR
https://github.com/nushell/nushell/pull/12922, we no longer change our
current working directory even after `cd` is executed, and some OS might
lock the directory where Nushell started.

The location of the Nushell executable is chosen because it cannot be
removed while Nushell is running anyways, so we don't have to worry
about OS locking it.

This PR has the side effect that it breaks buggy command even harder.
I'll keep this PR as a draft until these commands are fixed, but it
might be helpful to pull this PR if you're working on fixing one of
those bugs.

---------

Co-authored-by: Devyn Cairns <devyn.cairns@gmail.com>
Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2024-09-25 13:04:26 -05:00

160 lines
5.9 KiB
Rust

use nu_engine::command_prelude::*;
use nu_utils::filesystem::{have_permission, PermissionResult};
#[derive(Clone)]
pub struct Cd;
impl Command for Cd {
fn name(&self) -> &str {
"cd"
}
fn description(&self) -> &str {
"Change directory."
}
fn search_terms(&self) -> Vec<&str> {
vec!["change", "directory", "dir", "folder", "switch"]
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("cd")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.switch("physical", "use the physical directory structure; resolve symbolic links before processing instances of ..", Some('P'))
.optional("path", SyntaxShape::Directory, "The path to change to.")
.input_output_types(vec![
(Type::Nothing, Type::Nothing),
(Type::String, Type::Nothing),
])
.allow_variants_without_examples(true)
.category(Category::FileSystem)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let physical = call.has_flag(engine_state, stack, "physical")?;
let path_val: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?;
// If getting PWD failed, default to the home directory. The user can
// use `cd` to reset PWD to a good state.
let cwd = engine_state
.cwd(Some(stack))
.ok()
.or_else(nu_path::home_dir)
.map(|path| path.into_std_path_buf())
.unwrap_or_default();
let path_val = {
if let Some(path) = path_val {
Some(Spanned {
item: nu_utils::strip_ansi_string_unlikely(path.item),
span: path.span,
})
} else {
path_val
}
};
let path = match path_val {
Some(v) => {
if v.item == "-" {
if let Some(oldpwd) = stack.get_env_var(engine_state, "OLDPWD") {
oldpwd.to_path()?
} else {
cwd
}
} else {
// Trim whitespace from the end of path.
let path_no_whitespace =
&v.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
// If `--physical` is specified, canonicalize the path; otherwise expand the path.
if physical {
if let Ok(path) = nu_path::canonicalize_with(path_no_whitespace, &cwd) {
if !path.is_dir() {
return Err(ShellError::NotADirectory { span: v.span });
};
path
} else {
return Err(ShellError::DirectoryNotFound {
dir: path_no_whitespace.to_string(),
span: v.span,
});
}
} else {
let path = nu_path::expand_path_with(path_no_whitespace, &cwd, true);
if !path.exists() {
return Err(ShellError::DirectoryNotFound {
dir: path_no_whitespace.to_string(),
span: v.span,
});
};
if !path.is_dir() {
return Err(ShellError::NotADirectory { span: v.span });
};
path
}
}
}
None => nu_path::expand_tilde("~"),
};
// Set OLDPWD.
// We're using `Stack::get_env_var()` instead of `EngineState::cwd()` to avoid a conversion roundtrip.
if let Some(oldpwd) = stack.get_env_var(engine_state, "PWD") {
stack.add_env_var("OLDPWD".into(), oldpwd)
}
match have_permission(&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
PermissionResult::PermissionOk => {
stack.set_cwd(path)?;
Ok(PipelineData::empty())
}
PermissionResult::PermissionDenied(reason) => Err(ShellError::IOError {
msg: format!(
"Cannot change directory to {}: {}",
path.to_string_lossy(),
reason
),
}),
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Change to your home directory",
example: r#"cd ~"#,
result: None,
},
Example {
description: r#"Change to the previous working directory (same as "cd $env.OLDPWD")"#,
example: r#"cd -"#,
result: None,
},
Example {
description: "Changing directory with a custom command requires 'def --env'",
example: r#"def --env gohome [] { cd ~ }"#,
result: None,
},
Example {
description: "Move two directories up in the tree (the parent directory's parent). Additional dots can be added for additional levels.",
example: r#"cd ..."#,
result: None,
},
Example {
description: "The cd command itself is often optional. Simply entering a path to a directory will cd to it.",
example: r#"/home"#,
result: None,
},
]
}
}