Jelajahi Sumber

Support sending a range for inlay_hints and semantic tokens

We use the full range of all visible buffers, which is kind of weak but
we model this all at buffer level not window level.

Note that clangd doesn't support range tokens request so this might not
work well yet.
Ben Jackson 2 tahun lalu
induk
melakukan
eb6ac04071

+ 55 - 28
autoload/youcompleteme.vim

@@ -236,6 +236,7 @@ function! youcompleteme#EnableCursorMovedAutocommands()
     autocmd TextChangedI * call s:OnTextChangedInsertMode( v:false )
     autocmd TextChangedP * call s:OnTextChangedInsertMode( v:true )
     autocmd InsertCharPre * call s:OnInsertChar()
+    autocmd WinScrolled * call s:OnWinScrolled()
   augroup END
 endfunction
 
@@ -768,29 +769,39 @@ function! s:OnFileReadyToParse( ... )
           \ s:pollers.file_parse_response.wait_milliseconds,
           \ function( 's:PollFileParseResponse' ) )
 
-    call s:StopPoller( s:pollers.semantic_highlighting )
-    if !s:is_neovim &&
-          \ get( b:, 'ycm_enable_semantic_highlighting',
-          \   get( g:, 'ycm_enable_semantic_highlighting', 0 ) )
+    call s:UpdateSemanticHighlighting( bufnr() )
+    call s:UpdateInlayHints( bufnr() )
 
-      py3 ycm_state.CurrentBuffer().SendSemanticTokensRequest()
-      let s:pollers.semantic_highlighting.id = timer_start(
-            \ s:pollers.semantic_highlighting.wait_milliseconds,
-            \ function( 's:PollSemanticHighlighting' ) )
+  endif
+endfunction
 
-    endif
+function! s:UpdateSemanticHighlighting( bufnr ) 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 ] ) )
 
-    call s:StopPoller( s:pollers.inlay_hints )
-    if !s:is_neovim &&
-          \ get( b:, 'ycm_enable_inlay_hints',
-          \   get( g:, 'ycm_enable_inlay_hints', 0 ) )
+  endif
+endfunction
 
-      py3 ycm_state.CurrentBuffer().SendInlayHintsRequest()
-      let s:pollers.inlay_hints.id = timer_start(
-            \ s:pollers.inlay_hints.wait_milliseconds,
-            \ function( 's:PollInlayHints' ) )
 
-    endif
+function! s:UpdateInlayHints( bufnr )
+  call s:StopPoller( s:pollers.inlay_hints )
+  if !s:is_neovim &&
+        \ get( b:, 'ycm_enable_inlay_hints',
+        \   get( g:, 'ycm_enable_inlay_hints', 0 ) )
+
+    py3 ycm_state.Buffer( int( vim.eval( 'a:bufnr' ) ) ).SendInlayHintsRequest()
+    let s:pollers.inlay_hints.id = timer_start(
+          \ s:pollers.inlay_hints.wait_milliseconds,
+          \ function( 's:PollInlayHints', [ a:bufnr ] ) )
+
   endif
 endfunction
 
@@ -810,28 +821,34 @@ function! s:PollFileParseResponse( ... )
 endfunction
 
 
-function! s:PollSemanticHighlighting( ... )
-  if !py3eval( 'ycm_state.CurrentBuffer().SemanticTokensRequestReady()' )
+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' ) )
-  elseif ! py3eval( 'ycm_state.CurrentBuffer().UpdateSemanticTokens()' )
+          \ 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' ) )
+          \ function( 's:PollSemanticHighlighting', [ a:bufnr ] ) )
   endif
 endfunction
 
 
-function! s:PollInlayHints( ... )
-  if !py3eval( 'ycm_state.CurrentBuffer().InlayHintsReady()' )
+function! s:PollInlayHints( bufnr, ... )
+  if !py3eval(
+      \ 'ycm_state.Buffer( int( vim.eval( "a:bufnr" ) ) ).InlayHintsReady()' )
     let s:pollers.inlay_hints.id = timer_start(
           \ s:pollers.inlay_hints.wait_milliseconds,
-          \ function( 's:PollInlayHints' ) )
-  elseif ! py3eval( 'ycm_state.CurrentBuffer().UpdateInlayHints()' )
+          \ function( 's:PollInlayHints', [ a:bufnr ] ) )
+  elseif ! py3eval(
+      \ 'ycm_state.Buffer( int( vim.eval( "a:bufnr" ) ) ).UpdateInlayHints()' )
     let s:pollers.inlay_hints.id = timer_start(
           \ s:pollers.inlay_hints.wait_milliseconds,
-          \ function( 's:PollInlayHints' ) )
+          \ function( 's:PollInlayHints', [ a:bufnr ] ) )
   endif
 endfunction
 
@@ -887,6 +904,16 @@ function! s:OnCursorMovedNormalMode()
 endfunction
 
 
+function! s:OnWinScrolled()
+  if !s:AllowedToCompleteInCurrentBuffer()
+    return
+  endif
+  let bufnr = winbufnr( expand( '<afile>' ) )
+  call s:UpdateSemanticHighlighting( bufnr )
+  call s:UpdateInlayHints( bufnr )
+endfunction
+
+
 function! s:OnTextChangedNormalMode()
   if !s:AllowedToCompleteInCurrentBuffer()
     return

+ 1 - 10
python/ycm/inlay_hints.py

@@ -72,16 +72,7 @@ class InlayHints:
     # Perhaps the maximal range of visible windows or something.
     request_data = BuildRequestData( self._bufnr )
     request_data.update( {
-      'range': {
-        'start': {
-          'line_num': 1,
-          'column_num': 1
-        },
-        'end': {
-          'line_num': max( len( vim.buffers[ self._bufnr ] ), 1 ),
-          'column_num': len( ToBytes( vim.buffers[ self._bufnr ][ -1 ] ) ) + 1
-        }
-      }
+      'range': vimsupport.RangeVisibleInBuffer( self._bufnr )
     } )
     self._request = InlayHintsRequest( request_data )
     self._request.Start()

+ 5 - 1
python/ycm/semantic_highlighting.py

@@ -96,7 +96,11 @@ class SemanticHighlighting:
 
     self.tick = vimsupport.GetBufferChangedTick( self._bufnr )
 
-    self._request = SemanticTokensRequest( BuildRequestData( self._bufnr ) )
+    request: dict = BuildRequestData( self._bufnr )
+    request.update( {
+      'range': vimsupport.RangeVisibleInBuffer( self._bufnr )
+    } )
+    self._request = SemanticTokensRequest( request )
     self._request.Start()
 
   def IsResponseReady( self ):

+ 51 - 0
python/ycm/vimsupport.py

@@ -181,6 +181,57 @@ def GetBufferChangedTick( bufnr ):
   return GetIntValue( f'getbufvar({ bufnr }, "changedtick")' )
 
 
+# Returns a range covering the earliest and latest lines visible in the current
+# tab page for the supplied buffer number. By default this range is then
+# extended by half of the resulting range size
+def RangeVisibleInBuffer( bufnr, factor=0.5 ):
+  windows = [ w for w in vim.eval( f'win_findbuf( { bufnr } )' )
+              if GetIntValue( vim.eval( f'win_id2tabwin( { w } )[ 0 ]' ) ) ==
+                vim.current.tabpage.number ]
+
+  class Location:
+    line: int = None
+    col: int = None
+
+  class Range:
+    start: Location = Location()
+    end: Location = Location()
+
+  buffer = vim.buffers[ bufnr ]
+
+  if not windows:
+    return None
+
+  r = Range()
+  for winid in windows:
+    win_info = vim.eval( f'getwininfo( { winid } )[ 0 ]' )
+    if r.start.line is None or r.start.line > int( win_info[ 'topline' ] ):
+      r.start.line = int( win_info[ 'topline' ] )
+    if r.end.line is None or r.end.line < int( win_info[ 'botline' ] ):
+      r.end.line = int( win_info[ 'botline' ] )
+
+  # Extend the range by 1 factor, and calculate the columns
+  num_lines = r.end.line - r.start.line + 1
+  r.start.line = max( r.start.line - int( num_lines * factor ), 1 )
+  r.start.col = 1
+  r.end.line = min( r.end.line + int( num_lines * factor ), len( buffer ) )
+  r.end.col = len( buffer[ r.end.line - 1 ] )
+
+  filepath = GetBufferFilepath( buffer )
+  return {
+    'start': {
+      'line_num': r.start.line,
+      'column_num': r.start.col,
+      'filepath': filepath,
+    },
+    'end': {
+      'line_num': r.end.line,
+      'column_num': r.end.col,
+      'filepath': filepath,
+    }
+  }
+
+
 def CaptureVimCommand( command ):
   vim.command( 'redir => b:ycm_command' )
   vim.command( f'silent! { command }' )

+ 5 - 1
python/ycm/youcompleteme.py

@@ -586,7 +586,11 @@ class YouCompleteMe:
 
 
   def CurrentBuffer( self ):
-    return self._buffers[ vimsupport.GetCurrentBufferNumber() ]
+    return self.Buffer( vimsupport.GetCurrentBufferNumber() )
+
+
+  def Buffer( self, bufnr ):
+    return self._buffers[ bufnr ]
 
 
   def OnInsertLeave( self ):