Explorar o código

Merge pull request #3785 from puremourning/fix-sig-help-traceback

[READY] Fix signature help traceback when using preview popup
mergify[bot] %!s(int64=4) %!d(string=hai) anos
pai
achega
c6227cb8af

+ 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

+ 17 - 72
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()
+  call youcompleteme#test#setup#PushGlobal( 'ycm_filetype_whitelist', {
+        \ '*': 1,
+        \ 'ycm_nofiletype': 1
+        \ } )
+  call youcompleteme#test#setup#PushGlobal( 'ycm_filetype_blacklist', {} )
 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 youcompleteme#test#setup#PopGlobal( 'ycm_filetype_whitelist' )
+  call youcompleteme#test#setup#PopGlobal( 'ycm_filetype_blacklist' )
 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 )
@@ -281,9 +232,9 @@ function! OmniFuncTester( findstart, query )
 endfunction
 
 function! SetUp_Test_OmniComplete_Filter()
-  let g:ycm_semantic_triggers = {
+  call youcompleteme#test#setup#PushGlobal( 'ycm_semantic_triggers', {
         \ 'omnifunc_test': [ ':', '.' ]
-        \ }
+        \ } )
 endfunction
 
 function! Test_OmniComplete_Filter()
@@ -325,12 +276,10 @@ function! Test_OmniComplete_Filter()
   call setline(1, 'te:' )
   call setpos( '.', [ 0, 1, 3 ] )
   call FeedAndCheckMain( 'ate', 'Check1' )
-
-  %bwipeout!
 endfunction
 
 function! TearDown_Test_OmniComplete_Filter()
-  unlet g:ycm_semantic_triggers
+  call youcompleteme#test#setup#PopGlobal( 'ycm_semantic_triggers' )
 endfunction
 
 function! Test_OmniComplete_Force()
@@ -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

+ 32 - 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
@@ -63,3 +63,34 @@ function! youcompleteme#test#setup#OpenFile( f, kwargs ) abort
 
   " FIXME: We need a much more robust way to wait for the server to be ready
 endfunction
+
+let s:g_stack = {}
+
+function! youcompleteme#test#setup#PushGlobal( name, value )
+  if !has_key( s:g_stack, a:name )
+    let s:g_stack[ a:name ] = []
+  endif
+
+  let old_value = get( g:, a:name, v:null )
+  call add( s:g_stack[ a:name ], old_value )
+  call extend( g:, { a:name: a:value  } )
+
+  return old_value
+endfunction
+
+function! youcompleteme#test#setup#PopGlobal( name )
+  if !has_key( s:g_stack, a:name ) || len( s:g_stack[ a:name ] ) == 0
+    return v:null
+  endif
+
+  let old_value = s:g_stack[ a:name ][ -1 ]
+  call remove( s:g_stack[ a:name ], -1 )
+
+  if old_value is v:null
+    silent! call remove( g:, a:name )
+  else
+    call extend( g:, { a:name: old_value  } )
+  endif
+
+  return old_value
+endfunction

+ 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!

+ 211 - 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,217 @@ 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
+  call youcompleteme#test#setup#PushGlobal( '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&
+  call youcompleteme#test#setup#PopGlobal( 'ycm_add_preview_to_completeopt' )
+endfunction
+
+function! SetUp_Test_Semantic_Completion_Popup_With_Sig_Help_EmptyBuf()
+  set signcolumn=no
+  call youcompleteme#test#setup#PushGlobal( 'ycm_filetype_whitelist', {
+        \ '*': 1,
+        \ 'ycm_nofiletype': 1
+        \ } )
+  call youcompleteme#test#setup#PushGlobal( 'ycm_filetype_blacklist', {} )
+  call youcompleteme#test#setup#PushGlobal( '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&
+  call youcompleteme#test#setup#PopGlobal( 'ycm_filetype_whitelist' )
+  call youcompleteme#test#setup#PopGlobal( 'ycm_filetype_blacklist' )
+  call youcompleteme#test#setup#PopGlobal( 'ycm_add_preview_to_completeopt' )
+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;
+
+}