Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 84 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -936,43 +936,95 @@ per buffer, by setting `b:ycm_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_<token type>`, then it will be used
in place of the defaults. The `<token type>` 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).
highlighting. In order to customise the coloring, you should set
`g:ycm_semantic_highlight_groups` list. Each item in that list must be
dictionary with the following keys:
- `filetypes` - list of filetypes that should use these settings for semantic
highlighting. If not defined, then these settings will be used as default for
any filetype without explicit configuration
- `highlight` - dictionary, where key is [token type](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens)
and value is highlighting group. If this key is not present, then semantic
highlighting will be disabled for that filetypes. If group is empty string or
`v:null`, then semantic highlighing for that group will be disabled

For compatibility reasons we also support defining text properties named
`YCM_HL_<token_type>`, but this may be removed in future.

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 `Normal` 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',
In the following example, we set a custom set of highlights for go, disable
semantic highlighting for rust, and define default highlighting groups for all
other languages:

```viml
let g:ycm_enable_semantic_highlighting=1

let g:ycm_semantic_highlight_groups = [
\{
\ 'filetypes': ['go'],
\ 'highlight': {
\ 'namespace': 'Namespace',
\ 'type': 'goType',
\ 'class': 'goType',
\ 'struct': 'goType',
\ 'interface': 'goType',
\ 'concept': v:null,
\ 'typeParameter': v:null,
\ 'enum': 'EnumConstant',
\ 'enumMember': 'EnumConstant',
\ 'function': 'goFunction',
\ 'method': 'goFunction',
\ 'member': 'goFunction',
\ 'property': 'goFunction',
\ 'macro': v:null,
\ 'variable': v:null,
\ 'parameter': v:null,
\ 'comment': v:null,
\ 'operator': v:null,
\ 'keyword': v:null,
\ 'modifier': v:null,
\ 'event': v:null,
\ 'number': v:null,
\ 'string': v:null,
\ 'regexp': v:null,
\ 'unknown': v:null,
\ 'bracket': v:null,
\ }

for tokenType in keys( MY_YCM_HIGHLIGHT_GROUP )
call prop_type_add( 'YCM_HL_' . tokenType,
\ { 'highlight': MY_YCM_HIGHLIGHT_GROUP[ tokenType ] } )
endfor
\},
\{
\ 'filetypes': ['rust']
\},
\{
\ 'highlight': {
\ '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',
\ 'method': 'Function',
\ 'keyword': 'Keyword',
\ 'modifier': 'Keyword',
\ 'comment': 'Comment',
\ 'string': 'String',
\ 'number': 'Number',
\ 'regexp': 'String',
\ 'operator': 'Operator',
\ 'unknown': 'Normal',
\ }
\}
\]
```

## Inlay hints
Expand Down
177 changes: 136 additions & 41 deletions python/ycm/semantic_highlighting.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.


from ycm.client.base_request import BaseRequest
from ycm.client.semantic_tokens_request import SemanticTokensRequest
from ycm.client.base_request import BuildRequestData
from ycm import vimsupport
Expand All @@ -25,53 +26,114 @@
import vim


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',
'method': 'Function',
'keyword': 'Keyword',
'modifier': 'Keyword',
'comment': 'Comment',
'string': 'String',
'number': 'Number',
'regexp': 'String',
'operator': 'Operator',
'unknown': 'Normal',
}
HIGHLIGHT_GROUPS = [{
'highlight': {
'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',
'method': 'Function',
'keyword': 'Keyword',
'modifier': 'Keyword',
'comment': 'Comment',
'string': 'String',
'number': 'Number',
'regexp': 'String',
'operator': 'Operator',
'unknown': 'Normal',
}
}]
REPORTED_MISSING_TYPES = set()


def AddHiForTokenType( bufnr, token_type, group ):
prop = f'YCM_HL_{ token_type }'
hi = group
combine = 0
filetypes = "(default)"
if bufnr is not None:
filetypes = vimsupport.GetBufferFiletypes(bufnr)

if group is None or len( group ) == 0:
hi = 'Normal'
combine = 1

if not vimsupport.GetIntValue(
f"hlexists( '{ vimsupport.EscapeForVim( hi ) }' )" ):
vimsupport.PostVimMessage(
f"Higlight group { hi } is not difined for { filetypes }. "
f"See :help youcompleteme-customising-highlight-groups" )
return

if bufnr is None:
props = tp.GetTextPropertyTypes()
if prop not in props:
tp.AddTextPropertyType( prop,
highlight = hi,
priority = 0,
combine = combine )
else:
try:
tp.AddTextPropertyType( prop,
highlight = hi,
priority = 0,
combine = combine,
bufnr = bufnr )
except vim.error as e:
if 'E969:' in str( e ):
# at YcmRestart we can get error about redefining properties, just ignore them
pass
else:
raise e



def Initialise():
if vimsupport.VimIsNeovim():
return

props = tp.GetTextPropertyTypes()
if 'YCM_HL_UNKNOWN' not in props:
tp.AddTextPropertyType( 'YCM_HL_UNKNOWN',
highlight = 'WarningMsg',
priority = 0 )
global HIGHLIGHT_GROUPS

if "ycm_semantic_highlight_groups" in vimsupport.GetVimGlobalsKeys():
hi_groups: list[dict] = vimsupport.VimExpressionToPythonType(
"g:ycm_semantic_highlight_groups" )
hi_groups.extend( HIGHLIGHT_GROUPS[:] )
HIGHLIGHT_GROUPS = hi_groups

# init default highlight
default_hi = None
for groups in HIGHLIGHT_GROUPS:
if 'filetypes' not in groups:
if 'highlight' not in groups:
continue

if default_hi is None:
default_hi = groups
else:
# merge all defaults
for token_type, group in groups[ 'highlight' ].items():
if token_type not in default_hi[ 'highlight' ]:
default_hi[ 'highlight' ][ token_type ] = group

if default_hi is None or 'highlight' not in default_hi:
return

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,
priority = 0 )
# define default settings globally for make it compatible with older settings,
# that used global highlight groups, instead of groups per buffer
for token_type, group in default_hi[ 'highlight' ].items():
AddHiForTokenType( None, token_type, group )


# "arbitrary" base id
Expand All @@ -94,14 +156,48 @@ def __init__( self, bufnr ):
self._prop_id = NextPropID()
super().__init__( bufnr )

self._filetypes = vimsupport.GetBufferFiletypes( bufnr )

default_hi = None
target_groups = None
for ft_groups in HIGHLIGHT_GROUPS:
if 'filetypes' in ft_groups:
for filetype in self._filetypes:
if filetype in ft_groups[ 'filetypes' ]:
target_groups = ft_groups
elif default_hi is None:
default_hi = ft_groups

if target_groups is None and ( default_hi is None or 'highlight' not in default_hi ):
self._do_highlight = False
return
elif target_groups is None:
# default highlight should be defined globaly
self._do_highlight = True
return
elif 'highlight' not in target_groups:
self._do_highlight = False
return

for token_type, group in target_groups[ 'highlight' ].items():
AddHiForTokenType( bufnr, token_type, group )

self._do_highlight = True


def _NewRequest( self, request_range ):
if self._do_highlight == False:
return BaseRequest()

request: dict = BuildRequestData( self._bufnr )
request[ 'range' ] = request_range
return SemanticTokensRequest( request )


def _Draw( self ):
if self._do_highlight == False:
return

# We requested a snapshot
tokens = self._latest_response.get( 'tokens', [] )

Expand All @@ -120,8 +216,7 @@ def _Draw( self ):
if token[ 'type' ] not in REPORTED_MISSING_TYPES:
REPORTED_MISSING_TYPES.add( token[ 'type' ] )
vimsupport.PostVimMessage(
f"Token type { token[ 'type' ] } not supported. "
f"Define property type { prop_type }. "
f"Token type { token[ 'type' ] } is not defined for { self._filetypes }. "
f"See :help youcompleteme-customising-highlight-groups" )
else:
raise e
Expand Down