" Copyright (C) 2021 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 .
" This is basic vim plugin boilerplate
let s:save_cpo = &cpoptions
set cpoptions&vim
scriptencoding utf-8
"
" Explanation for how this module works:
"
" The entry point is youcompleteme#finder#FindSymbol, which takes a scope
" argument:
"
" * 'document' scope - search document symbols - GoToDocumentOutline
" * 'workspace' scope - search workspace symbols - GoToSymbol
"
" In general, the approach is:
" - create a prompt buffer, and display it to use for input of a query
" - open up a popup to display the results in the middle of the screen
" - as the query changes, re-query the server to get the newly filtered results
" - as the server returns results, update the contents of the results-popup
" - use a popup-filter to implement some interractivity with the results, such
" as down/up, and select item
" - when an item is selected, open its buffer in the window that was current
" when the search started and jump to the selected item.
" - then populate the quickfix list with any matching results and close the
" popup and prompt buffer.
"
" However, there is an important wrinke/distinction that leads to the code being
" a little more complicated than that: the 'search' mechanism for workspace and
" document symbols is different.
"
" The reaosn for this differece is what the servers provide in response to the 2
" requests:
"
" * 'document' scope - we use ycmd's filtering and sorting.
" GoToDocumentOutline (LSP documentSymbol) request does not take any filter
" argument. It returns all symbols in the current document. Therefore, we use
" ycmd's filter_and_sort_candidates endpoint to use our own fuzzy search on
" the results. This is a _great_ experience, it's _super_ fast and familar to
" YCM users.
" * 'workspace' scope - we have to rely on the downstream server's filtering and
" sorting.
" GoToSymbol (LSP workspaceSymbol) request takes a query parameter to search
" with. While the LSP spec states that an empty query means 'return
" everything', no servers actually implement that, so we have to pass our
" query down to the server.
"
" The result of this is that while a lot of the code is shared, there are
" slightly different steps involved in the process:
"
" * for 'document' requests, as soon as the popup is opened, we issue a
" GoToDocumentOutline request, storing the results as `raw_results`, then
" then immediately start filtering it with filter_and_sort_candidates, storing
" the filtered results in `results`
" * for 'workspace' requests, as soon as the popup is opened, we issue a
" GoToSymbol request with an empty query. This usually returns nothing, but if
" any servers return anything then it would be stored in 'results'. As the
" user types, we repeat the GoToSymbol request and update the 'results'
"
" In order to simplify the re-query code, we put the function used to filter
" results in the state variable as 'query_func'.
"
" As the expereince of completely different filtering and sorting for workspace
" vs document is so jarring, by default we actually pass all restuls from
" 'workspace' search through filter_and_sort_candidates too. This isn't perfect
" because it breaks the mental model when the server's filtering is dumb, but it
" at least means that sorting is consistent. This can be disabled by setting
" g:ycm_refilter_workspace_symbols to 0.
"
" The key functions are:
"
" - FindSymbol - initiate the request - open the popup/prompt buffer, set up
" the state object to defaults and initiate the first request
" (RequestDocumentSymbols for 'documnt' and SearchWorkspace for 'workspace' )
"
" - RequestDocumentSymbols - perform the GoToDocumentOutline request and store
" the results in 'raw_results'
"
" - SearchDocument - perform filter_and_sort_candidates request on the
" 'raw_results', 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'
"
" - HandleKeyPress - handle a keypress while the popup is visible, intercepts
" things to handle interractivity
"
" - PopupClosed - callback when the popup is closed. This openes the result in
" the original window.
"
" The other functions are utility for the most part and handle things like
" TextChangedI event, starting/stopping drawing the spinner and such.
let s:highlight_group_for_symbol_kind = {
\ 'Array': 'Identifier',
\ 'Boolean': 'Boolean',
\ 'Class': 'Structure',
\ 'Constant': 'Constant',
\ 'Constructor': 'Function',
\ 'Enum': 'Structure',
\ 'EnumMember': 'Identifier',
\ 'Event': 'Identifier',
\ 'Field': 'Identifier',
\ 'Function': 'Function',
\ 'Interface': 'Structure',
\ 'Key': 'Identifier',
\ 'Method': 'Function',
\ 'Module': 'Include',
\ 'Namespace': 'Type',
\ 'Null': 'Keyword',
\ 'Number': 'Number',
\ 'Object': 'Structure',
\ 'Operator': 'Operator',
\ 'Package': 'Include',
\ 'Property': 'Identifier',
\ 'String': 'String',
\ 'Struct': 'Structure',
\ 'TypeParameter': 'Typedef',
\ 'Variable': 'Identifier',
\ }
let s:initialized_text_properties = v:false
let s:icon_spinner = [ '/', '-', '\', '|', '/', '-', '\', '|' ]
let s:icon_done = 'X'
let s:spinner_delay = 100
let s:prompt = 'Find Symbol: '
let s:find_symbol_status = {}
" Entry point {{{
function! youcompleteme#finder#FindSymbol( scope ) abort
if !py3eval( 'vimsupport.VimSupportsPopupWindows()' )
echo 'Sorry, this feature is not supported in your editor'
return
endif
if !s:initialized_text_properties
call prop_type_add( 'YCM-symbol-Normal', { 'highlight': 'Normal' } )
for k in keys( s:highlight_group_for_symbol_kind )
call prop_type_add(
\ 'YCM-symbol-' . k,
\ { '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
let s:find_symbol_status = {
\ 'selected': -1,
\ 'query': '',
\ 'results': [],
\ 'raw_results': v:none,
\ 'all_filetypes': v:true,
\ 'pending': [],
\ 'winid': win_getid(),
\ 'bufnr': bufnr(),
\ 'prompt_bufnr': -1,
\ 'prompt_winid': -1,
\ 'filter': v:none,
\ 'id': v:none,
\ 'cursorline_match': v:none,
\ 'spinner': 0,
\ 'spinner_timer': -1,
\ }
let opts = {
\ 'padding': [ 1, 2, 1, 2 ],
\ 'wrap': 0,
\ 'minwidth': &columns / 3 * 2,
\ 'minheight': &lines / 3 * 2,
\ 'maxwidth': &columns / 3 * 2,
\ 'maxheight': &lines / 3 * 2,
\ 'line': &lines / 6,
\ 'col': &columns / 6,
\ 'pos': 'topleft',
\ 'drag': 1,
\ 'resize': 1,
\ 'close': 'button',
\ 'border': [],
\ 'callback': function( 's:PopupClosed' ),
\ 'filter': function( 's:HandleKeyPress' ),
\ 'highlight': 'Normal',
\ }
if &ambiwidth ==# 'single' && &encoding ==? 'utf-8'
let opts[ 'borderchars' ] = [ '─', '│', '─', '│', '╭', '╮', '┛', '╰' ]
endif
if a:scope ==# 'document'
let s:find_symbol_status.query_func = function( 's:SearchDocument' )
else
let s:find_symbol_status.query_func = function( 's:SearchWorkspace' )
endif
let s:find_symbol_status.id = popup_create( 'Type to query for stuff', opts )
" Kick off the request now
if a:scope ==# 'document'
call s:RequestDocumentSymbols()
else
call s:SearchWorkspace( '', v:true )
endif
let bufnr = bufadd( '_ycm_filter_' )
silent call bufload( bufnr )
silent topleft 1split _ycm_filter_
" Disable ycm/completion in this buffer
call setbufvar( bufnr, 'ycm_largefile', 1 )
let s:find_symbol_status.prompt_bufnr = bufnr
let s:find_symbol_status.prompt_winid = win_getid()
setlocal buftype=prompt noswapfile modifiable nomodified noreadonly
setlocal nobuflisted bufhidden=delete textwidth=0
call prompt_setprompt( bufnr(), s:prompt )
augroup YCMPromptFindSymbol
autocmd!
autocmd TextChanged,TextChangedI call s:OnQueryTextChanged()
autocmd WinLeave call s:Cancel()
autocmd CmdLineEnter call s:Cancel()
augroup END
startinsert
endfunction
function! s:OnQueryTextChanged() abort
if s:find_symbol_status.id < 0
return
endif
let bufnr = s:find_symbol_status.prompt_bufnr
let query = getbufline( bufnr, '$' )[ 0 ]
let s:find_symbol_status.query = query[ len( s:prompt ) : ]
" really, re-query if we can
call s:RequeryFinderPopup( v:true )
call win_execute( s:find_symbol_status.prompt_winid, 'setlocal nomodified' )
endfunction
function! s:Cancel() abort
if s:find_symbol_status.id < 0
return
endif
call popup_close( s:find_symbol_status.id, -1 )
endfunction
" }}}
" Popup and keyboard events {{{
function! s:HandleKeyPress( id, key ) abort
let redraw = 0
let handled = 0
let requery = 0
" The input for the search/query is taken from the prompt buffer and the
" TextChangedI event
if a:key ==# "\" ||
\ a:key ==# "\" ||
\ a:key ==# "\" ||
\ a:key ==# "\"
let s:find_symbol_status.selected += 1
" wrap
if s:find_symbol_status.selected >= len( s:find_symbol_status.results )
let s:find_symbol_status.selected = 0
endif
let redraw = 1
let handled = 1
elseif a:key ==# "\" ||
\ a:key ==# "\" ||
\ a:key ==# "\" ||
\ a:key ==# "\"
let s:find_symbol_status.selected -= 1
" wrap
if s:find_symbol_status.selected < 0
let s:find_symbol_status.selected =
\ len( s:find_symbol_status.results ) - 1
endif
let redraw = 1
let handled = 1
elseif a:key ==# "\" || a:key ==# "\"
let s:find_symbol_status.selected +=
\ popup_getpos( s:find_symbol_status.id ).core_height
" Don't wrap
if s:find_symbol_status.selected >= len( s:find_symbol_status.results )
let s:find_symbol_status.selected =
\ len( s:find_symbol_status.results ) - 1
endif
let redraw = 1
let handled = 1
elseif a:key ==# "\" || a:key ==# "\"
let s:find_symbol_status.selected -=
\ popup_getpos( s:find_symbol_status.id ).core_height
" Don't wrap
if s:find_symbol_status.selected < 0
let s:find_symbol_status.selected = 0
endif
let redraw = 1
let handled = 1
elseif a:key ==# "\"
call popup_close( a:id, -1 )
let handled = 1
elseif a:key ==# "\"
if s:find_symbol_status.selected >= 0
call popup_close( a:id, s:find_symbol_status.selected )
let handled = 1
endif
elseif a:key ==# "\" || a:key ==# "\"
let s:find_symbol_status.selected = 0
let redraw = 1
let handled = 1
elseif a:key ==# "\" || a:key ==# "\"
let s:find_symbol_status.selected = len( s:find_symbol_status.results ) - 1
let redraw = 1
let handled = 1
elseif a:key ==# "\"
" TOggle filetypes?
let s:find_symbol_status.all_filetypes = !s:find_symbol_status.all_filetypes
let redraw = 0
let requery = 1
let handled = 1
endif
if requery
call s:RequeryFinderPopup( v:true )
elseif redraw
call s:RedrawFinderPopup()
endif
return handled
endfunction
" Handle the popup closing: jump to the selected item
function! s:PopupClosed( id, selected ) abort
stopinsert
call win_gotoid( s:find_symbol_status.prompt_winid )
silent bwipe!
" Return to original window
call win_gotoid( s:find_symbol_status.winid )
if a:selected >= 0
let selected = s:find_symbol_status.results[ a:selected ]
py3 vimsupport.JumpToLocation(
\ filename = vimsupport.ToUnicode( vim.eval( 'selected.filepath' ) ),
\ line = int( vim.eval( 'selected.line_num' ) ),
\ column = int( vim.eval( 'selected.column_num' ) ),
\ modifiers = '',
\ command = 'same-buffer'
\ )
if len( s:find_symbol_status.results ) > 1
" Also, populate the quickfix list
py3 vimsupport.SetQuickFixList(
\ [ vimsupport.BuildQfListItem( x ) for x in
\ vim.eval( 's:find_symbol_status.results' ) ] )
" Emulate :echo, to avoid a redraw getting rid of the message.
let txt = 'Added ' . len( getqflist() ) . ' entries to quickfix list.'
call popup_notification(
\ txt,
\ {
\ 'line': 1,
\ 'col': &columns - len( txt ),
\ 'padding': [ 0, 0, 0, 0 ],
\ 'border': [ 0, 0, 0, 0 ],
\ 'highlight': 'PMenu'
\ } )
" But don't open it, as this could take up valuable actual screen space
" py3 vimsupport.OpenQuickFixList()
endif
endif
call s:EndRequest()
let s:find_symbol_status.id = -1
endfunction
"}}}
" Results handling and re-query {{{
" Render a set of results returned from the filter/search function
function! s:HandleSymbolSearchResults( results ) abort
let s:find_symbol_status.results = []
if s:find_symbol_status.id < 0
" Popup was closed, ignore this event
return
endif
let s:find_symbol_status.results = a:results
call s:RedrawFinderPopup()
" Re-query but no change in the query text
call s:RequeryFinderPopup( v:false )
endfunction
" Set the popup text
function! s:RedrawFinderPopup() abort
" Clamp selected. If there are any results, the first one is selected by
" default
let s:find_symbol_status.selected = max( [
\ s:find_symbol_status.selected,
\ len( s:find_symbol_status.results ) > 0 ? 0 : -1
\ ] )
let s:find_symbol_status.selected = min( [
\ s:find_symbol_status.selected,
\ len( s:find_symbol_status.results ) - 1
\ ] )
if empty( s:find_symbol_status.results )
call popup_settext( s:find_symbol_status.id, 'No results' )
let s:find_symbol_status.selected = -1
else
let popup_width = popup_getpos( s:find_symbol_status.id ).core_width
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
" description and the file path. Include at least 8 spaces between them
" (if there's room).
if result->has_key( 'extra_data' )
let kind = result[ 'extra_data' ][ 'kind' ]
let name = result[ 'extra_data' ][ 'name' ]
let desc = kind .. ': ' .. name
if s:highlight_group_for_symbol_kind->has_key( kind )
let prop = 'YCM-symbol-' . kind
else
let prop = 'YCM-symbol-Normal'
endif
let props = [
\ { 'col': 1,
\ 'length': len( kind ) + 2,
\ 'type': 'YCM-symbol-Normal' },
\ { 'col': len( kind ) + 3,
\ 'length': len( name ),
\ 'type': prop },
\ ]
elseif result->has_key( 'description' )
let desc = result[ 'description' ]
let props = [
\ { 'col': 1, 'length': len( desc ), 'type': 'YCM-symbol-Normal' },
\ ]
else
let desc = 'Invalid entry: ' . string( result )
let props = []
endif
let line_num = result[ 'line_num' ]
let path = fnamemodify( result[ 'filepath' ], ':.' )
\ .. ':'
\ .. line_num
let path_includes_line = 1
let spaces = available_width - len( desc ) - len( path )
let spacing = 4
if spaces < spacing
let spaces = spacing
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_includes_line = 0
let path = '...'
endif
endif
let line = desc
\ .. repeat( ' ', spaces )
\ .. path
\ .. filetype_sep
\ .. result[ 'filetype' ]
if len( path ) > 0
if path_includes_line
let props += [
\ { 'col': available_width - len( path ) + 1,
\ 'length': len( path ) - len( line_num ),
\ 'type': 'YCM-symbol-file' },
\ { 'col': available_width - len( line_num ) + 1,
\ 'length': len( line_num ),
\ 'type': 'YCM-symbol-line-num' },
\ ]
else
let props += [
\ { 'col': available_width - len( path ) + 1,
\ 'length': len( path ),
\ 'type': 'YCM-symbol-file' },
\ ]
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 )
endif
if s:find_symbol_status.selected > -1
" Move the cursor so that cursorline highlights the selected item. Also
" scroll the window if the selected item is not in view. To make scrolling
" feel natural we position the current line a the bottom of the window if
" the new current line is below the current viewport, and at the top if the
" new current line is above the viewport.
let new_line = s:find_symbol_status.selected + 1
let pos = popup_getpos( s:find_symbol_status.id )
call win_execute( s:find_symbol_status.id,
\ 'call cursor( [' . string( new_line ) . ', 1] )' )
if new_line < pos.firstline
" New line is above the viewport, scroll so that this line is at the top
" of the window.
call win_execute( s:find_symbol_status.id, "normal z\" )
elseif new_line >= ( pos.firstline + pos.core_height )
" New line is below the viewport, scroll so that this line is at the
" bottom of the window.
call win_execute( s:find_symbol_status.id, ':normal z-' )
endif
" Otherwise, new item is already displayed - don't scroll the window.
if !getwinvar( s:find_symbol_status.id, '&cursorline' )
call win_execute( s:find_symbol_status.id,
\ 'set cursorline cursorlineopt&' )
endif
else
call win_execute( s:find_symbol_status.id, 'set nocursorline' )
endif
endfunction
function! s:SetTitle() abort
if s:find_symbol_status.spinner_timer > -1
let status = s:icon_spinner[ s:find_symbol_status.spinner ]
else
let status = s:icon_done
endif
call popup_setoptions( s:find_symbol_status.id, {
\ 'title': ' [' . status . '] Search for symbol: '
\ . s:find_symbol_status.query . ' '
\ } )
endfunction
" Re-query or re-filter by calling the filter function
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()
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( 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 = []
else
let results = [ a:results ]
endif
else
let results = a:results
endif
call map( results, { _, r -> extend( r, {
\ 'key': r->get( 'extra_data', {} )->get( 'name', r[ 'description' ] ),
\ 'filetype': a:filetype
\ } ) } )
return results
endfunction
" }}}
" Spinner {{{
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,
\ function( 's:TickSpinner' ) )
call s:SetTitle()
endfunction
function! s:EndRequest() abort
call timer_stop( s:find_symbol_status.spinner_timer )
let s:find_symbol_status.spinner_timer = -1
call s:SetTitle()
endfunction
function! s:TickSpinner( timer_id ) abort
let s:find_symbol_status.spinner = ( s:find_symbol_status.spinner + 1 ) %
\ len( s:icon_spinner )
let s:find_symbol_status.spinner_timer = timer_start( s:spinner_delay,
\ function( 's:TickSpinner' ) )
call s:SetTitle()
endfunction
" }}}
" Workspace search {{{
function! s:SearchWorkspace( query, new_query ) abort
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 = {}
" FIXME: We might still get results for any pending results. There is no
" cancellation mechanism implemented for the async request!
let s:find_symbol_status.pending = []
if s:find_symbol_status.all_filetypes
let ft_buffer_map = py3eval( 'vimsupport.AllOpenedFiletypes()' )
else
let current_filetypes = py3eval( 'vimsupport.CurrentFiletypes()' )
let ft_buffer_map = {}
for ft in current_filetypes
let ft_buffer_map[ ft ] = [ bufnr() ]
endfor
endif
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
" Just requery those completer filetypes that we're not currently waiting
" for
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( 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.
"
" We get the server to give us a result set, then use our own
" filter_and_sort_candidates on the result set filtered by the server
"
" The reason for this is:
" - server filterins will differ by server and this leads to horrible wonky
" user experience
" - ycmd filter is consistent, even if not perfect
" - servers are supposed to return _all_ symbols if we request a query of
" "" but not all servers actually do
"
" So as a compromise we let the server filter the results, then we
" _refilter_ and sort them using ycmd's method. This provides consistency
" with the filtering and sorting on the completion popup menu, with the
" disadvantage of additional latency.
"
" We're not currently sure this is going to be perfecct, so we have a hidden
" option to disable this re-filter/sort.
"
let results = py3eval(
\ 'ycm_state.FilterAndSortItems( vim.eval( "results" ),'
\ . ' "key",'
\ . ' vim.eval( "query" ) )' )
endif
if !waiting
call s:EndRequest()
endif
eval s:HandleSymbolSearchResults( results )
endfunction
" }}}
" Document Search {{{
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 popup_settext( s:find_symbol_status.id,
\ 'No symbols found in document' )
return
endif
" No spinner, because this is actually a synchronous call
" Call filter_and_sort_candidates on the results (synchronously)
let response = py3eval(
\ 'ycm_state.FilterAndSortItems( '
\ . ' vim.eval( "s:find_symbol_status.raw_results" ),'
\ . ' "key",'
\ . ' vim.eval( "a:query" ) )' )
eval s:HandleSymbolSearchResults( response )
endfunction
function! s:RequestDocumentSymbols()
call s:StartRequest()
call youcompleteme#GetRawCommandResponseAsync(
\ function( 's:HandleDocumentSymbols' ),
\ 'GoToDocumentOutline' )
endfunction
function! s:HandleDocumentSymbols( results ) abort
call s:EndRequest()
let s:find_symbol_status.raw_results = s:ParseGoToResponse( '', a:results )
call s:SearchDocument( '', v:true )
endfunction
" }}}
" Utility for testing {{{
function! youcompleteme#finder#GetState() abort
return s:find_symbol_status
endfunction
" }}}
" This is basic vim plugin boilerplate
let &cpoptions = s:save_cpo
unlet s:save_cpo
" vim: foldmethod=marker