199 lines
6.4 KiB
Python
199 lines
6.4 KiB
Python
|
# 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/>.
|
||
|
|
||
|
import vim
|
||
|
import json
|
||
|
from ycm import vimsupport
|
||
|
from ycmd import utils
|
||
|
from ycm.vimsupport import memoize, GetIntValue
|
||
|
|
||
|
|
||
|
class SignatureHelpState:
|
||
|
ACTIVE = 'ACTIVE'
|
||
|
INACTIVE = 'INACTIVE'
|
||
|
ACTIVE_SUPPRESSED = 'ACTIVE_SUPPRESSED'
|
||
|
|
||
|
def __init__( self,
|
||
|
popup_win_id = None,
|
||
|
state = INACTIVE ):
|
||
|
self.popup_win_id = popup_win_id
|
||
|
self.state = state
|
||
|
self.anchor = None
|
||
|
|
||
|
|
||
|
def ToggleVisibility( self ):
|
||
|
if self.state == 'ACTIVE':
|
||
|
self.state = 'ACTIVE_SUPPRESSED'
|
||
|
vim.eval( f'popup_hide( { self.popup_win_id } )' )
|
||
|
elif self.state == 'ACTIVE_SUPPRESSED':
|
||
|
self.state = 'ACTIVE'
|
||
|
vim.eval( f'popup_show( { self.popup_win_id } )' )
|
||
|
|
||
|
|
||
|
def IsActive( self ):
|
||
|
if self.state in ( 'ACTIVE', 'ACTIVE_SUPPRESSED' ):
|
||
|
return 'ACTIVE'
|
||
|
return 'INACTIVE'
|
||
|
|
||
|
|
||
|
def _MakeSignatureHelpBuffer( signature_info ):
|
||
|
active_parameter = int( signature_info.get( 'activeParameter', 0 ) )
|
||
|
|
||
|
lines = []
|
||
|
signatures = ( signature_info.get( 'signatures' ) or [] )
|
||
|
|
||
|
for sig_index, signature in enumerate( signatures ):
|
||
|
props = []
|
||
|
|
||
|
sig_label = signature[ 'label' ]
|
||
|
parameters = ( signature.get( 'parameters' ) or [] )
|
||
|
for param_index, parameter in enumerate( parameters ):
|
||
|
param_label = parameter[ 'label' ]
|
||
|
begin = int( param_label[ 0 ] )
|
||
|
end = int( param_label[ 1 ] )
|
||
|
if param_index == active_parameter:
|
||
|
props.append( {
|
||
|
'col': begin + 1, # 1-based
|
||
|
'length': end - begin,
|
||
|
'type': 'YCM-signature-help-current-argument'
|
||
|
} )
|
||
|
|
||
|
lines.append( {
|
||
|
'text': sig_label,
|
||
|
'props': props
|
||
|
} )
|
||
|
|
||
|
return lines
|
||
|
|
||
|
|
||
|
@memoize()
|
||
|
def ShouldUseSignatureHelp():
|
||
|
return ( vimsupport.VimHasFunctions( 'screenpos', 'pum_getpos' ) and
|
||
|
vimsupport.VimSupportsPopupWindows() )
|
||
|
|
||
|
|
||
|
def UpdateSignatureHelp( state, signature_info ): # noqa
|
||
|
if not ShouldUseSignatureHelp():
|
||
|
return state
|
||
|
|
||
|
signatures = signature_info.get( 'signatures' ) or []
|
||
|
|
||
|
if not signatures:
|
||
|
if state.popup_win_id:
|
||
|
# TODO/FIXME: Should we use popup_hide() instead ?
|
||
|
vim.eval( f"popup_close( { state.popup_win_id } )" )
|
||
|
return SignatureHelpState( None, SignatureHelpState.INACTIVE )
|
||
|
|
||
|
if state.state == SignatureHelpState.INACTIVE:
|
||
|
state.anchor = vimsupport.CurrentLineAndColumn()
|
||
|
|
||
|
state.state = SignatureHelpState.ACTIVE
|
||
|
|
||
|
# Generate the buffer as a list of lines
|
||
|
buf_lines = _MakeSignatureHelpBuffer( signature_info )
|
||
|
screen_pos = vimsupport.ScreenPositionForLineColumnInWindow(
|
||
|
vim.current.window,
|
||
|
state.anchor[ 0 ] + 1, # anchor 0-based
|
||
|
state.anchor[ 1 ] + 1 ) # anchor 0-based
|
||
|
|
||
|
# Simulate 'flip' at the screen boundaries by using screenpos and hiding the
|
||
|
# signature help menu if it overlaps the completion popup (pum).
|
||
|
#
|
||
|
# FIXME: revert to cursor-relative positioning and the 'flip' option when that
|
||
|
# is implemented (if that is indeed better).
|
||
|
|
||
|
# By default display above the anchor
|
||
|
line = int( screen_pos[ 'row' ] ) - 1 # -1 to display above the cur line
|
||
|
pos = "botleft"
|
||
|
|
||
|
cursor_line = vimsupport.CurrentLineAndColumn()[ 0 ] + 1
|
||
|
if int( screen_pos[ 'row' ] ) <= len( buf_lines ):
|
||
|
# No room at the top, display below
|
||
|
line = int( screen_pos[ 'row' ] ) + 1
|
||
|
pos = "topleft"
|
||
|
|
||
|
# Don't allow the popup to overlap the cursor
|
||
|
if ( pos == 'topleft' and
|
||
|
line < cursor_line and
|
||
|
line + len( buf_lines ) >= cursor_line ):
|
||
|
line = 0
|
||
|
|
||
|
# Don't allow the popup to overlap the pum
|
||
|
if line > 0 and GetIntValue( 'pumvisible()' ):
|
||
|
pum_line = GetIntValue( 'pum_getpos().row' ) + 1
|
||
|
if pos == 'botleft' and pum_line <= line:
|
||
|
line = 0
|
||
|
elif ( pos == 'topleft' and
|
||
|
pum_line >= line and
|
||
|
pum_line < ( line + len( buf_lines ) ) ):
|
||
|
line = 0
|
||
|
|
||
|
if line <= 0:
|
||
|
# Nowhere to put it so hide it
|
||
|
if state.popup_win_id:
|
||
|
# TODO/FIXME: Should we use popup_hide() instead ?
|
||
|
vim.eval( f"popup_close( { state.popup_win_id } )" )
|
||
|
return SignatureHelpState( None, SignatureHelpState.INACTIVE )
|
||
|
|
||
|
if int( screen_pos[ 'curscol' ] ) <= 1:
|
||
|
col = 1
|
||
|
else:
|
||
|
# -1 for padding,
|
||
|
# -1 for the trigger character inserted (the anchor is set _after_ the
|
||
|
# character is inserted, so we remove it).
|
||
|
# FIXME: multi-byte characters would be wrong. Need to set anchor before
|
||
|
# inserting the char ?
|
||
|
col = int( screen_pos[ 'curscol' ] ) - 2
|
||
|
|
||
|
if col <= 0:
|
||
|
col = 1
|
||
|
|
||
|
options = {
|
||
|
"line": line,
|
||
|
"col": col,
|
||
|
"pos": pos,
|
||
|
"wrap": 0,
|
||
|
# NOTE: We *dont'* use "cursorline" here - that actually uses PMenuSel,
|
||
|
# which is just too invasive for us (it's more selected item than actual
|
||
|
# cursorline. So instead, we manually set 'cursorline' in the popup window
|
||
|
# and enable syntax based on the current file syntax)
|
||
|
"flip": 1,
|
||
|
"padding": [ 0, 1, 0, 1 ], # Pad 1 char in X axis to match completion menu
|
||
|
"hidden": int( state.state == SignatureHelpState.ACTIVE_SUPPRESSED )
|
||
|
}
|
||
|
|
||
|
if not state.popup_win_id:
|
||
|
state.popup_win_id = GetIntValue(
|
||
|
f'popup_create( { json.dumps( buf_lines ) }, '
|
||
|
f'{ json.dumps( options ) } )' )
|
||
|
else:
|
||
|
vim.eval( f'popup_settext( { state.popup_win_id }, '
|
||
|
f'{ json.dumps( buf_lines ) } )' )
|
||
|
|
||
|
# Should do nothing if already visible
|
||
|
vim.eval( f'popup_move( { state.popup_win_id }, { json.dumps( options ) } )' )
|
||
|
if state.state == SignatureHelpState.ACTIVE:
|
||
|
vim.eval( f'popup_show( { state.popup_win_id } )' )
|
||
|
|
||
|
syntax = utils.ToUnicode( vim.current.buffer.options[ 'syntax' ] )
|
||
|
active_signature = int( signature_info.get( 'activeSignature', 0 ) )
|
||
|
vim.eval( f"win_execute( { state.popup_win_id }, "
|
||
|
f"'set syntax={ syntax } cursorline | "
|
||
|
f"call cursor( [ { active_signature + 1 }, 1 ] )' )" )
|
||
|
|
||
|
return state
|