Skip to content

Commit

Permalink
Merge pull request #4150 from puremourning/semantic-highlighting-opti…
Browse files Browse the repository at this point in the history
…misation

Semantic highlighting optimisation
  • Loading branch information
puremourning committed Jun 12, 2023
2 parents 78ba06e + 187489d commit 49ced5a
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 221 deletions.
64 changes: 31 additions & 33 deletions autoload/youcompleteme.vim
Original file line number Diff line number Diff line change
Expand Up @@ -822,24 +822,29 @@ function! s:OnFileReadyToParse( ... )
\ s:pollers.file_parse_response.wait_milliseconds,
\ function( 's:PollFileParseResponse' ) )

call s:UpdateSemanticHighlighting( bufnr() )
call s:UpdateSemanticHighlighting( bufnr(), 1, 0 )
call s:UpdateInlayHints( bufnr(), 1, 0 )

endif
endfunction

function! s:UpdateSemanticHighlighting( bufnr ) abort
function! s:UpdateSemanticHighlighting( bufnr, force, redraw_anyway ) abort
call s:StopPoller( s:pollers.semantic_highlighting )
if !s:is_neovim &&
\ get( b:, 'ycm_enable_semantic_highlighting',
\ get( g:, 'ycm_enable_semantic_highlighting', 0 ) )

py3 ycm_state.Buffer(
\ int( vim.eval( "a:bufnr" ) ) ).SendSemanticTokensRequest()
let s:pollers.semantic_highlighting.id = timer_start(
\ s:pollers.semantic_highlighting.wait_milliseconds,
\ function( 's:PollSemanticHighlighting', [ a:bufnr ] ) )

if py3eval(
\ 'ycm_state.Buffer( int( vim.eval( "a:bufnr" ) ) ).'
\ . 'semantic_highlighting.Request( '
\ . ' force=int( vim.eval( "a:force" ) ) )' )
let s:pollers.semantic_highlighting.id = timer_start(
\ s:pollers.semantic_highlighting.wait_milliseconds,
\ function( 's:PollSemanticHighlighting', [ a:bufnr ] ) )
elseif a:redraw_anyway
py3 ycm_state.Buffer(
\ int( vim.eval( "a:bufnr" ) ) ).semantic_highlighting.Refresh()
endif
endif
endfunction

Expand All @@ -850,7 +855,7 @@ function s:ShouldUseInlayHintsNow( bufnr )
\ get( g:, 'ycm_enable_inlay_hints', 0 ) )
endfunction

function! s:UpdateInlayHints( bufnr, force, redraw_anyway )
function! s:UpdateInlayHints( bufnr, force, redraw_anyway ) abort
call s:StopPoller( s:pollers.inlay_hints )

if s:ShouldUseInlayHintsNow( a:bufnr )
Expand Down Expand Up @@ -883,36 +888,29 @@ function! s:PollFileParseResponse( ... )
endfunction


function! s:PollSemanticHighlighting( bufnr, ... )
if !py3eval(
\ 'ycm_state.Buffer( int( vim.eval( "a:bufnr" ) ) )'
\ . '.SemanticTokensRequestReady()' )
let s:pollers.semantic_highlighting.id = timer_start(
\ s:pollers.semantic_highlighting.wait_milliseconds,
\ function( 's:PollSemanticHighlighting', [ a:bufnr ] ) )
elseif !py3eval(
\ 'ycm_state.Buffer( int( vim.eval( "a:bufnr" ) ) )'
\ . '.UpdateSemanticTokens()' )
let s:pollers.semantic_highlighting.id = timer_start(
\ s:pollers.semantic_highlighting.wait_milliseconds,
\ function( 's:PollSemanticHighlighting', [ a:bufnr ] ) )
endif
function! s:PollSemanticHighlighting( bufnr, ... ) abort
return s:PollScrollable( a:bufnr, 'semantic_highlighting' )
endfunction


function! s:PollInlayHints( bufnr, ... ) abort
return s:PollScrollable( a:bufnr, 'inlay_hints' )
endfunction


function! s:PollInlayHints( bufnr, ... )
function! s:PollScrollable( bufnr, scrollable, ... ) abort
if !py3eval(
\ 'ycm_state.Buffer( int( vim.eval( "a:bufnr" ) ) )'
\ . '.inlay_hints.Ready()' )
let s:pollers.inlay_hints.id = timer_start(
\ s:pollers.inlay_hints.wait_milliseconds,
\ function( 's:PollInlayHints', [ a:bufnr ] ) )
\ . '.' . a:scrollable . '.Ready()' )
let s:pollers[a:scrollable].id = timer_start(
\ s:pollers[a:scrollable].wait_milliseconds,
\ function( 's:PollScrollable', [ a:bufnr, a:scrollable ] ) )
elseif ! py3eval(
\ 'ycm_state.Buffer( int( vim.eval( "a:bufnr" ) ) )'
\ . '.inlay_hints.Update()' )
let s:pollers.inlay_hints.id = timer_start(
\ s:pollers.inlay_hints.wait_milliseconds,
\ function( 's:PollInlayHints', [ a:bufnr ] ) )
\ . '.' . a:scrollable . '.Update()' )
let s:pollers[ a:scrollable ].id = timer_start(
\ s:pollers[ a:scrollable ].wait_milliseconds,
\ function( 's:PollScrollable', [ a:bufnr, a:scrollable ] ) )
endif
endfunction

Expand Down Expand Up @@ -973,7 +971,7 @@ function! s:OnWinScrolled()
return
endif
let bufnr = winbufnr( expand( '<afile>' ) )
call s:UpdateSemanticHighlighting( bufnr )
call s:UpdateSemanticHighlighting( bufnr, 0, 0 )
call s:UpdateInlayHints( bufnr, 0, 0 )
endfunction

Expand Down
16 changes: 2 additions & 14 deletions python/ycm/buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def __init__( self, bufnr, user_options, filetypes ):
self._diag_interface = DiagnosticInterface( bufnr, user_options )
self._open_loclist_on_ycm_diags = user_options[
'open_loclist_on_ycm_diags' ]
self._semantic_highlighting = SemanticHighlighting( bufnr, user_options )
self.inlay_hints = InlayHints( bufnr, user_options )
self.semantic_highlighting = SemanticHighlighting( bufnr )
self.inlay_hints = InlayHints( bufnr )
self.UpdateFromFileTypes( filetypes )


Expand Down Expand Up @@ -145,18 +145,6 @@ def UpdateFromFileTypes( self, filetypes ):
self._async_diags = False


def SendSemanticTokensRequest( self ):
self._semantic_highlighting.SendRequest()


def SemanticTokensRequestReady( self ):
return self._semantic_highlighting.IsResponseReady()


def UpdateSemanticTokens( self ):
return self._semantic_highlighting.Update()


def _ChangedTick( self ):
return vimsupport.GetBufferChangedTick( self._number )

Expand Down
95 changes: 15 additions & 80 deletions python/ycm/inlay_hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from ycm.client.base_request import BuildRequestData
from ycm import vimsupport
from ycm import text_properties as tp
from ycm import scrolling_range as sr


HIGHLIGHT_GROUP = {
Expand Down Expand Up @@ -55,110 +56,44 @@ def Initialise():
return True


class InlayHints:
class InlayHints( sr.ScrollingBufferRange ):
"""Stores the inlay hints state for a Vim buffer"""

# FIXME: Send a request per-disjoint range for this buffer rather than the
# maximal range. then collaate the results when all responses are returned
def __init__( self, bufnr, user_options ):
self._request = None
self._bufnr = bufnr
self.tick = -1
self._latest_inlay_hints = []
self._last_requested_range = None


def Request( self, force=False ):
if self._request and not self.Ready():
return True

# Check to see if the buffer ranges would actually change anything visible.
# This avoids a round-trip for every single line scroll event
if ( not force and
self.tick == vimsupport.GetBufferChangedTick( self._bufnr ) and
vimsupport.VisibleRangeOfBufferOverlaps(
self._bufnr,
self._last_requested_range ) ):
return False # don't poll

# We're requesting changes, so the existing results are now invalid
self._latest_inlay_hints = []
# FIXME: This call is duplicated in the call to VisibleRangeOfBufferOverlaps
# - remove the expansion param
# - look up the actual visible range, then call this function
# - if not overlapping, do the factor expansion and request
self._last_requested_range = vimsupport.RangeVisibleInBuffer( self._bufnr )
self.tick = vimsupport.GetBufferChangedTick( self._bufnr )

def _NewRequest( self, request_range ):
request_data = BuildRequestData( self._bufnr )
request_data.update( {
'range': self._last_requested_range
} )
self._request = InlayHintsRequest( request_data )
self._request.Start()
return True


def Ready( self ):
return self._request is not None and self._request.Done()
request_data[ 'range' ] = request_range
return InlayHintsRequest( request_data )


def Clear( self ):
# ClearTextProperties is slow as it must scan the whole buffer
# we shouldn't use _last_requested_range, because the server is free to
# return a larger range, so we pick the first/last from the latest results
types = [ 'YCM_INLAY_UNKNOWN', 'YCM_INLAY_PADDING' ] + [
f'YCM_INLAY_{ prop_type }' for prop_type in HIGHLIGHT_GROUP.keys()
]

tp.ClearTextProperties( self._bufnr, prop_types = types )

def Update( self ):
if not self._request:
# Nothing to update
return True

assert self.Ready()

# We're ready to use this response. Clear it (to avoid repeatedly
# re-polling).
self._latest_inlay_hints = self._request.Response()
self._request = None

if self.tick != vimsupport.GetBufferChangedTick( self._bufnr ):
# Buffer has changed, we should ignore the data and retry
self.Request( force=True )
return False # poll again

self._Draw()

# No need to re-poll
return True


def Refresh( self ):
if self.tick != vimsupport.GetBufferChangedTick( self._bufnr ):
# stale data
return

if self._request is not None:
# request in progress; we''l handle refreshing when it's done.
return

self._Draw()


def _Draw( self ):
self.Clear()

for inlay_hint in self._latest_inlay_hints:
for inlay_hint in self._latest_response:
if 'kind' not in inlay_hint:
prop_type = 'YCM_INLAY_UNKNOWN'
elif inlay_hint[ 'kind' ] not in HIGHLIGHT_GROUP:
prop_type = 'YCM_INLAY_UNKNOWN'
else:
prop_type = 'YCM_INLAY_' + inlay_hint[ 'kind' ]

self.GrowRangeIfNeeded( {
'start': inlay_hint[ 'position' ],
'end': {
'line_num': inlay_hint[ 'position' ][ 'line_num' ],
'column_num': inlay_hint[ 'position' ][ 'column_num' ] + len(
inlay_hint[ 'label' ] )
}
} )

if inlay_hint.get( 'paddingLeft', False ):
tp.AddTextProperty( self._bufnr,
None,
Expand Down
Loading

0 comments on commit 49ced5a

Please sign in to comment.