mirror of
https://github.com/nushell/nushell.git
synced 2025-05-30 06:39:33 +02:00
Merge branch 'main' into ecow-record
This commit is contained in:
commit
e67221edec
8
.github/workflows/nightly-build.yml
vendored
8
.github/workflows/nightly-build.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
||||
uses: hustcer/setup-nu@v3.10
|
||||
if: github.repository == 'nushell/nightly'
|
||||
with:
|
||||
version: 0.91.0
|
||||
version: 0.93.0
|
||||
|
||||
# Synchronize the main branch of nightly repo with the main branch of Nushell official repo
|
||||
- name: Prepare for Nightly Release
|
||||
@ -141,7 +141,7 @@ jobs:
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.10
|
||||
with:
|
||||
version: 0.91.0
|
||||
version: 0.93.0
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
@ -253,7 +253,7 @@ jobs:
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.10
|
||||
with:
|
||||
version: 0.91.0
|
||||
version: 0.93.0
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
@ -317,7 +317,7 @@ jobs:
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.10
|
||||
with:
|
||||
version: 0.91.0
|
||||
version: 0.93.0
|
||||
|
||||
# Keep the last a few releases
|
||||
- name: Delete Older Releases
|
||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -89,7 +89,7 @@ jobs:
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.10
|
||||
with:
|
||||
version: 0.91.0
|
||||
version: 0.93.0
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
@ -179,7 +179,7 @@ jobs:
|
||||
- name: Setup Nushell
|
||||
uses: hustcer/setup-nu@v3.10
|
||||
with:
|
||||
version: 0.91.0
|
||||
version: 0.93.0
|
||||
|
||||
- name: Release Nu Binary
|
||||
id: nu
|
||||
|
182
Cargo.lock
generated
182
Cargo.lock
generated
@ -240,30 +240,6 @@ dependencies = [
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener 5.3.0",
|
||||
"event-listener-strategy 0.5.1",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b"
|
||||
dependencies = [
|
||||
"event-listener 4.0.3",
|
||||
"event-listener-strategy 0.4.0",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.5"
|
||||
@ -286,12 +262,6 @@ dependencies = [
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.79"
|
||||
@ -318,12 +288,6 @@ version = "0.15.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ae037714f313c1353189ead58ef9eec30a8e8dc101b2622d461418fd59e28a9"
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.2.0"
|
||||
@ -472,22 +436,6 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-lock",
|
||||
"async-task",
|
||||
"fastrand",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"piper",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "borsh"
|
||||
version = "1.4.0"
|
||||
@ -905,15 +853,6 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "condtype"
|
||||
version = "1.3.0"
|
||||
@ -1446,48 +1385,6 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c"
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "4.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
|
||||
dependencies = [
|
||||
"event-listener 4.0.3",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3"
|
||||
dependencies = [
|
||||
"event-listener 5.3.0",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
@ -1704,16 +1601,6 @@ version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.30"
|
||||
@ -2135,30 +2022,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "interprocess"
|
||||
version = "1.2.1"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81f2533f3be42fffe3b5e63b71aeca416c1c3bc33e4e27be018521e76b1f38fb"
|
||||
checksum = "6d5f0e3c218e7a86a6712fd3adc84672304f9e839402b866685b9117a077c37f"
|
||||
dependencies = [
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"intmap",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"rustc_version",
|
||||
"spinning",
|
||||
"thiserror",
|
||||
"to_method",
|
||||
"winapi",
|
||||
"recvmsg",
|
||||
"widestring",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "intmap"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae52f28f45ac2bc96edb7714de995cffc174a395fb0abf5bff453587c980d7b9"
|
||||
|
||||
[[package]]
|
||||
name = "inventory"
|
||||
version = "0.3.15"
|
||||
@ -3364,6 +3237,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros 0.26.2",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"typetag",
|
||||
]
|
||||
@ -3840,12 +3714,6 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
@ -4069,17 +3937,6 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "piper"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"fastrand",
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.30"
|
||||
@ -4875,6 +4732,12 @@ dependencies = [
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "recvmsg"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
@ -5615,15 +5478,6 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spinning"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d4f0e86297cad2658d92a707320d87bf4e6ae1050287f51d19b67ef3f153a7b"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlparser"
|
||||
version = "0.39.0"
|
||||
@ -6061,12 +5915,6 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "to_method"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.37.0"
|
||||
@ -6772,6 +6620,12 @@ dependencies = [
|
||||
"winsafe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
|
||||
|
||||
[[package]]
|
||||
name = "wild"
|
||||
version = "2.2.1"
|
||||
|
@ -94,7 +94,7 @@ heck = "0.5.0"
|
||||
human-date-parser = "0.1.1"
|
||||
indexmap = "2.2"
|
||||
indicatif = "0.17"
|
||||
interprocess = "1.2.1"
|
||||
interprocess = "2.0.0"
|
||||
is_executable = "1.0"
|
||||
itertools = "0.12"
|
||||
libc = "0.2"
|
||||
|
@ -21,6 +21,7 @@ fn load_bench_commands() -> EngineState {
|
||||
}
|
||||
|
||||
fn canonicalize_path(engine_state: &EngineState, path: &Path) -> PathBuf {
|
||||
#[allow(deprecated)]
|
||||
let cwd = engine_state.current_work_dir();
|
||||
|
||||
if path.exists() {
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -267,24 +267,6 @@ fn nested_suggestions(
|
||||
|
||||
output
|
||||
}
|
||||
Value::LazyRecord { val, .. } => {
|
||||
// Add all the columns as completion
|
||||
for column_name in val.column_names() {
|
||||
output.push(SemanticSuggestion {
|
||||
suggestion: Suggestion {
|
||||
value: column_name.to_string(),
|
||||
description: None,
|
||||
style: None,
|
||||
extra: None,
|
||||
span: current_span,
|
||||
append_whitespace: false,
|
||||
},
|
||||
kind: Some(kind.clone()),
|
||||
});
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
Value::List { vals, .. } => {
|
||||
for column_name in get_columns(vals.as_slice()) {
|
||||
output.push(SemanticSuggestion {
|
||||
@ -321,17 +303,6 @@ fn recursive_value(val: &Value, sublevels: &[Vec<u8>]) -> Result<Value, Span> {
|
||||
Err(span)
|
||||
}
|
||||
}
|
||||
Value::LazyRecord { val, .. } => {
|
||||
for col in val.column_names() {
|
||||
if col.as_bytes() == *sublevel {
|
||||
let val = val.get_column_value(col).map_err(|_| span)?;
|
||||
return recursive_value(&val, next_sublevels);
|
||||
}
|
||||
}
|
||||
|
||||
// Current sublevel value not found
|
||||
Err(span)
|
||||
}
|
||||
Value::List { vals, .. } => {
|
||||
for col in get_columns(vals.as_slice()) {
|
||||
if col.as_bytes() == *sublevel {
|
||||
|
@ -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| {
|
||||
|
@ -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| {
|
||||
|
@ -1,4 +1,10 @@
|
||||
use crate::prompt_update::{POST_PROMPT_MARKER, PRE_PROMPT_MARKER};
|
||||
use crate::prompt_update::{
|
||||
POST_PROMPT_MARKER, PRE_PROMPT_MARKER, VSCODE_POST_PROMPT_MARKER, VSCODE_PRE_PROMPT_MARKER,
|
||||
};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
Value,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use nu_utils::enable_vt_processing;
|
||||
use reedline::{
|
||||
@ -10,7 +16,8 @@ use std::borrow::Cow;
|
||||
/// Nushell prompt definition
|
||||
#[derive(Clone)]
|
||||
pub struct NushellPrompt {
|
||||
shell_integration: bool,
|
||||
shell_integration_osc133: bool,
|
||||
shell_integration_osc633: bool,
|
||||
left_prompt_string: Option<String>,
|
||||
right_prompt_string: Option<String>,
|
||||
default_prompt_indicator: Option<String>,
|
||||
@ -18,12 +25,20 @@ pub struct NushellPrompt {
|
||||
default_vi_normal_prompt_indicator: Option<String>,
|
||||
default_multiline_indicator: Option<String>,
|
||||
render_right_prompt_on_last_line: bool,
|
||||
engine_state: EngineState,
|
||||
stack: Stack,
|
||||
}
|
||||
|
||||
impl NushellPrompt {
|
||||
pub fn new(shell_integration: bool) -> NushellPrompt {
|
||||
pub fn new(
|
||||
shell_integration_osc133: bool,
|
||||
shell_integration_osc633: bool,
|
||||
engine_state: EngineState,
|
||||
stack: Stack,
|
||||
) -> NushellPrompt {
|
||||
NushellPrompt {
|
||||
shell_integration,
|
||||
shell_integration_osc133,
|
||||
shell_integration_osc633,
|
||||
left_prompt_string: None,
|
||||
right_prompt_string: None,
|
||||
default_prompt_indicator: None,
|
||||
@ -31,6 +46,8 @@ impl NushellPrompt {
|
||||
default_vi_normal_prompt_indicator: None,
|
||||
default_multiline_indicator: None,
|
||||
render_right_prompt_on_last_line: false,
|
||||
engine_state,
|
||||
stack,
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +123,17 @@ impl Prompt for NushellPrompt {
|
||||
.to_string()
|
||||
.replace('\n', "\r\n");
|
||||
|
||||
if self.shell_integration {
|
||||
if self.shell_integration_osc633 {
|
||||
if self.stack.get_env_var(&self.engine_state, "TERM_PROGRAM")
|
||||
== Some(Value::test_string("vscode"))
|
||||
{
|
||||
// We're in vscode and we have osc633 enabled
|
||||
format!("{VSCODE_PRE_PROMPT_MARKER}{prompt}{VSCODE_POST_PROMPT_MARKER}").into()
|
||||
} else {
|
||||
// If we're in VSCode but we don't find the env var, just return the regular markers
|
||||
format!("{PRE_PROMPT_MARKER}{prompt}{POST_PROMPT_MARKER}").into()
|
||||
}
|
||||
} else if self.shell_integration_osc133 {
|
||||
format!("{PRE_PROMPT_MARKER}{prompt}{POST_PROMPT_MARKER}").into()
|
||||
} else {
|
||||
prompt.into()
|
||||
|
@ -23,10 +23,37 @@ pub(crate) const TRANSIENT_PROMPT_INDICATOR_VI_NORMAL: &str =
|
||||
"TRANSIENT_PROMPT_INDICATOR_VI_NORMAL";
|
||||
pub(crate) const TRANSIENT_PROMPT_MULTILINE_INDICATOR: &str =
|
||||
"TRANSIENT_PROMPT_MULTILINE_INDICATOR";
|
||||
|
||||
// Store all these Ansi Escape Markers here so they can be reused easily
|
||||
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
pub(crate) const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||
pub(crate) const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
||||
pub(crate) const PRE_EXECUTION_MARKER: &str = "\x1b]133;C\x1b\\";
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const POST_EXECUTION_MARKER_PREFIX: &str = "\x1b]133;D;";
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const POST_EXECUTION_MARKER_SUFFIX: &str = "\x1b\\";
|
||||
|
||||
// OSC633 is the same as OSC133 but specifically for VSCode
|
||||
pub(crate) const VSCODE_PRE_PROMPT_MARKER: &str = "\x1b]633;A\x1b\\";
|
||||
pub(crate) const VSCODE_POST_PROMPT_MARKER: &str = "\x1b]633;B\x1b\\";
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const VSCODE_PRE_EXECUTION_MARKER: &str = "\x1b]633;C\x1b\\";
|
||||
#[allow(dead_code)]
|
||||
//"\x1b]633;D;{}\x1b\\"
|
||||
pub(crate) const VSCODE_POST_EXECUTION_MARKER_PREFIX: &str = "\x1b]633;D;";
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const VSCODE_POST_EXECUTION_MARKER_SUFFIX: &str = "\x1b\\";
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const VSCODE_COMMANDLINE_MARKER: &str = "\x1b]633;E\x1b\\";
|
||||
#[allow(dead_code)]
|
||||
// "\x1b]633;P;Cwd={}\x1b\\"
|
||||
pub(crate) const VSCODE_CWD_PROPERTY_MARKER_PREFIX: &str = "\x1b]633;P;Cwd=";
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const VSCODE_CWD_PROPERTY_MARKER_SUFFIX: &str = "\x1b\\";
|
||||
|
||||
pub(crate) const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
|
||||
|
||||
fn get_prompt_string(
|
||||
prompt: &str,
|
||||
@ -85,16 +112,46 @@ pub(crate) fn update_prompt(
|
||||
|
||||
// Now that we have the prompt string lets ansify it.
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
let left_prompt_string = if config.shell_integration {
|
||||
if let Some(prompt_string) = left_prompt_string {
|
||||
let left_prompt_string_133 = if config.shell_integration_osc133 {
|
||||
if let Some(prompt_string) = left_prompt_string.clone() {
|
||||
Some(format!(
|
||||
"{PRE_PROMPT_MARKER}{prompt_string}{POST_PROMPT_MARKER}"
|
||||
))
|
||||
} else {
|
||||
left_prompt_string
|
||||
left_prompt_string.clone()
|
||||
}
|
||||
} else {
|
||||
left_prompt_string
|
||||
left_prompt_string.clone()
|
||||
};
|
||||
|
||||
let left_prompt_string_633 = if config.shell_integration_osc633 {
|
||||
if let Some(prompt_string) = left_prompt_string.clone() {
|
||||
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode"))
|
||||
{
|
||||
// If the user enabled osc633 and we're in vscode, use the vscode markers
|
||||
Some(format!(
|
||||
"{VSCODE_PRE_PROMPT_MARKER}{prompt_string}{VSCODE_POST_PROMPT_MARKER}"
|
||||
))
|
||||
} else {
|
||||
// otherwise, use the regular osc133 markers
|
||||
Some(format!(
|
||||
"{PRE_PROMPT_MARKER}{prompt_string}{POST_PROMPT_MARKER}"
|
||||
))
|
||||
}
|
||||
} else {
|
||||
left_prompt_string.clone()
|
||||
}
|
||||
} else {
|
||||
left_prompt_string.clone()
|
||||
};
|
||||
|
||||
let left_prompt_string = match (left_prompt_string_133, left_prompt_string_633) {
|
||||
(None, None) => left_prompt_string,
|
||||
(None, Some(l633)) => Some(l633),
|
||||
(Some(l133), None) => Some(l133),
|
||||
// If both are set, it means we're in vscode, so use the vscode markers
|
||||
// and even if we're not actually in vscode atm, the regular 133 markers are used
|
||||
(Some(_l133), Some(l633)) => Some(l633),
|
||||
};
|
||||
|
||||
let right_prompt_string = get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, stack);
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::{menus::NuMenuCompleter, NuHelpCompleter};
|
||||
use crossterm::event::{KeyCode, KeyModifiers};
|
||||
use log::trace;
|
||||
use nu_ansi_term::Style;
|
||||
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::parse;
|
||||
@ -158,21 +159,14 @@ fn add_menu(
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! add_style {
|
||||
// first arm match add!(1,2), add!(2,3) etc
|
||||
($name:expr, $record: expr, $span:expr, $config: expr, $menu:expr, $f:expr) => {
|
||||
$menu = match extract_value($name, $record, $span) {
|
||||
Ok(text) => {
|
||||
let style = match text {
|
||||
Value::String { val, .. } => lookup_ansi_color_style(&val),
|
||||
Value::Record { .. } => color_record_to_nustyle(&text),
|
||||
_ => lookup_ansi_color_style("green"),
|
||||
};
|
||||
$f($menu, style)
|
||||
}
|
||||
Err(_) => $menu,
|
||||
};
|
||||
};
|
||||
fn get_style(record: &Record, name: &str, span: Span) -> Option<Style> {
|
||||
extract_value(name, record, span)
|
||||
.ok()
|
||||
.map(|text| match text {
|
||||
Value::String { val, .. } => lookup_ansi_color_style(val),
|
||||
Value::Record { .. } => color_record_to_nustyle(text),
|
||||
_ => lookup_ansi_color_style("green"),
|
||||
})
|
||||
}
|
||||
|
||||
// Adds a columnar menu to the editor engine
|
||||
@ -215,46 +209,21 @@ pub(crate) fn add_columnar_menu(
|
||||
|
||||
let span = menu.style.span();
|
||||
if let Value::Record { val, .. } = &menu.style {
|
||||
add_style!(
|
||||
"text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
columnar_menu,
|
||||
ColumnarMenu::with_text_style
|
||||
);
|
||||
add_style!(
|
||||
"selected_text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
columnar_menu,
|
||||
ColumnarMenu::with_selected_text_style
|
||||
);
|
||||
add_style!(
|
||||
"description_text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
columnar_menu,
|
||||
ColumnarMenu::with_description_text_style
|
||||
);
|
||||
add_style!(
|
||||
"match_text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
columnar_menu,
|
||||
ColumnarMenu::with_match_text_style
|
||||
);
|
||||
add_style!(
|
||||
"selected_match_text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
columnar_menu,
|
||||
ColumnarMenu::with_selected_match_text_style
|
||||
);
|
||||
if let Some(style) = get_style(val, "text", span) {
|
||||
columnar_menu = columnar_menu.with_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "selected_text", span) {
|
||||
columnar_menu = columnar_menu.with_selected_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "description_text", span) {
|
||||
columnar_menu = columnar_menu.with_description_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "match_text", span) {
|
||||
columnar_menu = columnar_menu.with_match_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "selected_match_text", span) {
|
||||
columnar_menu = columnar_menu.with_selected_match_text_style(style);
|
||||
}
|
||||
}
|
||||
|
||||
let marker = menu.marker.to_expanded_string("", config);
|
||||
@ -313,30 +282,15 @@ pub(crate) fn add_list_menu(
|
||||
|
||||
let span = menu.style.span();
|
||||
if let Value::Record { val, .. } = &menu.style {
|
||||
add_style!(
|
||||
"text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
list_menu,
|
||||
ListMenu::with_text_style
|
||||
);
|
||||
add_style!(
|
||||
"selected_text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
list_menu,
|
||||
ListMenu::with_selected_text_style
|
||||
);
|
||||
add_style!(
|
||||
"description_text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
list_menu,
|
||||
ListMenu::with_description_text_style
|
||||
);
|
||||
if let Some(style) = get_style(val, "text", span) {
|
||||
list_menu = list_menu.with_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "selected_text", span) {
|
||||
list_menu = list_menu.with_selected_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "description_text", span) {
|
||||
list_menu = list_menu.with_description_text_style(style);
|
||||
}
|
||||
}
|
||||
|
||||
let marker = menu.marker.to_expanded_string("", config);
|
||||
@ -520,46 +474,21 @@ pub(crate) fn add_ide_menu(
|
||||
|
||||
let span = menu.style.span();
|
||||
if let Value::Record { val, .. } = &menu.style {
|
||||
add_style!(
|
||||
"text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
ide_menu,
|
||||
IdeMenu::with_text_style
|
||||
);
|
||||
add_style!(
|
||||
"selected_text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
ide_menu,
|
||||
IdeMenu::with_selected_text_style
|
||||
);
|
||||
add_style!(
|
||||
"description_text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
ide_menu,
|
||||
IdeMenu::with_description_text_style
|
||||
);
|
||||
add_style!(
|
||||
"match_text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
ide_menu,
|
||||
IdeMenu::with_match_text_style
|
||||
);
|
||||
add_style!(
|
||||
"selected_match_text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
ide_menu,
|
||||
IdeMenu::with_selected_match_text_style
|
||||
);
|
||||
if let Some(style) = get_style(val, "text", span) {
|
||||
ide_menu = ide_menu.with_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "selected_text", span) {
|
||||
ide_menu = ide_menu.with_selected_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "description_text", span) {
|
||||
ide_menu = ide_menu.with_description_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "match_text", span) {
|
||||
ide_menu = ide_menu.with_match_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "selected_match_text", span) {
|
||||
ide_menu = ide_menu.with_selected_match_text_style(style);
|
||||
}
|
||||
}
|
||||
|
||||
let marker = menu.marker.to_expanded_string("", config);
|
||||
@ -650,30 +579,15 @@ pub(crate) fn add_description_menu(
|
||||
|
||||
let span = menu.style.span();
|
||||
if let Value::Record { val, .. } = &menu.style {
|
||||
add_style!(
|
||||
"text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
description_menu,
|
||||
DescriptionMenu::with_text_style
|
||||
);
|
||||
add_style!(
|
||||
"selected_text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
description_menu,
|
||||
DescriptionMenu::with_selected_text_style
|
||||
);
|
||||
add_style!(
|
||||
"description_text",
|
||||
val,
|
||||
span,
|
||||
config,
|
||||
description_menu,
|
||||
DescriptionMenu::with_description_text_style
|
||||
);
|
||||
if let Some(style) = get_style(val, "text", span) {
|
||||
description_menu = description_menu.with_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "selected_text", span) {
|
||||
description_menu = description_menu.with_selected_text_style(style);
|
||||
}
|
||||
if let Some(style) = get_style(val, "description_text", span) {
|
||||
description_menu = description_menu.with_description_text_style(style);
|
||||
}
|
||||
}
|
||||
|
||||
let marker = menu.marker.to_expanded_string("", config);
|
||||
|
@ -1,3 +1,9 @@
|
||||
use crate::prompt_update::{
|
||||
POST_EXECUTION_MARKER_PREFIX, POST_EXECUTION_MARKER_SUFFIX, PRE_EXECUTION_MARKER,
|
||||
RESET_APPLICATION_MODE, VSCODE_CWD_PROPERTY_MARKER_PREFIX, VSCODE_CWD_PROPERTY_MARKER_SUFFIX,
|
||||
VSCODE_POST_EXECUTION_MARKER_PREFIX, VSCODE_POST_EXECUTION_MARKER_SUFFIX,
|
||||
VSCODE_PRE_EXECUTION_MARKER,
|
||||
};
|
||||
use crate::{
|
||||
completions::NuCompleter,
|
||||
nu_highlight::NoOpHighlighter,
|
||||
@ -14,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,
|
||||
@ -42,16 +49,6 @@ use std::{
|
||||
};
|
||||
use sysinfo::System;
|
||||
|
||||
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
// These first two have been moved to prompt_update to get as close as possible to the prompt.
|
||||
// const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||
// const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
||||
const PRE_EXECUTE_MARKER: &str = "\x1b]133;C\x1b\\";
|
||||
// This one is in get_command_finished_marker() now so we can capture the exit codes properly.
|
||||
// const CMD_FINISHED_MARKER: &str = "\x1b]133;D;{}\x1b\\";
|
||||
const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
|
||||
|
||||
/// The main REPL loop, including spinning up the prompt itself.
|
||||
pub fn evaluate_repl(
|
||||
engine_state: &mut EngineState,
|
||||
@ -66,7 +63,7 @@ pub fn evaluate_repl(
|
||||
// so that it may be read by various reedline plugins. During this, we
|
||||
// can't modify the stack, but at the end of the loop we take back ownership
|
||||
// from the Arc. This lets us avoid copying stack variables needlessly
|
||||
let mut unique_stack = stack;
|
||||
let mut unique_stack = stack.clone();
|
||||
let config = engine_state.get_config();
|
||||
let use_color = config.use_ansi_coloring;
|
||||
|
||||
@ -74,8 +71,19 @@ pub fn evaluate_repl(
|
||||
|
||||
let mut entry_num = 0;
|
||||
|
||||
let shell_integration = config.shell_integration;
|
||||
let nu_prompt = NushellPrompt::new(shell_integration);
|
||||
// Let's grab the shell_integration configs
|
||||
let shell_integration_osc2 = config.shell_integration_osc2;
|
||||
let shell_integration_osc7 = config.shell_integration_osc7;
|
||||
let shell_integration_osc9_9 = config.shell_integration_osc9_9;
|
||||
let shell_integration_osc133 = config.shell_integration_osc133;
|
||||
let shell_integration_osc633 = config.shell_integration_osc633;
|
||||
|
||||
let nu_prompt = NushellPrompt::new(
|
||||
shell_integration_osc133,
|
||||
shell_integration_osc633,
|
||||
engine_state.clone(),
|
||||
stack.clone(),
|
||||
);
|
||||
|
||||
let start_time = std::time::Instant::now();
|
||||
// Translate environment variables from Strings to Values
|
||||
@ -116,8 +124,22 @@ pub fn evaluate_repl(
|
||||
}
|
||||
|
||||
let hostname = System::host_name();
|
||||
if shell_integration {
|
||||
shell_integration_osc_7_633_2(hostname.as_deref(), engine_state, &mut unique_stack);
|
||||
if shell_integration_osc2 {
|
||||
run_shell_integration_osc2(None, engine_state, &mut unique_stack, use_color);
|
||||
}
|
||||
if shell_integration_osc7 {
|
||||
run_shell_integration_osc7(
|
||||
hostname.as_deref(),
|
||||
engine_state,
|
||||
&mut unique_stack,
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
if shell_integration_osc9_9 {
|
||||
run_shell_integration_osc9_9(engine_state, &mut unique_stack, use_color);
|
||||
}
|
||||
if shell_integration_osc633 {
|
||||
run_shell_integration_osc633(engine_state, &mut unique_stack, use_color);
|
||||
}
|
||||
|
||||
engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64);
|
||||
@ -513,7 +535,14 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
.with_highlighter(Box::<NoOpHighlighter>::default())
|
||||
// CLEAR STACK-REFERENCE 2
|
||||
.with_completer(Box::<DefaultCompleter>::default());
|
||||
let shell_integration = config.shell_integration;
|
||||
|
||||
// Let's grab the shell_integration configs
|
||||
let shell_integration_osc2 = config.shell_integration_osc2;
|
||||
let shell_integration_osc7 = config.shell_integration_osc7;
|
||||
let shell_integration_osc9_9 = config.shell_integration_osc9_9;
|
||||
let shell_integration_osc133 = config.shell_integration_osc133;
|
||||
let shell_integration_osc633 = config.shell_integration_osc633;
|
||||
let shell_integration_reset_application_mode = config.shell_integration_reset_application_mode;
|
||||
|
||||
let mut stack = Stack::unwrap_unique(stack_arc);
|
||||
|
||||
@ -575,10 +604,40 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
repl.buffer = line_editor.current_buffer_contents().to_string();
|
||||
drop(repl);
|
||||
|
||||
if shell_integration {
|
||||
if shell_integration_osc633 {
|
||||
if stack.get_env_var(engine_state, "TERM_PROGRAM")
|
||||
== Some(Value::test_string("vscode"))
|
||||
{
|
||||
start_time = Instant::now();
|
||||
|
||||
run_ansi_sequence(VSCODE_PRE_EXECUTION_MARKER);
|
||||
|
||||
perf(
|
||||
"pre_execute_marker (633;C) ansi escape sequence",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
} else {
|
||||
start_time = Instant::now();
|
||||
|
||||
run_ansi_sequence(PRE_EXECUTION_MARKER);
|
||||
|
||||
perf(
|
||||
"pre_execute_marker (133;C) ansi escape sequence",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
} else if shell_integration_osc133 {
|
||||
start_time = Instant::now();
|
||||
|
||||
run_ansi_sequence(PRE_EXECUTE_MARKER);
|
||||
run_ansi_sequence(PRE_EXECUTION_MARKER);
|
||||
|
||||
perf(
|
||||
"pre_execute_marker (133;C) ansi escape sequence",
|
||||
@ -598,20 +657,13 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
ReplOperation::AutoCd { cwd, target, span } => {
|
||||
do_auto_cd(target, cwd, &mut stack, engine_state, span);
|
||||
|
||||
if shell_integration {
|
||||
start_time = Instant::now();
|
||||
|
||||
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
|
||||
|
||||
perf(
|
||||
"post_execute_marker (133;D) ansi escape sequences",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
run_finaliziation_ansi_sequence(
|
||||
&stack,
|
||||
engine_state,
|
||||
shell_integration_osc633,
|
||||
shell_integration_osc133,
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
ReplOperation::RunCommand(cmd) => {
|
||||
line_editor = do_run_cmd(
|
||||
@ -619,25 +671,18 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
&mut stack,
|
||||
engine_state,
|
||||
line_editor,
|
||||
shell_integration,
|
||||
shell_integration_osc2,
|
||||
*entry_num,
|
||||
use_color,
|
||||
);
|
||||
|
||||
if shell_integration {
|
||||
start_time = Instant::now();
|
||||
|
||||
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
|
||||
|
||||
perf(
|
||||
"post_execute_marker (133;D) ansi escape sequences",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
run_finaliziation_ansi_sequence(
|
||||
&stack,
|
||||
engine_state,
|
||||
shell_integration_osc633,
|
||||
shell_integration_osc133,
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
// as the name implies, we do nothing in this case
|
||||
ReplOperation::DoNothing => {}
|
||||
@ -663,56 +708,45 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
}
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
start_time = Instant::now();
|
||||
|
||||
shell_integration_osc_7_633_2(hostname, engine_state, &mut stack);
|
||||
|
||||
perf(
|
||||
"shell_integration_finalize ansi escape sequences",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
if shell_integration_osc2 {
|
||||
run_shell_integration_osc2(None, engine_state, &mut stack, use_color);
|
||||
}
|
||||
if shell_integration_osc7 {
|
||||
run_shell_integration_osc7(hostname, engine_state, &mut stack, use_color);
|
||||
}
|
||||
if shell_integration_osc9_9 {
|
||||
run_shell_integration_osc9_9(engine_state, &mut stack, use_color);
|
||||
}
|
||||
if shell_integration_osc633 {
|
||||
run_shell_integration_osc633(engine_state, &mut stack, use_color);
|
||||
}
|
||||
if shell_integration_reset_application_mode {
|
||||
run_shell_integration_reset_application_mode();
|
||||
}
|
||||
|
||||
flush_engine_state_repl_buffer(engine_state, &mut line_editor);
|
||||
}
|
||||
Ok(Signal::CtrlC) => {
|
||||
// `Reedline` clears the line content. New prompt is shown
|
||||
if shell_integration {
|
||||
start_time = Instant::now();
|
||||
|
||||
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
|
||||
|
||||
perf(
|
||||
"command_finished_marker ansi escape sequence",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
run_finaliziation_ansi_sequence(
|
||||
&stack,
|
||||
engine_state,
|
||||
shell_integration_osc633,
|
||||
shell_integration_osc133,
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
Ok(Signal::CtrlD) => {
|
||||
// When exiting clear to a new line
|
||||
if shell_integration {
|
||||
start_time = Instant::now();
|
||||
|
||||
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
|
||||
run_finaliziation_ansi_sequence(
|
||||
&stack,
|
||||
engine_state,
|
||||
shell_integration_osc633,
|
||||
shell_integration_osc133,
|
||||
use_color,
|
||||
);
|
||||
|
||||
perf(
|
||||
"command_finished_marker ansi escape sequence",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
println!();
|
||||
return (false, stack, line_editor);
|
||||
}
|
||||
@ -725,20 +759,14 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
||||
// e.g. https://github.com/nushell/nushell/issues/6452
|
||||
// Alternatively only allow that expected failures let the REPL loop
|
||||
}
|
||||
if shell_integration {
|
||||
start_time = Instant::now();
|
||||
|
||||
run_ansi_sequence(&get_command_finished_marker(&stack, engine_state));
|
||||
|
||||
perf(
|
||||
"command_finished_marker ansi escape sequence",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
run_finaliziation_ansi_sequence(
|
||||
&stack,
|
||||
engine_state,
|
||||
shell_integration_osc633,
|
||||
shell_integration_osc133,
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
}
|
||||
perf(
|
||||
@ -772,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());
|
||||
@ -842,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('`') {
|
||||
@ -882,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()
|
||||
};
|
||||
|
||||
@ -946,7 +974,7 @@ fn do_run_cmd(
|
||||
// we pass in the line editor so it can be dropped in the case of a process exit
|
||||
// (in the normal case we don't want to drop it so return it as-is otherwise)
|
||||
line_editor: Reedline,
|
||||
shell_integration: bool,
|
||||
shell_integration_osc2: bool,
|
||||
entry_num: usize,
|
||||
use_color: bool,
|
||||
) -> Reedline {
|
||||
@ -973,39 +1001,8 @@ fn do_run_cmd(
|
||||
}
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
let start_time = Instant::now();
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
match cwd.coerce_into_string() {
|
||||
Ok(path) => {
|
||||
// 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 binary_name = s.split_whitespace().next();
|
||||
|
||||
if let Some(binary_name) = binary_name {
|
||||
run_ansi_sequence(&format!(
|
||||
"\x1b]2;{maybe_abbrev_path}> {binary_name}\x07"
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Could not coerce working directory to string {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
perf(
|
||||
"set title with command ansi escape sequence",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
if shell_integration_osc2 {
|
||||
run_shell_integration_osc2(Some(s), engine_state, stack, use_color);
|
||||
}
|
||||
|
||||
eval_source(
|
||||
@ -1025,54 +1022,136 @@ fn do_run_cmd(
|
||||
/// can have more information about what is going on (both on startup and after we have
|
||||
/// run a command)
|
||||
///
|
||||
fn shell_integration_osc_7_633_2(
|
||||
fn run_shell_integration_osc2(
|
||||
command_name: Option<&str>,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
use_color: bool,
|
||||
) {
|
||||
#[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(), "~")
|
||||
} 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn run_shell_integration_osc7(
|
||||
hostname: Option<&str>,
|
||||
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"))
|
||||
{
|
||||
// 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!("\x1b]633;P;Cwd={}\x1b\\", path));
|
||||
} else {
|
||||
// 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)
|
||||
));
|
||||
}
|
||||
#[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(), "~")
|
||||
} else {
|
||||
path
|
||||
};
|
||||
// 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)
|
||||
));
|
||||
|
||||
// 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;{maybe_abbrev_path}\x07"));
|
||||
}
|
||||
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) {
|
||||
#[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)
|
||||
));
|
||||
|
||||
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) {
|
||||
#[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
|
||||
));
|
||||
|
||||
perf(
|
||||
"communicate path to terminal with osc633;P",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_shell_integration_reset_application_mode() {
|
||||
run_ansi_sequence(RESET_APPLICATION_MODE);
|
||||
}
|
||||
|
||||
@ -1219,12 +1298,28 @@ fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> Option<SetCursorSty
|
||||
}
|
||||
}
|
||||
|
||||
fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
|
||||
fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState, vscode: bool) -> String {
|
||||
let exit_code = stack
|
||||
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
||||
.and_then(|e| e.as_i64().ok());
|
||||
|
||||
format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
|
||||
if vscode {
|
||||
// format!("\x1b]633;D;{}\x1b\\", exit_code.unwrap_or(0))
|
||||
format!(
|
||||
"{}{}{}",
|
||||
VSCODE_POST_EXECUTION_MARKER_PREFIX,
|
||||
exit_code.unwrap_or(0),
|
||||
VSCODE_POST_EXECUTION_MARKER_SUFFIX
|
||||
)
|
||||
} else {
|
||||
// format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
|
||||
format!(
|
||||
"{}{}{}",
|
||||
POST_EXECUTION_MARKER_PREFIX,
|
||||
exit_code.unwrap_or(0),
|
||||
POST_EXECUTION_MARKER_SUFFIX
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn run_ansi_sequence(seq: &str) {
|
||||
@ -1235,6 +1330,58 @@ fn run_ansi_sequence(seq: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
fn run_finaliziation_ansi_sequence(
|
||||
stack: &Stack,
|
||||
engine_state: &EngineState,
|
||||
use_color: bool,
|
||||
shell_integration_osc633: bool,
|
||||
shell_integration_osc133: bool,
|
||||
) {
|
||||
if shell_integration_osc633 {
|
||||
// Only run osc633 if we are in vscode
|
||||
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
|
||||
let start_time = Instant::now();
|
||||
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state, true));
|
||||
|
||||
perf(
|
||||
"post_execute_marker (633;D) ansi escape sequences",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
} else {
|
||||
let start_time = Instant::now();
|
||||
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state, false));
|
||||
|
||||
perf(
|
||||
"post_execute_marker (133;D) ansi escape sequences",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
} else if shell_integration_osc133 {
|
||||
let start_time = Instant::now();
|
||||
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state, false));
|
||||
|
||||
perf(
|
||||
"post_execute_marker (133;D) ansi escape sequences",
|
||||
start_time,
|
||||
file!(),
|
||||
line!(),
|
||||
column!(),
|
||||
use_color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
|
||||
#[cfg(windows)]
|
||||
static DRIVE_PATH_REGEX: once_cell::sync::Lazy<fancy_regex::Regex> =
|
||||
|
@ -4,7 +4,7 @@ use nu_color_config::{get_matching_brackets_style, get_shape_color};
|
||||
use nu_engine::env;
|
||||
use nu_parser::{flatten_block, parse, FlatShape};
|
||||
use nu_protocol::{
|
||||
ast::{Argument, Block, Expr, Expression, PipelineRedirection, RecordItem},
|
||||
ast::{Block, Expr, Expression, PipelineRedirection, RecordItem},
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Config, Span,
|
||||
};
|
||||
@ -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)
|
||||
{
|
||||
@ -86,27 +87,6 @@ impl Highlighter for NuHighlighter {
|
||||
[(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)]
|
||||
.to_string();
|
||||
|
||||
macro_rules! add_colored_token_with_bracket_highlight {
|
||||
($shape:expr, $span:expr, $text:expr) => {{
|
||||
let spans = split_span_by_highlight_positions(
|
||||
line,
|
||||
$span,
|
||||
&matching_brackets_pos,
|
||||
global_span_offset,
|
||||
);
|
||||
spans.iter().for_each(|(part, highlight)| {
|
||||
let start = part.start - $span.start;
|
||||
let end = part.end - $span.start;
|
||||
let text = (&next_token[start..end]).to_string();
|
||||
let mut style = get_shape_color($shape.to_string(), &self.config);
|
||||
if *highlight {
|
||||
style = get_matching_brackets_style(style, &self.config);
|
||||
}
|
||||
output.push((style, text));
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
let mut add_colored_token = |shape: &FlatShape, text: String| {
|
||||
output.push((get_shape_color(shape.to_string(), &self.config), text));
|
||||
};
|
||||
@ -128,23 +108,32 @@ impl Highlighter for NuHighlighter {
|
||||
FlatShape::Operator => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::Signature => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::String => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::RawString => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::StringInterpolation => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::DateTime => add_colored_token(&shape.1, next_token),
|
||||
FlatShape::List => {
|
||||
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||
}
|
||||
FlatShape::Table => {
|
||||
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||
}
|
||||
FlatShape::Record => {
|
||||
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||
}
|
||||
|
||||
FlatShape::Block => {
|
||||
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||
}
|
||||
FlatShape::Closure => {
|
||||
add_colored_token_with_bracket_highlight!(shape.1, shape.0, next_token)
|
||||
FlatShape::List
|
||||
| FlatShape::Table
|
||||
| FlatShape::Record
|
||||
| FlatShape::Block
|
||||
| FlatShape::Closure => {
|
||||
let span = shape.0;
|
||||
let shape = &shape.1;
|
||||
let spans = split_span_by_highlight_positions(
|
||||
line,
|
||||
span,
|
||||
&matching_brackets_pos,
|
||||
global_span_offset,
|
||||
);
|
||||
for (part, highlight) in spans {
|
||||
let start = part.start - span.start;
|
||||
let end = part.end - span.start;
|
||||
let text = next_token[start..end].to_string();
|
||||
let mut style = get_shape_color(shape.to_string(), &self.config);
|
||||
if highlight {
|
||||
style = get_matching_brackets_style(style, &self.config);
|
||||
}
|
||||
output.push((style, text));
|
||||
}
|
||||
}
|
||||
|
||||
FlatShape::Filepath => add_colored_token(&shape.1, next_token),
|
||||
@ -310,20 +299,6 @@ fn find_matching_block_end_in_expr(
|
||||
global_span_offset: usize,
|
||||
global_cursor_offset: usize,
|
||||
) -> Option<usize> {
|
||||
macro_rules! find_in_expr_or_continue {
|
||||
($inner_expr:ident) => {
|
||||
if let Some(pos) = find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
$inner_expr,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
) {
|
||||
return Some(pos);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if expression.span.contains(global_cursor_offset) && expression.span.start >= global_span_offset
|
||||
{
|
||||
let expr_first = expression.span.start;
|
||||
@ -353,6 +328,7 @@ fn find_matching_block_end_in_expr(
|
||||
Expr::Directory(_, _) => None,
|
||||
Expr::GlobPattern(_, _) => None,
|
||||
Expr::String(_) => None,
|
||||
Expr::RawString(_) => None,
|
||||
Expr::CellPath(_) => None,
|
||||
Expr::ImportPattern(_) => None,
|
||||
Expr::Overlay(_) => None,
|
||||
@ -370,15 +346,19 @@ fn find_matching_block_end_in_expr(
|
||||
Some(expr_last)
|
||||
} else {
|
||||
// cursor is inside table
|
||||
for inner_expr in table.columns.as_ref() {
|
||||
find_in_expr_or_continue!(inner_expr);
|
||||
}
|
||||
for row in table.rows.as_ref() {
|
||||
for inner_expr in row.as_ref() {
|
||||
find_in_expr_or_continue!(inner_expr);
|
||||
}
|
||||
}
|
||||
None
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.chain(table.rows.iter().flat_map(AsRef::as_ref))
|
||||
.find_map(|expr| {
|
||||
find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
expr,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -391,36 +371,45 @@ fn find_matching_block_end_in_expr(
|
||||
Some(expr_last)
|
||||
} else {
|
||||
// cursor is inside record
|
||||
for expr in exprs {
|
||||
match expr {
|
||||
RecordItem::Pair(k, v) => {
|
||||
find_in_expr_or_continue!(k);
|
||||
find_in_expr_or_continue!(v);
|
||||
}
|
||||
RecordItem::Spread(_, record) => {
|
||||
find_in_expr_or_continue!(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
exprs.iter().find_map(|expr| match expr {
|
||||
RecordItem::Pair(k, v) => find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
k,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
)
|
||||
.or_else(|| {
|
||||
find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
v,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
)
|
||||
}),
|
||||
RecordItem::Spread(_, record) => find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
record,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Call(call) => {
|
||||
for arg in &call.arguments {
|
||||
let opt_expr = match arg {
|
||||
Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(),
|
||||
Argument::Positional(inner_expr) => Some(inner_expr),
|
||||
Argument::Unknown(inner_expr) => Some(inner_expr),
|
||||
Argument::Spread(inner_expr) => Some(inner_expr),
|
||||
};
|
||||
|
||||
if let Some(inner_expr) = opt_expr {
|
||||
find_in_expr_or_continue!(inner_expr);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
Expr::Call(call) => call.arguments.iter().find_map(|arg| {
|
||||
arg.expr().and_then(|expr| {
|
||||
find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
expr,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
)
|
||||
})
|
||||
}),
|
||||
|
||||
Expr::FullCellPath(b) => find_matching_block_end_in_expr(
|
||||
line,
|
||||
@ -430,12 +419,15 @@ fn find_matching_block_end_in_expr(
|
||||
global_cursor_offset,
|
||||
),
|
||||
|
||||
Expr::BinaryOp(lhs, op, rhs) => {
|
||||
find_in_expr_or_continue!(lhs);
|
||||
find_in_expr_or_continue!(op);
|
||||
find_in_expr_or_continue!(rhs);
|
||||
None
|
||||
}
|
||||
Expr::BinaryOp(lhs, op, rhs) => [lhs, op, rhs].into_iter().find_map(|expr| {
|
||||
find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
expr,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
)
|
||||
}),
|
||||
|
||||
Expr::Block(block_id)
|
||||
| Expr::Closure(block_id)
|
||||
@ -460,12 +452,15 @@ fn find_matching_block_end_in_expr(
|
||||
}
|
||||
}
|
||||
|
||||
Expr::StringInterpolation(inner_expr) => {
|
||||
for inner_expr in inner_expr {
|
||||
find_in_expr_or_continue!(inner_expr);
|
||||
}
|
||||
None
|
||||
}
|
||||
Expr::StringInterpolation(exprs) => exprs.iter().find_map(|expr| {
|
||||
find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
expr,
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
)
|
||||
}),
|
||||
|
||||
Expr::List(list) => {
|
||||
if expr_last == global_cursor_offset {
|
||||
@ -475,12 +470,15 @@ fn find_matching_block_end_in_expr(
|
||||
// cursor is at list start
|
||||
Some(expr_last)
|
||||
} else {
|
||||
// cursor is inside list
|
||||
for item in list {
|
||||
let expr = item.expr();
|
||||
find_in_expr_or_continue!(expr);
|
||||
}
|
||||
None
|
||||
list.iter().find_map(|item| {
|
||||
find_matching_block_end_in_expr(
|
||||
line,
|
||||
working_set,
|
||||
item.expr(),
|
||||
global_span_offset,
|
||||
global_cursor_offset,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -6,7 +6,7 @@ use nu_parser::parse;
|
||||
use nu_protocol::{debugger::WithoutDebug, engine::StateWorkingSet, PipelineData};
|
||||
use reedline::{Completer, Suggestion};
|
||||
use rstest::{fixture, rstest};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{PathBuf, MAIN_SEPARATOR};
|
||||
use support::{
|
||||
completions_helpers::{new_partial_engine, new_quote_engine},
|
||||
file, folder, match_suggestions, new_engine,
|
||||
@ -220,7 +220,7 @@ fn file_completions() {
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for the current folder
|
||||
let target_dir = format!("cp {dir_str}");
|
||||
let target_dir = format!("cp {dir_str}{MAIN_SEPARATOR}");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
@ -664,7 +664,7 @@ fn folder_with_directorycompletions() {
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
// Test completions for the current folder
|
||||
let target_dir = format!("cd {dir_str}");
|
||||
let target_dir = format!("cd {dir_str}{MAIN_SEPARATOR}");
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
|
@ -8,9 +8,7 @@ use nu_protocol::{
|
||||
};
|
||||
use nu_test_support::fs;
|
||||
use reedline::Suggestion;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
use std::path::{PathBuf, MAIN_SEPARATOR};
|
||||
|
||||
fn create_default_context() -> EngineState {
|
||||
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context())
|
||||
@ -20,12 +18,11 @@ fn create_default_context() -> EngineState {
|
||||
pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
// Target folder inside assets
|
||||
let dir = fs::fixtures().join("completions");
|
||||
let mut dir_str = dir
|
||||
let dir_str = dir
|
||||
.clone()
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap_or_default();
|
||||
dir_str.push(SEP);
|
||||
|
||||
// Create a new engine with default context
|
||||
let mut engine_state = create_default_context();
|
||||
@ -77,12 +74,11 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
// Target folder inside assets
|
||||
let dir = fs::fixtures().join("quoted_completions");
|
||||
let mut dir_str = dir
|
||||
let dir_str = dir
|
||||
.clone()
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap_or_default();
|
||||
dir_str.push(SEP);
|
||||
|
||||
// Create a new engine with default context
|
||||
let mut engine_state = create_default_context();
|
||||
@ -113,12 +109,11 @@ pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
pub fn new_partial_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
// Target folder inside assets
|
||||
let dir = fs::fixtures().join("partial_completions");
|
||||
let mut dir_str = dir
|
||||
let dir_str = dir
|
||||
.clone()
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap_or_default();
|
||||
dir_str.push(SEP);
|
||||
|
||||
// Create a new engine with default context
|
||||
let mut engine_state = create_default_context();
|
||||
@ -165,7 +160,7 @@ pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
||||
// append the separator to the converted path
|
||||
pub fn folder(path: PathBuf) -> String {
|
||||
let mut converted_path = file(path);
|
||||
converted_path.push(SEP);
|
||||
converted_path.push(MAIN_SEPARATOR);
|
||||
|
||||
converted_path
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ pub fn get_init_cwd() -> PathBuf {
|
||||
}
|
||||
|
||||
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
|
||||
#[allow(deprecated)]
|
||||
nu_engine::env::current_dir(engine_state, stack).unwrap_or_else(|e| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
|
@ -26,7 +26,6 @@ impl Command for Describe {
|
||||
"show detailed information about the value",
|
||||
Some('d'),
|
||||
)
|
||||
.switch("collect-lazyrecords", "collect lazy records", Some('l'))
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
@ -44,21 +43,7 @@ impl Command for Describe {
|
||||
let options = Options {
|
||||
no_collect: call.has_flag(engine_state, stack, "no-collect")?,
|
||||
detailed: call.has_flag(engine_state, stack, "detailed")?,
|
||||
collect_lazyrecords: call.has_flag(engine_state, stack, "collect-lazyrecords")?,
|
||||
};
|
||||
if options.collect_lazyrecords {
|
||||
nu_protocol::report_error_new(
|
||||
engine_state,
|
||||
&ShellError::GenericError {
|
||||
error: "Deprecated flag".into(),
|
||||
msg: "the `--collect-lazyrecords` flag is deprecated, since lazy records will be removed in 0.94.0"
|
||||
.into(),
|
||||
span: Some(call.head),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
);
|
||||
}
|
||||
run(Some(engine_state), call, input, options)
|
||||
}
|
||||
|
||||
@ -71,7 +56,6 @@ impl Command for Describe {
|
||||
let options = Options {
|
||||
no_collect: call.has_flag_const(working_set, "no-collect")?,
|
||||
detailed: call.has_flag_const(working_set, "detailed")?,
|
||||
collect_lazyrecords: call.has_flag_const(working_set, "collect-lazyrecords")?,
|
||||
};
|
||||
run(None, call, input, options)
|
||||
}
|
||||
@ -89,13 +73,11 @@ impl Command for Describe {
|
||||
"{shell:'true', uwu:true, features: {bugs:false, multiplatform:true, speed: 10}, fib: [1 1 2 3 5 8], on_save: {|x| print $'Saving ($x)'}, first_commit: 2019-05-10, my_duration: (4min + 20sec)} | describe -d",
|
||||
result: Some(Value::test_record(record!(
|
||||
"type" => Value::test_string("record"),
|
||||
"lazy" => Value::test_bool(false),
|
||||
"columns" => Value::test_record(record!(
|
||||
"shell" => Value::test_string("string"),
|
||||
"uwu" => Value::test_string("bool"),
|
||||
"features" => Value::test_record(record!(
|
||||
"type" => Value::test_string("record"),
|
||||
"lazy" => Value::test_bool(false),
|
||||
"columns" => Value::test_record(record!(
|
||||
"bugs" => Value::test_string("bool"),
|
||||
"multiplatform" => Value::test_string("bool"),
|
||||
@ -168,7 +150,6 @@ impl Command for Describe {
|
||||
struct Options {
|
||||
no_collect: bool,
|
||||
detailed: bool,
|
||||
collect_lazyrecords: bool,
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -243,7 +224,7 @@ fn run(
|
||||
if options.no_collect {
|
||||
Value::string("any", head)
|
||||
} else {
|
||||
describe_value(input.into_value(head), head, engine_state, options)?
|
||||
describe_value(input.into_value(head), head, engine_state, )
|
||||
}
|
||||
},
|
||||
"metadata" => metadata_to_value(metadata, head),
|
||||
@ -264,7 +245,7 @@ fn run(
|
||||
if !options.detailed {
|
||||
Value::string(value.get_type().to_string(), head)
|
||||
} else {
|
||||
describe_value(value, head, engine_state, options)?
|
||||
describe_value(value, head, engine_state)
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -288,9 +269,8 @@ fn describe_value(
|
||||
value: Value,
|
||||
head: nu_protocol::Span,
|
||||
engine_state: Option<&EngineState>,
|
||||
options: Options,
|
||||
) -> Result<Value, ShellError> {
|
||||
Ok(match value {
|
||||
) -> Value {
|
||||
match value {
|
||||
Value::Custom { val, .. } => Value::record(
|
||||
record!(
|
||||
"type" => Value::string("custom", head),
|
||||
@ -319,14 +299,12 @@ fn describe_value(
|
||||
std::mem::take(v),
|
||||
head,
|
||||
engine_state,
|
||||
options,
|
||||
)?);
|
||||
));
|
||||
}
|
||||
|
||||
Value::record(
|
||||
record!(
|
||||
"type" => Value::string("record", head),
|
||||
"lazy" => Value::bool(false, head),
|
||||
"columns" => Value::record(val, head),
|
||||
),
|
||||
head,
|
||||
@ -337,11 +315,9 @@ fn describe_value(
|
||||
"type" => Value::string("list", head),
|
||||
"length" => Value::int(vals.len() as i64, head),
|
||||
"values" => Value::list(vals.into_iter().map(|v|
|
||||
Ok(compact_primitive_description(
|
||||
describe_value(v, head, engine_state, options)?
|
||||
))
|
||||
compact_primitive_description(describe_value(v, head, engine_state))
|
||||
)
|
||||
.collect::<Result<Vec<Value>, ShellError>>()?, head),
|
||||
.collect(), head),
|
||||
),
|
||||
head,
|
||||
),
|
||||
@ -393,42 +369,7 @@ fn describe_value(
|
||||
),
|
||||
head,
|
||||
),
|
||||
Value::LazyRecord { val, .. } => {
|
||||
let mut record = Record::new();
|
||||
|
||||
record.push("type", Value::string("record", head));
|
||||
record.push("lazy", Value::bool(true, head));
|
||||
|
||||
if options.collect_lazyrecords {
|
||||
let collected = val.collect()?;
|
||||
if let Value::Record { val, .. } =
|
||||
describe_value(collected, head, engine_state, options)?
|
||||
{
|
||||
let mut val = Record::clone(&val);
|
||||
|
||||
for (_k, v) in val.iter_mut() {
|
||||
*v = compact_primitive_description(describe_value(
|
||||
std::mem::take(v),
|
||||
head,
|
||||
engine_state,
|
||||
options,
|
||||
)?);
|
||||
}
|
||||
|
||||
record.push("length", Value::int(val.len() as i64, head));
|
||||
record.push("columns", Value::record(val, head));
|
||||
} else {
|
||||
let cols = val.column_names();
|
||||
record.push("length", Value::int(cols.len() as i64, head));
|
||||
}
|
||||
} else {
|
||||
let cols = val.column_names();
|
||||
record.push("length", Value::int(cols.len() as i64, head));
|
||||
}
|
||||
|
||||
Value::record(record, head)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn metadata_to_value(metadata: Option<Box<PipelineMetadata>>, head: nu_protocol::Span) -> Value {
|
||||
|
@ -127,7 +127,7 @@ impl Command for Do {
|
||||
let stderr_msg = match stderr {
|
||||
None => "".to_string(),
|
||||
Some(stderr_stream) => {
|
||||
stderr_ctrlc = stderr_stream.ctrlc.clone();
|
||||
stderr_ctrlc.clone_from(&stderr_stream.ctrlc);
|
||||
stderr_stream.into_string().map(|s| s.item)?
|
||||
}
|
||||
};
|
||||
@ -152,7 +152,7 @@ impl Command for Do {
|
||||
let exit_code: Vec<Value> = match exit_code {
|
||||
None => vec![],
|
||||
Some(exit_code_stream) => {
|
||||
exit_code_ctrlc = exit_code_stream.ctrlc.clone();
|
||||
exit_code_ctrlc.clone_from(&exit_code_stream.ctrlc);
|
||||
exit_code_stream.into_iter().collect()
|
||||
}
|
||||
};
|
||||
|
@ -1,179 +0,0 @@
|
||||
use nu_engine::{command_prelude::*, eval_block};
|
||||
use nu_protocol::{debugger::WithoutDebug, engine::Closure, LazyRecord};
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LazyMake;
|
||||
|
||||
impl Command for LazyMake {
|
||||
fn name(&self) -> &str {
|
||||
"lazy make"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("lazy make")
|
||||
.input_output_types(vec![(Type::Nothing, Type::record())])
|
||||
.required_named(
|
||||
"columns",
|
||||
SyntaxShape::List(Box::new(SyntaxShape::String)),
|
||||
"Closure that gets called when the LazyRecord needs to list the available column names",
|
||||
Some('c')
|
||||
)
|
||||
.required_named(
|
||||
"get-value",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::String])),
|
||||
"Closure to call when a value needs to be produced on demand",
|
||||
Some('g')
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create a lazy record."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"Lazy records are special records that only evaluate their values once the property is requested.
|
||||
For example, when printing a lazy record, all of its fields will be collected. But when accessing
|
||||
a specific property, only it will be evaluated.
|
||||
|
||||
Note that this is unrelated to the lazyframes feature bundled with dataframes."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["deferred", "record", "procedural"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
nu_protocol::report_error_new(
|
||||
engine_state,
|
||||
&ShellError::GenericError {
|
||||
error: "Deprecated command".into(),
|
||||
msg: "warning: lazy records and the `lazy make` command will be removed in 0.94.0"
|
||||
.into(),
|
||||
span: Some(call.head),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
let span = call.head;
|
||||
let columns: Vec<Spanned<String>> = call
|
||||
.get_flag(engine_state, stack, "columns")?
|
||||
.expect("required flag");
|
||||
|
||||
let get_value: Closure = call
|
||||
.get_flag(engine_state, stack, "get-value")?
|
||||
.expect("required flag");
|
||||
|
||||
let mut unique = HashMap::with_capacity(columns.len());
|
||||
|
||||
for col in &columns {
|
||||
match unique.entry(&col.item) {
|
||||
Entry::Occupied(entry) => {
|
||||
return Err(ShellError::ColumnDefinedTwice {
|
||||
col_name: col.item.clone(),
|
||||
second_use: col.span,
|
||||
first_use: *entry.get(),
|
||||
});
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(col.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let stack = stack.clone().reset_out_dest().capture();
|
||||
|
||||
Ok(Value::lazy_record(
|
||||
Box::new(NuLazyRecord {
|
||||
engine_state: engine_state.clone(),
|
||||
stack: Arc::new(Mutex::new(stack)),
|
||||
columns: columns.into_iter().map(|s| s.item).collect(),
|
||||
get_value,
|
||||
span,
|
||||
}),
|
||||
span,
|
||||
)
|
||||
.into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
// TODO: Figure out how to "test" these examples, or leave results as None
|
||||
Example {
|
||||
description: "Create a lazy record",
|
||||
example: r#"lazy make --columns ["haskell", "futures", "nushell"] --get-value { |lazything| $lazything + "!" }"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Test the laziness of lazy records",
|
||||
example: r#"lazy make --columns ["hello"] --get-value { |key| print $"getting ($key)!"; $key | str upcase }"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct NuLazyRecord {
|
||||
engine_state: EngineState,
|
||||
stack: Arc<Mutex<Stack>>,
|
||||
columns: Vec<String>,
|
||||
get_value: Closure,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for NuLazyRecord {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("NuLazyRecord").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LazyRecord<'a> for NuLazyRecord {
|
||||
fn column_names(&'a self) -> Vec<&'a str> {
|
||||
self.columns.iter().map(|column| column.as_str()).collect()
|
||||
}
|
||||
|
||||
fn get_column_value(&self, column: &str) -> Result<Value, ShellError> {
|
||||
let block = self.engine_state.get_block(self.get_value.block_id);
|
||||
let mut stack = self.stack.lock().expect("lock must not be poisoned");
|
||||
let column_value = Value::string(column, self.span);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, column_value.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let pipeline_result = eval_block::<WithoutDebug>(
|
||||
&self.engine_state,
|
||||
&mut stack,
|
||||
block,
|
||||
PipelineData::Value(column_value, None),
|
||||
);
|
||||
|
||||
pipeline_result.map(|data| match data {
|
||||
PipelineData::Value(value, ..) => value,
|
||||
// TODO: Proper error handling.
|
||||
_ => Value::nothing(self.span),
|
||||
})
|
||||
}
|
||||
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
|
||||
fn clone_value(&self, span: Span) -> Value {
|
||||
Value::lazy_record(Box::new((*self).clone()), span)
|
||||
}
|
||||
}
|
@ -21,7 +21,6 @@ mod hide;
|
||||
mod hide_env;
|
||||
mod if_;
|
||||
mod ignore;
|
||||
mod lazy_make;
|
||||
mod let_;
|
||||
mod loop_;
|
||||
mod match_;
|
||||
@ -58,7 +57,6 @@ pub use hide::Hide;
|
||||
pub use hide_env::HideEnv;
|
||||
pub use if_::If;
|
||||
pub use ignore::Ignore;
|
||||
pub use lazy_make::LazyMake;
|
||||
pub use let_::Let;
|
||||
pub use loop_::Loop;
|
||||
pub use match_::Match;
|
||||
|
@ -43,7 +43,6 @@ pub fn create_default_context() -> EngineState {
|
||||
OverlayList,
|
||||
OverlayNew,
|
||||
OverlayHide,
|
||||
LazyMake,
|
||||
Let,
|
||||
Loop,
|
||||
Match,
|
||||
|
@ -306,10 +306,6 @@ impl<'a> std::fmt::Debug for DebuggableValue<'a> {
|
||||
Value::Custom { val, .. } => {
|
||||
write!(f, "CustomValue({:?})", val)
|
||||
}
|
||||
Value::LazyRecord { val, .. } => {
|
||||
let rec = val.collect().map_err(|_| std::fmt::Error)?;
|
||||
write!(f, "LazyRecord({:?})", DebuggableValue(&rec))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_plugin_engine::{GetPlugin, PersistentPlugin};
|
||||
use nu_protocol::{PluginGcConfig, PluginIdentity, PluginRegistryItem, RegisteredPlugin};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::util::{get_plugin_dirs, modify_plugin_file};
|
||||
|
||||
@ -82,6 +82,7 @@ apparent the next time `nu` is next launched with that plugin registry file.
|
||||
let filename: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let shell: Option<Spanned<String>> = call.get_flag(engine_state, stack, "shell")?;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
// Check the current directory, or fall back to NU_PLUGIN_DIRS
|
||||
|
@ -1,11 +1,11 @@
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_protocol::{engine::StateWorkingSet, PluginRegistryFile};
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_protocol::{engine::StateWorkingSet, PluginRegistryFile};
|
||||
|
||||
pub(crate) fn modify_plugin_file(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
@ -13,6 +13,7 @@ pub(crate) fn modify_plugin_file(
|
||||
custom_path: Option<Spanned<String>>,
|
||||
operate: impl FnOnce(&mut PluginRegistryFile) -> Result<(), ShellError>,
|
||||
) -> Result<(), ShellError> {
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
let plugin_registry_file_path = if let Some(ref custom_path) = custom_path {
|
||||
@ -58,6 +59,7 @@ pub(crate) fn canonicalize_possible_filename_arg(
|
||||
arg: &str,
|
||||
) -> PathBuf {
|
||||
// This results in the best possible chance of a match with the plugin item
|
||||
#[allow(deprecated)]
|
||||
if let Ok(cwd) = nu_engine::current_dir(engine_state, stack) {
|
||||
let path = nu_path::expand_path_with(arg, &cwd, true);
|
||||
// Try to canonicalize
|
||||
|
@ -32,6 +32,7 @@ pub fn default_shape_color(shape: String) -> Style {
|
||||
"shape_or" => Style::new().fg(Color::Purple).bold(),
|
||||
"shape_pipe" => Style::new().fg(Color::Purple).bold(),
|
||||
"shape_range" => Style::new().fg(Color::Yellow).bold(),
|
||||
"shape_raw_string" => Style::new().fg(Color::LightMagenta).bold(),
|
||||
"shape_record" => Style::new().fg(Color::Cyan).bold(),
|
||||
"shape_redirection" => Style::new().fg(Color::Purple).bold(),
|
||||
"shape_signature" => Style::new().fg(Color::Green).bold(),
|
||||
|
@ -106,10 +106,9 @@ impl<'a> StyleComputer<'a> {
|
||||
Value::Binary { .. } => TextStyle::with_style(Left, s),
|
||||
Value::CellPath { .. } => TextStyle::with_style(Left, s),
|
||||
Value::Record { .. } | Value::List { .. } => TextStyle::with_style(Left, s),
|
||||
Value::Closure { .. }
|
||||
| Value::Custom { .. }
|
||||
| Value::Error { .. }
|
||||
| Value::LazyRecord { .. } => TextStyle::basic_left(),
|
||||
Value::Closure { .. } | Value::Custom { .. } | Value::Error { .. } => {
|
||||
TextStyle::basic_left()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,7 +150,7 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||
head,
|
||||
),
|
||||
Ordering::Less => Value::binary(
|
||||
if end == isize::max_value() {
|
||||
if end == isize::MAX {
|
||||
val.iter()
|
||||
.skip(start as usize)
|
||||
.copied()
|
||||
|
@ -272,10 +272,6 @@ pub fn debug_string_without_formatting(value: &Value) -> String {
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
),
|
||||
Value::LazyRecord { val, .. } => match val.collect() {
|
||||
Ok(val) => debug_string_without_formatting(&val),
|
||||
Err(error) => format!("{error:?}"),
|
||||
},
|
||||
//TODO: It would be good to drill deeper into closures.
|
||||
Value::Closure { val, .. } => format!("<Closure {}>", val.block_id),
|
||||
Value::Nothing { .. } => String::new(),
|
||||
|
@ -1,5 +1,4 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::LazyRecord;
|
||||
use sysinfo::{MemoryRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
|
||||
|
||||
const ENV_PATH_SEPARATOR_CHAR: char = {
|
||||
@ -39,14 +38,10 @@ impl Command for DebugInfo {
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = Span::unknown();
|
||||
|
||||
let record = LazySystemInfoRecord { span };
|
||||
|
||||
Ok(Value::lazy_record(Box::new(record), span).into_pipeline_data())
|
||||
Ok(all_columns(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -58,207 +53,119 @@ impl Command for DebugInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct LazySystemInfoRecord {
|
||||
span: Span,
|
||||
}
|
||||
fn all_columns(span: Span) -> Value {
|
||||
let rk = RefreshKind::new()
|
||||
.with_processes(ProcessRefreshKind::everything())
|
||||
.with_memory(MemoryRefreshKind::everything());
|
||||
|
||||
impl LazySystemInfoRecord {
|
||||
fn get_column_value_with_system(
|
||||
&self,
|
||||
column: &str,
|
||||
system_option: Option<&System>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let pid = Pid::from(std::process::id() as usize);
|
||||
match column {
|
||||
"thread_id" => Ok(Value::int(get_thread_id() as i64, self.span)),
|
||||
"pid" => Ok(Value::int(pid.as_u32() as i64, self.span)),
|
||||
"ppid" => {
|
||||
// only get information requested
|
||||
let system_opt = SystemOpt::from((system_option, || {
|
||||
RefreshKind::new().with_processes(ProcessRefreshKind::everything())
|
||||
}));
|
||||
// only get information requested
|
||||
let sys = System::new_with_specifics(rk);
|
||||
|
||||
let system = system_opt.get_system();
|
||||
// get the process information for the nushell pid
|
||||
let pinfo = system.process(pid);
|
||||
let pid = Pid::from(std::process::id() as usize);
|
||||
let ppid = {
|
||||
sys.process(pid)
|
||||
.and_then(|p| p.parent())
|
||||
.map(|p| Value::int(p.as_u32().into(), span))
|
||||
.unwrap_or(Value::nothing(span))
|
||||
};
|
||||
|
||||
Ok(pinfo
|
||||
.and_then(|p| p.parent())
|
||||
.map(|p| Value::int(p.as_u32() as i64, self.span))
|
||||
.unwrap_or(Value::nothing(self.span)))
|
||||
}
|
||||
"system" => {
|
||||
// only get information requested
|
||||
let system_opt = SystemOpt::from((system_option, || {
|
||||
RefreshKind::new().with_memory(MemoryRefreshKind::everything())
|
||||
}));
|
||||
let system = Value::record(
|
||||
record! {
|
||||
"total_memory" => Value::filesize(sys.total_memory() as i64, span),
|
||||
"free_memory" => Value::filesize(sys.free_memory() as i64, span),
|
||||
"used_memory" => Value::filesize(sys.used_memory() as i64, span),
|
||||
"available_memory" => Value::filesize(sys.available_memory() as i64, span),
|
||||
},
|
||||
span,
|
||||
);
|
||||
|
||||
let system = system_opt.get_system();
|
||||
let process = if let Some(p) = sys.process(pid) {
|
||||
let root = if let Some(path) = p.exe().and_then(|p| p.parent()) {
|
||||
Value::string(path.to_string_lossy().to_string(), span)
|
||||
} else {
|
||||
Value::nothing(span)
|
||||
};
|
||||
|
||||
Ok(Value::record(
|
||||
record! {
|
||||
"total_memory" => Value::filesize(system.total_memory() as i64, self.span),
|
||||
"free_memory" => Value::filesize(system.free_memory() as i64, self.span),
|
||||
"used_memory" => Value::filesize(system.used_memory() as i64, self.span),
|
||||
"available_memory" => Value::filesize(system.available_memory() as i64, self.span),
|
||||
},
|
||||
self.span,
|
||||
))
|
||||
}
|
||||
"process" => {
|
||||
// only get information requested
|
||||
let system_opt = SystemOpt::from((system_option, || {
|
||||
RefreshKind::new().with_processes(ProcessRefreshKind::everything())
|
||||
}));
|
||||
let cwd = if let Some(path) = p.cwd() {
|
||||
Value::string(path.to_string_lossy().to_string(), span)
|
||||
} else {
|
||||
Value::nothing(span)
|
||||
};
|
||||
|
||||
let system = system_opt.get_system();
|
||||
let pinfo = system.process(pid);
|
||||
let exe_path = if let Some(path) = p.exe() {
|
||||
Value::string(path.to_string_lossy().to_string(), span)
|
||||
} else {
|
||||
Value::nothing(span)
|
||||
};
|
||||
|
||||
if let Some(p) = pinfo {
|
||||
Ok(Value::record(
|
||||
record! {
|
||||
"memory" => Value::filesize(p.memory() as i64, self.span),
|
||||
"virtual_memory" => Value::filesize(p.virtual_memory() as i64, self.span),
|
||||
"status" => Value::string(p.status().to_string(), self.span),
|
||||
"root" => {
|
||||
if let Some(path) = p.exe().and_then(|p| p.parent()) {
|
||||
Value::string(path.to_string_lossy().to_string(), self.span)
|
||||
} else {
|
||||
Value::nothing(self.span)
|
||||
}
|
||||
},
|
||||
"cwd" => {
|
||||
if let Some(path) = p.cwd() {
|
||||
Value::string(path.to_string_lossy().to_string(), self.span)
|
||||
}else{
|
||||
Value::nothing(self.span)
|
||||
}
|
||||
},
|
||||
"exe_path" => {
|
||||
if let Some(path)= p.exe() {
|
||||
Value::string(path.to_string_lossy().to_string(), self.span)
|
||||
}else{
|
||||
Value::nothing(self.span)
|
||||
}
|
||||
},
|
||||
"command" => Value::string(p.cmd().join(" "), self.span),
|
||||
"name" => Value::string(p.name().to_string(), self.span),
|
||||
"environment" => {
|
||||
let mut env_rec = Record::new();
|
||||
for val in p.environ() {
|
||||
if let Some((key, value)) = val.split_once('=') {
|
||||
let is_env_var_a_list = {
|
||||
{
|
||||
#[cfg(target_family = "windows")]
|
||||
{
|
||||
key == "Path" || key == "PATHEXT" || key == "PSMODULEPATH" || key == "PSModulePath"
|
||||
}
|
||||
#[cfg(not(target_family = "windows"))]
|
||||
{
|
||||
key == "PATH" || key == "DYLD_FALLBACK_LIBRARY_PATH"
|
||||
}
|
||||
}
|
||||
};
|
||||
if is_env_var_a_list {
|
||||
let items = value.split(ENV_PATH_SEPARATOR_CHAR).map(|r| Value::string(r.to_string(), self.span)).collect::<Vec<_>>();
|
||||
env_rec.push(key.to_string(), Value::list(items, self.span));
|
||||
} else if key == "LS_COLORS" { // LS_COLORS is a special case, it's a colon separated list of key=value pairs
|
||||
let items = value.split(':').map(|r| Value::string(r.to_string(), self.span)).collect::<Vec<_>>();
|
||||
env_rec.push(key.to_string(), Value::list(items, self.span));
|
||||
} else {
|
||||
env_rec.push(key.to_string(), Value::string(value.to_string(), self.span));
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::record(env_rec, self.span)
|
||||
},
|
||||
},
|
||||
self.span,
|
||||
))
|
||||
} else {
|
||||
// If we can't get the process information, just return the system information
|
||||
// only get information requested
|
||||
let system_opt = SystemOpt::from((system_option, || {
|
||||
RefreshKind::new().with_memory(MemoryRefreshKind::everything())
|
||||
}));
|
||||
let system = system_opt.get_system();
|
||||
|
||||
Ok(Value::record(
|
||||
record! {
|
||||
"total_memory" => Value::filesize(system.total_memory() as i64, self.span),
|
||||
"free_memory" => Value::filesize(system.free_memory() as i64, self.span),
|
||||
"used_memory" => Value::filesize(system.used_memory() as i64, self.span),
|
||||
"available_memory" => Value::filesize(system.available_memory() as i64, self.span),
|
||||
},
|
||||
self.span,
|
||||
))
|
||||
let environment = {
|
||||
let mut env_rec = Record::new();
|
||||
for val in p.environ() {
|
||||
if let Some((key, value)) = val.split_once('=') {
|
||||
let is_env_var_a_list = {
|
||||
{
|
||||
#[cfg(target_family = "windows")]
|
||||
{
|
||||
key == "Path"
|
||||
|| key == "PATHEXT"
|
||||
|| key == "PSMODULEPATH"
|
||||
|| key == "PSModulePath"
|
||||
}
|
||||
#[cfg(not(target_family = "windows"))]
|
||||
{
|
||||
key == "PATH" || key == "DYLD_FALLBACK_LIBRARY_PATH"
|
||||
}
|
||||
}
|
||||
};
|
||||
if is_env_var_a_list {
|
||||
let items = value
|
||||
.split(ENV_PATH_SEPARATOR_CHAR)
|
||||
.map(|r| Value::string(r.to_string(), span))
|
||||
.collect::<Vec<_>>();
|
||||
env_rec.push(key.to_string(), Value::list(items, span));
|
||||
} else if key == "LS_COLORS" {
|
||||
// LS_COLORS is a special case, it's a colon separated list of key=value pairs
|
||||
let items = value
|
||||
.split(':')
|
||||
.map(|r| Value::string(r.to_string(), span))
|
||||
.collect::<Vec<_>>();
|
||||
env_rec.push(key.to_string(), Value::list(items, span));
|
||||
} else {
|
||||
env_rec.push(key.to_string(), Value::string(value.to_string(), span));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::IncompatibleParametersSingle {
|
||||
msg: format!("Unknown column: {}", column),
|
||||
span: self.span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::record(env_rec, span)
|
||||
};
|
||||
|
||||
impl<'a> LazyRecord<'a> for LazySystemInfoRecord {
|
||||
fn column_names(&'a self) -> Vec<&'a str> {
|
||||
vec!["thread_id", "pid", "ppid", "process", "system"]
|
||||
}
|
||||
Value::record(
|
||||
record! {
|
||||
"memory" => Value::filesize(p.memory() as i64, span),
|
||||
"virtual_memory" => Value::filesize(p.virtual_memory() as i64, span),
|
||||
"status" => Value::string(p.status().to_string(), span),
|
||||
"root" => root,
|
||||
"cwd" => cwd,
|
||||
"exe_path" => exe_path,
|
||||
"command" => Value::string(p.cmd().join(" "), span),
|
||||
"name" => Value::string(p.name(), span),
|
||||
"environment" => environment,
|
||||
},
|
||||
span,
|
||||
)
|
||||
} else {
|
||||
Value::nothing(span)
|
||||
};
|
||||
|
||||
fn get_column_value(&self, column: &str) -> Result<Value, ShellError> {
|
||||
self.get_column_value_with_system(column, None)
|
||||
}
|
||||
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
|
||||
fn clone_value(&self, span: Span) -> Value {
|
||||
Value::lazy_record(Box::new(LazySystemInfoRecord { span }), span)
|
||||
}
|
||||
|
||||
fn collect(&'a self) -> Result<Value, ShellError> {
|
||||
let rk = RefreshKind::new()
|
||||
.with_processes(ProcessRefreshKind::everything())
|
||||
.with_memory(MemoryRefreshKind::everything());
|
||||
// only get information requested
|
||||
let system = System::new_with_specifics(rk);
|
||||
|
||||
self.column_names()
|
||||
.into_iter()
|
||||
.map(|col| {
|
||||
let val = self.get_column_value_with_system(col, Some(&system))?;
|
||||
Ok((col.to_owned(), val))
|
||||
})
|
||||
.collect::<Result<Record, _>>()
|
||||
.map(|record| Value::record(record, self.span()))
|
||||
}
|
||||
}
|
||||
|
||||
enum SystemOpt<'a> {
|
||||
Ptr(&'a System),
|
||||
Owned(Box<System>),
|
||||
}
|
||||
|
||||
impl<'a> SystemOpt<'a> {
|
||||
fn get_system(&'a self) -> &'a System {
|
||||
match self {
|
||||
SystemOpt::Ptr(system) => system,
|
||||
SystemOpt::Owned(system) => system,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, F: Fn() -> RefreshKind> From<(Option<&'a System>, F)> for SystemOpt<'a> {
|
||||
fn from((system_opt, refresh_kind_create): (Option<&'a System>, F)) -> Self {
|
||||
match system_opt {
|
||||
Some(system) => SystemOpt::<'a>::Ptr(system),
|
||||
None => SystemOpt::Owned(Box::new(System::new_with_specifics(refresh_kind_create()))),
|
||||
}
|
||||
}
|
||||
Value::record(
|
||||
record! {
|
||||
"thread_id" => Value::int(get_thread_id() as i64, span),
|
||||
"pid" => Value::int(pid.as_u32().into(), span),
|
||||
"ppid" => ppid,
|
||||
"system" => system,
|
||||
"process" => process,
|
||||
},
|
||||
span,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_thread_id() -> u64 {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_utils::filesystem::{have_permission, PermissionResult};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -20,6 +20,7 @@ impl Command for Cd {
|
||||
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),
|
||||
@ -36,8 +37,9 @@ impl Command for Cd {
|
||||
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)?;
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
|
||||
let path_val = {
|
||||
if let Some(path) = path_val {
|
||||
@ -53,54 +55,53 @@ impl Command for Cd {
|
||||
let (path, span) = match path_val {
|
||||
Some(v) => {
|
||||
if v.item == "-" {
|
||||
let oldpwd = stack.get_env_var(engine_state, "OLDPWD");
|
||||
|
||||
if let Some(oldpwd) = oldpwd {
|
||||
let path = oldpwd.to_path()?;
|
||||
let path = match nu_path::canonicalize_with(path.clone(), &cwd) {
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
return Err(ShellError::DirectoryNotFound {
|
||||
dir: path.to_string_lossy().to_string(),
|
||||
span: v.span,
|
||||
});
|
||||
}
|
||||
};
|
||||
(path.to_string_lossy().to_string(), v.span)
|
||||
if let Some(oldpwd) = stack.get_env_var(engine_state, "OLDPWD") {
|
||||
(oldpwd.to_path()?, v.span)
|
||||
} else {
|
||||
(cwd.to_string_lossy().to_string(), v.span)
|
||||
(cwd, v.span)
|
||||
}
|
||||
} else {
|
||||
// Trim whitespace from the end of path.
|
||||
let path_no_whitespace =
|
||||
&v.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
|
||||
|
||||
let path = match nu_path::canonicalize_with(path_no_whitespace, &cwd) {
|
||||
Ok(p) => {
|
||||
if !p.is_dir() {
|
||||
// If `--physical` is specified, canonicalize the path; otherwise expand the path.
|
||||
let 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 });
|
||||
};
|
||||
p
|
||||
}
|
||||
|
||||
// if canonicalize failed, let's check to see if it's abbreviated
|
||||
Err(_) => {
|
||||
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
|
||||
};
|
||||
(path.to_string_lossy().to_string(), v.span)
|
||||
(path, v.span)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let path = nu_path::expand_tilde("~");
|
||||
(path.to_string_lossy().to_string(), call.head)
|
||||
(path, call.head)
|
||||
}
|
||||
};
|
||||
|
||||
let path_value = Value::string(path.clone(), span);
|
||||
|
||||
// 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)
|
||||
}
|
||||
@ -109,11 +110,15 @@ impl Command for Cd {
|
||||
//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.add_env_var("PWD".into(), path_value);
|
||||
stack.add_env_var("PWD".into(), Value::string(path.to_string_lossy(), span));
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
PermissionResult::PermissionDenied(reason) => Err(ShellError::IOError {
|
||||
msg: format!("Cannot change directory to {path}: {reason}"),
|
||||
msg: format!(
|
||||
"Cannot change directory to {}: {}",
|
||||
path.to_string_lossy(),
|
||||
reason
|
||||
),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
use crate::{DirBuilder, DirInfo, FileInfo};
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_glob::Pattern;
|
||||
use nu_protocol::NuGlob;
|
||||
@ -98,6 +99,7 @@ impl Command for Du {
|
||||
let all = call.has_flag(engine_state, stack, "all")?;
|
||||
let deref = call.has_flag(engine_state, stack, "deref")?;
|
||||
let exclude = call.get_flag(engine_state, stack, "exclude")?;
|
||||
#[allow(deprecated)]
|
||||
let current_dir = current_dir(engine_state, stack)?;
|
||||
|
||||
let paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
|
@ -1,3 +1,4 @@
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, env::current_dir};
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
use wax::{Glob as WaxGlob, WalkBehavior, WalkEntry};
|
||||
@ -178,6 +179,7 @@ impl Command for Glob {
|
||||
}
|
||||
};
|
||||
|
||||
#[allow(deprecated)]
|
||||
let path = current_dir(engine_state, stack)?;
|
||||
let path = match nu_path::canonicalize_with(prefix, path) {
|
||||
Ok(path) => path,
|
||||
|
@ -1,6 +1,7 @@
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
use crate::{DirBuilder, DirInfo};
|
||||
use chrono::{DateTime, Local, LocalResult, TimeZone, Utc};
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, env::current_dir};
|
||||
use nu_glob::{MatchOptions, Pattern};
|
||||
use nu_path::expand_to_real_path;
|
||||
@ -91,6 +92,7 @@ impl Command for Ls {
|
||||
let use_mime_type = call.has_flag(engine_state, stack, "mime-type")?;
|
||||
let ctrl_c = engine_state.ctrlc.clone();
|
||||
let call_span = call.head;
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
let args = Args {
|
||||
@ -429,7 +431,7 @@ fn ls_for_one_pattern(
|
||||
Err(err) => Some(Value::error(err, call_span)),
|
||||
}
|
||||
}
|
||||
_ => Some(Value::nothing(call_span)),
|
||||
Err(err) => Some(Value::error(err, call_span)),
|
||||
})))
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, env::current_dir};
|
||||
use std::path::PathBuf;
|
||||
|
||||
@ -90,6 +91,7 @@ impl Command for Mktemp {
|
||||
} else if directory || tmpdir {
|
||||
Some(std::env::temp_dir())
|
||||
} else {
|
||||
#[allow(deprecated)]
|
||||
Some(current_dir(engine_state, stack)?)
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir, get_eval_block};
|
||||
use nu_protocol::{BufferedReader, DataSource, NuGlob, PipelineMetadata, RawStream};
|
||||
use std::{io::BufReader, path::Path};
|
||||
@ -51,6 +52,7 @@ impl Command for Open {
|
||||
let raw = call.has_flag(engine_state, stack, "raw")?;
|
||||
let call_span = call.head;
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
let eval_block = get_eval_block(engine_state);
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::util::{get_rest_for_glob_pattern, try_interaction};
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, env::current_dir};
|
||||
use nu_glob::MatchOptions;
|
||||
use nu_path::expand_path_with;
|
||||
@ -130,6 +131,7 @@ fn rm(
|
||||
|
||||
let mut unique_argument_check = None;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let currentdir_path = current_dir(engine_state, stack)?;
|
||||
|
||||
let home: Option<String> = nu_path::home_dir().map(|path| {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::progress_bar;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::{
|
||||
@ -85,6 +86,7 @@ impl Command for Save {
|
||||
};
|
||||
|
||||
let span = call.head;
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
let path_arg = call.req::<Spanned<PathBuf>>(engine_state, stack, 0)?;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use filetime::FileTime;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::NuGlob;
|
||||
@ -113,6 +114,7 @@ impl Command for Touch {
|
||||
})?;
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
for (index, glob) in files.into_iter().enumerate() {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use std::path::PathBuf;
|
||||
use uu_cp::{BackupMode, CopyMode, UpdateMode};
|
||||
@ -177,6 +178,7 @@ impl Command for UCp {
|
||||
let target_path = PathBuf::from(&nu_utils::strip_ansi_string_unlikely(
|
||||
target.item.to_string(),
|
||||
));
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let target_path = nu_path::expand_path_with(target_path, &cwd, target.item.is_expand());
|
||||
if target.item.as_ref().ends_with(PATH_SEPARATOR) && !target_path.is_dir() {
|
||||
|
@ -1,3 +1,4 @@
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
|
||||
use uu_mkdir::mkdir;
|
||||
@ -58,6 +59,7 @@ impl Command for UMkdir {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let mut directories = get_rest_for_glob_pattern(engine_state, stack, call, 0)?
|
||||
.into_iter()
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::NuGlob;
|
||||
@ -77,6 +78,7 @@ impl Command for UMv {
|
||||
uu_mv::OverwriteMode::Force
|
||||
};
|
||||
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
if paths.is_empty() {
|
||||
|
@ -5,6 +5,7 @@ use notify_debouncer_full::{
|
||||
EventKind, RecursiveMode, Watcher,
|
||||
},
|
||||
};
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir, ClosureEval};
|
||||
use nu_protocol::{
|
||||
engine::{Closure, StateWorkingSet},
|
||||
@ -73,6 +74,7 @@ impl Command for Watch {
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let path_arg: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
|
||||
|
@ -105,18 +105,6 @@ fn getcol(
|
||||
.into_pipeline_data(ctrlc)
|
||||
.set_metadata(metadata))
|
||||
}
|
||||
Value::LazyRecord { val, .. } => {
|
||||
Ok({
|
||||
// Unfortunate casualty to LazyRecord's column_names not generating 'static strs
|
||||
let cols: Vec<_> =
|
||||
val.column_names().iter().map(|s| s.to_string()).collect();
|
||||
|
||||
cols.into_iter()
|
||||
.map(move |x| Value::string(x, head))
|
||||
.into_pipeline_data(ctrlc)
|
||||
.set_metadata(metadata)
|
||||
})
|
||||
}
|
||||
Value::Record { val, .. } => Ok(val
|
||||
.into_iter()
|
||||
.map(move |(x, _)| Value::string(x, head))
|
||||
|
@ -533,15 +533,6 @@ fn value_should_be_printed(
|
||||
Value::Record { val, .. } => {
|
||||
record_matches_term(val, columns_to_search, filter_config, term, span)
|
||||
}
|
||||
Value::LazyRecord { val, .. } => match val.collect() {
|
||||
Ok(val) => match val {
|
||||
Value::Record { val, .. } => {
|
||||
record_matches_term(&val, columns_to_search, filter_config, term, span)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
Err(_) => false,
|
||||
},
|
||||
Value::Binary { .. } => false,
|
||||
});
|
||||
if invert {
|
||||
|
@ -44,12 +44,6 @@ impl Command for Items {
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Value(value, ..) => {
|
||||
let value = if let Value::LazyRecord { val, .. } = value {
|
||||
val.collect()?
|
||||
} else {
|
||||
value
|
||||
};
|
||||
|
||||
let span = value.span();
|
||||
match value {
|
||||
Value::Record { val, .. } => {
|
||||
|
@ -161,19 +161,6 @@ fn values(
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_pipeline_data_with_metadata(metadata, ctrlc)),
|
||||
Value::LazyRecord { val, .. } => {
|
||||
let record = match val.collect()? {
|
||||
Value::Record { val, .. } => val,
|
||||
_ => Err(ShellError::NushellFailedSpanned {
|
||||
msg: "`LazyRecord::collect()` promises `Value::Record`".into(),
|
||||
label: "Violating lazy record found here".into(),
|
||||
span,
|
||||
})?,
|
||||
};
|
||||
Ok(record
|
||||
.into_values()
|
||||
.into_pipeline_data_with_metadata(metadata, ctrlc))
|
||||
}
|
||||
// Propagate errors
|
||||
Value::Error { error, .. } => Err(*error),
|
||||
other => Err(ShellError::OnlySupportsThisInputType {
|
||||
|
@ -135,10 +135,6 @@ pub fn value_to_json_value(v: &Value) -> Result<nu_json::Value, ShellError> {
|
||||
}
|
||||
nu_json::Value::Object(m)
|
||||
}
|
||||
Value::LazyRecord { val, .. } => {
|
||||
let collected = val.collect()?;
|
||||
value_to_json_value(&collected)?
|
||||
}
|
||||
Value::Custom { val, .. } => {
|
||||
let collected = val.to_base_value(span)?;
|
||||
value_to_json_value(&collected)?
|
||||
|
@ -246,9 +246,6 @@ pub(crate) fn write_value(
|
||||
Value::Custom { val, .. } => {
|
||||
write_value(out, &val.to_base_value(span)?, depth)?;
|
||||
}
|
||||
Value::LazyRecord { val, .. } => {
|
||||
write_value(out, &val.collect()?, depth)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -129,10 +129,6 @@ fn local_into_string(value: Value, separator: &str, config: &Config) -> String {
|
||||
.map(|(x, y)| format!("{}: {}", x, local_into_string(y, ", ", config)))
|
||||
.collect::<Vec<_>>()
|
||||
.join(separator),
|
||||
Value::LazyRecord { val, .. } => match val.collect() {
|
||||
Ok(val) => local_into_string(val, separator, config),
|
||||
Err(error) => format!("{error:?}"),
|
||||
},
|
||||
Value::Closure { val, .. } => format!("<Closure {}>", val.block_id),
|
||||
Value::Nothing { .. } => String::new(),
|
||||
Value::Error { error, .. } => format!("{error:?}"),
|
||||
|
@ -62,10 +62,6 @@ fn helper(engine_state: &EngineState, v: &Value) -> Result<toml::Value, ShellErr
|
||||
}
|
||||
toml::Value::Table(m)
|
||||
}
|
||||
Value::LazyRecord { val, .. } => {
|
||||
let collected = val.collect()?;
|
||||
helper(engine_state, &collected)?
|
||||
}
|
||||
Value::List { vals, .. } => toml::Value::Array(toml_list(engine_state, vals)?),
|
||||
Value::Closure { .. } => {
|
||||
let code = engine_state.get_span_contents(span);
|
||||
|
@ -62,10 +62,6 @@ pub fn value_to_yaml_value(v: &Value) -> Result<serde_yaml::Value, ShellError> {
|
||||
}
|
||||
serde_yaml::Value::Mapping(m)
|
||||
}
|
||||
Value::LazyRecord { val, .. } => {
|
||||
let collected = val.collect()?;
|
||||
value_to_yaml_value(&collected)?
|
||||
}
|
||||
Value::List { vals, .. } => {
|
||||
let mut out = vec![];
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::PathSubcommandArguments;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir, current_dir_const};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
@ -53,6 +54,7 @@ If you need to distinguish dirs and files, please use `path type`."#
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
#[allow(deprecated)]
|
||||
let args = Arguments {
|
||||
pwd: current_dir(engine_state, stack)?,
|
||||
not_follow_symlink: call.has_flag(engine_state, stack, "no-symlink")?,
|
||||
@ -74,6 +76,7 @@ If you need to distinguish dirs and files, please use `path type`."#
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
#[allow(deprecated)]
|
||||
let args = Arguments {
|
||||
pwd: current_dir_const(working_set)?,
|
||||
not_follow_symlink: call.has_flag_const(working_set, "no-symlink")?,
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::PathSubcommandArguments;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{
|
||||
command_prelude::*,
|
||||
env::{current_dir_str, current_dir_str_const},
|
||||
@ -57,6 +58,7 @@ impl Command for SubCommand {
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
#[allow(deprecated)]
|
||||
let args = Arguments {
|
||||
strict: call.has_flag(engine_state, stack, "strict")?,
|
||||
cwd: current_dir_str(engine_state, stack)?,
|
||||
@ -79,6 +81,7 @@ impl Command for SubCommand {
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
#[allow(deprecated)]
|
||||
let args = Arguments {
|
||||
strict: call.has_flag_const(working_set, "strict")?,
|
||||
cwd: current_dir_str_const(working_set)?,
|
||||
|
@ -149,7 +149,7 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||
),
|
||||
Ordering::Less => Value::string(
|
||||
{
|
||||
if end == isize::max_value() {
|
||||
if end == isize::MAX {
|
||||
if args.graphemes {
|
||||
s.graphemes(true)
|
||||
.skip(start as usize)
|
||||
@ -245,7 +245,7 @@ mod tests {
|
||||
expectation("andre", (0, -1)),
|
||||
// str substring [ -4 , _ ]
|
||||
// str substring -4 ,
|
||||
expectation("dres", (-4, isize::max_value())),
|
||||
expectation("dres", (-4, isize::MAX)),
|
||||
expectation("", (0, -110)),
|
||||
expectation("", (6, 0)),
|
||||
expectation("", (6, -1)),
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::run_external::create_external_command;
|
||||
#[allow(deprecated)]
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_protocol::OutDest;
|
||||
|
||||
@ -62,6 +63,7 @@ fn exec(
|
||||
external_command.out = OutDest::Inherit;
|
||||
external_command.err = OutDest::Inherit;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let mut command = external_command.spawn_simple_command(&cwd.to_string_lossy())?;
|
||||
command.current_dir(cwd);
|
||||
|
@ -1,6 +1,5 @@
|
||||
use chrono::{DateTime, Local};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::LazyRecord;
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
use sysinfo::{
|
||||
Components, CpuRefreshKind, Disks, Networks, System, Users, MINIMUM_CPU_UPDATE_INTERVAL,
|
||||
@ -32,10 +31,7 @@ impl Command for Sys {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.span();
|
||||
let ret = Value::lazy_record(Box::new(SysResult { span }), span);
|
||||
|
||||
Ok(ret.into_pipeline_data())
|
||||
Ok(all_columns(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -64,36 +60,18 @@ pub struct SysResult {
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl LazyRecord<'_> for SysResult {
|
||||
fn column_names(&self) -> Vec<&'static str> {
|
||||
vec!["host", "cpu", "disks", "mem", "temp", "net"]
|
||||
}
|
||||
|
||||
fn get_column_value(&self, column: &str) -> Result<Value, ShellError> {
|
||||
let span = self.span;
|
||||
|
||||
match column {
|
||||
"host" => Ok(host(span)),
|
||||
"cpu" => Ok(cpu(span)),
|
||||
"disks" => Ok(disks(span)),
|
||||
"mem" => Ok(mem(span)),
|
||||
"temp" => Ok(temp(span)),
|
||||
"net" => Ok(net(span)),
|
||||
_ => Err(ShellError::LazyRecordAccessFailed {
|
||||
message: format!("Could not find column '{column}'"),
|
||||
column_name: column.to_string(),
|
||||
span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
|
||||
fn clone_value(&self, span: Span) -> Value {
|
||||
Value::lazy_record(Box::new((*self).clone()), span)
|
||||
}
|
||||
fn all_columns(span: Span) -> Value {
|
||||
Value::record(
|
||||
record! {
|
||||
"host" => host(span),
|
||||
"cpu" => cpu(span),
|
||||
"disks" => disks(span),
|
||||
"mem" => mem(span),
|
||||
"temp" => temp(span),
|
||||
"net" => net(span),
|
||||
},
|
||||
span,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn trim_cstyle_null(s: String) -> String {
|
||||
|
@ -229,6 +229,7 @@ fn which(
|
||||
|
||||
let mut output = vec![];
|
||||
|
||||
#[allow(deprecated)]
|
||||
let cwd = env::current_dir_str(engine_state, stack)?;
|
||||
let paths = env::path_str(engine_state, stack, call.head)?;
|
||||
|
||||
|
@ -394,10 +394,6 @@ fn handle_table_command(
|
||||
input.data = PipelineData::Empty;
|
||||
handle_record(input, cfg, val)
|
||||
}
|
||||
PipelineData::Value(Value::LazyRecord { val, .. }, ..) => {
|
||||
input.data = val.collect()?.into_pipeline_data();
|
||||
handle_table_command(input, cfg)
|
||||
}
|
||||
PipelineData::Value(Value::Error { error, .. }, ..) => {
|
||||
// Propagate this error outward, so that it goes to stderr
|
||||
// instead of stdout.
|
||||
@ -942,7 +938,11 @@ fn render_path_name(
|
||||
|
||||
// clickable links don't work in remote SSH sessions
|
||||
let in_ssh_session = std::env::var("SSH_CLIENT").is_ok();
|
||||
let show_clickable_links = config.show_clickable_links_in_ls && !in_ssh_session && has_metadata;
|
||||
//TODO: Deprecated show_clickable_links_in_ls in favor of shell_integration_osc8
|
||||
let show_clickable_links = config.show_clickable_links_in_ls
|
||||
&& !in_ssh_session
|
||||
&& has_metadata
|
||||
&& config.shell_integration_osc8;
|
||||
|
||||
let ansi_style = style.map(Style::to_nu_ansi_term_style).unwrap_or_default();
|
||||
|
||||
|
@ -207,7 +207,15 @@ fn filesystem_change_directory_to_symlink_relative() {
|
||||
$env.PWD
|
||||
"
|
||||
);
|
||||
assert_eq!(PathBuf::from(actual.out), dirs.test().join("foo_link"));
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test().join("boo"),
|
||||
"
|
||||
cd -P ../foo_link
|
||||
$env.PWD
|
||||
"
|
||||
);
|
||||
assert_eq!(PathBuf::from(actual.out), dirs.test().join("foo"));
|
||||
})
|
||||
}
|
||||
|
@ -98,21 +98,6 @@ fn insert_uses_enumerate_index() {
|
||||
assert_eq!(actual.out, "[[index, a, b]; [0, 7, 8], [1, 6, 8]]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_support_lazy_record() {
|
||||
let actual =
|
||||
nu!(r#"let x = (lazy make -c ["h"] -g {|a| $a | str upcase}); $x | insert a 10 | get a"#);
|
||||
assert_eq!(actual.out, "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lazy_record_test_values() {
|
||||
let actual = nu!(
|
||||
r#"lazy make --columns ["haskell", "futures", "nushell"] --get-value { |lazything| $lazything + "!" } | values | length"#
|
||||
);
|
||||
assert_eq!(actual.out, "3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deep_cell_path_creates_all_nested_records() {
|
||||
let actual = nu!("{a: {}} | insert a.b.c 0 | get a.b.c");
|
||||
|
@ -91,3 +91,18 @@ fn let_glob_type() {
|
||||
let actual = nu!("let x: glob = 'aa'; $x | describe");
|
||||
assert_eq!(actual.out, "glob");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_raw_string() {
|
||||
let actual = nu!(r#"let x = r#'abcde""fghi"''''jkl'#; $x"#);
|
||||
assert_eq!(actual.out, r#"abcde""fghi"''''jkl"#);
|
||||
|
||||
let actual = nu!(r#"let x = r##'abcde""fghi"''''#jkl'##; $x"#);
|
||||
assert_eq!(actual.out, r#"abcde""fghi"''''#jkl"#);
|
||||
|
||||
let actual = nu!(r#"let x = r###'abcde""fghi"'''##'#jkl'###; $x"#);
|
||||
assert_eq!(actual.out, r#"abcde""fghi"'''##'#jkl"#);
|
||||
|
||||
let actual = nu!(r#"let x = r#'abc'#; $x"#);
|
||||
assert_eq!(actual.out, "abc");
|
||||
}
|
||||
|
@ -125,3 +125,18 @@ fn mut_glob_type() {
|
||||
let actual = nu!("mut x: glob = 'aa'; $x | describe");
|
||||
assert_eq!(actual.out, "glob");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mut_raw_string() {
|
||||
let actual = nu!(r#"mut x = r#'abcde""fghi"''''jkl'#; $x"#);
|
||||
assert_eq!(actual.out, r#"abcde""fghi"''''jkl"#);
|
||||
|
||||
let actual = nu!(r#"mut x = r##'abcde""fghi"''''#jkl'##; $x"#);
|
||||
assert_eq!(actual.out, r#"abcde""fghi"''''#jkl"#);
|
||||
|
||||
let actual = nu!(r#"mut x = r###'abcde""fghi"'''##'#jkl'###; $x"#);
|
||||
assert_eq!(actual.out, r#"abcde""fghi"'''##'#jkl"#);
|
||||
|
||||
let actual = nu!(r#"mut x = r#'abc'#; $x"#);
|
||||
assert_eq!(actual.out, "abc");
|
||||
}
|
||||
|
@ -103,13 +103,6 @@ fn update_uses_enumerate_index() {
|
||||
assert_eq!(actual.out, "[[index, a]; [0, 8], [1, 8]]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_support_lazy_record() {
|
||||
let actual =
|
||||
nu!(r#"let x = (lazy make -c ["h"] -g {|a| $a | str upcase}); $x | update h 10 | get h"#);
|
||||
assert_eq!(actual.out, "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_replacement_closure() {
|
||||
let actual = nu!("[1, 2] | update 1 {|i| $i + 1 } | to nuon");
|
||||
|
@ -112,17 +112,6 @@ fn upsert_past_end_of_list_stream() {
|
||||
.contains("can't insert at index (the next available index is 3)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upsert_support_lazy_record() {
|
||||
let actual =
|
||||
nu!(r#"let x = (lazy make -c ["h"] -g {|a| $a | str upcase}); $x | upsert h 10 | get h"#);
|
||||
assert_eq!(actual.out, "10");
|
||||
|
||||
let actual =
|
||||
nu!(r#"let x = (lazy make -c ["h"] -g {|a| $a | str upcase}); $x | upsert aa 10 | get aa"#);
|
||||
assert_eq!(actual.out, "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deep_cell_path_creates_all_nested_records() {
|
||||
let actual = nu!("{a: {}} | upsert a.b.c 0 | get a.b.c");
|
||||
|
@ -2,7 +2,7 @@ use crate::ClosureEvalOnce;
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::{
|
||||
ast::{Call, Expr},
|
||||
engine::{EngineState, Stack, StateWorkingSet, PWD_ENV},
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
Config, ShellError, Span, Value, VarId,
|
||||
};
|
||||
use std::{
|
||||
@ -156,85 +156,56 @@ pub fn env_to_strings(
|
||||
Ok(env_vars_str)
|
||||
}
|
||||
|
||||
/// Shorthand for env_to_string() for PWD with custom error
|
||||
/// Returns the current working directory as a String, which is guaranteed to be canonicalized.
|
||||
/// Unlike `current_dir_str_const()`, this also considers modifications to the current working directory made on the stack.
|
||||
///
|
||||
/// Returns an error if $env.PWD doesn't exist, is not a String, or is not an absolute path.
|
||||
#[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
|
||||
pub fn current_dir_str(engine_state: &EngineState, stack: &Stack) -> Result<String, ShellError> {
|
||||
if let Some(pwd) = stack.get_env_var(engine_state, PWD_ENV) {
|
||||
// TODO: PWD should be string by default, we don't need to run ENV_CONVERSIONS on it
|
||||
match env_to_string(PWD_ENV, &pwd, engine_state, stack) {
|
||||
Ok(cwd) => {
|
||||
if Path::new(&cwd).is_absolute() {
|
||||
Ok(cwd)
|
||||
} else {
|
||||
Err(ShellError::GenericError {
|
||||
error: "Invalid current directory".into(),
|
||||
msg: format!("The 'PWD' environment variable must be set to an absolute path. Found: '{cwd}'"),
|
||||
span: Some(pwd.span()),
|
||||
help: None,
|
||||
inner: vec![]
|
||||
})
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::GenericError {
|
||||
error: "Current directory not found".into(),
|
||||
msg: "".into(),
|
||||
span: None,
|
||||
help: Some("The environment variable 'PWD' was not found. It is required to define the current directory.".into()),
|
||||
inner: vec![],
|
||||
})
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
current_dir(engine_state, stack).map(|path| path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
/// Simplified version of current_dir_str() for constant evaluation
|
||||
/// Returns the current working directory as a String, which is guaranteed to be canonicalized.
|
||||
///
|
||||
/// Returns an error if $env.PWD doesn't exist, is not a String, or is not an absolute path.
|
||||
#[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
|
||||
pub fn current_dir_str_const(working_set: &StateWorkingSet) -> Result<String, ShellError> {
|
||||
if let Some(pwd) = working_set.get_env_var(PWD_ENV) {
|
||||
let span = pwd.span();
|
||||
match pwd {
|
||||
Value::String { val, .. } => {
|
||||
if Path::new(val).is_absolute() {
|
||||
Ok(val.clone())
|
||||
} else {
|
||||
Err(ShellError::GenericError {
|
||||
error: "Invalid current directory".into(),
|
||||
msg: format!("The 'PWD' environment variable must be set to an absolute path. Found: '{val}'"),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![]
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::GenericError {
|
||||
error: "PWD is not a string".into(),
|
||||
msg: "".into(),
|
||||
span: None,
|
||||
help: Some(
|
||||
"Cusrrent working directory environment variable 'PWD' must be a string."
|
||||
.into(),
|
||||
),
|
||||
inner: vec![],
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::GenericError{
|
||||
error: "Current directory not found".into(),
|
||||
msg: "".into(),
|
||||
span: None,
|
||||
help: Some("The environment variable 'PWD' was not found. It is required to define the current directory.".into()),
|
||||
inner: vec![],
|
||||
})
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
current_dir_const(working_set).map(|path| path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
/// Calls current_dir_str() and returns the current directory as a PathBuf
|
||||
/// Returns the current working directory, which is guaranteed to be canonicalized.
|
||||
/// Unlike `current_dir_const()`, this also considers modifications to the current working directory made on the stack.
|
||||
///
|
||||
/// Returns an error if $env.PWD doesn't exist, is not a String, or is not an absolute path.
|
||||
#[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
|
||||
pub fn current_dir(engine_state: &EngineState, stack: &Stack) -> Result<PathBuf, ShellError> {
|
||||
current_dir_str(engine_state, stack).map(PathBuf::from)
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
// `EngineState::cwd()` always returns absolute path.
|
||||
// We're using `canonicalize_with` instead of `fs::canonicalize()` because
|
||||
// we still need to simplify Windows paths. "." is safe because `cwd` should
|
||||
// be an absolute path already.
|
||||
canonicalize_with(&cwd, ".").map_err(|_| ShellError::DirectoryNotFound {
|
||||
dir: cwd.to_string_lossy().to_string(),
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Version of current_dir() for constant evaluation
|
||||
/// Returns the current working directory, which is guaranteed to be canonicalized.
|
||||
///
|
||||
/// Returns an error if $env.PWD doesn't exist, is not a String, or is not an absolute path.
|
||||
#[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
|
||||
pub fn current_dir_const(working_set: &StateWorkingSet) -> Result<PathBuf, ShellError> {
|
||||
current_dir_str_const(working_set).map(PathBuf::from)
|
||||
let cwd = working_set.permanent_state.cwd(None)?;
|
||||
// `EngineState::cwd()` always returns absolute path.
|
||||
// We're using `canonicalize_with` instead of `fs::canonicalize()` because
|
||||
// we still need to simplify Windows paths. "." is safe because `cwd` should
|
||||
// be an absolute path already.
|
||||
canonicalize_with(&cwd, ".").map_err(|_| ShellError::DirectoryNotFound {
|
||||
dir: cwd.to_string_lossy().to_string(),
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the contents of path environment variable as a list of strings
|
||||
@ -315,6 +286,7 @@ pub fn find_in_dirs_env(
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
} else {
|
||||
#[allow(deprecated)]
|
||||
current_dir_str(engine_state, stack)?
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::{current_dir, current_dir_str, get_config, get_full_help};
|
||||
#[allow(deprecated)]
|
||||
use crate::{current_dir, get_config, get_full_help};
|
||||
use nu_path::expand_path_with;
|
||||
use nu_protocol::{
|
||||
ast::{
|
||||
@ -325,6 +326,7 @@ fn eval_redirection<D: DebugContext>(
|
||||
) -> Result<Redirection, ShellError> {
|
||||
match target {
|
||||
RedirectionTarget::File { expr, append, .. } => {
|
||||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
let value = eval_expression::<D>(engine_state, stack, expr)?;
|
||||
let path = Spanned::<PathBuf>::from_value(value)?.item;
|
||||
@ -633,7 +635,7 @@ impl Eval for EvalRuntime {
|
||||
if quoted {
|
||||
Ok(Value::string(path, span))
|
||||
} else {
|
||||
let cwd = current_dir_str(engine_state, stack)?;
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
let path = expand_path_with(path, cwd, true);
|
||||
|
||||
Ok(Value::string(path.to_string_lossy(), span))
|
||||
@ -652,7 +654,7 @@ impl Eval for EvalRuntime {
|
||||
} else if quoted {
|
||||
Ok(Value::string(path, span))
|
||||
} else {
|
||||
let cwd = current_dir_str(engine_state, stack)?;
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
let path = expand_path_with(path, cwd, true);
|
||||
|
||||
Ok(Value::string(path.to_string_lossy(), span))
|
||||
|
@ -51,10 +51,3 @@ pub trait ViewCommand {
|
||||
value: Option<Value>,
|
||||
) -> Result<Self::View>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Shortcode {
|
||||
pub code: &'static str,
|
||||
pub context: &'static str,
|
||||
pub description: &'static str,
|
||||
}
|
||||
|
@ -112,10 +112,6 @@ pub fn collect_input(value: Value) -> Result<(Vec<String>, Vec<Vec<Value>>)> {
|
||||
|
||||
Ok((vec![String::from("")], lines))
|
||||
}
|
||||
Value::LazyRecord { val, .. } => {
|
||||
let materialized = val.collect()?;
|
||||
collect_input(materialized)
|
||||
}
|
||||
Value::Nothing { .. } => Ok((vec![], vec![])),
|
||||
Value::Custom { val, .. } => {
|
||||
let materialized = val.to_base_value(span)?;
|
||||
|
@ -4,7 +4,7 @@ use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
text::Span,
|
||||
widgets::{Paragraph, StatefulWidget, Widget},
|
||||
widgets::{Paragraph, Widget},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -12,10 +12,6 @@ use crate::{
|
||||
views::util::{nu_style_to_tui, text_style_to_tui_style},
|
||||
};
|
||||
|
||||
use super::Layout;
|
||||
|
||||
type OptStyle = Option<NuStyle>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BinaryWidget<'a> {
|
||||
data: &'a [u8],
|
||||
@ -73,7 +69,7 @@ impl BinarySettings {
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct BinaryStyle {
|
||||
colors: BinaryStyleColors,
|
||||
color_index: Option<NuStyle>,
|
||||
indent_index: Indent,
|
||||
indent_data: Indent,
|
||||
indent_ascii: Indent,
|
||||
@ -83,7 +79,7 @@ pub struct BinaryStyle {
|
||||
|
||||
impl BinaryStyle {
|
||||
pub fn new(
|
||||
colors: BinaryStyleColors,
|
||||
color_index: Option<NuStyle>,
|
||||
indent_index: Indent,
|
||||
indent_data: Indent,
|
||||
indent_ascii: Indent,
|
||||
@ -91,7 +87,7 @@ impl BinaryStyle {
|
||||
show_split: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
colors,
|
||||
color_index,
|
||||
indent_index,
|
||||
indent_data,
|
||||
indent_ascii,
|
||||
@ -113,61 +109,8 @@ impl Indent {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct BinaryStyleColors {
|
||||
pub split_left: OptStyle,
|
||||
pub split_right: OptStyle,
|
||||
pub index: OptStyle,
|
||||
pub data: SymbolColor,
|
||||
pub ascii: SymbolColor,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct SymbolColor {
|
||||
pub default: OptStyle,
|
||||
pub zero: OptStyle,
|
||||
pub unknown: OptStyle,
|
||||
}
|
||||
|
||||
impl SymbolColor {
|
||||
pub fn new(default: OptStyle, zero: OptStyle, unknown: OptStyle) -> Self {
|
||||
Self {
|
||||
default,
|
||||
zero,
|
||||
unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryStyleColors {
|
||||
pub fn new(
|
||||
index: OptStyle,
|
||||
data: SymbolColor,
|
||||
ascii: SymbolColor,
|
||||
split_left: OptStyle,
|
||||
split_right: OptStyle,
|
||||
) -> Self {
|
||||
Self {
|
||||
split_left,
|
||||
split_right,
|
||||
index,
|
||||
data,
|
||||
ascii,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct BinaryWidgetState {
|
||||
pub layout_index: Layout,
|
||||
pub layout_data: Layout,
|
||||
pub layout_ascii: Layout,
|
||||
}
|
||||
|
||||
impl StatefulWidget for BinaryWidget<'_> {
|
||||
type State = BinaryWidgetState;
|
||||
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
impl Widget for BinaryWidget<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let min_width = get_widget_width(&self);
|
||||
|
||||
if (area.width as usize) < min_width {
|
||||
@ -178,12 +121,12 @@ impl StatefulWidget for BinaryWidget<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
render_hexdump(area, buf, state, self);
|
||||
render_hexdump(area, buf, self);
|
||||
}
|
||||
}
|
||||
|
||||
// todo: indent color
|
||||
fn render_hexdump(area: Rect, buf: &mut Buffer, _state: &mut BinaryWidgetState, w: BinaryWidget) {
|
||||
fn render_hexdump(area: Rect, buf: &mut Buffer, w: BinaryWidget) {
|
||||
const MIN_INDEX_SIZE: usize = 8;
|
||||
|
||||
let show_index = !w.opts.disable_index;
|
||||
@ -211,7 +154,7 @@ fn render_hexdump(area: Rect, buf: &mut Buffer, _state: &mut BinaryWidgetState,
|
||||
|
||||
if show_index {
|
||||
x += render_space(buf, x, y, 1, w.style.indent_index.left);
|
||||
x += render_hex_usize(buf, x, y, address, index_width, false, get_index_style(&w));
|
||||
x += render_hex_usize(buf, x, y, address, index_width, get_index_style(&w));
|
||||
x += render_space(buf, x, y, 1, w.style.indent_index.right);
|
||||
}
|
||||
|
||||
@ -251,7 +194,7 @@ fn render_hexdump(area: Rect, buf: &mut Buffer, _state: &mut BinaryWidgetState,
|
||||
|
||||
if show_index {
|
||||
x += render_space(buf, x, y, 1, w.style.indent_index.left);
|
||||
x += render_hex_usize(buf, x, y, address, index_width, false, get_index_style(&w));
|
||||
x += render_hex_usize(buf, x, y, address, index_width, get_index_style(&w));
|
||||
x += render_space(buf, x, y, 1, w.style.indent_index.right);
|
||||
}
|
||||
|
||||
@ -313,7 +256,7 @@ fn render_segment(buf: &mut Buffer, x: u16, y: u16, line: &[u8], w: &BinaryWidge
|
||||
}
|
||||
|
||||
let (_, style) = get_segment_char(w, n);
|
||||
size += render_hex_u8(buf, x + size, y, n, false, style);
|
||||
size += render_hex_u8(buf, x + size, y, n, style);
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
@ -346,7 +289,7 @@ fn render_ascii_line(buf: &mut Buffer, x: u16, y: u16, line: &[u8], w: &BinaryWi
|
||||
size
|
||||
}
|
||||
|
||||
fn render_ascii_char(buf: &mut Buffer, x: u16, y: u16, n: char, style: OptStyle) -> u16 {
|
||||
fn render_ascii_char(buf: &mut Buffer, x: u16, y: u16, n: char, style: Option<NuStyle>) -> u16 {
|
||||
let text = n.to_string();
|
||||
|
||||
let mut p = Paragraph::new(text);
|
||||
@ -362,8 +305,8 @@ fn render_ascii_char(buf: &mut Buffer, x: u16, y: u16, n: char, style: OptStyle)
|
||||
1
|
||||
}
|
||||
|
||||
fn render_hex_u8(buf: &mut Buffer, x: u16, y: u16, n: u8, big: bool, style: OptStyle) -> u16 {
|
||||
render_hex_usize(buf, x, y, n as usize, 2, big, style)
|
||||
fn render_hex_u8(buf: &mut Buffer, x: u16, y: u16, n: u8, style: Option<NuStyle>) -> u16 {
|
||||
render_hex_usize(buf, x, y, n as usize, 2, style)
|
||||
}
|
||||
|
||||
fn render_hex_usize(
|
||||
@ -372,10 +315,9 @@ fn render_hex_usize(
|
||||
y: u16,
|
||||
n: usize,
|
||||
width: u16,
|
||||
big: bool,
|
||||
style: OptStyle,
|
||||
style: Option<NuStyle>,
|
||||
) -> u16 {
|
||||
let text = usize_to_hex(n, width as usize, big);
|
||||
let text = usize_to_hex(n, width as usize);
|
||||
let mut p = Paragraph::new(text);
|
||||
if let Some(style) = style {
|
||||
let style = nu_style_to_tui(style);
|
||||
@ -389,7 +331,7 @@ fn render_hex_usize(
|
||||
width
|
||||
}
|
||||
|
||||
fn get_ascii_char(_w: &BinaryWidget, n: u8) -> (char, OptStyle) {
|
||||
fn get_ascii_char(_w: &BinaryWidget, n: u8) -> (char, Option<NuStyle>) {
|
||||
let (style, c) = categorize_byte(&n);
|
||||
let c = c.unwrap_or(n as char);
|
||||
let style = if style.is_plain() { None } else { Some(style) };
|
||||
@ -397,7 +339,7 @@ fn get_ascii_char(_w: &BinaryWidget, n: u8) -> (char, OptStyle) {
|
||||
(c, style)
|
||||
}
|
||||
|
||||
fn get_segment_char(_w: &BinaryWidget, n: u8) -> (char, OptStyle) {
|
||||
fn get_segment_char(_w: &BinaryWidget, n: u8) -> (char, Option<NuStyle>) {
|
||||
let (style, c) = categorize_byte(&n);
|
||||
let c = c.unwrap_or(n as char);
|
||||
let style = if style.is_plain() { None } else { Some(style) };
|
||||
@ -405,8 +347,8 @@ fn get_segment_char(_w: &BinaryWidget, n: u8) -> (char, OptStyle) {
|
||||
(c, style)
|
||||
}
|
||||
|
||||
fn get_index_style(w: &BinaryWidget) -> OptStyle {
|
||||
w.style.colors.index
|
||||
fn get_index_style(w: &BinaryWidget) -> Option<NuStyle> {
|
||||
w.style.color_index
|
||||
}
|
||||
|
||||
fn render_space(buf: &mut Buffer, x: u16, y: u16, height: u16, padding: u16) -> u16 {
|
||||
@ -443,7 +385,7 @@ fn get_max_index_size(w: &BinaryWidget) -> usize {
|
||||
let line_size = w.opts.count_segments * (w.opts.segment_size * 2);
|
||||
let count_lines = w.data.len() / line_size;
|
||||
let max_index = w.opts.index_offset + count_lines * line_size;
|
||||
usize_to_hex(max_index, 0, false).len()
|
||||
usize_to_hex(max_index, 0).len()
|
||||
}
|
||||
|
||||
fn get_widget_width(w: &BinaryWidget) -> usize {
|
||||
@ -453,7 +395,7 @@ fn get_widget_width(w: &BinaryWidget) -> usize {
|
||||
let count_lines = w.data.len() / line_size;
|
||||
|
||||
let max_index = w.opts.index_offset + count_lines * line_size;
|
||||
let index_size = usize_to_hex(max_index, 0, false).len();
|
||||
let index_size = usize_to_hex(max_index, 0).len();
|
||||
let index_size = index_size.max(MIN_INDEX_SIZE);
|
||||
|
||||
let data_split_size = w.opts.count_segments.saturating_sub(1) * w.style.indent_segment;
|
||||
@ -479,17 +421,11 @@ fn get_widget_width(w: &BinaryWidget) -> usize {
|
||||
min_width
|
||||
}
|
||||
|
||||
fn usize_to_hex(n: usize, width: usize, big: bool) -> String {
|
||||
fn usize_to_hex(n: usize, width: usize) -> String {
|
||||
if width == 0 {
|
||||
match big {
|
||||
true => format!("{:X}", n),
|
||||
false => format!("{:x}", n),
|
||||
}
|
||||
format!("{:x}", n)
|
||||
} else {
|
||||
match big {
|
||||
true => format!("{:0>width$X}", n, width = width),
|
||||
false => format!("{:0>width$x}", n, width = width),
|
||||
}
|
||||
format!("{:0>width$x}", n, width = width)
|
||||
}
|
||||
}
|
||||
|
||||
@ -499,9 +435,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_to_hex() {
|
||||
assert_eq!(usize_to_hex(1, 2, false), "01");
|
||||
assert_eq!(usize_to_hex(16, 2, false), "10");
|
||||
assert_eq!(usize_to_hex(29, 2, false), "1d");
|
||||
assert_eq!(usize_to_hex(29, 2, true), "1D");
|
||||
assert_eq!(usize_to_hex(1, 2), "01");
|
||||
assert_eq!(usize_to_hex(16, 2), "10");
|
||||
assert_eq!(usize_to_hex(29, 2), "1d");
|
||||
}
|
||||
}
|
||||
|
@ -19,29 +19,17 @@ use crate::{
|
||||
util::create_map,
|
||||
};
|
||||
|
||||
use self::binary_widget::{
|
||||
BinarySettings, BinaryStyle, BinaryStyleColors, BinaryWidget, BinaryWidgetState, Indent,
|
||||
SymbolColor,
|
||||
};
|
||||
use self::binary_widget::{BinarySettings, BinaryStyle, BinaryWidget, Indent};
|
||||
|
||||
use super::{cursor::XYCursor, Layout, View, ViewConfig};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BinaryView {
|
||||
data: Vec<u8>,
|
||||
mode: Option<CursorMode>,
|
||||
cursor: XYCursor,
|
||||
settings: Settings,
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // todo:
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum CursorMode {
|
||||
Index,
|
||||
Data,
|
||||
Ascii,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct Settings {
|
||||
opts: BinarySettings,
|
||||
@ -52,7 +40,6 @@ impl BinaryView {
|
||||
pub fn new(data: Vec<u8>) -> Self {
|
||||
Self {
|
||||
data,
|
||||
mode: None,
|
||||
cursor: XYCursor::default(),
|
||||
settings: Settings::default(),
|
||||
}
|
||||
@ -61,9 +48,8 @@ impl BinaryView {
|
||||
|
||||
impl View for BinaryView {
|
||||
fn draw(&mut self, f: &mut Frame, area: Rect, _cfg: ViewConfig<'_>, _layout: &mut Layout) {
|
||||
let mut state = BinaryWidgetState::default();
|
||||
let widget = create_binary_widget(self);
|
||||
f.render_stateful_widget(widget, area, &mut state);
|
||||
f.render_widget(widget, area);
|
||||
}
|
||||
|
||||
fn handle_input(
|
||||
@ -77,7 +63,7 @@ impl View for BinaryView {
|
||||
let result = handle_event_view_mode(self, &key);
|
||||
|
||||
if matches!(&result, Some(Transition::Ok)) {
|
||||
let report = create_report(self.mode, self.cursor);
|
||||
let report = create_report(self.cursor);
|
||||
info.status = Some(report);
|
||||
}
|
||||
|
||||
@ -206,21 +192,7 @@ fn settings_from_config(config: &ConfigMap) -> Settings {
|
||||
0,
|
||||
),
|
||||
style: BinaryStyle::new(
|
||||
BinaryStyleColors::new(
|
||||
colors.get("color_index").cloned(),
|
||||
SymbolColor::new(
|
||||
colors.get("color_segment").cloned(),
|
||||
colors.get("color_segment_zero").cloned(),
|
||||
colors.get("color_segment_unknown").cloned(),
|
||||
),
|
||||
SymbolColor::new(
|
||||
colors.get("color_ascii").cloned(),
|
||||
colors.get("color_ascii_zero").cloned(),
|
||||
colors.get("color_ascii_unknown").cloned(),
|
||||
),
|
||||
colors.get("color_split_left").cloned(),
|
||||
colors.get("color_split_right").cloned(),
|
||||
),
|
||||
colors.get("color_index").cloned(),
|
||||
Indent::new(
|
||||
config_get_usize(config, "padding_index_left", 2) as u16,
|
||||
config_get_usize(config, "padding_index_right", 2) as u16,
|
||||
@ -254,22 +226,17 @@ fn config_get_usize(config: &ConfigMap, key: &str, default: usize) -> usize {
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
fn create_report(mode: Option<CursorMode>, cursor: XYCursor) -> Report {
|
||||
fn create_report(cursor: XYCursor) -> Report {
|
||||
let covered_percent = report_row_position(cursor);
|
||||
let cursor = report_cursor_position(cursor);
|
||||
let mode = report_mode_name(mode);
|
||||
let mode = report_mode_name();
|
||||
let msg = String::new();
|
||||
|
||||
Report::new(msg, Severity::Info, mode, cursor, covered_percent)
|
||||
}
|
||||
|
||||
fn report_mode_name(cursor: Option<CursorMode>) -> String {
|
||||
match cursor {
|
||||
Some(CursorMode::Index) => String::from("ADDR"),
|
||||
Some(CursorMode::Data) => String::from("DUMP"),
|
||||
Some(CursorMode::Ascii) => String::from("TEXT"),
|
||||
None => String::from("VIEW"),
|
||||
}
|
||||
fn report_mode_name() -> String {
|
||||
String::from("VIEW")
|
||||
}
|
||||
|
||||
fn report_row_position(cursor: XYCursor) -> String {
|
||||
|
@ -32,25 +32,10 @@ impl XYCursor {
|
||||
self.x.index()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn row_offset(&self) -> usize {
|
||||
self.y.offset()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn column_limit(&self) -> usize {
|
||||
self.x.end()
|
||||
}
|
||||
|
||||
pub fn row_limit(&self) -> usize {
|
||||
self.y.end()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn column_offset(&self) -> usize {
|
||||
self.x.offset()
|
||||
}
|
||||
|
||||
pub fn row_starts_at(&self) -> usize {
|
||||
self.y.starts_at()
|
||||
}
|
||||
@ -67,11 +52,6 @@ impl XYCursor {
|
||||
self.x.offset()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn row_window_size(&self) -> usize {
|
||||
self.y.window()
|
||||
}
|
||||
|
||||
pub fn column_window_size(&self) -> usize {
|
||||
self.x.window()
|
||||
}
|
||||
@ -80,11 +60,6 @@ impl XYCursor {
|
||||
self.y.next(1)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn next_row_by(&mut self, i: usize) -> bool {
|
||||
self.y.next(i)
|
||||
}
|
||||
|
||||
pub fn next_row_page(&mut self) -> bool {
|
||||
self.y.next_window()
|
||||
}
|
||||
@ -101,11 +76,6 @@ impl XYCursor {
|
||||
self.y.prev(1)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn prev_row_by(&mut self, i: usize) -> bool {
|
||||
self.y.prev(i)
|
||||
}
|
||||
|
||||
pub fn prev_row_page(&mut self) -> bool {
|
||||
self.y.prev_window()
|
||||
}
|
||||
|
@ -1141,18 +1141,18 @@ mod test {
|
||||
|
||||
let v: Value = from_str("{\"a\":1.1}").unwrap();
|
||||
let vo = v.as_object().unwrap();
|
||||
assert!(vo["a"].as_f64().unwrap() - 1.1 < std::f64::EPSILON);
|
||||
assert!((vo["a"].as_f64().unwrap() - 1.1).abs() < f64::EPSILON);
|
||||
|
||||
let v: Value = from_str("{\"a\":-1.1}").unwrap();
|
||||
let vo = v.as_object().unwrap();
|
||||
assert!(vo["a"].as_f64().unwrap() + 1.1 > -(std::f64::EPSILON));
|
||||
assert!((vo["a"].as_f64().unwrap() + 1.1).abs() < f64::EPSILON);
|
||||
|
||||
let v: Value = from_str("{\"a\":1e6}").unwrap();
|
||||
let vo = v.as_object().unwrap();
|
||||
assert!(vo["a"].as_f64().unwrap() - 1e6 < std::f64::EPSILON);
|
||||
assert!((vo["a"].as_f64().unwrap() - 1e6).abs() < f64::EPSILON);
|
||||
|
||||
let v: Value = from_str("{\"a\":-1e6}").unwrap();
|
||||
let vo = v.as_object().unwrap();
|
||||
assert!(vo["a"].as_f64().unwrap() + 1e6 > -(std::f64::EPSILON));
|
||||
assert!((vo["a"].as_f64().unwrap() + 1e6).abs() < f64::EPSILON);
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ pub enum FlatShape {
|
||||
Or,
|
||||
Pipe,
|
||||
Range,
|
||||
RawString,
|
||||
Record,
|
||||
Redirection,
|
||||
Signature,
|
||||
@ -78,6 +79,7 @@ impl Display for FlatShape {
|
||||
FlatShape::Or => write!(f, "shape_or"),
|
||||
FlatShape::Pipe => write!(f, "shape_pipe"),
|
||||
FlatShape::Range => write!(f, "shape_range"),
|
||||
FlatShape::RawString => write!(f, "shape_raw_string"),
|
||||
FlatShape::Record => write!(f, "shape_record"),
|
||||
FlatShape::Redirection => write!(f, "shape_redirection"),
|
||||
FlatShape::Signature => write!(f, "shape_signature"),
|
||||
@ -509,6 +511,9 @@ pub fn flatten_expression(
|
||||
Expr::String(_) => {
|
||||
vec![(expr.span, FlatShape::String)]
|
||||
}
|
||||
Expr::RawString(_) => {
|
||||
vec![(expr.span, FlatShape::RawString)]
|
||||
}
|
||||
Expr::Table(table) => {
|
||||
let outer_span = expr.span;
|
||||
let mut last_end = outer_span.start;
|
||||
|
@ -503,6 +503,79 @@ fn lex_internal(
|
||||
} else if c == b' ' || c == b'\t' || additional_whitespace.contains(&c) {
|
||||
// If the next character is non-newline whitespace, skip it.
|
||||
curr_offset += 1;
|
||||
} else if c == b'r' {
|
||||
// A raw string literal looks like `echo r#'Look, I can use 'single quotes'!'#`
|
||||
// If the next character is `#` we're probably looking at a raw string literal
|
||||
// so we need to read all the text until we find a closing `#`. This raw string
|
||||
// can contain any character, including newlines and double quotes without needing
|
||||
// to escape them.
|
||||
//
|
||||
// A raw string can contain many `#` as prefix,
|
||||
// incase if there is a `'#` or `#'` in the string itself.
|
||||
// E.g: r##'I can use '#' in a raw string'##
|
||||
let mut prefix_sharp_cnt = 0;
|
||||
let start = curr_offset;
|
||||
while let Some(b'#') = input.get(start + prefix_sharp_cnt + 1) {
|
||||
prefix_sharp_cnt += 1;
|
||||
}
|
||||
|
||||
if prefix_sharp_cnt != 0 {
|
||||
// curr_offset is the character `r`, we need to move forward and skip all `#`
|
||||
// characters.
|
||||
//
|
||||
// e.g: r###'<body>
|
||||
// ^
|
||||
// ^
|
||||
// curr_offset
|
||||
curr_offset += prefix_sharp_cnt + 1;
|
||||
// the next one should be a single quote.
|
||||
if input.get(curr_offset) != Some(&b'\'') {
|
||||
error = Some(ParseError::Expected(
|
||||
"'",
|
||||
Span::new(span_offset + curr_offset, span_offset + curr_offset + 1),
|
||||
));
|
||||
}
|
||||
|
||||
curr_offset += 1;
|
||||
let mut matches = false;
|
||||
while let Some(ch) = input.get(curr_offset) {
|
||||
// check for postfix '###
|
||||
if *ch == b'#' {
|
||||
let start_ch = input[curr_offset - prefix_sharp_cnt];
|
||||
let postfix = &input[curr_offset - prefix_sharp_cnt + 1..=curr_offset];
|
||||
if start_ch == b'\'' && postfix.iter().all(|x| *x == b'#') {
|
||||
matches = true;
|
||||
curr_offset += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
curr_offset += 1
|
||||
}
|
||||
if matches {
|
||||
output.push(Token::new(
|
||||
TokenContents::Item,
|
||||
Span::new(span_offset + start, span_offset + curr_offset),
|
||||
));
|
||||
} else if error.is_none() {
|
||||
error = Some(ParseError::UnexpectedEof(
|
||||
"#".to_string(),
|
||||
Span::new(span_offset + curr_offset, span_offset + curr_offset),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
let (token, err) = lex_item(
|
||||
input,
|
||||
&mut curr_offset,
|
||||
span_offset,
|
||||
additional_whitespace,
|
||||
special_tokens,
|
||||
in_signature,
|
||||
);
|
||||
if error.is_none() {
|
||||
error = err;
|
||||
}
|
||||
output.push(token);
|
||||
}
|
||||
} else {
|
||||
let token = try_lex_special_piped_item(input, &mut curr_offset, span_offset);
|
||||
if let Some(token) = token {
|
||||
|
@ -1949,6 +1949,7 @@ pub fn parse_module_file_or_dir(
|
||||
return None;
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let cwd = working_set.get_cwd();
|
||||
|
||||
let module_path =
|
||||
@ -3341,6 +3342,7 @@ pub fn parse_mut(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline
|
||||
}
|
||||
|
||||
pub fn parse_source(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
|
||||
trace!("parsing source");
|
||||
let spans = &lite_command.parts;
|
||||
let name = working_set.get_span_contents(spans[0]);
|
||||
|
||||
@ -3358,6 +3360,7 @@ pub fn parse_source(working_set: &mut StateWorkingSet, lite_command: &LiteComman
|
||||
let scoped = name == b"source-env";
|
||||
|
||||
if let Some(decl_id) = working_set.find_decl(name) {
|
||||
#[allow(deprecated)]
|
||||
let cwd = working_set.get_cwd();
|
||||
|
||||
// Is this the right call to be using here?
|
||||
@ -3563,6 +3566,7 @@ pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteComm
|
||||
|
||||
let spans = &lite_command.parts;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let cwd = working_set.get_cwd();
|
||||
|
||||
// Checking that the function is used with the correct name
|
||||
@ -3784,6 +3788,7 @@ pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteComm
|
||||
pub fn parse_plugin_use(working_set: &mut StateWorkingSet, call: Box<Call>) -> Pipeline {
|
||||
use nu_protocol::{FromValue, PluginRegistryFile};
|
||||
|
||||
#[allow(deprecated)]
|
||||
let cwd = working_set.get_cwd();
|
||||
|
||||
if let Err(err) = (|| {
|
||||
|
@ -66,6 +66,11 @@ pub fn is_math_expression_like(working_set: &mut StateWorkingSet, span: Span) ->
|
||||
|
||||
let b = bytes[0];
|
||||
|
||||
// check for raw string
|
||||
if bytes.starts_with(b"r#") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if b == b'(' || b == b'{' || b == b'[' || b == b'$' || b == b'"' || b == b'\'' || b == b'-' {
|
||||
return true;
|
||||
}
|
||||
@ -578,6 +583,7 @@ pub fn parse_multispan_value(
|
||||
spans_idx: &mut usize,
|
||||
shape: &SyntaxShape,
|
||||
) -> Expression {
|
||||
trace!("parse multispan value");
|
||||
match shape {
|
||||
SyntaxShape::VarWithOptType => {
|
||||
trace!("parsing: var with opt type");
|
||||
@ -1565,6 +1571,66 @@ pub(crate) fn parse_dollar_expr(working_set: &mut StateWorkingSet, span: Span) -
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_raw_string(working_set: &mut StateWorkingSet, span: Span) -> Expression {
|
||||
trace!("parsing: raw-string, with required delimiters");
|
||||
|
||||
let bytes = working_set.get_span_contents(span);
|
||||
|
||||
let prefix_sharp_cnt = if bytes.starts_with(b"r#") {
|
||||
// actually `sharp_cnt` is always `index - 1`
|
||||
// but create a variable here to make it clearer.
|
||||
let mut sharp_cnt = 1;
|
||||
let mut index = 2;
|
||||
while index < bytes.len() && bytes[index] == b'#' {
|
||||
index += 1;
|
||||
sharp_cnt += 1;
|
||||
}
|
||||
sharp_cnt
|
||||
} else {
|
||||
working_set.error(ParseError::Expected("r#", span));
|
||||
return garbage(span);
|
||||
};
|
||||
let expect_postfix_sharp_cnt = prefix_sharp_cnt;
|
||||
// check the length of whole raw string.
|
||||
// the whole raw string should contains at least
|
||||
// 1(r) + prefix_sharp_cnt + 1(') + 1(') + postfix_sharp characters
|
||||
if bytes.len() < prefix_sharp_cnt + expect_postfix_sharp_cnt + 3 {
|
||||
working_set.error(ParseError::Unclosed('\''.into(), span));
|
||||
return garbage(span);
|
||||
}
|
||||
|
||||
// check for unbalanced # and single quotes.
|
||||
let postfix_bytes = &bytes[bytes.len() - expect_postfix_sharp_cnt..bytes.len()];
|
||||
if postfix_bytes.iter().any(|b| *b != b'#') {
|
||||
working_set.error(ParseError::Unbalanced(
|
||||
"prefix #".to_string(),
|
||||
"postfix #".to_string(),
|
||||
span,
|
||||
));
|
||||
return garbage(span);
|
||||
}
|
||||
// check for unblanaced single quotes.
|
||||
if bytes[1 + prefix_sharp_cnt] != b'\''
|
||||
|| bytes[bytes.len() - expect_postfix_sharp_cnt - 1] != b'\''
|
||||
{
|
||||
working_set.error(ParseError::Unclosed('\''.into(), span));
|
||||
return garbage(span);
|
||||
}
|
||||
|
||||
let bytes = &bytes[prefix_sharp_cnt + 1 + 1..bytes.len() - 1 - prefix_sharp_cnt];
|
||||
if let Ok(token) = String::from_utf8(bytes.into()) {
|
||||
Expression {
|
||||
expr: Expr::RawString(token),
|
||||
span,
|
||||
ty: Type::String,
|
||||
custom_completion: None,
|
||||
}
|
||||
} else {
|
||||
working_set.error(ParseError::Expected("utf8 raw-string", span));
|
||||
garbage(span)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_paren_expr(
|
||||
working_set: &mut StateWorkingSet,
|
||||
span: Span,
|
||||
@ -4553,6 +4619,9 @@ pub fn parse_value(
|
||||
return Expression::garbage(span);
|
||||
}
|
||||
},
|
||||
b'r' if bytes.len() > 1 && bytes[1] == b'#' => {
|
||||
return parse_raw_string(working_set, span);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@ -6075,6 +6144,7 @@ pub fn discover_captures_in_expr(
|
||||
}
|
||||
}
|
||||
Expr::String(_) => {}
|
||||
Expr::RawString(_) => {}
|
||||
Expr::StringInterpolation(exprs) => {
|
||||
for expr in exprs {
|
||||
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
|
||||
@ -6236,6 +6306,7 @@ pub fn parse(
|
||||
contents: &[u8],
|
||||
scoped: bool,
|
||||
) -> Arc<Block> {
|
||||
trace!("parse");
|
||||
let name = match fname {
|
||||
Some(fname) => {
|
||||
// use the canonical name for this filename
|
||||
@ -6253,9 +6324,13 @@ pub fn parse(
|
||||
|
||||
let mut output = {
|
||||
if let Some(block) = previously_parsed_block {
|
||||
// dbg!("previous block");
|
||||
return block;
|
||||
} else {
|
||||
// dbg!("starting lex");
|
||||
let (output, err) = lex(contents, new_span.start, &[], &[], false);
|
||||
// dbg!("finished lex");
|
||||
// dbg!(&output);
|
||||
if let Some(err) = err {
|
||||
working_set.error(err)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::ffi::OsString;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests;
|
||||
@ -23,6 +23,16 @@ pub fn make_local_socket_name(unique_id: &str) -> OsString {
|
||||
base.into()
|
||||
}
|
||||
|
||||
/// Interpret a local socket name for use with `interprocess`.
|
||||
#[cfg(unix)]
|
||||
pub fn interpret_local_socket_name(
|
||||
name: &OsStr,
|
||||
) -> Result<interprocess::local_socket::Name, std::io::Error> {
|
||||
use interprocess::local_socket::{GenericFilePath, ToFsName};
|
||||
|
||||
name.to_fs_name::<GenericFilePath>()
|
||||
}
|
||||
|
||||
/// Generate a name to be used for a local socket specific to this `nu` process, described by the
|
||||
/// given `unique_id`, which should be unique to the purpose of the socket.
|
||||
///
|
||||
@ -33,6 +43,16 @@ pub fn make_local_socket_name(unique_id: &str) -> OsString {
|
||||
format!("nu.{}.{}", std::process::id(), unique_id).into()
|
||||
}
|
||||
|
||||
/// Interpret a local socket name for use with `interprocess`.
|
||||
#[cfg(windows)]
|
||||
pub fn interpret_local_socket_name(
|
||||
name: &OsStr,
|
||||
) -> Result<interprocess::local_socket::Name, std::io::Error> {
|
||||
use interprocess::local_socket::{GenericNamespaced, ToNsName};
|
||||
|
||||
name.to_ns_name::<GenericNamespaced>()
|
||||
}
|
||||
|
||||
/// Determine if the error is just due to the listener not being ready yet in asynchronous mode
|
||||
#[cfg(not(windows))]
|
||||
pub fn is_would_block_err(err: &std::io::Error) -> bool {
|
||||
@ -48,37 +68,3 @@ pub fn is_would_block_err(err: &std::io::Error) -> bool {
|
||||
e as i64 == windows::Win32::Foundation::ERROR_PIPE_LISTENING.0 as i64
|
||||
})
|
||||
}
|
||||
|
||||
/// Wraps the `interprocess` local socket stream for greater compatibility
|
||||
#[derive(Debug)]
|
||||
pub struct LocalSocketStream(pub interprocess::local_socket::LocalSocketStream);
|
||||
|
||||
impl From<interprocess::local_socket::LocalSocketStream> for LocalSocketStream {
|
||||
fn from(value: interprocess::local_socket::LocalSocketStream) -> Self {
|
||||
LocalSocketStream(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::io::Read for LocalSocketStream {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.0.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::io::Write for LocalSocketStream {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.0.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
// We don't actually flush the underlying socket on Windows. The flush operation on a
|
||||
// Windows named pipe actually synchronizes with read on the other side, and won't finish
|
||||
// until the other side is empty. This isn't how most of our other I/O methods work, so we
|
||||
// just won't do it. The BufWriter above this will have still made a write call with the
|
||||
// contents of the buffer, which should be good enough.
|
||||
if cfg!(not(windows)) {
|
||||
self.0.flush()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,6 @@ use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio};
|
||||
|
||||
use nu_protocol::ShellError;
|
||||
|
||||
#[cfg(feature = "local-socket")]
|
||||
use interprocess::local_socket::LocalSocketListener;
|
||||
|
||||
#[cfg(feature = "local-socket")]
|
||||
mod local_socket;
|
||||
|
||||
@ -83,15 +80,14 @@ impl CommunicationMode {
|
||||
// For sockets: we need to create the server so that the child won't fail to connect.
|
||||
#[cfg(feature = "local-socket")]
|
||||
CommunicationMode::LocalSocket(name) => {
|
||||
let listener = LocalSocketListener::bind(name.as_os_str()).map_err(|err| {
|
||||
ShellError::IOError {
|
||||
use interprocess::local_socket::ListenerOptions;
|
||||
|
||||
let listener = interpret_local_socket_name(name)
|
||||
.and_then(|name| ListenerOptions::new().name(name).create_sync())
|
||||
.map_err(|err| ShellError::IOError {
|
||||
msg: format!("failed to open socket for plugin: {err}"),
|
||||
}
|
||||
})?;
|
||||
Ok(PreparedServerCommunication::LocalSocket {
|
||||
name: name.clone(),
|
||||
listener,
|
||||
})
|
||||
})?;
|
||||
Ok(PreparedServerCommunication::LocalSocket { listener })
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,11 +103,13 @@ impl CommunicationMode {
|
||||
// Connect to the specified socket.
|
||||
let get_socket = || {
|
||||
use interprocess::local_socket as ls;
|
||||
ls::LocalSocketStream::connect(name.as_os_str())
|
||||
use ls::traits::Stream;
|
||||
|
||||
interpret_local_socket_name(name)
|
||||
.and_then(|name| ls::Stream::connect(name))
|
||||
.map_err(|err| ShellError::IOError {
|
||||
msg: format!("failed to connect to socket: {err}"),
|
||||
})
|
||||
.map(LocalSocketStream::from)
|
||||
};
|
||||
// Reverse order from the server: read in, write out
|
||||
let read_in = get_socket()?;
|
||||
@ -133,9 +131,7 @@ pub enum PreparedServerCommunication {
|
||||
/// Contains the listener to accept connections on. On Unix, the socket is unlinked on `Drop`.
|
||||
#[cfg(feature = "local-socket")]
|
||||
LocalSocket {
|
||||
#[cfg_attr(windows, allow(dead_code))] // not used on Windows
|
||||
name: std::ffi::OsString,
|
||||
listener: LocalSocketListener,
|
||||
listener: interprocess::local_socket::Listener,
|
||||
},
|
||||
}
|
||||
|
||||
@ -161,6 +157,9 @@ impl PreparedServerCommunication {
|
||||
}
|
||||
#[cfg(feature = "local-socket")]
|
||||
PreparedServerCommunication::LocalSocket { listener, .. } => {
|
||||
use interprocess::local_socket::traits::{
|
||||
Listener, ListenerNonblockingMode, Stream,
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
const RETRY_PERIOD: Duration = Duration::from_millis(1);
|
||||
@ -170,13 +169,16 @@ impl PreparedServerCommunication {
|
||||
|
||||
// Use a loop to try to get two clients from the listener: one for read (the plugin
|
||||
// output) and one for write (the plugin input)
|
||||
listener.set_nonblocking(true)?;
|
||||
//
|
||||
// Be non-blocking on Accept only, so we can timeout.
|
||||
listener.set_nonblocking(ListenerNonblockingMode::Accept)?;
|
||||
let mut get_socket = || {
|
||||
let mut result = None;
|
||||
while let Ok(None) = child.try_wait() {
|
||||
match listener.accept() {
|
||||
Ok(stream) => {
|
||||
// Success! But make sure the stream is in blocking mode.
|
||||
// Success! Ensure the stream is in nonblocking mode though, for
|
||||
// good measure. Had an issue without this on macOS.
|
||||
stream.set_nonblocking(false)?;
|
||||
result = Some(stream);
|
||||
break;
|
||||
@ -198,7 +200,7 @@ impl PreparedServerCommunication {
|
||||
}
|
||||
}
|
||||
if let Some(stream) = result {
|
||||
Ok(LocalSocketStream(stream))
|
||||
Ok(stream)
|
||||
} else {
|
||||
// The process may have exited
|
||||
Err(ShellError::PluginFailedToLoad {
|
||||
@ -215,26 +217,13 @@ impl PreparedServerCommunication {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PreparedServerCommunication {
|
||||
fn drop(&mut self) {
|
||||
match self {
|
||||
#[cfg(all(unix, feature = "local-socket"))]
|
||||
PreparedServerCommunication::LocalSocket { name: path, .. } => {
|
||||
// Just try to remove the socket file, it's ok if this fails
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The required streams for communication from the engine side, i.e. the server in socket terms.
|
||||
pub enum ServerCommunicationIo {
|
||||
Stdio(ChildStdin, ChildStdout),
|
||||
#[cfg(feature = "local-socket")]
|
||||
LocalSocket {
|
||||
read_out: LocalSocketStream,
|
||||
write_in: LocalSocketStream,
|
||||
read_out: interprocess::local_socket::Stream,
|
||||
write_in: interprocess::local_socket::Stream,
|
||||
},
|
||||
}
|
||||
|
||||
@ -243,7 +232,7 @@ pub enum ClientCommunicationIo {
|
||||
Stdio(Stdin, Stdout),
|
||||
#[cfg(feature = "local-socket")]
|
||||
LocalSocket {
|
||||
read_in: LocalSocketStream,
|
||||
write_out: LocalSocketStream,
|
||||
read_in: interprocess::local_socket::Stream,
|
||||
write_out: interprocess::local_socket::Stream,
|
||||
},
|
||||
}
|
||||
|
@ -202,10 +202,13 @@ where
|
||||
if !self.ended {
|
||||
self.writer
|
||||
.write_stream_message(StreamMessage::Data(self.id, data.into()))?;
|
||||
// Flush after each data message to ensure they do predictably appear on the other side
|
||||
// when they're generated
|
||||
//
|
||||
// TODO: make the buffering configurable, as this is a factor for performance
|
||||
self.writer.flush()?;
|
||||
// This implements flow control, so we don't write too many messages:
|
||||
if !self.signal.notify_sent()? {
|
||||
// Flush the output, and then wait for acknowledgements
|
||||
self.writer.flush()?;
|
||||
self.signal.wait_for_drain()
|
||||
} else {
|
||||
Ok(())
|
||||
|
@ -2,8 +2,6 @@ use nu_protocol::{CustomValue, IntoSpanned, ShellError, Spanned, Value};
|
||||
|
||||
/// Do something with all [`CustomValue`]s recursively within a `Value`. This is not limited to
|
||||
/// plugin custom values.
|
||||
///
|
||||
/// `LazyRecord`s will be collected to plain values for completeness.
|
||||
pub fn with_custom_values_in<E>(
|
||||
value: &mut Value,
|
||||
mut f: impl FnMut(Spanned<&mut Box<dyn CustomValue>>) -> Result<(), E>,
|
||||
@ -18,13 +16,6 @@ where
|
||||
// Operate on a CustomValue.
|
||||
f(val.into_spanned(span))
|
||||
}
|
||||
// LazyRecord would be a problem for us, since it could return something else the
|
||||
// next time, and we have to collect it anyway to serialize it. Collect it in place,
|
||||
// and then use the result
|
||||
Value::LazyRecord { val, .. } => {
|
||||
*value = val.collect()?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
})
|
||||
@ -33,31 +24,7 @@ where
|
||||
#[test]
|
||||
fn find_custom_values() {
|
||||
use nu_plugin_protocol::test_util::test_plugin_custom_value;
|
||||
use nu_protocol::{engine::Closure, record, LazyRecord, Span};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Lazy;
|
||||
impl<'a> LazyRecord<'a> for Lazy {
|
||||
fn column_names(&'a self) -> Vec<&'a str> {
|
||||
vec!["custom", "plain"]
|
||||
}
|
||||
|
||||
fn get_column_value(&self, column: &str) -> Result<Value, ShellError> {
|
||||
Ok(match column {
|
||||
"custom" => Value::test_custom_value(Box::new(test_plugin_custom_value())),
|
||||
"plain" => Value::test_int(42),
|
||||
_ => unimplemented!(),
|
||||
})
|
||||
}
|
||||
|
||||
fn span(&self) -> Span {
|
||||
Span::test_data()
|
||||
}
|
||||
|
||||
fn clone_value(&self, span: Span) -> Value {
|
||||
Value::lazy_record(Box::new(self.clone()), span)
|
||||
}
|
||||
}
|
||||
use nu_protocol::{engine::Closure, record};
|
||||
|
||||
let mut cv = Value::test_custom_value(Box::new(test_plugin_custom_value()));
|
||||
|
||||
@ -73,7 +40,6 @@ fn find_custom_values() {
|
||||
captures: vec![(0, cv.clone()), (1, Value::test_string("foo"))]
|
||||
}
|
||||
),
|
||||
"lazy" => Value::test_lazy_record(Box::new(Lazy)),
|
||||
});
|
||||
|
||||
// Do with_custom_values_in, and count the number of custom values found
|
||||
@ -83,7 +49,7 @@ fn find_custom_values() {
|
||||
Ok(())
|
||||
})
|
||||
.expect("error");
|
||||
assert_eq!(4, found, "found in value");
|
||||
assert_eq!(3, found, "found in value");
|
||||
|
||||
// Try it on bare custom value too
|
||||
found = 0;
|
||||
|
@ -125,6 +125,7 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> {
|
||||
}
|
||||
|
||||
fn get_current_dir(&self) -> Result<Spanned<String>, ShellError> {
|
||||
#[allow(deprecated)]
|
||||
let cwd = nu_engine::env::current_dir_str(&self.engine_state, &self.stack)?;
|
||||
// The span is not really used, so just give it call.head
|
||||
Ok(cwd.into_spanned(self.call.head))
|
||||
|
@ -24,7 +24,10 @@ use crate::{
|
||||
PluginSource,
|
||||
};
|
||||
|
||||
pub(crate) const OUTPUT_BUFFER_SIZE: usize = 8192;
|
||||
/// This should be larger than the largest commonly sent message to avoid excessive fragmentation.
|
||||
///
|
||||
/// The buffers coming from external streams are typically each 8192 bytes, so double that.
|
||||
pub(crate) const OUTPUT_BUFFER_SIZE: usize = 16384;
|
||||
|
||||
/// Spawn the command for a plugin, in the given `mode`. After spawning, it can be passed to
|
||||
/// [`make_plugin_interface()`] to get a [`PluginInterface`].
|
||||
|
@ -179,11 +179,6 @@ impl PluginCustomValue {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
// Collect LazyRecord before proceeding
|
||||
Value::LazyRecord { ref val, .. } => {
|
||||
*value = val.collect()?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
})
|
||||
@ -205,11 +200,6 @@ impl PluginCustomValue {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
// Collect LazyRecord before proceeding
|
||||
Value::LazyRecord { ref val, .. } => {
|
||||
*value = val.collect()?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
})
|
||||
@ -224,11 +214,6 @@ impl PluginCustomValue {
|
||||
*value = val.to_base_value(span)?;
|
||||
Ok(())
|
||||
}
|
||||
// Collect LazyRecord before proceeding
|
||||
Value::LazyRecord { ref val, .. } => {
|
||||
*value = val.collect()?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
})
|
||||
|
@ -344,9 +344,6 @@ impl PluginTest {
|
||||
// All equal, and same length
|
||||
Ok(true)
|
||||
}
|
||||
// Must collect lazy records to compare.
|
||||
(Value::LazyRecord { val: a_val, .. }, _) => self.value_eq(&a_val.collect()?, b),
|
||||
(_, Value::LazyRecord { val: b_val, .. }) => self.value_eq(a, &b_val.collect()?),
|
||||
// Fall back to regular eq.
|
||||
_ => Ok(a == b),
|
||||
}
|
||||
|
@ -381,11 +381,6 @@ pub(crate) fn render_examples(
|
||||
plugin.custom_value_to_base_value(engine, val.into_spanned(span))?;
|
||||
Ok::<_, ShellError>(())
|
||||
}
|
||||
// Collect LazyRecord before proceeding
|
||||
Value::LazyRecord { ref val, .. } => {
|
||||
*value = val.collect()?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
})?;
|
||||
|
@ -28,8 +28,11 @@ mod interface;
|
||||
pub use command::{create_plugin_signature, PluginCommand, SimplePluginCommand};
|
||||
pub use interface::{EngineInterface, EngineInterfaceManager};
|
||||
|
||||
/// This should be larger than the largest commonly sent message to avoid excessive fragmentation.
|
||||
///
|
||||
/// The buffers coming from external streams are typically each 8192 bytes, so double that.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const OUTPUT_BUFFER_SIZE: usize = 8192;
|
||||
pub(crate) const OUTPUT_BUFFER_SIZE: usize = 16384;
|
||||
|
||||
/// The API for a Nushell plugin
|
||||
///
|
||||
|
@ -47,6 +47,7 @@ strum_macros = "0.26"
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.93.1" }
|
||||
pretty_assertions = { workspace = true }
|
||||
rstest = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
@ -36,6 +36,15 @@ impl Argument {
|
||||
Argument::Spread(e) => e.span,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expr(&self) -> Option<&Expression> {
|
||||
match self {
|
||||
Argument::Named((_, _, expr)) => expr.as_ref(),
|
||||
Argument::Positional(expr) | Argument::Unknown(expr) | Argument::Spread(expr) => {
|
||||
Some(expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
@ -36,6 +36,7 @@ pub enum Expr {
|
||||
Directory(String, bool),
|
||||
GlobPattern(String, bool),
|
||||
String(String),
|
||||
RawString(String),
|
||||
CellPath(CellPath),
|
||||
FullCellPath(Box<FullCellPath>),
|
||||
ImportPattern(Box<ImportPattern>),
|
||||
@ -80,6 +81,7 @@ impl Expr {
|
||||
| Expr::ValueWithUnit(_)
|
||||
| Expr::DateTime(_)
|
||||
| Expr::String(_)
|
||||
| Expr::RawString(_)
|
||||
| Expr::CellPath(_)
|
||||
| Expr::StringInterpolation(_)
|
||||
| Expr::Nothing => {
|
||||
|
@ -279,6 +279,7 @@ impl Expression {
|
||||
}
|
||||
Expr::Signature(_) => false,
|
||||
Expr::String(_) => false,
|
||||
Expr::RawString(_) => false,
|
||||
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
||||
let block = working_set.get_block(*block_id);
|
||||
|
||||
@ -436,6 +437,7 @@ impl Expression {
|
||||
}
|
||||
Expr::Signature(_) => {}
|
||||
Expr::String(_) => {}
|
||||
Expr::RawString(_) => {}
|
||||
Expr::StringInterpolation(items) => {
|
||||
for i in items {
|
||||
i.replace_span(working_set, replaced, new_span)
|
||||
|
@ -74,7 +74,14 @@ pub struct Config {
|
||||
pub menus: Vec<ParsedMenu>,
|
||||
pub hooks: Hooks,
|
||||
pub rm_always_trash: bool,
|
||||
pub shell_integration: bool,
|
||||
// Shell integration OSC meaning is described in the default_config.nu
|
||||
pub shell_integration_osc2: bool,
|
||||
pub shell_integration_osc7: bool,
|
||||
pub shell_integration_osc8: bool,
|
||||
pub shell_integration_osc9_9: bool,
|
||||
pub shell_integration_osc133: bool,
|
||||
pub shell_integration_osc633: bool,
|
||||
pub shell_integration_reset_application_mode: bool,
|
||||
pub buffer_editor: Value,
|
||||
pub table_index_mode: TableIndexMode,
|
||||
pub case_sensitive_completions: bool,
|
||||
@ -154,7 +161,15 @@ impl Default for Config {
|
||||
use_ansi_coloring: true,
|
||||
bracketed_paste: true,
|
||||
edit_mode: EditBindings::default(),
|
||||
shell_integration: false,
|
||||
// shell_integration: false,
|
||||
shell_integration_osc2: false,
|
||||
shell_integration_osc7: false,
|
||||
shell_integration_osc8: false,
|
||||
shell_integration_osc9_9: false,
|
||||
shell_integration_osc133: false,
|
||||
shell_integration_osc633: false,
|
||||
shell_integration_reset_application_mode: false,
|
||||
|
||||
render_right_prompt_on_last_line: false,
|
||||
|
||||
hooks: Hooks::new(),
|
||||
@ -639,7 +654,54 @@ impl Value {
|
||||
&mut errors);
|
||||
}
|
||||
"shell_integration" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration);
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"osc2" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration_osc2);
|
||||
}
|
||||
"osc7" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration_osc7);
|
||||
}
|
||||
"osc8" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration_osc8);
|
||||
}
|
||||
"osc9_9" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration_osc9_9);
|
||||
}
|
||||
"osc133" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration_osc133);
|
||||
}
|
||||
"osc633" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration_osc633);
|
||||
}
|
||||
"reset_application_mode" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration_reset_application_mode);
|
||||
}
|
||||
_ => {
|
||||
report_invalid_key(&[key, key2], span, &mut errors);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
true
|
||||
})
|
||||
} else {
|
||||
report_invalid_value("boolean value is deprecated, should be a record. see `config nu --default`.", span, &mut errors);
|
||||
// Reconstruct
|
||||
*value = Value::record(
|
||||
record! {
|
||||
"osc2" => Value::bool(config.shell_integration_osc2, span),
|
||||
"ocs7" => Value::bool(config.shell_integration_osc7, span),
|
||||
"osc8" => Value::bool(config.shell_integration_osc8, span),
|
||||
"osc9_9" => Value::bool(config.shell_integration_osc9_9, span),
|
||||
"osc133" => Value::bool(config.shell_integration_osc133, span),
|
||||
"osc633" => Value::bool(config.shell_integration_osc633, span),
|
||||
"reset_application_mode" => Value::bool(config.shell_integration_reset_application_mode, span),
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
}
|
||||
"buffer_editor" => match value {
|
||||
Value::Nothing { .. } | Value::String { .. } => {
|
||||
|
@ -253,7 +253,7 @@ fn expr_to_string(engine_state: &EngineState, expr: &Expr) -> String {
|
||||
Expr::Record(_) => "record".to_string(),
|
||||
Expr::RowCondition(_) => "row condition".to_string(),
|
||||
Expr::Signature(_) => "signature".to_string(),
|
||||
Expr::String(_) => "string".to_string(),
|
||||
Expr::String(_) | Expr::RawString(_) => "string".to_string(),
|
||||
Expr::StringInterpolation(_) => "string interpolation".to_string(),
|
||||
Expr::Subexpression(_) => "subexpression".to_string(),
|
||||
Expr::Table(_) => "table".to_string(),
|
||||
|
@ -26,8 +26,6 @@ type PoisonDebuggerError<'a> = PoisonError<MutexGuard<'a, Box<dyn Debugger>>>;
|
||||
#[cfg(feature = "plugin")]
|
||||
use crate::{PluginRegistryFile, PluginRegistryItem, RegisteredPlugin};
|
||||
|
||||
pub static PWD_ENV: &str = "PWD";
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum VirtualPath {
|
||||
File(FileId),
|
||||
@ -893,14 +891,6 @@ impl EngineState {
|
||||
self.num_files() - 1
|
||||
}
|
||||
|
||||
pub fn get_cwd(&self) -> Option<String> {
|
||||
if let Some(pwd_value) = self.get_env_var(PWD_ENV) {
|
||||
pwd_value.coerce_string().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_config_path(&mut self, key: &str, val: PathBuf) {
|
||||
self.config_path.insert(key.to_string(), val);
|
||||
}
|
||||
@ -922,12 +912,71 @@ impl EngineState {
|
||||
.map(|comment_spans| self.build_usage(comment_spans))
|
||||
}
|
||||
|
||||
/// Returns the current working directory, which is guaranteed to be canonicalized.
|
||||
///
|
||||
/// Returns an empty String if $env.PWD doesn't exist.
|
||||
#[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
|
||||
pub fn current_work_dir(&self) -> String {
|
||||
self.get_env_var("PWD")
|
||||
.map(|d| d.coerce_string().unwrap_or_default())
|
||||
self.cwd(None)
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns the current working directory, which is guaranteed to be an
|
||||
/// absolute path without trailing slashes, but might contain symlink
|
||||
/// components.
|
||||
///
|
||||
/// If `stack` is supplied, also considers modifications to the working
|
||||
/// directory on the stack that have yet to be merged into the engine state.
|
||||
pub fn cwd(&self, stack: Option<&Stack>) -> Result<PathBuf, ShellError> {
|
||||
// Helper function to create a simple generic error.
|
||||
// Its messages are not especially helpful, but these errors don't occur often, so it's probably fine.
|
||||
fn error(msg: &str) -> Result<PathBuf, ShellError> {
|
||||
Err(ShellError::GenericError {
|
||||
error: msg.into(),
|
||||
msg: "".into(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to check if a path has trailing slashes.
|
||||
fn has_trailing_slash(path: &Path) -> bool {
|
||||
nu_path::components(path).last()
|
||||
== Some(std::path::Component::Normal(std::ffi::OsStr::new("")))
|
||||
}
|
||||
|
||||
// Retrieve $env.PWD from the stack or the engine state.
|
||||
let pwd = if let Some(stack) = stack {
|
||||
stack.get_env_var(self, "PWD")
|
||||
} else {
|
||||
self.get_env_var("PWD").map(ToOwned::to_owned)
|
||||
};
|
||||
|
||||
if let Some(pwd) = pwd {
|
||||
if let Value::String { val, .. } = pwd {
|
||||
let path = PathBuf::from(val);
|
||||
|
||||
if has_trailing_slash(&path) {
|
||||
error("$env.PWD contains trailing slashes")
|
||||
} else if !path.is_absolute() {
|
||||
error("$env.PWD is not an absolute path")
|
||||
} else if !path.exists() {
|
||||
error("$env.PWD points to a non-existent directory")
|
||||
} else if !path.is_dir() {
|
||||
error("$env.PWD points to a non-directory")
|
||||
} else {
|
||||
Ok(path)
|
||||
}
|
||||
} else {
|
||||
error("$env.PWD is not a string")
|
||||
}
|
||||
} else {
|
||||
error("$env.PWD not found")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: see if we can completely get rid of this
|
||||
pub fn get_file_contents(&self) -> &[CachedFile] {
|
||||
&self.files
|
||||
@ -1077,3 +1126,213 @@ mod engine_state_tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_cwd {
|
||||
//! Here're the test cases we need to cover:
|
||||
//!
|
||||
//! `EngineState::cwd()` computes the result from `self.env_vars["PWD"]` and
|
||||
//! optionally `stack.env_vars["PWD"]`.
|
||||
//!
|
||||
//! PWD may be unset in either `env_vars`.
|
||||
//! PWD should NOT be an empty string.
|
||||
//! PWD should NOT be a non-string value.
|
||||
//! PWD should NOT be a relative path.
|
||||
//! PWD should NOT contain trailing slashes.
|
||||
//! PWD may point to a directory or a symlink to directory.
|
||||
//! PWD should NOT point to a file or a symlink to file.
|
||||
//! PWD should NOT point to non-existent entities in the filesystem.
|
||||
|
||||
use crate::{
|
||||
engine::{EngineState, Stack},
|
||||
Span, Value,
|
||||
};
|
||||
use nu_path::assert_path_eq;
|
||||
use std::path::Path;
|
||||
use tempfile::{NamedTempFile, TempDir};
|
||||
|
||||
/// Creates a symlink. Works on both Unix and Windows.
|
||||
#[cfg(any(unix, windows))]
|
||||
fn symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) -> std::io::Result<()> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
std::os::unix::fs::symlink(original, link)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if original.as_ref().is_dir() {
|
||||
std::os::windows::fs::symlink_dir(original, link)
|
||||
} else {
|
||||
std::os::windows::fs::symlink_file(original, link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an engine state initialized with the given PWD.
|
||||
fn engine_state_with_pwd(path: impl AsRef<Path>) -> EngineState {
|
||||
let mut engine_state = EngineState::new();
|
||||
engine_state.add_env_var(
|
||||
"PWD".into(),
|
||||
Value::String {
|
||||
val: path.as_ref().to_string_lossy().to_string(),
|
||||
internal_span: Span::unknown(),
|
||||
},
|
||||
);
|
||||
engine_state
|
||||
}
|
||||
|
||||
/// Create a stack initialized with the given PWD.
|
||||
fn stack_with_pwd(path: impl AsRef<Path>) -> Stack {
|
||||
let mut stack = Stack::new();
|
||||
stack.add_env_var(
|
||||
"PWD".into(),
|
||||
Value::String {
|
||||
val: path.as_ref().to_string_lossy().to_string(),
|
||||
internal_span: Span::unknown(),
|
||||
},
|
||||
);
|
||||
stack
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pwd_not_set() {
|
||||
let engine_state = EngineState::new();
|
||||
engine_state.cwd(None).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pwd_is_empty_string() {
|
||||
let engine_state = engine_state_with_pwd("");
|
||||
engine_state.cwd(None).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pwd_is_non_string_value() {
|
||||
let mut engine_state = EngineState::new();
|
||||
engine_state.add_env_var(
|
||||
"PWD".into(),
|
||||
Value::Glob {
|
||||
val: "*".into(),
|
||||
no_expand: false,
|
||||
internal_span: Span::unknown(),
|
||||
},
|
||||
);
|
||||
engine_state.cwd(None).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pwd_is_relative_path() {
|
||||
let engine_state = engine_state_with_pwd("./foo");
|
||||
|
||||
engine_state.cwd(None).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pwd_has_trailing_slash() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let engine_state = engine_state_with_pwd(dir.path().join(""));
|
||||
|
||||
engine_state.cwd(None).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pwd_points_to_normal_file() {
|
||||
let file = NamedTempFile::new().unwrap();
|
||||
let engine_state = engine_state_with_pwd(file.path());
|
||||
|
||||
engine_state.cwd(None).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pwd_points_to_normal_directory() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let engine_state = engine_state_with_pwd(dir.path());
|
||||
|
||||
let cwd = engine_state.cwd(None).unwrap();
|
||||
assert_path_eq!(cwd, dir.path());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pwd_points_to_symlink_to_file() {
|
||||
let file = NamedTempFile::new().unwrap();
|
||||
let dir = TempDir::new().unwrap();
|
||||
let link = dir.path().join("link");
|
||||
symlink(file.path(), &link).unwrap();
|
||||
let engine_state = engine_state_with_pwd(&link);
|
||||
|
||||
engine_state.cwd(None).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pwd_points_to_symlink_to_directory() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let link = dir.path().join("link");
|
||||
symlink(dir.path(), &link).unwrap();
|
||||
let engine_state = engine_state_with_pwd(&link);
|
||||
|
||||
let cwd = engine_state.cwd(None).unwrap();
|
||||
assert_path_eq!(cwd, link);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pwd_points_to_broken_symlink() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let link = dir.path().join("link");
|
||||
symlink(TempDir::new().unwrap().path(), &link).unwrap();
|
||||
let engine_state = engine_state_with_pwd(&link);
|
||||
|
||||
engine_state.cwd(None).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pwd_points_to_nonexistent_entity() {
|
||||
let engine_state = engine_state_with_pwd(TempDir::new().unwrap().path());
|
||||
|
||||
engine_state.cwd(None).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stack_pwd_not_set() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let engine_state = engine_state_with_pwd(dir.path());
|
||||
let stack = Stack::new();
|
||||
|
||||
let cwd = engine_state.cwd(Some(&stack)).unwrap();
|
||||
assert_eq!(cwd, dir.path());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stack_pwd_is_empty_string() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let engine_state = engine_state_with_pwd(dir.path());
|
||||
let stack = stack_with_pwd("");
|
||||
|
||||
engine_state.cwd(Some(&stack)).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stack_pwd_points_to_normal_directory() {
|
||||
let dir1 = TempDir::new().unwrap();
|
||||
let dir2 = TempDir::new().unwrap();
|
||||
let engine_state = engine_state_with_pwd(dir1.path());
|
||||
let stack = stack_with_pwd(dir2.path());
|
||||
|
||||
let cwd = engine_state.cwd(Some(&stack)).unwrap();
|
||||
assert_path_eq!(cwd, dir2.path());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stack_pwd_points_to_normal_directory_with_symlink_components() {
|
||||
// `/tmp/dir/link` points to `/tmp/dir`, then we set PWD to `/tmp/dir/link/foo`
|
||||
let dir = TempDir::new().unwrap();
|
||||
let link = dir.path().join("link");
|
||||
symlink(dir.path(), &link).unwrap();
|
||||
let foo = link.join("foo");
|
||||
std::fs::create_dir(dir.path().join("foo")).unwrap();
|
||||
let engine_state = EngineState::new();
|
||||
let stack = stack_with_pwd(&foo);
|
||||
|
||||
let cwd = engine_state.cwd(Some(&stack)).unwrap();
|
||||
assert_path_eq!(cwd, foo);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
ast::Block,
|
||||
engine::{
|
||||
usage::build_usage, CachedFile, Command, CommandType, EngineState, OverlayFrame,
|
||||
StateDelta, Variable, VirtualPath, Visibility, PWD_ENV,
|
||||
StateDelta, Variable, VirtualPath, Visibility,
|
||||
},
|
||||
BlockId, Category, Config, DeclId, FileId, Module, ModuleId, ParseError, ParseWarning, Span,
|
||||
Type, Value, VarId, VirtualPathId,
|
||||
@ -601,13 +601,16 @@ impl<'a> StateWorkingSet<'a> {
|
||||
next_id
|
||||
}
|
||||
|
||||
/// Returns the current working directory as a String, which is guaranteed to be canonicalized.
|
||||
/// Returns an empty string if $env.PWD doesn't exist, is not a String, or is not an absolute path.
|
||||
///
|
||||
/// It does NOT consider modifications to the working directory made on a stack.
|
||||
#[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
|
||||
pub fn get_cwd(&self) -> String {
|
||||
let pwd = self
|
||||
.permanent_state
|
||||
.get_env_var(PWD_ENV)
|
||||
.expect("internal error: can't find PWD");
|
||||
pwd.coerce_string()
|
||||
.expect("internal error: PWD not a string")
|
||||
self.permanent_state
|
||||
.cwd(None)
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_env_var(&self, name: &str) -> Option<&Value> {
|
||||
@ -622,16 +625,6 @@ impl<'a> StateWorkingSet<'a> {
|
||||
&self.permanent_state.config
|
||||
}
|
||||
|
||||
pub fn list_env(&self) -> Vec<String> {
|
||||
let mut env_vars = vec![];
|
||||
|
||||
for env_var in self.permanent_state.env_vars.iter() {
|
||||
env_vars.push(env_var.0.clone());
|
||||
}
|
||||
|
||||
env_vars
|
||||
}
|
||||
|
||||
pub fn set_variable_type(&mut self, var_id: VarId, ty: Type) {
|
||||
let num_permanent_vars = self.permanent_state.num_vars();
|
||||
if var_id < num_permanent_vars {
|
||||
|
@ -1178,16 +1178,6 @@ pub enum ShellError {
|
||||
span: Option<Span>,
|
||||
},
|
||||
|
||||
/// An attempt to access a record column failed.
|
||||
#[error("Access failure: {message}")]
|
||||
#[diagnostic(code(nu::shell::lazy_record_access_failed))]
|
||||
LazyRecordAccessFailed {
|
||||
message: String,
|
||||
column_name: String,
|
||||
#[label("Could not access '{column_name}' on this record")]
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// Operation interrupted by user
|
||||
#[error("Operation interrupted by user")]
|
||||
InterruptedByUser {
|
||||
|
@ -139,7 +139,7 @@ pub trait Eval {
|
||||
Ok(Value::list(output_rows, expr.span))
|
||||
}
|
||||
Expr::Keyword(kw) => Self::eval::<D>(state, mut_state, &kw.expr),
|
||||
Expr::String(s) => Ok(Value::string(s.clone(), expr.span)),
|
||||
Expr::String(s) | Expr::RawString(s) => Ok(Value::string(s.clone(), expr.span)),
|
||||
Expr::Nothing => Ok(Value::nothing(expr.span)),
|
||||
Expr::ValueWithUnit(value) => match Self::eval::<D>(state, mut_state, &value.expr)? {
|
||||
Value::Int { val, .. } => value.unit.item.build_value(val, value.unit.span),
|
||||
|
@ -14,6 +14,7 @@ use std::{
|
||||
/// Create a Value for `$nu`.
|
||||
pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result<Value, ShellError> {
|
||||
fn canonicalize_path(engine_state: &EngineState, path: &Path) -> PathBuf {
|
||||
#[allow(deprecated)]
|
||||
let cwd = engine_state.current_work_dir();
|
||||
|
||||
if path.exists() {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user