Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] main from nushell:main #512

Merged
merged 10 commits into from
Oct 22, 2024
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ bench = false
# To use a development version of a dependency please use a global override here
# changing versions in each sub-crate of the workspace is tedious
[patch.crates-io]
# reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}

# Run all benchmarks with `cargo bench`
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ Please submit an issue or PR to be added to this list.
See [Contributing](CONTRIBUTING.md) for details. Thanks to all the people who already contributed!

<a href="https://github.com/nushell/nushell/graphs/contributors">
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=750" />
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=750&columns=20" />
</a>

## License
Expand Down
115 changes: 78 additions & 37 deletions crates/nu-cli/src/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,8 @@ pub fn evaluate_repl(
// escape a few things because this says so
// https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st
let cmd_text = line_editor.current_buffer_contents().to_string();
let len = cmd_text.len();
let mut cmd_text_chars = cmd_text[0..len].chars();
let mut replaced_cmd_text = String::with_capacity(len);

while let Some(c) = unescape_for_vscode(&mut cmd_text_chars) {
replaced_cmd_text.push(c);
}
let replaced_cmd_text = escape_special_vscode_bytes(&cmd_text)?;

run_shell_integration_osc633(
engine_state,
Expand Down Expand Up @@ -220,26 +215,41 @@ pub fn evaluate_repl(
Ok(())
}

fn unescape_for_vscode(text: &mut std::str::Chars) -> Option<char> {
match text.next() {
Some('\\') => match text.next() {
Some('0') => Some('\x00'), // NUL '\0' (null character)
Some('a') => Some('\x07'), // BEL '\a' (bell)
Some('b') => Some('\x08'), // BS '\b' (backspace)
Some('t') => Some('\x09'), // HT '\t' (horizontal tab)
Some('n') => Some('\x0a'), // LF '\n' (new line)
Some('v') => Some('\x0b'), // VT '\v' (vertical tab)
Some('f') => Some('\x0c'), // FF '\f' (form feed)
Some('r') => Some('\x0d'), // CR '\r' (carriage ret)
Some(';') => Some('\x3b'), // semi-colon
Some('\\') => Some('\x5c'), // backslash
Some('e') => Some('\x1b'), // escape
Some(c) => Some(c),
None => None,
},
Some(c) => Some(c),
None => None,
}
fn escape_special_vscode_bytes(input: &str) -> Result<String, ShellError> {
let bytes = input
.chars()
.flat_map(|c| {
let mut buf = [0; 4]; // Buffer to hold UTF-8 bytes of the character
let c_bytes = c.encode_utf8(&mut buf); // Get UTF-8 bytes for the character

if c_bytes.len() == 1 {
let byte = c_bytes.as_bytes()[0];

match byte {
// Escape bytes below 0x20
b if b < 0x20 => format!("\\x{:02X}", byte).into_bytes(),
// Escape semicolon as \x3B
b';' => "\\x3B".to_string().into_bytes(),
// Escape backslash as \\
b'\\' => "\\\\".to_string().into_bytes(),
// Otherwise, return the character unchanged
_ => vec![byte],
}
} else {
// pass through multi-byte characters unchanged
c_bytes.bytes().collect()
}
})
.collect();

String::from_utf8(bytes).map_err(|err| ShellError::CantConvert {
to_type: "string".to_string(),
from_type: "bytes".to_string(),
span: Span::unknown(),
help: Some(format!(
"Error {err}, Unable to convert {input} to escaped bytes"
)),
})
}

fn get_line_editor(engine_state: &mut EngineState, use_color: bool) -> Result<Reedline> {
Expand Down Expand Up @@ -1069,16 +1079,8 @@ fn run_shell_integration_osc633(

// escape a few things because this says so
// https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st

let replaced_cmd_text: String = repl_cmd_line_text
.chars()
.map(|c| match c {
'\n' => '\x0a',
'\r' => '\x0d',
'\x1b' => '\x1b',
_ => c,
})
.collect();
let replaced_cmd_text =
escape_special_vscode_bytes(&repl_cmd_line_text).unwrap_or(repl_cmd_line_text);

//OSC 633 ; E ; <commandline> [; <nonce] ST - Explicitly set the command line with an optional nonce.
run_ansi_sequence(&format!(
Expand Down Expand Up @@ -1421,7 +1423,7 @@ fn are_session_ids_in_sync() {

#[cfg(test)]
mod test_auto_cd {
use super::{do_auto_cd, parse_operation, ReplOperation};
use super::{do_auto_cd, escape_special_vscode_bytes, parse_operation, ReplOperation};
use nu_path::AbsolutePath;
use nu_protocol::engine::{EngineState, Stack};
use tempfile::tempdir;
Expand Down Expand Up @@ -1571,4 +1573,43 @@ mod test_auto_cd {
let input = if cfg!(windows) { r"foo\" } else { "foo/" };
check(tempdir, input, dir);
}

#[test]
fn escape_vscode_semicolon_test() {
let input = r#"now;is"#;
let expected = r#"now\x3Bis"#;
let actual = escape_special_vscode_bytes(input).unwrap();
assert_eq!(expected, actual);
}

#[test]
fn escape_vscode_backslash_test() {
let input = r#"now\is"#;
let expected = r#"now\\is"#;
let actual = escape_special_vscode_bytes(input).unwrap();
assert_eq!(expected, actual);
}

#[test]
fn escape_vscode_linefeed_test() {
let input = "now\nis";
let expected = r#"now\x0Ais"#;
let actual = escape_special_vscode_bytes(input).unwrap();
assert_eq!(expected, actual);
}

#[test]
fn escape_vscode_tab_null_cr_test() {
let input = "now\t\0\ris";
let expected = r#"now\x09\x00\x0Dis"#;
let actual = escape_special_vscode_bytes(input).unwrap();
assert_eq!(expected, actual);
}

#[test]
fn escape_vscode_multibyte_ok() {
let input = "now🍪is";
let actual = escape_special_vscode_bytes(input).unwrap();
assert_eq!(input, actual);
}
}
6 changes: 6 additions & 0 deletions crates/nu-command/src/filters/transpose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ pub fn transpose(

let metadata = input.metadata();
let input: Vec<_> = input.into_iter().collect();
// Ensure error values are propagated
for i in input.iter() {
if let Value::Error { .. } = i {
return Ok(i.clone().into_pipeline_data_with_metadata(metadata));
}
}

let descs = get_columns(&input);

Expand Down
59 changes: 39 additions & 20 deletions crates/nu-command/src/network/url/build_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ impl Command for SubCommand {
example: r#"{a:"AT&T", b: "AT T"} | url build-query"#,
result: Some(Value::test_string("a=AT%26T&b=AT+T")),
},
Example {
description: "Outputs a query string representing the contents of this record",
example: r#"{a: ["one", "two"], b: "three"} | url build-query"#,
result: Some(Value::test_string("a=one&a=two&b=three")),
},
]
}

Expand All @@ -66,30 +71,44 @@ fn to_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
Value::Record { ref val, .. } => {
let mut row_vec = vec![];
for (k, v) in &**val {
match v.coerce_string() {
Ok(s) => {
row_vec.push((k.clone(), s));
}
_ => {
return Err(ShellError::UnsupportedInput {
msg: "Expected a record with string values".to_string(),
input: "value originates from here".into(),
msg_span: head,
input_span: span,
});
match v {
Value::List { ref vals, .. } => {
for v_item in vals {
row_vec.push((
k.clone(),
v_item.coerce_string().map_err(|_| {
ShellError::UnsupportedInput {
msg: "Expected a record with list of string values"
.to_string(),
input: "value originates from here".into(),
msg_span: head,
input_span: span,
}
})?,
));
}
}
_ => row_vec.push((
k.clone(),
v.coerce_string()
.map_err(|_| ShellError::UnsupportedInput {
msg:
"Expected a record with string or list of string values"
.to_string(),
input: "value originates from here".into(),
msg_span: head,
input_span: span,
})?,
)),
}
}

match serde_urlencoded::to_string(row_vec) {
Ok(s) => Ok(s),
_ => Err(ShellError::CantConvert {
to_type: "URL".into(),
from_type: value.get_type().to_string(),
span: head,
help: None,
}),
}
serde_urlencoded::to_string(row_vec).map_err(|_| ShellError::CantConvert {
to_type: "URL".into(),
from_type: value.get_type().to_string(),
span: head,
help: None,
})
}
// Propagate existing errors
Value::Error { error, .. } => Err(*error),
Expand Down
6 changes: 6 additions & 0 deletions crates/nu-command/src/system/ps.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(target_os = "macos")]
use chrono::{Local, TimeZone};
#[cfg(windows)]
use itertools::Itertools;
use nu_engine::command_prelude::*;
Expand Down Expand Up @@ -175,6 +177,10 @@ fn run_ps(
#[cfg(target_os = "macos")]
{
record.push("cwd", Value::string(proc.cwd(), span));
let timestamp = Local
.timestamp_nanos(proc.start_time * 1_000_000_000)
.into();
record.push("start_time", Value::date(timestamp, span));
}
}

Expand Down
11 changes: 6 additions & 5 deletions crates/nu-command/tests/commands/let_.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use nu_test_support::nu;
use rstest::rstest;

#[test]
fn let_name_builtin_var() {
let actual = nu!("let in = 3");

assert!(actual
#[rstest]
#[case("let in = 3")]
#[case("let in: int = 3")]
fn let_name_builtin_var(#[case] assignment: &str) {
assert!(nu!(assignment)
.err
.contains("'in' is the name of a builtin Nushell variable"));
}
Expand Down
11 changes: 6 additions & 5 deletions crates/nu-command/tests/commands/mut_.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use nu_test_support::nu;
use rstest::rstest;

#[test]
fn mut_variable() {
Expand All @@ -7,11 +8,11 @@ fn mut_variable() {
assert_eq!(actual.out, "4");
}

#[test]
fn mut_name_builtin_var() {
let actual = nu!("mut in = 3");

assert!(actual
#[rstest]
#[case("mut in = 3")]
#[case("mut in: int = 3")]
fn mut_name_builtin_var(#[case] assignment: &str) {
assert!(nu!(assignment)
.err
.contains("'in' is the name of a builtin Nushell variable"));
}
Expand Down
12 changes: 12 additions & 0 deletions crates/nu-command/tests/commands/transpose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ fn row_but_all() {

assert!(actual.out.contains("foo: [1, 2]"));
}

#[test]
fn throw_inner_error() {
let error_msg = "This message should show up";
let error = format!("(error make {{ msg: \"{}\" }})", error_msg);
let actual = nu!(format!(
"[[key value]; [foo 1] [foo 2] [{} 3]] | transpose",
error
));

assert!(actual.err.contains(error.as_str()));
}
14 changes: 9 additions & 5 deletions crates/nu-parser/src/lite_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,15 @@ impl LiteCommand {
}

pub fn parts_including_redirection(&self) -> impl Iterator<Item = Span> + '_ {
self.parts.iter().copied().chain(
self.redirection
.iter()
.flat_map(|redirection| redirection.spans()),
)
self.parts
.iter()
.copied()
.chain(
self.redirection
.iter()
.flat_map(|redirection| redirection.spans()),
)
.sorted_unstable_by_key(|a| (a.start, a.end))
}
}

Expand Down
Loading
Loading