mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +00:00
fix: fix commandline
when called with no arguments (#8207)
# Description This fixes the `commandline` command when it's run with no arguments, so it outputs the command being run. New line characters are included. This allows for: - [A way to get current command inside pre_execution hook · Issue #6264 · nushell/nushell](https://github.com/nushell/nushell/issues/6264) - The possibility of *Atuin* to work work *Nushell*. *Atuin* hooks need to know the current repl input before it is run. # User-Facing Changes # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
This commit is contained in:
parent
0903a891e4
commit
b2a557d4ed
9 changed files with 244 additions and 57 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2746,6 +2746,7 @@ dependencies = [
|
||||||
"nu-test-support",
|
"nu-test-support",
|
||||||
"nu-utils",
|
"nu-utils",
|
||||||
"shadow-rs",
|
"shadow-rs",
|
||||||
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -14,7 +14,7 @@ use nu_parser::{lex, parse, trim_quotes_str};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::PathMember,
|
ast::PathMember,
|
||||||
config::NuCursorShape,
|
config::NuCursorShape,
|
||||||
engine::{EngineState, ReplOperation, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
|
format_duration, BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span,
|
||||||
Spanned, Type, Value, VarId,
|
Spanned, Type, Value, VarId,
|
||||||
};
|
};
|
||||||
|
@ -459,18 +459,30 @@ pub fn evaluate_repl(
|
||||||
.into_diagnostic()?; // todo: don't stop repl if error here?
|
.into_diagnostic()?; // todo: don't stop repl if error here?
|
||||||
}
|
}
|
||||||
|
|
||||||
engine_state
|
|
||||||
.repl_buffer_state
|
|
||||||
.lock()
|
|
||||||
.expect("repl buffer state mutex")
|
|
||||||
.replace(line_editor.current_buffer_contents().to_string());
|
|
||||||
|
|
||||||
// Right before we start running the code the user gave us,
|
// Right before we start running the code the user gave us,
|
||||||
// fire the "pre_execution" hook
|
// fire the "pre_execution" hook
|
||||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||||
|
// Set the REPL buffer to the current command for the "pre_execution" hook
|
||||||
|
let mut repl_buffer = engine_state
|
||||||
|
.repl_buffer_state
|
||||||
|
.lock()
|
||||||
|
.expect("repl buffer state mutex");
|
||||||
|
let next_repl_buffer = repl_buffer.to_string();
|
||||||
|
*repl_buffer = s.to_string();
|
||||||
|
drop(repl_buffer);
|
||||||
|
|
||||||
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook) {
|
if let Err(err) = eval_hook(engine_state, stack, None, vec![], &hook) {
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore the REPL buffer state for the next command. It could've been edited
|
||||||
|
// by `commandline`.
|
||||||
|
let mut repl_buffer = engine_state
|
||||||
|
.repl_buffer_state
|
||||||
|
.lock()
|
||||||
|
.expect("repl buffer state mutex");
|
||||||
|
*repl_buffer = next_repl_buffer;
|
||||||
|
drop(repl_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if shell_integration {
|
if shell_integration {
|
||||||
|
@ -628,23 +640,23 @@ pub fn evaluate_repl(
|
||||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ops = engine_state
|
let mut repl_buffer = engine_state
|
||||||
.repl_operation_queue
|
.repl_buffer_state
|
||||||
.lock()
|
.lock()
|
||||||
.expect("repl op queue mutex");
|
.expect("repl buffer state mutex");
|
||||||
while let Some(op) = ops.pop_front() {
|
let mut repl_cursor_pos = engine_state
|
||||||
match op {
|
.repl_cursor_pos
|
||||||
ReplOperation::Append(s) => line_editor.run_edit_commands(&[
|
.lock()
|
||||||
EditCommand::MoveToEnd,
|
.expect("repl cursor pos mutex");
|
||||||
EditCommand::InsertString(s),
|
line_editor.run_edit_commands(&[
|
||||||
]),
|
EditCommand::Clear,
|
||||||
ReplOperation::Insert(s) => {
|
EditCommand::InsertString(repl_buffer.to_string()),
|
||||||
line_editor.run_edit_commands(&[EditCommand::InsertString(s)])
|
EditCommand::MoveToPosition(*repl_cursor_pos),
|
||||||
}
|
]);
|
||||||
ReplOperation::Replace(s) => line_editor
|
*repl_buffer = "".to_string();
|
||||||
.run_edit_commands(&[EditCommand::Clear, EditCommand::InsertString(s)]),
|
drop(repl_buffer);
|
||||||
}
|
*repl_cursor_pos = 0;
|
||||||
}
|
drop(repl_cursor_pos);
|
||||||
}
|
}
|
||||||
Ok(Signal::CtrlC) => {
|
Ok(Signal::CtrlC) => {
|
||||||
// `Reedline` clears the line content. New prompt is shown
|
// `Reedline` clears the line content. New prompt is shown
|
||||||
|
|
|
@ -24,6 +24,7 @@ fancy-regex = "0.11.0"
|
||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
shadow-rs = { version = "0.21.0", default-features = false }
|
shadow-rs = { version = "0.21.0", default-features = false }
|
||||||
|
unicode-segmentation = "1.10.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = { version = "0.21.0", default-features = false }
|
shadow-rs = { version = "0.21.0", default-features = false }
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::ReplOperation;
|
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::Category;
|
use nu_protocol::Category;
|
||||||
use nu_protocol::IntoPipelineData;
|
use nu_protocol::IntoPipelineData;
|
||||||
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Type, Value};
|
use nu_protocol::{PipelineData, ShellError, Signature, SyntaxShape, Type, Value};
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Commandline;
|
pub struct Commandline;
|
||||||
|
@ -17,6 +17,11 @@ impl Command for Commandline {
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("commandline")
|
Signature::build("commandline")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.switch(
|
||||||
|
"cursor",
|
||||||
|
"Set or get the current cursor position",
|
||||||
|
Some('c'),
|
||||||
|
)
|
||||||
.switch(
|
.switch(
|
||||||
"append",
|
"append",
|
||||||
"appends the string to the end of the buffer",
|
"appends the string to the end of the buffer",
|
||||||
|
@ -56,30 +61,77 @@ impl Command for Commandline {
|
||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
|
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
|
||||||
let mut ops = engine_state
|
let mut buffer = engine_state
|
||||||
.repl_operation_queue
|
.repl_buffer_state
|
||||||
.lock()
|
.lock()
|
||||||
.expect("repl op queue mutex");
|
.expect("repl buffer state mutex");
|
||||||
ops.push_back(if call.has_flag("append") {
|
let mut cursor_pos = engine_state
|
||||||
ReplOperation::Append(cmd.as_string()?)
|
.repl_cursor_pos
|
||||||
|
.lock()
|
||||||
|
.expect("repl cursor pos mutex");
|
||||||
|
|
||||||
|
if call.has_flag("cursor") {
|
||||||
|
let cmd_str = cmd.as_string()?;
|
||||||
|
match cmd_str.parse::<i64>() {
|
||||||
|
Ok(n) => {
|
||||||
|
*cursor_pos = if n <= 0 {
|
||||||
|
0usize
|
||||||
|
} else {
|
||||||
|
buffer
|
||||||
|
.grapheme_indices(true)
|
||||||
|
.map(|(i, _c)| i)
|
||||||
|
.nth(n as usize)
|
||||||
|
.unwrap_or(buffer.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
return Err(ShellError::CantConvert {
|
||||||
|
to_type: "int".to_string(),
|
||||||
|
from_type: "string".to_string(),
|
||||||
|
span: cmd.span()?,
|
||||||
|
help: Some(format!(
|
||||||
|
r#"string "{cmd_str}" does not represent a valid integer"#
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if call.has_flag("append") {
|
||||||
|
buffer.push_str(&cmd.as_string()?);
|
||||||
} else if call.has_flag("insert") {
|
} else if call.has_flag("insert") {
|
||||||
ReplOperation::Insert(cmd.as_string()?)
|
let cmd_str = cmd.as_string()?;
|
||||||
|
buffer.insert_str(*cursor_pos, &cmd_str);
|
||||||
|
*cursor_pos += cmd_str.len();
|
||||||
} else {
|
} else {
|
||||||
ReplOperation::Replace(cmd.as_string()?)
|
*buffer = cmd.as_string()?;
|
||||||
});
|
*cursor_pos = buffer.len();
|
||||||
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
|
||||||
} else if let Some(ref cmd) = *engine_state
|
|
||||||
.repl_buffer_state
|
|
||||||
.lock()
|
|
||||||
.expect("repl buffer state mutex")
|
|
||||||
{
|
|
||||||
Ok(Value::String {
|
|
||||||
val: cmd.clone(),
|
|
||||||
span: call.head,
|
|
||||||
}
|
}
|
||||||
.into_pipeline_data())
|
|
||||||
} else {
|
|
||||||
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
Ok(Value::Nothing { span: call.head }.into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
let buffer = engine_state
|
||||||
|
.repl_buffer_state
|
||||||
|
.lock()
|
||||||
|
.expect("repl buffer state mutex");
|
||||||
|
if call.has_flag("cursor") {
|
||||||
|
let cursor_pos = engine_state
|
||||||
|
.repl_cursor_pos
|
||||||
|
.lock()
|
||||||
|
.expect("repl cursor pos mutex");
|
||||||
|
let char_pos = buffer
|
||||||
|
.grapheme_indices(true)
|
||||||
|
.position(|(i, _c)| i == *cursor_pos)
|
||||||
|
.unwrap_or(buffer.len());
|
||||||
|
Ok(Value::String {
|
||||||
|
val: char_pos.to_string(),
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
Ok(Value::String {
|
||||||
|
val: buffer.to_string(),
|
||||||
|
span: call.head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use std::num::NonZeroUsize;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet, VecDeque},
|
collections::{HashMap, HashSet},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, AtomicU32},
|
atomic::{AtomicBool, AtomicU32},
|
||||||
Arc, Mutex,
|
Arc, Mutex,
|
||||||
|
@ -22,15 +22,6 @@ use std::{
|
||||||
|
|
||||||
static PWD_ENV: &str = "PWD";
|
static PWD_ENV: &str = "PWD";
|
||||||
|
|
||||||
// TODO: move to different file? where?
|
|
||||||
/// An operation to be performed with the current buffer of the interactive shell.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum ReplOperation {
|
|
||||||
Append(String),
|
|
||||||
Insert(String),
|
|
||||||
Replace(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Organizes usage messages for various primitives
|
/// Organizes usage messages for various primitives
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Usage {
|
pub struct Usage {
|
||||||
|
@ -134,8 +125,9 @@ pub struct EngineState {
|
||||||
pub previous_env_vars: HashMap<String, Value>,
|
pub previous_env_vars: HashMap<String, Value>,
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
pub pipeline_externals_state: Arc<(AtomicU32, AtomicU32)>,
|
pub pipeline_externals_state: Arc<(AtomicU32, AtomicU32)>,
|
||||||
pub repl_buffer_state: Arc<Mutex<Option<String>>>,
|
pub repl_buffer_state: Arc<Mutex<String>>,
|
||||||
pub repl_operation_queue: Arc<Mutex<VecDeque<ReplOperation>>>,
|
// A byte position, as `EditCommand::MoveToPosition` is also a byte position
|
||||||
|
pub repl_cursor_pos: Arc<Mutex<usize>>,
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
pub plugin_signatures: Option<PathBuf>,
|
pub plugin_signatures: Option<PathBuf>,
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
|
@ -186,8 +178,8 @@ impl EngineState {
|
||||||
previous_env_vars: HashMap::new(),
|
previous_env_vars: HashMap::new(),
|
||||||
config: Config::default(),
|
config: Config::default(),
|
||||||
pipeline_externals_state: Arc::new((AtomicU32::new(0), AtomicU32::new(0))),
|
pipeline_externals_state: Arc::new((AtomicU32::new(0), AtomicU32::new(0))),
|
||||||
repl_buffer_state: Arc::new(Mutex::new(None)),
|
repl_buffer_state: Arc::new(Mutex::new("".to_string())),
|
||||||
repl_operation_queue: Arc::new(Mutex::new(VecDeque::new())),
|
repl_cursor_pos: Arc::new(Mutex::new(0)),
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
plugin_signatures: None,
|
plugin_signatures: None,
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
|
|
|
@ -209,6 +209,12 @@ pub fn nu_repl() {
|
||||||
|
|
||||||
// Check for pre_execution hook
|
// Check for pre_execution hook
|
||||||
let config = engine_state.get_config();
|
let config = engine_state.get_config();
|
||||||
|
|
||||||
|
*engine_state
|
||||||
|
.repl_buffer_state
|
||||||
|
.lock()
|
||||||
|
.expect("repl buffer state mutex") = line.to_string();
|
||||||
|
|
||||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||||
if let Err(err) = eval_hook(&mut engine_state, &mut stack, None, vec![], &hook) {
|
if let Err(err) = eval_hook(&mut engine_state, &mut stack, None, vec![], &hook) {
|
||||||
outcome_err(&engine_state, &err);
|
outcome_err(&engine_state, &err);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
mod test_bits;
|
mod test_bits;
|
||||||
mod test_cell_path;
|
mod test_cell_path;
|
||||||
|
mod test_commandline;
|
||||||
mod test_conditionals;
|
mod test_conditionals;
|
||||||
mod test_config_path;
|
mod test_config_path;
|
||||||
mod test_converters;
|
mod test_converters;
|
||||||
|
|
109
src/tests/test_commandline.rs
Normal file
109
src/tests/test_commandline.rs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
use crate::tests::{fail_test, run_test, TestResult};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn commandline_test_get_empty() -> TestResult {
|
||||||
|
run_test("commandline", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn commandline_test_append() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
"commandline --replace '0👩❤️👩2'\n\
|
||||||
|
commandline --cursor '2'\n\
|
||||||
|
commandline --append 'ab'\n\
|
||||||
|
commandline\n\
|
||||||
|
commandline --cursor",
|
||||||
|
"0👩❤️👩2ab\n\
|
||||||
|
2",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn commandline_test_insert() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
"commandline --replace '0👩❤️👩2'\n\
|
||||||
|
commandline --cursor '2'\n\
|
||||||
|
commandline --insert 'ab'\n\
|
||||||
|
commandline\n\
|
||||||
|
commandline --cursor",
|
||||||
|
"0👩❤️👩ab2\n\
|
||||||
|
4",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn commandline_test_replace() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
"commandline --replace '0👩❤️👩2'\n\
|
||||||
|
commandline --replace 'ab'\n\
|
||||||
|
commandline\n\
|
||||||
|
commandline --cursor",
|
||||||
|
"ab\n\
|
||||||
|
2",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn commandline_test_cursor() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
"commandline --replace '0👩❤️👩2'\n\
|
||||||
|
commandline --cursor '1' \n\
|
||||||
|
commandline --insert 'x'\n\
|
||||||
|
commandline",
|
||||||
|
"0x👩❤️👩2",
|
||||||
|
)?;
|
||||||
|
run_test(
|
||||||
|
"commandline --replace '0👩❤️👩2'\n\
|
||||||
|
commandline --cursor '2' \n\
|
||||||
|
commandline --insert 'x'\n\
|
||||||
|
commandline",
|
||||||
|
"0👩❤️👩x2",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn commandline_test_cursor_show_pos() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
"commandline --replace '0👩❤️👩2'\n\
|
||||||
|
commandline --cursor '1' \n\
|
||||||
|
commandline --cursor",
|
||||||
|
"1",
|
||||||
|
)?;
|
||||||
|
run_test(
|
||||||
|
"commandline --replace '0👩❤️👩2'\n\
|
||||||
|
commandline --cursor '2' \n\
|
||||||
|
commandline --cursor",
|
||||||
|
"2",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn commandline_test_cursor_too_small() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
"commandline --replace '123456'\n\
|
||||||
|
commandline --cursor '-1' \n\
|
||||||
|
commandline --insert '0'\n\
|
||||||
|
commandline",
|
||||||
|
"0123456",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn commandline_test_cursor_too_large() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
"commandline --replace '123456'\n\
|
||||||
|
commandline --cursor '10' \n\
|
||||||
|
commandline --insert '0'\n\
|
||||||
|
commandline",
|
||||||
|
"1234560",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn commandline_test_cursor_invalid() -> TestResult {
|
||||||
|
fail_test(
|
||||||
|
"commandline --replace '123456'\n\
|
||||||
|
commandline --cursor 'abc'",
|
||||||
|
r#"string "abc" does not represent a valid integer"#,
|
||||||
|
)
|
||||||
|
}
|
|
@ -325,6 +325,19 @@ fn pre_execution_block_preserve_env_var() {
|
||||||
assert_eq!(actual_repl.out, "spam");
|
assert_eq!(actual_repl.out, "spam");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_execution_commandline() {
|
||||||
|
let inp = &[
|
||||||
|
&pre_execution_hook_code(r#"{ let-env repl_commandline = (commandline) }"#),
|
||||||
|
"echo foo!; $env.repl_commandline",
|
||||||
|
];
|
||||||
|
|
||||||
|
let actual_repl = nu!(cwd: "tests/hooks", nu_repl_code(inp));
|
||||||
|
|
||||||
|
assert_eq!(actual_repl.err, "");
|
||||||
|
assert_eq!(actual_repl.out, "foo!echo foo!; $env.repl_commandline");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn env_change_shadow_command() {
|
fn env_change_shadow_command() {
|
||||||
let inp = &[
|
let inp = &[
|
||||||
|
|
Loading…
Reference in a new issue