fix(input): only disable mapped CTRL-C interrupts when getting input (#18310)

This commit is contained in:
zeertzjq 2022-04-30 13:11:35 +08:00 committed by GitHub
parent 7df25a1372
commit 2ba539f449
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 47 deletions

View File

@ -4661,7 +4661,6 @@ void global_exe(char_u *cmd)
linenr_T old_lcount; // b_ml.ml_line_count before the command linenr_T old_lcount; // b_ml.ml_line_count before the command
buf_T *old_buf = curbuf; // remember what buffer we started in buf_T *old_buf = curbuf; // remember what buffer we started in
linenr_T lnum; // line number according to old situation 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. // Set current position only once for a global command.
// If global_busy is set, setpcmark() will not do anything. // 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. // When the command writes a message, don't overwrite the command.
msg_didout = true; msg_didout = true;
// Disable CTRL-C mapping, let it interrupt (potentially long output).
mapped_ctrl_c = 0;
sub_nsubs = 0; sub_nsubs = 0;
sub_nlines = 0; sub_nlines = 0;
@ -4684,7 +4681,6 @@ void global_exe(char_u *cmd)
os_breakcheck(); os_breakcheck();
} }
mapped_ctrl_c = save_mapped_ctrl_c;
global_busy = 0; global_busy = 0;
if (global_need_beginline) { if (global_need_beginline) {
beginline(BL_WHITE | BL_FIX); beginline(BL_WHITE | BL_FIX);

View File

@ -2317,6 +2317,10 @@ static int vgetorpeek(bool advance)
// try re-mapping. // try re-mapping.
for (;;) { for (;;) {
check_end_reg_executing(advance); 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 // os_breakcheck() is slow, don't use it too often when
// inside a mapping. But call it each time for typed // inside a mapping. But call it each time for typed
// characters. // characters.
@ -2325,6 +2329,7 @@ static int vgetorpeek(bool advance)
} else { } else {
os_breakcheck(); // check for CTRL-C os_breakcheck(); // check for CTRL-C
} }
ctrl_c_interrupts = true;
int keylen = 0; int keylen = 0;
if (got_int) { if (got_int) {
// flush all input // flush all input

View File

@ -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 bool no_abbr INIT(= true); // true when no abbreviations loaded
EXTERN int mapped_ctrl_c INIT(= 0); // Modes where CTRL-C is mapped. 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 EXTERN cmdmod_T cmdmod; // Ex command modifiers

View File

@ -98,7 +98,7 @@ static void create_cursorhold_event(bool events_enabled)
/// Low level input function /// 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. /// until `events` is non-empty.
int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events) 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); 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; InbufPollResult result;
if (ms >= 0) { if (ms >= 0) {
if ((result = inbuf_poll(ms, events)) == kInputNone) { 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 input was put directly in typeahead buffer bail out here.
if (typebuf_changed(tb_change_cnt)) { if (typebuf_changed(tb_change_cnt)) {
return 0; return 0;
@ -275,7 +282,7 @@ size_t input_enqueue(String keys)
} }
size_t rv = (size_t)(ptr - keys.data); size_t rv = (size_t)(ptr - keys.data);
process_interrupts(); process_ctrl_c();
return rv; 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; return;
} }

View File

@ -2,10 +2,15 @@ local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen') local Screen = require('test.functional.ui.screen')
local clear, feed, source = helpers.clear, helpers.feed, helpers.source local clear, feed, source = helpers.clear, helpers.feed, helpers.source
local command = helpers.command local command = helpers.command
local sleep = helpers.sleep
describe("CTRL-C (mapped)", function() describe("CTRL-C (mapped)", function()
local screen
before_each(function() before_each(function()
clear() clear()
screen = Screen.new(52, 6)
screen:attach()
end) end)
it("interrupts :global", function() it("interrupts :global", function()
@ -20,14 +25,6 @@ describe("CTRL-C (mapped)", function()
]]) ]])
command("silent edit! test/functional/fixtures/bigfile.txt") 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([[ screen:expect([[
^0000;<control>;Cc;0;BN;;;;;N;NULL;;;; | ^0000;<control>;Cc;0;BN;;;;;N;NULL;;;; |
@ -56,4 +53,23 @@ describe("CTRL-C (mapped)", function()
end end
end end
end) end)
it('interrupts :sleep', function()
command('nnoremap <C-C> <Nop>')
feed(':sleep 100<CR>')
-- wait for :sleep to start
sleep(10)
feed('foo<C-C>')
-- wait for input buffer to be flushed
sleep(10)
feed('i')
screen:expect([[
^ |
~ |
~ |
~ |
~ |
-- INSERT -- |
]])
end)
end) end)

View File

@ -2,7 +2,7 @@
local helpers = require('test.functional.helpers')(after_each) local helpers = require('test.functional.helpers')(after_each)
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert 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 command, eq, eval, meths = helpers.command, helpers.eq, helpers.eval, helpers.meths
local sleep = helpers.sleep local sleep = helpers.sleep
@ -15,7 +15,7 @@ describe('mapping', function()
]]) ]])
-- Abbreviations with р (0x80) should work. -- Abbreviations with р (0x80) should work.
feed_command('inoreab чкпр vim') command('inoreab чкпр vim')
feed('GAчкпр <esc>') feed('GAчкпр <esc>')
expect([[ expect([[
@ -25,17 +25,15 @@ describe('mapping', function()
it('Ctrl-c works in Insert mode', function() it('Ctrl-c works in Insert mode', function()
-- Mapping of ctrl-c in insert mode -- Mapping of ctrl-c in insert mode
feed_command('set cpo-=< cpo-=k') command('set cpo-=< cpo-=k')
feed_command('inoremap <c-c> <ctrl-c>') command('inoremap <c-c> <ctrl-c>')
feed_command('cnoremap <c-c> dummy') command('cnoremap <c-c> dummy')
feed_command('cunmap <c-c>') command('cunmap <c-c>')
feed('GA<cr>') feed('GA<cr>')
feed('TEST2: CTRL-C |') -- XXX: editor must be in Insert mode before <C-C> is put into input buffer
poke_eventloop() poke_eventloop()
feed('<c-c>A|<cr><esc>') feed('TEST2: CTRL-C |<c-c>A|<cr><esc>')
poke_eventloop() command('unmap! <c-c>')
feed_command('unmap <c-c>')
feed_command('unmap! <c-c>')
expect([[ expect([[
@ -44,13 +42,12 @@ describe('mapping', function()
end) end)
it('Ctrl-c works in Visual mode', function() it('Ctrl-c works in Visual mode', function()
feed_command([[vnoremap <c-c> :<C-u>$put ='vmap works'<cr>]]) command([[vnoremap <c-c> :<C-u>$put ='vmap works'<cr>]])
feed('GV') feed('GV')
-- XXX: For some reason the mapping is only triggered -- XXX: editor must be in Visual mode before <C-C> is put into input buffer
-- when <C-c> is in a separate feed command.
poke_eventloop() poke_eventloop()
feed('<c-c>') feed('vV<c-c>')
feed_command('vunmap <c-c>') command('vunmap <c-c>')
expect([[ expect([[
@ -59,23 +56,23 @@ describe('mapping', function()
it('langmap', function() it('langmap', function()
-- langmap should not get remapped in insert mode. -- langmap should not get remapped in insert mode.
feed_command('inoremap { FAIL_ilangmap') command('inoremap { FAIL_ilangmap')
feed_command('set langmap=+{ langnoremap') command('set langmap=+{ langnoremap')
feed('o+<esc>') feed('o+<esc>')
-- Insert mode expr mapping with langmap. -- Insert mode expr mapping with langmap.
feed_command('inoremap <expr> { "FAIL_iexplangmap"') command('inoremap <expr> { "FAIL_iexplangmap"')
feed('o+<esc>') feed('o+<esc>')
-- langmap should not get remapped in cmdline mode. -- langmap should not get remapped in cmdline mode.
feed_command('cnoremap { FAIL_clangmap') command('cnoremap { FAIL_clangmap')
feed('o+<esc>') feed('o+<esc>')
feed_command('cunmap {') command('cunmap {')
-- cmdline mode expr mapping with langmap. -- cmdline mode expr mapping with langmap.
feed_command('cnoremap <expr> { "FAIL_cexplangmap"') command('cnoremap <expr> { "FAIL_cexplangmap"')
feed('o+<esc>') feed('o+<esc>')
feed_command('cunmap {') command('cunmap {')
-- Assert buffer contents. -- Assert buffer contents.
expect([[ expect([[
@ -93,10 +90,10 @@ describe('mapping', function()
]]) ]])
-- Vim's issue #212 (feedkeys insert mapping at current position) -- Vim's issue #212 (feedkeys insert mapping at current position)
feed_command('nnoremap . :call feedkeys(".", "in")<cr>') command('nnoremap . :call feedkeys(".", "in")<cr>')
feed('/^a b<cr>') feed('/^a b<cr>')
feed('0qqdw.ifoo<esc>qj0@q<esc>') feed('0qqdw.ifoo<esc>qj0@q<esc>')
feed_command('unmap .') command('unmap .')
expect([[ expect([[
fooc d fooc d
fooc d fooc d
@ -105,15 +102,15 @@ describe('mapping', function()
it('i_CTRL-G_U', function() it('i_CTRL-G_U', function()
-- <c-g>U<cursor> works only within a single line -- <c-g>U<cursor> works only within a single line
feed_command('imapclear') command('imapclear')
feed_command('imap ( ()<c-g>U<left>') command('imap ( ()<c-g>U<left>')
feed('G2o<esc>ki<cr>Test1: text with a (here some more text<esc>k.') feed('G2o<esc>ki<cr>Test1: text with a (here some more text<esc>k.')
-- test undo -- test undo
feed('G2o<esc>ki<cr>Test2: text wit a (here some more text [und undo]<c-g>u<esc>k.u') feed('G2o<esc>ki<cr>Test2: text wit a (here some more text [und undo]<c-g>u<esc>k.u')
feed_command('imapclear') command('imapclear')
feed_command('set whichwrap=<,>,[,]') command('set whichwrap=<,>,[,]')
feed('G3o<esc>2k') feed('G3o<esc>2k')
feed_command([[:exe ":norm! iTest3: text with a (parenthesis here\<C-G>U\<Right>new line here\<esc>\<up>\<up>."]]) command([[:exe ":norm! iTest3: text with a (parenthesis here\<C-G>U\<Right>new line here\<esc>\<up>\<up>."]])
expect([[ expect([[

View File

@ -268,7 +268,7 @@ describe('system()', function()
:call system("for /L %I in (1,0,2) do @echo y") |]] :call system("for /L %I in (1,0,2) do @echo y") |]]
or [[ or [[
:call system("yes") |]])) :call system("yes") |]]))
feed('<c-c>') feed('foo<c-c>')
screen:expect([[ screen:expect([[
^ | ^ |
~ | ~ |
@ -286,6 +286,49 @@ describe('system()', function()
Type :qa and press <Enter> to exit Nvim | Type :qa and press <Enter> to exit Nvim |
]]) ]])
end) end)
it('`yes` interrupted with mapped CTRL-C', function()
command('nnoremap <C-C> i')
feed(':call system("' .. (iswin()
and 'for /L %I in (1,0,2) do @echo y'
or 'yes') .. '")<cr>')
screen:expect([[
|
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
]] .. (iswin()
and [[
:call system("for /L %I in (1,0,2) do @echo y") |]]
or [[
:call system("yes") |]]))
feed('foo<c-c>')
screen:expect([[
^ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
-- INSERT -- |
]])
end)
end) end)
describe('passing no input', function() describe('passing no input', function()