diff --git a/src/builtin_commandline.cpp b/src/builtin_commandline.cpp index a4bcf9768..315f04c51 100644 --- a/src/builtin_commandline.cpp +++ b/src/builtin_commandline.cpp @@ -294,8 +294,14 @@ maybe_t builtin_commandline(parser_t &parser, io_streams_t &streams, wchar_ return STATUS_INVALID_ARGS; } + using rl = readline_cmd_t; for (i = w.woptind; i < argc; i++) { if (auto mc = input_function_get_code(argv[i])) { + // Don't enqueue a repaint if we're currently in the middle of one, + // because that's an infinite loop. + if (mc == rl::repaint_mode || mc == rl::force_repaint || mc == rl::repaint) { + if (ld.is_repaint) continue; + } // Inserts the readline function at the back of the queue. reader_queue_ch(*mc); } else { diff --git a/src/parser.h b/src/parser.h index ca2dec40f..7c8b37b01 100644 --- a/src/parser.h +++ b/src/parser.h @@ -151,6 +151,10 @@ struct library_data_t { /// Number of recursive calls to builtin_complete(). uint32_t builtin_complete_recursion_level{0}; + /// If we're currently repainting the commandline. + /// Useful to stop infinite loops. + bool is_repaint{false}; + /// Whether we called builtin_complete -C without parameter. bool builtin_complete_current_commandline{false}; diff --git a/src/reader.cpp b/src/reader.cpp index 531ef162a..ce7465e7f 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -2781,10 +2781,17 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat // // Because some users set `fish_mode_prompt` to an empty function and display the mode // elsewhere, we detect if the mode output is empty. + + // Don't go into an infinite loop of repainting. + // This can happen e.g. if a variable triggers a repaint, + // and the variable is set inside the prompt (#7324). + // builtin commandline will refuse to enqueue these. + parser().libdata().is_repaint = true; exec_mode_prompt(); if (!mode_prompt_buff.empty()) { s_reset_line(&screen, true /* redraw prompt */); if (this->is_repaint_needed()) this->layout_and_repaint(L"mode"); + parser().libdata().is_repaint = false; break; } // Else we repaint as normal. @@ -2792,10 +2799,12 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat } case rl::force_repaint: case rl::repaint: { + parser().libdata().is_repaint = true; exec_prompt(); s_reset_line(&screen, true /* redraw prompt */); this->layout_and_repaint(L"readline"); force_exec_prompt_and_repaint = false; + parser().libdata().is_repaint = false; break; } case rl::complete: diff --git a/tests/pexpects/commandline.py b/tests/pexpects/commandline.py index 2e2b70dc5..0c544dd0f 100644 --- a/tests/pexpects/commandline.py +++ b/tests/pexpects/commandline.py @@ -23,3 +23,10 @@ sendline( expect_prompt() sendline("echo one \"two three\" four'five six'{7} 'eight~") expect_prompt("\r\n@GUARD:2@\r\n(.*)\r\n@/GUARD:2@\r\n") + +# Check that we don't infinitely loop here. +sendline("function fish_mode_prompt; commandline -f repaint; end") +expect_prompt() + +sendline("echo foo") +expect_prompt("foo")