mirror of
https://github.com/nushell/nushell.git
synced 2025-06-19 17:38:14 +02:00
check signals while printing values (#14980)
Fixes #14960 # User-Facing Changes - The output of non-streaming values can now be interrupted with ctrl-c: ```nushell ~> use std repeat; random chars --length 100kb | repeat 2000 | str join ' ' | collect <data omitted>^C Error: × Operation interrupted ╭─[entry #1:1:61] 1 │ use std repeat; random chars --length 100kb | repeat 2000 | str join ' ' | collect · ────┬─── · ╰── This operation was interrupted ╰──── ``` - When IO errors occur while printing data, nushell no longer panics: ```diff $ nu -c "true | print" | - -Error: - x Main thread panicked. - |-> at crates/nu-protocol/src/errors/shell_error/io.rs:198:13 - `-> for unknown spans with paths, use `new_internal_with_path` +Error: nu:🐚:io::broken_pipe + + x I/O error + `-> x Broken pipe + + ,-[source:1:1] + 1 | true | print + : ^^|^ + : `-| Writing to stdout failed + : | Broken pipe + `---- ```
This commit is contained in:
parent
fb8ac4198b
commit
942030199d
@ -1,11 +1,11 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use nu_engine::test_help::{convert_single_value_to_cmd_args, eval_block_with_input};
|
||||||
|
use nu_engine::{current_dir, eval_expression};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
PipelineData, Span, Spanned, Type, Value,
|
PipelineData, Span, Spanned, Type, Value,
|
||||||
};
|
};
|
||||||
use nu_engine::test_help::{convert_single_value_to_cmd_args, eval_block_with_input};
|
|
||||||
use nu_engine::{current_dir, eval_expression};
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// Create a minimal test engine state and stack to run commands against.
|
/// Create a minimal test engine state and stack to run commands against.
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ast::{Call, PathMember},
|
ast::{Call, PathMember},
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
shell_error::io::IoError,
|
location,
|
||||||
|
shell_error::{io::IoError, location::Location},
|
||||||
ByteStream, ByteStreamType, Config, ListStream, OutDest, PipelineMetadata, Range, ShellError,
|
ByteStream, ByteStreamType, Config, ListStream, OutDest, PipelineMetadata, Range, ShellError,
|
||||||
Signals, Span, Type, Value,
|
Signals, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use nu_utils::{stderr_write_all_and_flush, stdout_write_all_and_flush};
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
const LINE_ENDING_PATTERN: &[char] = &['\r', '\n'];
|
const LINE_ENDING_PATTERN: &[char] = &['\r', '\n'];
|
||||||
@ -662,25 +662,24 @@ impl PipelineData {
|
|||||||
no_newline: bool,
|
no_newline: bool,
|
||||||
to_stderr: bool,
|
to_stderr: bool,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
|
let span = self.span();
|
||||||
if let PipelineData::Value(Value::Binary { val: bytes, .. }, _) = self {
|
if let PipelineData::Value(Value::Binary { val: bytes, .. }, _) = self {
|
||||||
if to_stderr {
|
if to_stderr {
|
||||||
stderr_write_all_and_flush(bytes).map_err(|err| {
|
write_all_and_flush(
|
||||||
IoError::new_with_additional_context(
|
bytes,
|
||||||
err.kind(),
|
&mut std::io::stderr().lock(),
|
||||||
Span::unknown(),
|
"stderr",
|
||||||
None,
|
span,
|
||||||
"Writing to stderr failed",
|
engine_state.signals(),
|
||||||
)
|
)?;
|
||||||
})?
|
|
||||||
} else {
|
} else {
|
||||||
stdout_write_all_and_flush(bytes).map_err(|err| {
|
write_all_and_flush(
|
||||||
IoError::new_with_additional_context(
|
bytes,
|
||||||
err.kind(),
|
&mut std::io::stdout().lock(),
|
||||||
Span::unknown(),
|
"stdout",
|
||||||
None,
|
span,
|
||||||
"Writing to stdout failed",
|
engine_state.signals(),
|
||||||
)
|
)?;
|
||||||
})?
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@ -694,6 +693,7 @@ impl PipelineData {
|
|||||||
no_newline: bool,
|
no_newline: bool,
|
||||||
to_stderr: bool,
|
to_stderr: bool,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
|
let span = self.span();
|
||||||
if let PipelineData::ByteStream(stream, ..) = self {
|
if let PipelineData::ByteStream(stream, ..) = self {
|
||||||
// Copy ByteStreams directly
|
// Copy ByteStreams directly
|
||||||
stream.print(to_stderr)
|
stream.print(to_stderr)
|
||||||
@ -711,23 +711,21 @@ impl PipelineData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if to_stderr {
|
if to_stderr {
|
||||||
stderr_write_all_and_flush(out).map_err(|err| {
|
write_all_and_flush(
|
||||||
IoError::new_with_additional_context(
|
out,
|
||||||
err.kind(),
|
&mut std::io::stderr().lock(),
|
||||||
Span::unknown(),
|
"stderr",
|
||||||
None,
|
span,
|
||||||
"Writing to stderr failed",
|
engine_state.signals(),
|
||||||
)
|
)?;
|
||||||
})?
|
|
||||||
} else {
|
} else {
|
||||||
stdout_write_all_and_flush(out).map_err(|err| {
|
write_all_and_flush(
|
||||||
IoError::new_with_additional_context(
|
out,
|
||||||
err.kind(),
|
&mut std::io::stdout().lock(),
|
||||||
Span::unknown(),
|
"stdout",
|
||||||
None,
|
span,
|
||||||
"Writing to stdout failed",
|
engine_state.signals(),
|
||||||
)
|
)?;
|
||||||
})?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -764,6 +762,41 @@ impl PipelineData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_all_and_flush<T>(
|
||||||
|
data: T,
|
||||||
|
destination: &mut impl Write,
|
||||||
|
destination_name: &str,
|
||||||
|
span: Option<Span>,
|
||||||
|
signals: &Signals,
|
||||||
|
) -> Result<(), ShellError>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
let io_error_map = |err: std::io::Error, location: Location| {
|
||||||
|
let context = format!("Writing to {} failed", destination_name);
|
||||||
|
match span {
|
||||||
|
None => IoError::new_internal(err.kind(), context, location),
|
||||||
|
Some(span) if span == Span::unknown() => {
|
||||||
|
IoError::new_internal(err.kind(), context, location)
|
||||||
|
}
|
||||||
|
Some(span) => IoError::new_with_additional_context(err.kind(), span, None, context),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let span = span.unwrap_or(Span::unknown());
|
||||||
|
const OUTPUT_CHUNK_SIZE: usize = 8192;
|
||||||
|
for chunk in data.as_ref().chunks(OUTPUT_CHUNK_SIZE) {
|
||||||
|
signals.check(span)?;
|
||||||
|
destination
|
||||||
|
.write_all(chunk)
|
||||||
|
.map_err(|err| io_error_map(err, location!()))?;
|
||||||
|
}
|
||||||
|
destination
|
||||||
|
.flush()
|
||||||
|
.map_err(|err| io_error_map(err, location!()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
enum PipelineIteratorInner {
|
enum PipelineIteratorInner {
|
||||||
Empty,
|
Empty,
|
||||||
Value(Value),
|
Value(Value),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user