diff --git a/Cargo.lock b/Cargo.lock index 77671ba8e..971d896ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -264,6 +264,16 @@ dependencies = [ "syn", ] +[[package]] +name = "ctrlc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf" +dependencies = [ + "nix", + "winapi", +] + [[package]] name = "dialoguer" version = "0.9.0" @@ -339,6 +349,7 @@ version = "0.1.0" dependencies = [ "assert_cmd", "crossterm", + "ctrlc", "dialoguer", "miette", "nu-cli", @@ -558,6 +569,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "nix" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + [[package]] name = "ntapi" version = "0.3.6" diff --git a/Cargo.toml b/Cargo.toml index 6ee4ab820..d08ad421b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ nu-protocol = { path = "./crates/nu-protocol" } nu-table = { path = "./crates/nu-table" } nu-term-grid = { path = "./crates/nu-term-grid" } miette = "3.0.0" +ctrlc = "3.2.1" # mimalloc = { version = "*", default-features = false } [dev-dependencies] diff --git a/TODO.md b/TODO.md index b77f6b7a9..f27d17a92 100644 --- a/TODO.md +++ b/TODO.md @@ -32,8 +32,8 @@ - [x] Config file loading - [x] block variable captures - [x] improved history and config paths +- [x] ctrl-c support - [ ] Support for `$in` -- [ ] ctrl-c support - [ ] operator overflow - [ ] shells - [ ] plugins diff --git a/crates/nu-command/src/conversions/into/binary.rs b/crates/nu-command/src/conversions/into/binary.rs index 4704b2032..1cd2eb7b4 100644 --- a/crates/nu-command/src/conversions/into/binary.rs +++ b/crates/nu-command/src/conversions/into/binary.rs @@ -26,12 +26,12 @@ impl Command for SubCommand { fn run( &self, - _engine_state: &EngineState, + engine_state: &EngineState, _stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { - into_binary(call, input) + into_binary(engine_state, call, input) } fn examples(&self) -> Vec { @@ -86,27 +86,31 @@ impl Command for SubCommand { } fn into_binary( + engine_state: &EngineState, call: &Call, input: PipelineData, ) -> Result { let head = call.head; // let column_paths: Vec = call.rest(context, 0)?; - input.map(move |v| { - action(v, head) - // FIXME: Add back in cell_path support - // if column_paths.is_empty() { - // action(v, head) - // } else { - // let mut ret = v; - // for path in &column_paths { - // ret = - // ret.swap_data_by_cell_path(path, Box::new(move |old| action(old, old.tag())))?; - // } + input.map( + move |v| { + action(v, head) + // FIXME: Add back in cell_path support + // if column_paths.is_empty() { + // action(v, head) + // } else { + // let mut ret = v; + // for path in &column_paths { + // ret = + // ret.swap_data_by_cell_path(path, Box::new(move |old| action(old, old.tag())))?; + // } - // Ok(ret) - // } - }) + // Ok(ret) + // } + }, + engine_state.ctrlc.clone(), + ) } fn int_to_endian(n: i64) -> Vec { diff --git a/crates/nu-command/src/conversions/into/filesize.rs b/crates/nu-command/src/conversions/into/filesize.rs index 68c9746e7..124914c73 100644 --- a/crates/nu-command/src/conversions/into/filesize.rs +++ b/crates/nu-command/src/conversions/into/filesize.rs @@ -26,12 +26,12 @@ impl Command for SubCommand { fn run( &self, - _engine_state: &EngineState, + engine_state: &EngineState, _stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { - into_filesize(call, input) + into_filesize(engine_state, call, input) } fn examples(&self) -> Vec { @@ -114,30 +114,34 @@ impl Command for SubCommand { } fn into_filesize( + engine_state: &EngineState, call: &Call, input: PipelineData, ) -> Result { let head = call.head; // let call_paths: Vec = args.rest(0)?; - input.map(move |v| { - action(v, head) + input.map( + move |v| { + action(v, head) - // FIXME: Add back cell_path support - // if column_paths.is_empty() { - // action(&v, v.tag()) - // } else { - // let mut ret = v; - // for path in &column_paths { - // ret = ret.swap_data_by_column_path( - // path, - // Box::new(move |old| action(old, old.tag())), - // )?; - // } + // FIXME: Add back cell_path support + // if column_paths.is_empty() { + // action(&v, v.tag()) + // } else { + // let mut ret = v; + // for path in &column_paths { + // ret = ret.swap_data_by_column_path( + // path, + // Box::new(move |old| action(old, old.tag())), + // )?; + // } - // Ok(ret) - // } - }) + // Ok(ret) + // } + }, + engine_state.ctrlc.clone(), + ) } pub fn action(input: Value, span: Span) -> Value { diff --git a/crates/nu-command/src/conversions/into/int.rs b/crates/nu-command/src/conversions/into/int.rs index eb76ef9aa..e6bceb915 100644 --- a/crates/nu-command/src/conversions/into/int.rs +++ b/crates/nu-command/src/conversions/into/int.rs @@ -26,12 +26,12 @@ impl Command for SubCommand { fn run( &self, - _engine_state: &EngineState, + engine_state: &EngineState, _stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { - into_int(call, input) + into_int(engine_state, call, input) } fn examples(&self) -> Vec { @@ -90,27 +90,31 @@ impl Command for SubCommand { } fn into_int( + engine_state: &EngineState, call: &Call, input: PipelineData, ) -> Result { let head = call.head; // let column_paths: Vec = call.rest(context, 0)?; - input.map(move |v| { - action(v, head) - // FIXME: Add back cell_path support - // if column_paths.is_empty() { - // action(&v, v.tag()) - // } else { - // let mut ret = v; - // for path in &column_paths { - // ret = ret - // .swap_data_by_column_path(path, Box::new(move |old| action(old, old.tag())))?; - // } + input.map( + move |v| { + action(v, head) + // FIXME: Add back cell_path support + // if column_paths.is_empty() { + // action(&v, v.tag()) + // } else { + // let mut ret = v; + // for path in &column_paths { + // ret = ret + // .swap_data_by_column_path(path, Box::new(move |old| action(old, old.tag())))?; + // } - // Ok(ret) - // } - }) + // Ok(ret) + // } + }, + engine_state.ctrlc.clone(), + ) } pub fn action(input: Value, span: Span) -> Value { diff --git a/crates/nu-command/src/core_commands/for_.rs b/crates/nu-command/src/core_commands/for_.rs index beee775b2..53daf287c 100644 --- a/crates/nu-command/src/core_commands/for_.rs +++ b/crates/nu-command/src/core_commands/for_.rs @@ -1,7 +1,9 @@ use nu_engine::{eval_block, eval_expression}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Example, IntoPipelineData, PipelineData, Signature, Span, SyntaxShape, Value}; +use nu_protocol::{ + Example, IntoInterruptiblePipelineData, PipelineData, Signature, Span, SyntaxShape, Value, +}; #[derive(Clone)] pub struct For; @@ -55,6 +57,7 @@ impl Command for For { .as_block() .expect("internal error: expected block"); + let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); let block = engine_state.get_block(block_id).clone(); let mut stack = stack.collect_captures(&block.captures); @@ -71,7 +74,7 @@ impl Command for For { Err(error) => Value::Error { error }, } }) - .into_pipeline_data()), + .into_pipeline_data(ctrlc)), Value::Range { val, .. } => Ok(val .into_range_iter()? .map(move |x| { @@ -83,7 +86,7 @@ impl Command for For { Err(error) => Value::Error { error }, } }) - .into_pipeline_data()), + .into_pipeline_data(ctrlc)), x => { stack.add_var(var_id, x); diff --git a/crates/nu-command/src/core_commands/help.rs b/crates/nu-command/src/core_commands/help.rs index 06f0e208f..534a36978 100644 --- a/crates/nu-command/src/core_commands/help.rs +++ b/crates/nu-command/src/core_commands/help.rs @@ -1,8 +1,8 @@ use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - span, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape, - Value, + span, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, + Signature, Spanned, SyntaxShape, Value, }; use nu_engine::{get_full_help, CallExt}; @@ -121,7 +121,9 @@ fn help( } } - return Ok(found_cmds_vec.into_iter().into_pipeline_data()); + return Ok(found_cmds_vec + .into_iter() + .into_pipeline_data(engine_state.ctrlc.clone())); } if !rest.is_empty() { @@ -155,7 +157,9 @@ fn help( }); } - Ok(found_cmds_vec.into_iter().into_pipeline_data()) + Ok(found_cmds_vec + .into_iter() + .into_pipeline_data(engine_state.ctrlc.clone())) } else { let mut name = String::new(); let mut output = String::new(); diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 2dfe49b61..93e9e49f9 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -7,6 +7,7 @@ use crate::*; pub fn create_default_context() -> EngineState { let mut engine_state = EngineState::new(); + let delta = { let mut working_set = StateWorkingSet::new(&engine_state); diff --git a/crates/nu-command/src/experimental/list_git_branches.rs b/crates/nu-command/src/experimental/list_git_branches.rs index 19c171bce..7c2bd2fdc 100644 --- a/crates/nu-command/src/experimental/list_git_branches.rs +++ b/crates/nu-command/src/experimental/list_git_branches.rs @@ -7,7 +7,7 @@ use nu_protocol::ast::Call; use nu_protocol::engine::Command; use nu_protocol::engine::EngineState; use nu_protocol::engine::Stack; -use nu_protocol::IntoPipelineData; +use nu_protocol::IntoInterruptiblePipelineData; use nu_protocol::PipelineData; use nu_protocol::{Signature, Value}; @@ -30,7 +30,7 @@ impl Command for ListGitBranches { fn run( &self, - _engine_state: &EngineState, + engine_state: &EngineState, _stack: &mut Stack, call: &Call, _input: PipelineData, @@ -62,7 +62,9 @@ impl Command for ListGitBranches { }) .collect(); - Ok(lines.into_iter().into_pipeline_data()) + Ok(lines + .into_iter() + .into_pipeline_data(engine_state.ctrlc.clone())) } else { Ok(PipelineData::new()) } diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 6ae7fa49b..ea4907b46 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -2,7 +2,7 @@ use chrono::{DateTime, Utc}; use nu_engine::eval_expression; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{IntoPipelineData, PipelineData, Signature, SyntaxShape, Value}; +use nu_protocol::{IntoInterruptiblePipelineData, PipelineData, Signature, SyntaxShape, Value}; #[derive(Clone)] pub struct Ls; @@ -112,6 +112,6 @@ impl Command for Ls { }, _ => Value::Nothing { span: call_span }, }) - .into_pipeline_data()) + .into_pipeline_data(engine_state.ctrlc.clone())) } } diff --git a/crates/nu-command/src/filesystem/mkdir.rs b/crates/nu-command/src/filesystem/mkdir.rs index c4ecc874b..df0beaa80 100644 --- a/crates/nu-command/src/filesystem/mkdir.rs +++ b/crates/nu-command/src/filesystem/mkdir.rs @@ -4,7 +4,9 @@ use std::env::current_dir; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value}; +use nu_protocol::{ + IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value, +}; #[derive(Clone)] pub struct Mkdir; @@ -69,6 +71,8 @@ impl Command for Mkdir { } } - Ok(stream.into_iter().into_pipeline_data()) + Ok(stream + .into_iter() + .into_pipeline_data(engine_state.ctrlc.clone())) } } diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index 685141082..7c83b0bcc 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -8,7 +8,9 @@ use super::util::get_interactive_confirmation; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value}; +use nu_protocol::{ + IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value, +}; #[derive(Clone)] pub struct Rm; @@ -170,7 +172,9 @@ fn rm( // let temp = rm_helper(call, args).flatten(); // let temp = input.flatten(call.head, move |_| rm_helper(call, args)); - Ok(response.into_iter().into_pipeline_data()) + Ok(response + .into_iter() + .into_pipeline_data(engine_state.ctrlc.clone())) // Ok(Value::Nothing { span }) } diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index 145584b53..5d5413aff 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -1,7 +1,10 @@ use nu_engine::eval_block; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Example, IntoPipelineData, PipelineData, Signature, Span, SyntaxShape, Value}; +use nu_protocol::{ + Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature, Span, + SyntaxShape, Value, +}; #[derive(Clone)] pub struct Each; @@ -63,6 +66,7 @@ impl Command for Each { .expect("internal error: expected block"); let numbered = call.has_flag("numbered"); + let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); let block = engine_state.get_block(block_id).clone(); let mut stack = stack.collect_captures(&block.captures); @@ -101,7 +105,7 @@ impl Command for Each { Err(error) => Value::Error { error }, } }) - .into_pipeline_data()), + .into_pipeline_data(ctrlc)), PipelineData::Value(Value::List { vals: val, .. }) => Ok(val .into_iter() .enumerate() @@ -134,7 +138,7 @@ impl Command for Each { Err(error) => Value::Error { error }, } }) - .into_pipeline_data()), + .into_pipeline_data(ctrlc)), PipelineData::Stream(stream) => Ok(stream .enumerate() .map(move |(idx, x)| { @@ -166,7 +170,7 @@ impl Command for Each { Err(error) => Value::Error { error }, } }) - .into_pipeline_data()), + .into_pipeline_data(ctrlc)), PipelineData::Value(Value::Record { cols, vals, .. }) => { let mut output_cols = vec![]; let mut output_vals = vec![]; diff --git a/crates/nu-command/src/filters/last.rs b/crates/nu-command/src/filters/last.rs index a87807680..2d9e92939 100644 --- a/crates/nu-command/src/filters/last.rs +++ b/crates/nu-command/src/filters/last.rs @@ -3,7 +3,8 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, + Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, + Value, }; use std::convert::TryInto; @@ -54,7 +55,7 @@ impl Command for Last { .into_iter() .skip(beginning_rows_to_skip.try_into().unwrap()); - Ok(iter.into_pipeline_data()) + Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) } } diff --git a/crates/nu-command/src/filters/lines.rs b/crates/nu-command/src/filters/lines.rs index b1a11b576..a4ad8143f 100644 --- a/crates/nu-command/src/filters/lines.rs +++ b/crates/nu-command/src/filters/lines.rs @@ -1,6 +1,6 @@ use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Signature, Value}; +use nu_protocol::{IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Value}; #[derive(Clone)] pub struct Lines; @@ -22,7 +22,7 @@ impl Command for Lines { fn run( &self, - _engine_state: &EngineState, + engine_state: &EngineState, _stack: &mut Stack, call: &Call, input: PipelineData, @@ -46,7 +46,7 @@ impl Command for Lines { } }); - Ok(iter.into_pipeline_data()) + Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) } PipelineData::Stream(stream) => { let iter = stream @@ -74,7 +74,7 @@ impl Command for Lines { }) .flatten(); - Ok(iter.into_pipeline_data()) + Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) } PipelineData::Value(val) => Err(ShellError::UnsupportedInput( format!("Not supported input: {}", val.as_string()?), diff --git a/crates/nu-command/src/filters/par_each.rs b/crates/nu-command/src/filters/par_each.rs index 8f4b5ac5f..d59983bd2 100644 --- a/crates/nu-command/src/filters/par_each.rs +++ b/crates/nu-command/src/filters/par_each.rs @@ -1,7 +1,10 @@ use nu_engine::eval_block; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value}; +use nu_protocol::{ + Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature, SyntaxShape, + Value, +}; use rayon::prelude::*; #[derive(Clone)] @@ -46,6 +49,7 @@ impl Command for ParEach { .expect("internal error: expected block"); let numbered = call.has_flag("numbered"); + let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); let block = engine_state.get_block(block_id); let mut stack = stack.collect_captures(&block.captures); @@ -92,7 +96,7 @@ impl Command for ParEach { .collect::>() .into_iter() .flatten() - .into_pipeline_data()), + .into_pipeline_data(ctrlc)), PipelineData::Value(Value::List { vals: val, .. }) => Ok(val .into_iter() .enumerate() @@ -133,7 +137,7 @@ impl Command for ParEach { .collect::>() .into_iter() .flatten() - .into_pipeline_data()), + .into_pipeline_data(ctrlc)), PipelineData::Stream(stream) => Ok(stream .enumerate() .par_bridge() @@ -173,7 +177,7 @@ impl Command for ParEach { .collect::>() .into_iter() .flatten() - .into_pipeline_data()), + .into_pipeline_data(ctrlc)), PipelineData::Value(Value::Record { cols, vals, .. }) => { let mut output_cols = vec![]; let mut output_vals = vec![]; diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs index 1a43094ec..aedf0378f 100644 --- a/crates/nu-command/src/filters/select.rs +++ b/crates/nu-command/src/filters/select.rs @@ -2,7 +2,8 @@ use nu_engine::CallExt; use nu_protocol::ast::{Call, CellPath}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, + Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, Signature, + Span, SyntaxShape, Value, }; #[derive(Clone)] @@ -35,7 +36,7 @@ impl Command for Select { let columns: Vec = call.rest(engine_state, stack, 0)?; let span = call.head; - select(span, columns, input) + select(engine_state, span, columns, input) } fn examples(&self) -> Vec { @@ -55,6 +56,7 @@ impl Command for Select { } fn select( + engine_state: &EngineState, span: Span, columns: Vec, input: PipelineData, @@ -84,7 +86,9 @@ fn select( output.push(Value::Record { cols, vals, span }) } - Ok(output.into_iter().into_pipeline_data()) + Ok(output + .into_iter() + .into_pipeline_data(engine_state.ctrlc.clone())) } PipelineData::Stream(stream) => Ok(stream .map(move |x| { @@ -106,7 +110,7 @@ fn select( Value::Record { cols, vals, span } }) - .into_pipeline_data()), + .into_pipeline_data(engine_state.ctrlc.clone())), PipelineData::Value(v) => { let mut cols = vec![]; let mut vals = vec![]; diff --git a/crates/nu-command/src/filters/where_.rs b/crates/nu-command/src/filters/where_.rs index d479835ef..c837b47a2 100644 --- a/crates/nu-command/src/filters/where_.rs +++ b/crates/nu-command/src/filters/where_.rs @@ -1,7 +1,10 @@ use nu_engine::eval_expression; use nu_protocol::ast::{Call, Expr, Expression}; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value}; +use nu_protocol::{ + IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, Signature, + SyntaxShape, Value, +}; #[derive(Clone)] pub struct Where; @@ -28,6 +31,7 @@ impl Command for Where { ) -> Result { let cond = call.positional[0].clone(); + let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); // FIXME: very expensive @@ -53,7 +57,7 @@ impl Command for Where { _ => false, } }) - .into_pipeline_data()), + .into_pipeline_data(ctrlc)), PipelineData::Value(Value::List { vals, .. }) => Ok(vals .into_iter() .filter(move |value| { @@ -66,7 +70,7 @@ impl Command for Where { _ => false, } }) - .into_pipeline_data()), + .into_pipeline_data(ctrlc)), PipelineData::Value(x) => { stack.add_var(var_id, x.clone()); diff --git a/crates/nu-command/src/filters/wrap.rs b/crates/nu-command/src/filters/wrap.rs index d06324725..48a751858 100644 --- a/crates/nu-command/src/filters/wrap.rs +++ b/crates/nu-command/src/filters/wrap.rs @@ -1,7 +1,9 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{IntoPipelineData, PipelineData, Signature, SyntaxShape, Value}; +use nu_protocol::{ + IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value, +}; #[derive(Clone)] pub struct Wrap; @@ -37,14 +39,14 @@ impl Command for Wrap { vals: vec![x], span, }) - .into_pipeline_data()), + .into_pipeline_data(engine_state.ctrlc.clone())), PipelineData::Stream(stream) => Ok(stream .map(move |x| Value::Record { cols: vec![name.clone()], vals: vec![x], span, }) - .into_pipeline_data()), + .into_pipeline_data(engine_state.ctrlc.clone())), PipelineData::Value(input) => Ok(Value::Record { cols: vec![name], vals: vec![input], diff --git a/crates/nu-command/src/formats/from/json.rs b/crates/nu-command/src/formats/from/json.rs index fab0cf968..2de433dd1 100644 --- a/crates/nu-command/src/formats/from/json.rs +++ b/crates/nu-command/src/formats/from/json.rs @@ -1,6 +1,9 @@ use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value}; +use nu_protocol::{ + Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, Signature, + Span, Value, +}; #[derive(Clone)] pub struct FromJson; @@ -68,7 +71,7 @@ impl Command for FromJson { fn run( &self, - _engine_state: &EngineState, + engine_state: &EngineState, _stack: &mut Stack, call: &Call, input: PipelineData, @@ -90,7 +93,7 @@ impl Command for FromJson { Err(error) => Value::Error { error }, } }) - .into_pipeline_data()) + .into_pipeline_data(engine_state.ctrlc.clone())) } else { Ok(convert_string_to_value(string_input, span)?.into_pipeline_data()) } diff --git a/crates/nu-command/src/math/abs.rs b/crates/nu-command/src/math/abs.rs index 95f46ef7a..888317cd2 100644 --- a/crates/nu-command/src/math/abs.rs +++ b/crates/nu-command/src/math/abs.rs @@ -20,13 +20,16 @@ impl Command for SubCommand { fn run( &self, - _engine_state: &EngineState, + engine_state: &EngineState, _stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { let head = call.head; - input.map(move |value| abs_helper(value, head)) + input.map( + move |value| abs_helper(value, head), + engine_state.ctrlc.clone(), + ) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/strings/size.rs b/crates/nu-command/src/strings/size.rs index 96bf047b9..c8307845c 100644 --- a/crates/nu-command/src/strings/size.rs +++ b/crates/nu-command/src/strings/size.rs @@ -24,12 +24,12 @@ impl Command for Size { fn run( &self, - _engine_state: &EngineState, + engine_state: &EngineState, _stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { - size(call, input) + size(engine_state, call, input) } fn examples(&self) -> Vec { @@ -100,18 +100,25 @@ impl Command for Size { } } -fn size(call: &Call, input: PipelineData) -> Result { +fn size( + engine_state: &EngineState, + call: &Call, + input: PipelineData, +) -> Result { let span = call.head; - input.map(move |v| match v.as_string() { - Ok(s) => count(&s, span), - Err(_) => Value::Error { - error: ShellError::PipelineMismatch { - expected: Type::String, - expected_span: span, - origin: span, + input.map( + move |v| match v.as_string() { + Ok(s) => count(&s, span), + Err(_) => Value::Error { + error: ShellError::PipelineMismatch { + expected: Type::String, + expected_span: span, + origin: span, + }, }, }, - }) + engine_state.ctrlc.clone(), + ) } fn count(contents: &str, span: Span) -> Value { diff --git a/crates/nu-command/src/strings/split/chars.rs b/crates/nu-command/src/strings/split/chars.rs index 163d5bb11..bfd48a2d8 100644 --- a/crates/nu-command/src/strings/split/chars.rs +++ b/crates/nu-command/src/strings/split/chars.rs @@ -39,22 +39,26 @@ impl Command for SubCommand { fn run( &self, - _engine_state: &EngineState, + engine_state: &EngineState, _stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { - split_chars(call, input) + split_chars(engine_state, call, input) } } fn split_chars( + engine_state: &EngineState, call: &Call, input: PipelineData, ) -> Result { let span = call.head; - input.flat_map(move |x| split_chars_helper(&x, span)) + input.flat_map( + move |x| split_chars_helper(&x, span), + engine_state.ctrlc.clone(), + ) } fn split_chars_helper(v: &Value, name: Span) -> Vec { diff --git a/crates/nu-command/src/strings/split/column.rs b/crates/nu-command/src/strings/split/column.rs index 374904907..dbdfa28e1 100644 --- a/crates/nu-command/src/strings/split/column.rs +++ b/crates/nu-command/src/strings/split/column.rs @@ -54,7 +54,10 @@ fn split_column( let rest: Vec> = call.rest(engine_state, stack, 1)?; let collapse_empty = call.has_flag("collapse-empty"); - input.flat_map(move |x| split_column_helper(&x, &separator, &rest, collapse_empty, name_span)) + input.flat_map( + move |x| split_column_helper(&x, &separator, &rest, collapse_empty, name_span), + engine_state.ctrlc.clone(), + ) } fn split_column_helper( diff --git a/crates/nu-command/src/strings/split/row.rs b/crates/nu-command/src/strings/split/row.rs index 880672faf..b5ada3f14 100644 --- a/crates/nu-command/src/strings/split/row.rs +++ b/crates/nu-command/src/strings/split/row.rs @@ -45,7 +45,10 @@ fn split_row( let name_span = call.head; let separator: Spanned = call.req(engine_state, stack, 0)?; - input.flat_map(move |x| split_row_helper(&x, &separator, name_span)) + input.flat_map( + move |x| split_row_helper(&x, &separator, name_span), + engine_state.ctrlc.clone(), + ) } fn split_row_helper(v: &Value, separator: &Spanned, name: Span) -> Vec { diff --git a/crates/nu-command/src/system/ps.rs b/crates/nu-command/src/system/ps.rs index efff137da..a1f9e904f 100644 --- a/crates/nu-command/src/system/ps.rs +++ b/crates/nu-command/src/system/ps.rs @@ -1,7 +1,7 @@ use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Example, IntoPipelineData, PipelineData, ShellError, Signature, Value, + Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Value, }; use sysinfo::{ProcessExt, System, SystemExt}; @@ -30,12 +30,12 @@ impl Command for Ps { fn run( &self, - _engine_state: &EngineState, + engine_state: &EngineState, _stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { - run_ps(call) + run_ps(engine_state, call) } fn examples(&self) -> Vec { @@ -47,7 +47,7 @@ impl Command for Ps { } } -fn run_ps(call: &Call) -> Result { +fn run_ps(engine_state: &EngineState, call: &Call) -> Result { let span = call.head; let long = call.has_flag("long"); let mut sys = System::new_all(); @@ -126,5 +126,7 @@ fn run_ps(call: &Call) -> Result { } } - Ok(output.into_iter().into_pipeline_data()) + Ok(output + .into_iter() + .into_pipeline_data(engine_state.ctrlc.clone())) } diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index c1f27e58b..ca790b07d 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -7,7 +7,7 @@ use std::sync::mpsc; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ast::Call, engine::Command, ShellError, Signature, SyntaxShape, Value}; -use nu_protocol::{IntoPipelineData, PipelineData, Span, Spanned}; +use nu_protocol::{IntoInterruptiblePipelineData, PipelineData, Span, Spanned}; use nu_engine::CallExt; @@ -49,7 +49,7 @@ impl Command for External { last_expression, env_vars, }; - command.run_with_input(input) + command.run_with_input(engine_state, input) } } @@ -61,9 +61,15 @@ pub struct ExternalCommand { } impl ExternalCommand { - pub fn run_with_input(&self, input: PipelineData) -> Result { + pub fn run_with_input( + &self, + engine_state: &EngineState, + input: PipelineData, + ) -> Result { let mut process = self.create_command(); + let ctrlc = engine_state.ctrlc.clone(); + // TODO. We don't have a way to know the current directory // This should be information from the EvaluationContex or EngineState let path = env::current_dir().unwrap(); @@ -155,7 +161,7 @@ impl ExternalCommand { }); // The ValueStream is consumed by the next expression in the pipeline - ChannelReceiver::new(rx).into_pipeline_data() + ChannelReceiver::new(rx).into_pipeline_data(ctrlc) } else { PipelineData::new() }; diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index de240af11..c9333ec7d 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -1,7 +1,10 @@ use super::Command; use crate::{ast::Block, BlockId, DeclId, Example, Signature, Span, Type, VarId}; use core::panic; -use std::collections::HashMap; +use std::{ + collections::HashMap, + sync::{atomic::AtomicBool, Arc}, +}; #[derive(Clone)] pub struct EngineState { @@ -11,6 +14,7 @@ pub struct EngineState { decls: im::Vector>, blocks: im::Vector, pub scope: im::Vector, + pub ctrlc: Option>, } // Tells whether a decl etc. is visible or not @@ -102,6 +106,7 @@ impl EngineState { decls: im::vector![], blocks: im::vector![], scope: im::vector![ScopeFrame::new()], + ctrlc: None, } } diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index db77ad61f..4e6f6f8ef 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -1,3 +1,5 @@ +use std::sync::{atomic::AtomicBool, Arc}; + use crate::{ast::PathMember, ShellError, Span, Value, ValueStream}; pub enum PipelineData { @@ -40,18 +42,22 @@ impl PipelineData { } /// Simplified mapper to help with simple values also. For full iterator support use `.into_iter()` instead - pub fn map(self, mut f: F) -> Result + pub fn map( + self, + mut f: F, + ctrlc: Option>, + ) -> Result where Self: Sized, F: FnMut(Value) -> Value + 'static + Send, { match self { PipelineData::Value(Value::List { vals, .. }) => { - Ok(vals.into_iter().map(f).into_pipeline_data()) + Ok(vals.into_iter().map(f).into_pipeline_data(ctrlc)) } - PipelineData::Stream(stream) => Ok(stream.map(f).into_pipeline_data()), + PipelineData::Stream(stream) => Ok(stream.map(f).into_pipeline_data(ctrlc)), PipelineData::Value(Value::Range { val, .. }) => { - Ok(val.into_range_iter()?.map(f).into_pipeline_data()) + Ok(val.into_range_iter()?.map(f).into_pipeline_data(ctrlc)) } PipelineData::Value(v) => { let output = f(v); @@ -64,7 +70,11 @@ impl PipelineData { } /// Simplified flatmapper. For full iterator support use `.into_iter()` instead - pub fn flat_map(self, mut f: F) -> Result + pub fn flat_map( + self, + mut f: F, + ctrlc: Option>, + ) -> Result where Self: Sized, U: IntoIterator, @@ -73,14 +83,14 @@ impl PipelineData { { match self { PipelineData::Value(Value::List { vals, .. }) => { - Ok(vals.into_iter().map(f).flatten().into_pipeline_data()) + Ok(vals.into_iter().map(f).flatten().into_pipeline_data(ctrlc)) } - PipelineData::Stream(stream) => Ok(stream.map(f).flatten().into_pipeline_data()), + PipelineData::Stream(stream) => Ok(stream.map(f).flatten().into_pipeline_data(ctrlc)), PipelineData::Value(Value::Range { val, .. }) => match val.into_range_iter() { - Ok(iter) => Ok(iter.map(f).flatten().into_pipeline_data()), + Ok(iter) => Ok(iter.map(f).flatten().into_pipeline_data(ctrlc)), Err(error) => Err(error), }, - PipelineData::Value(v) => Ok(f(v).into_iter().into_pipeline_data()), + PipelineData::Value(v) => Ok(f(v).into_iter().into_pipeline_data(ctrlc)), } } } @@ -100,9 +110,12 @@ impl IntoIterator for PipelineData { fn into_iter(self) -> Self::IntoIter { match self { - PipelineData::Value(Value::List { vals, .. }) => PipelineIterator( - PipelineData::Stream(ValueStream(Box::new(vals.into_iter()))), - ), + PipelineData::Value(Value::List { vals, .. }) => { + PipelineIterator(PipelineData::Stream(ValueStream { + stream: Box::new(vals.into_iter()), + ctrlc: None, + })) + } x => PipelineIterator(x), } } @@ -133,11 +146,18 @@ impl IntoPipelineData for Value { } } -impl IntoPipelineData for T +pub trait IntoInterruptiblePipelineData { + fn into_pipeline_data(self, ctrlc: Option>) -> PipelineData; +} + +impl IntoInterruptiblePipelineData for T where T: Iterator + Send + 'static, { - fn into_pipeline_data(self) -> PipelineData { - PipelineData::Stream(ValueStream(Box::new(self))) + fn into_pipeline_data(self, ctrlc: Option>) -> PipelineData { + PipelineData::Stream(ValueStream { + stream: Box::new(self), + ctrlc, + }) } } diff --git a/crates/nu-protocol/src/value/stream.rs b/crates/nu-protocol/src/value/stream.rs index 81aec0f0a..3c69cc504 100644 --- a/crates/nu-protocol/src/value/stream.rs +++ b/crates/nu-protocol/src/value/stream.rs @@ -1,7 +1,16 @@ use crate::*; -use std::fmt::Debug; +use std::{ + fmt::Debug, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; -pub struct ValueStream(pub Box + Send + 'static>); +pub struct ValueStream { + pub stream: Box + Send + 'static>, + pub ctrlc: Option>, +} impl ValueStream { pub fn into_string(self) -> String { @@ -19,8 +28,14 @@ impl ValueStream { .join("\n") } - pub fn from_stream(input: impl Iterator + Send + 'static) -> ValueStream { - ValueStream(Box::new(input)) + pub fn from_stream( + input: impl Iterator + Send + 'static, + ctrlc: Option>, + ) -> ValueStream { + ValueStream { + stream: Box::new(input), + ctrlc, + } } } @@ -34,8 +49,14 @@ impl Iterator for ValueStream { type Item = Value; fn next(&mut self) -> Option { - { - self.0.next() + if let Some(ctrlc) = &self.ctrlc { + if ctrlc.load(Ordering::SeqCst) { + None + } else { + self.stream.next() + } + } else { + self.stream.next() } } } diff --git a/src/main.rs b/src/main.rs index 22bb5de0c..5fff81f55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,10 @@ -use std::io::Write; +use std::{ + io::Write, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; use dialoguer::{ console::{Style, Term}, @@ -84,6 +90,20 @@ fn main() -> Result<()> { let mut engine_state = create_default_context(); + // TODO: make this conditional in the future + // Ctrl-c protection section + let ctrlc = Arc::new(AtomicBool::new(false)); + let handler_ctrlc = ctrlc.clone(); + let engine_state_ctrlc = ctrlc.clone(); + + ctrlc::set_handler(move || { + handler_ctrlc.store(true, Ordering::SeqCst); + }) + .expect("Error setting Ctrl-C handler"); + + engine_state.ctrlc = Some(engine_state_ctrlc); + // End ctrl-c protection section + if let Some(path) = std::env::args().nth(1) { let file = std::fs::read(&path).into_diagnostic()?; @@ -153,6 +173,9 @@ fn main() -> Result<()> { }; loop { + //Reset the ctrl-c handler + ctrlc.store(false, Ordering::SeqCst); + let line_editor = Reedline::create() .into_diagnostic()? .with_completion_action_handler(Box::new(FuzzyCompletion {