Migrate to a new PWD API (#12603)

This is the first PR towards migrating to a new `$env.PWD` API that
returns potentially un-canonicalized paths. Refer to PR #12515 for
motivations.

## New API: `EngineState::cwd()`

The goal of the new API is to cover both parse-time and runtime use
case, and avoid unintentional misuse. It takes an `Option<Stack>` as
argument, which if supplied, will search for `$env.PWD` on the stack in
additional to the engine state. I think with this design, there's less
confusion over parse-time and runtime environments. If you have access
to a stack, just supply it; otherwise supply `None`.

## Deprecation of other PWD-related APIs

Other APIs are re-implemented using `EngineState::cwd()` and properly
documented. They're marked deprecated, but their behavior is unchanged.
Unused APIs are deleted, and code that accesses `$env.PWD` directly
without using an API is rewritten.

Deprecated APIs:

* `EngineState::current_work_dir()`
* `StateWorkingSet::get_cwd()`
* `env::current_dir()`
* `env::current_dir_str()`
* `env::current_dir_const()`
* `env::current_dir_str_const()`

Other changes:

* `EngineState::get_cwd()` (deleted)
* `StateWorkingSet::list_env()` (deleted)
* `repl::do_run_cmd()` (rewritten with `env::current_dir_str()`)

## `cd` and `pwd` now use logical paths by default

This pulls the changes from PR #12515. It's currently somewhat broken
because using non-canonicalized paths exposed a bug in our path
normalization logic (Issue #12602). Once that is fixed, this should
work.

## Future plans

This PR needs some tests. Which test helpers should I use, and where
should I put those tests?

I noticed that unquoted paths are expanded within `eval_filepath()` and
`eval_directory()` before they even reach the `cd` command. This means
every paths is expanded twice. Is this intended?

Once this PR lands, the plan is to review all usages of the deprecated
APIs and migrate them to `EngineState::cwd()`. In the meantime, these
usages are annotated with `#[allow(deprecated)]` to avoid breaking CI.

---------

Co-authored-by: Jakub Žádník <kubouch@gmail.com>
This commit is contained in:
YizhePKU
2024-05-03 19:33:09 +08:00
committed by GitHub
parent f32ecc641f
commit bdb6daa4b5
44 changed files with 551 additions and 275 deletions

View File

@ -43,6 +43,7 @@ impl Completer for DirectoryCompletion {
let AdjustView { prefix, span, .. } = adjust_if_intermediate(&prefix, working_set, span);
// Filter only the folders
#[allow(deprecated)]
let output: Vec<_> = directory_completion(
span,
&prefix,

View File

@ -84,6 +84,7 @@ impl Completer for DotNuCompletion {
partial = base_dir_partial;
} else {
// Fetch the current folder
#[allow(deprecated)]
let current_folder = self.engine_state.current_work_dir();
is_current_folder = true;

View File

@ -47,6 +47,7 @@ impl Completer for FileCompletion {
readjusted,
} = adjust_if_intermediate(&prefix, working_set, span);
#[allow(deprecated)]
let output: Vec<_> = complete_item(
readjusted,
span,

View File

@ -177,6 +177,7 @@ pub fn add_plugin_file(
use std::path::Path;
let working_set = StateWorkingSet::new(engine_state);
#[allow(deprecated)]
let cwd = working_set.get_cwd();
if let Some(plugin_file) = plugin_file {
@ -235,6 +236,7 @@ pub fn eval_config_contents(
engine_state.file = prev_file;
// Merge the environment in case env vars changed in the config
#[allow(deprecated)]
match nu_engine::env::current_dir(engine_state, stack) {
Ok(cwd) => {
if let Err(e) = engine_state.merge_env(stack, cwd) {
@ -272,6 +274,7 @@ pub fn migrate_old_plugin_file(engine_state: &EngineState, storage_path: &str) -
let start_time = std::time::Instant::now();
#[allow(deprecated)]
let cwd = engine_state.current_work_dir();
let Some(config_dir) = nu_path::config_dir().and_then(|mut dir| {

View File

@ -1,6 +1,7 @@
use crate::util::eval_source;
use log::{info, trace};
use miette::{IntoDiagnostic, Result};
#[allow(deprecated)]
use nu_engine::{convert_env_values, current_dir, eval_block};
use nu_parser::parse;
use nu_path::canonicalize_with;
@ -29,6 +30,7 @@ pub fn evaluate_file(
std::process::exit(1);
}
#[allow(deprecated)]
let cwd = current_dir(engine_state, stack)?;
let file_path = canonicalize_with(&path, cwd).unwrap_or_else(|e| {

View File

@ -20,7 +20,8 @@ use nu_cmd_base::{
util::{get_editor, get_guaranteed_cwd},
};
use nu_color_config::StyleComputer;
use nu_engine::{convert_env_values, env_to_strings};
#[allow(deprecated)]
use nu_engine::{convert_env_values, current_dir_str, env_to_strings};
use nu_parser::{lex, parse, trim_quotes_str};
use nu_protocol::{
config::NuCursorShape,
@ -799,6 +800,7 @@ fn prepare_history_metadata(
line_editor: &mut Reedline,
) {
if !s.is_empty() && line_editor.has_last_command_context() {
#[allow(deprecated)]
let result = line_editor
.update_last_command_context(&|mut c| {
c.start_timestamp = Some(chrono::Utc::now());
@ -869,6 +871,7 @@ fn parse_operation(
) -> Result<ReplOperation, ErrReport> {
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
// Check if this is a single call to a directory, if so auto-cd
#[allow(deprecated)]
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
let mut orig = s.clone();
if orig.starts_with('`') {
@ -909,8 +912,6 @@ fn do_auto_cd(
},
);
}
let path = nu_path::canonicalize_with(path, &cwd)
.expect("internal error: cannot canonicalize known path");
path.to_string_lossy().to_string()
};
@ -1027,50 +1028,44 @@ fn run_shell_integration_osc2(
stack: &mut Stack,
use_color: bool,
) {
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
match cwd.coerce_into_string() {
Ok(path) => {
let start_time = Instant::now();
#[allow(deprecated)]
if let Ok(path) = current_dir_str(engine_state, stack) {
let start_time = Instant::now();
// Try to abbreviate string for windows title
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
path.replace(&p.as_path().display().to_string(), "~")
// Try to abbreviate string for windows title
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
path.replace(&p.as_path().display().to_string(), "~")
} else {
path
};
let title = match command_name {
Some(binary_name) => {
let split_binary_name = binary_name.split_whitespace().next();
if let Some(binary_name) = split_binary_name {
format!("{maybe_abbrev_path}> {binary_name}")
} else {
path
};
let title = match command_name {
Some(binary_name) => {
let split_binary_name = binary_name.split_whitespace().next();
if let Some(binary_name) = split_binary_name {
format!("{maybe_abbrev_path}> {binary_name}")
} else {
maybe_abbrev_path.to_string()
}
}
None => maybe_abbrev_path.to_string(),
};
// Set window title too
// https://tldp.org/HOWTO/Xterm-Title-3.html
// ESC]0;stringBEL -- Set icon name and window title to string
// ESC]1;stringBEL -- Set icon name to string
// ESC]2;stringBEL -- Set window title to string
run_ansi_sequence(&format!("\x1b]2;{title}\x07"));
perf(
"set title with command osc2",
start_time,
file!(),
line!(),
column!(),
use_color,
);
maybe_abbrev_path.to_string()
}
}
Err(e) => {
warn!("Could not coerce working directory to string {e}");
}
}
None => maybe_abbrev_path.to_string(),
};
// Set window title too
// https://tldp.org/HOWTO/Xterm-Title-3.html
// ESC]0;stringBEL -- Set icon name and window title to string
// ESC]1;stringBEL -- Set icon name to string
// ESC]2;stringBEL -- Set window title to string
run_ansi_sequence(&format!("\x1b]2;{title}\x07"));
perf(
"set title with command osc2",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
}
@ -1080,98 +1075,78 @@ fn run_shell_integration_osc7(
stack: &mut Stack,
use_color: bool,
) {
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
match cwd.coerce_into_string() {
Ok(path) => {
let start_time = Instant::now();
#[allow(deprecated)]
if let Ok(path) = current_dir_str(engine_state, stack) {
let start_time = Instant::now();
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
run_ansi_sequence(&format!(
"\x1b]7;file://{}{}{}\x1b\\",
percent_encoding::utf8_percent_encode(
hostname.unwrap_or("localhost"),
percent_encoding::CONTROLS
),
if path.starts_with('/') { "" } else { "/" },
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
));
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
run_ansi_sequence(&format!(
"\x1b]7;file://{}{}{}\x1b\\",
percent_encoding::utf8_percent_encode(
hostname.unwrap_or("localhost"),
percent_encoding::CONTROLS
),
if path.starts_with('/') { "" } else { "/" },
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
));
perf(
"communicate path to terminal with osc7",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
Err(e) => {
warn!("Could not coerce working directory to string {e}");
}
}
perf(
"communicate path to terminal with osc7",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
}
fn run_shell_integration_osc9_9(engine_state: &EngineState, stack: &mut Stack, use_color: bool) {
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
match cwd.coerce_into_string() {
Ok(path) => {
let start_time = Instant::now();
#[allow(deprecated)]
if let Ok(path) = current_dir_str(engine_state, stack) {
let start_time = Instant::now();
// Otherwise, communicate the path as OSC 9;9 from ConEmu (often used for spawning new tabs in the same dir)
run_ansi_sequence(&format!(
"\x1b]9;9;{}{}\x1b\\",
if path.starts_with('/') { "" } else { "/" },
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
));
// Otherwise, communicate the path as OSC 9;9 from ConEmu (often used for spawning new tabs in the same dir)
run_ansi_sequence(&format!(
"\x1b]9;9;{}{}\x1b\\",
if path.starts_with('/') { "" } else { "/" },
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
));
perf(
"communicate path to terminal with osc9;9",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
Err(e) => {
warn!("Could not coerce working directory to string {e}");
}
}
perf(
"communicate path to terminal with osc9;9",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
}
fn run_shell_integration_osc633(engine_state: &EngineState, stack: &mut Stack, use_color: bool) {
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
match cwd.coerce_into_string() {
Ok(path) => {
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
if stack.get_env_var(engine_state, "TERM_PROGRAM")
== Some(Value::test_string("vscode"))
{
let start_time = Instant::now();
#[allow(deprecated)]
if let Ok(path) = current_dir_str(engine_state, stack) {
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
let start_time = Instant::now();
// If we're in vscode, run their specific ansi escape sequence.
// This is helpful for ctrl+g to change directories in the terminal.
run_ansi_sequence(&format!(
"{}{}{}",
VSCODE_CWD_PROPERTY_MARKER_PREFIX, path, VSCODE_CWD_PROPERTY_MARKER_SUFFIX
));
// If we're in vscode, run their specific ansi escape sequence.
// This is helpful for ctrl+g to change directories in the terminal.
run_ansi_sequence(&format!(
"{}{}{}",
VSCODE_CWD_PROPERTY_MARKER_PREFIX, path, VSCODE_CWD_PROPERTY_MARKER_SUFFIX
));
perf(
"communicate path to terminal with osc633;P",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
}
Err(e) => {
warn!("Could not coerce working directory to string {e}");
}
perf(
"communicate path to terminal with osc633;P",
start_time,
file!(),
line!(),
column!(),
use_color,
);
}
}
}

View File

@ -37,6 +37,7 @@ impl Highlighter for NuHighlighter {
let str_word = String::from_utf8_lossy(str_contents).to_string();
let paths = env::path_str(&self.engine_state, &self.stack, *span).ok();
#[allow(deprecated)]
let res = if let Ok(cwd) =
env::current_dir_str(&self.engine_state, &self.stack)
{