diff --git a/crates/nu-cli/src/commands/commandline.rs b/crates/nu-cli/src/commands/commandline/commandline_.rs similarity index 57% rename from crates/nu-cli/src/commands/commandline.rs rename to crates/nu-cli/src/commands/commandline/commandline_.rs index 973fe72315..fd0fc2bac1 100644 --- a/crates/nu-cli/src/commands/commandline.rs +++ b/crates/nu-cli/src/commands/commandline/commandline_.rs @@ -19,7 +19,6 @@ impl Command for Commandline { .input_output_types(vec![ (Type::Nothing, Type::Nothing), (Type::String, Type::String), - (Type::String, Type::Int), ]) .switch( "cursor", @@ -75,6 +74,17 @@ impl Command for Commandline { let mut repl = engine_state.repl_state.lock().expect("repl state mutex"); if call.has_flag(engine_state, stack, "cursor")? { + nu_protocol::report_error_new( + engine_state, + &ShellError::GenericError { + error: "`--cursor (-c)` is deprecated".into(), + msg: "Setting the current cursor position by `--cursor (-c)` is deprecated" + .into(), + span: Some(call.arguments_span()), + help: Some("Use `commandline set-cursor`".into()), + inner: vec![], + }, + ); match cmd.parse::() { Ok(n) => { repl.cursor_pos = if n <= 0 { @@ -97,12 +107,42 @@ impl Command for Commandline { } } } else if call.has_flag(engine_state, stack, "append")? { + nu_protocol::report_error_new( + engine_state, + &ShellError::GenericError { + error: "`--append (-a)` is deprecated".into(), + msg: "Appending the string to the end of the buffer by `--append (-a)` is deprecated".into(), + span: Some(call.arguments_span()), + help: Some("Use `commandline edit --append (-a)`".into()), + inner: vec![], + }, + ); repl.buffer.push_str(&cmd); } else if call.has_flag(engine_state, stack, "insert")? { + nu_protocol::report_error_new( + engine_state, + &ShellError::GenericError { + error: "`--insert (-i)` is deprecated".into(), + msg: "Inserts the string into the buffer at the cursor position by `--insert (-i)` is deprecated".into(), + span: Some(call.arguments_span()), + help: Some("Use `commandline edit --insert (-i)`".into()), + inner: vec![], + }, + ); let cursor_pos = repl.cursor_pos; repl.buffer.insert_str(cursor_pos, &cmd); repl.cursor_pos += cmd.len(); } else { + nu_protocol::report_error_new( + engine_state, + &ShellError::GenericError { + error: "`--replace (-r)` is deprecated".into(), + msg: "Replaceing the current contents of the buffer by `--replace (-p)` or positional argument is deprecated".into(), + span: Some(call.arguments_span()), + help: Some("Use `commandline edit --replace (-r)`".into()), + inner: vec![], + }, + ); repl.buffer = cmd; repl.cursor_pos = repl.buffer.len(); } @@ -110,25 +150,37 @@ impl Command for Commandline { } else { let mut repl = engine_state.repl_state.lock().expect("repl state mutex"); if call.has_flag(engine_state, stack, "cursor-end")? { + nu_protocol::report_error_new( + engine_state, + &ShellError::GenericError { + error: "`--cursor-end (-e)` is deprecated".into(), + msg: "Setting the current cursor position to the end of the buffer by `--cursor-end (-e)` is deprecated".into(), + span: Some(call.arguments_span()), + help: Some("Use `commandline set-cursor --end (-e)`".into()), + inner: vec![], + }, + ); repl.cursor_pos = repl.buffer.len(); Ok(Value::nothing(call.head).into_pipeline_data()) } else if call.has_flag(engine_state, stack, "cursor")? { + nu_protocol::report_error_new( + engine_state, + &ShellError::GenericError { + error: "`--cursor (-c)` is deprecated".into(), + msg: "Getting the current cursor position by `--cursor (-c)` is deprecated" + .into(), + span: Some(call.arguments_span()), + help: Some("Use `commandline get-cursor`".into()), + inner: vec![], + }, + ); let char_pos = repl .buffer .grapheme_indices(true) .chain(std::iter::once((repl.buffer.len(), ""))) .position(|(i, _c)| i == repl.cursor_pos) .expect("Cursor position isn't on a grapheme boundary"); - match i64::try_from(char_pos) { - Ok(pos) => Ok(Value::int(pos, call.head).into_pipeline_data()), - Err(e) => Err(ShellError::GenericError { - error: "Failed to convert cursor position to int".to_string(), - msg: e.to_string(), - span: None, - help: None, - inner: vec![], - }), - } + Ok(Value::string(char_pos.to_string(), call.head).into_pipeline_data()) } else { Ok(Value::string(repl.buffer.to_string(), call.head).into_pipeline_data()) } diff --git a/crates/nu-cli/src/commands/commandline/edit.rs b/crates/nu-cli/src/commands/commandline/edit.rs new file mode 100644 index 0000000000..85aebab164 --- /dev/null +++ b/crates/nu-cli/src/commands/commandline/edit.rs @@ -0,0 +1,71 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, +}; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "commandline edit" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .input_output_types(vec![(Type::Nothing, Type::Nothing)]) + .switch( + "append", + "appends the string to the end of the buffer", + Some('a'), + ) + .switch( + "insert", + "inserts the string into the buffer at the cursor position", + Some('i'), + ) + .switch( + "replace", + "replaces the current contents of the buffer (default)", + Some('r'), + ) + .required( + "str", + SyntaxShape::String, + "the string to perform the operation with", + ) + .category(Category::Core) + } + + fn usage(&self) -> &str { + "Modify the current command line input buffer." + } + + fn search_terms(&self) -> Vec<&str> { + vec!["repl", "interactive"] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let str: String = call.req(engine_state, stack, 0)?; + let mut repl = engine_state.repl_state.lock().expect("repl state mutex"); + if call.has_flag(engine_state, stack, "append")? { + repl.buffer.push_str(&str); + } else if call.has_flag(engine_state, stack, "insert")? { + let cursor_pos = repl.cursor_pos; + repl.buffer.insert_str(cursor_pos, &str); + repl.cursor_pos += str.len(); + } else { + repl.buffer = str; + repl.cursor_pos = repl.buffer.len(); + } + Ok(Value::nothing(call.head).into_pipeline_data()) + } +} diff --git a/crates/nu-cli/src/commands/commandline/get_cursor.rs b/crates/nu-cli/src/commands/commandline/get_cursor.rs new file mode 100644 index 0000000000..e0458bc031 --- /dev/null +++ b/crates/nu-cli/src/commands/commandline/get_cursor.rs @@ -0,0 +1,56 @@ +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value, +}; +use unicode_segmentation::UnicodeSegmentation; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "commandline get-cursor" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .input_output_types(vec![(Type::Nothing, Type::Int)]) + .allow_variants_without_examples(true) + .category(Category::Core) + } + + fn usage(&self) -> &str { + "Get the current cursor position." + } + + fn search_terms(&self) -> Vec<&str> { + vec!["repl", "interactive"] + } + + fn run( + &self, + engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let repl = engine_state.repl_state.lock().expect("repl state mutex"); + let char_pos = repl + .buffer + .grapheme_indices(true) + .chain(std::iter::once((repl.buffer.len(), ""))) + .position(|(i, _c)| i == repl.cursor_pos) + .expect("Cursor position isn't on a grapheme boundary"); + match i64::try_from(char_pos) { + Ok(pos) => Ok(Value::int(pos, call.head).into_pipeline_data()), + Err(e) => Err(ShellError::GenericError { + error: "Failed to convert cursor position to int".to_string(), + msg: e.to_string(), + span: None, + help: None, + inner: vec![], + }), + } + } +} diff --git a/crates/nu-cli/src/commands/commandline/mod.rs b/crates/nu-cli/src/commands/commandline/mod.rs new file mode 100644 index 0000000000..d3991c0fa5 --- /dev/null +++ b/crates/nu-cli/src/commands/commandline/mod.rs @@ -0,0 +1,9 @@ +mod commandline_; +mod edit; +mod get_cursor; +mod set_cursor; + +pub use commandline_::Commandline; +pub use edit::SubCommand as CommandlineEdit; +pub use get_cursor::SubCommand as CommandlineGetCursor; +pub use set_cursor::SubCommand as CommandlineSetCursor; diff --git a/crates/nu-cli/src/commands/commandline/set_cursor.rs b/crates/nu-cli/src/commands/commandline/set_cursor.rs new file mode 100644 index 0000000000..bff05a6834 --- /dev/null +++ b/crates/nu-cli/src/commands/commandline/set_cursor.rs @@ -0,0 +1,69 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, +}; +use unicode_segmentation::UnicodeSegmentation; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "commandline set-cursor" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .input_output_types(vec![(Type::Nothing, Type::Nothing)]) + .switch( + "end", + "set the current cursor position to the end of the buffer", + Some('e'), + ) + .optional("pos", SyntaxShape::Int, "Cursor position to be set") + .category(Category::Core) + } + + fn usage(&self) -> &str { + "Set the current cursor position." + } + + fn search_terms(&self) -> Vec<&str> { + vec!["repl", "interactive"] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let mut repl = engine_state.repl_state.lock().expect("repl state mutex"); + if let Some(pos) = call.opt::(engine_state, stack, 0)? { + repl.cursor_pos = if pos <= 0 { + 0usize + } else { + repl.buffer + .grapheme_indices(true) + .map(|(i, _c)| i) + .nth(pos as usize) + .unwrap_or(repl.buffer.len()) + }; + Ok(Value::nothing(call.head).into_pipeline_data()) + } else if call.has_flag(engine_state, stack, "end")? { + repl.cursor_pos = repl.buffer.len(); + Ok(Value::nothing(call.head).into_pipeline_data()) + } else { + Err(ShellError::GenericError { + error: "Required a positional argument or a flag".to_string(), + msg: "".to_string(), + span: None, + help: None, + inner: vec![], + }) + } + } +} diff --git a/crates/nu-cli/src/commands/default_context.rs b/crates/nu-cli/src/commands/default_context.rs index 9f3f35fecb..cd5025fdc7 100644 --- a/crates/nu-cli/src/commands/default_context.rs +++ b/crates/nu-cli/src/commands/default_context.rs @@ -14,6 +14,9 @@ pub fn add_cli_context(mut engine_state: EngineState) -> EngineState { bind_command! { Commandline, + CommandlineEdit, + CommandlineGetCursor, + CommandlineSetCursor, History, HistorySession, Keybindings, diff --git a/crates/nu-cli/src/commands/mod.rs b/crates/nu-cli/src/commands/mod.rs index 6089d79293..f63724e95f 100644 --- a/crates/nu-cli/src/commands/mod.rs +++ b/crates/nu-cli/src/commands/mod.rs @@ -6,7 +6,7 @@ mod keybindings_default; mod keybindings_list; mod keybindings_listen; -pub use commandline::Commandline; +pub use commandline::{Commandline, CommandlineEdit, CommandlineGetCursor, CommandlineSetCursor}; pub use history::{History, HistorySession}; pub use keybindings::Keybindings; pub use keybindings_default::KeybindingsDefault; diff --git a/src/tests/test_commandline.rs b/src/tests/test_commandline.rs index d618ba68b5..cab1d18e80 100644 --- a/src/tests/test_commandline.rs +++ b/src/tests/test_commandline.rs @@ -1,4 +1,5 @@ use crate::tests::{fail_test, run_test, TestResult}; +use nu_test_support::nu; #[test] fn commandline_test_get_empty() -> TestResult { @@ -7,6 +8,142 @@ fn commandline_test_get_empty() -> TestResult { #[test] fn commandline_test_append() -> TestResult { + run_test( + "commandline edit --replace '0👩‍❤️‍👩2'\n\ + commandline set-cursor 2\n\ + commandline edit --append 'ab'\n\ + print (commandline)\n\ + commandline get-cursor", + "0👩‍❤️‍👩2ab\n\ + 2", + ) +} + +#[test] +fn commandline_test_insert() -> TestResult { + run_test( + "commandline edit --replace '0👩‍❤️‍👩2'\n\ + commandline set-cursor 2\n\ + commandline edit --insert 'ab'\n\ + print (commandline)\n\ + commandline get-cursor", + "0👩‍❤️‍👩ab2\n\ + 4", + ) +} + +#[test] +fn commandline_test_replace() -> TestResult { + run_test( + "commandline edit --replace '0👩‍❤️‍👩2'\n\ + commandline edit --replace 'ab'\n\ + print (commandline)\n\ + commandline get-cursor", + "ab\n\ + 2", + ) +} + +#[test] +fn commandline_test_cursor() -> TestResult { + run_test( + "commandline edit --replace '0👩‍❤️‍👩2'\n\ + commandline set-cursor 1\n\ + commandline edit --insert 'x'\n\ + commandline", + "0x👩‍❤️‍👩2", + )?; + run_test( + "commandline edit --replace '0👩‍❤️‍👩2'\n\ + commandline set-cursor 2\n\ + commandline edit --insert 'x'\n\ + commandline", + "0👩‍❤️‍👩x2", + ) +} + +#[test] +fn commandline_test_cursor_show_pos_begin() -> TestResult { + run_test( + "commandline edit --replace '0👩‍❤️‍👩'\n\ + commandline set-cursor 0\n\ + commandline get-cursor", + "0", + ) +} + +#[test] +fn commandline_test_cursor_show_pos_end() -> TestResult { + run_test( + "commandline edit --replace '0👩‍❤️‍👩'\n\ + commandline set-cursor 2\n\ + commandline get-cursor", + "2", + ) +} + +#[test] +fn commandline_test_cursor_show_pos_mid() -> TestResult { + run_test( + "commandline edit --replace '0👩‍❤️‍👩2'\n\ + commandline set-cursor 1\n\ + commandline get-cursor", + "1", + )?; + run_test( + "commandline edit --replace '0👩‍❤️‍👩2'\n\ + commandline set-cursor 2\n\ + commandline get-cursor", + "2", + ) +} + +#[test] +fn commandline_test_cursor_too_small() -> TestResult { + run_test( + "commandline edit --replace '123456'\n\ + commandline set-cursor -1\n\ + commandline edit --insert '0'\n\ + commandline", + "0123456", + ) +} + +#[test] +fn commandline_test_cursor_too_large() -> TestResult { + run_test( + "commandline edit --replace '123456'\n\ + commandline set-cursor 10\n\ + commandline edit --insert '0'\n\ + commandline", + "1234560", + ) +} + +#[test] +fn commandline_test_cursor_invalid() -> TestResult { + fail_test( + "commandline edit --replace '123456'\n\ + commandline set-cursor 'abc'", + "expected int", + ) +} + +#[test] +fn commandline_test_cursor_end() -> TestResult { + run_test( + "commandline edit --insert '🤔🤔'; commandline set-cursor --end; commandline get-cursor", + "2", // 2 graphemes + ) +} + +#[test] +fn commandline_test_cursor_type() -> TestResult { + run_test("commandline get-cursor | describe", "int") +} + +#[test] +fn deprecated_commandline_test_append() -> TestResult { run_test( "commandline --replace '0👩‍❤️‍👩2'\n\ commandline --cursor '2'\n\ @@ -19,7 +156,7 @@ fn commandline_test_append() -> TestResult { } #[test] -fn commandline_test_insert() -> TestResult { +fn deprecated_commandline_test_insert() -> TestResult { run_test( "commandline --replace '0👩‍❤️‍👩2'\n\ commandline --cursor '2'\n\ @@ -32,7 +169,7 @@ fn commandline_test_insert() -> TestResult { } #[test] -fn commandline_test_replace() -> TestResult { +fn deprecated_commandline_test_replace() -> TestResult { run_test( "commandline --replace '0👩‍❤️‍👩2'\n\ commandline --replace 'ab'\n\ @@ -44,7 +181,7 @@ fn commandline_test_replace() -> TestResult { } #[test] -fn commandline_test_cursor() -> TestResult { +fn deprecated_commandline_test_cursor() -> TestResult { run_test( "commandline --replace '0👩‍❤️‍👩2'\n\ commandline --cursor '1'\n\ @@ -62,7 +199,7 @@ fn commandline_test_cursor() -> TestResult { } #[test] -fn commandline_test_cursor_show_pos_begin() -> TestResult { +fn deprecated_commandline_test_cursor_show_pos_begin() -> TestResult { run_test( "commandline --replace '0👩‍❤️‍👩'\n\ commandline --cursor '0'\n\ @@ -72,7 +209,7 @@ fn commandline_test_cursor_show_pos_begin() -> TestResult { } #[test] -fn commandline_test_cursor_show_pos_end() -> TestResult { +fn deprecated_commandline_test_cursor_show_pos_end() -> TestResult { run_test( "commandline --replace '0👩‍❤️‍👩'\n\ commandline --cursor '2'\n\ @@ -82,7 +219,7 @@ fn commandline_test_cursor_show_pos_end() -> TestResult { } #[test] -fn commandline_test_cursor_show_pos_mid() -> TestResult { +fn deprecated_commandline_test_cursor_show_pos_mid() -> TestResult { run_test( "commandline --replace '0👩‍❤️‍👩2'\n\ commandline --cursor '1'\n\ @@ -98,7 +235,7 @@ fn commandline_test_cursor_show_pos_mid() -> TestResult { } #[test] -fn commandline_test_cursor_too_small() -> TestResult { +fn deprecated_commandline_test_cursor_too_small() -> TestResult { run_test( "commandline --replace '123456'\n\ commandline --cursor '-1'\n\ @@ -109,7 +246,7 @@ fn commandline_test_cursor_too_small() -> TestResult { } #[test] -fn commandline_test_cursor_too_large() -> TestResult { +fn deprecated_commandline_test_cursor_too_large() -> TestResult { run_test( "commandline --replace '123456'\n\ commandline --cursor '10'\n\ @@ -120,7 +257,7 @@ fn commandline_test_cursor_too_large() -> TestResult { } #[test] -fn commandline_test_cursor_invalid() -> TestResult { +fn deprecated_commandline_test_cursor_invalid() -> TestResult { fail_test( "commandline --replace '123456'\n\ commandline --cursor 'abc'", @@ -129,7 +266,7 @@ fn commandline_test_cursor_invalid() -> TestResult { } #[test] -fn commandline_test_cursor_end() -> TestResult { +fn deprecated_commandline_test_cursor_end() -> TestResult { run_test( "commandline --insert '🤔🤔'; commandline --cursor-end; commandline --cursor", "2", // 2 graphemes @@ -137,6 +274,43 @@ fn commandline_test_cursor_end() -> TestResult { } #[test] -fn commandline_test_cursor_type() -> TestResult { - run_test("commandline --cursor | describe", "int") +fn deprecated_commandline_flag_cursor_get() { + let actual = nu!("commandline --cursor"); + assert!(actual.err.contains("deprecated")); +} + +#[test] +fn deprecated_commandline_flag_cursor_set() { + let actual = nu!("commandline -c 0"); + assert!(actual.err.contains("deprecated")); +} + +#[test] +fn deprecated_commandline_flag_cursor_end() { + let actual = nu!("commandline --cursor-end"); + assert!(actual.err.contains("deprecated")); +} + +#[test] +fn deprecated_commandline_flag_append() { + let actual = nu!("commandline --append 'abc'"); + assert!(actual.err.contains("deprecated")); +} + +#[test] +fn deprecated_commandline_flag_insert() { + let actual = nu!("commandline --insert 'abc'"); + assert!(actual.err.contains("deprecated")); +} + +#[test] +fn deprecated_commandline_flag_replace() { + let actual = nu!("commandline --replace 'abc'"); + assert!(actual.err.contains("deprecated")); +} + +#[test] +fn deprecated_commandline_replace_current_buffer() { + let actual = nu!("commandline 'abc'"); + assert!(actual.err.contains("deprecated")); }