Просмотр исходного кода

Fix signature help traceback when using preview popup

When we made YCM parse buffers with no filetype, this ended up including
the buffers within things like popups. Unfortuantely this leads to a
problem that we call "ClearSignatureHelp" from within
"FileReadyToParse". If the "FileReadyToParse" in question was within a
popup, we ge an error "Not valid in popup window" (referring to a call
to popup_close()) and vim gets in a very bad state.

it's not obvious how to solve this in the general case, but for now as
completion in non-filetype buffers is new and rare, we Blacklist enpty
buffers by default to work around the bug.

We allow users to explicitly enable it (and suffer this bug!) by
explicitly whiteliisting it again in ycm_filetype_whitelist.

Added a test which shows that it works when ycm_nofiletyp is
blacklisted, and another test which exercises the (still) broken
behaviour, when ycm_nofiletype is whitelisted.
Ben Jackson 4 лет назад
Родитель
Сommit
758f0b6596

+ 18 - 0
README.md

@@ -2341,6 +2341,20 @@ Default: `{'*': 1}`
 let g:ycm_filetype_whitelist = {'*': 1}
 ```
 
+** Completion in buffers with no filetype **
+
+There is one exception to the above rule. YCM supports completion in buffers
+with no filetype set, but this must be _explicitly_ whitelisted. To identify
+buffers with no filetype, we use the `ycm_nofiletype` pseudo-filetype. To enable
+completion in buffers with no filetype, set:
+
+```viml
+let g:ycm_filetype_whitelist = {
+  \ '*': 1,
+  \ 'ycm_nofiletype': 1
+  \ }
+```
+
 ### The `g:ycm_filetype_blacklist` option
 
 This option controls for which Vim filetypes (see `:h filetype`) should YCM be
@@ -2368,6 +2382,10 @@ let g:ycm_filetype_blacklist = {
       \}
 ```
 
+In addition, `ycm_nofiletype` (representing buffers with no filetype set)
+is blacklisted if `ycm_nofiletype` is not _explicitly_ whitelisted (using
+`g:ycm_filetype_whitelist`).
+
 ### The `g:ycm_filetype_specific_completion_to_disable` option
 
 This option controls for which Vim filetypes (see `:h filetype`) should the YCM

+ 3 - 1
autoload/youcompleteme.vim

@@ -63,7 +63,9 @@ let s:pollers = {
 let s:buftype_blacklist = {
       \   'help': 1,
       \   'terminal': 1,
-      \   'quickfix': 1
+      \   'quickfix': 1,
+      \   'popup': 1,
+      \   'nofile': 1,
       \ }
 let s:last_char_inserted_by_user = v:true
 let s:enable_hover = 0

+ 6 - 0
plugin/youcompleteme.vim

@@ -93,6 +93,12 @@ let g:ycm_filetype_blacklist =
       \   'mail': 1
       \ } )
 
+" Blacklist empty buffers unless explicity whitelisted; workaround for
+" https://github.com/ycm-core/YouCompleteMe/issues/3781
+if !has_key( g:ycm_filetype_whitelist, 'ycm_nofiletype' )
+  let g:ycm_filetype_blacklist[ 'ycm_nofiletype' ] = 1
+endif
+
 let g:ycm_open_loclist_on_ycm_diags =
       \ get( g:, 'ycm_open_loclist_on_ycm_diags', 1 )
 

+ 19 - 0
test/.vimspector.json

@@ -0,0 +1,19 @@
+{
+  "$schema": "https://puremourning.github.io/vimspector/schema/vimspector.schema.json",
+  "configurations": {
+    "Run vim test": {
+      "adapter": "vim-debug-adapter",
+      "configuration": {
+        "request": "launch",
+        "cwd": "${workspaceRoot}",
+        "args": [
+          "--clean",
+          "--not-a-term",
+          "-S", "lib/run_test.vim",
+          "${file}",
+          "${TestFunction}"
+        ]
+      }
+    }
+  }
+}

+ 9 - 12
test/commands.test.vim

@@ -10,40 +10,40 @@ function! Test_ToggleLogs()
   let bcount = len( getbufinfo() )
 
   " default - show
-  exe 'YcmToggleLogs' keys( log_files )[ 0 ]
+  silent exe 'YcmToggleLogs' keys( log_files )[ 0 ]
   call assert_equal( bcount + 1, len( getbufinfo() ) )
   let win = getbufinfo( keys( log_files )[ 0 ] )[ 0 ].windows[ 0 ]
   call assert_equal( &previewheight, winheight( win_id2win( win ) ) )
 
   " default - hide
-  exe 'YcmToggleLogs' keys( log_files )[ 0 ]
+  silent exe 'YcmToggleLogs' keys( log_files )[ 0 ]
   " buffer is wiped out
   call assert_equal( bcount, len( getbufinfo() ) )
   call assert_equal( [], getbufinfo( keys( log_files )[ 0 ] ) )
 
   " show - 10 lines
-  exe '10YcmToggleLogs' keys( log_files )[ 0 ]
+  silent exe '10YcmToggleLogs' keys( log_files )[ 0 ]
   call assert_equal( bcount + 1, len( getbufinfo() ) )
   let win = getbufinfo( keys( log_files )[ 0 ] )[ 0 ].windows[ 0 ]
   call assert_equal( 10, winheight( win_id2win( win ) ) )
 
   " hide
-  exe '10YcmToggleLogs' keys( log_files )[ 0 ]
+  silent exe '10YcmToggleLogs' keys( log_files )[ 0 ]
   call assert_equal( bcount, len( getbufinfo() ) )
   call assert_equal( [], getbufinfo( keys( log_files )[ 0 ] ) )
 
   " show - 15 cols
-  exe 'vertical 15YcmToggleLogs' keys( log_files )[ 0 ]
+  silent exe 'vertical 15YcmToggleLogs' keys( log_files )[ 0 ]
   call assert_equal( bcount + 1, len( getbufinfo() ) )
   let win = getbufinfo( keys( log_files )[ 0 ] )[ 0 ].windows[ 0 ]
   call assert_equal( 15, winwidth( win_id2win( win ) ) )
 
   " hide
-  exe 'YcmToggleLogs' keys( log_files )[ 0 ]
+  silent exe 'YcmToggleLogs' keys( log_files )[ 0 ]
   call assert_equal( bcount, len( getbufinfo() ) )
   call assert_equal( [], getbufinfo( keys( log_files )[ 0 ] ) )
 
-  %bwipeout!
+
 endfunction
 
 function! Test_GetCommandResponse()
@@ -84,7 +84,6 @@ function! Test_GetCommandResponse()
   call setpos( '.', [ 0, 1, 3 ] )
   call assert_equal( '', youcompleteme#GetCommandResponse( 'GetDoc' ) )
 
-  %bwipe!
 endfunction
 
 
@@ -96,15 +95,12 @@ function! Test_GetCommandResponse_FixIt()
   call assert_equal( '',
                    \ youcompleteme#GetCommandResponse( 'FixIt' ) )
 
-  %bwipe!
 endfunction
 
 function! Test_GetDefinedSubcommands_Native()
   call youcompleteme#test#setup#OpenFile( '/test/testdata/cpp/fixit.c', {} )
   call assert_equal( 1, count( youcompleteme#GetDefinedSubcommands(),
                              \ 'GetDoc' ) )
-
-  %bwipe!
 endfunction
 
 function! Test_GetDefinedSubcommands_NoNative()
@@ -112,5 +108,6 @@ function! Test_GetDefinedSubcommands_NoNative()
   setf not_a_filetype
   call assert_equal( [], youcompleteme#GetDefinedSubcommands() )
 
-  %bwipe!
+  " The above call prints ValueError: No semantic completer ...."
+  messages clear
 endfunction

+ 14 - 69
test/completion.common.vim

@@ -1,56 +1,5 @@
 scriptencoding utf-8
 
-function! CheckCompletionItems( expected_props, ... )
-  let prop = 'abbr'
-  if a:0 > 0
-    let prop = a:1
-  endif
-
-  let items = complete_info( [ 'items' ] )[ 'items' ]
-  let abbrs = []
-  for item in items
-    call add( abbrs, get( item, prop ) )
-  endfor
-
-  call assert_equal( a:expected_props,
-        \ abbrs,
-        \ 'not matched: '
-        \ .. string( a:expected_props )
-        \ .. ' against '
-        \ .. prop
-        \ .. ' in '
-        \ .. string( items )
-        \ .. ' matching '
-        \ .. string( abbrs ) )
-endfunction
-
-function! FeedAndCheckMain( keys, func )
-  call timer_start( 500, a:func )
-  call feedkeys( a:keys, 'tx!' )
-endfunction
-
-function! FeedAndCheckAgain( keys, func )
-  call timer_start( 500, a:func )
-  call feedkeys( a:keys )
-endfunction
-
-function! WaitForCompletion()
-  call WaitForAssert( {->
-        \ assert_true( pyxeval( 'ycm_state.GetCurrentCompletionRequest() is not None' ) )
-        \ } )
-  call WaitForAssert( {->
-        \ assert_true( pyxeval( 'ycm_state.CompletionRequestReady()' ) )
-        \ } )
-  redraw
-  call WaitForAssert( {->
-        \ assert_true( pumvisible(), 'pumvisible()' )
-        \ }, 10000 )
-endfunction
-
-function! CheckCurrentLine( expected_value )
-  return assert_equal( a:expected_value, getline( '.' ) )
-endfunction
-
 function! Test_Compl_After_Trigger()
   call youcompleteme#test#setup#OpenFile(
         \ '/third_party/ycmd/ycmd/tests/clangd/testdata/basic.cpp', {} )
@@ -73,7 +22,6 @@ function! Test_Compl_After_Trigger()
   call assert_false( pumvisible(), 'pumvisible()' )
 
   call test_override( 'ALL', 0 )
-  %bwipeout!
 endfunctio
 
 function! Test_Force_Semantic_TopLevel()
@@ -101,7 +49,6 @@ function! Test_Force_Semantic_TopLevel()
   call assert_false( pumvisible(), 'pumvisible()' )
 
   call test_override( 'ALL', 0 )
-  %bwipeout!
 endfunction
 
 function! Test_Select_Next_Previous()
@@ -151,7 +98,6 @@ function! Test_Select_Next_Previous()
   call assert_false( pumvisible(), 'pumvisible()' )
 
   call test_override( 'ALL', 0 )
-  %bwipeout!
 endfunction
 
 function! Test_Enter_Delete_Chars_Updates_Filter()
@@ -200,10 +146,18 @@ function! Test_Enter_Delete_Chars_Updates_Filter()
   call assert_false( pumvisible(), 'pumvisible()' )
 
   call test_override( 'ALL', 0 )
-  %bwipeout!
+endfunction
+
+function! SetUp_Test_Compl_No_Filetype()
+  let g:ycm_filetype_whitelist = {
+        \ '*': 1,
+        \ 'ycm_nofiletype': 1
+        \ }
+  silent! call remove( g:ycm_filetype_blacklist, 'ycm_nofiletype' )
 endfunction
 
 function! Test_Compl_No_Filetype()
+  call assert_false( has_key( g:ycm_filetype_blacklist, 'ycm_nofiletype' ) )
   enew
   call setline( '.', 'hello this is some text ' )
 
@@ -233,18 +187,16 @@ function! Test_Compl_No_Filetype()
 
   call test_override( 'ALL', 0 )
   delfunc! Check
-  %bwipeout!
 endfunction
 
-function! SetUp_Test_Compl_No_Filetype_Blacklisted()
-  let g:ycm_filetype_blacklist = { 'ycm_nofiletype': 1 }
-endfunction
-
-function! TearDown__Test_Compl_No_Filetype_Blacklisted()
-  unlet! g:ycm_filetype_blacklist
+function! TearDown_Test_Compl_No_Filetype()
+  call remove( g:ycm_filetype_whitelist, 'ycm_nofiletype' )
+  let g:ycm_filetype_blacklist[ 'ycm_nofiletype' ] = 1
 endfunction
 
 function! Test_Compl_No_Filetype_Blacklisted()
+  call assert_true( has_key( g:ycm_filetype_blacklist, 'ycm_nofiletype' ) )
+
   enew
   call setline( '.', 'hello this is some text ' )
 
@@ -270,7 +222,6 @@ function! Test_Compl_No_Filetype_Blacklisted()
 
   call test_override( 'ALL', 0 )
   delfunc! Check
-  %bwipeout!
 endfunction
 
 function! OmniFuncTester( findstart, query )
@@ -325,8 +276,6 @@ function! Test_OmniComplete_Filter()
   call setline(1, 'te:' )
   call setpos( '.', [ 0, 1, 3 ] )
   call FeedAndCheckMain( 'ate', 'Check1' )
-
-  %bwipeout!
 endfunction
 
 function! TearDown_Test_OmniComplete_Filter()
@@ -372,7 +321,6 @@ function! Test_OmniComplete_Force()
   call setline(1, 'te' )
   call setpos( '.', [ 0, 1, 3 ] )
   call FeedAndCheckMain( "a\<C-Space>", 'Check1' )
-  %bwipeout!
 endfunction
 
 function! Test_Completion_FixIt()
@@ -413,8 +361,6 @@ function! Test_Completion_FixIt()
 
   call setpos( '.', [ 0, 3, 1 ] )
   call FeedAndCheckMain( "Ado_a\<C-Space>", funcref( 'Check1' ) )
-
-  %bwipeout!
 endfunction
 
 function! Test_Select_Next_Previous_InsertModeMapping()
@@ -475,5 +421,4 @@ function! Test_Select_Next_Previous_InsertModeMapping()
 
   call test_override( 'ALL', 0 )
   iunmap <C-n>
-  %bwipeout!
 endfunction

+ 0 - 2
test/completion.test.vim

@@ -32,6 +32,4 @@ function! Test_Using_Upfront_Resolve()
   endfor
 
   call assert_report( "Didn't find the resolve type in the YcmDebugInfo" )
-
-  %bwipeout!
 endfunction

+ 0 - 3
test/completion_info.test.vim

@@ -46,7 +46,6 @@ function! Test_Using_Ondemand_Resolve()
 
   call assert_report( "Didn't find the resolve type in the YcmDebugInfo" )
 
-  %bwipeout!
 endfunction
 
 function! Test_ResolveCompletion_OnChange()
@@ -112,7 +111,6 @@ function! Test_ResolveCompletion_OnChange()
   call assert_equal( 1, found_getAString )
 
   call test_override( 'ALL', 0 )
-  %bwipeout!
 endfunction
 
 function! Test_DontResolveCompletion_AlreadyResolved()
@@ -162,5 +160,4 @@ function! Test_DontResolveCompletion_AlreadyResolved()
   call assert_false( pumvisible(), 'pumvisible()' )
 
   call test_override( 'ALL', 0 )
-  %bwipeout!
 endfunction

+ 0 - 2
test/completion_noresolve.test.vim

@@ -32,6 +32,4 @@ function! Test_No_Resolve()
   endfor
 
   call assert_report( "Didn't find the resolve type in the YcmDebugInfo" )
-
-  %bwipeout!
 endfunction

+ 1 - 3
test/diagnostics.test.vim

@@ -31,7 +31,6 @@ function! Test_Changing_Filetype_Refreshes_Diagnostics()
   call assert_equal( 1, len( sign_getplaced()[ 0 ][ 'signs' ] ) )
   call assert_equal( 'YcmError', sign_getplaced()[ 0 ][ 'signs' ][ 0 ][ 'name' ] )
   call assert_false( empty( getloclist( 0 ) ) )
-  %bwipeout!
 endfunction
 
 function! Test_MessagePoll_After_LocationList()
@@ -47,7 +46,6 @@ function! Test_MessagePoll_After_LocationList()
   doautocmd TextChanged
   call WaitForAssert( {-> assert_true( empty( sign_getplaced() ) ) } )
   call assert_true( empty( getloclist( 0 ) ) )
-  %bwipeout!
 endfunction
 
 function! Test_MessagePoll_Multiple_Filetypes()
@@ -56,7 +54,7 @@ function! Test_MessagePoll_Multiple_Filetypes()
         \ '/src/com/test/TestLauncher.java', {} )
   call WaitForAssert( {-> assert_true( len( sign_getplaced( '%' )[ 0 ][ 'signs' ] ) ) } )
   let java_signs = sign_getplaced( '%' )[ 0 ][ 'signs' ]
-  vsplit testdata/diagnostics/foo.cpp
+  silent vsplit testdata/diagnostics/foo.cpp
   " Make sure we've left the java buffer
   call assert_equal( java_signs, sign_getplaced( '#' )[ 0 ][ 'signs' ] )
   " Clangd emits two diagnostics for foo.cpp.

+ 5 - 11
test/filesize.test.vim

@@ -4,9 +4,6 @@ function! SetUp()
   let g:ycm_auto_trigger = 1
   let g:ycm_keep_logfiles = 1
   let g:ycm_always_populate_location_list = 1
-  let g:ycm_filetype_blacklist = {
-        \ 'ycm_nofiletype': 1
-        \ }
 
   " diagnostics take ages
   let g:ycm_test_min_delay = 7 
@@ -24,12 +21,10 @@ function! Test_Open_Unsupported_Filetype_Messages()
   let X = join( map( range( 0, 1000 * 1024 + 1 ), {->'X'} ), '' )
   call append( line( '$' ), X )
 
-  w! Xtest
+  silent w! Xtest
 
-  let l:stderr = substitute( execute( 'messages' ), '\n', '\t', 'g' )
+  let l:stderr = substitute( execute( '1messages' ), '\n', '\t', 'g' )
   call assert_notmatch( 'the file exceeded the max size', stderr )
-
-  %bwipeout!
   call delete( 'Xtest' )
 endfunction
 
@@ -39,14 +34,13 @@ function! Test_Open_Supported_Filetype_Messages()
   let X = join( map( range( 0, 1000 * 1024 + 1 ), {->'X'} ), '' )
   call append( line( '$' ), X )
 
-  w! Xtest
-  messages clear
+  silent w! Xtest
   setf cpp
 
-  let l:stderr = substitute( execute( 'messages' ), '\n', '\t', 'g' )
+  let l:stderr = substitute( execute( '1messages' ), '\n', '\t', 'g' )
   call assert_match( 'the file exceeded the max size', stderr )
   call assert_equal( 1, b:ycm_largefile )
+  messages clear
 
-  %bwipeout!
   call delete( 'Xtest' )
 endfunction

+ 0 - 1
test/fixit.test.vim

@@ -34,7 +34,6 @@ function! Test_Ranged_Fixit_Works()
   call assert_match( '        String \(x\|string\) = "Did something useful: "' .
                      \ ' + w.getWidgetInfo();', getline( 34 ) )
   call assert_match( '\t\tSystem.out.println( \(x\|string\) );', getline( 35 ) )
-  %bwipeout!
   delfunction SelectEntry
 endfunction
 

+ 5 - 18
test/hover.test.vim

@@ -134,7 +134,6 @@ function! Test_Hover_Uses_GetDoc()
   normal \D
   call s:CheckPopupVisible( 11, 4, s:python_oneline.GetDoc, '' )
   call popup_clear()
-  %bwipe!
 endfunction
 
 function! Test_Hover_Uses_GetHover()
@@ -159,7 +158,6 @@ EOPYTHON
   call s:CheckPopupNotVisible( 11, 4 )
   call popup_clear()
 
-  %bwipe!
 endfunction
 
 function! Test_Hover_Uses_None()
@@ -177,7 +175,6 @@ EOPYTHON
   call s:CheckPopupNotVisible( 11, 4, v:false )
 
   call popup_clear()
-  %bwipe!
 endfunction
 
 function! Test_Hover_Uses_GetType()
@@ -216,7 +213,6 @@ EOPYTHON
   call s:CheckPopupVisible( 11, 4, s:python_oneline.GetType, 'python' )
   call popup_clear()
 
-  %bwipe!
 endfunction
 
 function! Test_Hover_NonNative()
@@ -234,7 +230,6 @@ function! Test_Hover_NonNative()
   call assert_equal( messages_before, execute( 'messages' ) )
 
   call popup_clear()
-  %bwipe!
 endfunction
 
 function SetUp_Test_Hover_Disabled_NonNative()
@@ -245,13 +240,12 @@ function! Test_Hover_Disabled_NonNative()
   call youcompleteme#test#setup#OpenFile( '_not_a_file', { 'native_ft': 0 } )
   setfiletype NoASupportedFileType
   let messages_before = execute( 'messages' )
-  silent doautocmd CursorHold
+  silent! doautocmd CursorHold
   call s:CheckNoCommandRequest()
   call assert_false( exists( 'b:ycm_hover' ) )
   call assert_equal( messages_before, execute( 'messages' ) )
 
   call popup_clear()
-  %bwipe!
 endfunction
 
 function! SetUp_Test_AutoHover_Disabled()
@@ -266,7 +260,7 @@ function! Test_AutoHover_Disabled()
   call assert_false( exists( 'b:ycm_hover' ) )
 
   call setpos( '.', [ 0, 12, 3 ] )
-  silent doautocmd CursorHold
+  silent! doautocmd CursorHold
   call s:CheckPopupNotVisible( 11, 4, v:false )
   call assert_equal( messages_before, execute( 'messages' ) )
 
@@ -282,7 +276,6 @@ function! Test_AutoHover_Disabled()
   call assert_equal( messages_before, execute( 'messages' ) )
 
   call popup_clear()
-  %bwipeout!
 endfunction
 
 function! Test_Hover_MoveCursor()
@@ -315,7 +308,6 @@ function! Test_Hover_MoveCursor()
   call test_override( 'ALL', 0 )
 
   call popup_clear()
-  %bwipeout!
 endfunction
 
 function! Test_Hover_Dismiss()
@@ -336,7 +328,7 @@ function! Test_Hover_Dismiss()
   call s:CheckPopupNotVisible( 11, 3, v:false )
 
   " Make sure it doesn't come back
-  doautocmd CursorHold
+  silent! doautocmd CursorHold
   call s:CheckPopupNotVisible( 11, 3, v:false )
 
   " Move the cursor (again this is tricky). I couldn't find any tests in vim's
@@ -347,7 +339,6 @@ function! Test_Hover_Dismiss()
   call s:CheckPopupVisible( 11, 3, s:python_oneline.GetDoc, '' )
 
   call popup_clear()
-  %bwipeout!
 endfunction
 
 function! SetUp_Test_Hover_Custom_Syntax()
@@ -377,11 +368,10 @@ function! Test_Hover_Custom_Syntax()
   call s:CheckPopupNotVisibleScreenPos( { 'row': 7, 'col': 9 }, v:false )
 
   call popup_clear()
-  %bwipe!
 endfunction
 
 function! TearDown_Test_Hover_Custom_Syntax()
-  au! MyYCMCustom
+  silent! au! MyYCMCustom
 endfunction
 
 function! SetUp_Test_Hover_Custom_Command()
@@ -407,11 +397,10 @@ function! Test_Hover_Custom_Command()
   call s:CheckPopupVisible( 5, 9, s:cpp_lifetime.GetType, 'cpp' )
 
   call popup_clear()
-  %bwipe!
 endfunction
 
 function! TearDown_Test_Hover_Custom_Command()
-  au! MyYCMCustom
+  silent! au! MyYCMCustom
 endfunction
 
 function! Test_Long_Single_Line()
@@ -435,7 +424,6 @@ function! Test_Long_Single_Line()
   call s:CheckPopupVisible( 33, &columns, v:none, '' )
 
   call popup_clear()
-  %bwipe!
 endfunction
 
 function! Test_Long_Wrapped()
@@ -460,5 +448,4 @@ function! Test_Long_Wrapped()
   call s:CheckPopupNotVisible( 26, &columns, v:false )
 
   call popup_clear()
-  %bwipe!
 endfunction

+ 1 - 1
test/lib/autoload/youcompleteme/test/setup.vim

@@ -28,7 +28,7 @@ function! youcompleteme#test#setup#CleanUp() abort
 endfunction
 
 function! youcompleteme#test#setup#OpenFile( f, kwargs ) abort
-  execute 'edit '
+  silent execute 'edit '
         \ . g:ycm_test_plugin_dir
         \ . '/'
         \ . a:f

+ 47 - 0
test/lib/plugin/completion.vim

@@ -0,0 +1,47 @@
+function! CheckCompletionItems( expected_props, ... )
+  let prop = 'abbr'
+  if a:0 > 0
+    let prop = a:1
+  endif
+
+  let items = complete_info( [ 'items' ] )[ 'items' ]
+  let abbrs = []
+  for item in items
+    call add( abbrs, get( item, prop ) )
+  endfor
+
+  call assert_equal( a:expected_props,
+        \ abbrs,
+        \ 'not matched: '
+        \ .. string( a:expected_props )
+        \ .. ' against '
+        \ .. prop
+        \ .. ' in '
+        \ .. string( items )
+        \ .. ' matching '
+        \ .. string( abbrs ) )
+endfunction
+
+function! FeedAndCheckMain( keys, func )
+  call timer_start( 500, a:func )
+  call feedkeys( a:keys, 'tx!' )
+endfunction
+
+function! FeedAndCheckAgain( keys, func )
+  call timer_start( 500, a:func )
+  call feedkeys( a:keys )
+endfunction
+
+function! WaitForCompletion()
+  call WaitForAssert( {->
+        \ assert_true( pyxeval( 'ycm_state.GetCurrentCompletionRequest() is not None' ) )
+        \ } )
+  call WaitForAssert( {->
+        \ assert_true( pyxeval( 'ycm_state.CompletionRequestReady()' ) )
+        \ } )
+  redraw
+  call WaitForAssert( {->
+        \ assert_true( pumvisible(), 'pumvisible()' )
+        \ }, 10000 )
+endfunction
+

+ 4 - 0
test/lib/plugin/util.vim

@@ -0,0 +1,4 @@
+function! CheckCurrentLine( expected_value )
+  return assert_equal( a:expected_value, getline( '.' ) )
+endfunction
+

+ 17 - 0
test/lib/run_test.vim

@@ -147,10 +147,24 @@ func RunTheTest(test)
 
     au VimLeavePre * call EarlyExit(s:test)
     call ch_log( 'StartTest: ' . a:test )
+
+    messages clear
     exe 'call ' . a:test
+    " We require that tests either don't make errors or that they call messages
+    " clear
+    call assert_true(
+          \ empty( execute( 'messages' ) ),
+          \ 'Test '
+          \ .. a:test
+          \ .. ' produced unexpected messages output '
+          \ .. string( execute( 'messages' ) )
+          \ .. ' (hint: call :messages clear if this is expected, '
+          \ .. 'or use :silent)' )
+
     call ch_log( 'EndTest: ' . a:test )
     au! VimLeavePre
   catch /^\cskipped/
+    let v:errors = []
     call ch_log( 'Skipped: ' . a:test )
     call add(s:messages, '    Skipped')
     call add(s:skipped,
@@ -210,6 +224,9 @@ func RunTheTest(test)
   " Clear any autocommands
   au!
 
+  call test_override( 'ALL', 0 )
+  %bwipe!
+
   " Close any extra tab pages and windows and make the current one not modified.
   while tabpagenr('$') > 1
     quit!

+ 209 - 9
test/signature_help.test.vim

@@ -98,7 +98,6 @@ endfunction
 "   " clean up
 "   call StopVimInTerminal(vim)
 "   call delete('XtestPopup')
-"   %bwipeout!
 " endfunction
 
 function! Test_Enough_Screen_Space()
@@ -175,7 +174,6 @@ function! Test_Signatures_After_Trigger()
         \ } )
 
   call test_override( 'ALL', 0 )
-  %bwipeout!
   delfunc! Check
 endfunction
 
@@ -195,7 +193,7 @@ function! Test_Signatures_With_PUM_NoSigns()
   " https://github.com/vim/vim/issues/4665#event-2480928194
   call test_override( 'char_avail', 1 )
 
-  function Check2( id ) closure
+  function! Check2( id ) closure
     call WaitForAssert( {-> assert_true( pumvisible() ) } )
     call WaitForAssert( {-> assert_notequal( [], complete_info().items ) } )
     call assert_equal( 7, pum_getpos().row )
@@ -251,7 +249,6 @@ function! Test_Signatures_With_PUM_NoSigns()
         \ } )
 
   call test_override( 'ALL', 0 )
-  %bwipeout!
   delfunc! Check
   delfunc! Check2
 endfunction
@@ -331,7 +328,6 @@ function! Test_Signatures_With_PUM_Signs()
         \ } )
 
   call test_override( 'ALL', 0 )
-  %bwipeout!
   delfunc! Check
   delfunc! Check2
 endfunction
@@ -414,7 +410,6 @@ function! Test_Placement_Simple()
   call s:_ClearSigHelp()
 
   call popup_clear()
-  %bwipeout!
 endfunction
 
 function! Test_Placement_MultiLine()
@@ -498,7 +493,6 @@ function! Test_Placement_MultiLine()
   call s:_ClearSigHelp()
 
   call popup_clear()
-  %bwipeout!
 endfunction
 
 function! Test_Signatures_TopLine()
@@ -519,7 +513,6 @@ function! Test_Signatures_TopLine()
   call feedkeys( 'cl(', 'ntx!' )
 
   call test_override( 'ALL', 0 )
-  %bwipeout!
   delfun! Check
 endfunction
 
@@ -574,8 +567,215 @@ function! Test_Signatures_TopLineWithPUM()
   call feedkeys( 'C(', 'ntx!' )
 
   call test_override( 'ALL', 0 )
-  %bwipeout!
 
   delfunc! CheckSigHelpAndTriggerCompletion
   delfunc! CheckCompletionVisibleAndSigHelpHidden
 endfunction
+
+function! SetUp_Test_Semantic_Completion_Popup_With_Sig_Help()
+  set signcolumn=no
+  let g:ycm_add_preview_to_completeopt = 'popup'
+endfunction
+
+function! Test_Semantic_Completion_Popup_With_Sig_Help()
+  call youcompleteme#test#setup#OpenFile(
+        \ 'test/testdata/cpp/complete_with_sig_help.cc', {} )
+  call s:WaitForSigHelpAvailable( 'cpp' )
+
+  call setpos( '.', [ 0, 10, 1 ] )
+
+  " Required to trigger TextChangedI
+  " https://github.com/vim/vim/issues/4665#event-2480928194
+  call test_override( 'char_avail', 1 )
+
+  function! Check( ... )
+    call youcompleteme#test#popup#CheckPopupPosition(
+          \ s:_GetSigHelpWinID(),
+          \ { 'line': 9, 'col': 6, 'visible': 1 } )
+
+    call FeedAndCheckAgain( '"", t.', funcref( 'Check2' ) )
+  endfunction
+
+  function! Check2( ... )
+    call WaitForCompletion()
+    call CheckCompletionItems( [ 'that_is_a_thing', 'this_is_a_thing' ] )
+
+    call youcompleteme#test#popup#CheckPopupPosition(
+          \ s:_GetSigHelpWinID(),
+          \ { 'line': 9, 'col': 6, 'visible': 1 } )
+
+    call CheckCurrentLine( 'printf("", t.' )
+    call FeedAndCheckAgain( "\<Tab>", funcref( 'Check3' ) )
+  endfunction
+
+  function! Check3( ... )
+    " Ensure that we didn't make an error?
+    call WaitForCompletion()
+    call CheckCompletionItems( [ 'that_is_a_thing', 'this_is_a_thing' ] )
+
+    call youcompleteme#test#popup#CheckPopupPosition(
+          \ s:_GetSigHelpWinID(),
+          \ { 'line': 9, 'col': 6, 'visible': 1 } )
+
+    let compl = complete_info()
+    let selected = compl.items[ compl.selected ]
+    call assert_equal( 'that_is_a_thing', selected.word )
+
+    call WaitFor( {->
+          \ popup_findinfo() > 0 &&
+          \ has_key( popup_getpos( popup_findinfo() ), 'visible' ) } )
+    let info_popup_id = popup_findinfo()
+    call assert_true( popup_getpos( info_popup_id )[ 'visible' ] )
+
+    call CheckCurrentLine( 'printf("", t.that_is_a_thing' )
+    call FeedAndCheckAgain( "\<Tab>", funcref( 'Check4' ) )
+
+  endfunction
+
+  function! Check4( ... )
+    " Ensure that we didn't make an error?
+    call WaitForCompletion()
+    call CheckCompletionItems( [ 'that_is_a_thing', 'this_is_a_thing' ] )
+
+    call youcompleteme#test#popup#CheckPopupPosition(
+          \ s:_GetSigHelpWinID(),
+          \ { 'line': 9, 'col': 6, 'visible': 1 } )
+
+    let info_popup_id = popup_findinfo()
+    let compl = complete_info()
+    let selected = compl.items[ compl.selected ]
+    call assert_equal( 'this_is_a_thing', selected.word )
+    call assert_true( popup_getpos( info_popup_id )[ 'visible' ] )
+
+    call CheckCurrentLine( 'printf("", t.this_is_a_thing' )
+
+    call feedkeys( "\<Esc>" )
+  endfunction
+
+  call FeedAndCheckMain( 'iprintf(', funcref( 'Check' ) )
+
+  call test_override( 'ALL', 0 )
+
+  delfunc! Check
+  delfunc! Check2
+  delfunc! Check3
+  delfunc! Check4
+endfunction
+
+function! TearDown_Test_Semantic_Completion_Popup_With_Sig_Help()
+  set signcolumn&
+  unlet! g:ycm_add_preview_to_completeopt
+endfunction
+
+function! SetUp_Test_Semantic_Completion_Popup_With_Sig_Help_EmptyBuf()
+  set signcolumn=no
+  let g:ycm_filetype_whitelist = {
+        \ '*': 1,
+        \ 'ycm_nofiletype': 1
+        \ }
+  silent! call remove( g:ycm_filetype_blacklist, 'ycm_nofiletype' )
+  let g:ycm_add_preview_to_completeopt = 'popup'
+endfunction
+
+function! Test_Semantic_Completion_Popup_With_Sig_Help_EmptyBuf()
+  call youcompleteme#test#setup#OpenFile(
+        \ 'test/testdata/cpp/complete_with_sig_help.cc', {} )
+  call s:WaitForSigHelpAvailable( 'cpp' )
+
+  call setpos( '.', [ 0, 10, 1 ] )
+
+  " Required to trigger TextChangedI
+  " https://github.com/vim/vim/issues/4665#event-2480928194
+  call test_override( 'char_avail', 1 )
+
+  function! Check( ... )
+    call youcompleteme#test#popup#CheckPopupPosition(
+          \ s:_GetSigHelpWinID(),
+          \ { 'line': 9, 'col': 6, 'visible': 1 } )
+
+    call FeedAndCheckAgain( '"", t.', funcref( 'Check2' ) )
+  endfunction
+
+  function! Check2( ... )
+    call WaitForCompletion()
+    call CheckCompletionItems( [ 'that_is_a_thing', 'this_is_a_thing' ] )
+
+    call youcompleteme#test#popup#CheckPopupPosition(
+          \ s:_GetSigHelpWinID(),
+          \ { 'line': 9, 'col': 6, 'visible': 1 } )
+
+    call CheckCurrentLine( 'printf("", t.' )
+    call FeedAndCheckAgain( "\<Tab>", funcref( 'Check3' ) )
+  endfunction
+
+  function! Check3( ... )
+    " Ensure that we didn't make an error?
+    call WaitForCompletion()
+    call CheckCompletionItems( [ 'that_is_a_thing', 'this_is_a_thing' ] )
+
+    " XFAIL: Currently the test fails here because the signature help popup
+    " disappears when the info_popup is displayed. This seems to be because we
+    " end up triggering a FileReadyToParse event inside the info popup (or the
+    " signature popup) due to what appears to be a vim bug - the `buftype` of
+    " the buffer inside the popup starts off as `popup` but shimers to `""` at
+    " some point, making us think it's ok to parse it with ycm_nofiletype is
+    " whitelisted.
+    call youcompleteme#test#popup#CheckPopupPosition(
+          \ s:_GetSigHelpWinID(),
+          \ { 'line': 9, 'col': 6, 'visible': 1 } )
+
+    let compl = complete_info()
+    let selected = compl.items[ compl.selected ]
+    call assert_equal( 'that_is_a_thing', selected.word )
+
+    call WaitFor( {->
+          \ popup_findinfo() > 0 &&
+          \ has_key( popup_getpos( popup_findinfo() ), 'visible' ) } )
+    let info_popup_id = popup_findinfo()
+    call assert_true( popup_getpos( info_popup_id )[ 'visible' ] )
+
+    call CheckCurrentLine( 'printf("", t.that_is_a_thing' )
+    call FeedAndCheckAgain( "\<Tab>", funcref( 'Check4' ) )
+
+  endfunction
+
+  function! Check4( ... )
+    " Ensure that we didn't make an error?
+    call WaitForCompletion()
+    call CheckCompletionItems( [ 'that_is_a_thing', 'this_is_a_thing' ] )
+
+    call youcompleteme#test#popup#CheckPopupPosition(
+          \ s:_GetSigHelpWinID(),
+          \ { 'line': 9, 'col': 6, 'visible': 1 } )
+
+    let info_popup_id = popup_findinfo()
+    let compl = complete_info()
+    let selected = compl.items[ compl.selected ]
+    call assert_equal( 'this_is_a_thing', selected.word )
+    call assert_true( popup_getpos( info_popup_id )[ 'visible' ] )
+
+    call CheckCurrentLine( 'printf("", t.this_is_a_thing' )
+
+    call feedkeys( "\<Esc>" )
+  endfunction
+
+  call FeedAndCheckMain( 'iprintf(', funcref( 'Check' ) )
+
+  call test_override( 'ALL', 0 )
+
+  delfunc! Check
+  delfunc! Check2
+  delfunc! Check3
+  delfunc! Check4
+
+  throw 'SKIPPED: XFAIL: This test is expected to fail due to '
+        \ .. 'https://github.com/ycm-core/YouCompleteMe/issues/3781'
+
+endfunction
+
+function! TearDown_Test_Semantic_Completion_Popup_With_Sig_Help_EmptyBuf()
+  set signcolumn&
+  unlet! g:ycm_add_preview_to_completeopt
+  call remove( g:ycm_filetype_whitelist, 'ycm_nofiletype' )
+  let g:ycm_filetype_blacklist[ 'ycm_nofiletype' ] = 1
+endfunction

+ 11 - 0
test/testdata/cpp/complete_with_sig_help.cc

@@ -0,0 +1,11 @@
+#include <stdio.h>
+
+struct Test
+{
+  int this_is_a_thing; int that_is_a_thing;
+};
+
+int main() {
+  Test t;
+
+}