1

Regenerate nvim config

This commit is contained in:
2024-06-02 03:29:20 +02:00
parent 75eea0c030
commit ef2e28883d
5576 changed files with 604886 additions and 503 deletions

View File

@ -0,0 +1,107 @@
--[[
Copyright 2023 Alejandro "HiPhish" Sanchez
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--]]
local function get_nested(table, index, key)
local result
-- 1. User setting for file type
if vim.g.rainbow_delimiters and vim.g.rainbow_delimiters[index] then
result = rawget(vim.g.rainbow_delimiters[index], key)
end
if result ~= nil then return result end
-- 2. User setting for fallback
if vim.g.rainbow_delimiters and vim.g.rainbow_delimiters[index] then
result = rawget(vim.g.rainbow_delimiters[index], '')
end
if result ~= nil then return result end
-- 3. Default setting
result = rawget(table, key)
if result ~= nil then return result end
result = require('rainbow-delimiters.default')[index][key]
return result
end
---Plugin settings lookup table. This table is only used for looking up
---values. Set `g:rainbow_delimiters` to change the values.
local M = {
query = setmetatable({}, {
__index = function(table, key)
return get_nested(table, 'query', key)
end
}),
strategy = setmetatable({}, {
__index = function(table, key)
return get_nested(table, 'strategy', key)
end
}),
priority = setmetatable({}, {
__index = function(table, key)
return get_nested(table, 'priority', key)
end
}),
log = setmetatable({}, {
__index = function(table, key)
return get_nested(table, 'log', key)
end
}),
enabled_for = function(lang)
local conf = vim.g.rainbow_delimiters
if not conf then return true end
local whitelist = conf.whitelist
local blacklist = conf.blacklist
if whitelist then
for _, v in ipairs(whitelist) do
if v == lang then return true end
end
return false
end
if blacklist then
for _, v in ipairs(blacklist) do
if v == lang then return false end
end
return true
end
return true
end
}
setmetatable(M, {
__index = function(table, key)
if key == 'highlight' then
local highlight
if vim.g.rainbow_delimiters then
highlight = rawget(vim.g.rainbow_delimiters, 'highlight')
end
if highlight and #highlight > 0 then return highlight end
highlight = require('rainbow-delimiters.default').highlight
return highlight
end
return rawget(table, key)
end,
})
return M
-- vim:tw=79:ts=4:sw=4:noet:

View File

@ -0,0 +1,74 @@
--[[
Copyright 2023 Alejandro "HiPhish" Sanchez
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--]]
---Default plugin configuration.
---@type rainbow_delimiters.config
local M = {
---Query names by file type
query = {
[''] = 'rainbow-delimiters',
javascript = 'rainbow-delimiters-react'
},
---Highlight strategies by file type
strategy = {
[''] = require 'rainbow-delimiters.strategy.global',
},
priority = {
[''] = 110,
},
---Event logging settings
log = {
---Log level of the module, see `:h log_levels`.
level = vim.log.levels.WARN,
---File name of the log file
file = vim.fn.stdpath('log') .. '/rainbow-delimiters.log',
},
-- Highlight groups in order of display
highlight = {
-- The colours are intentionally not in the usual order to make
-- the contrast between them stronger
'RainbowDelimiterRed',
'RainbowDelimiterYellow',
'RainbowDelimiterBlue',
'RainbowDelimiterOrange',
'RainbowDelimiterGreen',
'RainbowDelimiterViolet',
'RainbowDelimiterCyan',
}
}
---If the key does not exist in the table fall back on the empty string as
---key.
local function get_with_fallback(table, key)
return rawget(table, key) or rawget(table, '')
end
setmetatable(M.query, {
__index = get_with_fallback,
})
setmetatable(M.strategy, {
__index = get_with_fallback,
})
setmetatable(M.priority, {
__index = get_with_fallback,
})
return M
-- vim:tw=79:ts=4:sw=4:noet:

View File

@ -0,0 +1,284 @@
---Health check module.
local M = {}
-- In Neovim 0.10 the following functions have been renamed
local start = vim.health.start or vim.health.report_start
local ok = vim.health.ok or vim.health.report_ok
local info = vim.health.info or vim.health.report_info
local warn = vim.health.warn or vim.health.report_warn
local error = vim.health.error or vim.health.report_error
local filewritable = vim.fn.filewritable
local fnamemodify = vim.fn.fnamemodify
local STRATEGY_ADVICE = "See :h rb-delimiters-strategy for the strategy protocol"
local QUERY_ADVICE = "See :h rb-delimiters-query for included standard queries."
local HLGROUP_ADVICE = "Consecutive highlight groups make delimiter levels indistinguishable, use another highlight group."
local SCHEMA_ADVICE = "This might be a typo, see :h g:rainbow_delimiters for valid entries."
---Specification of valid options. The key is the name of an option, the value
---is either true (no further validation) or a table containing the nested
---schema for the option
local schema = {
strategy = true,
query = true,
highlight = true,
priority = true,
blacklist = true,
whitelist = true,
log = {level = true, file = true},
}
---Check whether there is a parser installed for the given language.
---@param lang string
---@return boolean
local function check_parser_installed(lang)
local success = pcall(vim.treesitter.language.inspect, lang)
return success
end
---Check whether the strategy is a valid strategy.
---
---This is not a 100% reliable check; we only test the type of the argument and
---whether the table has the correct fields, but not what the callback
---functions actually do.
---@param strategy rainbow_delimiters.strategy | fun(bufnr: integer): rainbow_delimiters.strategy?
---@return boolean
local function check_strategy(strategy)
if type(strategy) == 'function' then
local finfo = debug.getinfo(strategy)
return finfo.nparams == 0 or finfo.nparams == 1
end
if type(strategy) == 'table' then
if type(strategy.on_attach) ~= 'function' then
return false
end
if type(strategy.on_detach) ~= 'function' then
return false
end
if type(strategy.on_reset) ~= 'function' then
return false
end
return true
end
return false
end
---Check whether the given query is defined for the given language.
---@param lang string
---@param name string | fun(bufnr: integer): string
---@return boolean
local function check_query(lang, name)
if type(name) == 'function' then
local finfo = debug.getinfo(name)
return finfo.nparams == 0 or finfo.nparams == 1
end
if type(name) == 'string' then
local query = vim.treesitter.query.get(lang, name)
return query ~= nil
end
return false
end
---Check whether the given priority is defined for the given language.
---@param priority integer | fun(bufnr: integer): integer
---@return boolean
local function check_priority(priority)
if type(priority) == 'function' then
local finfo = debug.getinfo(priority)
return finfo.nparams == 0 or finfo.nparams == 1
end
if type(priority) == 'number' then
return true
end
return false
end
---@param settings rainbow_delimiters.logging
local function check_logging(settings)
local level, file = settings.level, settings.file
if level then
-- Note: although the log level is an integer, Lua 5.1 only has the
-- number type
if type(level) ~= 'number' then
error('The log level must be a number', 'See :h vim.log.levels for valid log levels.')
else
ok('Valid log level.')
end
end
if file then
if type(file) ~= 'string' then
error('The log file path must be a string')
elseif filewritable(file) == 0 then
if filewritable(fnamemodify(file, ':h')) == 2 then
ok('Valid location for log file.')
else
local msg = string.format("Cannot write to file '%s'", file)
error(msg)
end
else
ok('Valid log file.')
end
end
local advice = "This might be a typo, see :h rb-delimiters-logging for valid entries."
for option in pairs(settings) do
if not schema.log[option] then
local msg = string.format("Unknown logging option '%s' in settings", option)
warn(msg, advice)
end
end
end
function M.check()
local settings = vim.g.rainbow_delimiters --[[@as rainbow_delimiters.config]]
if not settings then
return
info("No custom configuration; see :h rb-delimiters-setup for information.")
end
local whitelist = settings.whitelist
if whitelist then
start 'Parsers for whitelisted languages'
for _, lang in ipairs(whitelist) do
local success = check_parser_installed(lang)
if success then
local msg = string.format("Parser installed for '%s'", lang)
ok(msg)
else
local msg = string.format("No parser installed for '%s'", lang)
warn(msg)
end
end
end
local strategies = settings.strategy
if strategies then
start 'Custom strategies'
for lang, strategy in pairs(strategies) do
local has_strategy = check_strategy(strategy)
if lang == '' then
if has_strategy then
local msg = 'Valid custom default strategy.'
ok(msg)
else
local msg = 'Invalid custom default strategy.'
error(msg, STRATEGY_ADVICE)
end
else
local has_parser = check_parser_installed(lang)
if not has_parser then
local msg = string.format("No parser installed for '%s'", lang)
error(msg)
end
if not has_strategy then
local msg = string.format("Invalid custom strategy for '%s'", lang)
error(msg, STRATEGY_ADVICE)
end
if has_parser and has_strategy then
local msg = string.format("Valid custom strategy for '%s'.", lang)
ok(msg)
end
end
end
end
local queries = settings.query
if queries then
start 'Custom queries'
for lang, query in pairs(queries) do
if lang == '' then
if query ~= 'rainbow-delimiters' then
local msg = string.format(
"User-defined default query '%s'\
If you meant 'rainbow-delimiters' check for typos",
query
)
ok(msg)
else
local msg = "Valid custom default query"
ok(msg)
end
else
local has_lang = check_parser_installed(lang)
local has_query = check_query(lang, query)
if not has_lang then
local msg = string.format("No parser installed for '%s'.", lang)
warn(msg)
end
if not has_query then
local msg = string.format("No query named '%s' for '%s' found.", query, lang)
warn(msg, QUERY_ADVICE)
end
if has_lang and has_query then
local msg = string.format("Valid custom query for '%s'", lang)
ok(msg)
end
end
end
end
local priorities = settings.priority
if priorities then
start 'Custom priorities'
for lang, priority in pairs(priorities) do
local is_valid_prirority = check_priority(priority)
if lang == '' then
if is_valid_prirority then
local msg = "Valid custom default priority"
ok(msg)
else
local msg = "Invalid custom default priority"
error(msg)
end
else
if is_valid_prirority then
local msg = string.format("Valid custom priority for '%s'", lang)
ok(msg)
else
local msg = string.format("Invalid custom priority for '%s'", lang)
error(msg)
end
end
end
end
local hlgroups = settings.highlight
if hlgroups then
start 'Custom highlight groups'
local previous
for _, hlgroup in ipairs(hlgroups) do
local has_hlgroup = vim.fn.hlID(hlgroup) ~= 0
if has_hlgroup then
ok(string.format("Highlight group '%s' defined.", hlgroup))
else
error(string.format("Highlight group '%s' not defined.", hlgroup))
end
if previous and hlgroup == previous then
local msg = string.format("Consecutive highlight group '%s'", hlgroup)
warn(msg, HLGROUP_ADVICE)
end
previous = hlgroup
end
end
local logging = settings.log
if logging then
start 'Logging settings'
check_logging(logging)
end
for option in pairs(settings) do
if not schema[option] then
local msg = string.format("Unknown option '%s' in settings", option)
warn(msg, SCHEMA_ADVICE)
end
end
end
return M
-- vim:tw=79:ts=4:sw=4:noet:

View File

@ -0,0 +1,248 @@
--[[
Copyright 2023 Alejandro "HiPhish" Sanchez
Copyright 2020-2022 Chinmay Dalal
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--]]
local api = vim.api
local get_query = vim.treesitter.query.get
local get_parser = vim.treesitter.get_parser
local log = require 'rainbow-delimiters.log'
local config = require 'rainbow-delimiters.config'
local util = require 'rainbow-delimiters.util'
---[ Internal ]----------------------------------------------------------------
-- The following symbols should only be used internally. In particular, they
-- should not be used by strategies, or else our strategies are using
-- undocumented APIs.
---Private library of shared internal functions and variables.
local M = {}
M.enabled_for = config.enabled_for
---Per-language namespaces. This table instantiates namespaces on demand, i.e.
---a namespace won't exist until we first try to get it from the table.
M.nsids = setmetatable({}, {
__index = function(t, k)
local result = rawget(t, k)
if result == nil then
result = vim.api.nvim_create_namespace('')
rawset(t, k, result)
end
return result
end,
-- Note: this will only catch new indices, not assignment to an already
-- existing key
__newindex = function(_, _, _)
error('Table is immutable')
end
})
---Keeps track of attached buffers. The key is the buffer number and the value
---is a table of information about that buffer (e.g. language, strategy,
---query). This also makes sure we keep track of all parsers in active use to
---prevent them from being garbage-collected.
---@type table<integer, rainbow_delimiters.buffer_settings | false>
M.buffers = {}
---[ This stuff needs to be re-exported ]--------------------------------------
-- The following entries can be used in the public API as well.
---Fetches the query object for the given language from the settings. If a
---buffer number is given it will be used as the current buffer, otherwise the
---actual current buffer is used.
---
---@param lang string Name of the language to get the query for
---@param bufnr integer Use this buffer as the current buffer
---@return Query? query The query object
function M.get_query(lang, bufnr)
local name = config['query'][lang]
if type(name) == "function" then
name = name(bufnr)
end
local query = get_query(lang, name)
if not query then
log.debug('Query %s not found for %s', name, lang)
else
log.trace('Query %s found for %s', name, lang)
end
return query
end
---Apply highlighting to a single node.
---@param bufnr integer Buffer which contains the node
---@param lang string Language of the node (to group HL into namespaces)
---@param node table Node to highlight
---@param hlgroup string Name of the highlight group to apply.
function M.highlight(bufnr, lang, node, hlgroup)
-- range of the capture, zero-indexed
local startRow, startCol, endRow, endCol = node:range()
local start, finish = {startRow, startCol}, {endRow, endCol - 1}
local priority = config.priority[lang]
if type(priority) == "function" then
priority = priority(bufnr)
end
local opts = {
regtype = 'c',
inclusive = true,
priority = priority,
}
local nsid = M.nsids[lang]
if vim.api.nvim_buf_is_loaded(bufnr) then
vim.highlight.range(bufnr, nsid, hlgroup, start, finish, opts)
end
end
---Get the appropriate highlight group for the given level of nesting.
---@param i integer One-based index into the highlight groups
---@return string hlgroup Name of the highlight groups
function M.hlgroup_at(i)
local hlgroups = config.highlight
return hlgroups[(i - 1) % #hlgroups + 1]
end
---Clears the reserved Rainbow namespace.
---
---@param bufnr integer Number of the buffer for which to clear the namespace
---@param lang string
---@param line_start integer?
---@param line_end integer?
function M.clear_namespace(bufnr, lang, line_start, line_end)
local nsid = M.nsids[lang]
if vim.api.nvim_buf_is_valid(bufnr) then
vim.api.nvim_buf_clear_namespace(bufnr, nsid, line_start or 0, line_end or -1)
end
end
---Start rainbow highlighting for the given buffer
---@param bufnr integer
function M.attach(bufnr)
-- Rainbow delimiters was explicitly disabled for this buffer
if M.buffers[bufnr] == false then return end
local lang = vim.treesitter.language.get_lang(vim.bo[bufnr].ft)
if not lang then
log.trace('Cannot attach to buffer %d, no parser for %s', bufnr, lang)
return
end
log.trace('Attaching to buffer %d with language %s.', bufnr, lang)
local settings = M.buffers[bufnr]
if settings then
-- if M.buffers[bufnr].lang == lang then return end
-- TODO: If the language is the same reload the parser
if settings.lang == lang then
local parser = get_parser(bufnr, lang)
local strategy = settings.strategy
parser:invalidate(true)
parser:parse()
strategy.on_reset(bufnr, settings)
return
end
-- The file type of the buffer has changed, so we need to detach first
-- before we re-attach
M.detach(bufnr)
end
local parser
do
local success
success, parser = pcall(get_parser, bufnr, lang)
if not success then return end
end
local strategy
do
strategy = config.strategy[lang]
if type(strategy) == 'function' then
strategy = strategy(bufnr)
end
end
-- Intentionally abort; the user has explicitly disabled rainbow delimiters
-- for this buffer, usually by setting a strategy- or query function which
-- returned nil.
if not strategy then
log.warn('No strategy defined for %s', lang)
end
if not strategy or strategy == vim.NIL then return end
parser:register_cbs {
---@param bnr integer
on_detach = function(bnr)
if not M.buffers[bnr] then return end
M.detach(bufnr)
end,
---@param child LanguageTree
on_child_removed = function(child)
M.clear_namespace(bufnr, child:lang())
end,
}
settings = {
strategy = strategy,
parser = parser,
lang = lang
}
M.buffers[bufnr] = settings
-- For now we silently discard errors, but in the future we should log
-- them.
local success, error = pcall(strategy.on_attach, bufnr, settings)
if not success then
log.error('Error attaching strategy to buffer %d: %s', bufnr, error)
M.buffers[bufnr] = nil
end
end
---Start rainbow highlighting for the given buffer
---@param bufnr integer
function M.detach(bufnr)
log.trace('Detaching from buffer %d.', bufnr)
if not M.buffers[bufnr] then
return
end
local strategy = M.buffers[bufnr].strategy
local parser = M.buffers[bufnr].parser
-- Clear all the namespaces for each language
util.for_each_child(nil, parser:lang(), parser, function(_, lang)
M.clear_namespace(bufnr, lang)
end)
-- Finally release all resources the parser is holding on to
parser:destroy()
-- For now we silently discard errors, but in the future we should log
-- them.
local success, error = pcall(strategy.on_detach, bufnr)
if not success then
log.error('Error detaching strategy from buffer %d: %s', bufnr, error)
end
M.buffers[bufnr] = nil
end
return M
-- vim:tw=79:ts=4:sw=4:noet:

View File

@ -0,0 +1,103 @@
--[[
Copyright 2023 Alejandro "HiPhish" Sanchez
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--]]
---Logger module for rainbow delimiters. Logs any message whose log level is
---equal to or greater than the log level of the module.
local M = {}
local date = os.date
local levels = vim.log.levels
local config = require 'rainbow-delimiters.config'
---Reverse lookup table; maps a log level to its text label
local level_str = {}
for key, value in pairs(levels) do
level_str[value] = key
end
---Dynamically determines the module from which the log function was called.
---If it was called from somewhere else return the name of the plugin.
---@return string
local function get_module()
local module = debug.getinfo(4, 'S').source:match('^.+rainbow%-delimiters/(.+).lua$')
if not module then
return ''
end
return module:gsub('/', '.')
end
---@param file file*
---@param level integer
---@param module string
---@param message any
---@param ... any
local function write_log(file, level, module, message, ...)
local msg
local timestamp = date('%FT%H:%M%z')
if type(message) == 'function' then
msg = message()
else
msg = string.format(message, ...)
end
file:write(string.format('%s %s %s %s\n', timestamp, level, module, msg))
end
---@param level integer
---@param message any
---@param ... any
local function log(level, message, ...)
if level < config.log.level then return end
local file = io.open(config.log.file, 'a+')
-- Intentional: Silently discard the log if the log file cannot be opened
if not file then return end
-- Wrap inside a pcall to make sure the file gets closed even if an error
-- occurs
pcall(write_log, file, level_str[level], get_module(), message, ...)
file:close()
-- Should I also print the message?
end
---Log an error message
function M.error(message, ...)
log(levels.ERROR, message, ...)
end
---Log a warning message
function M.warn(message, ...)
log(levels.WARN, message, ...)
end
---Log a tracing message
function M.debug(message, ...)
log(levels.DEBUG, message, ...)
end
---Log a tracing message
function M.trace(message, ...)
log(levels.TRACE, message, ...)
end
---Log an info message
function M.info(message, ...)
log(levels.INFO, message, ...)
end
return M
-- vim:tw=79:ts=4:sw=4:noet:

View File

@ -0,0 +1,17 @@
local M = {}
---Apply the given configuration to the rainbow-delimiter settings. Will
---overwrite existing settings.
---
---@param opts rainbow_delimiters.config Settings, same format as `vim.g.rainbow_delimiters`
function M.setup(opts)
vim.g.rainbow_delimiters = opts
end
-- Make it possible to call the module directly; for backwards compatibility
-- with a previous version of this module.
setmetatable(M, {__call = function(_t, opts) M.setup(opts) end})
return M
-- vim:tw=79:ts=4:sw=4:noet:

View File

@ -0,0 +1,124 @@
--[[
Copyright 2023 Alejandro "HiPhish" Sanchez
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--]]
---Helper library for stack-like tables.
local M = {}
---@class (exact) Stack
---@field public size fun(self: Stack): integer
---@field public peek fun(self: Stack): any
---@field public push fun(self: Stack, item: any): Stack
---@field public pop fun(self: Stack): any
---@field public iter fun(self: Stack): ((fun(i: integer, item: any): integer?, any), Stack, integer)
---@field package content any[]
---The stack metatable.
local mt = {}
---The actual iterator implementation, hidden behind the iter-method.
---@param stack Stack
---@param i integer
---@return integer?
---@return any
local function iter_stack(stack, i)
if i <= 1 then return end
return i - 1, stack.content[i - 1]
end
---@param stack Stack
---@return string
local function stack_tostring(stack)
local items = {}
for _, item in ipairs(stack.content) do
items[#items + 1] = tostring(item)
end
return string.format('[%s]', table.concat(items, ', '))
end
---[ Methods ]-----------------------------------------------------------------
---Returns the current number of items in the stack.
---@param self Stack
---@return integer size Current size of the stack
local function size(self)
return #self.content
end
---Iterate through the content of the stack from top to bottom. Each iteration
---returns the current index (one-based, counting from the bottom) and the
---current item.
---@param self Stack The stack instance
---@return fun(i: integer, stack: Stack): integer?, any
---@return Stack
---@return integer
local function iter(self)
return iter_stack, self, self:size() + 1
end
---Add a new item to the top of the stack. Modifies the stack in-place.
---@param item any The item to push onto the stack
---@return Stack stack The stack.
local function push(self, item)
self.content[self:size() + 1] = item
return self
end
---Returns the topmost item of the stack without altering the stack.
---@return any top The top-most item.
local function peek(self)
local result = self.content[self:size()]
return result
end
---Returns the topmost item of the stack and removes it from the stack.
---@return any top The top-most item.
local function pop(self)
local n = self:size()
local result = self.content[n]
self.content[n] = nil
return result
end
---[ Public module interface ]-------------------------------------------------
---Instantiates a new stack containing the given items, or the empty stack if
---the argument is `nil`.
---@param items any[]? Array of items in order from bottom to top
---@return Stack stack The new stack instance
function M.new(items)
---@type Stack
local result = {
content = {},
size = size,
iter = iter,
push = push,
pop = pop,
peek = peek,
}
setmetatable(result, mt)
for _, item in ipairs(items or {}) do result:push(item) end
return result
end
---[ Metamethods ]-------------------------------------------------------------
mt.__tostring = stack_tostring
return M
-- vim:tw=79:ts=4:sw=4:noet:

View File

@ -0,0 +1,75 @@
--[[
Copyright 2023 Alejandro "HiPhish" Sanchez
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--]]
---Strategy decorator which makes your delimiters change colours like Christmas
---lights. This module is meant as a joke and will not be loaded by default
---with the rest of the plugin.
local M = {}
local uv = vim.loop
local lib = require 'rainbow-delimiters.lib'
local original_hlgroup_at = lib.hlgroup_at
local counter = 0
---Wrapper around the original function which applies some offset to the index.
---@param i integer
---@return string hlgroup
local function patched_hlgroup_at(i)
return original_hlgroup_at(counter + i)
end
---Wraps the given strategy with a new strategy that switches colours like a
---chain of Christmas lights.
---@param strategy rainbow_delimiters.strategy The original strategy
---@param delay integer? Time between switches in milliseconds (default 500)
---@return rainbow_delimiters.strategy christmas_lights A new strategy object
function M.lights(strategy, delay)
delay = delay or 500
local timer = uv.new_timer()
---@param bufnr integer
---@param settings rainbow_delimiters.buffer_settings
local function on_attach(bufnr, settings)
local function blink()
counter = counter + 1
local function callback()
lib.hlgroup_at = patched_hlgroup_at
strategy.on_reset(bufnr, lib.buffers[bufnr])
lib.hlgroup_at = original_hlgroup_at
end
vim.schedule(callback)
end
timer:start(0, delay, blink)
strategy.on_attach(bufnr, settings)
end
---@param bufnr integer
local function on_detach(bufnr)
timer:stop()
strategy.on_detach(bufnr)
end
return {
strategy = strategy,
on_attach = on_attach,
on_detach = on_detach,
on_reset = strategy.on_reset,
}
end
return M

View File

@ -0,0 +1,300 @@
--[[
Copyright 2023 Alejandro "HiPhish" Sanchez
Copyright 2020-2022 Chinmay Dalal
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--]]
local Stack = require 'rainbow-delimiters.stack'
local lib = require 'rainbow-delimiters.lib'
local util = require 'rainbow-delimiters.util'
local log = require 'rainbow-delimiters.log'
---Strategy which highlights the entire buffer.
local M = {}
---Changes are range objects and come in two variants: one with four entries and
---one with six entries. We only want the four-entry variant. See
---`:h TSNode:range()`
---@param change integer[]
---@return integer[]
local function normalize_change(change)
local result
if #change == 4 then
result = change
elseif #change == 6 then
result = {change[1], change[2], change[4], change[5]}
else
result = {}
end
return result
end
---@param bufnr integer
---@param lang string
---@param matches Stack
---@param level integer
local function highlight_matches(bufnr, lang, matches, level)
local hlgroup = lib.hlgroup_at(level)
for _, match in matches:iter() do
for _, delimiter in match.delimiter:iter() do lib.highlight(bufnr, lang, delimiter, hlgroup) end
highlight_matches(bufnr, lang, match.children, level + 1)
end
end
---Create a new empty match_record
---@return table
local function new_match_record()
return {
delimiter = Stack.new(),
children = Stack.new(),
}
end
---Update highlights for a range. Called every time text is changed.
---@param bufnr integer Buffer number
---@param changes table List of node ranges in which the changes occurred
---@param tree TSTree TS tree
---@param lang string Language
local function update_range(bufnr, changes, tree, lang)
log.debug('Updated range with changes %s', vim.inspect(changes))
if not lib.enabled_for(lang) then return end
if vim.fn.pumvisible() ~= 0 or not lang then return end
local query = lib.get_query(lang, bufnr)
if not query then return end
local matches = Stack.new()
for _, change in ipairs(changes) do
-- This is the match record, it lists all the relevant nodes from
-- each match.
---@type table?
local match_record
local root_node = tree:root()
local start_row, end_row = change[1], change[3] + 1
lib.clear_namespace(bufnr, lang, start_row, end_row)
for qid, node, _ in query:iter_captures(root_node, bufnr, start_row, end_row) do
local name = query.captures[qid]
-- check for 'delimiter' first, since that should be the most
-- common name
if name == 'delimiter' and match_record then
match_record.delimiter:push(node)
elseif name == 'container' and not match_record then
match_record = new_match_record()
elseif name == 'container' then
-- temporarily push the match_record to matches to be retrieved
-- later, since we haven't closed it yet
matches:push(match_record)
match_record = new_match_record()
-- since we didn't close the previous match_record, it must
-- mean that the current match_record has it as an ancestor
match_record.has_ancestor = true
elseif name == 'sentinel' and match_record then
-- if we see the sentinel, then we are done with the current
-- container
if match_record.has_ancestor then
local prev_match_record = matches:pop()
if prev_match_record then
-- since we have an ancestor, it has to be the last
-- element of the stack
prev_match_record.children:push(match_record)
match_record = prev_match_record
else
-- since match_record.has_ancestor was true, we shouldn't
-- be able to get to here unless something went wrong
-- with the queries or treesitter itself
log.error([[You are missing a @container,
which should be impossible!
Please double check the queries.]])
end
else
-- if match_record doesn't have an ancestor, the sentinel
-- means that we are done with it
matches:push(match_record)
match_record = nil
end
elseif (name == 'delimiter' or name == 'sentinel') and not match_record then
log.error([[You query got the capture name %s.
But it didn't come with a container, which should be impossible!
Please double check your queries.]], name)
end -- do nothing with other capture names
end
if match_record then
-- we might have a dangling match_record, so we push it back into
-- matches
-- this should only happen when the query is on a proper subset
-- of the full tree (usually just one line)
matches:push(match_record)
end
end
-- when we capture on a row and not the full tree, we get the previous
-- containers (on earlier rows) included in the above, but not the
-- delimiters and sentinels from them, so we push them up as long as
-- we know they have an ancestor
local last_match = matches:pop()
while last_match and last_match.has_ancestor do
local prev_match = matches:pop()
if prev_match then
prev_match.children:push(last_match)
else
log.error('You are in what should be an unreachable position.')
end
last_match = prev_match
end
matches:push(last_match)
highlight_matches(bufnr, lang, matches, 1)
end
---Update highlights for every tree in given buffer.
---@param bufnr integer # Buffer number
---@param parser LanguageTree
local function full_update(bufnr, parser)
log.debug('Performing full updated on buffer %d', bufnr)
local function callback(tree, sub_parser)
local changes = {{tree:root():range()}}
update_range(bufnr, changes, tree, sub_parser:lang())
end
parser:for_each_tree(callback)
end
---Sets up all the callbacks and performs an initial highlighting
---@param bufnr integer # Buffer number
---@param parser LanguageTree
---@param start_parent_lang string? # Parent language or nil
local function setup_parser(bufnr, parser, start_parent_lang)
log.debug('Setting up parser for buffer %d', bufnr)
util.for_each_child(start_parent_lang, parser:lang(), parser, function(p, lang, parent_lang)
log.debug("Setting up parser for '%s' in buffer %d", lang, bufnr)
-- Skip languages which are not supported, otherwise we get a
-- nil-reference error
if not lib.get_query(lang, bufnr) then return end
p:register_cbs {
---@param changes table
---@param tree TSTree
on_changedtree = function(changes, tree)
log.trace('Changed tree in buffer %d with languages %s', bufnr, lang)
-- HACK: As of Neovim v0.9.1 there is no way of unregistering a
-- callback, so we use this check to abort
if not lib.buffers[bufnr] then return end
-- HACK: changes can accidentally overwrite highlighting in injected code
-- blocks.
if not parent_lang then
-- If we have no parent language, then we use changes, otherwise we use the
-- whole tree's range.
-- Normalize the changes object if we have no parent language (the one we
-- get from on_changedtree)
changes = vim.tbl_map(normalize_change, changes)
elseif parent_lang ~= lang and changes[1] then
-- We have a parent language, so we are in an injected language code
-- block, thus we update all of the current code block
changes = {{tree:root():range()}}
else
-- some languages (like rust) use injections of the language itself for
-- certain functionality (e.g., macros in rust). For these the
-- highlighting will be updated by the non-injected language part of the
-- code.
changes = {}
end
-- If a line has been moved from another region it will still carry with it
-- the extmarks from the old region. We need to clear all extmarks which
-- do not belong to the current language
for _, change in ipairs(changes) do
for key, nsid in pairs(lib.nsids) do
if key ~= lang then
-- HACK: changes in the main language sometimes need to overwrite
-- highlighting on one more line
local line_end = change[3] + (parent_lang and 0 or 1)
vim.api.nvim_buf_clear_namespace(bufnr, nsid, change[1], line_end)
end
end
end
-- only update highlighting if we have changes
if changes[1] then
update_range(bufnr, changes, tree, lang)
end
-- HACK: Since we update the whole tree when we have a parent
-- language, we need to make sure to then update all children
-- too, even if there is no change in them. This shouldn't
-- affect performance, since it only affects code nested at
-- least 2 injection languages deep.
if parent_lang then
local children = p:children()
for child_lang, child in pairs(children) do
if lang == child_lang then return end
child:for_each_tree(function(child_tree, child_p)
local child_changes = {{child_tree:root():range()}}
-- we don't need to remove old extmarks, since
-- the above code will handle that correctly
-- already, but we might have accidentally
-- removed extmarks that we need to set again
update_range(bufnr, child_changes, child_tree, child_p:lang())
end)
end
end
end,
-- New languages can be added into the text at some later time, e.g.
-- code snippets in Markdown
---@param child LanguageTree
on_child_added = function(child)
setup_parser(bufnr, child, lang)
end,
}
log.trace("Done with setting up parser for '%s' in buffer %d", lang, bufnr)
end)
full_update(bufnr, parser)
end
---on_attach implementation for the global strategy
---@param bufnr integer
---@param settings rainbow_delimiters.buffer_settings
function M.on_attach(bufnr, settings)
log.trace('global strategy on_attach')
local parser = settings.parser
setup_parser(bufnr, parser, nil)
end
---on_detach implementation for the global strategy
---@param _bufnr integer
function M.on_detach(_bufnr)
end
---on_reset implementation for the global strategy
---@param bufnr integer
---@param settings rainbow_delimiters.buffer_settings
function M.on_reset(bufnr, settings)
log.trace('global strategy on_reset')
full_update(bufnr, settings.parser)
end
return M --[[@as rainbow_delimiters.strategy]]
-- vim:tw=79:ts=4:sw=4:noet:

View File

@ -0,0 +1,338 @@
--[[
Copyright 2023 Alejandro "HiPhish" Sanchez
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--]]
local Stack = require 'rainbow-delimiters.stack'
local lib = require 'rainbow-delimiters.lib'
local log = require 'rainbow-delimiters.log'
local util = require 'rainbow-delimiters.util'
local api = vim.api
local ts = vim.treesitter
---Highlight strategy which highlights the sub-tree of the buffer which
---contains the cursor. Re-computes -highlights when the buffer contents change
---or when the cursor is moved.
local M = {}
-- Implementation note: This strategy uses a two-step process: on every change
-- to the document tree we compute the match tree and cache it, then when the
-- cursor moves we use the cached match tree and the current cursor position to
-- decide which matches to highlight.
--
-- The document tree changes rarely, so there is no need to re-compute the
-- match tree every time the cursor moves.
---Cache of match trees, maps a buffer number to its match tree. We compute
---the match tree on every change, so that when the cursor moves without
---changing the tree we don't need to re-compute it.
---
---Each match tree maps a language and TS Tree to the corresponding match tree.
---We need TS Tree because there might be multiple trees per buffer, such as a
---Markdown buffer which contains multiple code blocks.
local match_trees = {}
---Reusable autogroup for events in this strategy.
---@type integer
local augroup = api.nvim_create_augroup('TSRainbowLocalCursor', {})
---Highlights a single match with the given highlight group
---@param bufnr integer
---@param lang string
---@param match table
---@param hlgroup string
local function highlight_match(bufnr, lang, match, hlgroup)
for _, delimiter in match.delimiter:iter() do lib.highlight(bufnr, lang, delimiter, hlgroup) end
end
---Highlights all matches and their children on the stack of matches. All
---matches must be on the same level of the match tree.
---
---@param bufnr integer Number of the buffer
---@param matches Stack Stack of matches
---@param level integer Level of the matches
local function highlight_matches(bufnr, lang, matches, level)
local hlgroup = lib.hlgroup_at(level)
for _, match in matches:iter() do
highlight_match(bufnr, lang, match, hlgroup)
highlight_matches(bufnr, lang, match.children, level + 1)
end
end
---Finds a match (and its level) in the match tree whose container node is the
---given container node.
---@param matches Stack
---@param container TSNode
---@param level integer
---@return table
---@return integer
---If no match is found, return nil.
---@overload fun(matches: Stack, container: TSNode, level: integer)
local function find_container(matches, container, level)
for _, match in matches:iter() do
if match.container == container then return match, level end
local result, final_level = find_container(match.children, container, level + 1)
if result then return result, final_level end
end
end
--- Create a new empty match_record with an optionally set container
---@param container TSNode
---@return table
local function new_match_record(container)
return {
container = container,
delimiter = Stack.new(),
children = Stack.new(),
}
end
---Assembles the match tree, usually called after the document tree has
---changed.
---@param bufnr integer Buffer number
---@param changes table List of node ranges in which the changes occurred
---@param tree TSTree TS tree
---@param lang string Language
---@return Stack?
local function build_match_tree(bufnr, changes, tree, lang)
if not lib.enabled_for(lang) then return end
local query = lib.get_query(lang, bufnr)
if not query then return end
local matches = Stack.new()
for _, change in ipairs(changes) do
-- This is the match record, it lists all the relevant nodes from
-- each match.
---@type table?
local match_record
local root_node = tree:root()
local start_row, end_row = change[1], change[3] + 1
lib.clear_namespace(bufnr, lang, start_row, end_row)
for qid, node, _ in query:iter_captures(root_node, bufnr, start_row, end_row) do
local name = query.captures[qid]
-- check for 'delimiter' first, since that should be the most
-- common name
if name == 'delimiter' and match_record then
match_record.delimiter:push(node)
elseif name == 'container' and not match_record then
match_record = new_match_record(node)
elseif name == 'container' then
local prev_match_record = match_record
-- temporarily push the match_record to matches to be retrieved
-- later, since we haven't closed it yet
matches:push(match_record)
match_record = new_match_record(node)
-- since we didn't close the previous match_record, it must
-- mean that the current match_record has it as an ancestor
match_record.ancestor = prev_match_record
elseif name == 'sentinel' and match_record then
-- if we see the sentinel, then we are done with the current
-- container
if match_record.ancestor then
local prev_match_record = matches:pop()
if prev_match_record then
-- since we have an ancestor, it has to be the last
-- element of the stack
prev_match_record.children:push(match_record)
match_record = prev_match_record
else
-- since match_record.has_ancestor was true, we shouldn't
-- be able to get to here unless something went wrong
-- with the queries or treesitter itself
log.error([[You are missing a @container,
which should be impossible!
Please double check the queries.]])
end
else
-- if match_record doesn't have an ancestor, the sentinel
-- means that we are done with it
matches:push(match_record)
match_record = nil
end
elseif (name == 'delimiter' or name == 'sentinel') and not match_record then
log.error([[You query got the capture name: %s.
But it didn't come with a container, which should be impossible!
Please double check your queries.]], name)
end -- do nothing with other capture names
end
end
return matches
end
---@param bufnr integer
---@param tree TSTree
---@param lang string
local function update_local(bufnr, tree, lang)
if not lib.enabled_for(lang) then return end
local query = lib.get_query(lang, bufnr)
if not query then return end
-- Find the lowest container node which contains the cursor
local cursor_container
do
local curpos = api.nvim_win_get_cursor(0)
-- The order of traversal guarantees that the first match which
-- contains the cursor is also the lowest one.
for _, match in query:iter_matches(tree:root(), bufnr, 0, -1) do
if cursor_container then break end
for id, node in pairs(match) do
local name = query.captures[id]
if name == 'container' and ts.is_in_node_range(node, curpos[1] - 1, curpos[2]) then
cursor_container = node
break
end
end
end
end
if not cursor_container then return end
local matches_lang = match_trees[bufnr][lang]
if not matches_lang then
log.debug("Did not build any matches Stack for language '%s'", lang)
return
end
local matches = matches_lang[tree]
if not matches then
-- Note: vim.inspect(tree:root():range()) errors, so we need
-- to make it into a table instead of a list of numbers
log.debug("Did not build any matches Stack for tree '%s'", vim.inspect({tree:root():range()}))
return
end
-- Find the correct container in the match tree
local cursor_match, level = find_container(matches, cursor_container, 1)
if not cursor_match then return end
-- Highlight the container match and everything below
highlight_matches(bufnr, lang, Stack.new {cursor_match}, level)
-- Starting with the cursor match travel up and highlight every ancestor as
-- well
local ancestor = cursor_match.ancestor
level = level - 1
while ancestor do
highlight_match(bufnr, lang, ancestor, lib.hlgroup_at(level))
ancestor, level = ancestor.ancestor, level - 1
end
end
---Callback function to re-highlight the buffer according to the current cursor
---position.
---@param bufnr integer
---@param parser LanguageTree
local function local_rainbow(bufnr, parser)
parser:for_each_tree(function(tree, sub_parser)
update_local(bufnr, tree, sub_parser:lang())
end)
end
---Sets up all the callbacks and performs an initial highlighting
---@param bufnr integer # Buffer number
---@param parser LanguageTree
local function setup_parser(bufnr, parser)
log.debug('Setting up parser for buffer %d', bufnr)
util.for_each_child(nil, parser:lang(), parser, function(p, lang, _parent_lang)
log.debug("Setting up parser for '%s' in buffer %d", lang, bufnr)
-- Skip languages which are not supported, otherwise we get a
-- nil-reference error
if not lib.get_query(lang, bufnr) then return end
p:register_cbs {
---@param _changes table
---@param tree TSTree
on_changedtree = function(_changes, tree)
-- HACK: As of Neovim v0.9.1 there is no way of unregistering a
-- callback, so we use this check to abort
if not lib.buffers[bufnr] then return end
if vim.fn.pumvisible() ~= 0 then return end
-- Ideally we would only rebuild the parts of the tree that have changed,
-- but this doesn't work, so we will rebuild the entire tree
-- instead.
local fake_changes = {
{tree:root():range()}
}
match_trees[bufnr][lang] = match_trees[bufnr][lang] or {}
match_trees[bufnr][lang][tree] = build_match_tree(bufnr, fake_changes, tree, lang)
-- Re-highlight after the change
local_rainbow(bufnr, p)
end,
-- New languages can be added into the text at some later time, e.g.
-- code snippets in Markdown
---@param child LanguageTree
on_child_added = function(child)
setup_parser(bufnr, child)
end,
}
log.trace("Done with setting up parser for '%s' in buffer %d", lang, bufnr)
end)
end
---on_attach implementation for the local strategy
---@param bufnr integer
---@param settings rainbow_delimiters.buffer_settings
function M.on_attach(bufnr, settings)
local parser = settings.parser
setup_parser(bufnr, parser)
api.nvim_create_autocmd('CursorMoved', {
group = augroup,
buffer = bufnr,
callback = function(args)
util.for_each_child(nil, parser:lang(), parser, function(_, lang, _)
lib.clear_namespace(bufnr, lang)
end)
local_rainbow(args.buf, parser)
end
})
-- Build up the initial match tree
match_trees[bufnr] = {}
parser:for_each_tree(function(tree, sub_parser)
local sub_lang = sub_parser:lang()
local changes = {
{tree:root():range()}
}
match_trees[bufnr][sub_lang] = match_trees[bufnr][sub_lang] or {}
match_trees[bufnr][sub_lang][tree] = build_match_tree(bufnr, changes, tree, sub_lang)
end)
local_rainbow(bufnr, parser)
end
---on_detach implementation for the local strategy
---@param bufnr integer
function M.on_detach(bufnr)
-- Uninstall autocommand and delete cached match tree
api.nvim_clear_autocmds {
buffer = bufnr,
group = augroup,
}
match_trees[bufnr] = nil
end
---on_reset implementation for the local strategy
---@param bufnr integer
---@param settings rainbow_delimiters.buffer_settings
function M.on_reset(bufnr, settings)
local parser = settings.parser
local_rainbow(bufnr, parser)
end
return M --[[@as rainbow_delimiters.strategy]]
-- vim:tw=79:ts=4:sw=4:noet:

View File

@ -0,0 +1,39 @@
--[[
Copyright 2023 Alejandro "HiPhish" Sanchez
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--]]
---A dummy strategy which does nothing; can be used in testing.
local M = {}
---on_attach implementation for the noop strategy
---@param _bufnr integer
---@param _settings rainbow_delimiters.buffer_settings
M.on_attach = function(_bufnr, _settings)
end
---on_detach implementation for the noop strategy
---@param _bufnr integer
M.on_detach = function(_bufnr)
end
---on_reset implementation for the noop strategy
---@param _bufnr integer
---@param _settings rainbow_delimiters.buffer_settings
M.on_reset = function(_bufnr, _settings)
end
return M --[[@as rainbow_delimiters.strategy]]
-- vim:tw=79:ts=4:sw=4:noet:

View File

@ -0,0 +1,51 @@
--[[
Copyright 2023 Alejandro "HiPhish" Sanchez
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--]]
---A strategy decorator; the decorated strategy keeps track of all currently
---attached buffers. Thew new strategy has the following fields:
---
--- - strategy The wrapped strategy object
--- - buffers Table mapping of buffer number to buffer settings
--- - attachments Number of currently active attachments
---
---@param strategy table
local function track(strategy)
local buffers = {}
local attachments = {0} -- Table because I want to pass it by reference
return {
strategy = strategy,
buffers = buffers,
attachments = attachments,
on_attach = function(bufnr, settings, ...)
buffers[bufnr] = settings
attachments[1] = attachments[1] + 1
strategy.on_attach(bufnr, settings, ...)
end,
on_detach = function(bufnr, ...)
buffers[bufnr] = nil
attachments[1] = attachments[1] - 1
strategy.on_detach(bufnr, ...)
end,
on_reset = function(...)
strategy.on_reset(...)
end,
}
end
return track
-- vim:tw=79:ts=4:sw=4:noet:

View File

@ -0,0 +1,42 @@
--[[
Copyright 2023 Alejandro "HiPhish" Sanchez
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--]]
---Internal helper functions. This module will probably be removed when I no
---longer need the helpers.
local M = {}
---Similar to the function `LanguageTree:for_each_child` which has been
---deprecated. Applies the thunk to the language tree and each of its
---descendants recursively.
---
---See also https://github.com/neovim/neovim/pull/25154 for a better
---replacement.
---@param parent_lang string? # Parent language or nil
---@param lang string
---@param language_tree LanguageTree
---@param thunk fun(p: LanguageTree, lang: string, parent_lang: string?)
function M.for_each_child(parent_lang, lang, language_tree, thunk)
thunk(language_tree, lang, parent_lang)
local children = language_tree:children()
for child_lang, child in pairs(children) do
M.for_each_child(lang, child_lang, child, thunk)
end
end
return M
-- vim:tw=79:ts=4:sw=4:noet: