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

feat(semanticTokens)!: token highlight groups #4667

Merged
merged 6 commits into from
Mar 20, 2024
Merged
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
6 changes: 4 additions & 2 deletions autoload/coc/highlight.vim
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ let s:namespace_map = {}
let s:ns_id = 1
let s:diagnostic_hlgroups = ['CocErrorHighlight', 'CocWarningHighlight', 'CocInfoHighlight', 'CocHintHighlight', 'CocDeprecatedHighlight', 'CocUnusedHighlight']
" Maximum count to highlight each time.
let g:coc_highlight_maximum_count = get(g:, 'coc_highlight_maximum_count', 100)
let g:coc_highlight_maximum_count = get(g:, 'coc_highlight_maximum_count', 200)
let s:term = &termguicolors == 0 && !has('gui_running')

if has('nvim-0.5.0') && s:clear_match_by_window == 0
Expand Down Expand Up @@ -319,7 +319,9 @@ function! coc#highlight#add_highlight(bufnr, src_id, hl_group, line, col_start,
call nvim_buf_add_highlight(a:bufnr, a:src_id, a:hl_group, a:line, a:col_start, a:col_end)
endif
else
call coc#api#exec('buf_add_highlight', [a:bufnr, a:src_id, a:hl_group, a:line, a:col_start, a:col_end, opts])
if hlexists(a:hl_group)
call coc#api#exec('buf_add_highlight', [a:bufnr, a:src_id, a:hl_group, a:line, a:col_start, a:col_end, opts])
endif
endif
endfunction

Expand Down
23 changes: 8 additions & 15 deletions doc/coc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1877,7 +1877,7 @@ g:coc_highlight_maximum_count *g:coc_highlight_maximum_count*
When highlight items exceed maximum count, highlight items will be
grouped and added by using |timer_start| for better user experience.

Default `100`
Default `200`

g:coc_default_semantic_highlight_groups *g:coc_default_semantic_highlight_groups*

Expand Down Expand Up @@ -3356,27 +3356,20 @@ Semantic highlight groups are starts with `CocSem` which link to related
highlight groups, use variable |g:coc_default_semantic_highlight_groups| to
disable creation of these highlight groups.

The highlight groups rules:
>
`CocSemType + type` for types
`CocSemTypeMode + type + modifier` for modifier with type
<

Only semantic tokens types and `deprecated` modifier have default
highlight groups.

You need create highlight groups for highlight other modifiers and/or specific
modifier with type, for example:
>
" Add highlights for defaultLibrary modifier
hi link CocSemDefaultLibrary TSOtherDefaultLibrary
hi link CocSemDefaultLibraryClass TSTypeDefaultLibrary
hi link CocSemDefaultLibraryInterface TSTypeDefaultLibrary
hi link CocSemDefaultLibraryEnum TSTypeDefaultLibrary
hi link CocSemDefaultLibraryType TSTypeDefaultLibrary
hi link CocSemDefaultLibraryNamespace TSTypeDefaultLibrary

" Add highlights for declaration modifier
hi link CocSemDeclaration TSOtherDeclaration
hi link CocSemDeclarationClass TSTypeDeclaration
hi link CocSemDeclarationInterface TSTypeDeclaration
hi link CocSemDeclarationEnum TSTypeDeclaration
hi link CocSemDeclarationType TSTypeDeclaration
hi link CocSemDeclarationNamespace TSTypeDeclaration
hi link CocSemTypeModClassDeclaration ClassDeclaration
<
The modifier highlight groups have higher priority.

Expand Down
7 changes: 7 additions & 0 deletions history.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# 2024-02-28

- Increase `g:coc_highlight_maximum_count` default to 200
- Break change: semanticTokens highlight groups changed:
- `CocSem + type` to `CocSemType + type`
- `CocSem + modifier + type` to `CocSemTypeMod + type + modifier`

# 2024-03-06

- add `outline.autoHide` configuration to automatically hide the outline window when an item is clicked
Expand Down
56 changes: 28 additions & 28 deletions plugin/coc.vim
Original file line number Diff line number Diff line change
Expand Up @@ -562,31 +562,31 @@ function! s:Highlight() abort

if get(g:, 'coc_default_semantic_highlight_groups', 1)
let hlMap = {
\ 'Namespace': ['@namespace', 'Include'],
\ 'Type': ['@type', 'Type'],
\ 'Class': ['@constructor', 'Special'],
\ 'Enum': ['@type', 'Type'],
\ 'Interface': ['@type', 'Type'],
\ 'Struct': ['@structure', 'Identifier'],
\ 'TypeParameter': ['@parameter', 'Identifier'],
\ 'Parameter': ['@parameter', 'Identifier'],
\ 'Variable': ['@variable', 'Identifier'],
\ 'Property': ['@property', 'Identifier'],
\ 'EnumMember': ['@property', 'Constant'],
\ 'Event': ['@keyword', 'Keyword'],
\ 'Function': ['@function', 'Function'],
\ 'Method': ['@method', 'Function'],
\ 'Macro': ['@constant.macro', 'Define'],
\ 'Keyword': ['@keyword', 'Keyword'],
\ 'Modifier': ['@storageclass', 'StorageClass'],
\ 'Comment': ['@comment', 'Comment'],
\ 'String': ['@string', 'String'],
\ 'Number': ['@number', 'Number'],
\ 'Boolean': ['@boolean', 'Boolean'],
\ 'Regexp': ['@string.regex', 'String'],
\ 'Operator': ['@operator', 'Operator'],
\ 'Decorator': ['@symbol', 'Identifier'],
\ 'Deprecated': ['@text.strike', 'CocDeprecatedHighlight']
\ 'TypeNamespace': ['@module', 'Include'],
\ 'TypeType': ['@type', 'Type'],
\ 'TypeClass': ['@constructor', 'Special'],
\ 'TypeEnum': ['@type', 'Type'],
\ 'TypeInterface': ['@type', 'Type'],
\ 'TypeStruct': ['@structure', 'Identifier'],
\ 'TypeTypeParameter': ['@variable.parameter', 'Identifier'],
\ 'TypeParameter': ['@variable.parameter', 'Identifier'],
\ 'TypeVariable': ['@variable', 'Identifier'],
\ 'TypeProperty': ['@property', 'Identifier'],
\ 'TypeEnumMember': ['@property', 'Constant'],
\ 'TypeEvent': ['@keyword', 'Keyword'],
\ 'TypeFunction': ['@function', 'Function'],
\ 'TypeMethod': ['@function.method', 'Function'],
\ 'TypeMacro': ['@constant.macro', 'Define'],
\ 'TypeKeyword': ['@keyword', 'Keyword'],
\ 'TypeModifier': ['@keyword.storage', 'StorageClass'],
\ 'TypeComment': ['@comment', 'Comment'],
\ 'TypeString': ['@string', 'String'],
\ 'TypeNumber': ['@number', 'Number'],
\ 'TypeBoolean': ['@boolean', 'Boolean'],
\ 'TypeRegexp': ['@string.regexp', 'String'],
\ 'TypeOperator': ['@operator', 'Operator'],
\ 'TypeDecorator': ['@string.special.symbol', 'Identifier'],
\ 'ModDeprecated': ['@markup.strikethrough', 'CocDeprecatedHighlight']
\ }
for [key, value] in items(hlMap)
let ts = get(value, 0, '')
Expand All @@ -596,7 +596,7 @@ function! s:Highlight() abort
endif
let symbolMap = {
\ 'Keyword': ['@keyword', 'Keyword'],
\ 'Namespace': ['@namespace', 'Include'],
\ 'Namespace': ['@module', 'Include'],
\ 'Class': ['@constructor', 'Special'],
\ 'Method': ['@method', 'Function'],
\ 'Property': ['@property', 'Identifier'],
Expand All @@ -610,7 +610,7 @@ function! s:Highlight() abort
\ 'File': ['@file', 'Statement'],
\ 'Module': ['@module', 'Statement'],
\ 'Package': ['@package', 'Statement'],
\ 'Field': ['@field', 'Identifier'],
\ 'Field': ['@variable.member', 'Identifier'],
\ 'Constructor': ['@constructor', 'Special'],
\ 'Enum': ['@type', 'CocSymbolDefault'],
\ 'Interface': ['@type', 'CocSymbolDefault'],
Expand All @@ -628,7 +628,7 @@ function! s:Highlight() abort
\ 'Struct': ['@structure', 'Keyword'],
\ 'Event': ['@constant', 'Constant'],
\ 'Operator': ['@operator', 'Operator'],
\ 'TypeParameter': ['@parameter', 'Identifier'],
\ 'TypeParameter': ['@variable.parameter', 'Identifier'],
\ }
for [key, value] in items(symbolMap)
let hlGroup = coc#highlight#valid(value[0]) ? value[0] : get(value, 1, 'CocSymbolDefault')
Expand Down
12 changes: 2 additions & 10 deletions src/__tests__/handler/semanticTokens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,6 @@ describe('semanticTokens', () => {
}
}
}, { tokenModifiers: [], tokenTypes: [] }))
await semanticTokens.fetchHighlightGroups()
await semanticTokens.showHighlightInfo()
let buf = await nvim.buffer
let lines = await buf.lines
Expand Down Expand Up @@ -339,7 +338,7 @@ describe('semanticTokens', () => {
let buf = await win.buffer
let lines = await buf.lines
let content = lines.join('\n')
expect(content).toMatch('CocSemDeclarationFunction')
expect(content).toMatch('Type: function\nModifiers: declaration\nHighlight group: CocSemTypeFunction')
await window.moveTo({ line: 1, character: 0 })
await commandManager.executeCommand('semanticTokens.inspect')
win = await helper.getFloat()
Expand Down Expand Up @@ -717,7 +716,7 @@ describe('semanticTokens', () => {
await nvim.call('CocAction', 'semanticHighlight')
const highlights = await nvim.call("coc#highlight#get_highlights", [doc.bufnr, 'semanticTokens']) as any[]
expect(highlights.length).toBeGreaterThan(0)
expect(highlights[0][0]).toBe('CocSemKeyword')
expect(highlights[0][0]).toBe('CocSemTypeKeyword')
})
})

Expand Down Expand Up @@ -761,17 +760,10 @@ describe('semanticTokens', () => {
// @ts-ignore
workspace._env.updateHighlight = true
expect(enabled).toBe(false)
semanticTokens.staticConfig.highlightGroups = ['CocSemKeyword']
expect(() => {
item.checkState()
}).toThrow('provider not found')
registerProvider()
doc = await helper.createDocument('t.rs')
item = semanticTokens.getItem(doc.bufnr)
semanticTokens.staticConfig.highlightGroups = []
expect(() => {
item.checkState()
}).toThrow('Unable to find highlight groups')
})
})

Expand Down
49 changes: 19 additions & 30 deletions src/handler/semanticTokens/buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ const highlightGroupMap: Map<string, string> = new Map()

export interface StaticConfig {
filetypes: string[] | null
highlightGroups: ReadonlyArray<string>
}

export default class SemanticTokensBuffer implements SyncItem {
Expand Down Expand Up @@ -190,7 +189,6 @@ export default class SemanticTokensBuffer implements SyncItem {
if (!workspace.env.updateHighlight) throw new Error(`Can't perform highlight update, highlight update requires vim >= 8.1.1719 or neovim >= 0.5.0`)
if (!this.configEnabled) throw new Error(`Semantic tokens highlight not enabled for current filetype: ${this.doc.filetype}`)
if (!this.hasProvider || !this.hasLegend) throw new Error(`SemanticTokens provider not found for ${this.doc.uri}`)
if (this.staticConfig.highlightGroups.length === 0) throw new Error(`Unable to find highlight groups starts with CocSem`)
}

public async getTokenRanges(
Expand Down Expand Up @@ -232,42 +230,33 @@ export default class SemanticTokensBuffer implements SyncItem {
* Single line only.
*/
private addHighlightItems(highlights: SemanticTokenRange[], range: [number, number, number], tokenType: string, tokenModifiers: string[]): void {
// highlight groups:
// CocSem + Type + type
// CocSem + TypeMod + type + modifier

let { combinedModifiers } = this.config
let { highlightGroups } = this.staticConfig
let highlightGroup: string
let combine = false
// Compose highlight group CocSem + modifier + type
for (let item of tokenModifiers) {
let hlGroup = HLGROUP_PREFIX + toHighlightPart(item) + toHighlightPart(tokenType)
if (highlightGroups.includes(hlGroup)) {
combine = combinedModifiers.includes(item)
highlightGroup = hlGroup
break
}
}
if (!highlightGroup) {
for (let modifier of tokenModifiers) {
let hlGroup = HLGROUP_PREFIX + toHighlightPart(modifier)
if (highlightGroups.includes(hlGroup)) {
highlightGroup = hlGroup
combine = combinedModifiers.includes(modifier)
break
}
}
}
if (!highlightGroup) {
let hlGroup = HLGROUP_PREFIX + toHighlightPart(tokenType)
if (highlightGroups.includes(hlGroup)) {
highlightGroup = hlGroup
}
}

highlights.push({
range,
tokenType,
combine,
hlGroup: highlightGroup,
hlGroup: HLGROUP_PREFIX + 'Type' + toHighlightPart(tokenType),
tokenModifiers,
})

if (tokenModifiers.length) {
// only use first modifier to avoid highlight flicking
const modifier = tokenModifiers[0]
combine = combinedModifiers.includes(modifier)
highlights.push({
range,
tokenType,
combine,
hlGroup: HLGROUP_PREFIX + 'TypeMod' + toHighlightPart(tokenType) + toHighlightPart(modifier),
tokenModifiers,
})
}
}

private toHighlightItems(highlights: ReadonlyArray<SemanticTokenRange>, startLine?: number, endLine?: number): HighlightItem[] {
Expand Down
27 changes: 4 additions & 23 deletions src/handler/semanticTokens/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import BufferSync from '../../model/bufferSync'
import Highlighter from '../../model/highlighter'
import { Documentation, FloatFactory } from '../../types'
import { disposeAll } from '../../util'
import { distinct, isFalsyOrEmpty, toArray } from '../../util/array'
import { distinct, toArray } from '../../util/array'
import type { Disposable } from '../../util/protocol'
import { toErrorText, toText, upperFirst } from '../../util/string'
import { toErrorText, toText } from '../../util/string'
import window from '../../window'
import workspace from '../../workspace'
import SemanticTokensBuffer, { HLGROUP_PREFIX, NAMESPACE, StaticConfig, toHighlightPart } from './buffer'
Expand All @@ -28,7 +28,6 @@ export default class SemanticTokens {
constructor(private nvim: Neovim) {
this.staticConfig = {
filetypes: getFiletypes(),
highlightGroups: []
}
workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('semanticTokens')) {
Expand Down Expand Up @@ -76,7 +75,6 @@ export default class SemanticTokens {
return new SemanticTokensBuffer(this.nvim, doc, this.staticConfig)
})
languages.onDidSemanticTokensRefresh(async selector => {
if (isFalsyOrEmpty(this.staticConfig.highlightGroups)) await this.fetchHighlightGroups()
let visibleBufs = window.visibleTextEditors.map(o => o.document.bufnr)
for (let item of this.highlighters.items) {
if (!workspace.match(selector, item.doc)) continue
Expand Down Expand Up @@ -157,11 +155,6 @@ export default class SemanticTokens {
floatFactory?.close()
}

public async fetchHighlightGroups(): Promise<void> {
let highlightGroups = await this.nvim.call('coc#util#semantic_hlgroups') as string[]
this.staticConfig.highlightGroups = highlightGroups
}

public async getCurrentItem(): Promise<SemanticTokensBuffer | undefined> {
let buf = await this.nvim.buffer
return this.getItem(buf.id)
Expand All @@ -176,7 +169,6 @@ export default class SemanticTokens {
public async highlightCurrent(): Promise<void> {
let item = await this.getCurrentItem()
if (!item || !item.enabled) throw new Error(`Unable to perform semantic highlights for current buffer.`)
await this.fetchHighlightGroups()
await item.forceHighlight()
}

Expand Down Expand Up @@ -215,26 +207,15 @@ export default class SemanticTokens {
let legend = languages.getLegend(doc.textDocument) ?? languages.getLegend(doc.textDocument, true)
if (legend.tokenTypes.length) {
for (const t of [...new Set(legend.tokenTypes)]) {
let text = HLGROUP_PREFIX + toHighlightPart(t)
let text = HLGROUP_PREFIX + 'Type' + toHighlightPart(t)
hl.addTexts([{ text: '-', hlGroup: 'Comment' }, { text: ' ' }, { text, hlGroup: text }])
}
hl.addLine('')
} else {
hl.addLine('No token types supported', 'Comment')
hl.addLine('')
}
hl.addLine('Tokens modifiers that current Language Server supported:', headGroup)
hl.addLine('')
if (legend.tokenModifiers.length) {
for (const t of [...new Set(legend.tokenModifiers)]) {
let text = HLGROUP_PREFIX + toHighlightPart(t)
hl.addTexts([{ text: '-', hlGroup: 'Comment' }, { text: ' ' }, { text, hlGroup: text }])
}
hl.addLine('')
} else {
hl.addLine('No token modifiers exist', 'Comment')
hl.addLine('')
}
// modifiers are added to one token type, we can't list them directly
} catch (e) {
hl.addLine(toErrorText(e))
}
Expand Down