aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLewis Russell <lewis6991@gmail.com>2025-03-01 15:51:09 +0000
committerGitHub <noreply@github.com>2025-03-01 15:51:09 +0000
commitec8922978eb14fe62671628bb25215743712eac0 (patch)
treed47554beaef78ec4f0ca988adc48f3e2336f38c2
parent48e6147e64b819d2a4130697bb348dcd368a4391 (diff)
downloadrneovim-ec8922978eb14fe62671628bb25215743712eac0.tar.gz
rneovim-ec8922978eb14fe62671628bb25215743712eac0.tar.bz2
rneovim-ec8922978eb14fe62671628bb25215743712eac0.zip
feat(treesitter): add more metadata to `language.inspect()` (#32657)
Problem: No way to check the version of a treesitter parser. Solution: Add version metadata (ABI 15 parsers only) as well as parser state count and supertype information (ABI 15) in `vim.treesitter.language.inspect()`. Also graduate the `abi_version` field, as this is now the official upstream name. --------- Co-authored-by: Christian Clason <c.clason@uni-graz.at>
-rw-r--r--runtime/doc/news.txt2
-rw-r--r--runtime/doc/treesitter.txt9
-rw-r--r--runtime/lua/vim/treesitter/_meta/misc.lua7
-rw-r--r--runtime/lua/vim/treesitter/health.lua2
-rw-r--r--runtime/lua/vim/treesitter/language.lua8
-rw-r--r--src/nvim/lua/treesitter.c105
-rw-r--r--test/functional/treesitter/language_spec.lua9
7 files changed, 105 insertions, 37 deletions
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 0d47c7adb9..b9c1e55305 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -409,6 +409,8 @@ TREESITTER
• |:InspectTree| now shows which nodes are missing.
• Bundled markdown highlight queries use `conceal_lines` metadata to conceal
code block fence lines vertically.
+• |vim.treesitter.language.inspect()| shows additional information, including
+ parser version for ABI 15 parsers.
TUI
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index 8c63f0b5db..5916ec9371 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -1192,14 +1192,17 @@ get_lang({filetype}) *vim.treesitter.language.get_lang()*
inspect({lang}) *vim.treesitter.language.inspect()*
Inspects the provided language.
- Inspecting provides some useful information on the language like node and
- field names, ABI version, and whether the language came from a WASM
- module.
+ Inspecting provides some useful information on the language like ABI
+ version, parser state count (a measure of parser complexity), node and
+ field names, and whether the language came from a WASM module.
Node names are returned in a table mapping each node name to a `boolean`
indicating whether or not the node is named (i.e., not anonymous).
Anonymous nodes are surrounded with double quotes (`"`).
+ For ABI 15 parsers, also show parser metadata (major, minor, patch
+ version) and a table of supertypes with their respective subtypes.
+
Parameters: ~
• {lang} (`string`) Language
diff --git a/runtime/lua/vim/treesitter/_meta/misc.lua b/runtime/lua/vim/treesitter/_meta/misc.lua
index c532257f49..99267bb36e 100644
--- a/runtime/lua/vim/treesitter/_meta/misc.lua
+++ b/runtime/lua/vim/treesitter/_meta/misc.lua
@@ -22,10 +22,15 @@ error('Cannot require a meta file')
---@field patterns table<integer, (integer|string)[][]>
---
---@class TSLangInfo
+---@field abi_version integer
+---@field major_version? integer
+---@field minor_version? integer
+---@field patch_version? integer
+---@field state_count integer
---@field fields string[]
---@field symbols table<string,boolean>
+---@field supertypes table<string,string[]>
---@field _wasm boolean
----@field _abi_version integer
--- @param lang string
--- @return TSLangInfo
diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua
index 53b64d1dec..0c9e210fc1 100644
--- a/runtime/lua/vim/treesitter/health.lua
+++ b/runtime/lua/vim/treesitter/health.lua
@@ -35,7 +35,7 @@ function M.check()
else
local lang = ts.language.inspect(parsername)
health.ok(
- string.format('Parser: %-20s ABI: %d, path: %s', parsername, lang._abi_version, parser)
+ string.format('Parser: %-20s ABI: %d, path: %s', parsername, lang.abi_version, parser)
)
end
end
diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua
index 38d309a102..f70f99421c 100644
--- a/runtime/lua/vim/treesitter/language.lua
+++ b/runtime/lua/vim/treesitter/language.lua
@@ -168,13 +168,17 @@ end
--- Inspects the provided language.
---
---- Inspecting provides some useful information on the language like node and field names, ABI
---- version, and whether the language came from a WASM module.
+--- Inspecting provides some useful information on the language like ABI version, parser state count
+--- (a measure of parser complexity), node and field names, and whether the language came from a
+--- WASM module.
---
--- Node names are returned in a table mapping each node name to a `boolean` indicating whether or
--- not the node is named (i.e., not anonymous). Anonymous nodes are surrounded with double quotes
--- (`"`).
---
+--- For ABI 15 parsers, also show parser metadata (major, minor, patch version) and a table of
+--- supertypes with their respective subtypes.
+---
---@param lang string Language
---@return TSLangInfo
function M.inspect(lang)
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index 259f2d4739..fa5cf1118d 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -263,45 +263,92 @@ int tslua_inspect_lang(lua_State *L)
lua_createtable(L, 0, 2); // [retval]
- uint32_t nsymbols = ts_language_symbol_count(lang);
- assert(nsymbols < INT_MAX);
-
- lua_createtable(L, (int)(nsymbols - 1), 1); // [retval, symbols]
- for (uint32_t i = 0; i < nsymbols; i++) {
- TSSymbolType t = ts_language_symbol_type(lang, (TSSymbol)i);
- if (t == TSSymbolTypeAuxiliary) {
- // not used by the API
- continue;
- }
- const char *name = ts_language_symbol_name(lang, (TSSymbol)i);
- bool named = t != TSSymbolTypeAnonymous;
- lua_pushboolean(L, named); // [retval, symbols, is_named]
- if (!named) {
- char buf[256];
- snprintf(buf, sizeof(buf), "\"%s\"", name);
- lua_setfield(L, -2, buf); // [retval, symbols]
- } else {
- lua_setfield(L, -2, name); // [retval, symbols]
+ { // Symbols
+ uint32_t nsymbols = ts_language_symbol_count(lang);
+ assert(nsymbols < INT_MAX);
+
+ lua_createtable(L, (int)(nsymbols - 1), 1); // [retval, symbols]
+ for (uint32_t i = 0; i < nsymbols; i++) {
+ TSSymbolType t = ts_language_symbol_type(lang, (TSSymbol)i);
+ if (t == TSSymbolTypeAuxiliary) {
+ // not used by the API
+ continue;
+ }
+ const char *name = ts_language_symbol_name(lang, (TSSymbol)i);
+ bool named = t != TSSymbolTypeAnonymous;
+ lua_pushboolean(L, named); // [retval, symbols, is_named]
+ if (!named) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "\"%s\"", name);
+ lua_setfield(L, -2, buf); // [retval, symbols]
+ } else {
+ lua_setfield(L, -2, name); // [retval, symbols]
+ }
}
+
+ lua_setfield(L, -2, "symbols"); // [retval]
}
- lua_setfield(L, -2, "symbols"); // [retval]
+ { // Fields
+ uint32_t nfields = ts_language_field_count(lang);
+ lua_createtable(L, (int)nfields, 1); // [retval, fields]
+ // Field IDs go from 1 to nfields inclusive (extra index 0 maps to NULL)
+ for (uint32_t i = 1; i <= nfields; i++) {
+ lua_pushstring(L, ts_language_field_name_for_id(lang, (TSFieldId)i));
+ lua_rawseti(L, -2, (int)i); // [retval, fields]
+ }
- uint32_t nfields = ts_language_field_count(lang);
- lua_createtable(L, (int)nfields, 1); // [retval, fields]
- // Field IDs go from 1 to nfields inclusive (extra index 0 maps to NULL)
- for (uint32_t i = 1; i <= nfields; i++) {
- lua_pushstring(L, ts_language_field_name_for_id(lang, (TSFieldId)i));
- lua_rawseti(L, -2, (int)i); // [retval, fields]
+ lua_setfield(L, -2, "fields"); // [retval]
}
- lua_setfield(L, -2, "fields"); // [retval]
-
lua_pushboolean(L, ts_language_is_wasm(lang));
lua_setfield(L, -2, "_wasm");
lua_pushinteger(L, ts_language_abi_version(lang)); // [retval, version]
- lua_setfield(L, -2, "_abi_version");
+ lua_setfield(L, -2, "abi_version");
+
+ { // Metadata
+ const TSLanguageMetadata *meta = ts_language_metadata(lang);
+
+ if (meta != NULL) {
+ lua_createtable(L, 0, 3);
+
+ lua_pushinteger(L, meta->major_version);
+ lua_setfield(L, -2, "major_version");
+ lua_pushinteger(L, meta->minor_version);
+ lua_setfield(L, -2, "minor_version");
+ lua_pushinteger(L, meta->patch_version);
+ lua_setfield(L, -2, "patch_version");
+
+ lua_setfield(L, -2, "metadata");
+ }
+ }
+
+ lua_pushinteger(L, ts_language_state_count(lang));
+ lua_setfield(L, -2, "state_count");
+
+ { // Supertypes
+ uint32_t nsupertypes;
+ const TSSymbol *supertypes = ts_language_supertypes(lang, &nsupertypes);
+
+ lua_createtable(L, 0, (int)nsupertypes); // [retval, supertypes]
+ for (uint32_t i = 0; i < nsupertypes; i++) {
+ const TSSymbol supertype = *(supertypes + i);
+
+ uint32_t nsubtypes;
+ const TSSymbol *subtypes = ts_language_subtypes(lang, supertype, &nsubtypes);
+
+ lua_createtable(L, (int)nsubtypes, 0);
+ for (uint32_t j = 1; j <= nsubtypes; j++) {
+ lua_pushstring(L, ts_language_symbol_name(lang, *(subtypes + j)));
+ lua_rawseti(L, -2, (int)j);
+ }
+
+ lua_setfield(L, -2, ts_language_symbol_name(lang, supertype));
+ }
+
+ lua_setfield(L, -2, "supertypes"); // [retval]
+ }
return 1;
}
diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua
index a93b1063a1..e009d97210 100644
--- a/test/functional/treesitter/language_spec.lua
+++ b/test/functional/treesitter/language_spec.lua
@@ -63,7 +63,14 @@ describe('treesitter language API', function()
return { keys, lang.fields, lang.symbols }
end))
- eq({ fields = true, symbols = true, _abi_version = true, _wasm = false }, keys)
+ eq({
+ abi_version = true,
+ fields = true,
+ symbols = true,
+ state_count = true,
+ supertypes = true,
+ _wasm = false,
+ }, keys)
local fset = {}
for _, f in pairs(fields) do