neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit 1d11808bfd2879bf278cd05a7095a6634fa5afec
parent 169f37b94f5227fd3bb98aef3e1f592ab22f96b7
Author: ibhagwan <59988195+ibhagwan@users.noreply.github.com>
Date:   Mon, 19 Aug 2024 15:23:56 -0700

fix(terminal): interrupt/got_int hangs terminal (#30056)

Upon `terminal_enter`, `mapped_ctrl_c` is set in order to avoid `CTRL-C`
interrupts (which is proxied to the terminal process instead), `os_inchar`
will then test `mapped_ctrl_c` against `State` and set `ctrl_c_interrupts=false`
which prevents `process_ctrl_c` from setting `got_int=true` in a terminal
state.

However, if `got_int` is set outside of `process_ctrl_c`, e.g. via
`interrupt()`, this will hang the neovim process as `terminal_execute` will
enter an endless loop as `got_int` will never be cleared causing `safe_vgetc`
to always return `Ctrl_C`.

A minimal example reproducing this bug:
```vim
:autocmd TermEnter * call timer_start(500, {-> interrupt()})
:terminal
:startinsert
```

To fix, we make sure `got_int` is cleared inside `terminal_execute` when
it detects `Ctrl_C`.

Closes #20726

Co-authored-by: zeertzjq <zeertzjq@outlook.com>
Diffstat:
Msrc/nvim/terminal.c | 6++++++
Mtest/functional/terminal/buffer_spec.lua | 10++++++++++
2 files changed, 16 insertions(+), 0 deletions(-)

diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c @@ -782,6 +782,12 @@ static int terminal_execute(VimState *state, int key) FALLTHROUGH; default: + if (key == Ctrl_C) { + // terminal_enter() always sets `mapped_ctrl_c` to avoid `got_int`. 8eeda7169aa4 + // But `got_int` may be set elsewhere, e.g. by interrupt() or an autocommand, + // so ensure that it is cleared. + got_int = false; + } if (key == Ctrl_BSL && !s->got_bsl) { s->got_bsl = true; break; diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua @@ -312,6 +312,16 @@ describe(':terminal buffer', function() pcall_err(command, 'write test/functional/fixtures/tty-test.c') ) end) + + it('external interrupt (got_int) does not hang #20726', function() + eq({ mode = 't', blocking = false }, api.nvim_get_mode()) + command('call timer_start(0, {-> interrupt()})') + feed('<Ignore>') -- Add input to separate two RPC requests + eq({ mode = 't', blocking = false }, api.nvim_get_mode()) + feed([[<C-\><C-N>]]) + eq({ mode = 'nt', blocking = false }, api.nvim_get_mode()) + command('bd!') + end) end) describe(':terminal buffer', function()