From f194400aaf93dceee523d2805d830f5b8119d81f Mon Sep 17 00:00:00 2001
From: Shuangcheng-Ni <110970449+Shuangcheng-Ni@users.noreply.github.com>
Date: Mon, 30 Jan 2023 18:03:53 +0800
Subject: [PATCH 1/3] Fix the problem of key bindings in finder buffers
---
autoload/finder.vim | 879 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 879 insertions(+)
create mode 100644 autoload/finder.vim
diff --git a/autoload/finder.vim b/autoload/finder.vim
new file mode 100644
index 0000000000..5172c738c9
--- /dev/null
+++ b/autoload/finder.vim
@@ -0,0 +1,879 @@
+" 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
+ " override all the global mappings in the finder buffer
+ " by remapping each previously mapped key sequence to itself
+ " (still preserve the previously defined buffer-local mappings)
+ if exists( '*maplist' ) && exists( '*mapset' )
+ let bufmapsave = maplist()->filter( 'v:val.buffer == 1' )
+ for mapitem in map(
+ \ maplist()->filter( 'v:val.buffer == 0' ),
+ \ { _, val -> val->extend( #{ expr: 0, noremap: 1, rhs: val.lhs, buffer: 1, silent: 1 } ) } )
+ call mapset( mapitem )
+ endfor
+ for mapitem in bufmapsave
+ call mapset( mapitem )
+ endfor
+ else
+ let bufmapsave = []
+ let mapitems = []
+ for mapitem in split( execute( 'map | map!' ), '\n\+' )
+ let [ mapmode, lhs, attr; _ ] = split( mapitem, '\s\+', 1 )
+ if attr =~ '^[*&]\?@'
+ call add( bufmapsave, lhs )
+ else
+ call add( mapitems, [ mapmode, lhs ] )
+ endif
+ endfor
+ for mapitem in mapitems
+ let [ mapmode, lhs ] = mapitem
+ if index( bufmapsave, lhs ) == -1
+ let mapcmd = mapmode == '!' ? 'noremap!' : ( mapmode . 'noremap' )
+ silent exec mapcmd . ' ' . lhs . ' ' . lhs
+ endif
+ endfor
+ endif
+ 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
From afc30350f851f0502fd7ad4919b496b0fcdb9cbf Mon Sep 17 00:00:00 2001
From: Shuangcheng-Ni <110970449+Shuangcheng-Ni@users.noreply.github.com>
Date: Mon, 30 Jan 2023 19:32:02 +0800
Subject: [PATCH 2/3] Delete finder.vim
---
autoload/finder.vim | 879 --------------------------------------------
1 file changed, 879 deletions(-)
delete mode 100644 autoload/finder.vim
diff --git a/autoload/finder.vim b/autoload/finder.vim
deleted file mode 100644
index 5172c738c9..0000000000
--- a/autoload/finder.vim
+++ /dev/null
@@ -1,879 +0,0 @@
-" 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
- " override all the global mappings in the finder buffer
- " by remapping each previously mapped key sequence to itself
- " (still preserve the previously defined buffer-local mappings)
- if exists( '*maplist' ) && exists( '*mapset' )
- let bufmapsave = maplist()->filter( 'v:val.buffer == 1' )
- for mapitem in map(
- \ maplist()->filter( 'v:val.buffer == 0' ),
- \ { _, val -> val->extend( #{ expr: 0, noremap: 1, rhs: val.lhs, buffer: 1, silent: 1 } ) } )
- call mapset( mapitem )
- endfor
- for mapitem in bufmapsave
- call mapset( mapitem )
- endfor
- else
- let bufmapsave = []
- let mapitems = []
- for mapitem in split( execute( 'map | map!' ), '\n\+' )
- let [ mapmode, lhs, attr; _ ] = split( mapitem, '\s\+', 1 )
- if attr =~ '^[*&]\?@'
- call add( bufmapsave, lhs )
- else
- call add( mapitems, [ mapmode, lhs ] )
- endif
- endfor
- for mapitem in mapitems
- let [ mapmode, lhs ] = mapitem
- if index( bufmapsave, lhs ) == -1
- let mapcmd = mapmode == '!' ? 'noremap!' : ( mapmode . 'noremap' )
- silent exec mapcmd . ' ' . lhs . ' ' . lhs
- endif
- endfor
- endif
- 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
From 4b3f4f7ee08d63259b57302c70fe07695a72ea11 Mon Sep 17 00:00:00 2001
From: Shuangcheng-Ni <110970449+Shuangcheng-Ni@users.noreply.github.com>
Date: Mon, 30 Jan 2023 19:33:04 +0800
Subject: [PATCH 3/3] Add files via upload
---
autoload/youcompleteme/finder.vim | 32 +++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/autoload/youcompleteme/finder.vim b/autoload/youcompleteme/finder.vim
index 1406312db7..5172c738c9 100644
--- a/autoload/youcompleteme/finder.vim
+++ b/autoload/youcompleteme/finder.vim
@@ -243,6 +243,38 @@ function! youcompleteme#finder#FindSymbol( scope ) abort
autocmd WinLeave call s:Cancel()
autocmd CmdLineEnter call s:Cancel()
augroup END
+ " override all the global mappings in the finder buffer
+ " by remapping each previously mapped key sequence to itself
+ " (still preserve the previously defined buffer-local mappings)
+ if exists( '*maplist' ) && exists( '*mapset' )
+ let bufmapsave = maplist()->filter( 'v:val.buffer == 1' )
+ for mapitem in map(
+ \ maplist()->filter( 'v:val.buffer == 0' ),
+ \ { _, val -> val->extend( #{ expr: 0, noremap: 1, rhs: val.lhs, buffer: 1, silent: 1 } ) } )
+ call mapset( mapitem )
+ endfor
+ for mapitem in bufmapsave
+ call mapset( mapitem )
+ endfor
+ else
+ let bufmapsave = []
+ let mapitems = []
+ for mapitem in split( execute( 'map | map!' ), '\n\+' )
+ let [ mapmode, lhs, attr; _ ] = split( mapitem, '\s\+', 1 )
+ if attr =~ '^[*&]\?@'
+ call add( bufmapsave, lhs )
+ else
+ call add( mapitems, [ mapmode, lhs ] )
+ endif
+ endfor
+ for mapitem in mapitems
+ let [ mapmode, lhs ] = mapitem
+ if index( bufmapsave, lhs ) == -1
+ let mapcmd = mapmode == '!' ? 'noremap!' : ( mapmode . 'noremap' )
+ silent exec mapcmd . ' ' . lhs . ' ' . lhs
+ endif
+ endfor
+ endif
startinsert
endfunction