diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index cdd27aebc7..918510bbd0 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4661,7 +4661,6 @@ 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 - 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. @@ -4670,8 +4669,6 @@ void global_exe(char_u *cmd) // 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; @@ -4684,7 +4681,6 @@ void global_exe(char_u *cmd) os_breakcheck(); } - mapped_ctrl_c = save_mapped_ctrl_c; global_busy = 0; if (global_need_beginline) { beginline(BL_WHITE | BL_FIX); diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 819b3906d9..7d06164c89 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -2317,6 +2317,10 @@ static int vgetorpeek(bool advance) // try re-mapping. for (;;) { check_end_reg_executing(advance); + // os_breakcheck() can call input_enqueue() + if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) { + ctrl_c_interrupts = false; + } // os_breakcheck() is slow, don't use it too often when // inside a mapping. But call it each time for typed // characters. @@ -2325,6 +2329,7 @@ static int vgetorpeek(bool advance) } else { os_breakcheck(); // check for CTRL-C } + ctrl_c_interrupts = true; int keylen = 0; if (got_int) { // flush all input diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 8d3269451d..6443759a39 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -670,6 +670,7 @@ EXTERN bool ins_at_eol INIT(= false); // put cursor after eol when EXTERN bool no_abbr INIT(= true); // true when no abbreviations loaded EXTERN int mapped_ctrl_c INIT(= 0); // Modes where CTRL-C is mapped. +EXTERN bool ctrl_c_interrupts INIT(= true); // CTRL-C sets got_int EXTERN cmdmod_T cmdmod; // Ex command modifiers diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 9941774d82..9ee2f57b3d 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -98,7 +98,7 @@ static void create_cursorhold_event(bool events_enabled) /// Low level input function /// -/// wait until either the input buffer is non-empty or , if `events` is not NULL +/// wait until either the input buffer is non-empty or, if `events` is not NULL /// until `events` is non-empty. int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events) { @@ -106,6 +106,11 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); } + // No risk of a UI flood, so disable CTRL-C "interrupt" behavior if it's mapped. + if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) { + ctrl_c_interrupts = false; + } + InbufPollResult result; if (ms >= 0) { if ((result = inbuf_poll(ms, events)) == kInputNone) { @@ -127,6 +132,8 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e } } + ctrl_c_interrupts = true; + // If input was put directly in typeahead buffer bail out here. if (typebuf_changed(tb_change_cnt)) { return 0; @@ -275,7 +282,7 @@ size_t input_enqueue(String keys) } size_t rv = (size_t)(ptr - keys.data); - process_interrupts(); + process_ctrl_c(); return rv; } @@ -480,9 +487,9 @@ static void input_read_cb(Stream *stream, RBuffer *buf, size_t c, void *data, bo } } -static void process_interrupts(void) +static void process_ctrl_c(void) { - if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) { + if (!ctrl_c_interrupts) { return; } diff --git a/test/functional/ex_cmds/ctrl_c_spec.lua b/test/functional/ex_cmds/ctrl_c_spec.lua index f19fab5550..c2e4bf0fb7 100644 --- a/test/functional/ex_cmds/ctrl_c_spec.lua +++ b/test/functional/ex_cmds/ctrl_c_spec.lua @@ -2,10 +2,15 @@ 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 local command = helpers.command +local sleep = helpers.sleep describe("CTRL-C (mapped)", function() + local screen + before_each(function() clear() + screen = Screen.new(52, 6) + screen:attach() end) it("interrupts :global", function() @@ -20,14 +25,6 @@ describe("CTRL-C (mapped)", function() ]]) command("silent edit! test/functional/fixtures/bigfile.txt") - 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([[ ^0000;;Cc;0;BN;;;;;N;NULL;;;; | @@ -56,4 +53,23 @@ describe("CTRL-C (mapped)", function() end end end) + + it('interrupts :sleep', function() + command('nnoremap ') + feed(':sleep 100') + -- wait for :sleep to start + sleep(10) + feed('foo') + -- wait for input buffer to be flushed + sleep(10) + feed('i') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + -- INSERT -- | + ]]) + end) end) diff --git a/test/functional/legacy/mapping_spec.lua b/test/functional/legacy/mapping_spec.lua index 0f65d5eb65..552c26e7f2 100644 --- a/test/functional/legacy/mapping_spec.lua +++ b/test/functional/legacy/mapping_spec.lua @@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert -local feed_command, expect, poke_eventloop = helpers.feed_command, helpers.expect, helpers.poke_eventloop +local expect, poke_eventloop = helpers.expect, helpers.poke_eventloop local command, eq, eval, meths = helpers.command, helpers.eq, helpers.eval, helpers.meths local sleep = helpers.sleep @@ -15,7 +15,7 @@ describe('mapping', function() ]]) -- Abbreviations with р (0x80) should work. - feed_command('inoreab чкпр vim') + command('inoreab чкпр vim') feed('GAчкпр ') expect([[ @@ -25,17 +25,15 @@ describe('mapping', function() it('Ctrl-c works in Insert mode', function() -- Mapping of ctrl-c in insert mode - feed_command('set cpo-=< cpo-=k') - feed_command('inoremap ') - feed_command('cnoremap dummy') - feed_command('cunmap ') + command('set cpo-=< cpo-=k') + command('inoremap ') + command('cnoremap dummy') + command('cunmap ') feed('GA') - feed('TEST2: CTRL-C |') + -- XXX: editor must be in Insert mode before is put into input buffer poke_eventloop() - feed('A|') - poke_eventloop() - feed_command('unmap ') - feed_command('unmap! ') + feed('TEST2: CTRL-C |A|') + command('unmap! ') expect([[ @@ -44,13 +42,12 @@ describe('mapping', function() end) it('Ctrl-c works in Visual mode', function() - feed_command([[vnoremap :$put ='vmap works']]) + command([[vnoremap :$put ='vmap works']]) feed('GV') - -- XXX: For some reason the mapping is only triggered - -- when is in a separate feed command. + -- XXX: editor must be in Visual mode before is put into input buffer poke_eventloop() - feed('') - feed_command('vunmap ') + feed('vV') + command('vunmap ') expect([[ @@ -59,23 +56,23 @@ describe('mapping', function() it('langmap', function() -- langmap should not get remapped in insert mode. - feed_command('inoremap { FAIL_ilangmap') - feed_command('set langmap=+{ langnoremap') + command('inoremap { FAIL_ilangmap') + command('set langmap=+{ langnoremap') feed('o+') -- Insert mode expr mapping with langmap. - feed_command('inoremap { "FAIL_iexplangmap"') + command('inoremap { "FAIL_iexplangmap"') feed('o+') -- langmap should not get remapped in cmdline mode. - feed_command('cnoremap { FAIL_clangmap') + command('cnoremap { FAIL_clangmap') feed('o+') - feed_command('cunmap {') + command('cunmap {') -- cmdline mode expr mapping with langmap. - feed_command('cnoremap { "FAIL_cexplangmap"') + command('cnoremap { "FAIL_cexplangmap"') feed('o+') - feed_command('cunmap {') + command('cunmap {') -- Assert buffer contents. expect([[ @@ -93,10 +90,10 @@ describe('mapping', function() ]]) -- Vim's issue #212 (feedkeys insert mapping at current position) - feed_command('nnoremap . :call feedkeys(".", "in")') + command('nnoremap . :call feedkeys(".", "in")') feed('/^a b') feed('0qqdw.ifooqj0@q') - feed_command('unmap .') + command('unmap .') expect([[ fooc d fooc d @@ -105,15 +102,15 @@ describe('mapping', function() it('i_CTRL-G_U', function() -- U works only within a single line - feed_command('imapclear') - feed_command('imap ( ()U') + command('imapclear') + command('imap ( ()U') feed('G2okiTest1: text with a (here some more textk.') -- test undo feed('G2okiTest2: text wit a (here some more text [und undo]uk.u') - feed_command('imapclear') - feed_command('set whichwrap=<,>,[,]') + command('imapclear') + command('set whichwrap=<,>,[,]') feed('G3o2k') - feed_command([[:exe ":norm! iTest3: text with a (parenthesis here\U\new line here\\\."]]) + command([[:exe ":norm! iTest3: text with a (parenthesis here\U\new line here\\\."]]) expect([[ diff --git a/test/functional/vimscript/system_spec.lua b/test/functional/vimscript/system_spec.lua index bedf7e5498..9cc6424d31 100644 --- a/test/functional/vimscript/system_spec.lua +++ b/test/functional/vimscript/system_spec.lua @@ -268,7 +268,7 @@ describe('system()', function() :call system("for /L %I in (1,0,2) do @echo y") |]] or [[ :call system("yes") |]])) - feed('') + feed('foo') screen:expect([[ ^ | ~ | @@ -286,6 +286,49 @@ describe('system()', function() Type :qa and press to exit Nvim | ]]) end) + + it('`yes` interrupted with mapped CTRL-C', function() + command('nnoremap i') + feed(':call system("' .. (iswin() + and 'for /L %I in (1,0,2) do @echo y' + or 'yes') .. '")') + screen:expect([[ + | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | +]] .. (iswin() + and [[ + :call system("for /L %I in (1,0,2) do @echo y") |]] + or [[ + :call system("yes") |]])) + feed('foo') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + -- INSERT -- | + ]]) + end) end) describe('passing no input', function()