feat(treesitter): add injection language fallback (#24659)

* feat(treesitter): add injection language fallback

Problem: injection languages are often specified via aliases (e.g.,
filetype or in upper case), requiring custom directives.

Solution: include lookup logic (try as parser name, then filetype, then
lowercase) in LanguageTree itself and remove `#inject-language`
directive.

Co-authored-by: Lewis Russell <me@lewisr.dev>
This commit is contained in:
Christian Clason
2023-08-11 17:05:17 +02:00
committed by GitHub
parent 72e64a1afe
commit 31c4ed26bc
6 changed files with 39 additions and 54 deletions

View File

@@ -635,6 +635,29 @@ local function add_injection(t, tree_index, pattern, lang, combined, ranges)
table.insert(t[tree_index][lang][pattern].regions, ranges)
end
-- TODO(clason): replace by refactored `ts.has_parser` API (without registering)
---@param lang string parser name
---@return boolean # true if parser for {lang} exists on rtp
local has_parser = function(lang)
return vim._ts_has_language(lang)
or #vim.api.nvim_get_runtime_file('parser/' .. lang .. '.*', false) > 0
end
--- Return parser name for language (if exists) or filetype (if registered and exists)
---
---@param alias string language or filetype name
---@return string? # resolved parser name
local function resolve_lang(alias)
if has_parser(alias) then
return alias
end
local lang = vim.treesitter.language.get_lang(alias)
if lang and has_parser(lang) then
return lang
end
end
---@private
--- Extract injections according to:
--- https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
@@ -649,10 +672,10 @@ function LanguageTree:_get_injection(match, metadata)
for id, node in pairs(match) do
local name = self._injection_query.captures[id]
-- Lang should override any other language tag
if name == 'injection.language' then
lang = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] })
local text = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] })
lang = resolve_lang(text) or resolve_lang(text:lower())
elseif name == 'injection.content' then
ranges = get_node_ranges(node, self._source, metadata[id], include_children)
end

View File

@@ -541,33 +541,6 @@ local directive_handlers = {
metadata.range = { start_row, start_col, end_row, end_col }
end
end,
-- Set injection language from node text, interpreted first as language and then as filetype
-- Example: (#inject-language! @_lang)
['inject-language!'] = function(match, _, bufnr, pred, metadata)
local id = pred[2]
local node = match[id]
if not node then
return
end
-- TODO(clason): replace by refactored `ts.has_parser` API
local has_parser = function(lang)
return vim._ts_has_language(lang)
or #vim.api.nvim_get_runtime_file('parser/' .. lang .. '.*', false) > 0
end
local alias = vim.treesitter.get_node_text(node, bufnr, { metadata = metadata[id] })
if not alias then
return
elseif has_parser(alias) then
metadata['injection.language'] = alias
else
local lang = vim.treesitter.language.get_lang(alias)
if lang and has_parser(lang) then
metadata['injection.language'] = lang
end
end
end,
}
--- Adds a new predicate to be used in queries