[vim/vim] Unexpectedly leaving insert mode when completion is triggered in prompt buffer (regression by patch 8.2.1654) (#7035)

18 views
Skip to first unread message

Ben Jackson

unread,
Sep 28, 2020, 1:41:17 PM9/28/20
to vim/vim, Subscribed

Instructions: Replace the template text and remove irrelevant text (including this line)

Describe the bug

After patch 8.2.1654, insert mode is exited unexpectedly using prompt-buffers and ctrl-x mode. Perhaps related, similar to #7013. Initially I noticed that the pum was visible but I was unexpectedly in normal mode. when the completion menu is shown in a prompt buffer. But since that was fixed, now the behaviour is just that you end up in normal mode without pressing escape.

I have a very solid reproduction case, but it's full of Vimspector and YouCompleteMe which are heavyweight vim plugins. Both make use of channels and timers, both for completion.

I used the repro case to bisect this to 8.2.1654 patch. Prior to that patch completion works well in prompt buffers, but since then user is unexpectedly bumped out of insert mode.

To Reproduce
Detailed steps to reproduce the behavior:

I'm working on a minimal vim-only repro, but it's a bit tricky. I can't expect you to install YCM and Vimspector to test this, but for what it's worth this can repro reliably:

let &rtp = getcwd() .. '/YouCompleteMe,' .. getcwd() .. 'vimspector'
filetype plugin indent on
let g:vimspector_enable_mappings = 'HUMAN'
let g:ycm_semantic_triggers = { 'VimspectorPrompt': [ '.' ] }
  • vim -Nu ycm_plus_vimspector vimspector/support/test/python/simple_python/main.py
  • <F5>
  • select Run - default
  • Hit enter a few times to accept the default launch arguments
  • Hit <F10> to step over the class TestClass line
  • In the Watches window (a prompt buffer), enter insert mode and type TestClass.

You will be bumped unexpectedly out of insert mode into normal mode as you're typing.

Expected behavior
You should see the completion menu with various elements of TestClass. <Tab> or <C-n> should cycle through them.
This repeating the last few steps on patches prior to v8.2.1654 works.

Environment (please complete the following information):

  • Vim version 8.6.1654 and later
  • OS: Linux RHEL 7
  • Terminal: tmux, PuTTY

Additional context

As mentioned, I'll try and write a test for this, as I realise entirely that installing 2 huge plugins is not in any way "minimal".

YCM is using the TextChangedI and TextChangeP auto commands and the InsertCharPre auto command. It's calling the omnifunc provided by Vimspector when you press . in insert mode, which is doing a ch_read() blocking on a response from a server. Then YCM is calling complete() to populate the completion menu.

Again apologies for lack of minimal repro. I'll try and work on it tonight.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or unsubscribe.

Bram Moolenaar

unread,
Sep 28, 2020, 3:18:44 PM9/28/20
to vim/vim, Subscribed

I'm surprised patch 8.6.1654 would cause this, and only now gets reported.
Are you sure it's not related to 8.2.1762 ?

Ben Jackson

unread,
Sep 28, 2020, 4:31:40 PM9/28/20
to vim/vim, Subscribed

I can check but I bisected and it indicated the above commit. I can confirm that with 8.2.1762 we no longer have the pum left open, but it still exits insert mode unexpectedly. With 8.1.1654, you end up with both - exit insert mode unexpectedly and the pum is left open.

Tony Mechelynck

unread,
Sep 28, 2020, 4:45:37 PM9/28/20
to vim/vim, Subscribed

  • Does it happen also in gvim?
  • What are the answers to
:set timeout? timeoutlen? ttimeout? ttimeoutlen?

(including the question marks)?

Best regards,
Tony.

Ben Jackson

unread,
Sep 28, 2020, 5:23:17 PM9/28/20
to vim/vim, Subscribed

Does it happen also in gvim?

Will check motif GUI at work tomorrow, sorry don't have a gvim here right now.

For the record, it does repro in MacVim with:

ben@BenMBP vim-clean % gvim --version
VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Sep 28 2020 22:02:11)
macOS version
Included patches: 1-1768
Compiled by Homebrew
Huge version with MacVim GUI.  Features included (+) or not (-):

What are the answers to :set timeout? timeoutlen? ttimeout? ttimeoutlen?

Screenshot 2020-09-28 at 22 21 56

  • timeout
  • timeoutlen=1000
  • ttimeout
  • ttimeoutlen=100

Tony Mechelynck

unread,
Sep 28, 2020, 7:32:42 PM9/28/20
to vim_dev, reply+ACY5DGFSGI6PJ6UM2X...@reply.github.com, vim/vim, Subscribed
On Mon, Sep 28, 2020 at 11:23 PM Ben Jackson <vim-dev...@256bit.org> wrote:

Does it happen also in gvim?

Will check motif GUI at work tomorrow, sorry don't have a gvim here right now.

For the record, it does repro in MacVim with:

ben@BenMBP vim-clean % gvim --version
VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Sep 28 2020 22:02:11)
macOS version
Included patches: 1-1768
Compiled by Homebrew
Huge version with MacVim GUI.  Features included (+) or not (-):

What are the answers to :set timeout? timeoutlen? ttimeout? ttimeoutlen?

Screenshot 2020-09-28 at 22 21 56

  • timeout
  • timeoutlen=1000
  • ttimeout
  • ttimeoutlen=100

Hm, with the above settings, Vim ought to be able to tell the difference between an Esc key hit at the keyboard and a ^[ sent as part of a keycode, so I suppose it isn't that. Also, if the problem happens in the MacVim GUI I would expect it to happen also in an X11 or W32/64 GUI but I'm less sure about that.

Best regards,
Tony.

vim-dev ML

unread,
Sep 28, 2020, 7:33:01 PM9/28/20
to vim/vim, vim-dev ML, Your activity

On Mon, Sep 28, 2020 at 11:23 PM Ben Jackson <vim-dev...@256bit.org>
wrote:

> Does it happen also in gvim?
>
> Will check motif GUI at work tomorrow, sorry don't have a gvim here right
> now.
>
> For the record, it *does* repro in MacVim with:

>
> ben@BenMBP vim-clean % gvim --version
> VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Sep 28 2020 22:02:11)
> macOS version
> Included patches: 1-1768
> Compiled by Homebrew
> Huge version with MacVim GUI. Features included (+) or not (-):
>
> What are the answers to :set timeout? timeoutlen? ttimeout? ttimeoutlen?
>
> [image: Screenshot 2020-09-28 at 22 21 56]
> <https://user-images.githubusercontent.com/10584846/94487504-09dde480-01d9-11eb-8c34-ec4fec370429.png>
>
> - timeout
> - timeoutlen=1000
> - ttimeout
> - ttimeoutlen=100

>
>
> Hm, with the above settings, Vim ought to be able to tell the difference
between an Esc key hit at the keyboard and a ^[ sent as part of a keycode,
so I suppose it isn't that. Also, if the problem happens in the MacVim GUI
I would expect it to happen also in an X11 or W32/64 GUI but I'm less sure
about that.

Best regards,
Tony.

Ben Jackson

unread,
Sep 29, 2020, 3:01:03 AM9/29/20
to vim/vim, vim-dev ML, Comment

Yes Tony I’m fairly sure that this is not a mapping issue. It’s as if something is calling stopinsert. I have a couple of theories of how to minimise the repro case. Will try after work hopefully.

Re the point about being not reported before, it seems that it’s only prompt buffers that are affected, which are not frequently used.


You are receiving this because you commented.

Ben Jackson

unread,
Sep 29, 2020, 9:10:23 AM9/29/20
to vim/vim, vim-dev ML, Comment

OK, this is easy to repro. Here's a trivial case.

set buftype=prompt

call prompt_setprompt( bufnr(), 'Enter some text: ' )

call job_start( [ 'uuencode', '/dev/urandom', '/dev/stdout' ], #{
      \ out_io: 'buffer',
      \ out_name: ''
      \ } )
  • vim -Nu NONE -S test.vim
  • enter insert mode i and type some characters - you're bumped out of insert mode


You are receiving this because you commented.

Ben Jackson

unread,
Sep 29, 2020, 9:23:28 AM9/29/20
to vim/vim, vim-dev ML, Comment

Here's a test that repros it:


function! Test_Prompt_While_Writing_To_Hidden_Buffer()
  call CanTestPromptBuffer()
  let scriptName = 'XpromptscriptHiddenBuf'
  let script =<< trim END
    set buftype=prompt
    call prompt_setprompt( bufnr(), 'cmd:' )
    let job = job_start( [ 'uuencode', '/dev/urandom', '/dev/stdout' ], #{
      \ out_io: 'buffer',
      \ out_name: '' } )
    startinsert
  END
  exec script->writefile( scriptName )

  let buf = RunVimInTerminal('-S ' . scriptName, {})
  call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))})

  call term_sendkeys( buf, 'test' )
  call WaitForAssert( {-> assert_equal('cmd:test', term_getline(buf, 1)) } )

  call StopVimInTerminal(buf)
  call delete(scriptName)
endfunc

|| command line..script /Users/ben/Development/vim/src/testdir/runtest.vim[459]..function RunTheTest[39]..Test_Prompt_While_Writing_To_Hidden_Buffer[17]..WaitForAssert[2]..6_WaitForCommon[11]..3 line 1: Expected 'cmd:test' but got 'cmd:t'


You are receiving this because you commented.

Ben Jackson

unread,
Sep 29, 2020, 4:46:02 PM9/29/20
to vim/vim, vim-dev ML, Comment

Been debugging this. I'm thinking that it's related to this:

    static void
leaving_window(win_T *win)
{
    // Only matters for a prompt window.
    if (!bt_prompt(win->w_buffer))
	return;

    // When leaving a prompt window stop Insert mode and perhaps restart
    // it when entering that window again.
    win->w_buffer->b_prompt_insert = restart_edit;
    if (restart_edit != 0 && mode_displayed)
	clear_cmdline = TRUE;		// unshow mode later
    restart_edit = NUL;

    // When leaving the window (or closing the window) was done from a
    // callback we need to break out of the Insert mode loop and restart Insert
    // mode when entering the window again.
    if (State & INSERT)
    {
	stop_insert_mode = TRUE;
	if (win->w_buffer->b_prompt_insert == NUL)
	    win->w_buffer->b_prompt_insert = 'A';
    }
}

    static void
entering_window(win_T *win)
{
    // Only matters for a prompt window.
    if (!bt_prompt(win->w_buffer))
	return;

    // When switching to a prompt buffer that was in Insert mode, don't stop
    // Insert mode, it may have been set in leaving_window().
    if (win->w_buffer->b_prompt_insert != NUL)
	stop_insert_mode = FALSE;

    // When entering the prompt window restart Insert mode if we were in Insert
    // mode when we left it.
    restart_edit = win->w_buffer->b_prompt_insert;
}

When using aucmd_prepwin we actually leave the window in order to write to the hidden buffer, and we're calling leaving_window(). . But it seems like we're not calling entering_window() for the prompt buffer when reverting this temporary window.

For certain when we do call entering_window(), bt_prompt(win->w_buffer) is false. I'ma summing that's actually entering the temporary autocmd window.

Are we missing a call to win_enter in aucmd_restbuf() ? That seems intrusive, but I haven't fully groked this code yet.


You are receiving this because you commented.

Bram Moolenaar

unread,
Sep 29, 2020, 4:58:17 PM9/29/20
to vim/vim, vim-dev ML, Comment

I can't reproduce because "uuencode: Command not found". Perhaps something more common (and not keeping the CPU busy)?


You are receiving this because you commented.

Bram Moolenaar

unread,
Sep 29, 2020, 5:01:35 PM9/29/20
to vim/vim, vim-dev ML, Comment

Oh, and there is no aucmd_prepwin(), you probably mean aucmd_prepbuf(). But it doesn't call leaving_window() directly.


You are receiving this because you commented.

Ben Jackson

unread,
Sep 29, 2020, 5:04:03 PM9/29/20
to vim/vim, vim-dev ML, Comment

Just need a job to write to a hidden buffer while in insert mode.

This repro's it too

set buftype=prompt

call prompt_setprompt( bufnr(), 'Enter some text: ' )

augroup TEST
  au InsertEnter call job_start( [ 'ls' ], #{
      \ out_io: 'buffer',
      \ out_name: ''
      \ } )
augroup END

this seems to resolve it:

ben@BenMBP vim-clean % git diff
diff --git a/src/autocmd.c b/src/autocmd.c
index d5c61ca6a..1789d0477 100644
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -1530,6 +1530,7 @@ win_found:
 
        restore_snapshot(SNAP_AUCMD_IDX, FALSE);
        (void)win_comp_pos();   // recompute window positions
+       win_enter(aco->save_curwin, TRUE);
        unblock_autocmds();
 
        if (win_valid(aco->save_curwin))
ben@BenMBP vim-clean % 


You are receiving this because you commented.

Ben Jackson

unread,
Sep 29, 2020, 5:06:24 PM9/29/20
to vim/vim, vim-dev ML, Comment

Oh, and there is no aucmd_prepwin(), you probably mean aucmd_prepbuf(). But it doesn't call leaving_window() directly.

Yeah I meant aucmd_prepbuf() - this calls win_split_ins(), which calls win_enter_ext(wp, FALSE, FALSE, TRUE, TRUE, TRUE); ... and that calls leaving_window()


You are receiving this because you commented.

Ben Jackson

unread,
Sep 29, 2020, 5:38:35 PM9/29/20
to vim/vim, vim-dev ML, Comment

OK here's a test which repros it too; had to introduce some sleeps though :/

function! Test_Prompt_While_Writing_To_Hidden_Buffer()
  call CanTestPromptBuffer()
  let scriptName = 'XpromptscriptHiddenBuf'
  let script =<< trim
 END
    set buftype=prompt
    call prompt_setprompt( bufnr(), 'cmd:' )
    let job = job_start( [ '/bin/sh', '-c', 'while true; do echo line; done' ], #{
      \ out_io: 'buffer',
      \ out_name: '' } )
    
startinsert
  END
  exec script->writefile( scriptName )

  let buf = RunVimInTerminal('-S ' . scriptName, {})
  call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))})

  call term_sendkeys( buf, 'test' )
  call WaitForAssert( {-> assert_equal('cmd:test', term_getline(buf, 1
)) } )
  sleep 10m
  call term_sendkeys( buf, 'test' )
  call WaitForAssert( {-> assert_equal('cmd:testtest', term_getline(buf, 1)) } )
  sleep 10m
  call term_sendkeys( buf, 'test' )
  call WaitForAssert( {-> assert_equal('cmd:testtesttest', term_getline(buf, 1)) } )

  
call StopVimInTerminal(buf)
  call delete(scriptName)
endfunc


You are receiving this because you commented.

TJ DeVries

unread,
Sep 29, 2020, 8:38:35 PM9/29/20
to vim/vim, vim-dev ML, Comment

I was wondering if this patch (that I just made myself when I first encountered the bug) is useful:

neovim/neovim#12977

I have no idea if this is the "right" solution or not, just figured I'd show what I was thinking. I just expected :stopinsert to just completely shutdown everything that could be happening in insert mode.


You are receiving this because you commented.

Stanley Chan

unread,
Sep 30, 2020, 10:56:09 PM9/30/20
to vim/vim, vim-dev ML, Comment

Just so no one is confused about @puremourning 's test script, WaitForAssert is defined in https://github.com/puremourning/vimspector/blob/4ed915828285c067b7f4a35fecc7e0ed56fc9be8/tests/lib/plugin/shared.vim.


You are receiving this because you commented.

Ben Jackson

unread,
Oct 1, 2020, 2:48:14 AM10/1/20
to vim/vim, vim-dev ML, Comment

@Happy-Dude my test runs in Vim’s test suite. Paste it into test_promt_buffer.vim

Those functions are defined in vim’s shared.vim and friends.


You are receiving this because you commented.

Bram Moolenaar

unread,
Oct 1, 2020, 1:57:07 PM10/1/20
to vim/vim, vim-dev ML, Comment

Thanks for figuring this out and writing a test. I'll clean it up a bit.


You are receiving this because you commented.

Bram Moolenaar

unread,
Oct 1, 2020, 2:03:38 PM10/1/20
to vim/vim, vim-dev ML, Comment

Closed #7035 via 4537bcc.


You are receiving this because you commented.

Ben Jackson

unread,
Oct 1, 2020, 4:38:29 PM10/1/20
to vim/vim, vim-dev ML, Comment

Thanks Bram. That's working nice now.


You are receiving this because you commented.

Bram Moolenaar

unread,
Oct 1, 2020, 4:49:14 PM10/1/20
to vim...@googlegroups.com, Ben Jackson

Ben Jackson wrote:

> Thanks Bram. That's working nice now.

It did cause a test to fail. I think it's fixed now, by not calling
win_enter() but only using the part that we need. This buffer-switching
remains tricky.


--
How To Keep A Healthy Level Of Insanity:
5. Put decaf in the coffee maker for 3 weeks. Once everyone has gotten
over their caffeine addictions, switch to espresso.

/// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\
/// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\ an exciting new programming language -- http://www.Zimbu.org ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///
Reply all
Reply to author
Forward
0 new messages