Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow to set semantic highlight groups based on filetype #4167

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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 @@ -942,43 +942,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
189 changes: 141 additions & 48 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,60 +26,119 @@
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',
'decorator': 'Special',
'unknown': 'Normal',

# These are not part of the spec, but are used by clangd
'bracket': 'Normal',
'concept': 'Type',
# These are not part of the spec, but are used by jdt.ls
'annotation': 'Macro',
}
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',

# These are not part of the spec, but are used by clangd
'bracket': 'Normal',
# These are not part of the spec, but are used by jdt.ls
'annotation': 'Macro',
}
}]
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)

Check warning on line 72 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L72

Added line #L72 was not covered by tests

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

Check warning on line 76 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L75-L76

Added lines #L75 - L76 were not covered by tests

if not vimsupport.GetIntValue(
f"hlexists( '{ vimsupport.EscapeForVim( hi ) }' )" ):
vimsupport.PostVimMessage(

Check warning on line 80 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L80

Added line #L80 was not covered by tests
f"Higlight group { hi } is not difined for { filetypes }. "
f"See :help youcompleteme-customising-highlight-groups" )
return

Check warning on line 83 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L83

Added line #L83 was not covered by tests

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,

Check warning on line 94 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L93-L94

Added lines #L93 - L94 were not covered by tests
highlight = hi,
priority = 0,
combine = combine,
bufnr = bufnr )
except vim.error as e:
if 'E969:' in str( e ):

Check warning on line 100 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L99-L100

Added lines #L99 - L100 were not covered by tests
# at YcmRestart we can get error about redefining properties, just ignore them
pass

Check warning on line 102 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L102

Added line #L102 was not covered by tests
else:
raise e

Check warning on line 104 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L104

Added line #L104 was not covered by tests



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(

Check warning on line 115 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L115

Added line #L115 was not covered by tests
"g:ycm_semantic_highlight_groups" )
hi_groups.extend( HIGHLIGHT_GROUPS[:] )
HIGHLIGHT_GROUPS = hi_groups

Check warning on line 118 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L117-L118

Added lines #L117 - L118 were not covered by tests

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

Check warning on line 125 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L125

Added line #L125 was not covered by tests

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

Check warning on line 133 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L131-L133

Added lines #L131 - L133 were not covered by tests

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

Check warning on line 136 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L136

Added line #L136 was not covered by tests

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 @@ -101,14 +161,48 @@
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

Check warning on line 172 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L170-L172

Added lines #L170 - L172 were not covered by tests
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

Check warning on line 178 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L177-L178

Added lines #L177 - L178 were not covered by tests
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

Check warning on line 185 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L183-L185

Added lines #L183 - L185 were not covered by tests

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

Check warning on line 188 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L187-L188

Added lines #L187 - L188 were not covered by tests

self._do_highlight = True

Check warning on line 190 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L190

Added line #L190 was not covered by tests


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

Check warning on line 195 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L195

Added line #L195 was not covered by tests

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


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

Check warning on line 204 in python/ycm/semantic_highlighting.py

View check run for this annotation

Codecov / codecov/patch

python/ycm/semantic_highlighting.py#L204

Added line #L204 was not covered by tests

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

Expand All @@ -127,8 +221,7 @@
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
Loading