From 3818efbe28ebb43ccb91c84731693d08c1f7ddef Mon Sep 17 00:00:00 2001 From: Paris Oplopoios Date: Tue, 14 Jun 2022 19:27:38 +0300 Subject: [PATCH 001/249] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 639aea5c50..d4a4e45436 100644 --- a/README.md +++ b/README.md @@ -1023,7 +1023,7 @@ your environment, but again if your environment can't be supported, you can build or acquire `libclang` for yourself and specify it when building, as: ``` -$ EXTRA_CMAKE_ARGS='-DPATH_TO_LLVM_ROOT=/path/to/your/llvm' ./install.py --clang-compelter --system-libclang +$ EXTRA_CMAKE_ARGS='-DPATH_TO_LLVM_ROOT=/path/to/your/llvm' ./install.py --clang-completer --system-libclang ``` Please note that if using custom `clangd` or `libclang` it _must_ match the From b7b1a4c4d56416bc3d88e07a21090d2e732b0c9c Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Tue, 14 Jun 2022 18:03:58 +0100 Subject: [PATCH 002/249] Update ycmd --- third_party/ycmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/ycmd b/third_party/ycmd index ba5814d381..d1707c1488 160000 --- a/third_party/ycmd +++ b/third_party/ycmd @@ -1 +1 @@ -Subproject commit ba5814d3810c21ef9868d70ba7a8c7eef9c1d276 +Subproject commit d1707c14883ced0e32fcca9c0f5dbd6849b5f751 From 3dbba28a1a434a7d0c6d1a8e601488996cdf3d25 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Tue, 14 Jun 2022 18:04:11 +0100 Subject: [PATCH 003/249] Add maintainer PR approval process --- .mergify.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.mergify.yml b/.mergify.yml index b81442b70d..1aeb95c223 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -48,3 +48,23 @@ pull_request_rules: name: default comment: message: Thanks for sending a PR! + + - name: Manual merge on Pipelines and Maintainer Override from owner PR + conditions: + - base=master + - author=puremourning + - status-success=ubuntu-20.04 - Python 3.6 x64 + - status-success=macos-10.15 - Python 3.6 x64 + - status-success=windows-2019 - Python 3.9 x64 + - status-success=windows-2019 - Python 3.9 x86 + - status-success=Vim tests - new + - status-success=Vim tests - old + + - "#changes-requested-reviews-by=0" + - label="Ship It!" + actions: + queue: + method: merge + name: default + comment: + message: Thanks for sending a PR! From 0af70ac8921963669af6ea1f4c31b7dc1608f8bc Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Tue, 14 Jun 2022 18:38:49 +0100 Subject: [PATCH 004/249] Force 'sudo', as containers run as uid 0 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4d0e5f7d0..6b8aa57271 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,7 +66,7 @@ jobs: - name: Install dependencies run: sudo -H pip3 install -r python/test_requirements.txt - name: Build ycmd - run: python3 ./install.py --ts-completer --clangd-completer --java-completer + run: python3 ./install.py --force-sudo --ts-completer --clangd-completer --java-completer - name: Run tests in old vim # System vim should be oldest supported. if: matrix.vim == 'old' From f60bf1d26e1fe8f85311b758afa4524377aa0a9c Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 8 Aug 2020 20:39:41 +0100 Subject: [PATCH 005/249] Initial support for inefficient semantic highlighting Receive tokens asynchronously Allow overriding the property types Don't apply the highlights if buffer changed... this can lead to errors Disable by default; add a swtich to enable --- autoload/youcompleteme.vim | 44 ++++- python/ycm/buffer.py | 15 ++ python/ycm/client/semantic_tokens_request.py | 64 +++++++ python/ycm/semantic_highlighting.py | 181 +++++++++++++++++++ python/ycm/vimsupport.py | 9 +- 5 files changed, 303 insertions(+), 10 deletions(-) create mode 100644 python/ycm/client/semantic_tokens_request.py create mode 100644 python/ycm/semantic_highlighting.py diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 1090722ccb..34c044db97 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -40,28 +40,32 @@ let s:previous_allowed_buffer_number = 0 let s:pollers = { \ 'completion': { \ 'id': -1, - \ 'wait_milliseconds': 10 + \ 'wait_milliseconds': 10, \ }, \ 'signature_help': { \ 'id': -1, - \ 'wait_milliseconds': 10 + \ 'wait_milliseconds': 10, \ }, \ 'file_parse_response': { \ 'id': -1, - \ 'wait_milliseconds': 100 + \ 'wait_milliseconds': 100, \ }, \ 'server_ready': { \ 'id': -1, - \ 'wait_milliseconds': 100 + \ 'wait_milliseconds': 100, \ }, \ 'receive_messages': { \ 'id': -1, - \ 'wait_milliseconds': 100 + \ 'wait_milliseconds': 100, \ }, \ 'command': { \ 'id': -1, - \ 'wait_milliseconds': 100 - \ } + \ 'wait_milliseconds': 100, + \ }, + \ 'semantic_highlighting': { + \ 'id': -1, + \ 'wait_milliseconds': 100, + \ }, \ } let s:buftype_blacklist = { \ 'help': 1, @@ -265,6 +269,7 @@ sys.path[ 0:0 ] = [ p.join( root_folder, 'python' ), try: # Import the modules used in this file. from ycm import base, vimsupport, youcompleteme + from ycm import semantic_highlighting as ycm_semantic_highlighting if 'ycm_state' in globals(): # If re-initializing, pretend that we shut down @@ -289,6 +294,7 @@ try: default_options = {} ycm_state = youcompleteme.YouCompleteMe( default_options ) + ycm_semantic_highlighting.Initialise() except Exception as error: # We don't use PostVimMessage or EchoText from the vimsupport module because # importing this module may fail. @@ -755,6 +761,15 @@ function! s:OnFileReadyToParse( ... ) let s:pollers.file_parse_response.id = timer_start( \ s:pollers.file_parse_response.wait_milliseconds, \ function( 's:PollFileParseResponse' ) ) + + if get( g:, 'ycm_enable_semantic_highlightng', 0 ) || + \ get( b:, 'ycm_enable_semantic_highlightng', 0 ) + py3 ycm_state.CurrentBuffer().SendSemanticTokensRequest() + call s:StopPoller( s:pollers.semantic_highlighting ) + let s:pollers.semantic_highlighting.id = timer_start( + \ s:pollers.semantic_highlighting.wait_milliseconds, + \ function( 's:PollSemanticHighlighting' ) ) + endif endif endfunction @@ -774,6 +789,21 @@ function! s:PollFileParseResponse( ... ) endfunction +function! s:PollSemanticHighlighting( ... ) + if !py3eval( 'ycm_state.CurrentBuffer().SemanticTokensRequestReady()' ) + let s:pollers.semantic_highlighting.id = timer_start( + \ s:pollers.semantic_highlighting.wait_milliseconds, + \ function( 's:PollSemanticHighlighting' ) ) + endif + + if py3eval( 'ycm_state.CurrentBuffer().UpdateSemanticTokens()' ) + let s:pollers.semantic_highlighting.id = timer_start( + \ s:pollers.semantic_highlighting.wait_milliseconds, + \ function( 's:PollSemanticHighlighting' ) ) + endif +endfunction + + function! s:SendKeys( keys ) " By default keys are added to the end of the typeahead buffer. If there are " already keys in the buffer, they will be processed first and may change diff --git a/python/ycm/buffer.py b/python/ycm/buffer.py index 60b96e420d..e785eee593 100644 --- a/python/ycm/buffer.py +++ b/python/ycm/buffer.py @@ -18,6 +18,7 @@ from ycm import vimsupport from ycm.client.event_notification import EventNotification from ycm.diagnostic_interface import DiagnosticInterface +from ycm.semantic_highlighting import SemanticHighlighting # Emulates Vim buffer @@ -35,6 +36,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.UpdateFromFileTypes( filetypes ) @@ -133,6 +136,18 @@ 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 ) diff --git a/python/ycm/client/semantic_tokens_request.py b/python/ycm/client/semantic_tokens_request.py new file mode 100644 index 0000000000..beebb7cecd --- /dev/null +++ b/python/ycm/client/semantic_tokens_request.py @@ -0,0 +1,64 @@ +# Copyright (C) 2020, 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 . + + +import logging +from ycm.client.base_request import ( BaseRequest, DisplayServerException, + MakeServerException ) + +_logger = logging.getLogger( __name__ ) + + +# FIXME: This is copy/pasta from SignatureHelpRequest - abstract a +# SimpleAsyncRequest base that does all of this generically +class SemanticTokensRequest( BaseRequest ): + def __init__( self, request_data ): + super().__init__() + self.request_data = request_data + self._response_future = None + + + def Start( self ): + self._response_future = self.PostDataToHandlerAsync( self.request_data, + 'semantic_tokens' ) + + def Done( self ): + return bool( self._response_future ) and self._response_future.done() + + + def Reset( self ): + self._response_future = None + + def Response( self ): + if not self._response_future: + return {} + + response = self.HandleFuture( self._response_future, + truncate_message = True ) + + if not response: + return {} + + # Vim may not be able to convert the 'errors' entry to its internal format + # so we remove it from the response. + errors = response.pop( 'errors', [] ) + for e in errors: + exception = MakeServerException( e ) + _logger.error( exception ) + DisplayServerException( exception, truncate_message = True ) + + return response.get( 'semantic_tokens' ) or {} diff --git a/python/ycm/semantic_highlighting.py b/python/ycm/semantic_highlighting.py new file mode 100644 index 0000000000..0d4c53114f --- /dev/null +++ b/python/ycm/semantic_highlighting.py @@ -0,0 +1,181 @@ +# Copyright (C) 2020, 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 . + + +from ycm.client.semantic_tokens_request import SemanticTokensRequest +from ycm.client.base_request import BuildRequestData +from ycm import vimsupport +from ycmd import utils + +import vim +import json + + +HIGHLIGHT_GROUP = { + 'namespace': 'Type', + 'type': 'Type', + 'class': 'Structure', + 'enum': 'Structure', + 'interface': 'Structure', + 'struct': 'Structure', + 'typeParameter': 'Identifier', + 'parameter': 'Identifier', + 'variable': 'Identifier', + 'property': 'Identifier', + 'enumMember': 'Identifier', + 'enumConstant': 'Constant', + 'event': 'Identifier', + 'function': 'Function', + 'member': 'Identifier', + 'macro': 'Macro', + 'keyword': 'Keyword', + 'modifier': 'Keyword', + 'comment': 'Comment', + 'string': 'String', + 'number': 'Number', + 'regexp': 'String', + 'operator': 'Operator', +} + + +def Initialise(): + props = GetTextPropertyTypes() + if 'YCM_HL_UNKNOWN' not in props: + AddTextPropertyType( 'YCM_HL_UNKNOWN', highlight = 'WarningMsg' ) + + for token_type, group in HIGHLIGHT_GROUP.items(): + prop = f'YCM_HL_{ token_type }' + if prop not in props: + AddTextPropertyType( prop, highlight = group ) + + +# "arbitrary" base id +NEXT_TEXT_PROP_ID = 70784 + + +def NextPropID(): + global NEXT_TEXT_PROP_ID + try: + return NEXT_TEXT_PROP_ID + finally: + NEXT_TEXT_PROP_ID += 1 + + + +class SemanticHighlighting: + """Stores the semantic highlighting state for a Vim buffer""" + + def __init__( self, bufnr, user_options ): + self._request = None + self._bufnr = bufnr + self._prop_id = NextPropID() + self.tick = -1 + + + def SendRequest( self ): + if self._request and not self.IsResponseReady(): + return + + self.tick = vimsupport.GetBufferChangedTick( self._bufnr ) + self._request = SemanticTokensRequest( BuildRequestData() ) + self._request.Start() + + def IsResponseReady( self ): + return self._request is not None and self._request.Done() + + def Update( self ): + if not self.IsResponseReady(): + # Not ready - poll + return False + + if self.tick != vimsupport.GetBufferChangedTick( self._bufnr ): + # Buffer has changed, we should ignore the data and retry + # self.SendRequest() + return False + + # We requested a snapshot + response = self._request.Response() + self._request = None + + tokens = response.get( 'tokens', [] ) + + prev_prop_id = self._prop_id + self._prop_id = NextPropID() + + for token in tokens: + if token[ 'type' ] not in HIGHLIGHT_GROUP: + continue + prop_type = f"YCM_HL_{ token[ 'type' ] }" + AddTextProperty( self._bufnr, self._prop_id, prop_type, token[ 'range' ] ) + + ClearTextProperties( self._bufnr, prev_prop_id ) + + # No need to re-poll + return False + + +# FIXME/TODO: Merge this with vimsupport funcitons, added after these were +# writted for Diagnostics + +if not vimsupport.VimSupportsTextProperties(): + def AddTextPropertyType( *args, **kwargs ): + pass + def GetTextPropertyTypes( *args, **kwargs ): + return [] + def AddTextProperty( *args, **kwargs ): + pass + def ClearTextProperties( *args, **kwargs ): + pass + +else: + def AddTextPropertyType( name, **kwargs ): + props = { + 'highlight': 'Ignore', + 'combine': False, + 'start_incl': False, + 'end_incl': False, + 'priority': 10 + } + props.update( kwargs ) + + vim.eval( f"prop_type_add( '{ vimsupport.EscapeForVim( name ) }', " + f" { json.dumps( kwargs ) } )" ) + + + def GetTextPropertyTypes( *args, **kwargs ): + return [ utils.ToUnicode( p ) for p in vim.eval( 'prop_type_list()' ) ] + + + def AddTextProperty( bufnr, prop_id, prop_type, range ): + props = { + 'end_lnum': range[ 'end' ][ 'line_num' ], + 'end_col': range[ 'end' ][ 'column_num' ], + 'bufnr': bufnr, + 'id': prop_id, + 'type': prop_type + } + vim.eval( f"prop_add( { range[ 'start' ][ 'line_num' ] }," + f" { range[ 'start' ][ 'column_num' ] }," + f" { json.dumps( props ) } )" ) + + def ClearTextProperties( bufnr, prop_id ): + props = { + 'id': prop_id, + 'bufnr': bufnr, + 'all': 1, + } + vim.eval( f"prop_remove( { json.dumps( props ) } )" ) diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index 742ea13cfb..bb67269d59 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -1352,6 +1352,11 @@ def HasFastPropList(): return GetBoolValue( 'has( "patch-8.2.3652" )' ) +@memoize() +def VimSupportsTextProperties(): + return VimHasFunctions( 'prop_add', 'prop_type_add' ) + + @memoize() def VimSupportsPopupWindows(): return VimHasFunctions( 'popup_create', @@ -1360,9 +1365,7 @@ def VimSupportsPopupWindows(): 'popup_hide', 'popup_settext', 'popup_show', - 'popup_close', - 'prop_add', - 'prop_type_add' ) + 'popup_close' ) and VimSupportsTextProperties() @memoize() From 96815e442da52e755485a040a08e481f82a8f493 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 20 Nov 2021 10:41:57 +0000 Subject: [PATCH 006/249] Don't crash if a hlgroup doesn't exist --- python/ycm/semantic_highlighting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/ycm/semantic_highlighting.py b/python/ycm/semantic_highlighting.py index 0d4c53114f..33c439cd2e 100644 --- a/python/ycm/semantic_highlighting.py +++ b/python/ycm/semantic_highlighting.py @@ -59,7 +59,8 @@ def Initialise(): for token_type, group in HIGHLIGHT_GROUP.items(): prop = f'YCM_HL_{ token_type }' - if prop not in props: + if prop not in props and vimsupport.GetIntValue( + f"hlexists( '{ vimsupport.EscapeForVim( group ) }' )" ): AddTextPropertyType( prop, highlight = group ) From 62e5bfeee5db9541dddc2f76782c8dc0310fc82d Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 27 Nov 2021 13:59:42 +0000 Subject: [PATCH 007/249] Fix repeatedly spinning timer --- autoload/youcompleteme.vim | 8 ++++---- python/ycm/semantic_highlighting.py | 30 ++++++++++++++++++++--------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 34c044db97..6b8435cdc3 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -764,11 +764,13 @@ function! s:OnFileReadyToParse( ... ) if get( g:, 'ycm_enable_semantic_highlightng', 0 ) || \ get( b:, 'ycm_enable_semantic_highlightng', 0 ) - py3 ycm_state.CurrentBuffer().SendSemanticTokensRequest() + call s:StopPoller( s:pollers.semantic_highlighting ) + py3 ycm_state.CurrentBuffer().SendSemanticTokensRequest() let s:pollers.semantic_highlighting.id = timer_start( \ s:pollers.semantic_highlighting.wait_milliseconds, \ function( 's:PollSemanticHighlighting' ) ) + endif endif endfunction @@ -794,9 +796,7 @@ function! s:PollSemanticHighlighting( ... ) let s:pollers.semantic_highlighting.id = timer_start( \ s:pollers.semantic_highlighting.wait_milliseconds, \ function( 's:PollSemanticHighlighting' ) ) - endif - - if py3eval( 'ycm_state.CurrentBuffer().UpdateSemanticTokens()' ) + elseif ! py3eval( 'ycm_state.CurrentBuffer().UpdateSemanticTokens()' ) let s:pollers.semantic_highlighting.id = timer_start( \ s:pollers.semantic_highlighting.wait_milliseconds, \ function( 's:PollSemanticHighlighting' ) ) diff --git a/python/ycm/semantic_highlighting.py b/python/ycm/semantic_highlighting.py index 33c439cd2e..11bec7a65c 100644 --- a/python/ycm/semantic_highlighting.py +++ b/python/ycm/semantic_highlighting.py @@ -42,6 +42,7 @@ 'function': 'Function', 'member': 'Identifier', 'macro': 'Macro', + 'method': 'Function', 'keyword': 'Keyword', 'modifier': 'Keyword', 'comment': 'Comment', @@ -49,7 +50,9 @@ 'number': 'Number', 'regexp': 'String', 'operator': 'Operator', + 'unknown': 'Normal', } +REPORTED_MISSING_TYPES = set() def Initialise(): @@ -92,6 +95,7 @@ def SendRequest( self ): return self.tick = vimsupport.GetBufferChangedTick( self._bufnr ) + self._request = SemanticTokensRequest( BuildRequestData() ) self._request.Start() @@ -99,19 +103,23 @@ def IsResponseReady( self ): return self._request is not None and self._request.Done() def Update( self ): - if not self.IsResponseReady(): - # Not ready - poll - return False + if not self._request: + # Nothing to update + return True - if self.tick != vimsupport.GetBufferChangedTick( self._bufnr ): - # Buffer has changed, we should ignore the data and retry - # self.SendRequest() - return False + assert self.IsResponseReady() - # We requested a snapshot + # We're ready to use this response. Clear it (to avoid repeatedly + # re-polling). response = self._request.Response() self._request = None + if self.tick != vimsupport.GetBufferChangedTick( self._bufnr ): + # Buffer has changed, we should ignore the data and retry + self.SendRequest() + return False # poll again + + # We requested a snapshot tokens = response.get( 'tokens', [] ) prev_prop_id = self._prop_id @@ -119,6 +127,10 @@ def Update( self ): for token in tokens: if token[ 'type' ] not in HIGHLIGHT_GROUP: + if token[ 'type' ] not in REPORTED_MISSING_TYPES: + REPORTED_MISSING_TYPES.add( token[ 'type' ] ) + vimsupport.PostVimMessage( + f"Missing property type for { token[ 'type' ] }" ) continue prop_type = f"YCM_HL_{ token[ 'type' ] }" AddTextProperty( self._bufnr, self._prop_id, prop_type, token[ 'range' ] ) @@ -126,7 +138,7 @@ def Update( self ): ClearTextProperties( self._bufnr, prev_prop_id ) # No need to re-poll - return False + return True # FIXME/TODO: Merge this with vimsupport funcitons, added after these were From f03b70191e2d1c2c3470985804f8660cb38f5361 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Thu, 16 Dec 2021 20:35:25 +0000 Subject: [PATCH 008/249] Fix typo in semantic highlighting option --- autoload/youcompleteme.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 6b8435cdc3..f1739f3665 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -762,8 +762,8 @@ function! s:OnFileReadyToParse( ... ) \ s:pollers.file_parse_response.wait_milliseconds, \ function( 's:PollFileParseResponse' ) ) - if get( g:, 'ycm_enable_semantic_highlightng', 0 ) || - \ get( b:, 'ycm_enable_semantic_highlightng', 0 ) + if get( g:, 'ycm_enable_semantic_highlighting', 0 ) || + \ get( b:, 'ycm_enable_semantic_highlighting', 0 ) call s:StopPoller( s:pollers.semantic_highlighting ) py3 ycm_state.CurrentBuffer().SendSemanticTokensRequest() From e062744e39c7100472b0d46d3bd9fd654723504c Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Fri, 10 Jun 2022 19:58:13 +0100 Subject: [PATCH 009/249] Properly exclude neovim --- autoload/youcompleteme.vim | 7 ++- python/ycm/diagnostic_interface.py | 3 + python/ycm/semantic_highlighting.py | 93 +++++++++++++---------------- 3 files changed, 50 insertions(+), 53 deletions(-) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index f1739f3665..d948672d2b 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -762,10 +762,11 @@ function! s:OnFileReadyToParse( ... ) \ s:pollers.file_parse_response.wait_milliseconds, \ function( 's:PollFileParseResponse' ) ) - if get( g:, 'ycm_enable_semantic_highlighting', 0 ) || - \ get( b:, 'ycm_enable_semantic_highlighting', 0 ) + call s:StopPoller( s:pollers.semantic_highlighting ) + if !s:is_neovim && + \ ( get( g:, 'ycm_enable_semantic_highlighting', 0 ) || + \ get( b:, 'ycm_enable_semantic_highlighting', 0 ) ) - call s:StopPoller( s:pollers.semantic_highlighting ) py3 ycm_state.CurrentBuffer().SendSemanticTokensRequest() let s:pollers.semantic_highlighting.id = timer_start( \ s:pollers.semantic_highlighting.wait_milliseconds, diff --git a/python/ycm/diagnostic_interface.py b/python/ycm/diagnostic_interface.py index 04348d8261..0d2b492d28 100644 --- a/python/ycm/diagnostic_interface.py +++ b/python/ycm/diagnostic_interface.py @@ -143,6 +143,9 @@ def UpdateMatches( self ): diag ): global YCM_VIM_PROPERTY_ID + # FIXME: This remove() gambit probably never works because the IDs are + # almost certain to not match + # Perhaps we should have AddTextProperty return the ID? diag_prop = vimsupport.DiagnosticProperty( YCM_VIM_PROPERTY_ID, name, diff --git a/python/ycm/semantic_highlighting.py b/python/ycm/semantic_highlighting.py index 11bec7a65c..482507edb1 100644 --- a/python/ycm/semantic_highlighting.py +++ b/python/ycm/semantic_highlighting.py @@ -56,6 +56,9 @@ def Initialise(): + if vimsupport.VimIsNeovim(): + return + props = GetTextPropertyTypes() if 'YCM_HL_UNKNOWN' not in props: AddTextPropertyType( 'YCM_HL_UNKNOWN', highlight = 'WarningMsg' ) @@ -142,53 +145,43 @@ def Update( self ): # FIXME/TODO: Merge this with vimsupport funcitons, added after these were -# writted for Diagnostics - -if not vimsupport.VimSupportsTextProperties(): - def AddTextPropertyType( *args, **kwargs ): - pass - def GetTextPropertyTypes( *args, **kwargs ): - return [] - def AddTextProperty( *args, **kwargs ): - pass - def ClearTextProperties( *args, **kwargs ): - pass - -else: - def AddTextPropertyType( name, **kwargs ): - props = { - 'highlight': 'Ignore', - 'combine': False, - 'start_incl': False, - 'end_incl': False, - 'priority': 10 - } - props.update( kwargs ) - - vim.eval( f"prop_type_add( '{ vimsupport.EscapeForVim( name ) }', " - f" { json.dumps( kwargs ) } )" ) - - - def GetTextPropertyTypes( *args, **kwargs ): - return [ utils.ToUnicode( p ) for p in vim.eval( 'prop_type_list()' ) ] - - - def AddTextProperty( bufnr, prop_id, prop_type, range ): - props = { - 'end_lnum': range[ 'end' ][ 'line_num' ], - 'end_col': range[ 'end' ][ 'column_num' ], - 'bufnr': bufnr, - 'id': prop_id, - 'type': prop_type - } - vim.eval( f"prop_add( { range[ 'start' ][ 'line_num' ] }," - f" { range[ 'start' ][ 'column_num' ] }," - f" { json.dumps( props ) } )" ) - - def ClearTextProperties( bufnr, prop_id ): - props = { - 'id': prop_id, - 'bufnr': bufnr, - 'all': 1, - } - vim.eval( f"prop_remove( { json.dumps( props ) } )" ) +# written. It's not trivial, as those vimsupport functions are a bit fiddly. +# They also support neovim, but we don't. +def AddTextPropertyType( name, **kwargs ): + props = { + 'highlight': 'Ignore', + 'combine': False, + 'start_incl': False, + 'end_incl': False, + 'priority': 10 + } + props.update( kwargs ) + + vim.eval( f"prop_type_add( '{ vimsupport.EscapeForVim( name ) }', " + f" { json.dumps( kwargs ) } )" ) + + +def GetTextPropertyTypes( *args, **kwargs ): + return [ utils.ToUnicode( p ) for p in vim.eval( 'prop_type_list()' ) ] + + +def AddTextProperty( bufnr, prop_id, prop_type, range ): + props = { + 'end_lnum': range[ 'end' ][ 'line_num' ], + 'end_col': range[ 'end' ][ 'column_num' ], + 'bufnr': bufnr, + 'id': prop_id, + 'type': prop_type + } + vim.eval( f"prop_add( { range[ 'start' ][ 'line_num' ] }," + f" { range[ 'start' ][ 'column_num' ] }," + f" { json.dumps( props ) } )" ) + + +def ClearTextProperties( bufnr, prop_id ): + props = { + 'id': prop_id, + 'bufnr': bufnr, + 'all': 1, + } + vim.eval( f"prop_remove( { json.dumps( props ) } )" ) From 231f808972cc3d830e65d0571cc5b11394072d55 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Fri, 10 Jun 2022 20:32:52 +0100 Subject: [PATCH 010/249] Update readme, make b:ycm_enable_semantic_highlighting work --- README.md | 71 ++++++++++++++++++++++++++++++++++++++ autoload/youcompleteme.vim | 4 +-- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d4a4e45436..c3d59f31e5 100644 --- a/README.md +++ b/README.md @@ -733,6 +733,7 @@ Quick Feature Summary * Reference finding (`GoToReferences`) * Renaming symbols (`RefactorRename `) * Code formatting (`Format`) +* Semantic highlighting ### C♯ @@ -808,6 +809,7 @@ Quick Feature Summary * Renaming symbols (`RefactorRename `) * Code formatting (`Format`) * Management of `rust-analyzer` server instance +* Semantic highlighting ### Java @@ -830,6 +832,7 @@ Quick Feature Summary * Detection of java projects * Execute custom server command (`ExecuteCommand `) * Management of `jdt.ls` server instance +* Semantic highlighting User Guide ---------- @@ -958,6 +961,74 @@ _NOTE_: No default mapping is provided because insert mappings are very difficult to create without breaking or overriding some existing functionality. Ctrl-l is not a suggestion, just an example. +### Semantic highlighting + +**NOTE**: This feature is highly experimental and offered in the hope that it is +useful. It shall not be considered stable; if you find issues with it, feel free +to report them however. + +Semantic highlighting is the process where the buffer text is coloured according +to the underlying semantic type of the word, rather than classic syntax +highlighting based on regular expressions. This can be powerful additional data +that we can process very quickly. + +This feature is only supported in Vim. + +For example, here is a function with classic highlighting: + +![highliting-classic](https://user-images.githubusercontent.com/10584846/173137003-a265e8b0-84db-4993-98f0-03ee81b9de94.png) + +And here is the same function with semantic highlighting: + +![highliting-semantic](https://user-images.githubusercontent.com/10584846/173137012-7547de0b-145f-45fa-ace3-18943acd2141.png) + +As you can see, the function calls, macros, etc. are correctly identified. + +This can be enabled globally with `let g:ycm_enable_semantic_highlighting=1` or +per buffer, by setting `b:ycn_enable_semantic_highlighting`. + +#### Customising the highlight groups + +YCM uses text properties (see `:help text-prop-intro`) for semantic +highlighting. In order to customise the coloring, you can define the text +properties that are used. + +If you define a text property named `YCM_HL_`, then it will be used +in place of the defaults. The `` is defined as the Language Server +Protocol semantic token type, defined in the [LSP Spec](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens). + +Some servers also use custom values. In this case, YCM prints a warning +including the token type name that you can customise. + +For example, to render `parameter` tokens using the `Nomral` highlight group, +you can do this: + +```viml +call prop_type_add( 'YCM_HL_parameter', { 'highlight': 'Normal' } ) +``` + +More generally, this pattern can be useful for customising the groups: + +```viml +let MY_YCM_HIGHLIGHT_GROUP = { + \ 'typeParameter': 'PreProc', + \ 'parameter': 'Normal', + \ 'variable': 'Normal', + \ 'property': 'Normal', + \ 'enumMember': 'Normal', + \ 'event': 'Special', + \ 'member': 'Normal', + \ 'method': 'Normal', + \ 'class': 'Special', + \ 'namespace': 'Special', + \ } + +for tokenType in keys( MY_YCM_HIGHLIGHT_GROUP ) + call prop_type_add( 'YCM_HL_' . tokenType, + \ { 'highlight': MY_YCM_HIGHLIGHT_GROUP[ tokenType ] } ) +endfor +``` + ### General Semantic Completion You can use Ctrl+Space to trigger the completion suggestions anywhere, even diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index d948672d2b..8a1806c815 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -764,8 +764,8 @@ function! s:OnFileReadyToParse( ... ) call s:StopPoller( s:pollers.semantic_highlighting ) if !s:is_neovim && - \ ( get( g:, 'ycm_enable_semantic_highlighting', 0 ) || - \ get( b:, 'ycm_enable_semantic_highlighting', 0 ) ) + \ get( b:, 'ycm_enable_semantic_highlighting', + \ get( g:, 'ycm_enable_semantic_highlighting', 0 ) ) py3 ycm_state.CurrentBuffer().SendSemanticTokensRequest() let s:pollers.semantic_highlighting.id = timer_start( From 7eeeca3ac5c4018992fbdfaf911a4d52589bbc12 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 11 Jun 2022 15:13:58 +0100 Subject: [PATCH 011/249] All supported vims support text properties --- python/ycm/vimsupport.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index bb67269d59..2bb9bb8f54 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -1352,11 +1352,6 @@ def HasFastPropList(): return GetBoolValue( 'has( "patch-8.2.3652" )' ) -@memoize() -def VimSupportsTextProperties(): - return VimHasFunctions( 'prop_add', 'prop_type_add' ) - - @memoize() def VimSupportsPopupWindows(): return VimHasFunctions( 'popup_create', @@ -1365,7 +1360,7 @@ def VimSupportsPopupWindows(): 'popup_hide', 'popup_settext', 'popup_show', - 'popup_close' ) and VimSupportsTextProperties() + 'popup_close' ) @memoize() From c99f408270e8a17a2c84ec59e898afd711823fd7 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Tue, 14 Jun 2022 20:15:42 +0100 Subject: [PATCH 012/249] Update TOC --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c3d59f31e5..b97e69451e 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ Contents - [Completion String Ranking](#completion-string-ranking) - [General Semantic Completion](#general-semantic-completion) - [Signature Help](#signature-help) + - [Semantic Highlighting](#semantic-highlighting) - [C-family Semantic Completion](#c-family-semantic-completion) - [Java Semantic Completion](#java-semantic-completion) - [C# Semantic Completion](#c-semantic-completion) From fcf61f20b1c8f5a428faf20352783fb23a4d685c Mon Sep 17 00:00:00 2001 From: Heyward Fann Date: Thu, 16 Jun 2022 16:52:21 +0800 Subject: [PATCH 013/249] fix: typos with ycm_enable_semantic_highlighting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b97e69451e..77303fd950 100644 --- a/README.md +++ b/README.md @@ -986,7 +986,7 @@ And here is the same function with semantic highlighting: As you can see, the function calls, macros, etc. are correctly identified. This can be enabled globally with `let g:ycm_enable_semantic_highlighting=1` or -per buffer, by setting `b:ycn_enable_semantic_highlighting`. +per buffer, by setting `b:ycm_enable_semantic_highlighting`. #### Customising the highlight groups From bb235ac8e92ada3ebbf1b22c62f04064ade29b3a Mon Sep 17 00:00:00 2001 From: Matheus Souza Date: Tue, 5 Jul 2022 20:40:35 -0300 Subject: [PATCH 014/249] fix: typo in highlight groups section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 77303fd950..ffddfa4738 100644 --- a/README.md +++ b/README.md @@ -1001,7 +1001,7 @@ Protocol semantic token type, defined in the [LSP Spec](https://microsoft.github Some servers also use custom values. In this case, YCM prints a warning including the token type name that you can customise. -For example, to render `parameter` tokens using the `Nomral` highlight group, +For example, to render `parameter` tokens using the `Normal` highlight group, you can do this: ```viml From 93003daf9695604ded32ad85820304e56dee7571 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 8 Aug 2022 14:10:13 +0100 Subject: [PATCH 015/249] WIP: Add support for inlay hints First cut of just spamming them in there. Very inefficient, doesn't quite work yet --- autoload/youcompleteme.vim | 32 ++++++ python/ycm/buffer.py | 17 ++- python/ycm/client/inlay_hints_request.py | 64 +++++++++++ python/ycm/inlay_hints.py | 135 +++++++++++++++++++++++ python/ycm/semantic_highlighting.py | 62 ++--------- python/ycm/text_properties.py | 74 +++++++++++++ 6 files changed, 330 insertions(+), 54 deletions(-) create mode 100644 python/ycm/client/inlay_hints_request.py create mode 100644 python/ycm/inlay_hints.py create mode 100644 python/ycm/text_properties.py diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 8a1806c815..7084ec88e9 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -66,6 +66,10 @@ let s:pollers = { \ 'id': -1, \ 'wait_milliseconds': 100, \ }, + \ 'inlay_hints': { + \ 'id': -1, + \ 'wait_milliseconds': 100, + \ }, \ } let s:buftype_blacklist = { \ 'help': 1, @@ -270,6 +274,7 @@ try: # Import the modules used in this file. from ycm import base, vimsupport, youcompleteme from ycm import semantic_highlighting as ycm_semantic_highlighting + from ycm import inlay_hints as ycm_inlay_hints if 'ycm_state' in globals(): # If re-initializing, pretend that we shut down @@ -295,6 +300,7 @@ try: ycm_state = youcompleteme.YouCompleteMe( default_options ) ycm_semantic_highlighting.Initialise() + ycm_inlay_hints.Initialise() except Exception as error: # We don't use PostVimMessage or EchoText from the vimsupport module because # importing this module may fail. @@ -773,6 +779,18 @@ function! s:OnFileReadyToParse( ... ) \ function( 's:PollSemanticHighlighting' ) ) endif + + 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.CurrentBuffer().SendInlayHintsRequest() + let s:pollers.inlay_hints.id = timer_start( + \ s:pollers.inlay_hints.wait_milliseconds, + \ function( 's:PollInlayHints' ) ) + + endif endif endfunction @@ -805,6 +823,20 @@ function! s:PollSemanticHighlighting( ... ) endfunction +function! s:PollInlayHints( ... ) + if !py3eval( 'ycm_state.CurrentBuffer().InlayHintsReady()' ) + let s:pollers.inlay_hints.id = timer_start( + \ s:pollers.inlay_hints.wait_milliseconds, + \ function( 's:PollInlayHints' ) ) + elseif ! py3eval( 'ycm_state.CurrentBuffer().UpdateInlayHints()' ) + let s:pollers.inlay_hints.id = timer_start( + \ s:pollers.inlay_hints.wait_milliseconds, + \ function( 's:PollInlayHints' ) ) + endif +endfunction + + + function! s:SendKeys( keys ) " By default keys are added to the end of the typeahead buffer. If there are " already keys in the buffer, they will be processed first and may change diff --git a/python/ycm/buffer.py b/python/ycm/buffer.py index e785eee593..5e43cb89d9 100644 --- a/python/ycm/buffer.py +++ b/python/ycm/buffer.py @@ -19,6 +19,7 @@ from ycm.client.event_notification import EventNotification from ycm.diagnostic_interface import DiagnosticInterface from ycm.semantic_highlighting import SemanticHighlighting +from ycm.inlay_hints import InlayHints # Emulates Vim buffer @@ -36,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._semantic_highlighting = SemanticHighlighting( bufnr, user_options ) + self._inlay_hints = InlayHints( bufnr, user_options ) self.UpdateFromFileTypes( filetypes ) @@ -148,6 +149,18 @@ def UpdateSemanticTokens( self ): return self._semantic_highlighting.Update() + def SendInlayHintsRequest( self ): + self._inlay_hints.SendRequest() + + + def InlayHintsReady( self ): + return self._inlay_hints.IsResponseReady() + + + def UpdateInlayHints( self ): + return self._inlay_hints.Update() + + def _ChangedTick( self ): return vimsupport.GetBufferChangedTick( self._number ) diff --git a/python/ycm/client/inlay_hints_request.py b/python/ycm/client/inlay_hints_request.py new file mode 100644 index 0000000000..ce1225dbda --- /dev/null +++ b/python/ycm/client/inlay_hints_request.py @@ -0,0 +1,64 @@ +# Copyright (C) 2022, 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 . + + +import logging +from ycm.client.base_request import ( BaseRequest, DisplayServerException, + MakeServerException ) + +_logger = logging.getLogger( __name__ ) + + +# FIXME: This is copy/pasta from SemanticTokensRequest - abstract a +# SimpleAsyncRequest base that does all of this generically +class InlayHintsRequest( BaseRequest ): + def __init__( self, request_data ): + super().__init__() + self.request_data = request_data + self._response_future = None + + + def Start( self ): + self._response_future = self.PostDataToHandlerAsync( self.request_data, + 'inlay_hints' ) + + def Done( self ): + return bool( self._response_future ) and self._response_future.done() + + + def Reset( self ): + self._response_future = None + + def Response( self ): + if not self._response_future: + return [] + + response = self.HandleFuture( self._response_future, + truncate_message = True ) + + if not response: + return [] + + # Vim may not be able to convert the 'errors' entry to its internal format + # so we remove it from the response. + errors = response.pop( 'errors', [] ) + for e in errors: + exception = MakeServerException( e ) + _logger.error( exception ) + DisplayServerException( exception, truncate_message = True ) + + return response.get( 'inlay_hints' ) or [] diff --git a/python/ycm/inlay_hints.py b/python/ycm/inlay_hints.py new file mode 100644 index 0000000000..35038a9815 --- /dev/null +++ b/python/ycm/inlay_hints.py @@ -0,0 +1,135 @@ +# Copyright (C) 2022, 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 . + + +from ycm.client.inlay_hints_request import InlayHintsRequest +from ycm.client.base_request import BuildRequestData +from ycm import vimsupport +from ycm import text_properties as tp +import vim +from ycmd.utils import ToBytes + + +HIGHLIGHT_GROUP = { + # 1-based inedexes + 0: '', + 1: 'Comment', # Type + 2: 'Comment' # Parameter +} +REPORTED_MISSING_TYPES = set() + + +def Initialise(): + if vimsupport.VimIsNeovim(): + return + + props = tp.GetTextPropertyTypes() + if 'YCM_INLAY_UNKNOWN' not in props: + tp.AddTextPropertyType( 'YCM_INLAY_UNKNOWN', highlight = 'Comment' ) + + for token_type, group in HIGHLIGHT_GROUP.items(): + prop = f'YCM_INLAY_{ token_type }' + if prop not in props and vimsupport.GetIntValue( + f"hlexists( '{ vimsupport.EscapeForVim( group ) }' )" ): + tp.AddTextPropertyType( prop, highlight = group ) + + +class InlayHints: + """Stores the inlay hints state for a Vim buffer""" + + def __init__( self, bufnr, user_options ): + self._request = None + self._bufnr = bufnr + self._prop_ids = set() + self.tick = -1 + + + def SendRequest( self ): + if self._request and not self.IsResponseReady(): + return + + self.tick = vimsupport.GetBufferChangedTick( self._bufnr ) + + # TODO: How to determine the range to display ? Should we do the range + # visible in "all" windows? We're doing this per-buffer, but perhaps it + # should actually be per-window; that might ultimately be a better model + # but the resulting properties are per-buffer, not per-window. + # + # 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 + } + } + } ) + self._request = InlayHintsRequest( request_data ) + self._request.Start() + + def IsResponseReady( self ): + return self._request is not None and self._request.Done() + + def Update( self ): + if not self._request: + # Nothing to update + return True + + assert self.IsResponseReady() + + # We're ready to use this response. Clear it (to avoid repeatedly + # re-polling). + 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.SendRequest() + return False # poll again + + for prop_id in self._prop_ids: + tp.ClearTextProperties( self._bufnr, prop_id ) + + self._prop_ids.clear() + + for inlay_hint in inlay_hints: + 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_' + str( inlay_hint[ 'kind' ] ) + + self._prop_ids.add( + tp.AddTextProperty( self._bufnr, + None, + prop_type, + { + 'start': inlay_hint[ 'position' ], + 'end': inlay_hint[ 'position' ], + }, + { + 'text': inlay_hint[ 'label' ] + } ) ) + + # No need to re-poll + return True diff --git a/python/ycm/semantic_highlighting.py b/python/ycm/semantic_highlighting.py index 482507edb1..9fb9de8bdd 100644 --- a/python/ycm/semantic_highlighting.py +++ b/python/ycm/semantic_highlighting.py @@ -19,10 +19,7 @@ from ycm.client.semantic_tokens_request import SemanticTokensRequest from ycm.client.base_request import BuildRequestData from ycm import vimsupport -from ycmd import utils - -import vim -import json +from ycm import text_properties as tp HIGHLIGHT_GROUP = { @@ -59,15 +56,15 @@ def Initialise(): if vimsupport.VimIsNeovim(): return - props = GetTextPropertyTypes() + props = tp.GetTextPropertyTypes() if 'YCM_HL_UNKNOWN' not in props: - AddTextPropertyType( 'YCM_HL_UNKNOWN', highlight = 'WarningMsg' ) + tp.AddTextPropertyType( 'YCM_HL_UNKNOWN', highlight = 'WarningMsg' ) for token_type, group in HIGHLIGHT_GROUP.items(): prop = f'YCM_HL_{ token_type }' if prop not in props and vimsupport.GetIntValue( f"hlexists( '{ vimsupport.EscapeForVim( group ) }' )" ): - AddTextPropertyType( prop, highlight = group ) + tp.AddTextPropertyType( prop, highlight = group ) # "arbitrary" base id @@ -99,7 +96,7 @@ def SendRequest( self ): self.tick = vimsupport.GetBufferChangedTick( self._bufnr ) - self._request = SemanticTokensRequest( BuildRequestData() ) + self._request = SemanticTokensRequest( BuildRequestData( self._bufnr ) ) self._request.Start() def IsResponseReady( self ): @@ -136,52 +133,13 @@ def Update( self ): f"Missing property type for { token[ 'type' ] }" ) continue prop_type = f"YCM_HL_{ token[ 'type' ] }" - AddTextProperty( self._bufnr, self._prop_id, prop_type, token[ 'range' ] ) + tp.AddTextProperty( self._bufnr, + self._prop_id, + prop_type, + token[ 'range' ] ) - ClearTextProperties( self._bufnr, prev_prop_id ) + tp.ClearTextProperties( self._bufnr, prev_prop_id ) # No need to re-poll return True - -# FIXME/TODO: Merge this with vimsupport funcitons, added after these were -# written. It's not trivial, as those vimsupport functions are a bit fiddly. -# They also support neovim, but we don't. -def AddTextPropertyType( name, **kwargs ): - props = { - 'highlight': 'Ignore', - 'combine': False, - 'start_incl': False, - 'end_incl': False, - 'priority': 10 - } - props.update( kwargs ) - - vim.eval( f"prop_type_add( '{ vimsupport.EscapeForVim( name ) }', " - f" { json.dumps( kwargs ) } )" ) - - -def GetTextPropertyTypes( *args, **kwargs ): - return [ utils.ToUnicode( p ) for p in vim.eval( 'prop_type_list()' ) ] - - -def AddTextProperty( bufnr, prop_id, prop_type, range ): - props = { - 'end_lnum': range[ 'end' ][ 'line_num' ], - 'end_col': range[ 'end' ][ 'column_num' ], - 'bufnr': bufnr, - 'id': prop_id, - 'type': prop_type - } - vim.eval( f"prop_add( { range[ 'start' ][ 'line_num' ] }," - f" { range[ 'start' ][ 'column_num' ] }," - f" { json.dumps( props ) } )" ) - - -def ClearTextProperties( bufnr, prop_id ): - props = { - 'id': prop_id, - 'bufnr': bufnr, - 'all': 1, - } - vim.eval( f"prop_remove( { json.dumps( props ) } )" ) diff --git a/python/ycm/text_properties.py b/python/ycm/text_properties.py new file mode 100644 index 0000000000..3deddc345c --- /dev/null +++ b/python/ycm/text_properties.py @@ -0,0 +1,74 @@ +# Copyright (C) 2020, 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 . + + +from ycm import vimsupport +from ycmd import utils + +import vim +import json + + +# FIXME/TODO: Merge this with vimsupport funcitons, added after these were +# written. It's not trivial, as those vimsupport functions are a bit fiddly. +# They also support neovim, but we don't. +def AddTextPropertyType( name, **kwargs ): + props = { + 'highlight': 'Ignore', + 'combine': False, + 'start_incl': False, + 'end_incl': False, + 'priority': 10 + } + props.update( kwargs ) + + vim.eval( f"prop_type_add( '{ vimsupport.EscapeForVim( name ) }', " + f" { json.dumps( kwargs ) } )" ) + + +def GetTextPropertyTypes( *args, **kwargs ): + return [ utils.ToUnicode( p ) for p in vim.eval( 'prop_type_list()' ) ] + + +def AddTextProperty( bufnr, + prop_id, + prop_type, + range, + extra_args: dict = None ): + props = { + 'end_lnum': range[ 'end' ][ 'line_num' ], + 'end_col': range[ 'end' ][ 'column_num' ], + 'bufnr': bufnr, + 'type': prop_type + } + if prop_id is not None: + props[ 'id' ]: prop_id + if extra_args: + props.update( extra_args ) + return vim.eval( f"prop_add( { range[ 'start' ][ 'line_num' ] }," + f" { range[ 'start' ][ 'column_num' ] }," + f" { json.dumps( props ) } )" ) + + +def ClearTextProperties( bufnr, prop_id ): + props = { + 'id': prop_id, + 'bufnr': bufnr, + 'all': 1, + } + vim.eval( f"prop_remove( { json.dumps( props ) } )" ) + From 9c01c5ddf68224b6892bc3c38d4a51367bf51d88 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 8 Aug 2022 14:12:33 +0100 Subject: [PATCH 016/249] Update ycmd --- third_party/ycmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/ycmd b/third_party/ycmd index d1707c1488..c21284ccd2 160000 --- a/third_party/ycmd +++ b/third_party/ycmd @@ -1 +1 @@ -Subproject commit d1707c14883ced0e32fcca9c0f5dbd6849b5f751 +Subproject commit c21284ccd29d6e6e0c4013e7d8e7067d7c43a3a8 From 4d014ea5de1928080fae307e309552fdc3260f1e Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Wed, 10 Aug 2022 20:22:28 +0100 Subject: [PATCH 017/249] Inlay Hints: clear on insert enter (TODO: alternatively only display strictly in normal mode) --- autoload/youcompleteme.vim | 7 ++++++- python/ycm/buffer.py | 4 ++++ python/ycm/inlay_hints.py | 12 ++++++++---- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 7084ec88e9..f5a14f048f 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -231,7 +231,7 @@ function! youcompleteme#EnableCursorMovedAutocommands() autocmd! autocmd CursorMoved * call s:OnCursorMovedNormalMode() autocmd CursorMovedI * let s:current_cursor_position = getpos( '.' ) - autocmd InsertEnter * let s:current_cursor_position = getpos( '.' ) + autocmd InsertEnter * call s:OnInsertEnter() autocmd TextChanged * call s:OnTextChangedNormalMode() autocmd TextChangedI * call s:OnTextChangedInsertMode( v:false ) autocmd TextChangedP * call s:OnTextChangedInsertMode( v:true ) @@ -953,6 +953,11 @@ function! s:OnTextChangedInsertMode( popup_is_visible ) endfunction +function! s:OnInsertEnter() abort + let s:current_cursor_position = getpos( '.' ) + py3 ycm_state.CurrentBuffer().ClearInlayHints() +endfunction + function! s:OnInsertLeave() if !s:AllowedToCompleteInCurrentBuffer() return diff --git a/python/ycm/buffer.py b/python/ycm/buffer.py index 5e43cb89d9..8b7b331db5 100644 --- a/python/ycm/buffer.py +++ b/python/ycm/buffer.py @@ -161,6 +161,10 @@ def UpdateInlayHints( self ): return self._inlay_hints.Update() + def ClearInlayHints( self ): + return self._inlay_hints.Clear() + + def _ChangedTick( self ): return vimsupport.GetBufferChangedTick( self._number ) diff --git a/python/ycm/inlay_hints.py b/python/ycm/inlay_hints.py index 35038a9815..931fff7386 100644 --- a/python/ycm/inlay_hints.py +++ b/python/ycm/inlay_hints.py @@ -89,6 +89,13 @@ def SendRequest( self ): def IsResponseReady( self ): return self._request is not None and self._request.Done() + + def Clear( self ): + for prop_id in self._prop_ids: + tp.ClearTextProperties( self._bufnr, prop_id ) + self._prop_ids.clear() + + def Update( self ): if not self._request: # Nothing to update @@ -106,10 +113,7 @@ def Update( self ): self.SendRequest() return False # poll again - for prop_id in self._prop_ids: - tp.ClearTextProperties( self._bufnr, prop_id ) - - self._prop_ids.clear() + self.Clear() for inlay_hint in inlay_hints: if 'kind' not in inlay_hint: From eb6ac0407113e32959d0e4d4db475874d6899d42 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Wed, 10 Aug 2022 21:45:30 +0100 Subject: [PATCH 018/249] 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. --- autoload/youcompleteme.vim | 83 +++++++++++++++++++---------- python/ycm/inlay_hints.py | 11 +--- python/ycm/semantic_highlighting.py | 6 ++- python/ycm/vimsupport.py | 51 ++++++++++++++++++ python/ycm/youcompleteme.py | 6 ++- 5 files changed, 117 insertions(+), 40 deletions(-) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index f5a14f048f..7404ac5caf 100644 --- a/autoload/youcompleteme.vim +++ b/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( '' ) ) + call s:UpdateSemanticHighlighting( bufnr ) + call s:UpdateInlayHints( bufnr ) +endfunction + + function! s:OnTextChangedNormalMode() if !s:AllowedToCompleteInCurrentBuffer() return diff --git a/python/ycm/inlay_hints.py b/python/ycm/inlay_hints.py index 931fff7386..1915cfd0db 100644 --- a/python/ycm/inlay_hints.py +++ b/python/ycm/inlay_hints.py @@ -72,16 +72,7 @@ def SendRequest( self ): # 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() diff --git a/python/ycm/semantic_highlighting.py b/python/ycm/semantic_highlighting.py index 9fb9de8bdd..1a5686ba61 100644 --- a/python/ycm/semantic_highlighting.py +++ b/python/ycm/semantic_highlighting.py @@ -96,7 +96,11 @@ def SendRequest( self ): 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 ): diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index 2bb9bb8f54..e5bbbe7a9a 100644 --- a/python/ycm/vimsupport.py +++ b/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 }' ) diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index f2c769d66f..7bdca60ed8 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -586,7 +586,11 @@ def OnBufferVisit( self ): def CurrentBuffer( self ): - return self._buffers[ vimsupport.GetCurrentBufferNumber() ] + return self.Buffer( vimsupport.GetCurrentBufferNumber() ) + + + def Buffer( self, bufnr ): + return self._buffers[ bufnr ] def OnInsertLeave( self ): From 6cd31c1888a4319e06d1ba15b0deb8f9a91c493d Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Wed, 10 Aug 2022 21:47:13 +0100 Subject: [PATCH 019/249] Update ycmd for ranges --- third_party/ycmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/ycmd b/third_party/ycmd index c21284ccd2..6562a2c5be 160000 --- a/third_party/ycmd +++ b/third_party/ycmd @@ -1 +1 @@ -Subproject commit c21284ccd29d6e6e0c4013e7d8e7067d7c43a3a8 +Subproject commit 6562a2c5be256592826f910a588ace51bcce0000 From 25bb6e4aa5b03e1ff152ef59ca9a06ce18f47415 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Wed, 10 Aug 2022 22:24:58 +0100 Subject: [PATCH 020/249] Add note that we can reduce the size of the request and update a lot further --- python/ycm/inlay_hints.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/ycm/inlay_hints.py b/python/ycm/inlay_hints.py index 1915cfd0db..4b9f563281 100644 --- a/python/ycm/inlay_hints.py +++ b/python/ycm/inlay_hints.py @@ -51,6 +51,8 @@ def Initialise(): class InlayHints: """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 From 777f61c816aba447ddb710314699eb8fd4a1fca1 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Thu, 11 Aug 2022 09:33:24 +0100 Subject: [PATCH 021/249] Inlay Hints: cache results and reinstate on insert leave --- autoload/youcompleteme.vim | 13 +++++++++---- python/ycm/buffer.py | 18 +----------------- python/ycm/inlay_hints.py | 26 +++++++++++++++++--------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 7404ac5caf..26c9d22fa9 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -797,7 +797,7 @@ function! s:UpdateInlayHints( bufnr ) \ get( b:, 'ycm_enable_inlay_hints', \ get( g:, 'ycm_enable_inlay_hints', 0 ) ) - py3 ycm_state.Buffer( int( vim.eval( 'a:bufnr' ) ) ).SendInlayHintsRequest() + py3 ycm_state.Buffer( int( vim.eval( 'a:bufnr' ) ) ).inlay_hints.Request() let s:pollers.inlay_hints.id = timer_start( \ s:pollers.inlay_hints.wait_milliseconds, \ function( 's:PollInlayHints', [ a:bufnr ] ) ) @@ -840,12 +840,14 @@ endfunction function! s:PollInlayHints( bufnr, ... ) if !py3eval( - \ 'ycm_state.Buffer( int( vim.eval( "a:bufnr" ) ) ).InlayHintsReady()' ) + \ '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 ] ) ) elseif ! py3eval( - \ 'ycm_state.Buffer( int( vim.eval( "a:bufnr" ) ) ).UpdateInlayHints()' ) + \ '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 ] ) ) @@ -982,7 +984,7 @@ endfunction function! s:OnInsertEnter() abort let s:current_cursor_position = getpos( '.' ) - py3 ycm_state.CurrentBuffer().ClearInlayHints() + py3 ycm_state.CurrentBuffer().inlay_hints.Clear() endfunction function! s:OnInsertLeave() @@ -1004,6 +1006,9 @@ function! s:OnInsertLeave() endif call s:ClearSignatureHelp() + " We cleared inlay hints on insert enter + " TODO: Probalby should use ModeChange + py3 ycm_state.CurrentBuffer().inlay_hints.Refresh() endfunction diff --git a/python/ycm/buffer.py b/python/ycm/buffer.py index 8b7b331db5..f010d28aad 100644 --- a/python/ycm/buffer.py +++ b/python/ycm/buffer.py @@ -38,7 +38,7 @@ def __init__( self, bufnr, user_options, filetypes ): 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.inlay_hints = InlayHints( bufnr, user_options ) self.UpdateFromFileTypes( filetypes ) @@ -149,22 +149,6 @@ def UpdateSemanticTokens( self ): return self._semantic_highlighting.Update() - def SendInlayHintsRequest( self ): - self._inlay_hints.SendRequest() - - - def InlayHintsReady( self ): - return self._inlay_hints.IsResponseReady() - - - def UpdateInlayHints( self ): - return self._inlay_hints.Update() - - - def ClearInlayHints( self ): - return self._inlay_hints.Clear() - - def _ChangedTick( self ): return vimsupport.GetBufferChangedTick( self._number ) diff --git a/python/ycm/inlay_hints.py b/python/ycm/inlay_hints.py index 4b9f563281..7ce0d52b79 100644 --- a/python/ycm/inlay_hints.py +++ b/python/ycm/inlay_hints.py @@ -58,10 +58,11 @@ def __init__( self, bufnr, user_options ): self._bufnr = bufnr self._prop_ids = set() self.tick = -1 + self._latest_inlay_hints = [] - def SendRequest( self ): - if self._request and not self.IsResponseReady(): + def Request( self ): + if self._request and not self.Ready(): return self.tick = vimsupport.GetBufferChangedTick( self._bufnr ) @@ -79,7 +80,8 @@ def SendRequest( self ): self._request = InlayHintsRequest( request_data ) self._request.Start() - def IsResponseReady( self ): + + def Ready( self ): return self._request is not None and self._request.Done() @@ -94,11 +96,12 @@ def Update( self ): # Nothing to update return True - assert self.IsResponseReady() + assert self.Ready() # We're ready to use this response. Clear it (to avoid repeatedly # re-polling). - inlay_hints = self._request.Response() + self._latest_inlay_hints = [] ;# in case there was an error in request + self._latest_inlay_hints = self._request.Response() self._request = None if self.tick != vimsupport.GetBufferChangedTick( self._bufnr ): @@ -106,10 +109,17 @@ def Update( self ): self.SendRequest() return False # poll again + self.Refresh() + + # No need to re-poll + return True + + + def Refresh( self ): self.Clear() - for inlay_hint in inlay_hints: - if 'kind' not in inlay_hint: + for inlay_hint in self._latest_inlay_hints: + if 'kind' not in inlay_hint: prop_type = 'YCM_INLAY_UNKNOWN' elif inlay_hint[ 'kind' ] not in HIGHLIGHT_GROUP: prop_type = 'YCM_INLAY_UNKNOWN' @@ -128,5 +138,3 @@ def Update( self ): 'text': inlay_hint[ 'label' ] } ) ) - # No need to re-poll - return True From 77817add67903d0a38ff0e338688cffdf7b7f15f Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Thu, 11 Aug 2022 11:05:53 +0100 Subject: [PATCH 022/249] Inlay Hints: only refresh if buffer has not changed --- python/ycm/inlay_hints.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/python/ycm/inlay_hints.py b/python/ycm/inlay_hints.py index 7ce0d52b79..4105d472d8 100644 --- a/python/ycm/inlay_hints.py +++ b/python/ycm/inlay_hints.py @@ -65,6 +65,8 @@ def Request( self ): if self._request and not self.Ready(): return + # We're requesting changes, so the existing results are now invalid + self._latest_inlay_hints = [] self.tick = vimsupport.GetBufferChangedTick( self._bufnr ) # TODO: How to determine the range to display ? Should we do the range @@ -106,16 +108,28 @@ def Update( self ): if self.tick != vimsupport.GetBufferChangedTick( self._bufnr ): # Buffer has changed, we should ignore the data and retry - self.SendRequest() + self.Request() return False # poll again - self.Refresh() + self._Draw() # No need to re-poll return True def Refresh( self ): + if self.tick != vimsupport.GetBufferChangedTick( self._bufnr ): + # state 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: From 3ff522841e950ad9e9bd3e379ed80a8e46422667 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 13 Aug 2022 12:37:00 +0100 Subject: [PATCH 023/249] VirtualText - add a test for virtual text suppport --- python/ycm/vimsupport.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index e5bbbe7a9a..df1955284e 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -60,6 +60,10 @@ YCM_NEOVIM_NS_ID = vim.eval( 'g:ycm_neovim_ns_id' ) +# Virtual text is not a feature in itself and early patches don't work well, so +# we need to keep changing this at the moment +VIM_VIRTUAL_TEXT_VERSION_REQ = '9.0.193' + def CurrentLineAndColumn(): """Returns the 0-based current line and 0-based current column.""" @@ -1414,6 +1418,11 @@ def VimSupportsPopupWindows(): 'popup_close' ) +@memoize() +def VimSupportsVirtualText(): + return not VimIsNeovim() and VimVersionAtLeast( VIM_VIRTUAL_TEXT_VERSION_REQ ) + + @memoize() def VimHasFunction( func ): return bool( GetIntValue( f"exists( '*{ EscapeForVim( func ) }' )" ) ) From 6b4e5c712fd2e763e8834b6407b2d0afe4c3969e Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 13 Aug 2022 12:37:17 +0100 Subject: [PATCH 024/249] Inlay Hints: Only enable when supported by vim --- autoload/youcompleteme.vim | 18 ++++++++++++------ python/ycm/inlay_hints.py | 5 +++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 26c9d22fa9..aba4fd5f7d 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -81,6 +81,7 @@ let s:buftype_blacklist = { let s:last_char_inserted_by_user = v:true let s:enable_hover = 0 let s:cursorhold_popup = -1 +let s:enable_inlay_hints = 0 let s:force_preview_popup = 0 @@ -301,7 +302,8 @@ try: ycm_state = youcompleteme.YouCompleteMe( default_options ) ycm_semantic_highlighting.Initialise() - ycm_inlay_hints.Initialise() + vim.command( 'let s:enable_inlay_hints = ' + + '1' if ycm_inlay_hints.Initialise() else '0' ) except Exception as error: # We don't use PostVimMessage or EchoText from the vimsupport module because # importing this module may fail. @@ -793,7 +795,7 @@ endfunction function! s:UpdateInlayHints( bufnr ) call s:StopPoller( s:pollers.inlay_hints ) - if !s:is_neovim && + if s:enable_inlay_hints && \ get( b:, 'ycm_enable_inlay_hints', \ get( g:, 'ycm_enable_inlay_hints', 0 ) ) @@ -984,7 +986,9 @@ endfunction function! s:OnInsertEnter() abort let s:current_cursor_position = getpos( '.' ) - py3 ycm_state.CurrentBuffer().inlay_hints.Clear() + if s:enable_inlay_hints + py3 ycm_state.CurrentBuffer().inlay_hints.Clear() + endif endfunction function! s:OnInsertLeave() @@ -1006,9 +1010,11 @@ function! s:OnInsertLeave() endif call s:ClearSignatureHelp() - " We cleared inlay hints on insert enter - " TODO: Probalby should use ModeChange - py3 ycm_state.CurrentBuffer().inlay_hints.Refresh() + if s:enable_inlay_hints + " We cleared inlay hints on insert enter + " TODO: Probalby should use ModeChange + py3 ycm_state.CurrentBuffer().inlay_hints.Refresh() + endif endfunction diff --git a/python/ycm/inlay_hints.py b/python/ycm/inlay_hints.py index 4105d472d8..9c6a17a527 100644 --- a/python/ycm/inlay_hints.py +++ b/python/ycm/inlay_hints.py @@ -34,8 +34,8 @@ def Initialise(): - if vimsupport.VimIsNeovim(): - return + if not vimsupport.VimSupportsVirtualText(): + return False props = tp.GetTextPropertyTypes() if 'YCM_INLAY_UNKNOWN' not in props: @@ -47,6 +47,7 @@ def Initialise(): f"hlexists( '{ vimsupport.EscapeForVim( group ) }' )" ): tp.AddTextPropertyType( prop, highlight = group ) + return True class InlayHints: """Stores the inlay hints state for a Vim buffer""" From f6d20e5414bf0591007ed4c9ad45e23261d31a94 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 13 Aug 2022 12:44:47 +0100 Subject: [PATCH 025/249] Print some additional version info in debuginfo --- python/ycm/youcompleteme.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 7bdca60ed8..ae83db601d 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -723,6 +723,12 @@ def DebugInfo( self ): debug_info += ( 'Server logfiles:\n' f' { self._server_stdout }\n' f' { self._server_stderr }' ) + debug_info += ( '\nSemantic highlighting supported: ' + + str( not vimsupport.VimIsNeovim() ) ) + debug_info += ( '\nVirtual text supported: ' + + str( vimsupport.VimSupportsVirtualText() ) ) + debug_info += ( '\nPopup windows supported: ' + + str( vimsupport.VimSupportsPopupWindows() ) ) return debug_info From a7267e2d006376af88dcd78aa2bab1b70d64f350 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 13 Aug 2022 13:35:12 +0100 Subject: [PATCH 026/249] VirtualText: Add YcmPadding property --- autoload/youcompleteme.vim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index aba4fd5f7d..a5bf294940 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -435,6 +435,10 @@ function! s:SetUpSyntaxHighlighting() call prop_type_add( 'YcmErrorProperty', { \ 'highlight': 'YcmErrorSection' } ) endif + if s:PropertyTypeNotDefined( 'YcmPadding' ) + call prop_type_add( 'YcmPadding', + \ { 'highlight': 'Normal' } ) + endif if !hlexists( 'YcmWarningSection' ) if hlexists( 'SyntasticWarning' ) From 002283606d17caf4ad917196e4f0bc87780c0dac Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Fri, 5 Aug 2022 07:43:40 +0100 Subject: [PATCH 027/249] Use virtual text for printing current diagnostic TODO: - neovim? - feature/patch guard - tests - option --- python/ycm/diagnostic_interface.py | 43 +++++++++++++++++++--------- python/ycm/vimsupport.py | 45 +++++++++++++++++++----------- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/python/ycm/diagnostic_interface.py b/python/ycm/diagnostic_interface.py index 0d2b492d28..d78b8af684 100644 --- a/python/ycm/diagnostic_interface.py +++ b/python/ycm/diagnostic_interface.py @@ -31,7 +31,7 @@ def __init__( self, bufnr, user_options ): # Line and column numbers are 1-based self._line_to_diags = defaultdict( list ) self._previous_diag_line_number = -1 - self._diag_message_needs_clearing = False + self._diag_message_needs_clearing = None def OnCursorMoved( self ): @@ -97,14 +97,17 @@ def _EchoDiagnostic( self ): def _EchoDiagnosticForLine( self, line_num ): + global YCM_VIM_PROPERTY_ID + + if self._diag_message_needs_clearing is not None: + # Clear any previous diag echo + vimsupport.RemoveTextProperty( **self._diag_message_needs_clearing ) + self._diag_message_needs_clearing = None + self._previous_diag_line_number = line_num diags = self._line_to_diags[ line_num ] if not diags: - if self._diag_message_needs_clearing: - # Clear any previous diag echo - vimsupport.PostVimMessage( '', warning = False ) - self._diag_message_needs_clearing = False return first_diag = diags[ 0 ] @@ -112,8 +115,21 @@ def _EchoDiagnosticForLine( self, line_num ): if first_diag.get( 'fixit_available', False ): text += ' (FixIt)' - vimsupport.PostVimMessage( text, warning = False, truncate = True ) - self._diag_message_needs_clearing = True + self._diag_message_needs_clearing = { + 'buffer_number': self._bufnr, + 'prop_id': vimsupport.AddTextProperty( + self._bufnr, + line_num, + 0, + 'YcmErrorProperty', + { + 'text': ' ' + ' '.join( text.splitlines() ), + 'text_align': 'right', + 'text_wrap': 'wrap' + } ), + 'line_num': line_num, + 'prop_type': 'YcmErrorProperty' + } def _DiagnosticsCount( self, predicate ): @@ -143,9 +159,8 @@ def UpdateMatches( self ): diag ): global YCM_VIM_PROPERTY_ID - # FIXME: This remove() gambit probably never works because the IDs are - # almost certain to not match - # Perhaps we should have AddTextProperty return the ID? + # Note the following .remove() works because the __eq__ on + # DiagnosticProperty does not actually check the IDs match... diag_prop = vimsupport.DiagnosticProperty( YCM_VIM_PROPERTY_ID, name, @@ -155,15 +170,17 @@ def UpdateMatches( self ): try: props_to_remove.remove( diag_prop ) except ValueError: + extras.update( { + 'id': YCM_VIM_PROPERTY_ID + } ) vimsupport.AddTextProperty( self._bufnr, line, column, name, - extras, - YCM_VIM_PROPERTY_ID ) + extras ) YCM_VIM_PROPERTY_ID += 1 for prop in props_to_remove: - vimsupport.RemoveTextProperty( self._bufnr, prop ) + vimsupport.RemoveDiagnosticProperty( self._bufnr, prop ) def _UpdateSigns( self ): diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index df1955284e..3789e445df 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -341,14 +341,15 @@ def AddTextProperty( buffer_number, line, column, prop_type, - extra_args, - prop_id ): + extra_args ): if not VimIsNeovim(): extra_args.update( { 'type': prop_type, - 'bufnr': buffer_number, - 'id': prop_id } ) - vim.eval( f'prop_add( { line }, { column }, { extra_args } )' ) + 'bufnr': buffer_number + } ) + return GetIntValue( + vim.eval( f'prop_add( { line }, { column }, { extra_args } )' ) + ) else: extra_args[ 'hl_group' ] = prop_type # Neovim uses 0-based offsets @@ -358,25 +359,35 @@ def AddTextProperty( buffer_number, extra_args[ 'end_col' ] = extra_args.pop( 'end_col' ) - 1 line -= 1 column -= 1 - vim.eval( f'nvim_buf_set_extmark( { buffer_number }, ' - f'{ YCM_NEOVIM_NS_ID }, ' - f'{ line }, ' - f'{ column }, ' - f'{ extra_args } )' ) + return GetIntValue( + vim.eval( f'nvim_buf_set_extmark( { buffer_number }, ' + f'{ YCM_NEOVIM_NS_ID }, ' + f'{ line }, ' + f'{ column }, ' + f'{ extra_args } )' ) ) + + +def RemoveDiagnosticProperty( buffer_number: int, prop: DiagnosticProperty ): + RemoveTextProperty( buffer_number, + prop.line, + prop.id, + prop.type ) -def RemoveTextProperty( buffer_number: int, prop: DiagnosticProperty ): +def RemoveTextProperty( buffer_number, line_num, prop_id, prop_type ): if not VimIsNeovim(): p = { - 'bufnr': buffer_number, - 'id': prop.id, - 'type': prop.type, - 'both': 1 } - vim.eval( f'prop_remove( { p } )' ) + 'bufnr': buffer_number, + 'id': prop_id, + 'type': prop_type, + 'both': 1, + 'all': 1 + } + vim.eval( f'prop_remove( { p }, { line_num } )' ) else: vim.eval( f'nvim_buf_del_extmark( { buffer_number }, ' f'{ YCM_NEOVIM_NS_ID }, ' - f'{ prop.id } )' ) + f'{ prop_id } )' ) # Clamps the line and column numbers so that they are not past the contents of From 0f1b0cf0849624c7b12e47b273780d9ba54127ef Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 13 Aug 2022 13:36:01 +0100 Subject: [PATCH 028/249] Virtual text diags: add option, retain previous behaviour by default --- python/ycm/diagnostic_interface.py | 85 +++++++++++++++++++----------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/python/ycm/diagnostic_interface.py b/python/ycm/diagnostic_interface.py index d78b8af684..b8d9f505f4 100644 --- a/python/ycm/diagnostic_interface.py +++ b/python/ycm/diagnostic_interface.py @@ -31,7 +31,8 @@ def __init__( self, bufnr, user_options ): # Line and column numbers are 1-based self._line_to_diags = defaultdict( list ) self._previous_diag_line_number = -1 - self._diag_message_needs_clearing = None + self._diag_message_needs_clearing = False + self._diag_message_prop_ids = [] def OnCursorMoved( self ): @@ -97,39 +98,61 @@ def _EchoDiagnostic( self ): def _EchoDiagnosticForLine( self, line_num ): - global YCM_VIM_PROPERTY_ID - - if self._diag_message_needs_clearing is not None: - # Clear any previous diag echo - vimsupport.RemoveTextProperty( **self._diag_message_needs_clearing ) - self._diag_message_needs_clearing = None - self._previous_diag_line_number = line_num diags = self._line_to_diags[ line_num ] - if not diags: - return - - first_diag = diags[ 0 ] - text = first_diag[ 'text' ] - if first_diag.get( 'fixit_available', False ): - text += ' (FixIt)' - - self._diag_message_needs_clearing = { - 'buffer_number': self._bufnr, - 'prop_id': vimsupport.AddTextProperty( - self._bufnr, - line_num, - 0, - 'YcmErrorProperty', - { - 'text': ' ' + ' '.join( text.splitlines() ), - 'text_align': 'right', - 'text_wrap': 'wrap' - } ), - 'line_num': line_num, - 'prop_type': 'YcmErrorProperty' - } + text = None + if diags: + first_diag = diags[ 0 ] + text = first_diag[ 'text' ] + if first_diag.get( 'fixit_available', False ): + text += ' (FixIt)' + + if self._user_options[ 'echo_current_diagnostic' ] == 'virtual-text': + if self._diag_message_prop_ids: + # Clear any previous diag echo + for prop in self._diag_message_prop_ids: + vimsupport.RemoveTextProperty( **prop ) + self._diag_message_prop_ids.clear() + + if not text: + return + + def MakeVritualTextProperty( prop_type, text, position='after' ): + return { + 'buffer_number': self._bufnr, + 'prop_id': vimsupport.AddTextProperty( + self._bufnr, + line_num, + 0, + prop_type, + { + 'text': text, + 'text_align': position, + 'text_wrap': 'wrap' + } ), + 'line_num': line_num, + 'prop_type': prop_type + } + + self._diag_message_prop_ids = [ + MakeVritualTextProperty( + 'YcmPadding', + ' ' * vim.buffers[ self._bufnr ].options[ 'shiftwidth' ] ), + MakeVritualTextProperty( + 'YcmErrorProperty', + [ line for line in text.splitlines() if line ][ 0 ] ) + ] + else: + if not text: + if self._diag_message_needs_clearing: + # Clear any previous diag echo + vimsupport.PostVimMessage( '', warning = False ) + self._diag_message_needs_clearing = False + return + + vimsupport.PostVimMessage( text, warning = False, truncate = True ) + self._diag_message_needs_clearing = True def _DiagnosticsCount( self, predicate ): From f29823631ad6dfc93ed8d8f6fb7d67b52456a4d7 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 13 Aug 2022 15:00:25 +0100 Subject: [PATCH 029/249] Flake8 --- python/ycm/inlay_hints.py | 6 ++---- python/ycm/semantic_highlighting.py | 1 - python/ycm/text_properties.py | 1 - python/ycm/vimsupport.py | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/python/ycm/inlay_hints.py b/python/ycm/inlay_hints.py index 9c6a17a527..54152fc4e2 100644 --- a/python/ycm/inlay_hints.py +++ b/python/ycm/inlay_hints.py @@ -20,8 +20,6 @@ from ycm.client.base_request import BuildRequestData from ycm import vimsupport from ycm import text_properties as tp -import vim -from ycmd.utils import ToBytes HIGHLIGHT_GROUP = { @@ -49,6 +47,7 @@ def Initialise(): return True + class InlayHints: """Stores the inlay hints state for a Vim buffer""" @@ -103,7 +102,7 @@ def Update( self ): # We're ready to use this response. Clear it (to avoid repeatedly # re-polling). - self._latest_inlay_hints = [] ;# in case there was an error in request + self._latest_inlay_hints = [] # in case there was an error in request self._latest_inlay_hints = self._request.Response() self._request = None @@ -152,4 +151,3 @@ def _Draw( self ): { 'text': inlay_hint[ 'label' ] } ) ) - diff --git a/python/ycm/semantic_highlighting.py b/python/ycm/semantic_highlighting.py index 1a5686ba61..d2e080ba87 100644 --- a/python/ycm/semantic_highlighting.py +++ b/python/ycm/semantic_highlighting.py @@ -146,4 +146,3 @@ def Update( self ): # No need to re-poll return True - diff --git a/python/ycm/text_properties.py b/python/ycm/text_properties.py index 3deddc345c..eae5c57c54 100644 --- a/python/ycm/text_properties.py +++ b/python/ycm/text_properties.py @@ -71,4 +71,3 @@ def ClearTextProperties( bufnr, prop_id ): 'all': 1, } vim.eval( f"prop_remove( { json.dumps( props ) } )" ) - diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index 3789e445df..fd26d91757 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -359,7 +359,7 @@ def AddTextProperty( buffer_number, extra_args[ 'end_col' ] = extra_args.pop( 'end_col' ) - 1 line -= 1 column -= 1 - return GetIntValue( + return GetIntValue( vim.eval( f'nvim_buf_set_extmark( { buffer_number }, ' f'{ YCM_NEOVIM_NS_ID }, ' f'{ line }, ' From 7524fa30540cb53261dbe308ded6cc6fb4c3ecd8 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 13 Aug 2022 15:00:32 +0100 Subject: [PATCH 030/249] Update ycmd --- third_party/ycmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/ycmd b/third_party/ycmd index 6562a2c5be..a355f16166 160000 --- a/third_party/ycmd +++ b/third_party/ycmd @@ -1 +1 @@ -Subproject commit 6562a2c5be256592826f910a588ace51bcce0000 +Subproject commit a355f16166bdedacf0121e6630fe053d0f35fbc6 From fa4eb61ded1186193d2e04e50410660300f15914 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 13 Aug 2022 22:36:47 +0100 Subject: [PATCH 031/249] Inlay hints: allow easy customisation of the highlight groups, fix priorities and ensure start_incl is set --- autoload/youcompleteme.vim | 24 +++++++++--- python/ycm/inlay_hints.py | 59 +++++++++++++++++++++++------ python/ycm/semantic_highlighting.py | 8 +++- python/ycm/text_properties.py | 22 +++++++---- python/ycm/vimsupport.py | 7 ++-- 5 files changed, 91 insertions(+), 29 deletions(-) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index a5bf294940..d685156801 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -181,6 +181,10 @@ function! youcompleteme#Enable() call s:SetUpOptions() + py3 ycm_semantic_highlighting.Initialise() + py3 vim.command( 'let s:enable_inlay_hints = ' + + \ '1' if ycm_inlay_hints.Initialise() else '0' ) + call youcompleteme#EnableCursorMovedAutocommands() augroup youcompleteme autocmd! @@ -301,9 +305,6 @@ try: default_options = {} ycm_state = youcompleteme.YouCompleteMe( default_options ) - ycm_semantic_highlighting.Initialise() - vim.command( 'let s:enable_inlay_hints = ' + - '1' if ycm_inlay_hints.Initialise() else '0' ) except Exception as error: # We don't use PostVimMessage or EchoText from the vimsupport module because # importing this module may fail. @@ -433,11 +434,21 @@ function! s:SetUpSyntaxHighlighting() endif if s:PropertyTypeNotDefined( 'YcmErrorProperty' ) call prop_type_add( 'YcmErrorProperty', { - \ 'highlight': 'YcmErrorSection' } ) + \ 'highlight': 'YcmErrorSection', + \ 'priority': 30 } ) + endif + + " Used for virtual text + if !hlexists( 'YcmInvisible' ) + highlight default link YcmInvisible Normal + endif + if !hlexists( 'YcmInlayHint' ) + highlight default link YcmInlayHint NonText endif if s:PropertyTypeNotDefined( 'YcmPadding' ) call prop_type_add( 'YcmPadding', - \ { 'highlight': 'Normal' } ) + \ { 'highlight': 'YcmInvisible', + \ 'priority': 100 } ) endif if !hlexists( 'YcmWarningSection' ) @@ -449,7 +460,8 @@ function! s:SetUpSyntaxHighlighting() endif if s:PropertyTypeNotDefined( 'YcmWarningProperty' ) call prop_type_add( 'YcmWarningProperty', { - \ 'highlight': 'YcmWarningSection' } ) + \ 'highlight': 'YcmWarningSection', + \ 'priority': 30 } ) endif endfunction diff --git a/python/ycm/inlay_hints.py b/python/ycm/inlay_hints.py index 54152fc4e2..1640a4f0a4 100644 --- a/python/ycm/inlay_hints.py +++ b/python/ycm/inlay_hints.py @@ -25,8 +25,8 @@ HIGHLIGHT_GROUP = { # 1-based inedexes 0: '', - 1: 'Comment', # Type - 2: 'Comment' # Parameter + 1: 'YcmInlayHint', # Type + 2: 'YcmInlayHint' # Parameter } REPORTED_MISSING_TYPES = set() @@ -37,13 +37,21 @@ def Initialise(): props = tp.GetTextPropertyTypes() if 'YCM_INLAY_UNKNOWN' not in props: - tp.AddTextPropertyType( 'YCM_INLAY_UNKNOWN', highlight = 'Comment' ) + tp.AddTextPropertyType( 'YCM_INLAY_UNKNOWN', + highlight = 'YcmInlayHint', + start_incl = 1 ) + if 'YCM_INLAY_PADDING' not in props: + tp.AddTextPropertyType( 'YCM_INLAY_PADDING', + highlight = 'YcmInvisible', + start_incl = 1 ) for token_type, group in HIGHLIGHT_GROUP.items(): prop = f'YCM_INLAY_{ token_type }' if prop not in props and vimsupport.GetIntValue( f"hlexists( '{ vimsupport.EscapeForVim( group ) }' )" ): - tp.AddTextPropertyType( prop, highlight = group ) + tp.AddTextPropertyType( prop, + highlight = group, + start_incl = 1 ) return True @@ -88,9 +96,15 @@ def Ready( self ): def Clear( self ): - for prop_id in self._prop_ids: - tp.ClearTextProperties( self._bufnr, prop_id ) - self._prop_ids.clear() + # FIXME: ClearTextProperties is slow as it must scan the whole buffer + # We should use _last_requested_range to specify the range to clear + for type in HIGHLIGHT_GROUP.keys(): + if type == 0: + continue + tp.ClearTextProperties( self._bufnr, type=f'YCM_INLAY_{ type }' ) + + tp.ClearTextProperties( self._bufnr, type='YCM_INLAY_UNKNOWN' ) + tp.ClearTextProperties( self._bufnr, type='YCM_INLAY_PADDING' ) def Update( self ): @@ -140,14 +154,37 @@ def _Draw( self ): else: prop_type = 'YCM_INLAY_' + str( inlay_hint[ 'kind' ] ) - self._prop_ids.add( + if inlay_hint.get( 'paddingLeft', False ): + tp.AddTextProperty( self._bufnr, + None, + 'YCM_INLAY_PADDING', + { + 'start': inlay_hint[ 'position' ], + 'end': inlay_hint[ 'position' ], + }, + { + 'text': ' ' + } ) + + tp.AddTextProperty( self._bufnr, + None, + prop_type, + { + 'start': inlay_hint[ 'position' ], + 'end': inlay_hint[ 'position' ], + }, + { + 'text': inlay_hint[ 'label' ] + } ) + + if inlay_hint.get( 'paddingRight', False ): tp.AddTextProperty( self._bufnr, None, - prop_type, + 'YCM_INLAY_PADDING', { 'start': inlay_hint[ 'position' ], 'end': inlay_hint[ 'position' ], }, { - 'text': inlay_hint[ 'label' ] - } ) ) + 'text': ' ' + } ) diff --git a/python/ycm/semantic_highlighting.py b/python/ycm/semantic_highlighting.py index d2e080ba87..6e0cee0038 100644 --- a/python/ycm/semantic_highlighting.py +++ b/python/ycm/semantic_highlighting.py @@ -58,13 +58,17 @@ def Initialise(): props = tp.GetTextPropertyTypes() if 'YCM_HL_UNKNOWN' not in props: - tp.AddTextPropertyType( 'YCM_HL_UNKNOWN', highlight = 'WarningMsg' ) + tp.AddTextPropertyType( 'YCM_HL_UNKNOWN', + highlight = 'WarningMsg', + priority = 0 ) for token_type, group in HIGHLIGHT_GROUP.items(): prop = f'YCM_HL_{ token_type }' if prop not in props and vimsupport.GetIntValue( f"hlexists( '{ vimsupport.EscapeForVim( group ) }' )" ): - tp.AddTextPropertyType( prop, highlight = group ) + tp.AddTextPropertyType( prop, + highlight = group, + priority = 0 ) # "arbitrary" base id diff --git a/python/ycm/text_properties.py b/python/ycm/text_properties.py index eae5c57c54..cb2377bb65 100644 --- a/python/ycm/text_properties.py +++ b/python/ycm/text_properties.py @@ -29,15 +29,16 @@ def AddTextPropertyType( name, **kwargs ): props = { 'highlight': 'Ignore', - 'combine': False, - 'start_incl': False, - 'end_incl': False, + 'combine': 0, + 'override': 0, + 'start_incl': 0, + 'end_incl': 0, 'priority': 10 } props.update( kwargs ) vim.eval( f"prop_type_add( '{ vimsupport.EscapeForVim( name ) }', " - f" { json.dumps( kwargs ) } )" ) + f" { json.dumps( props ) } )" ) def GetTextPropertyTypes( *args, **kwargs ): @@ -56,7 +57,7 @@ def AddTextProperty( bufnr, 'type': prop_type } if prop_id is not None: - props[ 'id' ]: prop_id + props[ 'id' ] = prop_id if extra_args: props.update( extra_args ) return vim.eval( f"prop_add( { range[ 'start' ][ 'line_num' ] }," @@ -64,10 +65,17 @@ def AddTextProperty( bufnr, f" { json.dumps( props ) } )" ) -def ClearTextProperties( bufnr, prop_id ): +def ClearTextProperties( bufnr, prop_id=None, type=None ): props = { - 'id': prop_id, 'bufnr': bufnr, 'all': 1, } + if prop_id is not None: + props[ 'id' ] = prop_id + if type is not None: + props[ 'type' ] = type + + if prop_id is not None and type is not None: + props[ 'both' ] = 1 + vim.eval( f"prop_remove( { json.dumps( props ) } )" ) diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index fd26d91757..08a6a44b80 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -188,7 +188,7 @@ def GetBufferChangedTick( bufnr ): # 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 ): +def RangeVisibleInBuffer( bufnr, grow_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 ] @@ -207,6 +207,7 @@ class Range: return None r = Range() + # Note, for this we ignore horizontal scrolling 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' ] ): @@ -216,9 +217,9 @@ class Range: # 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.line = max( r.start.line - int( num_lines * grow_factor ), 1 ) r.start.col = 1 - r.end.line = min( r.end.line + int( num_lines * factor ), len( buffer ) ) + r.end.line = min( r.end.line + int( num_lines * grow_factor ), len( buffer ) ) r.end.col = len( buffer[ r.end.line - 1 ] ) filepath = GetBufferFilepath( buffer ) From e6765a110669fc71c9c0fdecdb782f2c2600c987 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 13 Aug 2022 22:37:19 +0100 Subject: [PATCH 032/249] Inlay hints - temporarily reinstate keeping the hints in insert mode --- autoload/youcompleteme.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index d685156801..75b189a131 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -1003,7 +1003,7 @@ endfunction function! s:OnInsertEnter() abort let s:current_cursor_position = getpos( '.' ) if s:enable_inlay_hints - py3 ycm_state.CurrentBuffer().inlay_hints.Clear() + "py3 ycm_state.CurrentBuffer().inlay_hints.Clear() endif endfunction @@ -1029,7 +1029,7 @@ function! s:OnInsertLeave() if s:enable_inlay_hints " We cleared inlay hints on insert enter " TODO: Probalby should use ModeChange - py3 ycm_state.CurrentBuffer().inlay_hints.Refresh() + "py3 ycm_state.CurrentBuffer().inlay_hints.Refresh() endif endfunction From 5495d8fafb2870c25955daffa8f8d24e989fdd29 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 13 Aug 2022 22:58:14 +0100 Subject: [PATCH 033/249] Inlay hints: don't always request on scroll, only when we hit the bounds of our requested window. this reduces stutter when scrolling --- autoload/youcompleteme.vim | 17 ++++++++++------- python/ycm/inlay_hints.py | 26 ++++++++++++++++++++------ python/ycm/vimsupport.py | 13 +++++++++++++ 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 75b189a131..48c3a1c8d3 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -788,7 +788,7 @@ function! s:OnFileReadyToParse( ... ) \ function( 's:PollFileParseResponse' ) ) call s:UpdateSemanticHighlighting( bufnr() ) - call s:UpdateInlayHints( bufnr() ) + call s:UpdateInlayHints( bufnr(), 1 ) endif endfunction @@ -809,16 +809,19 @@ function! s:UpdateSemanticHighlighting( bufnr ) abort endfunction -function! s:UpdateInlayHints( bufnr ) +function! s:UpdateInlayHints( bufnr, force ) call s:StopPoller( s:pollers.inlay_hints ) if s:enable_inlay_hints && \ get( b:, 'ycm_enable_inlay_hints', \ get( g:, 'ycm_enable_inlay_hints', 0 ) ) - py3 ycm_state.Buffer( int( vim.eval( 'a:bufnr' ) ) ).inlay_hints.Request() - let s:pollers.inlay_hints.id = timer_start( - \ s:pollers.inlay_hints.wait_milliseconds, - \ function( 's:PollInlayHints', [ a:bufnr ] ) ) + if py3eval( + \ 'ycm_state.Buffer( int( vim.eval( "a:bufnr" ) ) ).' + \ . 'inlay_hints.Request( force=int( vim.eval( "a:force" ) ) )' ) + let s:pollers.inlay_hints.id = timer_start( + \ s:pollers.inlay_hints.wait_milliseconds, + \ function( 's:PollInlayHints', [ a:bufnr ] ) ) + endif endif endfunction @@ -930,7 +933,7 @@ function! s:OnWinScrolled() endif let bufnr = winbufnr( expand( '' ) ) call s:UpdateSemanticHighlighting( bufnr ) - call s:UpdateInlayHints( bufnr ) + call s:UpdateInlayHints( bufnr, 0 ) endfunction diff --git a/python/ycm/inlay_hints.py b/python/ycm/inlay_hints.py index 1640a4f0a4..bda114fd7a 100644 --- a/python/ycm/inlay_hints.py +++ b/python/ycm/inlay_hints.py @@ -64,17 +64,31 @@ class InlayHints: def __init__( self, bufnr, user_options ): self._request = None self._bufnr = bufnr - self._prop_ids = set() self.tick = -1 self._latest_inlay_hints = [] + self._last_requested_range = None - def Request( self ): + def Request( self, force=False ): if self._request and not self.Ready(): - return + 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 ) # TODO: How to determine the range to display ? Should we do the range @@ -85,10 +99,11 @@ def Request( self ): # Perhaps the maximal range of visible windows or something. request_data = BuildRequestData( self._bufnr ) request_data.update( { - 'range': vimsupport.RangeVisibleInBuffer( self._bufnr ) + 'range': self._last_requested_range } ) self._request = InlayHintsRequest( request_data ) self._request.Start() + return True def Ready( self ): @@ -116,13 +131,12 @@ def Update( self ): # We're ready to use this response. Clear it (to avoid repeatedly # re-polling). - self._latest_inlay_hints = [] # in case there was an error in request 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() + self.Request( force=True ) return False # poll again self._Draw() diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index 08a6a44b80..950ea8bf40 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -237,6 +237,19 @@ class Range: } +def VisibleRangeOfBufferOverlaps( bufnr, expanded_range ): + visible_range = RangeVisibleInBuffer( bufnr, 0 ) + # As above, we ignore horizontal scroll and only check lines + return ( + expanded_range is not None and + visible_range is not None and + visible_range[ 'start' ][ 'line_num' ] + >= expanded_range[ 'start' ][ 'line_num' ] and + visible_range[ 'end' ][ 'line_num' ] + <= expanded_range[ 'end' ][ 'line_num' ] + ) + + def CaptureVimCommand( command ): vim.command( 'redir => b:ycm_command' ) vim.command( f'silent! { command }' ) From 9270ccc8b368ee212a9f85b0a686ce8c94bb4bd8 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 13 Aug 2022 22:59:46 +0100 Subject: [PATCH 034/249] Update ycmd --- third_party/ycmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/ycmd b/third_party/ycmd index a355f16166..1803ab68a1 160000 --- a/third_party/ycmd +++ b/third_party/ycmd @@ -1 +1 @@ -Subproject commit a355f16166bdedacf0121e6630fe053d0f35fbc6 +Subproject commit 1803ab68a1ec120536eb27b4a57b022f61b14347 From c14286b6975580f8b38e17b8a72cccf90fd5c903 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 13 Aug 2022 23:56:09 +0100 Subject: [PATCH 035/249] Improve interaction of text properties - use SpellBad/SpellCap without adornment for warnings/errors --- autoload/youcompleteme.vim | 56 +++++++++++++++++++++++++++--- python/ycm/diagnostic_interface.py | 54 +++++++++++++--------------- python/ycm/vimsupport.py | 2 +- 3 files changed, 76 insertions(+), 36 deletions(-) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 48c3a1c8d3..9f1b00393e 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -435,7 +435,9 @@ function! s:SetUpSyntaxHighlighting() if s:PropertyTypeNotDefined( 'YcmErrorProperty' ) call prop_type_add( 'YcmErrorProperty', { \ 'highlight': 'YcmErrorSection', - \ 'priority': 30 } ) + \ 'priority': 30, + \ 'combine': 0, + \ 'override': 1 } ) endif " Used for virtual text @@ -445,10 +447,52 @@ function! s:SetUpSyntaxHighlighting() if !hlexists( 'YcmInlayHint' ) highlight default link YcmInlayHint NonText endif + if !hlexists( 'YcmErrorText' ) + if exists( '*hlget' ) + let YcmErrorText = hlget( 'SpellBad', v:true )[ 0 ] + let YcmErrorText.name = 'YcmErrorText' + let YcmErrorText.cterm = {} + let YcmErrorText.gui = {} + let YcmErrorText.term = {} + call hlset( [ YcmErrorText ] ) + else + " approximation + hi default link YcmErrorText WarningMsg + endif + endif + if !hlexists( 'YcmWarningText' ) + if exists( '*hlget' ) + let YcmWarningText = hlget( 'SpellCap', v:true )[ 0 ] + let YcmWarningText.name = 'YcmWarningText' + let YcmWarningText.cterm = {} + let YcmWarningText.gui = {} + let YcmWarningText.term = {} + call hlset( [ YcmWarningText] ) + else + " Lame approximation + hi default link YcmWarningText Conceal + endif + endif + + if s:PropertyTypeNotDefined( 'YcmVirtError' ) + call prop_type_add( 'YcmVirtError', { + \ 'highlight': 'YcmErrorText', + \ 'priority': 20, + \ 'combine': 0 } ) + endif + if s:PropertyTypeNotDefined( 'YcmVirtWarning' ) + call prop_type_add( 'YcmVirtWarning', { + \ 'highlight': 'YcmWarningText', + \ 'priority': 19, + \ 'combine': 0 } ) + endif + + if s:PropertyTypeNotDefined( 'YcmPadding' ) - call prop_type_add( 'YcmPadding', - \ { 'highlight': 'YcmInvisible', - \ 'priority': 100 } ) + call prop_type_add( 'YcmPadding', { + \ 'highlight': 'YcmInvisible', + \ 'priority': 100, + \ 'combine': 1 } ) endif if !hlexists( 'YcmWarningSection' ) @@ -461,7 +505,9 @@ function! s:SetUpSyntaxHighlighting() if s:PropertyTypeNotDefined( 'YcmWarningProperty' ) call prop_type_add( 'YcmWarningProperty', { \ 'highlight': 'YcmWarningSection', - \ 'priority': 30 } ) + \ 'priority': 29, + \ 'combine': 0, + \ 'override': 1 } ) endif endfunction diff --git a/python/ycm/diagnostic_interface.py b/python/ycm/diagnostic_interface.py index b8d9f505f4..e1276503ba 100644 --- a/python/ycm/diagnostic_interface.py +++ b/python/ycm/diagnostic_interface.py @@ -18,6 +18,7 @@ from collections import defaultdict from ycm import vimsupport from ycm.diagnostic_filter import DiagnosticFilter, CompileLevel +from ycm import text_properties as tp import vim YCM_VIM_PROPERTY_ID = 0 @@ -32,7 +33,6 @@ def __init__( self, bufnr, user_options ): self._line_to_diags = defaultdict( list ) self._previous_diag_line_number = -1 self._diag_message_needs_clearing = False - self._diag_message_prop_ids = [] def OnCursorMoved( self ): @@ -109,40 +109,33 @@ def _EchoDiagnosticForLine( self, line_num ): text += ' (FixIt)' if self._user_options[ 'echo_current_diagnostic' ] == 'virtual-text': - if self._diag_message_prop_ids: + if self._diag_message_needs_clearing: # Clear any previous diag echo - for prop in self._diag_message_prop_ids: - vimsupport.RemoveTextProperty( **prop ) - self._diag_message_prop_ids.clear() + tp.ClearTextProperties( self._bufnr, type = 'YcmVirtError' ) + tp.ClearTextProperties( self._bufnr, type = 'YcmVirtWarning' ) + self._diag_message_needs_clearing = False if not text: return def MakeVritualTextProperty( prop_type, text, position='after' ): - return { - 'buffer_number': self._bufnr, - 'prop_id': vimsupport.AddTextProperty( - self._bufnr, - line_num, - 0, - prop_type, - { - 'text': text, - 'text_align': position, - 'text_wrap': 'wrap' - } ), - 'line_num': line_num, - 'prop_type': prop_type - } - - self._diag_message_prop_ids = [ - MakeVritualTextProperty( - 'YcmPadding', - ' ' * vim.buffers[ self._bufnr ].options[ 'shiftwidth' ] ), - MakeVritualTextProperty( - 'YcmErrorProperty', - [ line for line in text.splitlines() if line ][ 0 ] ) - ] + vimsupport.AddTextProperty( self._bufnr, + line_num, + 0, + prop_type, + { + 'text': text, + 'text_align': position, + 'text_wrap': 'wrap' + } ) + + MakeVritualTextProperty( + 'YcmPadding', + ' ' * vim.buffers[ self._bufnr ].options[ 'shiftwidth' ] ), + MakeVritualTextProperty( + 'YcmVirtError' if _DiagnosticIsError( first_diag ) + else 'YcmVirtWarning', + [ line for line in text.splitlines() if line ][ 0 ] ) else: if not text: if self._diag_message_needs_clearing: @@ -152,7 +145,8 @@ def MakeVritualTextProperty( prop_type, text, position='after' ): return vimsupport.PostVimMessage( text, warning = False, truncate = True ) - self._diag_message_needs_clearing = True + + self._diag_message_needs_clearing = True def _DiagnosticsCount( self, predicate ): diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index 950ea8bf40..06caa918bd 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -62,7 +62,7 @@ # Virtual text is not a feature in itself and early patches don't work well, so # we need to keep changing this at the moment -VIM_VIRTUAL_TEXT_VERSION_REQ = '9.0.193' +VIM_VIRTUAL_TEXT_VERSION_REQ = '9.0.199' def CurrentLineAndColumn(): From a2d8d77795d809c8cb5c6bc7c986ada515ac7998 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Tue, 16 Aug 2022 21:48:36 +0100 Subject: [PATCH 036/249] vimspector: made test debugging work again --- .vimspector.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.vimspector.json b/.vimspector.json index 9bd48fbb75..3e6991ddb9 100644 --- a/.vimspector.json +++ b/.vimspector.json @@ -34,18 +34,19 @@ "type": "debugpy", "request": "launch", - "cwd": "${workspaceRoot}/python", + "cwd": "${workspaceRoot}", "stopOnEntry": false, "console": "integratedTerminal", "justMyCode": true, + "logToFile": true, + "showReturnValue": true, "debugOptions": [], - "module": "pytest", + "module": "unittest", "python": "${python}", "args": [ "-v", - "--tb=native", "${Test}" ], "env": { From 9523936c48376b6c394fa7fd3055b8870af38a09 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Tue, 16 Aug 2022 21:49:07 +0100 Subject: [PATCH 037/249] virtual text - fix up awful mock tests --- python/ycm/tests/test_utils.py | 32 +++++++++++--------------- python/ycm/tests/youcompleteme_test.py | 6 +++-- python/ycm/vimsupport.py | 15 +++++------- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/python/ycm/tests/test_utils.py b/python/ycm/tests/test_utils.py index 30759b78c5..b5dda4f69f 100644 --- a/python/ycm/tests/test_utils.py +++ b/python/ycm/tests/test_utils.py @@ -42,18 +42,12 @@ GETBUFVAR_REGEX = re.compile( '^getbufvar\\((?P[0-9]+), "(?P