From a583fe723059a254d0d0d8ea90dd146def1f1543 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Mon, 8 Apr 2024 23:24:24 +0200 Subject: [PATCH] "commandline -f foo" to skip queue and execute immediately Commit c3cd68dda (Process shell commands from bindings like regular char events, 2024-03-02) mentions a "weird ordering difference". The issue is that "commandline -f foo" goes through the input queue while other commands are executed directly. For example bind ctrl-g "commandline -f end-of-line; commandline -i x" is executed in the wrong order. Fix that. This doesn't yet work for "commandline -f exit" but that can be fixed easily. It's hard to imagine anyone would rely on the existing behavior. "commandline -f" in bindings is mostly used for repainting the commandline. --- CHANGELOG.rst | 2 ++ src/builtins/commandline.rs | 18 +++++++++++------- src/reader.rs | 10 +++++++--- tests/pexpects/bind.py | 14 ++++++++++++++ 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 06d32072c..57e0272ce 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -107,6 +107,8 @@ Interactive improvements New or improved bindings ^^^^^^^^^^^^^^^^^^^^^^^^ - Bindings can now mix special input functions and shell commands, so ``bind ctrl-g expand-abbr "commandline -i \n"`` works as expected (:issue:`8186`). +- Special input functions run from bindings via ``commandline -f`` are now applied immediately instead of after the currently executing binding. + For example, ``commandline -f yank -f yank-pop`` inserts the last-but-one entry from the kill ring. - When the cursor is on a command that resolves to an executable script, :kbd:`Alt-O` will now open that script in your editor (:issue:`10266`). - Two improvements to the :kbd:`Alt-E` binding which edits the commandline in an external editor: - The editor's cursor position is copied back to fish. This is currently supported for Vim and Kakoune. diff --git a/src/builtins/commandline.rs b/src/builtins/commandline.rs index 8e3ca3395..4035672d8 100644 --- a/src/builtins/commandline.rs +++ b/src/builtins/commandline.rs @@ -12,7 +12,8 @@ use crate::parse_util::{ }; use crate::proc::is_interactive_session; use crate::reader::{ - commandline_get_state, commandline_set_buffer, commandline_set_search_field, reader_queue_ch, + commandline_get_state, commandline_set_buffer, commandline_set_search_field, + reader_execute_readline_cmd, }; use crate::tokenizer::TOK_ACCEPT_UNFINISHED; use crate::tokenizer::{TokenType, Tokenizer}; @@ -298,8 +299,6 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) let positional_args = w.argv.len() - w.woptind; - let ld = parser.libdata(); - if function_mode { // Check for invalid switch combinations. if buffer_part.is_some() @@ -335,13 +334,13 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) // Don't enqueue a repaint if we're currently in the middle of one, // because that's an infinite loop. if matches!(cmd, rl::RepaintMode | rl::ForceRepaint | rl::Repaint) { - if ld.pods.is_repaint { + if parser.libdata().pods.is_repaint { continue; } } // Inserts the readline function at the back of the queue. - reader_queue_ch(CharEvent::from_readline(cmd)); + reader_execute_readline_cmd(CharEvent::from_readline(cmd)); } return STATUS_CMD_OK; @@ -473,8 +472,13 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) } else if let Some(override_buffer) = &override_buffer { current_buffer = override_buffer; current_cursor_pos = current_buffer.len(); - } else if !ld.transient_commandlines.is_empty() && !cursor_mode { - transient = ld.transient_commandlines.last().unwrap().clone(); + } else if !parser.libdata().transient_commandlines.is_empty() && !cursor_mode { + transient = parser + .libdata() + .transient_commandlines + .last() + .unwrap() + .clone(); current_buffer = &transient; current_cursor_pos = transient.len(); } else if rstate.initialized { diff --git a/src/reader.rs b/src/reader.rs index 7d88f55aa..6b0a52ac1 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -904,10 +904,14 @@ pub fn reader_schedule_prompt_repaint() { } } -/// Enqueue an event to the back of the reader's input queue. -pub fn reader_queue_ch(ch: CharEvent) { +pub fn reader_execute_readline_cmd(ch: CharEvent) { if let Some(data) = current_data() { - data.inputter.queue_char(ch); + if data.rls.is_none() { + data.rls = Some(ReadlineLoopState::new()); + } + data.apply_commandline_state_changes(); + data.handle_char_event(Some(ch)); + data.update_commandline_state(); } } diff --git a/tests/pexpects/bind.py b/tests/pexpects/bind.py index 3c8bbb5f3..c217d3de4 100644 --- a/tests/pexpects/bind.py +++ b/tests/pexpects/bind.py @@ -358,6 +358,20 @@ send('\x02\x02\x02') # ctrl-b, backward-char sendline('\x1bu') # alt+u, upcase word expect_prompt("fooBAR") +send(""" + bind ctrl-g " + commandline --insert 'echo foo ar' + commandline -f backward-word + commandline --insert b + commandline -f backward-char + commandline -f backward-char + commandline -f delete-char + " +""") +send('\x07') # ctrl-g +send('\r') +expect_prompt("foobar") + # Check that the builtin version of `exit` works # (for obvious reasons this MUST BE LAST) sendline("function myexit; echo exit; exit; end; bind ctrl-z myexit")