mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	fix(treesitter): highlight injections properly
`on_line_impl` doesn't highlight single lines, so using pattern indexes to offset priority doesn't work.
This commit is contained in:
		 Lewis Russell
					Lewis Russell
				
			
				
					committed by
					
						 Lewis Russell
						Lewis Russell
					
				
			
			
				
	
			
			
			 Lewis Russell
						Lewis Russell
					
				
			
						parent
						
							274e414c94
						
					
				
				
					commit
					12faaf40f4
				
			| @@ -57,6 +57,7 @@ end | ||||
| ---@field next_row integer | ||||
| ---@field iter vim.treesitter.highlighter.Iter? | ||||
| ---@field highlighter_query vim.treesitter.highlighter.Query | ||||
| ---@field level integer Injection level | ||||
|  | ||||
| ---@nodoc | ||||
| ---@class vim.treesitter.highlighter | ||||
| @@ -192,12 +193,20 @@ function TSHighlighter:prepare_highlight_states(srow, erow) | ||||
|       return | ||||
|     end | ||||
|  | ||||
|     local level = 0 | ||||
|     local t = tree | ||||
|     while t do | ||||
|       t = t:parent() | ||||
|       level = level + 1 | ||||
|     end | ||||
|  | ||||
|     -- _highlight_states should be a list so that the highlights are added in the same order as | ||||
|     -- for_each_tree traversal. This ensures that parents' highlight don't override children's. | ||||
|     table.insert(self._highlight_states, { | ||||
|       tstree = tstree, | ||||
|       next_row = 0, | ||||
|       iter = nil, | ||||
|       level = level, | ||||
|       highlighter_query = highlighter_query, | ||||
|     }) | ||||
|   end) | ||||
| @@ -248,14 +257,10 @@ end | ||||
| ---@param line integer | ||||
| ---@param is_spell_nav boolean | ||||
| local function on_line_impl(self, buf, line, is_spell_nav) | ||||
|   -- Track the maximum pattern index encountered in each tree. For subsequent | ||||
|   -- trees, the subpriority passed to nvim_buf_set_extmark is offset by the | ||||
|   -- largest pattern index from the prior tree. This ensures that extmarks | ||||
|   -- from subsequent trees always appear "on top of" extmarks from previous | ||||
|   -- trees (e.g. injections should always appear over base highlights). | ||||
|   local pattern_offset = 0 | ||||
|  | ||||
|   self:for_each_highlight_state(function(state) | ||||
|     -- Use the injection level to offset the subpriority passed to nvim_buf_set_extmark | ||||
|     -- so injections always appear over base highlights. | ||||
|     local pattern_offset = state.level * 1000 | ||||
|     local root_node = state.tstree:root() | ||||
|     local root_start_row, _, root_end_row, _ = root_node:range() | ||||
|  | ||||
| @@ -270,14 +275,9 @@ local function on_line_impl(self, buf, line, is_spell_nav) | ||||
|         :iter_matches(root_node, self.bufnr, line, root_end_row + 1, { all = true }) | ||||
|     end | ||||
|  | ||||
|     local max_pattern_index = 0 | ||||
|     while line >= state.next_row do | ||||
|       local pattern, match, metadata = state.iter() | ||||
|  | ||||
|       if pattern and pattern > max_pattern_index then | ||||
|         max_pattern_index = pattern | ||||
|       end | ||||
|  | ||||
|       if not match then | ||||
|         state.next_row = root_end_row + 1 | ||||
|       end | ||||
| @@ -343,8 +343,6 @@ local function on_line_impl(self, buf, line, is_spell_nav) | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     pattern_offset = pattern_offset + max_pattern_index | ||||
|   end) | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -81,7 +81,7 @@ local TSCallbackNames = { | ||||
| ---List of regions this tree should manage and parse. If nil then regions are | ||||
| ---taken from _trees. This is mostly a short-lived cache for included_regions() | ||||
| ---@field private _lang string Language name | ||||
| ---@field private _parent_lang? string Parent language name | ||||
| ---@field private _parent? vim.treesitter.LanguageTree Parent LanguageTree | ||||
| ---@field private _source (integer|string) Buffer or string to parse | ||||
| ---@field private _trees table<integer, TSTree> Reference to parsed tree (one for each language). | ||||
| ---Each key is the index of region, which is synced with _regions and _valid. | ||||
| @@ -106,9 +106,8 @@ LanguageTree.__index = LanguageTree | ||||
| ---@param source (integer|string) Buffer or text string to parse | ||||
| ---@param lang string Root language of this tree | ||||
| ---@param opts vim.treesitter.LanguageTree.new.Opts? | ||||
| ---@param parent_lang? string Parent language name of this tree | ||||
| ---@return vim.treesitter.LanguageTree parser object | ||||
| function LanguageTree.new(source, lang, opts, parent_lang) | ||||
| function LanguageTree.new(source, lang, opts) | ||||
|   language.add(lang) | ||||
|   opts = opts or {} | ||||
|  | ||||
| @@ -122,7 +121,6 @@ function LanguageTree.new(source, lang, opts, parent_lang) | ||||
|   local self = { | ||||
|     _source = source, | ||||
|     _lang = lang, | ||||
|     _parent_lang = parent_lang, | ||||
|     _children = {}, | ||||
|     _trees = {}, | ||||
|     _opts = opts, | ||||
| @@ -505,19 +503,25 @@ function LanguageTree:add_child(lang) | ||||
|     self:remove_child(lang) | ||||
|   end | ||||
|  | ||||
|   local child = LanguageTree.new(self._source, lang, self._opts, self:lang()) | ||||
|   local child = LanguageTree.new(self._source, lang, self._opts) | ||||
|  | ||||
|   -- Inherit recursive callbacks | ||||
|   for nm, cb in pairs(self._callbacks_rec) do | ||||
|     vim.list_extend(child._callbacks_rec[nm], cb) | ||||
|   end | ||||
|  | ||||
|   child._parent = self | ||||
|   self._children[lang] = child | ||||
|   self:_do_callback('child_added', self._children[lang]) | ||||
|  | ||||
|   return self._children[lang] | ||||
| end | ||||
|  | ||||
| --- @package | ||||
| function LanguageTree:parent() | ||||
|   return self._parent | ||||
| end | ||||
|  | ||||
| --- Removes a child language from this |LanguageTree|. | ||||
| --- | ||||
| ---@private | ||||
| @@ -792,7 +796,7 @@ function LanguageTree:_get_injection(match, metadata) | ||||
|   local combined = metadata['injection.combined'] ~= nil | ||||
|   local injection_lang = metadata['injection.language'] --[[@as string?]] | ||||
|   local lang = metadata['injection.self'] ~= nil and self:lang() | ||||
|     or metadata['injection.parent'] ~= nil and self._parent_lang | ||||
|     or metadata['injection.parent'] ~= nil and self._parent | ||||
|     or (injection_lang and resolve_lang(injection_lang)) | ||||
|   local include_children = metadata['injection.include-children'] ~= nil | ||||
|  | ||||
|   | ||||
| @@ -867,6 +867,40 @@ describe('treesitter highlighting (help)', function() | ||||
|     ]], | ||||
|     } | ||||
|   end) | ||||
|  | ||||
|   it('correctly redraws injections subpriorities', function() | ||||
|     -- The top level string node will be highlighted first | ||||
|     -- with an extmark spanning multiple lines. | ||||
|     -- When the next line is drawn, which includes an injection, | ||||
|     -- make sure the highlight appears above the base tree highlight | ||||
|  | ||||
|     insert([=[ | ||||
|     local s = [[ | ||||
|       local also = lua | ||||
|     ]] | ||||
|     ]=]) | ||||
|  | ||||
|     exec_lua [[ | ||||
|       parser = vim.treesitter.get_parser(0, "lua", { | ||||
|         injections = { | ||||
|           lua = '(string content: (_) @injection.content (#set! injection.language lua))' | ||||
|         } | ||||
|       }) | ||||
|  | ||||
|       vim.treesitter.highlighter.new(parser) | ||||
|     ]] | ||||
|  | ||||
|     screen:expect { | ||||
|       grid = [=[ | ||||
|       {3:local} {4:s} {3:=} {5:[[}                            | | ||||
|       {5:  }{3:local}{5: }{4:also}{5: }{3:=}{5: }{4:lua}                      | | ||||
|       {5:]]}                                      | | ||||
|       ^                                        | | ||||
|       {2:~                                       }| | ||||
|                                               | | ||||
|     ]=], | ||||
|     } | ||||
|   end) | ||||
| end) | ||||
|  | ||||
| describe('treesitter highlighting (nested injections)', function() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user