mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	feat(lsp)!: add rule-based sem token highlighting (#22022)
feat(lsp)!: change semantic token highlighting Change the default highlights used, and add more highlights per token. Add an LspTokenUpdate event and a highlight_token function. :Inspect now shows any highlights applied by token highlighting rules, default or user-defined. BREAKING CHANGE: change the default highlight groups used by semantic token highlighting.
This commit is contained in:
		| @@ -482,6 +482,71 @@ LspSignatureActiveParameter | |||||||
|     Used to highlight the active parameter in the signature help. See |     Used to highlight the active parameter in the signature help. See | ||||||
|     |vim.lsp.handlers.signature_help()|. |     |vim.lsp.handlers.signature_help()|. | ||||||
|  |  | ||||||
|  | ------------------------------------------------------------------------------ | ||||||
|  | LSP SEMANTIC HIGHLIGHTS                               *lsp-semantic-highlight* | ||||||
|  |  | ||||||
|  | When available, the LSP client highlights code using |lsp-semantic_tokens|, | ||||||
|  | which are another way that LSP servers can provide information about source | ||||||
|  | code.  Note that this is in addition to treesitter syntax highlighting; | ||||||
|  | semantic highlighting does not replace syntax highlighting. | ||||||
|  |  | ||||||
|  | The server will typically provide one token per identifier in the source code. | ||||||
|  | The token will have a `type` such as "function" or "variable", and 0 or more | ||||||
|  | `modifier`s such as "readonly" or "deprecated." The standard types and | ||||||
|  | modifiers are described here: | ||||||
|  | https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens | ||||||
|  | LSP servers may also use off-spec types and modifiers. | ||||||
|  |  | ||||||
|  | The LSP client adds one or more highlights for each token. The highlight | ||||||
|  | groups are derived from the token's type and modifiers: | ||||||
|  |   • `@lsp.type.<type>.<ft>` for the type | ||||||
|  |   • `@lsp.mod.<mod>.<ft>` for each modifier | ||||||
|  |   • `@lsp.typemod.<type>.<mod>.<ft>` for each modifier | ||||||
|  | Use |:Inspect| to view the higlights for a specific token. Use |:hi| or | ||||||
|  | |nvim_set_hl()| to change the appearance of semantic highlights: >vim | ||||||
|  |  | ||||||
|  |     hi @lsp.type.function guifg=Yellow        " function names are yellow | ||||||
|  |     hi @lsp.type.variable.lua guifg=Green     " variables in lua are green | ||||||
|  |     hi @lsp.mod.deprecated gui=strikethrough  " deprecated is crossed out | ||||||
|  |     hi @lsp.typemod.function.async guifg=Blue " async functions are blue | ||||||
|  | < | ||||||
|  | The value |vim.highlight.priorities|`.semantic_tokens` is the priority of the | ||||||
|  | `@lsp.type.*` highlights. The `@lsp.mod.*` and `@lsp.typemod.*` highlights | ||||||
|  | have priorities one and two higher, respectively. | ||||||
|  |  | ||||||
|  | You can disable semantic highlights by clearing the highlight groups: >lua | ||||||
|  |  | ||||||
|  |     -- Hide semantic highlights for functions | ||||||
|  |     vim.api.nvim_set_hl(0, '@lsp.type.function', {}) | ||||||
|  |  | ||||||
|  |     -- Hide all semantic highlights | ||||||
|  |     for _, group in ipairs(vim.fn.getcompletion("@lsp", "highlight")) do | ||||||
|  |       vim.api.nvim_set_hl(0, group, {}) | ||||||
|  |     end | ||||||
|  | < | ||||||
|  | You probably want these inside a |ColorScheme| autocommand. | ||||||
|  |  | ||||||
|  | Use |LspTokenUpdate| and |vim.lsp.semantic_tokens.highlight_token()| for more | ||||||
|  | complex highlighting. | ||||||
|  |  | ||||||
|  | The following groups are linked by default to standard |group-name|s: | ||||||
|  | > | ||||||
|  |     @lsp.type.class         Structure | ||||||
|  |     @lsp.type.decorator     Function | ||||||
|  |     @lsp.type.enum          Structure | ||||||
|  |     @lsp.type.enumMember    Constant | ||||||
|  |     @lsp.type.function      Function | ||||||
|  |     @lsp.type.interface     Structure | ||||||
|  |     @lsp.type.macro         Macro | ||||||
|  |     @lsp.type.method        Function | ||||||
|  |     @lsp.type.namespace     Structure | ||||||
|  |     @lsp.type.parameter     Identifier | ||||||
|  |     @lsp.type.property      Identifier | ||||||
|  |     @lsp.type.struct        Structure | ||||||
|  |     @lsp.type.type          Type | ||||||
|  |     @lsp.type.typeParameter TypeDef | ||||||
|  |     @lsp.type.variable      Identifier | ||||||
|  | < | ||||||
| ============================================================================== | ============================================================================== | ||||||
| EVENTS                                                            *lsp-events* | EVENTS                                                            *lsp-events* | ||||||
|  |  | ||||||
| @@ -516,6 +581,29 @@ callback in the "data" table. Example: >lua | |||||||
|       end, |       end, | ||||||
|     }) |     }) | ||||||
| < | < | ||||||
|  |  | ||||||
|  | LspTokenUpdate                                                *LspTokenUpdate* | ||||||
|  |  | ||||||
|  | When a visible semantic token is sent or updated by the LSP server, or when an | ||||||
|  | existing token becomes visible for the first time. The |autocmd-pattern| is | ||||||
|  | the name of the buffer. When used from Lua, the token and client ID are passed | ||||||
|  | to the callback in the "data" table. The token fields are documented in | ||||||
|  | |vim.lsp.semantic_tokens.get_at_pos()|. Example: >lua | ||||||
|  |  | ||||||
|  |     vim.api.nvim_create_autocmd('LspTokenUpdate', { | ||||||
|  |       callback = function(args) | ||||||
|  |         local token = args.data.token | ||||||
|  |         if token.type == 'variable' and not token.modifiers.readonly then | ||||||
|  |           vim.lsp.semantic_tokens.highlight_token( | ||||||
|  |             token, args.buf, args.data.client_id, 'MyMutableVariableHighlight' | ||||||
|  |           ) | ||||||
|  |         end | ||||||
|  |       end, | ||||||
|  |     }) | ||||||
|  | < | ||||||
|  | Note: doing anything other than calling | ||||||
|  | |vim.lsp.semantic_tokens.highlight_token()| is considered experimental. | ||||||
|  |  | ||||||
| Also the following |User| |autocommand|s are provided: | Also the following |User| |autocommand|s are provided: | ||||||
|  |  | ||||||
| LspProgressUpdate                                          *LspProgressUpdate* | LspProgressUpdate                                          *LspProgressUpdate* | ||||||
| @@ -1332,7 +1420,8 @@ force_refresh({bufnr})               *vim.lsp.semantic_tokens.force_refresh()* | |||||||
|     highlighting (|vim.lsp.semantic_tokens.start()| has been called for it) |     highlighting (|vim.lsp.semantic_tokens.start()| has been called for it) | ||||||
|  |  | ||||||
|     Parameters: ~ |     Parameters: ~ | ||||||
|       • {bufnr}  (nil|number) default: current buffer |       • {bufnr}  (number|nil) filter by buffer. All buffers if nil, current | ||||||
|  |                  buffer if 0 | ||||||
|  |  | ||||||
|                                         *vim.lsp.semantic_tokens.get_at_pos()* |                                         *vim.lsp.semantic_tokens.get_at_pos()* | ||||||
| get_at_pos({bufnr}, {row}, {col}) | get_at_pos({bufnr}, {row}, {col}) | ||||||
| @@ -1345,7 +1434,34 @@ get_at_pos({bufnr}, {row}, {col}) | |||||||
|       • {col}    (number|nil) Position column (default cursor position) |       • {col}    (number|nil) Position column (default cursor position) | ||||||
|  |  | ||||||
|     Return: ~ |     Return: ~ | ||||||
|         (table|nil) List of tokens at position |         (table|nil) List of tokens at position. Each token has the following | ||||||
|  |         fields: | ||||||
|  |         • line (number) line number, 0-based | ||||||
|  |         • start_col (number) start column, 0-based | ||||||
|  |         • end_col (number) end column, 0-based | ||||||
|  |         • type (string) token type as string, e.g. "variable" | ||||||
|  |         • modifiers (table) token modifiers as a set. E.g., { static = true, | ||||||
|  |           readonly = true } | ||||||
|  |  | ||||||
|  |                                    *vim.lsp.semantic_tokens.highlight_token()* | ||||||
|  | highlight_token({token}, {bufnr}, {client_id}, {hl_group}, {opts}) | ||||||
|  |     Highlight a semantic token. | ||||||
|  |  | ||||||
|  |     Apply an extmark with a given highlight group for a semantic token. The | ||||||
|  |     mark will be deleted by the semantic token engine when appropriate; for | ||||||
|  |     example, when the LSP sends updated tokens. This function is intended for | ||||||
|  |     use inside |LspTokenUpdate| callbacks. | ||||||
|  |  | ||||||
|  |     Parameters: ~ | ||||||
|  |       • {token}      (table) a semantic token, found as `args.data.token` in | ||||||
|  |                      |LspTokenUpdate|. | ||||||
|  |       • {bufnr}      (number) the buffer to highlight | ||||||
|  |       • {client_id}  (number) The ID of the |vim.lsp.client| | ||||||
|  |       • {hl_group}   (string) Highlight group name | ||||||
|  |       • {opts}       (table|nil) Optional parameters. | ||||||
|  |                      • priority: (number|nil) Priority for the applied | ||||||
|  |                        extmark. Defaults to | ||||||
|  |                        `vim.highlight.priorities.semantic_tokens + 3` | ||||||
|  |  | ||||||
| start({bufnr}, {client_id}, {opts})          *vim.lsp.semantic_tokens.start()* | start({bufnr}, {client_id}, {opts})          *vim.lsp.semantic_tokens.start()* | ||||||
|     Start the semantic token highlighting engine for the given buffer with the |     Start the semantic token highlighting engine for the given buffer with the | ||||||
|   | |||||||
| @@ -90,7 +90,7 @@ The following new APIs or features were added. | |||||||
|   `semanticTokensProvider` from the LSP client's {server_capabilities} in the |   `semanticTokensProvider` from the LSP client's {server_capabilities} in the | ||||||
|   `LspAttach` callback. |   `LspAttach` callback. | ||||||
|  |  | ||||||
|   See |lsp-semantic_tokens| for more information. |   See |lsp-semantic-highlight| for more information. | ||||||
|  |  | ||||||
| • |vim.treesitter.inspect_tree()| and |:InspectTree| opens a split window | • |vim.treesitter.inspect_tree()| and |:InspectTree| opens a split window | ||||||
|   showing a text representation of the nodes in a language tree for the current |   showing a text representation of the nodes in a language tree for the current | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| ---@field syntax boolean include syntax based highlight groups (defaults to true) | ---@field syntax boolean include syntax based highlight groups (defaults to true) | ||||||
| ---@field treesitter boolean include treesitter based highlight groups (defaults to true) | ---@field treesitter boolean include treesitter based highlight groups (defaults to true) | ||||||
| ---@field extmarks boolean|"all" include extmarks. When `all`, then extmarks without a `hl_group` will also be included (defaults to true) | ---@field extmarks boolean|"all" include extmarks. When `all`, then extmarks without a `hl_group` will also be included (defaults to true) | ||||||
| ---@field semantic_tokens boolean include semantic tokens (defaults to true) | ---@field semantic_tokens boolean include semantic token highlights (defaults to true) | ||||||
| local defaults = { | local defaults = { | ||||||
|   syntax = true, |   syntax = true, | ||||||
|   treesitter = true, |   treesitter = true, | ||||||
| @@ -81,47 +81,54 @@ function vim.inspect_pos(bufnr, row, col, filter) | |||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   -- semantic tokens |   --- Convert an extmark tuple into a map-like table | ||||||
|   if filter.semantic_tokens then |   --- @private | ||||||
|     for _, token in ipairs(vim.lsp.semantic_tokens.get_at_pos(bufnr, row, col) or {}) do |   local function to_map(extmark) | ||||||
|       token.hl_groups = { |     extmark = { | ||||||
|         type = resolve_hl({ hl_group = '@' .. token.type }), |       id = extmark[1], | ||||||
|         modifiers = vim.tbl_map(function(modifier) |       row = extmark[2], | ||||||
|           return resolve_hl({ hl_group = '@' .. modifier }) |       col = extmark[3], | ||||||
|         end, token.modifiers or {}), |       opts = resolve_hl(extmark[4]), | ||||||
|       } |     } | ||||||
|       table.insert(results.semantic_tokens, token) |     extmark.end_row = extmark.opts.end_row or extmark.row -- inclusive | ||||||
|     end |     extmark.end_col = extmark.opts.end_col or (extmark.col + 1) -- exclusive | ||||||
|  |     return extmark | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   -- extmarks |   --- Check if an extmark overlaps this position | ||||||
|   if filter.extmarks then |   --- @private | ||||||
|     for ns, nsid in pairs(vim.api.nvim_get_namespaces()) do |   local function is_here(extmark) | ||||||
|       if ns:find('vim_lsp_semantic_tokens') ~= 1 then |     return (row >= extmark.row and row <= extmark.end_row) -- within the rows of the extmark | ||||||
|         local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, nsid, 0, -1, { details = true }) |       and (row > extmark.row or col >= extmark.col) -- either not the first row, or in range of the col | ||||||
|         for _, extmark in ipairs(extmarks) do |       and (row < extmark.end_row or col < extmark.end_col) -- either not in the last row or in range of the col | ||||||
|           extmark = { |  | ||||||
|             ns_id = nsid, |  | ||||||
|             ns = ns, |  | ||||||
|             id = extmark[1], |  | ||||||
|             row = extmark[2], |  | ||||||
|             col = extmark[3], |  | ||||||
|             opts = resolve_hl(extmark[4]), |  | ||||||
|           } |  | ||||||
|           local end_row = extmark.opts.end_row or extmark.row -- inclusive |  | ||||||
|           local end_col = extmark.opts.end_col or (extmark.col + 1) -- exclusive |  | ||||||
|           if |  | ||||||
|             (filter.extmarks == 'all' or extmark.opts.hl_group) -- filter hl_group |  | ||||||
|             and (row >= extmark.row and row <= end_row) -- within the rows of the extmark |  | ||||||
|             and (row > extmark.row or col >= extmark.col) -- either not the first row, or in range of the col |  | ||||||
|             and (row < end_row or col < end_col) -- either not in the last row or in range of the col |  | ||||||
|           then |  | ||||||
|             table.insert(results.extmarks, extmark) |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   -- all extmarks at this position | ||||||
|  |   local extmarks = {} | ||||||
|  |   for ns, nsid in pairs(vim.api.nvim_get_namespaces()) do | ||||||
|  |     local ns_marks = vim.api.nvim_buf_get_extmarks(bufnr, nsid, 0, -1, { details = true }) | ||||||
|  |     ns_marks = vim.tbl_map(to_map, ns_marks) | ||||||
|  |     ns_marks = vim.tbl_filter(is_here, ns_marks) | ||||||
|  |     for _, mark in ipairs(ns_marks) do | ||||||
|  |       mark.ns_id = nsid | ||||||
|  |       mark.ns = ns | ||||||
|  |     end | ||||||
|  |     vim.list_extend(extmarks, ns_marks) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   if filter.semantic_tokens then | ||||||
|  |     results.semantic_tokens = vim.tbl_filter(function(extmark) | ||||||
|  |       return extmark.ns:find('vim_lsp_semantic_tokens') == 1 | ||||||
|  |     end, extmarks) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   if filter.extmarks then | ||||||
|  |     results.extmarks = vim.tbl_filter(function(extmark) | ||||||
|  |       return extmark.ns:find('vim_lsp_semantic_tokens') ~= 1 | ||||||
|  |         and (filter.extmarks == 'all' or extmark.opts.hl_group) | ||||||
|  |     end, extmarks) | ||||||
|  |   end | ||||||
|  |  | ||||||
|   return results |   return results | ||||||
| end | end | ||||||
|  |  | ||||||
| @@ -174,16 +181,17 @@ function vim.show_pos(bufnr, row, col, filter) | |||||||
|     nl() |     nl() | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   -- semantic tokens | ||||||
|   if #items.semantic_tokens > 0 then |   if #items.semantic_tokens > 0 then | ||||||
|     append('Semantic Tokens', 'Title') |     append('Semantic Tokens', 'Title') | ||||||
|     nl() |     nl() | ||||||
|     for _, token in ipairs(items.semantic_tokens) do |     local sorted_marks = vim.fn.sort(items.semantic_tokens, function(left, right) | ||||||
|       local client = vim.lsp.get_client_by_id(token.client_id) |       local left_first = left.opts.priority < right.opts.priority | ||||||
|       client = client and (' (' .. client.name .. ')') or '' |         or left.opts.priority == right.opts.priority and left.opts.hl_group < right.opts.hl_group | ||||||
|       item(token.hl_groups.type, 'type' .. client) |       return left_first and -1 or 1 | ||||||
|       for _, modifier in ipairs(token.hl_groups.modifiers) do |     end) | ||||||
|         item(modifier, 'modifier' .. client) |     for _, extmark in ipairs(sorted_marks) do | ||||||
|       end |       item(extmark.opts, 'priority: ' .. extmark.opts.priority) | ||||||
|     end |     end | ||||||
|     nl() |     nl() | ||||||
|   end |   end | ||||||
| @@ -197,6 +205,7 @@ function vim.show_pos(bufnr, row, col, filter) | |||||||
|     end |     end | ||||||
|     nl() |     nl() | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   -- extmarks |   -- extmarks | ||||||
|   if #items.extmarks > 0 then |   if #items.extmarks > 0 then | ||||||
|     append('Extmarks', 'Title') |     append('Extmarks', 'Title') | ||||||
|   | |||||||
| @@ -8,8 +8,8 @@ local bit = require('bit') | |||||||
| --- @field start_col number start column 0-based | --- @field start_col number start column 0-based | ||||||
| --- @field end_col number end column 0-based | --- @field end_col number end column 0-based | ||||||
| --- @field type string token type as string | --- @field type string token type as string | ||||||
| --- @field modifiers string[] token modifiers as strings | --- @field modifiers table token modifiers as a set. E.g., { static = true, readonly = true } | ||||||
| --- @field extmark_added boolean whether this extmark has been added to the buffer yet | --- @field marked boolean whether this token has had extmarks applied | ||||||
| --- | --- | ||||||
| --- @class STCurrentResult | --- @class STCurrentResult | ||||||
| --- @field version number document version associated with this result | --- @field version number document version associated with this result | ||||||
| @@ -36,10 +36,13 @@ local bit = require('bit') | |||||||
| ---@field client_state table<number, STClientState> | ---@field client_state table<number, STClientState> | ||||||
| local STHighlighter = { active = {} } | local STHighlighter = { active = {} } | ||||||
|  |  | ||||||
|  | --- Do a binary search of the tokens in the half-open range [lo, hi). | ||||||
|  | --- | ||||||
|  | --- Return the index i in range such that tokens[j].line < line for all j < i, and | ||||||
|  | --- tokens[j].line >= line for all j >= i, or return hi if no such index is found. | ||||||
|  | --- | ||||||
| ---@private | ---@private | ||||||
| local function binary_search(tokens, line) | local function lower_bound(tokens, line, lo, hi) | ||||||
|   local lo = 1 |  | ||||||
|   local hi = #tokens |  | ||||||
|   while lo < hi do |   while lo < hi do | ||||||
|     local mid = math.floor((lo + hi) / 2) |     local mid = math.floor((lo + hi) / 2) | ||||||
|     if tokens[mid].line < line then |     if tokens[mid].line < line then | ||||||
| @@ -51,16 +54,34 @@ local function binary_search(tokens, line) | |||||||
|   return lo |   return lo | ||||||
| end | end | ||||||
|  |  | ||||||
|  | --- Do a binary search of the tokens in the half-open range [lo, hi). | ||||||
|  | --- | ||||||
|  | --- Return the index i in range such that tokens[j].line <= line for all j < i, and | ||||||
|  | --- tokens[j].line > line for all j >= i, or return hi if no such index is found. | ||||||
|  | --- | ||||||
|  | ---@private | ||||||
|  | local function upper_bound(tokens, line, lo, hi) | ||||||
|  |   while lo < hi do | ||||||
|  |     local mid = math.floor((lo + hi) / 2) | ||||||
|  |     if line < tokens[mid].line then | ||||||
|  |       hi = mid | ||||||
|  |     else | ||||||
|  |       lo = mid + 1 | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |   return lo | ||||||
|  | end | ||||||
|  |  | ||||||
| --- Extracts modifier strings from the encoded number in the token array | --- Extracts modifier strings from the encoded number in the token array | ||||||
| --- | --- | ||||||
| ---@private | ---@private | ||||||
| ---@return string[] | ---@return table<string, boolean> | ||||||
| local function modifiers_from_number(x, modifiers_table) | local function modifiers_from_number(x, modifiers_table) | ||||||
|   local modifiers = {} |   local modifiers = {} | ||||||
|   local idx = 1 |   local idx = 1 | ||||||
|   while x > 0 do |   while x > 0 do | ||||||
|     if bit.band(x, 1) == 1 then |     if bit.band(x, 1) == 1 then | ||||||
|       modifiers[#modifiers + 1] = modifiers_table[idx] |       modifiers[modifiers_table[idx]] = true | ||||||
|     end |     end | ||||||
|     x = bit.rshift(x, 1) |     x = bit.rshift(x, 1) | ||||||
|     idx = idx + 1 |     idx = idx + 1 | ||||||
| @@ -109,7 +130,7 @@ local function tokens_to_ranges(data, bufnr, client) | |||||||
|         end_col = end_col, |         end_col = end_col, | ||||||
|         type = token_type, |         type = token_type, | ||||||
|         modifiers = modifiers, |         modifiers = modifiers, | ||||||
|         extmark_added = false, |         marked = false, | ||||||
|       } |       } | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| @@ -355,7 +376,7 @@ end | |||||||
| --- | --- | ||||||
| ---@private | ---@private | ||||||
| function STHighlighter:on_win(topline, botline) | function STHighlighter:on_win(topline, botline) | ||||||
|   for _, state in pairs(self.client_state) do |   for client_id, state in pairs(self.client_state) do | ||||||
|     local current_result = state.current_result |     local current_result = state.current_result | ||||||
|     if current_result.version and current_result.version == util.buf_versions[self.bufnr] then |     if current_result.version and current_result.version == util.buf_versions[self.bufnr] then | ||||||
|       if not current_result.namespace_cleared then |       if not current_result.namespace_cleared then | ||||||
| @@ -372,52 +393,55 @@ function STHighlighter:on_win(topline, botline) | |||||||
|       -- |       -- | ||||||
|       -- Instead, we have to use normal extmarks that can attach to locations |       -- Instead, we have to use normal extmarks that can attach to locations | ||||||
|       -- in the buffer and are persisted between redraws. |       -- in the buffer and are persisted between redraws. | ||||||
|  |       -- | ||||||
|  |       -- `strict = false` is necessary here for the 1% of cases where the | ||||||
|  |       -- current result doesn't actually match the buffer contents. Some | ||||||
|  |       -- LSP servers can respond with stale tokens on requests if they are | ||||||
|  |       -- still processing changes from a didChange notification. | ||||||
|  |       -- | ||||||
|  |       -- LSP servers that do this _should_ follow up known stale responses | ||||||
|  |       -- with a refresh notification once they've finished processing the | ||||||
|  |       -- didChange notification, which would re-synchronize the tokens from | ||||||
|  |       -- our end. | ||||||
|  |       -- | ||||||
|  |       -- The server I know of that does this is clangd when the preamble of | ||||||
|  |       -- a file changes and the token request is processed with a stale | ||||||
|  |       -- preamble while the new one is still being built. Once the preamble | ||||||
|  |       -- finishes, clangd sends a refresh request which lets the client | ||||||
|  |       -- re-synchronize the tokens. | ||||||
|  |  | ||||||
|  |       local set_mark = function(token, hl_group, delta) | ||||||
|  |         vim.api.nvim_buf_set_extmark(self.bufnr, state.namespace, token.line, token.start_col, { | ||||||
|  |           hl_group = hl_group, | ||||||
|  |           end_col = token.end_col, | ||||||
|  |           priority = vim.highlight.priorities.semantic_tokens + delta, | ||||||
|  |           strict = false, | ||||||
|  |         }) | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |       local ft = vim.bo[self.bufnr].filetype | ||||||
|       local highlights = current_result.highlights |       local highlights = current_result.highlights | ||||||
|       local idx = binary_search(highlights, topline) |       local first = lower_bound(highlights, topline, 1, #highlights + 1) | ||||||
|  |       local last = upper_bound(highlights, botline, first, #highlights + 1) - 1 | ||||||
|  |  | ||||||
|       for i = idx, #highlights do |       for i = first, last do | ||||||
|         local token = highlights[i] |         local token = highlights[i] | ||||||
|  |         if not token.marked then | ||||||
|         if token.line > botline then |           set_mark(token, string.format('@lsp.type.%s.%s', token.type, ft), 0) | ||||||
|           break |           for modifier, _ in pairs(token.modifiers) do | ||||||
|         end |             set_mark(token, string.format('@lsp.mod.%s.%s', modifier, ft), 1) | ||||||
|  |             set_mark(token, string.format('@lsp.typemod.%s.%s.%s', token.type, modifier, ft), 2) | ||||||
|         if not token.extmark_added then |  | ||||||
|           -- `strict = false` is necessary here for the 1% of cases where the |  | ||||||
|           -- current result doesn't actually match the buffer contents. Some |  | ||||||
|           -- LSP servers can respond with stale tokens on requests if they are |  | ||||||
|           -- still processing changes from a didChange notification. |  | ||||||
|           -- |  | ||||||
|           -- LSP servers that do this _should_ follow up known stale responses |  | ||||||
|           -- with a refresh notification once they've finished processing the |  | ||||||
|           -- didChange notification, which would re-synchronize the tokens from |  | ||||||
|           -- our end. |  | ||||||
|           -- |  | ||||||
|           -- The server I know of that does this is clangd when the preamble of |  | ||||||
|           -- a file changes and the token request is processed with a stale |  | ||||||
|           -- preamble while the new one is still being built. Once the preamble |  | ||||||
|           -- finishes, clangd sends a refresh request which lets the client |  | ||||||
|           -- re-synchronize the tokens. |  | ||||||
|           api.nvim_buf_set_extmark(self.bufnr, state.namespace, token.line, token.start_col, { |  | ||||||
|             hl_group = '@' .. token.type, |  | ||||||
|             end_col = token.end_col, |  | ||||||
|             priority = vim.highlight.priorities.semantic_tokens, |  | ||||||
|             strict = false, |  | ||||||
|           }) |  | ||||||
|  |  | ||||||
|           -- TODO(bfredl) use single extmark when hl_group supports table |  | ||||||
|           if #token.modifiers > 0 then |  | ||||||
|             for _, modifier in pairs(token.modifiers) do |  | ||||||
|               api.nvim_buf_set_extmark(self.bufnr, state.namespace, token.line, token.start_col, { |  | ||||||
|                 hl_group = '@' .. modifier, |  | ||||||
|                 end_col = token.end_col, |  | ||||||
|                 priority = vim.highlight.priorities.semantic_tokens + 1, |  | ||||||
|                 strict = false, |  | ||||||
|               }) |  | ||||||
|             end |  | ||||||
|           end |           end | ||||||
|  |           token.marked = true | ||||||
|  |  | ||||||
|           token.extmark_added = true |           api.nvim_exec_autocmds('LspTokenUpdate', { | ||||||
|  |             pattern = vim.api.nvim_buf_get_name(self.bufnr), | ||||||
|  |             modeline = false, | ||||||
|  |             data = { | ||||||
|  |               token = token, | ||||||
|  |               client_id = client_id, | ||||||
|  |             }, | ||||||
|  |           }) | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| @@ -588,7 +612,13 @@ end | |||||||
| ---@param row number|nil Position row (default cursor position) | ---@param row number|nil Position row (default cursor position) | ||||||
| ---@param col number|nil Position column (default cursor position) | ---@param col number|nil Position column (default cursor position) | ||||||
| --- | --- | ||||||
| ---@return table|nil (table|nil) List of tokens at position | ---@return table|nil (table|nil) List of tokens at position. Each token has | ||||||
|  | ---        the following fields: | ||||||
|  | ---        - line (number) line number, 0-based | ||||||
|  | ---        - start_col (number) start column, 0-based | ||||||
|  | ---        - end_col (number) end column, 0-based | ||||||
|  | ---        - type (string) token type as string, e.g. "variable" | ||||||
|  | ---        - modifiers (table) token modifiers as a set. E.g., { static = true, readonly = true } | ||||||
| function M.get_at_pos(bufnr, row, col) | function M.get_at_pos(bufnr, row, col) | ||||||
|   if bufnr == nil or bufnr == 0 then |   if bufnr == nil or bufnr == 0 then | ||||||
|     bufnr = api.nvim_get_current_buf() |     bufnr = api.nvim_get_current_buf() | ||||||
| @@ -608,7 +638,7 @@ function M.get_at_pos(bufnr, row, col) | |||||||
|   for client_id, client in pairs(highlighter.client_state) do |   for client_id, client in pairs(highlighter.client_state) do | ||||||
|     local highlights = client.current_result.highlights |     local highlights = client.current_result.highlights | ||||||
|     if highlights then |     if highlights then | ||||||
|       local idx = binary_search(highlights, row) |       local idx = lower_bound(highlights, row, 1, #highlights + 1) | ||||||
|       for i = idx, #highlights do |       for i = idx, #highlights do | ||||||
|         local token = highlights[i] |         local token = highlights[i] | ||||||
|  |  | ||||||
| @@ -631,23 +661,60 @@ end | |||||||
| --- Only has an effect if the buffer is currently active for semantic token | --- Only has an effect if the buffer is currently active for semantic token | ||||||
| --- highlighting (|vim.lsp.semantic_tokens.start()| has been called for it) | --- highlighting (|vim.lsp.semantic_tokens.start()| has been called for it) | ||||||
| --- | --- | ||||||
| ---@param bufnr (nil|number) default: current buffer | ---@param bufnr (number|nil) filter by buffer. All buffers if nil, current | ||||||
|  | ---       buffer if 0 | ||||||
| function M.force_refresh(bufnr) | function M.force_refresh(bufnr) | ||||||
|   vim.validate({ |   vim.validate({ | ||||||
|     bufnr = { bufnr, 'n', true }, |     bufnr = { bufnr, 'n', true }, | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   if bufnr == nil or bufnr == 0 then |   local buffers = bufnr == nil and vim.tbl_keys(STHighlighter.active) | ||||||
|     bufnr = api.nvim_get_current_buf() |     or bufnr == 0 and { api.nvim_get_current_buf() } | ||||||
|   end |     or { bufnr } | ||||||
|  |  | ||||||
|  |   for _, buffer in ipairs(buffers) do | ||||||
|  |     local highlighter = STHighlighter.active[buffer] | ||||||
|  |     if highlighter then | ||||||
|  |       highlighter:reset() | ||||||
|  |       highlighter:send_request() | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  |  | ||||||
|  | --- Highlight a semantic token. | ||||||
|  | --- | ||||||
|  | --- Apply an extmark with a given highlight group for a semantic token. The | ||||||
|  | --- mark will be deleted by the semantic token engine when appropriate; for | ||||||
|  | --- example, when the LSP sends updated tokens. This function is intended for | ||||||
|  | --- use inside |LspTokenUpdate| callbacks. | ||||||
|  | ---@param token (table) a semantic token, found as `args.data.token` in | ||||||
|  | ---       |LspTokenUpdate|. | ||||||
|  | ---@param bufnr (number) the buffer to highlight | ||||||
|  | ---@param client_id (number) The ID of the |vim.lsp.client| | ||||||
|  | ---@param hl_group (string) Highlight group name | ||||||
|  | ---@param opts (table|nil) Optional parameters. | ||||||
|  | ---       - priority: (number|nil) Priority for the applied extmark. Defaults | ||||||
|  | ---         to `vim.highlight.priorities.semantic_tokens + 3` | ||||||
|  | function M.highlight_token(token, bufnr, client_id, hl_group, opts) | ||||||
|   local highlighter = STHighlighter.active[bufnr] |   local highlighter = STHighlighter.active[bufnr] | ||||||
|   if not highlighter then |   if not highlighter then | ||||||
|     return |     return | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   highlighter:reset() |   local state = highlighter.client_state[client_id] | ||||||
|   highlighter:send_request() |   if not state then | ||||||
|  |     return | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   opts = opts or {} | ||||||
|  |   local priority = opts.priority or vim.highlight.priorities.semantic_tokens + 3 | ||||||
|  |  | ||||||
|  |   vim.api.nvim_buf_set_extmark(bufnr, state.namespace, token.line, token.start_col, { | ||||||
|  |     hl_group = hl_group, | ||||||
|  |     end_col = token.end_col, | ||||||
|  |     priority = priority, | ||||||
|  |     strict = false, | ||||||
|  |   }) | ||||||
| end | end | ||||||
|  |  | ||||||
| --- |lsp-handler| for the method `workspace/semanticTokens/refresh` | --- |lsp-handler| for the method `workspace/semanticTokens/refresh` | ||||||
|   | |||||||
| @@ -72,6 +72,7 @@ return { | |||||||
|     'InsertLeavePre',         -- just before leaving Insert mode |     'InsertLeavePre',         -- just before leaving Insert mode | ||||||
|     'LspAttach',              -- after an LSP client attaches to a buffer |     'LspAttach',              -- after an LSP client attaches to a buffer | ||||||
|     'LspDetach',              -- after an LSP client detaches from a buffer |     'LspDetach',              -- after an LSP client detaches from a buffer | ||||||
|  |     'LspTokenUpdate',         -- after a visible LSP token is updated | ||||||
|     'MenuPopup',              -- just before popup menu is displayed |     'MenuPopup',              -- just before popup menu is displayed | ||||||
|     'ModeChanged',            -- after changing the mode |     'ModeChanged',            -- after changing the mode | ||||||
|     'OptionSet',              -- after setting any option |     'OptionSet',              -- after setting any option | ||||||
| @@ -151,6 +152,7 @@ return { | |||||||
|     DiagnosticChanged=true, |     DiagnosticChanged=true, | ||||||
|     LspAttach=true, |     LspAttach=true, | ||||||
|     LspDetach=true, |     LspDetach=true, | ||||||
|  |     LspTokenUpdate=true, | ||||||
|     RecordingEnter=true, |     RecordingEnter=true, | ||||||
|     RecordingLeave=true, |     RecordingLeave=true, | ||||||
|     Signal=true, |     Signal=true, | ||||||
|   | |||||||
| @@ -270,16 +270,22 @@ static const char *highlight_init_both[] = { | |||||||
|   "default link @tag Tag", |   "default link @tag Tag", | ||||||
|  |  | ||||||
|   // LSP semantic tokens |   // LSP semantic tokens | ||||||
|   "default link @class Structure", |   "default link @lsp.type.class Structure", | ||||||
|   "default link @struct Structure", |   "default link @lsp.type.decorator Function", | ||||||
|   "default link @enum Type", |   "default link @lsp.type.enum Structure", | ||||||
|   "default link @enumMember Constant", |   "default link @lsp.type.enumMember Constant", | ||||||
|   "default link @event Identifier", |   "default link @lsp.type.function Function", | ||||||
|   "default link @interface Identifier", |   "default link @lsp.type.interface Structure", | ||||||
|   "default link @modifier Identifier", |   "default link @lsp.type.macro Macro", | ||||||
|   "default link @regexp SpecialChar", |   "default link @lsp.type.method Function", | ||||||
|   "default link @typeParameter Type", |   "default link @lsp.type.namespace Structure", | ||||||
|   "default link @decorator Identifier", |   "default link @lsp.type.parameter Identifier", | ||||||
|  |   "default link @lsp.type.property Identifier", | ||||||
|  |   "default link @lsp.type.struct Structure", | ||||||
|  |   "default link @lsp.type.type Type", | ||||||
|  |   "default link @lsp.type.typeParameter TypeDef", | ||||||
|  |   "default link @lsp.type.variable Identifier", | ||||||
|  |  | ||||||
|   NULL |   NULL | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -37,10 +37,12 @@ describe('semantic token highlighting', function() | |||||||
|       [6] = { foreground = Screen.colors.Blue1 }; |       [6] = { foreground = Screen.colors.Blue1 }; | ||||||
|       [7] = { bold = true, foreground = Screen.colors.DarkCyan }; |       [7] = { bold = true, foreground = Screen.colors.DarkCyan }; | ||||||
|       [8] = { bold = true, foreground = Screen.colors.SlateBlue }; |       [8] = { bold = true, foreground = Screen.colors.SlateBlue }; | ||||||
|  |       [9] = { bold = true, foreground = tonumber('0x6a0dad') }; | ||||||
|     } |     } | ||||||
|     command([[ hi link @namespace Type ]]) |     command([[ hi link @lsp.type.namespace Type ]]) | ||||||
|     command([[ hi link @function Special ]]) |     command([[ hi link @lsp.type.function Special ]]) | ||||||
|     command([[ hi @declaration gui=bold ]]) |     command([[ hi link @lsp.type.comment Comment ]]) | ||||||
|  |     command([[ hi @lsp.mod.declaration gui=bold ]]) | ||||||
|   end) |   end) | ||||||
|  |  | ||||||
|   describe('general', function() |   describe('general', function() | ||||||
| @@ -129,6 +131,46 @@ describe('semantic token highlighting', function() | |||||||
|       ]] } |       ]] } | ||||||
|     end) |     end) | ||||||
|  |  | ||||||
|  |     it('use LspTokenUpdate and highlight_token', function() | ||||||
|  |       exec_lua([[ | ||||||
|  |         vim.api.nvim_create_autocmd("LspTokenUpdate", { | ||||||
|  |           callback = function(args) | ||||||
|  |             local token = args.data.token | ||||||
|  |             if token.type == "function" and token.modifiers.declaration then | ||||||
|  |               vim.lsp.semantic_tokens.highlight_token( | ||||||
|  |                 token, args.buf, args.data.client_id, "Macro" | ||||||
|  |               ) | ||||||
|  |             end | ||||||
|  |           end, | ||||||
|  |         }) | ||||||
|  |         bufnr = vim.api.nvim_get_current_buf() | ||||||
|  |         vim.api.nvim_win_set_buf(0, bufnr) | ||||||
|  |         client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) | ||||||
|  |       ]]) | ||||||
|  |  | ||||||
|  |       insert(text) | ||||||
|  |  | ||||||
|  |       screen:expect { grid = [[ | ||||||
|  |         #include <iostream>                     | | ||||||
|  |                                                 | | ||||||
|  |         int {9:main}()                              | | ||||||
|  |         {                                       | | ||||||
|  |             int {7:x};                              | | ||||||
|  |         #ifdef {5:__cplusplus}                      | | ||||||
|  |             {4:std}::{2:cout} << {2:x} << "\n";             | | ||||||
|  |         {6:#else}                                   | | ||||||
|  |         {6:    printf("%d\n", x);}                  | | ||||||
|  |         {6:#endif}                                  | | ||||||
|  |         }                                       | | ||||||
|  |         ^}                                       | | ||||||
|  |         {1:~                                       }| | ||||||
|  |         {1:~                                       }| | ||||||
|  |         {1:~                                       }| | ||||||
|  |                                                 | | ||||||
|  |       ]] } | ||||||
|  |  | ||||||
|  |     end) | ||||||
|  |  | ||||||
|     it('buffer is unhighlighted when client is detached', function() |     it('buffer is unhighlighted when client is detached', function() | ||||||
|       exec_lua([[ |       exec_lua([[ | ||||||
|         bufnr = vim.api.nvim_get_current_buf() |         bufnr = vim.api.nvim_get_current_buf() | ||||||
| @@ -580,14 +622,11 @@ describe('semantic token highlighting', function() | |||||||
|         expected = { |         expected = { | ||||||
|           { |           { | ||||||
|             line = 0, |             line = 0, | ||||||
|             modifiers = { |             modifiers = { declaration = true, globalScope = true }, | ||||||
|               'declaration', |  | ||||||
|               'globalScope', |  | ||||||
|             }, |  | ||||||
|             start_col = 6, |             start_col = 6, | ||||||
|             end_col = 9, |             end_col = 9, | ||||||
|             type = 'variable', |             type = 'variable', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
| @@ -615,67 +654,67 @@ int main() | |||||||
|         expected = { |         expected = { | ||||||
|           { -- main |           { -- main | ||||||
|             line = 1, |             line = 1, | ||||||
|             modifiers = { 'declaration', 'globalScope' }, |             modifiers = { declaration = true, globalScope = true }, | ||||||
|             start_col = 4, |             start_col = 4, | ||||||
|             end_col = 8, |             end_col = 8, | ||||||
|             type = 'function', |             type = 'function', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { --  __cplusplus |           { --  __cplusplus | ||||||
|             line = 3, |             line = 3, | ||||||
|             modifiers = { 'globalScope' }, |             modifiers = { globalScope = true }, | ||||||
|             start_col = 9, |             start_col = 9, | ||||||
|             end_col = 20, |             end_col = 20, | ||||||
|             type = 'macro', |             type = 'macro', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { -- x |           { -- x | ||||||
|             line = 4, |             line = 4, | ||||||
|             modifiers = { 'declaration', 'readonly', 'functionScope' }, |             modifiers = { declaration = true, readonly = true, functionScope = true }, | ||||||
|             start_col = 12, |             start_col = 12, | ||||||
|             end_col = 13, |             end_col = 13, | ||||||
|             type = 'variable', |             type = 'variable', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { -- std |           { -- std | ||||||
|             line = 5, |             line = 5, | ||||||
|             modifiers = { 'defaultLibrary', 'globalScope' }, |             modifiers = { defaultLibrary = true, globalScope = true }, | ||||||
|             start_col = 2, |             start_col = 2, | ||||||
|             end_col = 5, |             end_col = 5, | ||||||
|             type = 'namespace', |             type = 'namespace', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { -- cout |           { -- cout | ||||||
|             line = 5, |             line = 5, | ||||||
|             modifiers = { 'defaultLibrary', 'globalScope' }, |             modifiers = { defaultLibrary = true, globalScope = true }, | ||||||
|             start_col = 7, |             start_col = 7, | ||||||
|             end_col = 11, |             end_col = 11, | ||||||
|             type = 'variable', |             type = 'variable', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { -- x |           { -- x | ||||||
|             line = 5, |             line = 5, | ||||||
|             modifiers = { 'readonly', 'functionScope' }, |             modifiers = { readonly = true, functionScope = true }, | ||||||
|             start_col = 15, |             start_col = 15, | ||||||
|             end_col = 16, |             end_col = 16, | ||||||
|             type = 'variable', |             type = 'variable', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { -- std |           { -- std | ||||||
|             line = 5, |             line = 5, | ||||||
|             modifiers = { 'defaultLibrary', 'globalScope' }, |             modifiers = { defaultLibrary = true, globalScope = true }, | ||||||
|             start_col = 20, |             start_col = 20, | ||||||
|             end_col = 23, |             end_col = 23, | ||||||
|             type = 'namespace', |             type = 'namespace', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { -- endl |           { -- endl | ||||||
|             line = 5, |             line = 5, | ||||||
|             modifiers = { 'defaultLibrary', 'globalScope' }, |             modifiers = { defaultLibrary = true, globalScope = true }, | ||||||
|             start_col = 25, |             start_col = 25, | ||||||
|             end_col = 29, |             end_col = 29, | ||||||
|             type = 'function', |             type = 'function', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { -- #else comment #endif |           { -- #else comment #endif | ||||||
|             line = 6, |             line = 6, | ||||||
| @@ -683,7 +722,7 @@ int main() | |||||||
|             start_col = 0, |             start_col = 0, | ||||||
|             end_col = 7, |             end_col = 7, | ||||||
|             type = 'comment', |             type = 'comment', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 7, |             line = 7, | ||||||
| @@ -691,7 +730,7 @@ int main() | |||||||
|             start_col = 0, |             start_col = 0, | ||||||
|             end_col = 11, |             end_col = 11, | ||||||
|             type = 'comment', |             type = 'comment', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 8, |             line = 8, | ||||||
| @@ -699,7 +738,7 @@ int main() | |||||||
|             start_col = 0, |             start_col = 0, | ||||||
|             end_col = 8, |             end_col = 8, | ||||||
|             type = 'comment', |             type = 'comment', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
| @@ -724,23 +763,23 @@ b = "as"]], | |||||||
|             start_col = 0, |             start_col = 0, | ||||||
|             end_col = 10, |             end_col = 10, | ||||||
|             type = 'comment', -- comment |             type = 'comment', -- comment | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 1, |             line = 1, | ||||||
|             modifiers = { 'declaration' }, -- a |             modifiers = { declaration = true }, -- a | ||||||
|             start_col = 6, |             start_col = 6, | ||||||
|             end_col = 7, |             end_col = 7, | ||||||
|             type = 'variable', |             type = 'variable', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 2, |             line = 2, | ||||||
|             modifiers = { 'static' }, -- b (global) |             modifiers = { static = true }, -- b (global) | ||||||
|             start_col = 0, |             start_col = 0, | ||||||
|             end_col = 1, |             end_col = 1, | ||||||
|             type = 'variable', |             type = 'variable', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
| @@ -770,7 +809,7 @@ b = "as"]], | |||||||
|             start_col = 0, |             start_col = 0, | ||||||
|             end_col = 3, -- pub |             end_col = 3, -- pub | ||||||
|             type = 'keyword', |             type = 'keyword', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 0, |             line = 0, | ||||||
| @@ -778,15 +817,15 @@ b = "as"]], | |||||||
|             start_col = 4, |             start_col = 4, | ||||||
|             end_col = 6, -- fn |             end_col = 6, -- fn | ||||||
|             type = 'keyword', |             type = 'keyword', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 0, |             line = 0, | ||||||
|             modifiers = { 'declaration', 'public' }, |             modifiers = { declaration = true, public = true }, | ||||||
|             start_col = 7, |             start_col = 7, | ||||||
|             end_col = 11, -- main |             end_col = 11, -- main | ||||||
|             type = 'function', |             type = 'function', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 0, |             line = 0, | ||||||
| @@ -794,7 +833,7 @@ b = "as"]], | |||||||
|             start_col = 11, |             start_col = 11, | ||||||
|             end_col = 12, |             end_col = 12, | ||||||
|             type = 'parenthesis', |             type = 'parenthesis', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 0, |             line = 0, | ||||||
| @@ -802,7 +841,7 @@ b = "as"]], | |||||||
|             start_col = 12, |             start_col = 12, | ||||||
|             end_col = 13, |             end_col = 13, | ||||||
|             type = 'parenthesis', |             type = 'parenthesis', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 0, |             line = 0, | ||||||
| @@ -810,15 +849,15 @@ b = "as"]], | |||||||
|             start_col = 14, |             start_col = 14, | ||||||
|             end_col = 15, |             end_col = 15, | ||||||
|             type = 'brace', |             type = 'brace', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 1, |             line = 1, | ||||||
|             modifiers = { 'controlFlow' }, |             modifiers = { controlFlow = true }, | ||||||
|             start_col = 4, |             start_col = 4, | ||||||
|             end_col = 9, -- break |             end_col = 9, -- break | ||||||
|             type = 'keyword', |             type = 'keyword', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 1, |             line = 1, | ||||||
| @@ -826,7 +865,7 @@ b = "as"]], | |||||||
|             start_col = 10, |             start_col = 10, | ||||||
|             end_col = 13, -- rust |             end_col = 13, -- rust | ||||||
|             type = 'unresolvedReference', |             type = 'unresolvedReference', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 1, |             line = 1, | ||||||
| @@ -834,15 +873,15 @@ b = "as"]], | |||||||
|             start_col = 13, |             start_col = 13, | ||||||
|             end_col = 13, |             end_col = 13, | ||||||
|             type = 'semicolon', |             type = 'semicolon', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 2, |             line = 2, | ||||||
|             modifiers = { 'documentation' }, |             modifiers = { documentation = true }, | ||||||
|             start_col = 4, |             start_col = 4, | ||||||
|             end_col = 11, |             end_col = 11, | ||||||
|             type = 'comment', -- /// what? |             type = 'comment', -- /// what? | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 3, |             line = 3, | ||||||
| @@ -850,7 +889,7 @@ b = "as"]], | |||||||
|             start_col = 0, |             start_col = 0, | ||||||
|             end_col = 1, |             end_col = 1, | ||||||
|             type = 'brace', |             type = 'brace', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
| @@ -908,26 +947,26 @@ b = "as"]], | |||||||
|           { |           { | ||||||
|             line = 0, |             line = 0, | ||||||
|             modifiers = { |             modifiers = { | ||||||
|               'declaration', |               declaration = true, | ||||||
|               'globalScope', |               globalScope = true, | ||||||
|             }, |             }, | ||||||
|             start_col = 6, |             start_col = 6, | ||||||
|             end_col = 9, |             end_col = 9, | ||||||
|             type = 'variable', |             type = 'variable', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         expected2 = { |         expected2 = { | ||||||
|           { |           { | ||||||
|             line = 1, |             line = 1, | ||||||
|             modifiers = { |             modifiers = { | ||||||
|               'declaration', |               declaration = true, | ||||||
|               'globalScope', |               globalScope = true, | ||||||
|             }, |             }, | ||||||
|             start_col = 6, |             start_col = 6, | ||||||
|             end_col = 9, |             end_col = 9, | ||||||
|             type = 'variable', |             type = 'variable', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         expected_screen1 = function() |         expected_screen1 = function() | ||||||
| @@ -1018,55 +1057,55 @@ int main() | |||||||
|             line = 2, |             line = 2, | ||||||
|             start_col = 4, |             start_col = 4, | ||||||
|             end_col = 8, |             end_col = 8, | ||||||
|             modifiers = { 'declaration', 'globalScope' }, |             modifiers = { declaration = true, globalScope = true }, | ||||||
|             type = 'function', |             type = 'function', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 4, |             line = 4, | ||||||
|             start_col = 8, |             start_col = 8, | ||||||
|             end_col = 9, |             end_col = 9, | ||||||
|             modifiers = { 'declaration', 'functionScope' }, |             modifiers = { declaration = true, functionScope = true }, | ||||||
|             type = 'variable', |             type = 'variable', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 5, |             line = 5, | ||||||
|             start_col = 7, |             start_col = 7, | ||||||
|             end_col = 18, |             end_col = 18, | ||||||
|             modifiers = { 'globalScope' }, |             modifiers = { globalScope = true }, | ||||||
|             type = 'macro', |             type = 'macro', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 6, |             line = 6, | ||||||
|             start_col = 4, |             start_col = 4, | ||||||
|             end_col = 7, |             end_col = 7, | ||||||
|             modifiers = { 'defaultLibrary', 'globalScope' }, |             modifiers = { defaultLibrary = true, globalScope = true }, | ||||||
|             type = 'namespace', |             type = 'namespace', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 6, |             line = 6, | ||||||
|             start_col = 9, |             start_col = 9, | ||||||
|             end_col = 13, |             end_col = 13, | ||||||
|             modifiers = { 'defaultLibrary', 'globalScope' }, |             modifiers = { defaultLibrary = true, globalScope = true }, | ||||||
|             type = 'variable', |             type = 'variable', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 6, |             line = 6, | ||||||
|             start_col = 17, |             start_col = 17, | ||||||
|             end_col = 18, |             end_col = 18, | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|             modifiers = { 'functionScope' }, |             modifiers = { functionScope = true }, | ||||||
|             type = 'variable', |             type = 'variable', | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 7, |             line = 7, | ||||||
|             start_col = 0, |             start_col = 0, | ||||||
|             end_col = 5, |             end_col = 5, | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|             modifiers = {}, |             modifiers = {}, | ||||||
|             type = 'comment', |             type = 'comment', | ||||||
|           }, |           }, | ||||||
| @@ -1076,7 +1115,7 @@ int main() | |||||||
|             modifiers = {}, |             modifiers = {}, | ||||||
|             start_col = 0, |             start_col = 0, | ||||||
|             type = 'comment', |             type = 'comment', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 9, |             line = 9, | ||||||
| @@ -1084,7 +1123,7 @@ int main() | |||||||
|             end_col = 6, |             end_col = 6, | ||||||
|             modifiers = {}, |             modifiers = {}, | ||||||
|             type = 'comment', |             type = 'comment', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         expected2 = { |         expected2 = { | ||||||
| @@ -1092,63 +1131,63 @@ int main() | |||||||
|             line = 2, |             line = 2, | ||||||
|             start_col = 4, |             start_col = 4, | ||||||
|             end_col = 8, |             end_col = 8, | ||||||
|             modifiers = { 'declaration', 'globalScope' }, |             modifiers = { declaration = true, globalScope = true }, | ||||||
|             type = 'function', |             type = 'function', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 4, |             line = 4, | ||||||
|             start_col = 8, |             start_col = 8, | ||||||
|             end_col = 9, |             end_col = 9, | ||||||
|             modifiers = { 'declaration', 'globalScope' }, |             modifiers = { declaration = true, globalScope = true }, | ||||||
|             type = 'function', |             type = 'function', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 5, |             line = 5, | ||||||
|             end_col = 12, |             end_col = 12, | ||||||
|             start_col = 11, |             start_col = 11, | ||||||
|             modifiers = { 'declaration', 'functionScope' }, |             modifiers = { declaration = true, functionScope = true }, | ||||||
|             type = 'variable', |             type = 'variable', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 6, |             line = 6, | ||||||
|             start_col = 7, |             start_col = 7, | ||||||
|             end_col = 18, |             end_col = 18, | ||||||
|             modifiers = { 'globalScope' }, |             modifiers = { globalScope = true }, | ||||||
|             type = 'macro', |             type = 'macro', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 7, |             line = 7, | ||||||
|             start_col = 4, |             start_col = 4, | ||||||
|             end_col = 7, |             end_col = 7, | ||||||
|             modifiers = { 'defaultLibrary', 'globalScope' }, |             modifiers = { defaultLibrary = true, globalScope = true }, | ||||||
|             type = 'namespace', |             type = 'namespace', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 7, |             line = 7, | ||||||
|             start_col = 9, |             start_col = 9, | ||||||
|             end_col = 13, |             end_col = 13, | ||||||
|             modifiers = { 'defaultLibrary', 'globalScope' }, |             modifiers = { defaultLibrary = true, globalScope = true }, | ||||||
|             type = 'variable', |             type = 'variable', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 7, |             line = 7, | ||||||
|             start_col = 17, |             start_col = 17, | ||||||
|             end_col = 18, |             end_col = 18, | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|             modifiers = { 'globalScope' }, |             modifiers = { globalScope = true }, | ||||||
|             type = 'function', |             type = 'function', | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 8, |             line = 8, | ||||||
|             start_col = 0, |             start_col = 0, | ||||||
|             end_col = 5, |             end_col = 5, | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|             modifiers = {}, |             modifiers = {}, | ||||||
|             type = 'comment', |             type = 'comment', | ||||||
|           }, |           }, | ||||||
| @@ -1158,7 +1197,7 @@ int main() | |||||||
|             modifiers = {}, |             modifiers = {}, | ||||||
|             start_col = 0, |             start_col = 0, | ||||||
|             type = 'comment', |             type = 'comment', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             line = 10, |             line = 10, | ||||||
| @@ -1166,7 +1205,7 @@ int main() | |||||||
|             end_col = 6, |             end_col = 6, | ||||||
|             modifiers = {}, |             modifiers = {}, | ||||||
|             type = 'comment', |             type = 'comment', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         expected_screen1 = function() |         expected_screen1 = function() | ||||||
| @@ -1228,12 +1267,12 @@ int main() | |||||||
|           { |           { | ||||||
|             line = 0, |             line = 0, | ||||||
|             modifiers = { |             modifiers = { | ||||||
|               'declaration', |               declaration = true, | ||||||
|             }, |             }, | ||||||
|             start_col = 0, |             start_col = 0, | ||||||
|             end_col = 6, |             end_col = 6, | ||||||
|             type = 'variable', |             type = 'variable', | ||||||
|             extmark_added = true, |             marked = true, | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         expected2 = { |         expected2 = { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 swarn
					swarn