mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 03:18:16 +00:00
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:
@@ -77,15 +77,9 @@ The following new APIs and features were added.
|
|||||||
|
|
||||||
• Added |vim.keycode()| for translating keycodes in a string.
|
• Added |vim.keycode()| for translating keycodes in a string.
|
||||||
|
|
||||||
• Added |vim.treesitter.query.omnifunc()| for treesitter query files (set by
|
|
||||||
default).
|
|
||||||
|
|
||||||
• |'smoothscroll'| option to scroll by screen line rather than by text line
|
• |'smoothscroll'| option to scroll by screen line rather than by text line
|
||||||
when |'wrap'| is set.
|
when |'wrap'| is set.
|
||||||
|
|
||||||
• |Query:iter_matches()| now has the ability to set the maximum start depth
|
|
||||||
for matches.
|
|
||||||
|
|
||||||
• Added inline virtual text support to |nvim_buf_set_extmark()|.
|
• Added inline virtual text support to |nvim_buf_set_extmark()|.
|
||||||
|
|
||||||
• The terminal buffer now supports reflow (wrapped lines adapt when the buffer
|
• The terminal buffer now supports reflow (wrapped lines adapt when the buffer
|
||||||
@@ -120,8 +114,16 @@ The following new APIs and features were added.
|
|||||||
`client.supports_method(<method>)`. It considers both the dynamic
|
`client.supports_method(<method>)`. It considers both the dynamic
|
||||||
capabilities and static `server_capabilities`.
|
capabilities and static `server_capabilities`.
|
||||||
|
|
||||||
• Bundled treesitter parser and queries (highlight, folds) for Markdown,
|
• Treesitter
|
||||||
Python, and Bash.
|
• Bundled parsers and queries (highlight, folds) for Markdown, Python, and
|
||||||
|
Bash.
|
||||||
|
• Added |vim.treesitter.query.omnifunc()| for treesitter query files (set by
|
||||||
|
default).
|
||||||
|
• |Query:iter_matches()| now has the ability to set the maximum start depth
|
||||||
|
for matches.
|
||||||
|
• `@injection.language` now has smarter resolution and will now fallback to language aliases and/or attempt lower case variants of the text.
|
||||||
|
language via aliases (e.g., filetype) registered via
|
||||||
|
`vim.treesitter.language.register`.
|
||||||
|
|
||||||
• |vim.ui.open()| opens URIs using the system default handler (macOS `open`,
|
• |vim.ui.open()| opens URIs using the system default handler (macOS `open`,
|
||||||
Windows `explorer`, Linux `xdg-open`, etc.)
|
Windows `explorer`, Linux `xdg-open`, etc.)
|
||||||
|
@@ -311,19 +311,7 @@ The following directives are built in:
|
|||||||
{capture_id}
|
{capture_id}
|
||||||
|
|
||||||
Example: >query
|
Example: >query
|
||||||
(#inject-language! @_lang)
|
(#trim! @fold)
|
||||||
<
|
|
||||||
`inject-language!` *treesitter-directive-inject-language!*
|
|
||||||
Set the injection language from the node text, interpreted first as a
|
|
||||||
language name, then (if a parser is not found) a filetype. Custom
|
|
||||||
aliases can be added via |vim.treesitter.language.register()|. This
|
|
||||||
will set a new `metadata[capture_id]['injection.language']`.
|
|
||||||
|
|
||||||
Parameters: ~
|
|
||||||
{capture_id}
|
|
||||||
|
|
||||||
Example: >query
|
|
||||||
(#inject-language! @_lang)
|
|
||||||
<
|
<
|
||||||
Further directives can be added via |vim.treesitter.query.add_directive()|.
|
Further directives can be added via |vim.treesitter.query.add_directive()|.
|
||||||
Use |vim.treesitter.query.list_directives()| to list all available directives.
|
Use |vim.treesitter.query.list_directives()| to list all available directives.
|
||||||
|
@@ -635,6 +635,29 @@ local function add_injection(t, tree_index, pattern, lang, combined, ranges)
|
|||||||
table.insert(t[tree_index][lang][pattern].regions, ranges)
|
table.insert(t[tree_index][lang][pattern].regions, ranges)
|
||||||
end
|
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
|
---@private
|
||||||
--- Extract injections according to:
|
--- Extract injections according to:
|
||||||
--- https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
|
--- 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
|
for id, node in pairs(match) do
|
||||||
local name = self._injection_query.captures[id]
|
local name = self._injection_query.captures[id]
|
||||||
|
|
||||||
-- Lang should override any other language tag
|
-- Lang should override any other language tag
|
||||||
if name == 'injection.language' then
|
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
|
elseif name == 'injection.content' then
|
||||||
ranges = get_node_ranges(node, self._source, metadata[id], include_children)
|
ranges = get_node_ranges(node, self._source, metadata[id], include_children)
|
||||||
end
|
end
|
||||||
|
@@ -541,33 +541,6 @@ local directive_handlers = {
|
|||||||
metadata.range = { start_row, start_col, end_row, end_col }
|
metadata.range = { start_row, start_col, end_row, end_col }
|
||||||
end
|
end
|
||||||
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
|
--- Adds a new predicate to be used in queries
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
(fenced_code_block
|
(fenced_code_block
|
||||||
(info_string
|
(info_string
|
||||||
(language) @_lang)
|
(language) @injection.language)
|
||||||
(code_fence_content) @injection.content
|
(code_fence_content) @injection.content)
|
||||||
(#inject-language! @_lang))
|
|
||||||
|
|
||||||
((html_block) @injection.content
|
((html_block) @injection.content
|
||||||
(#set! injection.language "html")
|
(#set! injection.language "html")
|
||||||
|
@@ -783,7 +783,7 @@ int x = INT_MAX;
|
|||||||
return list
|
return list
|
||||||
]]
|
]]
|
||||||
|
|
||||||
eq({ 'gsub!', 'inject-language!', 'offset!', 'set!', 'trim!' }, res_list)
|
eq({ 'gsub!', 'offset!', 'set!', 'trim!' }, res_list)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
Reference in New Issue
Block a user