neovimtreesitter

How to get the TSNode from a TreeSitter query storing captures in metadata in neovim?


I want to get the signature of all the function definitions/declarations in a lua file and put them in a buffer.

Reading through the TreeSitter query syntax and neovim Treesitter help I came up with this query, also tested with :EditQuery and :InspectTree and it looks correct:


(
 (function_declaration name: (identifier) @fn_name parameters: (_) @fn_params) @fn_decl

 (#set! @fn_decl "name" @fn_name)
 (#set! @fn_decl "parameters" @fn_params)
)

(
 (
    assignment_statement
     (variable_list name: (identifier) @fn_name)
     (expression_list value: (function_definition parameters: (_) @fn_params))
 ) @fn_assign

 (#set! @fn_assign "name" @fn_name)
 (#set! @fn_assign "parameters" @fn_params)
)

Saved in ~/.config/nvim/after/queries/lua/code_layout.scm

The idea is to find the nodes matching the structure I want and then store sub-nodes within them in the metadata of the match so that I can retrieve them later in the code and get the text of the entire function signature.

I'm testing this out on a lua file (the code at the end is the actual logic, just put it there for convenience so I can select and run it with :'<,'>lua)

F = function()
end

local function some_local(param1, param2)
    return param1
end

-- test code starts here

local query = vim.treesitter.query.get('lua', 'code_layout')
if query == nil then return end
local tree = vim.treesitter.get_parser():parse()[1]
local bufnr = vim.api.nvim_get_current_buf()
for _, matches, metadata in query:iter_matches(tree:root(), bufnr, 0, -1, { all = true }) do
    for _, node_metadata in pairs(metadata) do
        local fn_name_node = matches[node_metadata['name']]
        local fn_params_node = matches[node_metadata['parameters']]
        print('metadata: ' .. vim.inspect(node_metadata))
        print('fn_name_node: ' .. vim.inspect(fn_name_node))
        print('fn_params_node: ' .. vim.inspect(fn_params_node))
        print('fn_name_node: ' .. vim.inspect(vim.treesitter.get_node_text(fn_name_node, bufnr)))
        print('fn_params_node: ' .. vim.inspect(vim.treesitter.get_node_text(fn_params_node, bufnr)))
    end
end

But when I run that test code I'm gettin the following error:

metadata: {                                                                                                                      
  name = 1,                                                                                                                      
  parameters = 2                                                                                                                 
}                                                                                                                                
fn_name_node: { <userdata 1> }                                                                                                   
fn_params_node: { <userdata 1> }                                                                                                 
Error detected while processing :{range}lua:                                                                                     
E5108: Error executing lua /usr/local/share/nvim/runtime/lua/vim/treesitter.lua:182: attempt to call method 'range' (a nil value)
stack traceback:                                                                                                                 
        /usr/local/share/nvim/runtime/lua/vim/treesitter.lua:182: in function 'get_range'                                        
        /usr/local/share/nvim/runtime/lua/vim/treesitter.lua:217: in function 'get_node_text'                                    
        [string ":{range}lua"]:12: in main chunk                                                                                 

To me this seems to indicate that the matches array returned by iter_matches() is not an instance of TSNode, as it should be according to the doc:

Return:
    (fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata)
    pattern id, match, metadata

Searching online I couldn't find anyone having such a problem with iter_matches, am I missing something?


Solution

  • Ok, I realized what was the problem. What I missed was that matches[node_metadata['name']] returns a list of TSNode, not just one.

    Doing matches[node_metadata['name']][1] instead works as expected.

    Amazing what taking a break can do sometimes.