feat: commandline edit --accept to instantly execute command (#16193)

# Description

Fixes 
- #11065

Revised 
- #15092

Depends on

- https://github.com/nushell/reedline/pull/933

Adds the `--accept` flag to the `commandline` command, to immediately
accept the input.

Example use case: atuin

# User-facing changes

Users get the ability to pass `--accept` or `-A` to `commandline edit`
in order to immediately execute the resulting commandline.

# Tests + Formatting

I added two test cases that execute `commandline edit -A`.
There is also some documentation about their unintuitive expectations
output.

# After Submitting

The [docs](https://www.nushell.sh/commands/docs/commandline_edit.html)
can be updated to the new flag:

--accept, -A: immediately execute the command (no additional return
required)

> [!NOTE]
>
> This PR will be revised if / when
https://github.com/nushell/reedline/pull/933 is accepted

# Demo


[![asciicast](https://asciinema.org/a/Ql4r1oWu8J0C4MecmIZjxw5Pg.svg)](https://asciinema.org/a/Ql4r1oWu8J0C4MecmIZjxw5Pg)
This commit is contained in:
Stuart Carnie
2025-08-18 06:24:17 +10:00
committed by GitHub
parent a40f6d5cba
commit fd4a04211a
4 changed files with 33 additions and 3 deletions

View File

@@ -26,6 +26,11 @@ impl Command for CommandlineEdit {
"replaces the current contents of the buffer (default)",
Some('r'),
)
.switch(
"accept",
"immediately executes the result after edit",
Some('A'),
)
.required(
"str",
SyntaxShape::String,
@@ -61,6 +66,9 @@ impl Command for CommandlineEdit {
repl.buffer = str;
repl.cursor_pos = repl.buffer.len();
}
repl.accept = call.has_flag(engine_state, stack, "accept")?;
Ok(Value::nothing(call.head).into_pipeline_data())
}
}

View File

@@ -491,7 +491,9 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
// CLEAR STACK-REFERENCE 1
.with_highlighter(Box::<NoOpHighlighter>::default())
// CLEAR STACK-REFERENCE 2
.with_completer(Box::<DefaultCompleter>::default());
.with_completer(Box::<DefaultCompleter>::default())
// Ensure immediately accept is always cleared
.with_immediately_accept(false);
// Let's grab the shell_integration configs
let shell_integration_osc2 = config.shell_integration.osc2;
@@ -671,7 +673,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
run_shell_integration_reset_application_mode();
}
flush_engine_state_repl_buffer(engine_state, &mut line_editor);
line_editor = flush_engine_state_repl_buffer(engine_state, line_editor);
}
Ok(Signal::CtrlC) => {
// `Reedline` clears the line content. New prompt is shown
@@ -1126,7 +1128,10 @@ fn run_shell_integration_reset_application_mode() {
///
/// Clear the screen and output anything remaining in the EngineState buffer.
///
fn flush_engine_state_repl_buffer(engine_state: &mut EngineState, line_editor: &mut Reedline) {
fn flush_engine_state_repl_buffer(
engine_state: &mut EngineState,
mut line_editor: Reedline,
) -> Reedline {
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
line_editor.run_edit_commands(&[
EditCommand::Clear,
@@ -1136,8 +1141,13 @@ fn flush_engine_state_repl_buffer(engine_state: &mut EngineState, line_editor: &
select: false,
},
]);
if repl.accept {
line_editor = line_editor.with_immediately_accept(true)
}
repl.accept = false;
repl.buffer = "".to_string();
repl.cursor_pos = 0;
line_editor
}
///

View File

@@ -46,6 +46,8 @@ pub struct ReplState {
pub buffer: String,
// A byte position, as `EditCommand::MoveToPosition` is also a byte position
pub cursor_pos: usize,
/// Immediately accept the buffer on the next loop.
pub accept: bool,
}
pub struct IsDebugging(AtomicBool);
@@ -185,6 +187,7 @@ impl EngineState {
repl_state: Arc::new(Mutex::new(ReplState {
buffer: "".to_string(),
cursor_pos: 0,
accept: false,
})),
table_decl_id: None,
#[cfg(feature = "plugin")]
@@ -1078,6 +1081,7 @@ impl EngineState {
self.repl_state = Arc::new(Mutex::new(ReplState {
buffer: "".to_string(),
cursor_pos: 0,
accept: false,
}));
}
if Mutex::is_poisoned(&self.jobs) {

View File

@@ -140,3 +140,11 @@ fn commandline_test_cursor_end() -> TestResult {
fn commandline_test_cursor_type() -> TestResult {
run_test("commandline get-cursor | describe", "int")
}
#[test]
fn commandline_test_accepted_command() -> TestResult {
run_test(
"commandline edit --accept \"print accepted\"\n | commandline",
"print accepted",
)
}