feat(treesitter)!: add default fallback to ft_to_lang lookups

Problem: Language names are only registered for filetype<->language
lookups when parsers are actually loaded; this means users cannot rely
on `vim.treesitter.language.get_lang()` or `get_filetypes()` to return
the correct value when language and filetype coincide and always need to
add explicit fallbacks.

Solution: Always return the language name as valid filetype in
`get_filetypes()`, and default to the filetype in `get_lang()`. Document
this behavior.
This commit is contained in:
Christian Clason
2024-09-14 13:27:44 +02:00
parent e40314811e
commit 041d98fe8d
5 changed files with 31 additions and 17 deletions

View File

@ -98,6 +98,12 @@ TREESITTER
backwards compatibility, an option `all=false` (only return the last backwards compatibility, an option `all=false` (only return the last
matching node) is provided that will be removed in a future release. matching node) is provided that will be removed in a future release.
• |vim.treesitter.language.get_filetypes()| always includes the {language}
argument in addition to explicitly registered filetypes.
• |vim.treesitter.language.get_lang()| falls back to the {filetype} argument
if no languages are explicitly registered.
TUI TUI
• TODO • TODO

View File

@ -969,14 +969,15 @@ add({lang}, {opts}) *vim.treesitter.language.add()*
Parameters: ~ Parameters: ~
• {lang} (`string`) Name of the parser (alphanumerical and `_` only) • {lang} (`string`) Name of the parser (alphanumerical and `_` only)
• {opts} (`table?`) Options: • {opts} (`table?`) Options:
• {filetype}? (`string|string[]`, default: {lang}) Default
filetype the parser should be associated with.
• {path}? (`string`) Optional path the parser is located at • {path}? (`string`) Optional path the parser is located at
• {symbol_name}? (`string`) Internal symbol name for the • {symbol_name}? (`string`) Internal symbol name for the
language to load language to load
get_filetypes({lang}) *vim.treesitter.language.get_filetypes()* get_filetypes({lang}) *vim.treesitter.language.get_filetypes()*
Get the filetypes associated with the parser named {lang}. Returns the filetypes for which a parser named {lang} is used.
The list includes {lang} itself plus all filetypes registered via
|vim.treesitter.language.register()|.
Parameters: ~ Parameters: ~
• {lang} (`string`) Name of parser • {lang} (`string`) Name of parser
@ -985,6 +986,11 @@ get_filetypes({lang}) *vim.treesitter.language.get_filetypes()*
(`string[]`) filetypes (`string[]`) filetypes
get_lang({filetype}) *vim.treesitter.language.get_lang()* get_lang({filetype}) *vim.treesitter.language.get_lang()*
Returns the language name to be used when loading a parser for {filetype}.
If no language has been explicitly registered via
|vim.treesitter.language.register()|, default to {filetype}. For composite
filetypes like `html.glimmer`, only the main filetype is returned.
Parameters: ~ Parameters: ~
• {filetype} (`string`) • {filetype} (`string`)

View File

@ -95,7 +95,7 @@ function M.get_parser(bufnr, lang, opts)
end end
if not valid_lang(lang) then if not valid_lang(lang) then
lang = M.language.get_lang(vim.bo[bufnr].filetype) or vim.bo[bufnr].filetype lang = M.language.get_lang(vim.bo[bufnr].filetype)
end end
if not valid_lang(lang) then if not valid_lang(lang) then

View File

@ -41,7 +41,7 @@ local function guess_query_lang(buf)
local filename = api.nvim_buf_get_name(buf) local filename = api.nvim_buf_get_name(buf)
if filename ~= '' then if filename ~= '' then
local resolved_filename = vim.F.npcall(vim.fn.fnamemodify, filename, ':p:h:t') local resolved_filename = vim.F.npcall(vim.fn.fnamemodify, filename, ':p:h:t')
return resolved_filename and vim.treesitter.language.get_lang(resolved_filename) or nil return resolved_filename and vim.treesitter.language.get_lang(resolved_filename)
end end
end end

View File

@ -7,11 +7,15 @@ local ft_to_lang = {
help = 'vimdoc', help = 'vimdoc',
} }
--- Get the filetypes associated with the parser named {lang}. --- Returns the filetypes for which a parser named {lang} is used.
---
--- The list includes {lang} itself plus all filetypes registered via
--- |vim.treesitter.language.register()|.
---
--- @param lang string Name of parser --- @param lang string Name of parser
--- @return string[] filetypes --- @return string[] filetypes
function M.get_filetypes(lang) function M.get_filetypes(lang)
local r = {} ---@type string[] local r = { lang } ---@type string[]
for ft, p in pairs(ft_to_lang) do for ft, p in pairs(ft_to_lang) do
if p == lang then if p == lang then
r[#r + 1] = ft r[#r + 1] = ft
@ -20,6 +24,12 @@ function M.get_filetypes(lang)
return r return r
end end
--- Returns the language name to be used when loading a parser for {filetype}.
---
--- If no language has been explicitly registered via |vim.treesitter.language.register()|,
--- default to {filetype}. For composite filetypes like `html.glimmer`, only the main filetype is
--- returned.
---
--- @param filetype string --- @param filetype string
--- @return string|nil --- @return string|nil
function M.get_lang(filetype) function M.get_lang(filetype)
@ -29,9 +39,9 @@ function M.get_lang(filetype)
if ft_to_lang[filetype] then if ft_to_lang[filetype] then
return ft_to_lang[filetype] return ft_to_lang[filetype]
end end
-- support subfiletypes like html.glimmer -- for subfiletypes like html.glimmer use only "main" filetype
filetype = vim.split(filetype, '.', { plain = true })[1] filetype = vim.split(filetype, '.', { plain = true })[1]
return ft_to_lang[filetype] return ft_to_lang[filetype] or filetype
end end
---@deprecated ---@deprecated
@ -59,10 +69,6 @@ end
---@class vim.treesitter.language.add.Opts ---@class vim.treesitter.language.add.Opts
---@inlinedoc ---@inlinedoc
--- ---
---Default filetype the parser should be associated with.
---(Default: {lang})
---@field filetype? string|string[]
---
---Optional path the parser is located at ---Optional path the parser is located at
---@field path? string ---@field path? string
--- ---
@ -78,21 +84,18 @@ end
function M.add(lang, opts) function M.add(lang, opts)
opts = opts or {} opts = opts or {}
local path = opts.path local path = opts.path
local filetype = opts.filetype or lang
local symbol_name = opts.symbol_name local symbol_name = opts.symbol_name
vim.validate({ vim.validate({
lang = { lang, 'string' }, lang = { lang, 'string' },
path = { path, 'string', true }, path = { path, 'string', true },
symbol_name = { symbol_name, 'string', true }, symbol_name = { symbol_name, 'string', true },
filetype = { filetype, { 'string', 'table' }, true },
}) })
-- parser names are assumed to be lowercase (consistent behavior on case-insensitive file systems) -- parser names are assumed to be lowercase (consistent behavior on case-insensitive file systems)
lang = lang:lower() lang = lang:lower()
if vim._ts_has_language(lang) then if vim._ts_has_language(lang) then
M.register(lang, filetype)
return return
end end
@ -117,7 +120,6 @@ function M.add(lang, opts)
else else
vim._ts_add_language_from_object(path, lang, symbol_name) vim._ts_add_language_from_object(path, lang, symbol_name)
end end
M.register(lang, filetype)
end end
--- @param x string|string[] --- @param x string|string[]