Browse Source

Request symbols from all availble filetypes and merge the results

Ben Jackson 3 years ago
parent
commit
1470710674

+ 2 - 19
autoload/youcompleteme.vim

@@ -483,15 +483,6 @@ function! s:DisableOnLargeFile( buffer )
   return b:ycm_largefile
 endfunction
 
-function! s:HasAnyKey( dict, keys )
-  for key in a:keys
-    if has_key( a:dict, key )
-      return 1
-    endif
-  endfor
-  return 0
-endfunction
-
 function! s:PropertyTypeNotDefined( type )
   return exists( '*prop_type_add' ) &&
     \ index( prop_type_list(), a:type ) == -1
@@ -509,21 +500,13 @@ function! s:AllowedToCompleteInBuffer( buffer )
     let filetype = 'ycm_nofiletype'
   endif
 
-  let whitelist_allows = type( g:ycm_filetype_whitelist ) != v:t_dict ||
-        \ has_key( g:ycm_filetype_whitelist, '*' ) ||
-        \ s:HasAnyKey( g:ycm_filetype_whitelist, split( filetype, '\.' ) )
-  let blacklist_allows = type( g:ycm_filetype_blacklist ) != v:t_dict ||
-        \ !s:HasAnyKey( g:ycm_filetype_blacklist, split( filetype, '\.' ) )
-
-  let allowed = whitelist_allows && blacklist_allows
+  let allowed = youcompleteme#filetypes#AllowedForFiletype( filetype )
 
   if !allowed || s:DisableOnLargeFile( a:buffer )
     return 0
   endif
 
-  if allowed
-    let s:previous_allowed_buffer_number = bufnr( a:buffer )
-  endif
+  let s:previous_allowed_buffer_number = bufnr( a:buffer )
   return allowed
 endfunction
 

+ 36 - 0
autoload/youcompleteme/filetypes.vim

@@ -0,0 +1,36 @@
+" Copyright (C) 2011-2018 YouCompleteMe contributors
+"
+" This file is part of YouCompleteMe.
+"
+" YouCompleteMe is free software: you can redistribute it and/or modify
+" it under the terms of the GNU General Public License as published by
+" the Free Software Foundation, either version 3 of the License, or
+" (at your option) any later version.
+"
+" YouCompleteMe is distributed in the hope that it will be useful,
+" but WITHOUT ANY WARRANTY; without even the implied warranty of
+" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+" GNU General Public License for more details.
+"
+" You should have received a copy of the GNU General Public License
+" along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
+
+
+function! s:HasAnyKey( dict, keys ) abort
+  for key in a:keys
+    if has_key( a:dict, key )
+      return 1
+    endif
+  endfor
+  return 0
+endfunction
+
+function! youcompleteme#filetypes#AllowedForFiletype( filetype ) abort
+  let whitelist_allows = type( g:ycm_filetype_whitelist ) != v:t_dict ||
+        \ has_key( g:ycm_filetype_whitelist, '*' ) ||
+        \ s:HasAnyKey( g:ycm_filetype_whitelist, split( a:filetype, '\.' ) )
+  let blacklist_allows = type( g:ycm_filetype_blacklist ) != v:t_dict ||
+        \ !s:HasAnyKey( g:ycm_filetype_blacklist, split( a:filetype, '\.' ) )
+
+  return whitelist_allows && blacklist_allows
+endfunction

+ 189 - 68
autoload/youcompleteme/finder.vim

@@ -97,8 +97,10 @@ scriptencoding utf-8
 "    'raw_results', and store the results in 'results', then call
 "    "HandleSymbolSearchResults"
 "
-"  - SearchWorkspace - perform GoToSymbol request, and store the results in
-"    'results', then call "HandleSymbolSearchResults"
+"  - SearchWorkspace - perform GoToSymbol request for all open filetypes,
+"     and store the results in 'raw_results' as a dict mapping
+"     filetype->results. Merge the results in to 'results', then call
+"     "HandleSymbolSearchResults"
 "
 "  - HandleSymbolSearchResults - redraw the popup with the 'results'
 "
@@ -162,6 +164,7 @@ function! youcompleteme#finder#FindSymbol( scope ) abort
             \ { 'highlight': s:highlight_group_for_symbol_kind[ k ] } )
     endfor
     call prop_type_add( 'YCM-symbol-file', { 'highlight': 'Comment' } )
+    call prop_type_add( 'YCM-symbol-filetype', { 'highlight': 'Special' } )
     call prop_type_add( 'YCM-symbol-line-num', { 'highlight': 'Number' } )
     let s:initialized_text_properties = v:true
   endif
@@ -171,8 +174,7 @@ function! youcompleteme#finder#FindSymbol( scope ) abort
         \ 'query': '',
         \ 'results': [],
         \ 'raw_results': v:none,
-        \ 'waiting': 0,
-        \ 'pending': 0,
+        \ 'pending': [],
         \ 'winid': win_getid(),
         \ 'bufnr': bufnr(),
         \ 'prompt_bufnr': -1,
@@ -216,12 +218,10 @@ function! youcompleteme#finder#FindSymbol( scope ) abort
   let s:find_symbol_status.id = popup_create( 'Type to query for stuff', opts )
 
   " Kick off the request now
-  let s:find_symbol_status.waiting = 1
   if a:scope ==# 'document'
-    call s:StartSpinner()
     call s:RequestDocumentSymbols()
   else
-    call s:SearchWorkspace( '' )
+    call s:SearchWorkspace( '', v:true )
   endif
 
   let bufnr = bufadd( '_ycm_filter_' )
@@ -239,6 +239,7 @@ function! youcompleteme#finder#FindSymbol( scope ) abort
   augroup YCMPromptFindSymbol
     autocmd!
     autocmd TextChanged,TextChangedI <buffer> call s:OnQueryTextChanged()
+    autocmd TextChanged,TextChanged <buffer> call s:OnQueryTextChanged()
     autocmd WinLeave <buffer> call s:Cancel()
     autocmd CmdLineEnter <buffer> call s:Cancel()
   augroup END
@@ -253,9 +254,11 @@ function! s:OnQueryTextChanged() abort
   let bufnr = s:find_symbol_status.prompt_bufnr
   let query = getbufline( bufnr, '$' )[ 0 ]
   let s:find_symbol_status.query = query[ len( s:prompt ) : ]
-  let s:find_symbol_status.pending = 1
-  call s:RequeryFinderPopup()
-  setlocal nomodified
+
+  " really, re-query if we can
+  call s:RequeryFinderPopup( v:true )
+
+  call win_execute( s:find_symbol_status.prompt_winid, 'setlocal nomodified' )
 endfunction
 
 
@@ -389,7 +392,7 @@ function! s:PopupClosed( id, selected ) abort
   endif
 
 
-  call s:StopSpinner()
+  call s:EndRequest()
   let s:find_symbol_status.id = -1
 endfunction
 
@@ -399,9 +402,7 @@ endfunction
 
 " Render a set of results returned from the filter/search function
 function! s:HandleSymbolSearchResults( results ) abort
-  let s:find_symbol_status.waiting = 0
   let s:find_symbol_status.results = []
-  call s:StopSpinner()
 
   if s:find_symbol_status.id < 0
     " Popup was closed, ignore this event
@@ -410,7 +411,9 @@ function! s:HandleSymbolSearchResults( results ) abort
 
   let s:find_symbol_status.results = a:results
   call s:RedrawFinderPopup()
-  call s:RequeryFinderPopup()
+
+  " Re-query but no change in the query text
+  call s:RequeryFinderPopup( v:false )
 endfunction
 
 
@@ -435,6 +438,20 @@ function! s:RedrawFinderPopup() abort
 
     let buffer = []
 
+    let len_filetype = 0
+
+    for result in s:find_symbol_status.results
+      let len_filetype = max( [ len_filetype, len( result[ 'filetype' ] ) ] )
+    endfor
+
+    if len_filetype > 0
+      let filetype_sep = ' '
+    else
+      let filetype_sep = ''
+    endif
+
+    let available_width = popup_width - len_filetype - len( filetype_sep )
+
     for result in s:find_symbol_status.results
       " Calculate  the text to use. Try and include the full path and line
       " number, (right aligned), but truncate if there isn't space for the
@@ -471,30 +488,54 @@ function! s:RedrawFinderPopup() abort
       let path = fnamemodify( result[ 'filepath' ], ':.' )
                \ .. ':'
                \ .. line_num
+      let path_includes_line = 1
 
-      let spaces = popup_width - len( desc ) - len( path )
-      let spacing = 8
+      let spaces = available_width - len( desc ) - len( path )
+      let spacing = 4
       if spaces < spacing
         let spaces = spacing
-        let path_len_to_use = popup_width - spacing - len( desc ) - 3
-        if path_len_to_use > 0
-          let path = '...' . strpart( path, len( path ) - path_len_to_use )
+        let space_for_path = available_width - spacing - len( desc )
+        let path_includes_line = space_for_path - 3 > len( line_num ) + 1
+        if space_for_path > 3
+          let path = '...' . strpart( path, len( path ) - space_for_path + 3 )
+        elseif space_for_path <= 0
+          let path = ''
         else
-          let path = '...:' .. line_num
+          let path_includes_line = 0
+          let path = '...'
         endif
       endif
-      let line = desc .. repeat( ' ', spaces ) .. path
-      call add( buffer, {
-            \ 'text': line,
-            \ 'props': props + [
-              \ { 'col': popup_width - len( path ) + 1,
+
+      let line = desc
+             \ .. repeat( ' ', spaces )
+             \ .. path
+             \ .. filetype_sep
+             \ .. result[ 'filetype' ]
+
+      if len( path ) > 0
+        let props += [
+              \ { 'col': available_width - len( path ) + 1,
               \   'length': len( path ) - len( line_num ),
               \   'type': 'YCM-symbol-file' },
-              \ { 'col': popup_width - len( line_num ) + 1,
-              \   'length': len( line_num ),
-              \   'type': 'YCM-symbol-line-num' }
               \ ]
-            \ } )
+        if path_includes_line
+          let props += [
+                \ { 'col': available_width - len( line_num ) + 1,
+                \   'length': len( line_num ),
+                \   'type': 'YCM-symbol-line-num' },
+                \ ]
+        endif
+      endif
+
+      if len_filetype > 0
+        let props += [
+            \ { 'col': popup_width - len_filetype + len( filetype_sep ),
+            \   'length': len_filetype,
+            \   'type': 'YCM-symbol-filetype' },
+            \ ]
+      endif
+
+      call add( buffer, { 'text': line, 'props': props } )
     endfor
 
     call popup_settext( s:find_symbol_status.id, buffer )
@@ -548,28 +589,44 @@ endfunction
 
 
 " Re-query or re-filter by calling the filter function
-function! s:RequeryFinderPopup() abort
+function! s:RequeryFinderPopup( new_query ) abort
   " Update the title even if we delay the query, as this makes the UI feel
   " snappy
   call s:SetTitle()
 
-  if s:find_symbol_status.waiting == 1
-    let s:find_symbol_status.pending = 1
-  elseif s:find_symbol_status.pending == 1
-    let s:find_symbol_status.pending = 0
-    let s:find_symbol_status.waiting = 1
-    call win_execute( s:find_symbol_status.winid,
-          \ 'call s:find_symbol_status.query_func('
-          \ . 's:find_symbol_status.query )' )
-  endif
+  " FIXME: This is feeling a bit shitty now. The result of this is that we have
+  " to get rsponses from a full round of servers before we send another query.
+  " That makes updates feel laggy if you have say python or typescript files
+  " (whose engines are super slow) at the same time as some cc files (whose
+  " engine is extremely fast). You  realy want there to be a "pending" flag _per
+  " filetype_ and for all of this to be managed by the query_func, e.g.
+  "
+  " remove waiting
+  " pending is owned by the query funcs
+  " this function just calls the query func.
+  "
+  " For workspace:
+  "  - waiting if any( raw_results[ x ] is none for x in raw_results )
+  "  - pending if pending[ filetype ]
+  "
+  " For document:
+  "   - waiting if raw_results is v:none
+  "   - pending if pending
+  "
+  " We already pass still_waiting to the result func, so that can continue
+  "
+  call win_execute( s:find_symbol_status.winid,
+        \ 'call s:find_symbol_status.query_func('
+        \ . 's:find_symbol_status.query,'
+        \ . 'a:new_query )' )
 endfunction
 
-function! s:ParseGoToResponse( results ) abort
+function! s:ParseGoToResponse( filetype, results ) abort
   if type( a:results ) == v:t_none || empty( a:results )
     let results = []
   elseif type( a:results ) != v:t_list
     if type( a:results ) == v:t_dict && has_key( a:results, 'error' )
-      let results = {}
+      let results = []
     else
       let results = [ a:results ]
     endif
@@ -577,12 +634,10 @@ function! s:ParseGoToResponse( results ) abort
     let results = a:results
   endif
 
-  call map( results,
-        \ { i,v -> extend( v,
-              \ { 'key': v->get( 'extra_data',
-              \                  {} )->get( 'name',
-              \                             v[ 'description' ] ) } ) } )
-
+  call map( results, { _, r -> extend( r, {
+      \   'key': r->get( 'extra_data', {} )->get( 'name', r[ 'description' ] ),
+      \   'filetype': a:filetype
+      \ } ) } )
   return results
 endfunction
 
@@ -590,8 +645,8 @@ endfunction
 
 " Spinner {{{
 
-function! s:StartSpinner() abort
-  call s:StopSpinner()
+function! s:StartRequest() abort
+  call s:EndRequest()
 
   let s:find_symbol_status.spinner = 0
   let s:find_symbol_status.spinner_timer = timer_start( s:spinner_delay,
@@ -600,8 +655,9 @@ function! s:StartSpinner() abort
   call s:SetTitle()
 endfunction
 
-function! s:StopSpinner() abort
+function! s:EndRequest() abort
   call timer_stop( s:find_symbol_status.spinner_timer )
+
   let s:find_symbol_status.spinner_timer = -1
 
   call s:SetTitle()
@@ -621,19 +677,77 @@ endfunction
 
 " Workspace search {{{
 
-function! s:SearchWorkspace( query ) abort
-  call s:StartSpinner()
+function! s:SearchWorkspace( query, new_query ) abort
 
-  let s:find_symbol_status.raw_results = v:none
-  call youcompleteme#GetRawCommandResponseAsync(
-        \ function( 's:HandleWorkspaceSymbols' ),
-        \ 'GoToSymbol',
-        \ a:query )
+  if a:new_query
+    if s:find_symbol_status.raw_results is# v:none
+      let raw_results = {}
+    else
+      let raw_results = copy( s:find_symbol_status.raw_results )
+    endif
+
+    let s:find_symbol_status.raw_results = {}
+    let s:find_symbol_status.pending = []
+
+    let ft_buffer_map = py3eval( 'vimsupport.AllOpenedFiletypes()' )
+    for ft in keys( ft_buffer_map )
+      if !youcompleteme#filetypes#AllowedForFiletype( ft )
+        continue
+      endif
+
+      let s:find_symbol_status.raw_results[ ft ] = v:none
+      if has_key( raw_results, ft ) && raw_results[ ft ] is# v:none
+        call add( s:find_symbol_status.pending,
+                \ [ ft, ft_buffer_map[ ft ][ 0 ] ] )
+      else
+        call youcompleteme#GetRawCommandResponseAsync(
+              \ function( 's:HandleWorkspaceSymbols', [ ft ] ),
+              \ 'GoToSymbol',
+              \ '--bufnr=' . ft_buffer_map[ ft ][ 0 ],
+              \ 'ft=' . ft,
+              \ a:query )
+      endif
+    endfor
+
+    if !empty( s:find_symbol_status.raw_results )
+      " We sent some requests
+      call s:StartRequest()
+    endif
+  else
+    for [ ft, bufnr ] in copy( s:find_symbol_status.pending )
+      if s:find_symbol_status.raw_results[ ft ] isnot# v:none
+        call filter( s:find_symbol_status.pending, { v -> v !=# ft } )
+        let s:find_symbol_status.raw_results[ ft ] = v:none
+        call youcompleteme#GetRawCommandResponseAsync(
+              \ function( 's:HandleWorkspaceSymbols', [ ft ] ),
+              \ 'GoToSymbol',
+              \ '--bufnr=' . bufnr,
+              \ 'ft=' . ft,
+              \ a:query )
+      endif
+    endfor
+  endif
 endfunction
 
 
-function! s:HandleWorkspaceSymbols( results ) abort
-  let results = s:ParseGoToResponse( a:results )
+function! s:HandleWorkspaceSymbols( filetype, results ) abort
+
+  let s:find_symbol_status.raw_results[ a:filetype ] =
+        \ s:ParseGoToResponse( a:filetype, a:results )
+
+  " Collate the results from each filetype
+  let results = []
+  let waiting = 0
+  for ft in keys( s:find_symbol_status.raw_results )
+    if s:find_symbol_status.raw_results[ ft ] is v:none
+      let waiting = 1
+      continue
+    endif
+
+    call extend( results, s:find_symbol_status.raw_results[ ft ] )
+  endfor
+
+  let query = s:find_symbol_status.query
 
   if g:ycm_refilter_workspace_symbols && !empty( results )
     " This is kinda wonky, but seems to work well enough.
@@ -657,12 +771,14 @@ function! s:HandleWorkspaceSymbols( results ) abort
     " option to disable this re-filter/sort.
     "
     let results = py3eval(
-          \ 'ycm_state.FilterAndSortItems( '
-          \ . ' vim.eval( "results" ),'
-          \ . ' "description",'
-          \ . ' vim.eval( "s:find_symbol_status.query" ) )' )
+          \ 'ycm_state.FilterAndSortItems( vim.eval( "results" ),'
+          \ .                              ' "description",'
+          \ .                              ' vim.eval( "query" ) )' )
   endif
 
+  if !waiting
+    call s:EndRequest()
+  endif
   eval s:HandleSymbolSearchResults( results )
 endfunction
 
@@ -670,15 +786,18 @@ endfunction
 
 " Document Search {{{
 
-function! s:SearchDocument( query ) abort
+function! s:SearchDocument( query, new_query ) abort
+  if !a:new_query
+    return
+  endif
+
   if type( s:find_symbol_status.raw_results ) == v:t_none
-    call s:StopSpinner()
     call popup_settext( s:find_symbol_status.id,
           \ 'No symbols found in document' )
     return
   endif
 
-  call s:StartSpinner()
+  " No spinner, because this is actually a synchronous call
 
   " Call filter_and_sort_candidates on the results (synchronously)
   let response = py3eval(
@@ -692,6 +811,7 @@ endfunction
 
 
 function! s:RequestDocumentSymbols()
+  call s:StartRequest()
   call youcompleteme#GetRawCommandResponseAsync(
         \ function( 's:HandleDocumentSymbols' ),
         \ 'GoToDocumentOutline' )
@@ -699,8 +819,9 @@ endfunction
 
 
 function! s:HandleDocumentSymbols( results ) abort
-  let s:find_symbol_status.raw_results = s:ParseGoToResponse( a:results )
-  call s:SearchDocument( '' )
+  call s:EndRequest()
+  let s:find_symbol_status.raw_results = s:ParseGoToResponse( '', a:results )
+  call s:SearchDocument( '', v:true )
 endfunction
 
 " }}}

+ 10 - 0
python/ycm/vimsupport.py

@@ -784,6 +784,16 @@ def EscapeForVim( text ):
   return ToUnicode( text.replace( "'", "''" ) )
 
 
+def AllOpenedFiletypes():
+  """Returns a dict mapping filetype to list of buffer numbers for all open
+  buffers"""
+  filetypes = defaultdict( list )
+  for buffer in vim.buffers:
+    for filetype in FiletypesForBuffer( buffer ):
+      filetypes[ filetype ].append( buffer.number )
+  return filetypes
+
+
 def CurrentFiletypes():
   filetypes = vim.eval( "&filetype" )
   if not filetypes:

+ 268 - 15
test/finder.test.vim

@@ -32,11 +32,10 @@ function! Test_WorkspaceSymbol_Basic()
 
   function SelectItem( ... )
     let id = youcompleteme#finder#GetState().id
-    let o = popup_getoptions( id )
 
     call WaitForAssert( { ->
           \ assert_equal( ' [X] Search for symbol: thisisathing ',
-          \ o.title  ) },
+          \ popup_getoptions( id ).title  ) },
           \ 10000 )
 
     call WaitForAssert( { -> assert_equal( 1, line( '$', id ) ) } )
@@ -81,11 +80,10 @@ function! Test_DocumentSymbols_Basic()
 
   function SelectItem( ... )
     let id = youcompleteme#finder#GetState().id
-    let o = popup_getoptions( id )
 
     call WaitForAssert( { ->
           \ assert_equal( ' [X] Search for symbol: thisisathing ',
-          \ o.title  ) },
+          \ popup_getoptions( id ).title  ) },
           \ 10000 )
 
     call WaitForAssert( { -> assert_equal( 1, line( '$', id ) ) } )
@@ -136,11 +134,10 @@ function! Test_Cancel_DocumentSymbol()
 
   function SelectItem( ... )
     let id = youcompleteme#finder#GetState().id
-    let o = popup_getoptions( id )
 
     call WaitForAssert( { ->
           \ assert_equal( ' [X] Search for symbol: thisisathing ',
-          \ o.title  ) },
+          \ popup_getoptions( id ).title  ) },
           \ 10000 )
 
     call WaitForAssert( { -> assert_equal( 1, line( '$', id ) ) } )
@@ -189,11 +186,10 @@ function! Test_EmptySearch()
 
   function SelectNothing( ... )
     let id = youcompleteme#finder#GetState().id
-    let o = popup_getoptions( id )
 
     call WaitForAssert( { ->
           \ assert_equal( ' [X] Search for symbol: nothingshouldmatchthis ',
-          \ o.title  ) },
+          \ popup_getoptions( id ).title  ) },
           \ 10000 )
 
     call WaitForAssert( { -> assert_equal( 1, line( '$', id ) ) } )
@@ -205,13 +201,12 @@ function! Test_EmptySearch()
 
   function ChangeSearch( ... )
     let id = youcompleteme#finder#GetState().id
-    let o = popup_getoptions( id )
 
     " Hitting enter with nothing to select clears the prompt, because prompt
     " buffer
     call WaitForAssert( { ->
           \ assert_equal( ' [X] Search for symbol: notarealthing ',
-          \ o.title  ) },
+          \ popup_getoptions( id ).title  ) },
           \ 10000 )
     call assert_equal( 'No results', getbufline( winbufnr( id ), '$' )[ 0 ] )
 
@@ -223,11 +218,10 @@ function! Test_EmptySearch()
   let popup_id = -1
   function TestUpDownSelect( ... ) closure
     let popup_id = youcompleteme#finder#GetState().id
-    let o = popup_getoptions( popup_id )
 
     call WaitForAssert( { ->
           \ assert_equal( ' [X] Search for symbol: tiat ',
-          \ o.title  ) },
+          \ popup_getoptions( popup_id ).title  ) },
           \ 10000 )
     call WaitForAssert( { -> assert_equal( 2, line( '$', popup_id ) ) } )
 
@@ -450,11 +444,10 @@ function! Test_NoFileType_NoCompletionIn_PromptBuffer()
 
   function! CheckNoPopup( ... )
     let id = youcompleteme#finder#GetState().id
-    let o = popup_getoptions( id )
 
     call WaitForAssert( { ->
-            \ assert_equal( ' [X] Search for symbol: thisisathing ', o.title )
-          \ },
+            \ assert_equal( ' [X] Search for symbol: thisisathing ',
+            \ popup_getoptions( id ).title  ) },
           \ 10000 )
 
     call WaitForAssert( { -> assert_equal( 1, line( '$', id ) ) } )
@@ -481,3 +474,263 @@ function! Test_NoFileType_NoCompletionIn_PromptBuffer()
   delfunct! PutQuery
   delfunct! CheckNoPopup
 endfunction
+
+function! Test_MultipleFileTypes()
+  call youcompleteme#test#setup#OpenFile(
+        \ '/test/testdata/cpp/complete_with_sig_help.cc', {} )
+  split
+  call youcompleteme#test#setup#OpenFile( '/test/testdata/python/doc.py', {} )
+
+  let original_win = winnr()
+  let b = bufnr()
+  let l = winlayout()
+
+  function! PutQuery( ... )
+    " Wait for the current buffer to be a prompt buffer
+    call WaitForAssert( { -> assert_equal( 'prompt', &buftype ) } )
+    call WaitForAssert( { -> assert_equal( 'i', mode() ) } )
+
+    let popup_id = youcompleteme#finder#GetState().id
+    call WaitForAssert( { ->
+          \ assert_equal( ' [X] Search for symbol: thiswillnotmatchanything ',
+          \ popup_getoptions( popup_id ).title  ) },
+          \ 10000 )
+
+
+    call WaitForAssert( { -> assert_true(
+          \ youcompleteme#finder#GetState().id != -1 ) } )
+
+    let id = youcompleteme#finder#GetState().id
+    call assert_equal( 'No results', getbufline( winbufnr( id ), '$' )[ 0 ] )
+    call FeedAndCheckAgain( "\<C-u>thisisathing", funcref( 'CheckCpp' ) )
+  endfunction
+
+  function! CheckCpp( ... )
+    let popup_id = youcompleteme#finder#GetState().id
+
+    " Python can be _really_ slow
+    call WaitForAssert( { ->
+          \ assert_equal( ' [X] Search for symbol: thisisathing ',
+          \ popup_getoptions( popup_id ).title  ) },
+          \ 10000 )
+
+    call WaitForAssert( { -> assert_equal( 1, line( '$', popup_id ) ) } )
+    call assert_equal( 0, youcompleteme#finder#GetState().selected )
+    call assert_equal( 'this_is_a_thing',
+          \ youcompleteme#finder#GetState().results[
+          \   youcompleteme#finder#GetState().selected ].extra_data.name )
+
+    " Wait for the current buffer to be a prompt buffer
+    call WaitForAssert( { -> assert_equal( 'prompt', &buftype ) } )
+    call WaitForAssert( { -> assert_equal( 'i', mode() ) } )
+
+    call FeedAndCheckAgain(
+          \ "\<C-u>Really_Long_Method",
+          \ funcref( 'CheckPython' ) )
+  endfunction
+
+  function! CheckPython( ... )
+    let popup_id = youcompleteme#finder#GetState().id
+
+    " Python can be _really_ slow
+    call WaitForAssert( { ->
+          \ assert_equal( ' [X] Search for symbol: Really_Long_Method ',
+          \ popup_getoptions( popup_id ).title ) },
+          \ 10000 )
+
+    call WaitForAssert( { -> assert_equal( 2, line( '$', popup_id ) ) },
+                      \ 10000 )
+    call assert_equal( 0, youcompleteme#finder#GetState().selected )
+    call assert_equal( 'def Really_Long_Method',
+          \ youcompleteme#finder#GetState().results[
+          \   youcompleteme#finder#GetState().selected ].description )
+
+    call feedkeys( "\<C-c>")
+  endfunction
+
+
+  " <Leader> is \ - this calls <Plug>(YCMFindSymbolInWorkspace)
+  call FeedAndCheckMain( '\\wthiswillnotmatchanything', funcref( 'PutQuery' ) )
+
+  call WaitForAssert( { -> assert_equal( l, winlayout() ) } )
+  call WaitForAssert( { -> assert_equal( original_win, winnr() ) } )
+  call assert_equal( b, bufnr() )
+endfunction
+
+function! Test_MultipleFileTypes_CurrentNotSemantic()
+  call youcompleteme#test#setup#OpenFile(
+        \ '/test/testdata/cpp/complete_with_sig_help.cc', {} )
+  split
+  call youcompleteme#test#setup#OpenFile( '/test/testdata/python/doc.py', {} )
+  split
+  " Current buffer is a ycm_nofiletype, which ycm is blacklisted in
+  " but otherwise we behave the same as before with the exception that we open
+  " the python file in the current window
+
+  let original_win = winnr()
+  let b = bufnr()
+  let l = winlayout()
+
+  function! PutQuery( ... )
+    " Wait for the current buffer to be a prompt buffer
+    call WaitForAssert( { -> assert_equal( 'prompt', &buftype ) } )
+    call WaitForAssert( { -> assert_equal( 'i', mode() ) } )
+
+    call WaitForAssert( { -> assert_true(
+          \ youcompleteme#finder#GetState().id != -1 ) } )
+
+    let popup_id = youcompleteme#finder#GetState().id
+    call WaitForAssert( { ->
+          \ assert_equal( ' [X] Search for symbol: thiswillnotmatchanything ',
+          \ popup_getoptions( popup_id ).title  ) },
+          \ 10000 )
+
+
+    let id = youcompleteme#finder#GetState().id
+    call assert_equal( 'No results', getbufline( winbufnr( id ), '$' )[ 0 ] )
+    call FeedAndCheckAgain( "\<C-u>thisisathing", funcref( 'CheckCpp' ) )
+  endfunction
+
+  function! CheckCpp( ... )
+    let popup_id = youcompleteme#finder#GetState().id
+
+    " Python can be _really_ slow
+    call WaitForAssert( { ->
+          \ assert_equal( ' [X] Search for symbol: thisisathing ',
+          \ popup_getoptions( popup_id ).title  ) },
+          \ 10000 )
+
+    call WaitForAssert( { -> assert_equal( 1, line( '$', popup_id ) ) } )
+    call assert_equal( 0, youcompleteme#finder#GetState().selected )
+    call assert_equal( 'this_is_a_thing',
+          \ youcompleteme#finder#GetState().results[
+          \   youcompleteme#finder#GetState().selected ].extra_data.name )
+
+    " Wait for the current buffer to be a prompt buffer
+    call WaitForAssert( { -> assert_equal( 'prompt', &buftype ) } )
+    call WaitForAssert( { -> assert_equal( 'i', mode() ) } )
+
+    call FeedAndCheckAgain(
+          \ "\<C-u>Really_Long_Method",
+          \ funcref( 'CheckPython' ) )
+  endfunction
+
+  function! CheckPython( ... )
+    let popup_id = youcompleteme#finder#GetState().id
+
+    " Python can be _really_ slow
+    call WaitForAssert( { ->
+          \ assert_equal( ' [X] Search for symbol: Really_Long_Method ',
+          \ popup_getoptions( popup_id ).title ) },
+          \ 10000 )
+
+    call WaitForAssert( { -> assert_equal( 2, line( '$', popup_id ) ) },
+                      \ 10000 )
+    call assert_equal( 0, youcompleteme#finder#GetState().selected )
+    call assert_equal( 'def Really_Long_Method',
+          \ youcompleteme#finder#GetState().results[
+          \   youcompleteme#finder#GetState().selected ].description )
+
+    call feedkeys( "\<CR>")
+  endfunction
+
+
+  " <Leader> is \ - this calls <Plug>(YCMFindSymbolInWorkspace)
+  call FeedAndCheckMain( '\\wthiswillnotmatchanything', funcref( 'PutQuery' ) )
+
+  " We pop up a notification with some text in it
+  if exists( '*popup_list' )
+    call assert_equal( 1, len( popup_list() ) )
+  endif
+
+  " Old vim doesn't have popup_list, so hit-test the top-right corner which is
+  " where we pup the popu
+  let notification_id = popup_locate( 1, &columns - 1 )
+  call assert_equal( [ 'Added 2 entries to quickfix list.' ],
+                   \ getbufline( winbufnr( notification_id ), 1, '$' ) )
+  " Wait for the notification to clear
+  call WaitForAssert(
+        \ { -> assert_equal( {}, popup_getpos( notification_id ) ) },
+        \ 10000 )
+
+  call WaitForAssert( { -> assert_equal( l, winlayout() ) } )
+  call WaitForAssert( { -> assert_equal( original_win, winnr() ) } )
+  call assert_equal( bufnr( 'doc.py' ), bufnr() )
+  call assert_equal( [ 0, 16, 5, 0 ], getpos( '.' ) )
+endfunction
+
+function! Test_WorkspaceSymbol_NormalModeChange()
+  call youcompleteme#test#setup#OpenFile(
+        \ '/test/testdata/cpp/complete_with_sig_help.cc', {} )
+
+  let original_win = winnr()
+  let b = bufnr()
+  let l = winlayout()
+
+  let popup_id = -1
+
+  function! PutQuery( ... )
+    " Wait for the current buffer to be a prompt buffer
+    call WaitForAssert( { -> assert_equal( 'prompt', &buftype ) } )
+    call WaitForAssert( { -> assert_equal( 'i', mode() ) } )
+
+    call WaitForAssert( { -> assert_true(
+          \ youcompleteme#finder#GetState().id != -1 ) } )
+
+    let id = youcompleteme#finder#GetState().id
+    call assert_equal( 'No results', getbufline( winbufnr( id ), '$' )[ 0 ] )
+    call FeedAndCheckAgain( 'thisisathing', funcref( 'ChangeQuery' ) )
+  endfunction
+
+  function ChangeQuery( ... )
+    let id = youcompleteme#finder#GetState().id
+
+    call WaitForAssert( { ->
+          \ assert_equal( ' [X] Search for symbol: thisisathing ',
+          \ popup_getoptions( id ).title  ) },
+          \ 10000 )
+
+    call WaitForAssert( { -> assert_equal( 1, line( '$', id ) ) } )
+    call assert_equal( 0, youcompleteme#finder#GetState().selected )
+    call assert_equal( 'this_is_a_thing',
+          \ youcompleteme#finder#GetState().results[
+          \   youcompleteme#finder#GetState().selected ].extra_data.name )
+
+    " Wait for the current buffer to be a prompt buffer
+    call WaitForAssert( { -> assert_equal( 'prompt', &buftype ) } )
+    call WaitForAssert( { -> assert_equal( 'i', mode() ) } )
+
+    call FeedAndCheckAgain( "\<Esc>bcwthatisathing",
+                          \ funcref( 'SelectNewItem' ) )
+  endfunction
+
+  function SelectNewItem( ... )
+    let id = youcompleteme#finder#GetState().id
+
+    call WaitForAssert( { ->
+          \ assert_equal( ' [X] Search for symbol: thatisathing ',
+          \ popup_getoptions( id ).title  ) },
+          \ 10000 )
+
+    call WaitForAssert( { -> assert_equal( 1, line( '$', id ) ) } )
+    call assert_equal( 0, youcompleteme#finder#GetState().selected )
+    call assert_equal( 'that_is_a_thing',
+          \ youcompleteme#finder#GetState().results[
+          \   youcompleteme#finder#GetState().selected ].extra_data.name )
+
+    call feedkeys( "\<CR>" )
+  endfunction
+
+  " <Leader> is \ - this calls <Plug>(YCMFindSymbolInWorkspace)
+  call FeedAndCheckMain( '\\w', funcref( 'PutQuery' ) )
+
+  call WaitForAssert( { -> assert_equal( l, winlayout() ) } )
+  call WaitForAssert( { -> assert_equal( original_win, winnr() ) } )
+  call assert_equal( b, bufnr() )
+  call assert_equal( [ 0, 5, 28, 0 ], getpos( '.' ) )
+
+  delfunct PutQuery
+  delfunct SelectNewItem
+  delfunct ChangeQuery
+  silent %bwipe!
+endfunction