diff options
-rw-r--r-- | src/nvim/ex_cmds.c | 67 | ||||
-rw-r--r-- | test/functional/ex_cmds/global_spec.lua | 74 |
2 files changed, 110 insertions, 31 deletions
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 17780c58e4..4674460a16 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4074,61 +4074,66 @@ void ex_global(exarg_T *eap) vim_regfree(regmatch.regprog); } -/* - * Execute "cmd" on lines marked with ml_setmarked(). - */ +/// Execute `cmd` on lines marked with ml_setmarked(). void global_exe(char_u *cmd) { - linenr_T old_lcount; /* b_ml.ml_line_count before the command */ - buf_T *old_buf = curbuf; /* remember what buffer we started in */ - linenr_T lnum; /* line number according to old situation */ - - /* - * Set current position only once for a global command. - * If global_busy is set, setpcmark() will not do anything. - * If there is an error, global_busy will be incremented. - */ + linenr_T old_lcount; // b_ml.ml_line_count before the command + buf_T *old_buf = curbuf; // remember what buffer we started in + linenr_T lnum; // line number according to old situation + int save_mapped_ctrl_c = mapped_ctrl_c; + + // Set current position only once for a global command. + // If global_busy is set, setpcmark() will not do anything. + // If there is an error, global_busy will be incremented. setpcmark(); - /* When the command writes a message, don't overwrite the command. */ - msg_didout = TRUE; + // When the command writes a message, don't overwrite the command. + msg_didout = true; + // Disable CTRL-C mapping, let it interrupt (potentially long output). + mapped_ctrl_c = 0; sub_nsubs = 0; sub_nlines = 0; - global_need_beginline = FALSE; + global_need_beginline = false; global_busy = 1; old_lcount = curbuf->b_ml.ml_line_count; + while (!got_int && (lnum = ml_firstmarked()) != 0 && global_busy == 1) { curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = 0; - if (*cmd == NUL || *cmd == '\n') + if (*cmd == NUL || *cmd == '\n') { do_cmdline((char_u *)"p", NULL, NULL, DOCMD_NOWAIT); - else + } else { do_cmdline(cmd, NULL, NULL, DOCMD_NOWAIT); + } os_breakcheck(); } + mapped_ctrl_c = save_mapped_ctrl_c; global_busy = 0; - if (global_need_beginline) + if (global_need_beginline) { beginline(BL_WHITE | BL_FIX); - else - check_cursor(); /* cursor may be beyond the end of the line */ + } else { + check_cursor(); // cursor may be beyond the end of the line + } - /* the cursor may not have moved in the text but a change in a previous - * line may move it on the screen */ + // the cursor may not have moved in the text but a change in a previous + // line may move it on the screen changed_line_abv_curs(); - /* If it looks like no message was written, allow overwriting the - * command with the report for number of changes. */ - if (msg_col == 0 && msg_scrolled == 0) - msg_didout = FALSE; + // If it looks like no message was written, allow overwriting the + // command with the report for number of changes. + if (msg_col == 0 && msg_scrolled == 0) { + msg_didout = false; + } - /* If substitutes done, report number of substitutes, otherwise report - * number of extra or deleted lines. - * Don't report extra or deleted lines in the edge case where the buffer - * we are in after execution is different from the buffer we started in. */ - if (!do_sub_msg(false) && curbuf == old_buf) + // If substitutes done, report number of substitutes, otherwise report + // number of extra or deleted lines. + // Don't report extra or deleted lines in the edge case where the buffer + // we are in after execution is different from the buffer we started in. + if (!do_sub_msg(false) && curbuf == old_buf) { msgmore(curbuf->b_ml.ml_line_count - old_lcount); + } } #if defined(EXITFREE) diff --git a/test/functional/ex_cmds/global_spec.lua b/test/functional/ex_cmds/global_spec.lua new file mode 100644 index 0000000000..81a0ef3248 --- /dev/null +++ b/test/functional/ex_cmds/global_spec.lua @@ -0,0 +1,74 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear, feed, source = helpers.clear, helpers.feed, helpers.source + +if helpers.pending_win32(pending) then return end + +describe(':global', function() + before_each(function() + clear() + end) + + it('is interrupted by mapped CTRL-C', function() + if os.getenv("TRAVIS") and os.getenv("CLANG_SANITIZER") == "ASAN_UBSAN" then + -- XXX: ASAN_UBSAN is too slow to react to the CTRL-C. + pending("", function() end) + return + end + + source([[ + set nomore + set undolevels=-1 + nnoremap <C-C> <NOP> + for i in range(0, 99999) + put ='XXX' + endfor + put ='ZZZ' + 1 + .delete + ]]) + + local screen = Screen.new(52, 6) + screen:attach() + screen:set_default_attr_ids({ + [0] = {foreground = Screen.colors.White, + background = Screen.colors.Red}, + [1] = {bold = true, + foreground = Screen.colors.SeaGreen} + }) + + screen:expect([[ + ^XXX | + XXX | + XXX | + XXX | + XXX | + | + ]]) + + local function test_ctrl_c(ms) + feed(":global/^/p<CR>") + helpers.sleep(ms) + feed("<C-C>") + screen:expect([[ + XXX | + XXX | + XXX | + XXX | + {0:Interrupted} | + Interrupt: {1:Press ENTER or type command to continue}^ | + ]]) + end + + -- The test is time-sensitive. Try with different sleep values. + local ms_values = {10, 50, 100} + for i, ms in ipairs(ms_values) do + if i < #ms_values then + local status, _ = pcall(test_ctrl_c, ms) + if status then break end + else -- Call the last attempt directly. + test_ctrl_c(ms) + end + end + end) +end) |