mirror of
https://github.com/nushell/nushell
synced 2025-01-26 03:45:19 +00:00
c8b5909ee8
This PR implements PWD-per-drive as described in discussion #14355 # Description On Windows, CMD or PowerShell assigns each drive its own current directory. For example, if you are in 'C:\Windows', switch to 'D:', and navigate to 'D:\Game', you can return to 'C:\Windows' by simply typing 'C:'. This PR enables Nushell on Windows to have the same capability, allowing each drive to maintain its own PWD (Present Working Directory). # User-Facing Changes Currently, 'cd' or 'ls' only accept absolute paths if the path starts with 'C:' or another drive letter. With PWD-per-drive, users can use 'cd' (or auto cd) and 'ls' in the same way as 'cd' and 'dir' in PowerShell, or similarly to 'cd' and 'dir' in CMD (noting that cd in CMD has slightly different behavior, 'cd' for another drive only changes current directory of that drive, but does not switch there). Interaction example on switching between drives: ```Nushell ~>D: D:\>cd Test D:\Test\>C: ~>D: D:\Test\>C: ~>cd D:.. D:\>C:x/../y/../z/.. ~>cd D:Test\Test D:\Test\Test>C: ~>D:... D:\> ``` Interaction example on auto-completion at cmd line: ```Nushell ~>cd D:\test[Enter] D:\test>~[Enter] ~>D:[TAB] ~>D:\test[Enter] D:\test>c:.c[TAB] c:\users\nushell\.cargo\ c:\users\nushell\.config\ ``` Interaction example on pass PWD-per-drive to child process: (Note CMD will use it, but PowerShell will ignore it though it still prepares such info for child process) ```Nushell ~>cd D:\Test D:\Test>cd E:\Test E:\Test\>~ ~>CMD Microsoft Windows [Version 10.0.22631.4460] (c) Microsoft Corporation. All rights reserved. C:\Users\Nushell>d: D:\Test>e: E:\Test> ``` # Brief Change Description 1.Added 'crates/nu-path/src/pwd_per_drive.rs' to implement a 26-slot array mapping drive letters to PWDs. Test cases are included in the same file, along with a doctest for the usage of PWD-per-drive. 2. Modified 'crates/nu-path/src/lib.rs' to declare module of pwd_per_drive and export struct for PWD-per-drive. 3. Modified 'crates/nu-protocol/src/engine/stack.rs' to sync PWD when set_cwd() is called. Add PWD-per-drive map as member. Clone between parent and child. Stub/proxy for nu_path::expand_path_with() to facilitate filesystem commands using PWD-per-drive. 4. Modified 'crates/nu-cli/src/repl.rs' auto_cd uses PWD-per-drive to expand path. 5. Modified 'crates/nu-cli/src/completions/completion_common.rs' to expand relative path when press [TAB] at command line. 6. Modified 'crates/nu-engine/src/env.rs' to collect PWD-per-drive info as env vars for child process as CMD or PowerShell do, this can let child process inherit PWD-per-drive info. 7. Modified 'crates/nu-engine/src/eval.rs', caller clone callee's PWD-per-drive info, supporting 'def --env' 8. Modified 'crates/nu-engine/src/eval_ir.rs', 'def --env' support. Remove duplicated fn redirect_env() 9. Modified 'src/run.rs', to init PWD-per-drive when startup. filesystem commands that modified: 1. Modified 'crates/nu-command/src/filesystem/cd.rs', 1 line change to use stackscoped PWD-per-drive. Other commands, commit pending.... Local test def --env OK: ```nushell E:\study\nushell> def --env env_cd_demo [] { ::: cd ~ ::: cd D:\Project ::: cd E:Crates ::: } E:\study\nushell> E:\study\nushell> def cd_no_demo [] { ::: cd ~ ::: cd D:\Project ::: cd E:Crates ::: } E:\study\nushell> cd_no_demo E:\study\nushell> C: C:\>D: D:\>E: E:\study\nushell>env_cd_demo E:\study\nushell\crates> C: ~>D: D:\Project>E: E:\study\nushell\crates> ``` # Tests + Formatting - `cargo fmt --all -- --check` passed. - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` passed. - `cargo test --workspace` passed on Windows developer mode and Ubuntu. - `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` passed. - nushell: ``` > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` passed --------- Co-authored-by: pegasus.cadence@gmail.com <pegasus.cadence@gmail.com>
245 lines
8.3 KiB
Rust
245 lines
8.3 KiB
Rust
use crate::{
|
|
command,
|
|
config_files::{self, setup_config},
|
|
};
|
|
use log::trace;
|
|
#[cfg(feature = "plugin")]
|
|
use nu_cli::read_plugin_file;
|
|
use nu_cli::{evaluate_commands, evaluate_file, evaluate_repl, EvaluateCommandsOpts};
|
|
use nu_protocol::{
|
|
engine::{EngineState, Stack},
|
|
report_shell_error, PipelineData, Spanned,
|
|
};
|
|
use nu_utils::perf;
|
|
|
|
#[cfg(windows)]
|
|
fn init_pwd_per_drive(engine_state: &EngineState, stack: &mut Stack) {
|
|
use nu_path::DriveToPwdMap;
|
|
use std::path::Path;
|
|
|
|
// Read environment for PWD-per-drive
|
|
for drive_letter in 'A'..='Z' {
|
|
let env_var = DriveToPwdMap::env_var_for_drive(drive_letter);
|
|
if let Some(env_pwd) = engine_state.get_env_var(&env_var) {
|
|
if let Ok(pwd_str) = nu_engine::env_to_string(&env_var, env_pwd, engine_state, stack) {
|
|
trace!("Get Env({}) {}", env_var, pwd_str);
|
|
let _ = stack.pwd_per_drive.set_pwd(Path::new(&pwd_str));
|
|
stack.remove_env_var(engine_state, &env_var);
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Ok(abs_pwd) = engine_state.cwd(None) {
|
|
if let Some(abs_pwd_str) = abs_pwd.to_str() {
|
|
let _ = stack.pwd_per_drive.set_pwd(Path::new(abs_pwd_str));
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn run_commands(
|
|
engine_state: &mut EngineState,
|
|
parsed_nu_cli_args: command::NushellCliArgs,
|
|
use_color: bool,
|
|
commands: &Spanned<String>,
|
|
input: PipelineData,
|
|
entire_start_time: std::time::Instant,
|
|
) {
|
|
trace!("run_commands");
|
|
|
|
let start_time = std::time::Instant::now();
|
|
let create_scaffold = nu_path::nu_config_dir().map_or(false, |p| !p.exists());
|
|
|
|
let mut stack = Stack::new();
|
|
#[cfg(windows)]
|
|
init_pwd_per_drive(engine_state, &mut stack);
|
|
|
|
// if the --no-config-file(-n) option is NOT passed, load the plugin file,
|
|
// load the default env file or custom (depending on parsed_nu_cli_args.env_file),
|
|
// and maybe a custom config file (depending on parsed_nu_cli_args.config_file)
|
|
//
|
|
// if the --no-config-file(-n) flag is passed, do not load plugin, env, or config files
|
|
if parsed_nu_cli_args.no_config_file.is_none() {
|
|
#[cfg(feature = "plugin")]
|
|
read_plugin_file(engine_state, parsed_nu_cli_args.plugin_file);
|
|
|
|
perf!("read plugins", start_time, use_color);
|
|
|
|
let start_time = std::time::Instant::now();
|
|
// If we have a env file parameter *OR* we have a login shell parameter, read the env file
|
|
if parsed_nu_cli_args.env_file.is_some() || parsed_nu_cli_args.login_shell.is_some() {
|
|
config_files::read_config_file(
|
|
engine_state,
|
|
&mut stack,
|
|
parsed_nu_cli_args.env_file,
|
|
true,
|
|
create_scaffold,
|
|
);
|
|
} else {
|
|
config_files::read_default_env_file(engine_state, &mut stack)
|
|
}
|
|
|
|
perf!("read env.nu", start_time, use_color);
|
|
|
|
let start_time = std::time::Instant::now();
|
|
let create_scaffold = nu_path::nu_config_dir().map_or(false, |p| !p.exists());
|
|
|
|
// If we have a config file parameter *OR* we have a login shell parameter, read the config file
|
|
if parsed_nu_cli_args.config_file.is_some() || parsed_nu_cli_args.login_shell.is_some() {
|
|
config_files::read_config_file(
|
|
engine_state,
|
|
&mut stack,
|
|
parsed_nu_cli_args.config_file,
|
|
false,
|
|
create_scaffold,
|
|
);
|
|
}
|
|
|
|
perf!("read config.nu", start_time, use_color);
|
|
|
|
// If we have a login shell parameter, read the login file
|
|
let start_time = std::time::Instant::now();
|
|
if parsed_nu_cli_args.login_shell.is_some() {
|
|
config_files::read_loginshell_file(engine_state, &mut stack);
|
|
}
|
|
|
|
perf!("read login.nu", start_time, use_color);
|
|
}
|
|
|
|
// Before running commands, set up the startup time
|
|
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
|
|
|
|
// Regenerate the $nu constant to contain the startup time and any other potential updates
|
|
engine_state.generate_nu_constant();
|
|
|
|
let start_time = std::time::Instant::now();
|
|
let result = evaluate_commands(
|
|
commands,
|
|
engine_state,
|
|
&mut stack,
|
|
input,
|
|
EvaluateCommandsOpts {
|
|
table_mode: parsed_nu_cli_args.table_mode,
|
|
error_style: parsed_nu_cli_args.error_style,
|
|
no_newline: parsed_nu_cli_args.no_newline.is_some(),
|
|
},
|
|
);
|
|
perf!("evaluate_commands", start_time, use_color);
|
|
|
|
if let Err(err) = result {
|
|
report_shell_error(engine_state, &err);
|
|
std::process::exit(err.exit_code().unwrap_or(0));
|
|
}
|
|
}
|
|
|
|
pub(crate) fn run_file(
|
|
engine_state: &mut EngineState,
|
|
parsed_nu_cli_args: command::NushellCliArgs,
|
|
use_color: bool,
|
|
script_name: String,
|
|
args_to_script: Vec<String>,
|
|
input: PipelineData,
|
|
) {
|
|
trace!("run_file");
|
|
let mut stack = Stack::new();
|
|
#[cfg(windows)]
|
|
init_pwd_per_drive(engine_state, &mut stack);
|
|
|
|
// if the --no-config-file(-n) option is NOT passed, load the plugin file,
|
|
// load the default env file or custom (depending on parsed_nu_cli_args.env_file),
|
|
// and maybe a custom config file (depending on parsed_nu_cli_args.config_file)
|
|
//
|
|
// if the --no-config-file(-n) flag is passed, do not load plugin, env, or config files
|
|
if parsed_nu_cli_args.no_config_file.is_none() {
|
|
let start_time = std::time::Instant::now();
|
|
let create_scaffold = nu_path::nu_config_dir().map_or(false, |p| !p.exists());
|
|
#[cfg(feature = "plugin")]
|
|
read_plugin_file(engine_state, parsed_nu_cli_args.plugin_file);
|
|
perf!("read plugins", start_time, use_color);
|
|
|
|
let start_time = std::time::Instant::now();
|
|
// only want to load config and env if relative argument is provided.
|
|
if parsed_nu_cli_args.env_file.is_some() {
|
|
config_files::read_config_file(
|
|
engine_state,
|
|
&mut stack,
|
|
parsed_nu_cli_args.env_file,
|
|
true,
|
|
create_scaffold,
|
|
);
|
|
} else {
|
|
config_files::read_default_env_file(engine_state, &mut stack)
|
|
}
|
|
perf!("read env.nu", start_time, use_color);
|
|
|
|
let start_time = std::time::Instant::now();
|
|
if parsed_nu_cli_args.config_file.is_some() {
|
|
config_files::read_config_file(
|
|
engine_state,
|
|
&mut stack,
|
|
parsed_nu_cli_args.config_file,
|
|
false,
|
|
create_scaffold,
|
|
);
|
|
}
|
|
perf!("read config.nu", start_time, use_color);
|
|
}
|
|
|
|
// Regenerate the $nu constant to contain the startup time and any other potential updates
|
|
engine_state.generate_nu_constant();
|
|
|
|
let start_time = std::time::Instant::now();
|
|
let result = evaluate_file(
|
|
script_name,
|
|
&args_to_script,
|
|
engine_state,
|
|
&mut stack,
|
|
input,
|
|
);
|
|
perf!("evaluate_file", start_time, use_color);
|
|
|
|
if let Err(err) = result {
|
|
report_shell_error(engine_state, &err);
|
|
std::process::exit(err.exit_code().unwrap_or(0));
|
|
}
|
|
}
|
|
|
|
pub(crate) fn run_repl(
|
|
engine_state: &mut EngineState,
|
|
parsed_nu_cli_args: command::NushellCliArgs,
|
|
entire_start_time: std::time::Instant,
|
|
) -> Result<(), miette::ErrReport> {
|
|
trace!("run_repl");
|
|
let mut stack = Stack::new();
|
|
#[cfg(windows)]
|
|
init_pwd_per_drive(engine_state, &mut stack);
|
|
|
|
let start_time = std::time::Instant::now();
|
|
|
|
if parsed_nu_cli_args.no_config_file.is_none() {
|
|
setup_config(
|
|
engine_state,
|
|
&mut stack,
|
|
#[cfg(feature = "plugin")]
|
|
parsed_nu_cli_args.plugin_file,
|
|
parsed_nu_cli_args.config_file,
|
|
parsed_nu_cli_args.env_file,
|
|
parsed_nu_cli_args.login_shell.is_some(),
|
|
);
|
|
}
|
|
|
|
// Reload use_color from config in case it's different from the default value
|
|
let use_color = engine_state.get_config().use_ansi_coloring;
|
|
perf!("setup_config", start_time, use_color);
|
|
|
|
let start_time = std::time::Instant::now();
|
|
let ret_val = evaluate_repl(
|
|
engine_state,
|
|
stack,
|
|
parsed_nu_cli_args.execute,
|
|
parsed_nu_cli_args.no_std_lib,
|
|
entire_start_time,
|
|
);
|
|
perf!("evaluate_repl", start_time, use_color);
|
|
|
|
ret_val
|
|
}
|